Index: /branches/eam_branches/eam_branch_20080706/ippScripts/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/.cvsignore	(revision 22157)
@@ -0,0 +1,6 @@
+Build
+META.yml
+Makefile
+Makefile.PL
+_build
+blib
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/Build.PL
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/Build.PL	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/Build.PL	(revision 22157)
@@ -0,0 +1,79 @@
+use Module::Build;
+# See perldoc Module::Build for details of how this works
+
+Module::Build->new(
+    module_name         => 'ippscripts',
+    dist_version        => '0.01',
+    author              => 'Joshua Hoblitt <jhoblitt@cpan.org>',
+    license             => 'gpl',
+    create_makefile_pl  => 'passthrough',
+    requires            => {
+        'Astro::FITS::CFITSIO'      => 0,
+        'Cache::File'               => 0,
+        'Carp'                      => 0,
+        'Data::Dumper'              => 0,
+        'File::Basename'            => 0,
+        'File::Spec'                => 0,
+        'File::Temp'                => 0,
+        'Getopt::Long'              => 0,
+        'IPC::Cmd'                  => 0,
+        'IPC::Run'                  => 0,
+        'Math::Trig'                => 0,
+        'Pod::Usage'                => 0,
+        'PS::IPP::Config'           => '1.01',
+        'PS::IPP::Metadata::Config' => 0,
+        'Statistics::Descriptive'   => 0,
+        'Storable'                  => 0,
+        'Sys::Hostname'             => 0,
+    },
+    recommends          => {
+        'Test::Distribution'    => '1.22',
+    },
+    script_files        => [qw(
+        scripts/ipp_datapath.pl
+        scripts/ipp_filename.pl
+        scripts/mdc2list.pl
+        scripts/register_imfile.pl
+        scripts/register_exp.pl
+        scripts/detrend_process_imfile.pl
+        scripts/detrend_process_exp.pl
+        scripts/detrend_stack.pl
+        scripts/detrend_norm_calc.pl
+        scripts/detrend_norm_apply.pl
+        scripts/detrend_norm_exp.pl
+        scripts/detrend_resid_imfile.pl
+        scripts/detrend_resid_exp.pl
+        scripts/detrend_reject_exp.pl
+        scripts/chip_imfile.pl
+        scripts/camera_exp.pl
+        scripts/fake_imfile.pl
+        scripts/warp_overlap.pl
+        scripts/warp_skycell.pl
+        scripts/diff_skycell.pl
+        scripts/stack_skycell.pl
+        scripts/magic_tree.pl
+        scripts/ipp_cleanup.pl
+        scripts/ipp_inject_fileset.pl
+        scripts/ipp_inject_expname.pl
+        scripts/ipp_serial_inject.pl
+        scripts/ipp_serial_inject_split.pl
+        scripts/ipp_serial_inject_mosaic.pl
+        scripts/ipp_serial_register.pl
+        scripts/ipp_serial_detrend.pl
+        scripts/ipp_serial_chip.pl
+        scripts/ipp_serial_camera.pl
+        scripts/ipp_serial_warp.pl
+        scripts/ipp_serial_diff.pl
+        scripts/ipp_serial_stack.pl
+        scripts/ipp_serial_mops.pl
+        scripts/ipp_mops_translate.pl
+        scripts/ipp_simulation_data.pl
+        scripts/isp_trans.pl
+        scripts/ds9_cmf_regions.pl
+        scripts/ipp_detrend_combine.pl
+        scripts/summit_copy.pl
+        scripts/ipp_image_path.pl
+    )],
+    dist_abstract => 'Scripts for running the Pan-STARRS IPP',
+    dist_author => 'Paul Price <price@ifa.hawaii.edu>',
+)->create_build_script;
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/Changes
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/Changes	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/Changes	(revision 22157)
@@ -0,0 +1,6 @@
+Revision history for Perl module ippScripts
+
+0.01 Thu Feb 23 13:55:57 2006
+    - help scripts split out of the ippTools package
+
+
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/LICENSE
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/LICENSE	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/LICENSE	(revision 22157)
@@ -0,0 +1,260 @@
+The General Public License (GPL)
+Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave,
+Cambridge, MA 02139, USA. Everyone is permitted to copy and distribute
+verbatim copies of this license document, but changing it is not allowed.
+
+Preamble
+
+The licenses for most software are designed to take away your freedom to share
+and change it. By contrast, the GNU General Public License is intended to
+guarantee your freedom to share and change free software--to make sure the
+software is free for all its users. This General Public License applies to most of
+the Free Software Foundation's software and to any other program whose
+authors commit to using it. (Some other Free Software Foundation software is
+covered by the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+When we speak of free software, we are referring to freedom, not price. Our
+General Public Licenses are designed to make sure that you have the freedom
+to distribute copies of free software (and charge for this service if you wish), that
+you receive source code or can get it if you want it, that you can change the
+software or use pieces of it in new free programs; and that you know you can do
+these things.
+
+To protect your rights, we need to make restrictions that forbid anyone to deny
+you these rights or to ask you to surrender the rights. These restrictions
+translate to certain responsibilities for you if you distribute copies of the
+software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis or for a
+fee, you must give the recipients all the rights that you have. You must make
+sure that they, too, receive or can get the source code. And you must show
+them these terms so they know their rights.
+
+We protect your rights with two steps: (1) copyright the software, and (2) offer
+you this license which gives you legal permission to copy, distribute and/or
+modify the software.
+
+Also, for each author's protection and ours, we want to make certain that
+everyone understands that there is no warranty for this free software. If the
+software is modified by someone else and passed on, we want its recipients to
+know that what they have is not the original, so that any problems introduced by
+others will not reflect on the original authors' reputations.
+
+Finally, any free program is threatened constantly by software patents. We wish
+to avoid the danger that redistributors of a free program will individually obtain
+patent licenses, in effect making the program proprietary. To prevent this, we
+have made it clear that any patent must be licensed for everyone's free use or
+not licensed at all.
+
+The precise terms and conditions for copying, distribution and modification
+follow.
+
+GNU GENERAL PUBLIC LICENSE
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND
+MODIFICATION
+
+0. This License applies to any program or other work which contains a notice
+placed by the copyright holder saying it may be distributed under the terms of
+this General Public License. The "Program", below, refers to any such program
+or work, and a "work based on the Program" means either the Program or any
+derivative work under copyright law: that is to say, a work containing the
+Program or a portion of it, either verbatim or with modifications and/or translated
+into another language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not covered by
+this License; they are outside its scope. The act of running the Program is not
+restricted, and the output from the Program is covered only if its contents
+constitute a work based on the Program (independent of having been made by
+running the Program). Whether that is true depends on what the Program does.
+
+1. You may copy and distribute verbatim copies of the Program's source code as
+you receive it, in any medium, provided that you conspicuously and appropriately
+publish on each copy an appropriate copyright notice and disclaimer of warranty;
+keep intact all the notices that refer to this License and to the absence of any
+warranty; and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and you may at
+your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion of it, thus
+forming a work based on the Program, and copy and distribute such
+modifications or work under the terms of Section 1 above, provided that you also
+meet all of these conditions:
+
+a) You must cause the modified files to carry prominent notices stating that you
+changed the files and the date of any change.
+
+b) You must cause any work that you distribute or publish, that in whole or in
+part contains or is derived from the Program or any part thereof, to be licensed
+as a whole at no charge to all third parties under the terms of this License.
+
+c) If the modified program normally reads commands interactively when run, you
+must cause it, when started running for such interactive use in the most ordinary
+way, to print or display an announcement including an appropriate copyright
+notice and a notice that there is no warranty (or else, saying that you provide a
+warranty) and that users may redistribute the program under these conditions,
+and telling the user how to view a copy of this License. (Exception: if the
+Program itself is interactive but does not normally print such an announcement,
+your work based on the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If identifiable
+sections of that work are not derived from the Program, and can be reasonably
+considered independent and separate works in themselves, then this License,
+and its terms, do not apply to those sections when you distribute them as
+separate works. But when you distribute the same sections as part of a whole
+which is a work based on the Program, the distribution of the whole must be on
+the terms of this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest your rights to
+work written entirely by you; rather, the intent is to exercise the right to control
+the distribution of derivative or collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program with the
+Program (or with a work based on the Program) on a volume of a storage or
+distribution medium does not bring the other work under the scope of this
+License.
+
+3. You may copy and distribute the Program (or a work based on it, under
+Section 2) in object code or executable form under the terms of Sections 1 and 2
+above provided that you also do one of the following:
+
+a) Accompany it with the complete corresponding machine-readable source
+code, which must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange; or,
+
+b) Accompany it with a written offer, valid for at least three years, to give any
+third party, for a charge no more than your cost of physically performing source
+distribution, a complete machine-readable copy of the corresponding source
+code, to be distributed under the terms of Sections 1 and 2 above on a medium
+customarily used for software interchange; or,
+
+c) Accompany it with the information you received as to the offer to distribute
+corresponding source code. (This alternative is allowed only for noncommercial
+distribution and only if you received the program in object code or executable
+form with such an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for making
+modifications to it. For an executable work, complete source code means all the
+source code for all modules it contains, plus any associated interface definition
+files, plus the scripts used to control compilation and installation of the
+executable. However, as a special exception, the source code distributed need
+not include anything that is normally distributed (in either source or binary form)
+with the major components (compiler, kernel, and so on) of the operating system
+on which the executable runs, unless that component itself accompanies the
+executable.
+
+If distribution of executable or object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the source
+code from the same place counts as distribution of the source code, even though
+third parties are not compelled to copy the source along with the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program except as
+expressly provided under this License. Any attempt otherwise to copy, modify,
+sublicense or distribute the Program is void, and will automatically terminate
+your rights under this License. However, parties who have received copies, or
+rights, from you under this License will not have their licenses terminated so long
+as such parties remain in full compliance.
+
+5. You are not required to accept this License, since you have not signed it.
+However, nothing else grants you permission to modify or distribute the Program
+or its derivative works. These actions are prohibited by law if you do not accept
+this License. Therefore, by modifying or distributing the Program (or any work
+based on the Program), you indicate your acceptance of this License to do so,
+and all its terms and conditions for copying, distributing or modifying the
+Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the Program),
+the recipient automatically receives a license from the original licensor to copy,
+distribute or modify the Program subject to these terms and conditions. You
+may not impose any further restrictions on the recipients' exercise of the rights
+granted herein. You are not responsible for enforcing compliance by third parties
+to this License.
+
+7. If, as a consequence of a court judgment or allegation of patent infringement
+or for any other reason (not limited to patent issues), conditions are imposed on
+you (whether by court order, agreement or otherwise) that contradict the
+conditions of this License, they do not excuse you from the conditions of this
+License. If you cannot distribute so as to satisfy simultaneously your obligations
+under this License and any other pertinent obligations, then as a consequence
+you may not distribute the Program at all. For example, if a patent license would
+not permit royalty-free redistribution of the Program by all those who receive
+copies directly or indirectly through you, then the only way you could satisfy
+both it and this License would be to refrain entirely from distribution of the
+Program.
+
+If any portion of this section is held invalid or unenforceable under any particular
+circumstance, the balance of the section is intended to apply and the section as
+a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any patents or other
+property right claims or to contest validity of any such claims; this section has
+the sole purpose of protecting the integrity of the free software distribution
+system, which is implemented by public license practices. Many people have
+made generous contributions to the wide range of software distributed through
+that system in reliance on consistent application of that system; it is up to the
+author/donor to decide if he or she is willing to distribute software through any
+other system and a licensee cannot impose that choice.
+
+This section is intended to make thoroughly clear what is believed to be a
+consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in certain countries
+either by patents or by copyrighted interfaces, the original copyright holder who
+places the Program under this License may add an explicit geographical
+distribution limitation excluding those countries, so that distribution is permitted
+only in or among countries not thus excluded. In such case, this License
+incorporates the limitation as if written in the body of this License.
+
+9. The Free Software Foundation may publish revised and/or new versions of the
+General Public License from time to time. Such new versions will be similar in
+spirit to the present version, but may differ in detail to address new problems or
+concerns.
+
+Each version is given a distinguishing version number. If the Program specifies a
+version number of this License which applies to it and "any later version", you
+have the option of following the terms and conditions either of that version or of
+any later version published by the Free Software Foundation. If the Program does
+not specify a version number of this License, you may choose any version ever
+published by the Free Software Foundation.
+
+10. If you wish to incorporate parts of the Program into other free programs
+whose distribution conditions are different, write to the author to ask for
+permission. For software which is copyrighted by the Free Software Foundation,
+write to the Free Software Foundation; we sometimes make exceptions for this.
+Our decision will be guided by the two goals of preserving the free status of all
+derivatives of our free software and of promoting the sharing and reuse of
+software generally.
+
+NO WARRANTY
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS
+NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE
+COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM
+"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR
+IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
+ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE,
+YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
+CORRECTION.
+
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED
+TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY
+WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS
+PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
+ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM
+(INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY
+OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS
+BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+END OF TERMS AND CONDITIONS
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/MANIFEST
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/MANIFEST	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/MANIFEST	(revision 22157)
@@ -0,0 +1,38 @@
+Build.PL
+Changes
+LICENSE
+MANIFEST
+README
+scripts/ipp_datapath.pl
+scripts/ipp_filename.pl
+scripts/mdc2list.pl
+scripts/register_imfile.pl
+scripts/register_exp.pl
+scripts/detrend_process_imfile.pl
+scripts/detrend_process_exp.pl
+scripts/detrend_stack.pl
+scripts/detrend_norm_calc.pl
+scripts/detrend_norm_apply.pl
+scripts/detrend_norm_exp.pl
+scripts/detrend_resid.pl
+scripts/detrend_reject_imfile.pl
+scripts/detrend_reject_exp.pl
+scripts/chip_imfile.pl
+scripts/camera_exp.pl
+scripts/warp_overlap.pl
+scripts/warp_skycell.pl
+scripts/diff_skycell.pl
+scripts/stack_skycell.pl
+scripts/ipp_serial_inject.pl
+scripts/ipp_serial_inject_mosaic.pl
+scripts/ipp_serial_register.pl
+scripts/ipp_serial_detrend.pl
+scripts/ipp_serial_chip.pl
+scripts/ipp_serial_camera.pl
+scripts/ipp_serial_warp.pl
+scripts/ipp_serial_diff.pl
+scripts/ipp_serial_stack.pl
+scripts/ipp_simulation_data.pl
+scripts/isp_trans.pl
+scripts/ds9_cmf_regions.pl
+t/00_distribution.t
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/README
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/README	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/README	(revision 22157)
@@ -0,0 +1,15 @@
+pod2text foo.pm > README
+
+If this is still here it means the programmer was too lazy to create the readme file.
+
+You can create it now by using the command shown above from this directory.
+
+At the very least you should be able to use this set of instructions
+to install the module...
+
+perl Build.PL
+./Build
+./Build test
+./Build install
+
+If you are on a windows box you should use 'nmake' rather than 'make'.
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/calibrate_dvo.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/calibrate_dvo.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/calibrate_dvo.pl	(revision 22157)
@@ -0,0 +1,196 @@
+#!/usr/bin/env perl
+
+use Carp;
+use warnings;
+use strict;
+
+## report the program and machine
+use Sys::Hostname;
+my $host = hostname();
+print "\n\n";
+print "Starting script $0 on $host\n\n";
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use Storable qw(freeze thaw);
+use File::Basename qw( basename);
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::Stats;
+use PS::IPP::Config 1.01 qw( :standard );
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+use File::Spec;
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+my ($cal_id, $dvodb, $region, $dbname, $workdir, $no_update, $no_op);
+GetOptions(
+    'cal_id|i=s'       => \$cal_id,
+    'dvodb|c=s'        => \$dvodb,
+    'region|r=s'       => \$region,
+    'dbname|d=s'       => \$dbname,# Database name
+    'workdir|w=s'      => \$workdir, # Working directory for output files
+    'no-update'        => \$no_update,
+    'no-op'            => \$no_op,
+) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options: --cal_id --dvodb --region",
+           -exitval => 3) unless
+    defined $cal_id and
+    defined $dvodb and
+    defined $region;
+
+# Look for programs we need
+my $missing_tools;
+my $addstar  = can_run('addstar')  or (warn "Can't find addstar"  and $missing_tools = 1);
+my $relphot  = can_run('relphot')  or (warn "Can't find relphot"  and $missing_tools = 1);
+my $uniphot  = can_run('uniphot')  or (warn "Can't find uniphot"  and $missing_tools = 1);
+my $relastro = can_run('relastro') or (warn "Can't find relastro" and $missing_tools = 1);
+my $caltool  = can_run('caltool')  or (warn "Can't find caltool"  and $missing_tools = 1);
+
+if ($missing_tools) { 
+    warn ("Can't find required tools");
+    exit($PS_EXIT_CONFIG_ERROR); 
+}
+
+# select the primary filters from DVO query?
+my (@filters) = `photcodeList -average`;
+
+# parse the region (RAs,RAe:DECs,DECe) : item = +/-NNN.NNNN
+my @coords = split (":", $region);
+my ($RAs, $RAe) = split (",", $coords[0]);
+my ($DECs, $DECe) = split (",", $coords[1]);
+
+# Run addstar -resort
+{
+    my $command = "$addstar -resort";
+    $command .= "-D CATDIR $dvodb";
+    $command .= "-region $RAs $RAe $DECs $DECe";
+
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        cache_run(command => $command, verbose => 1);
+
+    unless ($success) { 
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die ("Unable to perform addstar -resort on region $region: $error_code", $cal_id, $region, "RESORT", $status, $dbname);
+    }
+}
+
+# Run relphot (filter) for each filter
+{
+    foreach my $filter (@filters) {
+	my $command = "$relphot $filter";
+	$command .= "-D CATDIR $dvodb";
+	$command .= "-region $RAs $RAe $DECs $DECe";
+
+	my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	    cache_run(command => $command, verbose => 1);
+
+	unless ($success) { 
+	    $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+	    &my_die ("Unable to perform addstar -resort on region $region: $error_code", $cal_id, $region, "RELPHOT", $status, $dbname);
+	}
+    }
+}
+
+# Run uniphot (filter) for each filter
+# XXX skip this one?  run less frequently?
+if (0) {
+    foreach my $filter (@filters) {
+	my $command = "$uniphot $filter";
+	$command .= "-D CATDIR $dvodb";
+	$command .= "-region $RAs $RAe $DECs $DECe";
+
+	my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	    cache_run(command => $command, verbose => 1);
+
+	unless ($success) { 
+	    $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+	    &my_die ("Unable to perform addstar -resort on region $region: $error_code", $cal_id, $region, "UNIPHOT", $status, $dbname);
+	}
+    }
+}
+
+{
+    my $command = "$relastro -objects";
+    $command .= "-D CATDIR $dvodb";
+    $command .= "-region $RAs $RAe $DECs $DECe";
+
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	cache_run(command => $command, verbose => 1);
+
+    unless ($success) { 
+	$error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+	&my_die ("Unable to perform addstar -resort on region $region: $error_code", $cal_id, $region, "RELASTRO.OBJECTS", $status, $dbname);
+    }
+}
+
+{
+    my $command = "$relastro -images";
+    $command .= "-D CATDIR $dvodb";
+    $command .= "-region $RAs $RAe $DECs $DECe";
+
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	cache_run(command => $command, verbose => 1);
+
+    unless ($success) { 
+	$error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+	&my_die ("Unable to perform addstar -resort on region $region: $error_code", $cal_id, $region, "RELASTRO.IMAGES", $status, $dbname);
+    }
+}
+
+my $command = "$caltool -addrun";
+$command .= " -cal_id $cal_id";
+$command .= " -region $region";
+$command .= " -last_step RELASTRO.IMAGES";
+$command .= " -state 0";
+$command .= " -dbname $dbname" if defined $dbname;
+
+# Push the results into the database
+unless ($no_update) {
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => 1);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        warn ("Unable to perform regtool -addprocessedimfile: $error_code");
+        exit($error_code);
+    }
+} else {
+    print "skipping command: $command\n";
+}
+
+sub my_die
+{
+    my $msg = shift; # Warning message on die
+    my $cal_id    = shift;
+    my $region    = shift;
+    my $last_step = shift;
+    my $status 	  = shift;
+    my $dbname 	  = shift;
+
+    carp($msg);
+    if (defined $cal_id && defined $region && defined $last_step && defined $status and not $no_update) {
+        my $command = "$caltool -addcalrun";
+	$command .= " -cal_id $cal_id";
+        $command .= " -region $region";
+	$command .= " -last_step $last_step";
+	$command .= " -state $status";
+        $command .= " -dbname $dbname" if defined $dbname;
+        system ($command);
+    }
+    exit $exit_code;
+}
+
+# Pau.
+
+END {
+    my $exit = $?;
+    system("sync") == 0 or die "failed to execute sync: $!";
+    $? = $exit;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/camera_exp.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/camera_exp.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/camera_exp.pl	(revision 22157)
@@ -0,0 +1,347 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+use Carp;
+
+## report the program and machine
+use Sys::Hostname;
+my $host = hostname();
+print "\n\n";
+print "Starting script $0 on $host\n\n";
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::Stats;
+use PS::IPP::Metadata::List qw( parse_md_list );
+use PS::IPP::Config 1.01 qw( :standard );
+use File::Temp qw( tempfile );
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+my ( $exp_tag, $cam_id, $camera, $outroot, $recipe, $dbname, $reduction, $dvodb, $verbose, $no_update,
+     $no_op, $redirect, $save_temps );
+GetOptions(
+    'exp_tag=s'          => \$exp_tag, # Exposure identifier
+    'cam_id=s'          => \$cam_id, # Camtool identifier
+    'recipe=s'          => \$recipe, # Recipe to use
+    'camera|c=s'        => \$camera, # Camera
+    'dbname|d=s'        => \$dbname, # Database name
+    'outroot|w=s'       => \$outroot, # output file base name
+    'reduction=s'       => \$reduction, # Reduction class
+    'dvodb|w=s'         => \$dvodb,  # output DVO database
+    'verbose'           => \$verbose,   # Print to stdout
+    'no-update'         => \$no_update, # Update the database?
+    'no-op'             => \$no_op, # Don't do any operations?
+    'redirect-output'   => \$redirect,
+    'save-temps'        => \$save_temps, # Save temporary files?
+    ) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage(
+          -msg => "Required options: --exp_tag --cam_id --camera --outroot",
+          -exitval => 3,
+          ) unless
+    defined $exp_tag and
+    defined $cam_id and
+    defined $outroot and
+    defined $camera;
+
+$ipprc->define_camera($camera);
+
+my $logDest = $ipprc->filename("LOG.EXP", $outroot) or &my_die("Missing entry from camera config", $cam_id, $PS_EXIT_CONFIG_ERROR);
+
+$ipprc->redirect_output($logDest) if $redirect;
+
+# Recipes to use based on reduction class
+$reduction = 'DEFAULT' unless defined $reduction;
+
+my $recipe1 = $ipprc->reduction($reduction, 'JPEG_BIN1'); # Recipe to use
+&my_die("Unrecognised JPEG recipe", $cam_id, $PS_EXIT_CONFIG_ERROR) unless defined $recipe1;
+
+my $recipe2 = $ipprc->reduction($reduction, 'JPEG_BIN2'); # Recipe to use
+&my_die("Unrecognised JPEG recipe", $cam_id, $PS_EXIT_CONFIG_ERROR) unless defined $recipe2;
+
+# values to extract from output metadata and the stats to calculate
+# these should be coming from the psastro results
+my $CHIPSTATS =
+    [
+        #          PPSTATS KEYWORD         STATISTIC          CHIPTOOL FLAG
+        { name => "bg",             type => "mean",  flag => "-bg",             dtype => "float" },
+        { name => "bg_stdev",       type => "rms",   flag => "-bg_stdev",       dtype => "float" },
+        { name => "bg_mean_stdev",  type => "stdev", flag => "-bg_mean_stdev",  dtype => "float" },
+        { name => "bias",           type => "mean",  flag => "-bias",           dtype => "float" },
+        { name => "bias_stdev",     type => "rms",   flag => "-bias_stdev",     dtype => "float" },
+        { name => "fringe_0",       type => "mean",  flag => "-fringe_0",       dtype => "float" },
+        { name => "fringe_1",       type => "rms",   flag => "-fringe_1",       dtype => "float" },
+        { name => "fringe_0",       type => "stdev", flag => "-fringe_2",       dtype => "float" },
+#        { name => "sigma_ra",       type => "rms",   flag => "-sigma_ra",       dtype => "float" },
+#        { name => "sigma_dec",      type => "rms",   flag => "-sigma_dec",      dtype => "float" },
+        { name => "ap_resid",       type => "mean",  flag => "-ap_resid",       dtype => "float" },
+        { name => "ap_resid_stdev", type => "rms",   flag => "-ap_resid_stdev", dtype => "float" },
+        { name => "zp_mean",        type => "mean",  flag => "-zp_mean",        dtype => "float" },
+        { name => "zp_stdev",       type => "rms",   flag => "-zp_stdev",       dtype => "float" },
+        { name => "fwhm_major",     type => "mean",  flag => "-fwhm_major",     dtype => "float" },
+        { name => "fwhm_minor",     type => "mean",  flag => "-fwhm_minor",     dtype => "float" },
+        { name => "dtime_detrend",  type => "sum",   flag => "-dtime_detrend",  dtype => "float" },
+        { name => "dtime_photom",   type => "sum",   flag => "-dtime_photom",   dtype => "float" },
+        { name => "dtime_astrom",   type => "sum",   flag => "-dtime_astrom",   dtype => "float" },
+        { name => "n_stars",        type => "sum",   flag => "-n_stars",        dtype => "int"   },
+        { name => "n_extended",     type => "sum",   flag => "-n_extended",     dtype => "int"   },
+        { name => "n_cr",           type => "sum",   flag => "-n_cr",           dtype => "int"   },
+        ];
+my $chipStats = PS::IPP::Metadata::Stats->new($CHIPSTATS); # Stats parser
+
+# values to extract from camera-level output metadata and the stats to calculate
+my $CAMSTATS =
+   [
+       #          PPSTATS KEYWORD         STATISTIC          CHIPTOOL FLAG
+      { name => "CERROR",         type => "rms",   flag => "-sigma_ra",       dtype => "float" },
+      { name => "CERROR",         type => "rms",   flag => "-sigma_dec",      dtype => "float" },
+      { name => "DT_ASTR",        type => "sum",   flag => "-dtime_astrom",   dtype => "float" },
+      { name => "NASTRO",         type => "sum",   flag => "-n_astrom",       dtype => "int"   },
+#     { name => "ZP??",           type => "mean",  flag => "-zp_mean",        dtype => "float" },
+#     { name => "ZP??",           type => "rms",   flag => "-zp_stdev",       dtype => "float" },
+   ];
+my $camStats = PS::IPP::Metadata::Stats->new($CAMSTATS); # Stats parser
+
+# Look for programs we need
+my $missing_tools;
+my $camtool = can_run('camtool') or (warn "Can't find camtool" and $missing_tools = 1);
+my $ppImage = can_run('ppImage') or (warn "Can't find ppImage" and $missing_tools = 1);
+my $ppConfigDump = can_run('ppConfigDump') or (warn "Can't find ppConfigDump" and $missing_tools = 1);
+
+# test for addstar and psastro:
+my $psastro = can_run('psastro') or (warn "Can't find psastro" and $missing_tools = 1);
+my $addstar = can_run('addstar') or (warn "Can't find addstar" and $missing_tools = 1);
+
+if ($missing_tools) {
+    warn("Can't find required tools.");
+    exit($PS_EXIT_CONFIG_ERROR);
+}
+
+my $mdcParser = PS::IPP::Metadata::Config->new; # Parser for metadata config files
+
+# Get list of component files
+my $files;                      # Array of component files
+{
+    my $command = "$camtool -pendingimfile -cam_id $cam_id"; # Command to run
+    $command .= " -dbname $dbname" if defined $dbname;
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to perform camtool: $error_code", $cam_id, $error_code);
+    }
+    my $metadata = $mdcParser->parse(join "", @$stdout_buf) or
+        &my_die("Unable to parse metadata config doc", $cam_id, $PS_EXIT_PROG_ERROR);
+
+    # extract the metadata for the files into a hash list
+    $files = parse_md_list($metadata) or
+        &my_die("Unable to parse metadata list", $cam_id, $PS_EXIT_PROG_ERROR);
+
+    # extract the stats from the metadata
+    unless ($chipStats->parse($metadata)) {
+        &my_die("Unable to find all values in statistics output.\n", $cam_id, $PS_EXIT_PROG_ERROR);
+    }
+}
+
+### not needed to have such an extensive temp file name.
+my ($list1File, $list1Name) = tempfile( "$exp_tag.cm.$cam_id.b1.list.XXXX", UNLINK => !$save_temps ); # For binning 1
+my ($list2File, $list2Name) = tempfile( "$exp_tag.cm.$cam_id.b2.list.XXXX", UNLINK => !$save_temps ); # For binning 2
+my ($list3File, $list3Name) = tempfile( "$exp_tag.cm.$cam_id.b3.list.XXXX", UNLINK => !$save_temps ); # For astrometry
+
+# XXX we perform astrometry iff photometry output exists
+my $chipObjects;
+my $chipObjectsExist = 0;
+foreach my $file (@$files) {
+    # use the path_base as OUTPUT root and convert the filenames with ipprc->filename:
+    my $class_id = $file->{class_id};
+
+    # If there is only one chip, we use this name for the input to addstar
+    # we expect the chip analysis stage to produce psphot output (cmf file) and two binned images
+    $chipObjects = $ipprc->filename("PSPHOT.OUTPUT", $file->{path_base}, $class_id);
+    print $list1File ($ipprc->filename("PPIMAGE.BIN1", $file->{path_base}, $class_id) . "\n");
+    print $list2File ($ipprc->filename("PPIMAGE.BIN2", $file->{path_base}, $class_id) . "\n");
+    print $list3File ($ipprc->convert_filename_absolute($chipObjects) . "\n");
+
+    # if any of the output chip photometry files exist, we can run psastro / addstar below
+    if ($ipprc->file_exists($chipObjects)) {
+        $chipObjectsExist = 1;
+    }
+}
+close $list1File;
+close $list2File;
+close $list3File;
+
+# Output products
+$ipprc->outroot_prepare($outroot);
+
+# the camera configurations should define the psastro output to be a single file (MEF), regardless of the inputs
+my $jpeg1      = $ipprc->filename("PPIMAGE.JPEG1",      $outroot) or &my_die("Missing entry from camera config", $cam_id, $PS_EXIT_CONFIG_ERROR);
+my $jpeg2      = $ipprc->filename("PPIMAGE.JPEG2",      $outroot) or &my_die("Missing entry from camera config", $cam_id, $PS_EXIT_CONFIG_ERROR);
+my $fpaObjects = $ipprc->filename("PSASTRO.OUTPUT",     $outroot) or &my_die("Missing entry from camera config", $cam_id, $PS_EXIT_CONFIG_ERROR);
+my $fpaStats   = $ipprc->filename("PSASTRO.STATS",      $outroot) or &my_die("Missing entry from camera config", $cam_id, $PS_EXIT_CONFIG_ERROR);
+my $traceDest  = $ipprc->filename("TRACE.EXP",          $outroot) or &my_die("Missing entry from camera config", $cam_id, $PS_EXIT_CONFIG_ERROR);
+
+# convert supplied DVO database name to UNIX filename
+my $dvodbReal;
+if (defined $dvodb) {
+    $dvodbReal = $ipprc->dvo_catdir( $dvodb ); # catdir for DVO
+    $dvodbReal = $ipprc->convert_filename_absolute( $dvodbReal );
+}
+
+unless ($no_op) {
+
+    ## build the output JPEG images first so we get them even if the astrometry fails
+
+    # Make the jpeg for binning 1
+    {
+        my $command = "$ppImage -list $list1Name $outroot -recipe PPIMAGE $recipe1"; # Command to run
+        $command .= " -dbname $dbname" if defined $dbname;
+
+        my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+            run(command => $command, verbose => $verbose);
+        unless ($success) {
+            $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+            &my_die("Unable to perform ppImage: $error_code", $cam_id, $error_code);
+        }
+        &my_die("Unable to find expected output file: $jpeg1", $cam_id, $PS_EXIT_PROG_ERROR) unless -f $ipprc->file_resolve($jpeg1);
+    }
+
+    # Make the jpeg for binning 2
+    {
+        my $command = "$ppImage -list $list2Name $outroot -recipe PPIMAGE $recipe2"; # Command to run
+        $command .= " -dbname $dbname" if defined $dbname;
+
+        my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+            run(command => $command, verbose => $verbose);
+        unless ($success) {
+            $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+            &my_die("Unable to perform ppImage: $error_code", $cam_id, $error_code);
+        }
+        &my_die("Unable to find expected output file: $jpeg2", $cam_id, $PS_EXIT_PROG_ERROR) unless -f $ipprc->file_resolve($jpeg2);
+    }
+
+    # only run psastro / addstar if any of the output chip astrometry files exist (should we test for successful astrometry?)
+    if ($chipObjectsExist) {
+        # run psastro on the chipObjects, producing fpaObjects
+        # XXX add a ppStats call which will collect the astrometry stats
+        my $command;
+        $command  = "$psastro -list $list3Name $outroot";
+        $command .= " -tracedest $traceDest -log $logDest";
+	$command .= " -stats $fpaStats -recipe PPSTATS CHIPSTATS";
+        $command .= " -dbname $dbname" if defined $dbname;
+
+        my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+            run(command => $command, verbose => $verbose);
+        unless ($success) {
+            $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+            &my_die("Unable to perform psastro: $error_code", $cam_id, $error_code);
+        }
+        # XXX do we want to give an error if astrometry fails here?
+        &my_die("Unable to find expected output file: $fpaObjects", $cam_id, $PS_EXIT_PROG_ERROR) unless -f $ipprc->file_resolve($fpaObjects);
+        &my_die("Unable to find expected output file: $fpaStats",   $cam_id, $PS_EXIT_PROG_ERROR) unless -f $ipprc->file_resolve($fpaObjects);
+
+	# Get the statistics on the processed image
+	my $statsFile;              # File handle
+	open $statsFile, $ipprc->file_resolve($fpaStats) or &my_die("Can't open statistics file $fpaStats: $!", $cam_id, $PS_EXIT_SYS_ERROR);
+	my @contents = <$statsFile>; # Contents of file
+	close $statsFile;
+
+	# parse the statistics MDC file
+	my $mdcParser = PS::IPP::Metadata::Config->new();   # Parser for metadata config files
+	my $metadata = $mdcParser->parse(join "", @contents);
+	unless ($metadata) {
+	    &my_die("Unable to parse metadata config doc", $cam_id, $PS_EXIT_PROG_ERROR);
+	}
+
+	# extract the stats from the metadata
+	unless ($camStats->parse($metadata)) {
+	    &my_die("Failure extracting metadata from the statistics output file.\n", $cam_id, $PS_EXIT_PROG_ERROR);
+	}
+
+        # run addstar on the output fpaObjects (if a DVO database is defined)
+        if (defined $dvodbReal) {
+            # XXX this construct requires the user to have a valid .ptolemyrc
+            # XXX which in turn points at ippconfig/dvo.site
+            # require a defined output dvo database to run addstar (ie, refuse to use the .ptolemyrc default)
+            # XXX this needs to be converted to addstar_client...
+
+            my $camdir = $ipprc->dvo_cameradir(); # Camera directory for addstar
+            my $command;
+            $command  = "$addstar -D CAMERA $camdir -update";
+            $command .= " -D CATDIR $dvodbReal";
+
+            my $realFile = $ipprc->file_resolve($fpaObjects);
+            $command .= " $realFile";
+
+            my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+                run(command => $command, verbose => $verbose);
+            unless ($success) {
+                $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+                &my_die("Unable to perform addstar: $error_code", $cam_id, $error_code);
+            }
+        }
+    }
+}
+
+my $fpaCommand = "$camtool -addprocessedexp";
+$fpaCommand .= " -cam_id $cam_id";
+$fpaCommand .= " -uri UNKNOWN";
+$fpaCommand .= " -path_base $outroot";
+$fpaCommand .= " -hostname $host" if defined $host;
+$fpaCommand .= " -dbname $dbname" if defined $dbname;
+$fpaCommand .= $chipStats->cmdflags();
+$fpaCommand .= $camStats->cmdflags();
+
+# Add the result into the database
+unless ($no_update) {
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $fpaCommand, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        warn("Unable to add result to database: $error_code\n");
+        exit($error_code);
+    }
+} else {
+    print "skipping command: $fpaCommand\n";
+}
+
+
+sub my_die
+{
+    my $msg = shift; # Warning message on die
+    my $cam_id = shift; # Camtool identifier
+    my $exit_code = shift; # Exit code to add
+
+    carp($msg);
+    if (defined $cam_id and not $no_update) {
+        my $command = "$camtool -addprocessedexp";
+	$command .= " -cam_id $cam_id";
+	$command .= " -uri UNKNOWN";
+	$command .= " -code $exit_code";
+	$command .= " -path_base $outroot";
+        $command .= " -hostname $host" if defined $host;
+        $command .= " -dbname $dbname" if defined $dbname;
+        $command .= " -path_base $outroot" if defined $outroot;
+        system ($command);
+    }
+    exit $exit_code;
+}
+
+
+END {
+    my $status = $?;
+    system("sync") == 0
+        or die "failed to execute sync: $!" ;
+    $? = $status;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/chip_imfile.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/chip_imfile.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/chip_imfile.pl	(revision 22157)
@@ -0,0 +1,257 @@
+#!/usr/bin/env perl
+
+use Carp;
+use warnings;
+use strict;
+
+## report the program and machine
+use Sys::Hostname;
+my $host = hostname();
+print "\n\n";
+print "Starting script $0 on $host\n\n";
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::Stats;
+use PS::IPP::Config 1.01 qw( :standard );
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+# Parse the command-line arguments
+my ( $exp_id, $chip_id, $class_id, $uri, $camera, $outroot, $dbname, $reduction, $verbose,
+     $no_update, $no_op, $redirect );
+GetOptions(
+    'exp_id=s'      	=> \$exp_id,    # Exposure identifier
+    'chip_id=s'     	=> \$chip_id,   # Chiptool identifier
+    'class_id=s'    	=> \$class_id,  # Class identifier
+    'uri|u=s'       	=> \$uri,       # Input FITS file
+    'camera|c=s'    	=> \$camera,    # Camera
+    'outroot|w=s'   	=> \$outroot,   # output file base name
+    'dbname|d=s'    	=> \$dbname,    # Database name
+    'reduction=s'   	=> \$reduction, # Reduction class
+    'verbose'       	=> \$verbose,   # Print to stdout
+    'no-update'     	=> \$no_update, # Don't update the database?
+    'no-op'         	=> \$no_op,     # Don't do any operations?
+    'redirect-output'   => \$redirect,
+    ) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options: --exp_id --chip_id --class_id --uri --camera --outroot",
+           -exitval => 3) unless
+    defined $exp_id and
+    defined $chip_id and
+    defined $class_id and
+    defined $uri and
+    defined $camera and
+    defined $outroot;
+
+$ipprc->define_camera($camera);
+
+my $logDest = $ipprc->filename("LOG.IMFILE", $outroot, $class_id) or &my_die("Missing entry from camera config", $exp_id, $chip_id, $class_id, $PS_EXIT_CONFIG_ERROR);
+
+$ipprc->redirect_output($logDest) if $redirect;
+
+# Recipes to use based on reduction class
+# the CHIP recipe should not perform astrometry anymore (2008.04.08)
+
+$reduction = 'DEFAULT' unless defined $reduction;
+my $recipe = $ipprc->reduction($reduction, 'CHIP'); # Recipe to use
+unless ($recipe) {
+    &my_die("Couldn't find selected reduction for CHIP: $reduction\n", $exp_id, $chip_id, $class_id, $PS_EXIT_CONFIG_ERROR);
+}
+
+# values to extract from output metadata and the stats to calculate
+# XXX commented-out entries are not yet defined in the output files
+# XXX EAM : we are removing the astrometry analysis from chip, so we
+# do not bother to calculate those stats.
+my $STATS =
+   [
+       #          PPSTATS KEYWORD         STATISTIC          CHIPTOOL FLAG
+       { name => "ROBUST_MEDIAN",  type => "mean",  flag => "-bg",             dtype => "float" },
+       { name => "ROBUST_MEDIAN",  type => "stdev", flag => "-bg_mean_stdev",  dtype => "float" },
+       { name => "ROBUST_STDEV",   type => "rms",   flag => "-bg_stdev",       dtype => "float" },
+       { name => "OVER_VAL",       type => "mean",  flag => "-bias",           dtype => "float" },
+       { name => "OVER_VAL",       type => "stdev", flag => "-bias_stdev",     dtype => "float" },
+       { name => "FRINGE_0",       type => "rms",   flag => "-fringe_0",       dtype => "float" },
+       { name => "FRINGE_RESID_0", type => "rms",   flag => "-fringe_1",       dtype => "float" },
+       { name => "FRINGE_ERR_0",   type => "rms",   flag => "-fringe_2",       dtype => "float" },
+#      { name => "CERROR",         type => "rms",   flag => "-sigma_ra",       dtype => "float" },
+#      { name => "CERROR",         type => "rms",   flag => "-sigma_dec",      dtype => "float" },
+       { name => "APMIFIT",        type => "mean",  flag => "-ap_resid",       dtype => "float" },
+       { name => "DAPMIFIT",       type => "rms",   flag => "-ap_resid_stdev", dtype => "float" },
+#      { name => "ZP??",           type => "mean",  flag => "-zp_mean",        dtype => "float" },
+#      { name => "ZP??",           type => "rms",   flag => "-zp_stdev",       dtype => "float" },
+       { name => "FWHM_X",         type => "mean",  flag => "-fwhm_major",     dtype => "float" },
+       { name => "FWHM_Y",         type => "mean",  flag => "-fwhm_minor",     dtype => "float" },
+       { name => "DT_DET",         type => "sum",   flag => "-dtime_detrend",  dtype => "float" },
+       { name => "DT_PHOT",        type => "sum",   flag => "-dtime_photom",   dtype => "float" },
+#       { name => "DT_ASTR",        type => "sum",   flag => "-dtime_astrom",   dtype => "float" },
+       { name => "NSTARS",         type => "sum",   flag => "-n_stars",        dtype => "int"   },
+#      { name => "?",              type => "sum",   flag => "-n_extended",     dtype => "int"   },
+#      { name => "?",              type => "sum",   flag => "-n_cr",           dtype => "int"   },
+#      { name => "NASTRO",         type => "sum",   flag => "-n_astrom",       dtype => "int"   },
+   ];
+my $stats = PS::IPP::Metadata::Stats->new($STATS); # Stats parser
+
+# Look for programs we need
+my $missing_tools;
+my $chiptool = can_run('chiptool') or (warn "Can't find chiptool" and $missing_tools = 1);
+my $ppImage = can_run('ppImage') or (warn "Can't find ppImage" and $missing_tools = 1);
+my $ppConfigDump = can_run('ppConfigDump') or (warn "Can't find ppConfigDump" and $missing_tools = 1);
+if ($missing_tools) {
+    warn("Can't find required tools.");
+    exit($PS_EXIT_CONFIG_ERROR);
+}
+
+my $mdcParser = PS::IPP::Metadata::Config->new; # Parser for metadata config files
+
+&my_die("Couldn't find input file: $uri\n", $exp_id, $chip_id, $class_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($uri);
+
+# outroot examples (HOST components must be set)
+# file://data/ipp004.0/gpc1/20080130
+# neb:///ipp004-v1/gpc1/20080130
+# neb:///*/gpc1/20080130 (volume not specified)
+
+# check for existing directory, generate if needed
+$ipprc->outroot_prepare($outroot);
+
+## these names are used in ppImage, and thus may be URIs
+my $outputImage   = $ipprc->filename("PPIMAGE.CHIP",        $outroot, $class_id) or &my_die("Missing entry from camera config", $exp_id, $chip_id, $class_id, $PS_EXIT_CONFIG_ERROR);
+my $outputMask    = $ipprc->filename("PPIMAGE.CHIP.MASK",   $outroot, $class_id) or &my_die("Missing entry from camera config", $exp_id, $chip_id, $class_id, $PS_EXIT_CONFIG_ERROR);
+my $outputWeight  = $ipprc->filename("PPIMAGE.CHIP.WEIGHT", $outroot, $class_id) or &my_die("Missing entry from camera config", $exp_id, $chip_id, $class_id, $PS_EXIT_CONFIG_ERROR);
+my $outputBin1    = $ipprc->filename("PPIMAGE.BIN1",        $outroot, $class_id) or &my_die("Missing entry from camera config", $exp_id, $chip_id, $class_id, $PS_EXIT_CONFIG_ERROR);
+my $outputBin2    = $ipprc->filename("PPIMAGE.BIN2",        $outroot, $class_id) or &my_die("Missing entry from camera config", $exp_id, $chip_id, $class_id, $PS_EXIT_CONFIG_ERROR);
+my $outputStats   = $ipprc->filename("PPIMAGE.STATS",       $outroot, $class_id) or &my_die("Missing entry from camera config", $exp_id, $chip_id, $class_id, $PS_EXIT_CONFIG_ERROR);
+my $traceDest     = $ipprc->filename("TRACE.IMFILE",        $outroot, $class_id) or &my_die("Missing entry from camera config", $exp_id, $chip_id, $class_id, $PS_EXIT_CONFIG_ERROR);
+
+# Run ppImage
+unless ($no_op) {
+    ## XXX can we convert ppImage log and trace to use the filerules to generate consistent names
+    ## XXX also stats: output should be implied by $outroot
+    my $command = "$ppImage -file $uri $outroot";
+    $command .= " -recipe PPIMAGE $recipe";
+    $command .= " -recipe PPSTATS CHIPSTATS";
+    $command .= " -stats $outputStats";
+    $command .= " -dbname $dbname" if defined $dbname;
+    $command .= " -tracedest $traceDest -log $logDest";
+
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to perform ppImage: $error_code", $exp_id, $chip_id, $class_id, $error_code);
+    }
+
+    ## get the ppImage recipe for this camera and CHIP reduction
+    $command = "$ppConfigDump -camera $camera -dump-recipe PPIMAGE -recipe PPIMAGE $recipe -";
+    ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to perform ppConfigDump: $error_code", $exp_id, $chip_id, $class_id, $PS_EXIT_SYS_ERROR);
+    }
+    my $recipeData = $mdcParser->parse(join "", @$stdout_buf) or
+        &my_die("Unable to parse metadata config doc", $exp_id, $chip_id, $class_id, $PS_EXIT_SYS_ERROR);
+
+    ## allow the output images to be optional, depending on the recipe / reduction class
+    my $outputImageExpect = metadataLookupBool($recipeData, 'CHIP.FITS');
+    if ($outputImageExpect) {
+        &my_die("Couldn't find expected output file: $outputImage\n",  $exp_id, $chip_id, $class_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($outputImage);
+    }
+
+    my $outputMaskExpect = metadataLookupBool($recipeData, 'CHIP.MASK.FITS');
+    if ($outputMaskExpect) {
+        &my_die("Couldn't find expected output file: $outputMask\n",   $exp_id, $chip_id, $class_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($outputMask);
+    }
+
+    my $outputWeightExpect = metadataLookupBool($recipeData, 'CHIP.WEIGHT.FITS');
+    if ($outputWeightExpect) {
+        &my_die("Couldn't find expected output file: $outputWeight\n", $exp_id, $chip_id, $class_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($outputWeight);
+    }
+
+    &my_die("Couldn't find expected output file: $outputBin1\n",   $exp_id, $chip_id, $class_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($outputBin1);
+    &my_die("Couldn't find expected output file: $outputBin2\n",   $exp_id, $chip_id, $class_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($outputBin2);
+    &my_die("Couldn't find expected output file: $outputStats\n",  $exp_id, $chip_id, $class_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($outputStats);
+
+    # Get the statistics on the processed image
+    my $statsFile;              # File handle
+    open $statsFile, $ipprc->file_resolve($outputStats) or &my_die("Can't open statistics file $outputStats: $!", $exp_id, $chip_id, $class_id, $PS_EXIT_SYS_ERROR);
+    my @contents = <$statsFile>; # Contents of file
+    close $statsFile;
+
+    # parse the statistics MDC file
+    my $mdcParser = PS::IPP::Metadata::Config->new();   # Parser for metadata config files
+    my $metadata = $mdcParser->parse(join "", @contents);
+    unless ($metadata) {
+        &my_die("Unable to parse metadata config doc", $exp_id, $chip_id, $class_id, $PS_EXIT_PROG_ERROR);
+    }
+
+    # extract the stats from the metadata
+    unless ($stats->parse($metadata)) {
+        &my_die("Failure extracting metadata from the statistics output file.\n", $exp_id, $chip_id, $class_id, $PS_EXIT_PROG_ERROR);
+    }
+}
+
+# command to update database
+my $command = "$chiptool -addprocessedimfile";
+$command .= " -exp_id $exp_id";
+$command .= " -chip_id $chip_id";
+$command .= " -class_id $class_id";
+$command .= " -uri $outputImage";
+$command .= " -path_base $outroot";
+$command .= " -hostname $host" if defined $host;
+$command .= " -dbname $dbname" if defined $dbname;
+$command .= $stats->cmdflags();
+
+# Add the processed file to the database
+unless ($no_update) {
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        warn("Unable to perform chiptool -addprocessedimfile: $error_code\n");
+        exit($error_code);
+    }
+} else {
+    print "skipping command: $command\n";
+}
+
+sub my_die
+{
+    my $msg = shift; # Warning message on die
+    my $exp_id = shift; # Chiptool identifier
+    my $chip_id = shift; # Chiptool identifier
+    my $class_id = shift; # Class identifier
+    my $exit_code = shift; # Exit code to add
+    # outputImage and outroot are globals
+
+    carp($msg);
+    if (defined $chip_id and defined $class_id and not $no_update) {
+        my $command = "$chiptool -addprocessedimfile";
+        $command .= " -exp_id $exp_id";
+        $command .= " -chip_id $chip_id";
+        $command .= " -class_id $class_id";
+        $command .= " -code $exit_code";
+        $command .= " -uri $outputImage";
+        $command .= " -path_base $outroot";
+        $command .= " -hostname $host" if defined $host;
+        $command .= " -dbname $dbname" if defined $dbname;
+        system ($command);
+    }
+    exit $exit_code;
+}
+
+END {
+    my $status = $?;
+    system("sync") == 0
+        or die "failed to execute sync: $!" ;
+    $? = $status;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_correct_imfile.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_correct_imfile.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_correct_imfile.pl	(revision 22157)
@@ -0,0 +1,156 @@
+#!/usr/bin/env perl
+
+use Carp;
+use warnings;
+use strict;
+
+## report the program and machine
+use Sys::Hostname;
+my $host = hostname();
+print "\n\n";
+print "Starting script $0 on $host\n\n";
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::Stats;
+use PS::IPP::Config 1.01 qw( :standard );
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+my ( $det_id, $exp_id, $class_id, $det_type, $exp_tag, $input_uri, $camera, $dbname, $workdir, $reduction,
+     $verbose, $no_update, $no_op, $outroot, $redirect, $corr_id, $corr_type, $corr_uri );
+GetOptions(
+    'det_id|d=s'        => \$det_id,
+    'exp_id|e=s'        => \$exp_id,
+    'class_id|i=s'      => \$class_id,
+    'det_type|t=s'      => \$det_type,
+    'exp_tag|=s'        => \$exp_tag,
+    'input_uri|u=s'     => \$input_uri,
+    'corr_id|u=s'       => \$corr_id,
+    'corr_type|u=s'     => \$corr_type,
+    'camera|c=s'        => \$camera,
+    'dbname|d=s'        => \$dbname, # Database name
+    'workdir|w=s'       => \$workdir, # Working directory, for output files
+    'verbose'           => \$verbose,   # Print to stdout
+    'no-update'         => \$no_update,
+    'no-op'             => \$no_op,
+    'outroot'           => \$outroot,
+    'redirect-output'   => \$redirect,
+) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options: --det_id --exp_id --class_id --det_type --exp_tag --input_uri --camera",
+	   -exitval => 3)
+    unless defined $det_id
+    and defined $exp_id
+    and defined $class_id
+    and defined $det_type
+    and defined $exp_tag
+    and defined $input_uri
+    and defined $camera;
+
+# XXX this exits with status = 0 on failure
+$ipprc->define_camera($camera);
+
+if ($redirect) {
+    die "must suplly --outroot with --redirect-output" if !defined ($outroot);
+    my $logDest     = $ipprc->filename("LOG.IMFILE", $outroot, $class_id)
+       or &my_die("Missing entry from camera config", $det_id, $exp_id, $class_id, $PS_EXIT_CONFIG_ERROR);
+    $ipprc->redirect_output($logDest);
+}
+
+# Recipes to use as a function of detrend type
+$reduction = "DETREND" unless defined $reduction;
+my $recipe = $ipprc->reduction($reduction, uc($det_type) . '_PROCESS'); # Recipe name to use
+
+# Look for programs we need
+my $missing_tools;
+my $dettool = can_run('dettool') or (warn "Can't find dettool" and $missing_tools = 1);
+my $dvocorr = can_run('dvoApplyCorr') or (warn "Can't find dvoApplyCorr" and $missing_tools = 1);
+if ($missing_tools) { 
+    warn("Can't find required tools.");
+    exit($PS_EXIT_CONFIG_ERROR); 
+}
+
+&my_die("Couldn't find input file: $input_uri\n", $det_id, $exp_id, $class_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($input_uri);
+
+$workdir = caturi( $workdir, "$camera.$det_type.$det_id" ) if defined $workdir;
+
+# detselect -select -det_id $corr_id -class_id $class_id;
+
+my $outputRoot  = $ipprc->file_prepare( "$exp_tag/$exp_tag.detproc.$det_id", $workdir, $input_uri );
+my $outputImage = $ipprc->filename("DVOCORR.OUTPUT", $outputRoot, $class_id) or &my_die("Missing entry from camera config", $det_id, $exp_id, $class_id, $PS_EXIT_PROG_ERROR);
+
+# Run dvoApplyCorr
+unless ($no_op) {
+    my $command = "$dvocorr -file $input_uri $outputRoot";
+    $command .= " -corr $corr_uri";
+
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run(command => $command, verbose => $verbose);
+    unless ($success) {
+	$error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+	&my_die("Unable to perform ppImage: $error_code", $det_id, $exp_id, $class_id, $error_code);
+    }
+
+    &my_die("Couldn't find expected output file: $outputImage", $det_id, $exp_id, $class_id, $PS_EXIT_SYS_ERROR) unless -f $ipprc->file_resolve($outputImage);
+}
+
+# command to update database
+my $command = "$dettool -addcorrectedimfile";
+$command .= " -det_id $det_id";
+$command .= " -exp_id $exp_id";
+$command .= " -class_id $class_id";
+$command .= " -uri $outputImage";
+$command .= " -path_base $outputRoot";
+$command .= " -dbname $dbname" if defined $dbname;
+
+# Add the processed file to the database
+unless ($no_update) {
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run(command => $command, verbose => $verbose);
+    unless ($success) {
+	$error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+	warn("Unable to perform dettool -addcorrectedimfile: $error_code\n");
+	exit($error_code);
+    }
+} else {
+    print "skipping command: $command\n";
+}
+
+sub my_die
+{
+    my $msg = shift; # Warning message on die
+    my $det_id = shift;		# Detrend identifier
+    my $exp_id = shift; # Exposure tag
+    my $class_id = shift; # Class identifier
+    my $exit_code = shift; # Exit code to add
+
+    carp($msg);
+    if (defined $det_id and defined $exp_id and defined $class_id and not $no_update) {
+	my $command = "$dettool -addcorrectedimfile";
+	$command .= " -det_id $det_id";
+	$command .= " -exp_id $exp_id"; 
+	$command .= " -class_id $class_id";
+	$command .= " -path_base $outputRoot";
+	$command .= " -code $exit_code";
+	$command .= " -dbname $dbname" if defined $dbname;
+        system ($command);
+    }
+    exit $exit_code;
+}
+
+END {
+    my $status = $?;
+    system("sync") == 0
+        or die "failed to execute sync: $!" ;
+    $? = $status;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_norm_apply.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_norm_apply.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_norm_apply.pl	(revision 22157)
@@ -0,0 +1,230 @@
+#!/usr/bin/env perl
+
+use Carp;
+use warnings;
+use strict;
+
+## report the program and machine
+use Sys::Hostname;
+my $host = hostname();
+print "\n\n";
+print "Starting script $0 on $host\n\n";
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::Stats;
+use PS::IPP::Config 1.01 qw( :standard );
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+# Parse the command-line
+my ( $det_id, $iter, $class_id, $value, $input_uri, $camera, $det_type, $outroot, $dbname, $verbose,
+     $no_update, $no_op, $redirect );
+GetOptions(
+    'det_id|d=s'        => \$det_id,     # Detrend ID
+    'iteration|n=s'     => \$iter,       # Iteration
+    'class_id|i=s'      => \$class_id,   # Class ID
+    'value|v=s'         => \$value,      # Value to multiple (for normalisation)
+    'input_uri|u=s'     => \$input_uri,  # Input file
+    'camera|c=s'        => \$camera,     # Camera
+    'det_type|t=s'      => \$det_type,   # Detrend type
+    'outroot|w=s'       => \$outroot,    # output file base name
+    'dbname|d=s'        => \$dbname,     # Database name
+    'verbose'           => \$verbose,   # Print to stdout
+    'no-update'         => \$no_update,  # Don't update the database
+    'no-op'             => \$no_op,      # Don't do any operations
+    'redirect-output'   => \$redirect,   # send output from script to LOG.IMFILE
+    ) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options: --det_id --iteration --class_id --value --input_uri --camera --det_type --outroot",
+           -exitval => 3) unless
+    defined $det_id    and
+    defined $iter      and
+    defined $class_id  and
+    defined $value     and
+    defined $input_uri and
+    defined $camera    and
+    defined $det_type  and
+    defined $outroot;
+
+$ipprc->define_camera($camera);
+
+my $logDest = $ipprc->filename("LOG.IMFILE", $outroot, $class_id) 
+        or &my_die("Missing entry from camera config", $det_id, $iter, $class_id, $PS_EXIT_CONFIG_ERROR);
+$ipprc->redirect_output($logDest) if $redirect;
+
+my $RECIPE_PPIMAGE = 'PPIMAGE_N'; # Recipe to use with ppImage
+my $RECIPE_PPSTATS = 'CHIPSTATS'; # Recipe to use with ppStats
+
+# Define which detrend types we normalise
+use constant DETTYPE => {
+    'bias'     	       => 'bias',
+    'dark'     	       => 'dark',   
+    'dark_premask'     => 'dark',
+    'shutter'  	       => 'shutter',
+    'flat'     	       => 'flat',
+    'domeflat' 	       => 'flat',
+    'skyflat'  	       => 'flat',   
+    'flat_premask'     => 'flat',
+    'domeflat_premask' => 'flat',
+    'skyflat_premask'  => 'flat',
+    'fringe'   	       => 'fringe',   
+    'mask'     	       => 'mask',
+    'darkmask' 	       => 'mask',
+    'flatmask' 	       => 'mask',
+    }; 
+
+# convert supplied detrend type to a controlled namespace
+&my_die("Unrecognised detrend type: $det_type", $det_id, $iter, $class_id, $PS_EXIT_PROG_ERROR) unless exists DETTYPE()->{lc($det_type)};
+my $det_type_real = DETTYPE()->{lc($det_type)};
+
+# values to extract from output metadata and the stats to calculate
+my $STATS =
+   [
+       #          PPSTATS KEYWORD         STATISTIC          CHIPTOOL FLAG
+       { name => "ROBUST_MEDIAN",  type => "mean",  flag => "-bg",            dtype => "float" },
+       { name => "ROBUST_MEDIAN",  type => "stdev", flag => "-bg_mean_stdev", dtype => "float" },
+       { name => "ROBUST_STDEV",   type => "rms",   flag => "-bg_stdev",      dtype => "float" },
+   ];
+my $stats = PS::IPP::Metadata::Stats->new($STATS); # Stats parser
+
+# Look for programs we need
+my $missing_tools;
+my $dettool = can_run('dettool') or (warn "Can't find dettool" and $missing_tools = 1);
+my $ppImage = can_run('ppImage') or (warn "Can't find ppImage" and $missing_tools = 1);
+if ($missing_tools) {
+    warn("Can't find required tools.");
+    exit($PS_EXIT_CONFIG_ERROR);
+}
+
+&my_die("Couldn't find input file: $input_uri\n", $det_id, $iter, $class_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($input_uri);
+
+# outroot examples (HOST components must be set)
+# file://data/ipp004.0/gpc1/20080130
+# neb:///ipp004-v1/gpc1/20080130
+# neb:///*/gpc1/20080130 (volume not specified)
+
+# check for existing directory, generate if needed
+$ipprc->outroot_prepare($outroot);
+
+my $outFile = ($det_type_real eq "mask") ? 'PPIMAGE.OUTPUT.DETMASK' : 'PPIMAGE.OUTPUT';; # XXXX something of a hack (too many places to control things...)
+
+my $output    = $ipprc->filename($outFile,        $outroot, $class_id) or &my_die("Missing entry from camera config", $det_id, $iter, $class_id, $PS_EXIT_CONFIG_ERROR);
+my $b1name    = $ipprc->filename("PPIMAGE.BIN1",  $outroot, $class_id) or &my_die("Missing entry from camera config", $det_id, $iter, $class_id, $PS_EXIT_CONFIG_ERROR);
+my $b2name    = $ipprc->filename("PPIMAGE.BIN2",  $outroot, $class_id) or &my_die("Missing entry from camera config", $det_id, $iter, $class_id, $PS_EXIT_CONFIG_ERROR);
+my $statsName = $ipprc->filename("PPIMAGE.STATS", $outroot, $class_id) or &my_die("Missing entry from camera config", $det_id, $iter, $class_id, $PS_EXIT_CONFIG_ERROR);
+my $traceDest = $ipprc->filename("TRACE.IMFILE",  $outroot, $class_id) or &my_die("Missing entry from camera config", $det_id, $iter, $class_id, $PS_EXIT_CONFIG_ERROR);
+
+# Run normalisation
+unless ($no_op) {
+
+    # we cannot use ppImage to load a normalized mask : just copy it and build the jpeg images
+    if ($det_type_real eq 'mask') {
+	$RECIPE_PPIMAGE = 'PPIMAGE_BIN';
+	my $input_real = $ipprc->file_resolve($input_uri, 0);
+	my $output_real = $ipprc->file_resolve($output, 1);
+	system ("cp $input_real $output_real");
+    }
+
+    my $command = "$ppImage -file $input_uri $outroot";
+    $command .= " -norm $value -stats $statsName";
+    $command .= " -recipe PPIMAGE $RECIPE_PPIMAGE";
+    $command .= " -recipe PPSTATS $RECIPE_PPSTATS";
+    $command .= " -F PPIMAGE.OUTPUT $outFile" unless $outFile eq 'PPIMAGE.OUTPUT';
+    $command .= ' -isfringe' if $det_type_real eq 'fringe';
+    $command .= ' -isdark' if $det_type_real eq 'dark';
+    $command .= " -tracedest $traceDest -log $logDest";
+    $command .= " -dbname $dbname" if defined $dbname;
+
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to perform ppImage: $error_code", $det_id, $iter, $class_id, $error_code);
+    }
+    &my_die("Can't find expected output file: $output",    $det_id, $iter, $class_id, $PS_EXIT_SYS_ERROR) unless -f $ipprc->file_resolve($output);
+    &my_die("Can't find expected output file: $b1name",    $det_id, $iter, $class_id, $PS_EXIT_SYS_ERROR) unless -f $ipprc->file_resolve($b1name);
+    &my_die("Can't find expected output file: $b2name",    $det_id, $iter, $class_id, $PS_EXIT_SYS_ERROR) unless -f $ipprc->file_resolve($b2name);
+    &my_die("Can't find expected output file: $statsName", $det_id, $iter, $class_id, $PS_EXIT_SYS_ERROR) unless -f $ipprc->file_resolve($statsName);
+
+    # Get the statistics on the normalised image
+    my $statsFile; # File handle
+    open $statsFile, $ipprc->file_resolve($statsName) or &my_die("Can't open statistics file $statsName: $!\n", $det_id, $iter, $class_id, $PS_EXIT_SYS_ERROR);
+    &my_die("Can't open statistics file $statsName: $!\n", $det_id, $iter, $class_id, $PS_EXIT_SYS_ERROR) unless defined $statsFile;
+    my @contents = <$statsFile>; # Contents of file
+    close $statsFile;
+
+    # parse the statistics MDC file
+    my $mdcParser = PS::IPP::Metadata::Config->new;     # Parser for metadata config files
+    my $metadata = $mdcParser->parse(join "", @contents);
+    unless ($metadata) {
+        &my_die("Unable to parse metadata config", $det_id, $iter, $class_id, $PS_EXIT_PROG_ERROR);
+    }
+
+    # extract the stats from the metadata
+    unless ($stats->parse($metadata)) {
+        &my_die("Unable to find all values in statistics output.", $det_id, $iter, $class_id, $PS_EXIT_PROG_ERROR);
+    }
+}
+
+# Command to update the database
+my $command = "$dettool -addnormalizedimfile";
+$command .= " -det_id $det_id";
+$command .= " -iteration $iter";
+$command .= " -class_id $class_id";
+$command .= " -uri $output";
+$command .= " -path_base $outroot";
+$command .= " -dbname $dbname" if defined $dbname;
+$command .= $stats->cmdflags();
+
+# Add the processed file to the database
+unless ($no_update) {
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        warn("Unable to perform dettool -addnormalizedimfile: $error_code\n");
+        exit($error_code);
+    }
+} else {
+    print "skipping command: $command\n";
+}
+
+sub my_die
+{
+    my $msg = shift;            # Warning message on die
+    my $det_id = shift;         # Detrend identifier
+    my $iter = shift;           # Iteration
+    my $class_id = shift;       # Class identifier
+    my $exit_code = shift;      # Exit code to add
+
+    carp($msg);
+    if (defined $det_id and defined $iter and defined $class_id and not $no_update) {
+        my $command = "$dettool -addnormalizedimfile";
+        $command .= " -det_id $det_id";
+        $command .= " -iteration $iter";
+        $command .= " -class_id $class_id";
+	$command .= " -path_base $outroot";
+        $command .= " -code $exit_code";
+        $command .= " -dbname $dbname" if defined $dbname;
+        system ($command);
+    }
+    exit $exit_code;
+}
+
+
+END {
+    my $status = $?;
+    system("sync") == 0
+        or die "failed to execute sync: $!" ;
+    $? = $status;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_norm_calc.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_norm_calc.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_norm_calc.pl	(revision 22157)
@@ -0,0 +1,250 @@
+#!/usr/bin/env perl
+
+use Carp;
+use warnings;
+use strict;
+
+## report the program and machine
+use Sys::Hostname;
+my $host = hostname();
+print "\n\n";
+print "Starting script $0 on $host\n\n";
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use IPC::Cmd 0.36 qw( can_run );
+use IPC::Run 0.36 qw( run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::List qw( parse_md_list );
+use PS::IPP::Config 1.01 qw( :standard );
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+# Parse command-line arguments
+my ($det_id, $iter, $detType, $outroot, $dbname, $verbose, $no_update, $no_op, $redirect );
+GetOptions(
+    'det_id|d=s'	=> \$det_id,    # Detrend id			     
+    'iteration|i=s'	=> \$iter,	# Iteration			     
+    'det_type|t=s'  	=> \$detType,	# Detrend type			     
+    'outroot|w=s'   	=> \$outroot,   # output file base name
+    'dbname|d=s'    	=> \$dbname,	# Database name			     
+    'verbose'       	=> \$verbose,   # Print to stdout
+    'no-update'     	=> \$no_update,	# Don't update the database?	     
+    'no-op'         	=> \$no_op,	# Don't do operations                
+    'redirect-output'   => \$redirect,
+    ) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options --det_id --iteration --det_type --outroot",
+	   -exitval => 3,
+	   ) unless 
+    defined $det_id  and 
+    defined $iter    and 
+    defined $detType and
+    defined $outroot;
+
+my $logfile = $outroot . ".log";
+
+$ipprc->redirect_output($logfile) if $redirect;
+
+use constant STATISTIC => 'bg'; # Background statistic to use from the database
+
+# Define which detrend types we normalise
+use constant NORMALIZE => {
+    'bias'     	       => 0,
+    'dark'     	       => 0,   
+    'dark_premask'     => 0,
+    'shutter'  	       => 0,
+    'flat'     	       => 1,
+    'domeflat' 	       => 1,
+    'skyflat'  	       => 1,   
+    'flat_premask'     => 1,
+    'domeflat_premask' => 1,
+    'skyflat_premask'  => 1,
+    'fringe'   	       => 0,   
+    'mask'     	       => 0,
+    'darkmask' 	       => 0,
+    'flatmask' 	       => 0,
+    }; 
+
+# Look for programs we need
+my $missing_tools;
+my $dettool = can_run('dettool') or (warn "Can't find dettool" and $missing_tools = 1);
+my $ppNormCalc = can_run('ppNormCalc') or (warn "Can't find ppNormCalc" and $missing_tools = 1);
+if ($missing_tools) { 
+    warn("Can't find required tools.");
+    exit($PS_EXIT_CONFIG_ERROR); 
+}
+
+&my_die("Unrecognised detrend type: $detType", $det_id, $iter, $PS_EXIT_PROG_ERROR) unless exists NORMALIZE()->{lc($detType)};
+
+my $mdcParser = PS::IPP::Metadata::Config->new; # Parser for metadata config files
+
+# Get the list of inputs
+my @files;			# The input files
+{
+    my $command = "$dettool -processedimfile";
+    $command .= " -det_id $det_id"; # Command to run
+    $command .= " -included"; # only use the inputs for this detrend run to calculate the norm
+    $command .= " -dbname $dbname" if defined $dbname;
+    my @command = split /\s+/, $command;
+    my ( $stdin, $stdout, $stderr ); # Buffers for running program
+    print "Running [$command]...\n" if $verbose;
+    if (not run(\@command, \$stdin, \$stdout, \$stderr)) {
+	&my_die("Unable to perform dettool -processedimfile on detrend $det_id/$iter: $?",
+		$det_id, $iter, $PS_EXIT_SYS_ERROR);
+    }
+    print $stdout . "\n" if $verbose;
+    
+    # Because of the length, need to split into individual metadatas --- it parses SO much quicker!
+    my @whole = split /\n/, $stdout;
+    my @single = ();
+    while ( scalar @whole > 0 ) {
+	my $value = shift @whole;
+	push @single, $value;
+	if ($value =~ /^\s*END\s*$/) {
+	    push @single, "\n";
+
+	    my $list = parse_md_list( $mdcParser->parse( join( "\n", @single ) ) );
+	    &my_die("Unable to parse output from dettool", $det_id, $iter, $PS_EXIT_PROG_ERROR) unless
+		defined $list;
+	    push @files, @$list;
+	    @single = ();
+	}
+    }
+}
+
+my $norms;			# MDC with normalisations
+if (NORMALIZE()->{lc($detType)} and not $no_op) {
+
+    my %matrix; # Matrix of statistics as a function of exposures and classes
+    foreach my $file (@files) {
+	my $exp_id = $file->{'exp_id'}; # Exposure ID
+	my $class_id = $file->{'class_id'}; # Class ID
+	my $stat = $file->{STATISTIC()}; # Statistic of interest
+	
+	# Create matrix elements
+	$matrix{$exp_id} = {} if not defined $matrix{$exp_id};
+	$matrix{$exp_id}->{$class_id} = $stat;
+    }
+    
+    # Generate the input for ppNormCalc
+    my $normData;			# Normalisation data
+    foreach my $exp_id (keys %matrix) {
+	$normData .= "$exp_id\tMETADATA\n";
+	foreach my $class_id (keys %{$matrix{$exp_id}}) {
+	    $normData .= "\t" . $class_id . "\tF32\t" . $matrix{$exp_id}->{$class_id} . "\n";
+	}
+	$normData .= "END\n\n";
+    }
+
+    # Run ppNormCalc
+    {
+	my ( $stdout, $stderr ); # Buffers for running program
+	my @command = split /\s+/, $ppNormCalc;
+	print "Running [$ppNormCalc]...\n" if $verbose;
+	if (not run(\@command, \$normData, \$stdout, \$stderr)) {
+	    &my_die("Unable to perform ppNormCalc: $?", $det_id, $iter, $PS_EXIT_SYS_ERROR);
+	}
+	print $stdout . "\n" if $verbose;
+	
+	# Parse the output
+	$norms = $mdcParser->parse($stdout);
+	&my_die("Unable to parse metadata config doc", $det_id, $iter, $PS_EXIT_PROG_ERROR) unless $norms;
+    }
+
+} else {
+    # It's something that doesn't need normalisation --- just push in a normalisation of 1
+    my %classes;		# List of unique classes
+    foreach my $file (@files) {
+	my $class_id = $file->{'class_id'}; # Class Id
+	$classes{$class_id} = 1;
+    }
+    
+    foreach my $class_id (keys %classes) {
+	my %mdValue;	# Metadata value for this class id
+	$mdValue{name} = $class_id;
+	$mdValue{value} = 1.0;
+	push @$norms, \%mdValue;
+    }
+}
+
+my $commandBase = "$dettool -addnormalizedstat";
+$commandBase .= " -det_id $det_id";
+$commandBase .= " -iteration $iter";
+$commandBase .= " -dbname $dbname" if defined $dbname;
+
+# Process output normalisations
+unless ($no_update) {
+    foreach my $normItem (@$norms) {
+
+	my $className = $normItem->{name}; # Name of component
+	my $normalisation = $normItem->{value}; # Normalisation for component
+
+	if ($normalisation == 0.0 or lc($normalisation) eq 'nan') {
+	    warn("Class $className has bad normalisation: $normalisation");
+	    exit($PS_EXIT_SYS_ERROR);
+	}
+
+	my $command = $commandBase;
+	$command .= " -class_id $className";
+	$command .= " -norm $normalisation";
+
+	my @command = split /\s+/, $command;
+
+	my ( $stdin, $stdout, $stderr ); # Buffers for running program
+	print "Running [$command]...\n" if $verbose;
+	if (not run \@command, \$stdin, \$stdout, \$stderr) {
+	    warn("Unable to perform dettool -addnormstat for $className: $?");
+	    exit($PS_EXIT_SYS_ERROR);
+	}
+	print $stdout . "\n" if $verbose;
+    }
+} else {
+    print "skipping command: $commandBase\n";
+    foreach my $normItem (@$norms) {
+
+	my $className = $normItem->{name}; # Name of component
+	my $normalisation = $normItem->{value}; # Normalisation for component
+
+	if ($normalisation == 0.0 or lc($normalisation) eq 'nan') {
+	    warn("Class $className has bad normalisation: $normalisation");
+	    exit($PS_EXIT_SYS_ERROR);
+	}
+	print "$className : $normalisation\n";
+    }
+}
+
+sub my_die
+{
+    my $msg = shift;		# Warning message on die
+    my $det_id = shift;		# Detrend identifier
+    my $iter = shift;		# Iteration
+    my $exit_code = shift;	# Exit code to add
+
+    carp($msg);
+    if (defined $det_id and defined $iter and not $no_update) {
+	my $command = "$dettool -addnormalizedstat";
+	$command .= " -det_id $det_id";
+	$command .= " -iteration $iter";
+	# XXX EAM : we should add this to the db : $command .= " -path_base $outroot";
+	$command .= " -code $exit_code";
+	$command .= " -dbname $dbname" if defined $dbname;
+        system ($command);
+    }
+    exit $exit_code;
+}
+
+
+END {
+    my $status = $?;
+    system("sync") == 0
+        or die "failed to execute sync: $!" ;
+    $? = $status;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_norm_exp.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_norm_exp.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_norm_exp.pl	(revision 22157)
@@ -0,0 +1,205 @@
+#!/usr/bin/env perl
+
+use Carp;
+use warnings;
+use strict;
+
+## report the program and machine
+use Sys::Hostname;
+my $host = hostname();
+print "\n\n";
+print "Starting script $0 on $host\n\n";
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::Stats;
+use PS::IPP::Config 1.01 qw( :standard );
+use PS::IPP::Metadata::List qw( parse_md_list );
+use File::Temp qw( tempfile );
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+my ($det_id, $iter, $det_type, $camera, $outroot, $dbname, $reduction, $verbose, $no_update, $no_op, $redirect, $save_temps);
+GetOptions(
+    'det_id|d=s'        => \$det_id,
+    'iteration|i=s'     => \$iter,
+    'camera|c=s'        => \$camera,
+    'det_type|t=s'      => \$det_type,
+    'outroot|w=s'       => \$outroot,   # output file base name
+    'dbname|d=s'        => \$dbname, # Database name
+    'reduction|=s'      => \$reduction,
+    'verbose'           => \$verbose,   # Print to stdout
+    'no-update'         => \$no_update,
+    'no-op'             => \$no_op,
+    'redirect-output'   => \$redirect,
+    'save-temps'        => \$save_temps, # Save temporary files?
+    ) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options: --det_id --iteration --camera --det_type --outroot",
+           -exitval => 3) unless
+    defined $det_id   and
+    defined $iter     and
+    defined $camera   and
+    defined $det_type and
+    defined $outroot;
+
+my $logfile = $outroot . ".log";
+
+$ipprc->redirect_output($logfile) if $redirect;
+
+$ipprc->define_camera($camera);
+
+# Recipes to use based on reduction class
+$reduction = 'DETREND' unless defined $reduction;
+
+my $recipe1 = $ipprc->reduction($reduction, 'JPEG_BIN1_IMAGE_' . uc($det_type)); # Recipe to use
+&my_die("Unrecognised detrend type: $det_type", $det_id, $iter, $PS_EXIT_PROG_ERROR) unless defined $recipe1;
+
+my $recipe2 = $ipprc->reduction($reduction, 'JPEG_BIN2_IMAGE_' . uc($det_type)); # Recipe to use
+&my_die("Unrecognised detrend type: $det_type", $det_id, $iter, $PS_EXIT_PROG_ERROR) unless defined $recipe2;
+
+# values to extract from output metadata and the stats to calculate
+# XXX -bg_mean_stdev should take rms of bg_mean_stdev if bg_mean_stdev != 0 (A)
+# XXX -bg_mean_stdev should take stdev of bg_mean if bg_mean_stdev == 0     (B)
+# XXX  (A) if imfile.Ncomp > 1, (B) if imfile.Ncomp == 1
+my $STATS =
+    [
+        #          KEYWORD                 STATISTIC          CHIPTOOL FLAG
+        { name => "bg",             type => "mean",  flag => "-bg",            dtype => "float" },
+        { name => "bg",             type => "stdev", flag => "-bg_mean_stdev", dtype => "float" },
+        { name => "bg_stdev",       type => "rms",   flag => "-bg_stdev",      dtype => "float" },
+        # { name => "bg_mean_stdev",  type => "rms",   flag => "-bg_mean_stdev" },
+        ];
+my $stats = PS::IPP::Metadata::Stats->new($STATS); # Stats parser
+
+# Look for programs we need
+my $missing_tools;
+my $dettool = can_run('dettool') or (warn "Can't find dettool" and $missing_tools = 1);
+my $ppImage = can_run('ppImage') or (warn "Can't find ppImage" and $missing_tools = 1);
+if ($missing_tools) {
+    warn("Can't find required tools.");
+    exit($PS_EXIT_CONFIG_ERROR);
+}
+
+# Get list of component files
+my ($files, $command, $success, $error_code, $full_buf, $stdout_buf, $stderr_buf);
+{
+    $command  = "$dettool -normalizedimfile"; # Command to run
+    $command .= " -det_id $det_id";
+    $command .= " -iteration $iter"; 
+    $command .= " -dbname $dbname" if defined $dbname;
+    ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to get list of normalized imfiles from dettool: $error_code", $det_id, $iter, $error_code);
+    }
+
+    # convert stdout to a metadata
+    my $mdcParser = PS::IPP::Metadata::Config->new;     # Parser for metadata config files
+    my $metadata = $mdcParser->parse(join "", @$stdout_buf) or
+        &my_die("Unable to parse metadata config doc", $det_id, $iter, $PS_EXIT_PROG_ERROR);
+
+    # parse the file info in the metadata
+    $files = parse_md_list($metadata) or
+        &my_die("Unable to parse metadata list", $det_id, $iter, $PS_EXIT_PROG_ERROR);
+
+    # parse the stats in the metadata
+    unless ($stats->parse($metadata)) {
+        &my_die("Unable to find all values in statistics output.\n", $det_id, $iter, $PS_EXIT_PROG_ERROR);
+    }
+}
+
+my ($list1File, $list1Name) = tempfile( "$camera.$det_type.norm.$det_id.$iter.b1.list.XXXX", UNLINK => !$save_temps );
+my ($list2File, $list2Name) = tempfile( "$camera.$det_type.norm.$det_id.$iter.b2.list.XXXX", UNLINK => !$save_temps );
+foreach my $file (@$files) {
+    print $list1File ( $ipprc->filename( "PPIMAGE.BIN1", $file->{path_base}, $file->{class_id} ) . "\n");
+    print $list2File ( $ipprc->filename( "PPIMAGE.BIN2", $file->{path_base}, $file->{class_id} ) . "\n");
+}
+close $list1File;
+close $list2File;
+
+# outroot examples (HOST components must be set)
+# file://data/ipp004.0/gpc1/20080130
+# neb:///ipp004-v1/gpc1/20080130
+# neb:///*/gpc1/20080130 (volume not specified)
+
+# check for existing directory, generate if needed
+$ipprc->outroot_prepare($outroot);
+
+my $jpeg1Name = $ipprc->filename("PPIMAGE.JPEG1", $outroot); # Binned JPEG #1
+my $jpeg2Name = $ipprc->filename("PPIMAGE.JPEG2", $outroot); # Binned JPEG #2
+
+unless ($no_op) {
+    # Make the jpeg for binning 1
+    $command = "$ppImage -list $list1Name $outroot -recipe PPIMAGE $recipe1"; # Command to run
+    $command .= " -dbname $dbname" if defined $dbname;
+    ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    &my_die("Unable to find expected output file: $jpeg1Name", $det_id, $iter, $PS_EXIT_SYS_ERROR) unless -f $ipprc->file_resolve($jpeg1Name);
+
+    # Make the jpeg for binning 2
+    $command = "$ppImage -list $list2Name $outroot -recipe PPIMAGE $recipe2"; # Command to run
+    $command .= " -dbname $dbname" if defined $dbname;
+    ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    &my_die("Unable to find expected output file: $jpeg2Name", $det_id, $iter, $PS_EXIT_SYS_ERROR) unless -f $ipprc->file_resolve($jpeg2Name);
+
+}
+
+# command to update the database
+$command  = "$dettool -addnormalizedexp";
+$command .= " -det_id $det_id";
+$command .= " -iteration $iter";
+$command .= " -recip $recipe1,$recipe2";
+$command .= " -path_base $outroot ";
+$command .= " -dbname $dbname" if defined $dbname;
+$command .= $stats->cmdflags();
+
+# Add the processed file to the database
+unless ($no_update) {
+    ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to perform dettool -addnormalizedexp: $error_code", $det_id, $iter, $error_code);
+    }
+} else {
+    print "skipping command: $command\n";
+}
+
+sub my_die
+{
+    my $msg = shift; # Warning message on die
+    my $det_id = shift;         # Detrend identifier
+    my $iter = shift;           # Iteration
+    my $exit_code = shift; # Exit code to add
+
+    carp($msg);
+    if (defined $det_id and defined $iter and not $no_update) {
+        my $command = "$dettool -addnormalizedexp";
+        $command .= " -det_id $det_id";
+        $command .= " -iteration $iter";
+	$command .= " -path_base $outroot ";
+        $command .= " -code $exit_code";
+        $command .= " -dbname $dbname" if defined $dbname;
+        system ($command);
+    }
+    exit $exit_code;
+}
+
+END {
+    my $status = $?;
+    system("sync") == 0
+        or die "failed to execute sync: $!" ;
+    $? = $status;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_process_exp.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_process_exp.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_process_exp.pl	(revision 22157)
@@ -0,0 +1,216 @@
+#!/usr/bin/env perl
+
+use Carp;
+use warnings;
+use strict;
+
+## report the program and machine
+use Sys::Hostname;
+my $host = hostname();
+print "\n\n";
+print "Starting script $0 on $host\n\n";
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::Stats;
+use PS::IPP::Config 1.01 qw( :standard );
+use PS::IPP::Metadata::List qw( parse_md_list );
+use File::Temp qw( tempfile );
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+my ( $det_id, $exp_id, $det_type, $exp_tag, $camera, $outroot, $dbname, $reduction, $verbose, $no_update,
+     $no_op, $save_temps, $redirect );
+GetOptions(
+    'det_id|d=s'        => \$det_id,
+    'det_type|t=s'      => \$det_type,
+    'exp_id|d=s'        => \$exp_id,
+    'exp_tag|=s'        => \$exp_tag,
+    'camera|c=s'        => \$camera,
+    'outroot|w=s'       => \$outroot,   # output file base name
+    'dbname|d=s'        => \$dbname, # Database name
+    'reduction|=s'      => \$reduction,
+    'verbose'           => \$verbose,   # Print to stdout
+    'no-update'         => \$no_update,
+    'no-op'             => \$no_op,
+    'save-temps'        => \$save_temps, # Save temporary files?
+    'redirect-output'   => \$redirect,
+) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options: --det_id --det_type --exp_id --exp_tag --camera --outroot",
+           -exitval => 3) unless
+    defined $det_id   and
+    defined $det_type and
+    defined $exp_id   and
+    defined $exp_tag  and
+    defined $camera   and
+    defined $outroot;
+
+$ipprc->define_camera($camera);
+
+if ($redirect) {
+    my $logDest = $ipprc->filename("LOG.EXP", $outroot, "NONE") 
+        or &my_die("Missing entry in file rules", $det_id, $exp_id, $PS_EXIT_CONFIG_ERROR);
+    $ipprc->redirect_output($logDest);
+}
+
+# Recipes to use based on reduction class
+$reduction = 'DETREND' unless defined $reduction;
+
+my $recipe1 = $ipprc->reduction($reduction, 'JPEG_BIN1_IMAGE_' . uc($det_type)); # Recipe to use
+&my_die("Unrecognised detrend type: $det_type", $det_id, $exp_id, $PS_EXIT_PROG_ERROR) unless defined $recipe1;
+
+my $recipe2 = $ipprc->reduction($reduction, 'JPEG_BIN2_IMAGE_' . uc($det_type)); # Recipe to use
+&my_die("Unrecognised detrend type: $det_type", $det_id, $exp_id, $PS_EXIT_PROG_ERROR) unless defined $recipe2;
+
+# values to extract from output metadata and the stats to calculate
+# XXX -bg_mean_stdev should take rms of bg_mean_stdev if bg_mean_stdev != 0 (A)
+# XXX -bg_mean_stdev should take stdev of bg_mean if bg_mean_stdev == 0     (B)
+# XXX  (A) if imfile.Ncomp > 1, (B) if imfile.Ncomp == 1
+my $STATS =
+   [
+       #          KEYWORD                 STATISTIC          CHIPTOOL FLAG
+       { name => "bg",             type => "mean",  flag => "-bg",            dtype => "float" },
+       { name => "bg",             type => "stdev", flag => "-bg_mean_stdev", dtype => "float" },
+       { name => "bg_stdev",       type => "rms",   flag => "-bg_stdev",      dtype => "float" },
+       # { name => "bg_mean_stdev",  type => "rms",   flag => "-bg_mean_stdev" },
+   ];
+my $stats = PS::IPP::Metadata::Stats->new($STATS); # Stats parser
+
+# Look for programs we need
+my $missing_tools;
+my $dettool = can_run('dettool') or (warn "Can't find dettool" and $missing_tools = 1);
+my $ppImage = can_run('ppImage') or (warn "Can't find ppImage" and $missing_tools = 1);
+if ($missing_tools) {
+    warn("Can't find required tools.");
+    exit($PS_EXIT_CONFIG_ERROR);
+}
+
+# Get list of component files
+my ($files, $command, $success, $error_code, $full_buf, $stdout_buf, $stderr_buf);
+{
+    $command = "$dettool -processedimfile -det_id $det_id -exp_id $exp_id"; # Command to run
+    $command .= " -dbname $dbname" if defined $dbname;
+    ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to perform dettool -processedimfile: $error_code", $det_id, $exp_id, $error_code);
+    }
+
+    # convert stdout to a metadata
+    my $mdcParser = PS::IPP::Metadata::Config->new;     # Parser for metadata config files
+    my $metadata = $mdcParser->parse(join "", @$stdout_buf) or
+        &my_die("Unable to parse metadata config doc", $det_id, $exp_id, $PS_EXIT_PROG_ERROR);
+
+    # parse the file info in the metadata
+    $files = parse_md_list($metadata) or
+        &my_die("Unable to parse metadata list", $det_id, $exp_id, $PS_EXIT_PROG_ERROR);
+
+    # parse the stats in the metadata
+    unless ($stats->parse($metadata)) {
+        &my_die("Unable to find all values in statistics output.\n", $det_id, $exp_id, $PS_EXIT_PROG_ERROR);
+    }
+}
+
+# File lists for the two binnings
+my ($list1File, $list1Name) = tempfile( "$exp_tag.detproc.$det_id.b1.list.XXXX", UNLINK => !$save_temps );
+my ($list2File, $list2Name) = tempfile( "$exp_tag.detproc.$det_id.b2.list.XXXX", UNLINK => !$save_temps );
+foreach my $file (@$files) {
+    print $list1File ($ipprc->filename( "PPIMAGE.BIN1", $file->{path_base}, $file->{class_id} ) . "\n");
+    print $list2File ($ipprc->filename( "PPIMAGE.BIN2", $file->{path_base}, $file->{class_id} ) . "\n");
+}
+close $list1File;
+close $list2File;
+
+# outroot examples (HOST components must be set)
+# file://data/ipp004.0/gpc1/20080130
+# neb:///ipp004-v1/gpc1/20080130
+# neb:///*/gpc1/20080130 (volume not specified)
+
+# check for existing directory, generate if needed
+$ipprc->outroot_prepare($outroot);
+
+my $jpeg1 = $ipprc->filename("PPIMAGE.JPEG1", $outroot); # Binned JPEG #1
+my $jpeg2 = $ipprc->filename("PPIMAGE.JPEG2", $outroot); # Binned JPEG #2
+
+unless ($no_op) {
+    # Make the jpeg for binning 1
+    $command = "$ppImage -list $list1Name $outroot -recipe PPIMAGE $recipe1"; # Command to run
+    $command .= " -dbname $dbname" if defined $dbname;
+    ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to perform ppImage: $error_code", $det_id, $exp_id, $error_code);
+    }
+    &my_die("Unable to find expected output file: $jpeg1", $det_id, $exp_id, $PS_EXIT_SYS_ERROR) unless -f $ipprc->file_resolve($jpeg1);
+
+    # Make the jpeg for binning 2
+    $command = "$ppImage -list $list2Name $outroot -recipe PPIMAGE $recipe2"; # Command to run
+    $command .= " -dbname $dbname" if defined $dbname;
+    ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to perform ppImage: $error_code", $det_id, $exp_id, $error_code);
+    }
+    &my_die("Unable to find expected output file: $jpeg2", $det_id, $exp_id, $PS_EXIT_SYS_ERROR) unless -f $ipprc->file_resolve($jpeg2);
+}
+
+# Command to update the database
+$command  = "$dettool -addprocessedexp";
+$command .= " -det_id $det_id";
+$command .= " -exp_id $exp_id";
+$command .= " -recip $recipe1,$recipe2 -path_base $outroot";
+$command .= " -dbname $dbname" if defined $dbname;
+$command .= $stats->cmdflags();
+
+# Add the processed file to the database
+unless ($no_update) {
+    ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        warn("Unable to perform dettool -addprocessedexp: $error_code");
+        exit($error_code);
+    }
+} else {
+    print "skipping command: $command\n";
+}
+
+sub my_die
+{
+    my $msg = shift; # Warning message on die
+    my $det_id = shift;         # Detrend identifier
+    my $exp_id = shift; # Exposure tag
+    my $exit_code = shift; # Exit code to add
+
+    carp($msg);
+    if (defined $det_id and defined $exp_id and not $no_update) {
+        my $command = "$dettool -addprocessedexp";
+        $command .= " -det_id $det_id";
+        $command .= " -exp_id $exp_id";
+        $command .= " -code $exit_code";
+	$command .= " -path_base $outroot";
+        $command .= " -dbname $dbname" if defined $dbname;
+        system ($command);
+    }
+    exit $exit_code;
+}
+
+END {
+    my $status = $?;
+    system("sync") == 0
+        or die "failed to execute sync: $!" ;
+    $? = $status;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_process_imfile.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_process_imfile.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_process_imfile.pl	(revision 22157)
@@ -0,0 +1,190 @@
+#!/usr/bin/env perl
+
+use Carp;
+use warnings;
+use strict;
+
+## report the program and machine
+use Sys::Hostname;
+my $host = hostname();
+print "\n\n";
+print "Starting script $0 on $host\n\n";
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::Stats;
+use PS::IPP::Config 1.01 qw( :standard );
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+my ( $det_id, $exp_id, $class_id, $det_type, $exp_tag, $input_uri, $camera, $outroot, $dbname, $reduction,
+     $verbose, $no_update, $no_op, $redirect );
+GetOptions(
+    'det_id|d=s'        => \$det_id,
+    'exp_id|e=s'        => \$exp_id,
+    'class_id|i=s'      => \$class_id,
+    'det_type|t=s'      => \$det_type,
+    'exp_tag|=s'        => \$exp_tag,
+    'input_uri|u=s'     => \$input_uri,
+    'camera|c=s'        => \$camera,
+    'outroot|w=s'       => \$outroot, # output file base name
+    'dbname|d=s'        => \$dbname, # Database name
+    'reduction=s'       => \$reduction, # Reduction class
+    'verbose'           => \$verbose,   # Print to stdout
+    'no-update'         => \$no_update,
+    'no-op'             => \$no_op,
+    'redirect-output'   => \$redirect,
+) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options: --det_id --exp_id --class_id --det_type --exp_tag --input_uri --camera --outroot",
+           -exitval => 3) unless
+    defined $det_id    and
+    defined $exp_id    and
+    defined $class_id  and
+    defined $det_type  and
+    defined $exp_tag   and
+    defined $input_uri and
+    defined $camera    and
+    defined $outroot;
+
+# XXX this exits with status = 0 on failure
+$ipprc->define_camera($camera);
+
+my $logDest = $ipprc->filename("LOG.IMFILE", $outroot, $class_id) or &my_die("Missing entry from camera config", $det_id, $exp_id, $class_id, $PS_EXIT_CONFIG_ERROR);
+
+$ipprc->redirect_output($logDest) if $redirect;
+
+# Recipes to use as a function of detrend type
+$reduction = "DETREND" unless defined $reduction;
+my $recipe = $ipprc->reduction($reduction, uc($det_type) . '_PROCESS'); # Recipe name to use
+
+# values to extract from output metadata and the stats to calculate
+my $STATS =
+   [
+       #          PPSTATS KEYWORD         STATISTIC          CHIPTOOL FLAG
+       { name => "ROBUST_MEDIAN",  type => "mean",  flag => "-bg",            dtype => "float" },
+       { name => "ROBUST_MEDIAN",  type => "stdev", flag => "-bg_mean_stdev", dtype => "float" },
+       { name => "ROBUST_STDEV",   type => "rms",   flag => "-bg_stdev",      dtype => "float" },
+   ];
+my $stats = PS::IPP::Metadata::Stats->new($STATS); # Stats parser
+
+# Look for programs we need
+my $missing_tools;
+my $dettool = can_run('dettool') or (warn "Can't find dettool" and $missing_tools = 1);
+my $ppImage = can_run('ppImage') or (warn "Can't find ppImage" and $missing_tools = 1);
+if ($missing_tools) {
+    warn("Can't find required tools.");
+    exit($PS_EXIT_CONFIG_ERROR);
+}
+
+&my_die("Couldn't find input file: $input_uri\n", $det_id, $exp_id, $class_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($input_uri);
+
+# outroot examples (HOST components must be set)
+# file://data/ipp004.0/gpc1/20080130
+# neb:///ipp004-v1/gpc1/20080130
+# neb:///*/gpc1/20080130 (volume not specified)
+
+# check for existing directory, generate if needed
+$ipprc->outroot_prepare($outroot);
+
+my $outputImage = $ipprc->filename("PPIMAGE.OUTPUT", $outroot, $class_id) or &my_die("Missing entry from camera config", $det_id, $exp_id, $class_id, $PS_EXIT_PROG_ERROR);
+my $outputBin1  = $ipprc->filename("PPIMAGE.BIN1",   $outroot, $class_id) or &my_die("Missing entry from camera config", $det_id, $exp_id, $class_id, $PS_EXIT_PROG_ERROR);
+my $outputBin2  = $ipprc->filename("PPIMAGE.BIN2",   $outroot, $class_id) or &my_die("Missing entry from camera config", $det_id, $exp_id, $class_id, $PS_EXIT_PROG_ERROR);
+my $outputStats = $ipprc->filename("PPIMAGE.STATS",  $outroot, $class_id) or &my_die("Missing entry from camera config", $det_id, $exp_id, $class_id, $PS_EXIT_PROG_ERROR);
+my $traceDest   = $ipprc->filename("TRACE.IMFILE",   $outroot, $class_id) or &my_die("Missing entry from camera config", $exp_id, $exp_id, $class_id, $PS_EXIT_CONFIG_ERROR);
+# Run ppImage
+unless ($no_op) {
+    my $command = "$ppImage -file $input_uri $outroot";
+    $command .= " -recipe PPIMAGE $recipe";
+    $command .= " -recipe PPSTATS CHIPSTATS";
+    $command .= " -stats $outputStats";
+    $command .= " -tracedest $traceDest -log $logDest";
+    $command .= " -dbname $dbname" if defined $dbname;
+
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to perform ppImage: $error_code", $det_id, $exp_id, $class_id, $error_code);
+    }
+
+    &my_die("Couldn't find expected output file: $outputImage", $det_id, $exp_id, $class_id, $PS_EXIT_SYS_ERROR) unless -f $ipprc->file_resolve($outputImage);
+    &my_die("Couldn't find expected output file: $outputStats", $det_id, $exp_id, $class_id, $PS_EXIT_SYS_ERROR) unless -f $ipprc->file_resolve($outputStats);
+    &my_die("Couldn't find expected output file: $outputBin1",  $det_id, $exp_id, $class_id, $PS_EXIT_SYS_ERROR) unless -f $ipprc->file_resolve($outputBin1);
+    &my_die("Couldn't find expected output file: $outputBin2",  $det_id, $exp_id, $class_id, $PS_EXIT_SYS_ERROR) unless -f $ipprc->file_resolve($outputBin2);
+
+    # Get the statistics on the processed image
+    my $statsFile;              # File handle
+    open $statsFile, $ipprc->file_resolve("$outputStats") or die "Can't open statistics file $outputStats: $!\n";
+    my @contents = <$statsFile>; # Contents of file
+    close $statsFile;
+
+    # parse the statistics MDC file
+    my $mdcParser = PS::IPP::Metadata::Config->new;     # Parser for metadata config files
+    my $metadata = $mdcParser->parse(join "", @contents)
+        or &my_die("Unable to parse metadata config", $det_id, $exp_id, $class_id, $PS_EXIT_PROG_ERROR);
+
+    # extract the stats from the metadata
+    $stats->parse($metadata) or &my_die("Unable to find all values in statistics output.", $det_id, $exp_id, $class_id, $PS_EXIT_PROG_ERROR);
+}
+
+# command to update database
+my $command = "$dettool -addprocessedimfile";
+$command .= " -det_id $det_id";
+$command .= " -exp_id $exp_id";
+$command .= " -class_id $class_id";
+$command .= " -recip $reduction";
+$command .= " -uri $outputImage -path_base $outroot";
+$command .= " -dbname $dbname" if defined $dbname;
+$command .= $stats->cmdflags();
+
+# Add the processed file to the database
+unless ($no_update) {
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        warn("Unable to perform dettool -addprocessedimfile: $error_code\n");
+        exit($error_code);
+    }
+} else {
+    print "skipping command: $command\n";
+}
+
+sub my_die
+{
+    my $msg = shift; # Warning message on die
+    my $det_id = shift;         # Detrend identifier
+    my $exp_id = shift; # Exposure tag
+    my $class_id = shift; # Class identifier
+    my $exit_code = shift; # Exit code to add
+
+    carp($msg);
+    if (defined $det_id and defined $exp_id and defined $class_id and not $no_update) {
+        my $command = "$dettool -addprocessedimfile";
+        $command .= " -det_id $det_id";
+        $command .= " -exp_id $exp_id";
+        $command .= " -class_id $class_id";
+	$command .= " -path_base $outroot";
+        $command .= " -code $exit_code";
+        $command .= " -dbname $dbname" if defined $dbname;
+        system ($command);
+    }
+    exit $exit_code;
+}
+
+END {
+    my $status = $?;
+    system("sync") == 0
+        or die "failed to execute sync: $!" ;
+    $? = $status;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_reject_exp.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_reject_exp.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_reject_exp.pl	(revision 22157)
@@ -0,0 +1,371 @@
+#!/usr/bin/env perl
+
+use Carp;
+use warnings;
+use strict;
+
+## report the program and machine
+use Sys::Hostname;
+my $host = hostname();
+print "\n\n";
+print "Starting script $0 on $host\n\n";
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::Stats;
+use PS::IPP::Config 1.01 qw( :standard );
+use PS::IPP::Metadata::List qw( parse_md_list );
+use Statistics::Descriptive;
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+my $ITER_LIMIT = 20;
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+my ($det_id, $iter, $det_type, $camera, $outroot, $filter, $dbname, $verbose, $no_update, $no_op, $redirect);
+GetOptions(
+    'det_id|d=s'        => \$det_id,
+    'iteration=s'       => \$iter,
+    'det_type|t=s'      => \$det_type,
+    'camera=s'          => \$camera,
+    'outroot|w=s'       => \$outroot,   # output file base name
+    'filter=s'          => \$filter,
+    'dbname|d=s'        => \$dbname, # Database name
+    'verbose'           => \$verbose,   # Print to stdout
+    'no-update'         => \$no_update,
+    'no-op'             => \$no_op,
+    'redirect-output'   => \$redirect,
+) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options: --det_id --iteration --det_type --camera --outroot",
+           -exitval => 3) unless
+    defined $det_id   and
+    defined $iter     and
+    defined $det_type and
+    defined $camera   and
+    defined $outroot;
+
+# check for existing directory, generate if needed
+$ipprc->outroot_prepare($outroot);
+
+my $logName = "$outroot.log"; # Name for log
+
+$ipprc->redirect_output($logName) if $redirect;
+
+# values to extract from output metadata and the stats to calculate
+# XXX -bg_mean_stdev should take rms of bg_mean_stdev if bg_mean_stdev != 0 (A)
+# XXX -bg_mean_stdev should take stdev of bg_mean if bg_mean_stdev == 0     (B)
+# XXX  (A) if imfile.Ncomp > 1, (B) if imfile.Ncomp == 1
+my $STATS =
+   [
+       #          KEYWORD                 STATISTIC          CHIPTOOL FLAG
+       { name => "bg",             type => "mean",  flag => "-bg",            dtype => "float" },
+       { name => "bg_mean_stdev",  type => "stdev", flag => "-bg_mean_stdev", dtype => "float" },
+       { name => "bg_stdev",       type => "rms",   flag => "-bg_stdev",      dtype => "float" },
+   ];
+my $stats = PS::IPP::Metadata::Stats->new($STATS); # Stats parser
+
+# these stats are used it the rejections but not passed to the database
+# there is some duplication with the above, but the calculation time is minimal
+my $REJSTATS =
+   [
+       #          KEYWORD                 STATISTIC          CHIPTOOL FLAG
+       { name => "bg",             type => "clipmean",  flag => "ensMeanMean",       dtype => "float" },
+       { name => "bg",             type => "clipstdev", flag => "ensMeanStdev",      dtype => "float" },
+       { name => "bg_mean_stdev",  type => "clipmean",  flag => "ensMeanStdevMean",  dtype => "float" },
+       { name => "bg_mean_stdev",  type => "clipstdev", flag => "ensMeanStdevStdev", dtype => "float" },
+       { name => "bg_stdev",       type => "clipmean",  flag => "ensStdevMean",      dtype => "float" },
+       { name => "bg_stdev",       type => "clipstdev", flag => "ensStdevStdev",     dtype => "float" },
+       { name => "bg_skewness",    type => "clipmean",  flag => "ensSkewness",       dtype => "float" },
+       { name => "bg_kurtosis",    type => "clipmean",  flag => "ensKurtosis",       dtype => "float" },
+
+   ];
+my $rejstats = PS::IPP::Metadata::Stats->new($REJSTATS); # Stats parser
+
+# Look for programs we need
+my $missing_tools;
+my $dettool = can_run('dettool') or (warn "Can't find dettool" and $missing_tools = 1);
+if ($missing_tools) {
+    warn("Can't find required tools.");
+    exit($PS_EXIT_CONFIG_ERROR);
+}
+
+# Get list of component files
+my ($exposures, $command, $success, $error_code, $full_buf, $stdout_buf, $stderr_buf);
+{
+    # dettool command to select exp data for this det_run
+    $command = "$dettool -residexp";
+    $command .= " -det_id $det_id";
+    $command .= " -iteration $iter";
+    $command .= " -dbname $dbname" if defined $dbname;
+    ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to perform dettool -residexp: $error_code", $det_id, $iter, $error_code);
+    }
+
+    # Parse the stdout buffer into a metadata
+    my $mdcParser = PS::IPP::Metadata::Config->new;     # Parser for metadata config files
+    my $metadata = $mdcParser->parse(join "", @$stdout_buf) or
+        &my_die("Unable to parse metadata config doc", $det_id, $iter, $PS_EXIT_PROG_ERROR);
+
+    # parse the file info in the metadata
+    $exposures = parse_md_list($metadata) or
+        &my_die("Unable to parse metadata list", $det_id, $iter, $PS_EXIT_PROG_ERROR);
+
+    # Parse the statistics on the residual image
+    $stats->parse($metadata) or &my_die("Unable to find all values in statistics output.", $det_id, $iter, $PS_EXIT_PROG_ERROR);
+
+    # Parse the statistics for rejections
+    $rejstats->parse($metadata) or &my_die("Unable to find all values in statistics output.", $det_id, $iter, $PS_EXIT_PROG_ERROR);
+}
+
+# each image has a mean mean (average of means over all chips)
+# a standard deviation mean (average of standard deviations over all chips)
+# and a
+
+
+
+# we use the statistics of the ensemble to accept/reject exposurs
+my $ensMeanMean       = $rejstats->value_for_flag ("ensMeanMean");       # average of all exposure means (in turn averaged over all chips)
+my $ensMeanStdev      = $rejstats->value_for_flag ("ensMeanStdev");      # standard deviation of all exposure means
+my $ensMeanStdevMean  = $rejstats->value_for_flag ("ensMeanStdevMean");  # average over all exposures of the stdev of the means for each chip
+my $ensMeanStdevStdev = $rejstats->value_for_flag ("ensMeanStdevStdev"); # standard deviation over all exposures of the stdev of the means for each chip
+my $ensStdevMean      = $rejstats->value_for_flag ("ensStdevMean");      # average over all exposures of the sum of the squares of the stdevs for each chip
+my $ensStdevStdev     = $rejstats->value_for_flag ("ensStdevStdev");     # standard deviation over all exposures of the sum of the squares of the stdevs for each chip
+
+$ipprc->define_camera($camera);
+# Rejection thresholds
+my $reject_mean      = rejection_limit( 'ENSEMBLE.MEAN',      $det_type, $filter );
+my $reject_stdev     = rejection_limit( 'ENSEMBLE.STDEV',     $det_type, $filter );
+my $reject_meanstdev = rejection_limit( 'ENSEMBLE.MEANSTDEV', $det_type, $filter );
+
+# outroot examples (HOST components must be set)
+# file://data/ipp004.0/gpc1/20080130
+# neb:///ipp004-v1/gpc1/20080130
+# neb:///*/gpc1/20080130 (volume not specified)
+
+unless ($no_op) {
+    print "Ensemble mean $ensMeanMean +/- $ensMeanStdev\n";
+    print "Ensemble stdev $ensStdevMean +/- $ensStdevStdev\n";
+    print "Ensemble mean rms (over imfiles) $ensMeanStdevMean +/- $ensMeanStdevStdev\n\n";
+}
+
+# Go through again to do rejection, and update the database for each exposure
+my $numChanges = 0;             # Number of exposures with changed status
+my $numReject = 0;              # Number of exposures rejected
+my $firstElement = 1;
+
+foreach my $exposure (@$exposures) {
+    my $mean      = $exposure->{bg};    # Mean for this exposure
+    my $stdev     = $exposure->{bg_stdev}; # Stdev for this exposure
+    my $meanStdev = $exposure->{bg_mean_stdev}; # Stdev of Means for this exposure
+    my $expID     = $exposure->{exp_id};
+    my $accept    = $exposure->{accept};
+    my $include   = $exposure->{include};
+
+    &my_die("Unable to find exposure id.\n", $det_id, $iter, $PS_EXIT_SYS_ERROR) unless defined $expID;
+    &my_die("Unable to find accept.\n",      $det_id, $iter, $PS_EXIT_SYS_ERROR) unless defined $accept;
+    &my_die("Unable to find include.\n",     $det_id, $iter, $PS_EXIT_SYS_ERROR) unless defined $include;
+
+    my $reject = 0;             # Reject this exposure?
+
+    $command  = "$dettool -updateresidexp";
+    $command .= " -det_id $det_id";
+    $command .= " -iteration $iter";
+    $command .= " -exp_id $expID";
+    $command .= " -dbname $dbname" if defined $dbname;
+
+    if (not $accept) {
+        # Rejected this at an earlier stage
+        unless ($no_op) {
+            print "Rejecting $expID based on earlier determination.\n";
+        }
+        $reject = 1;
+        goto UPDATE;
+    }
+
+    # Cop-out if we're not doing any operations
+    if ($no_op) {
+        # Make sure something gets rejected (just once!), just so that
+        # we can trace the full range of the workflow
+        if ($firstElement and $iter == 0) {
+            $reject = 1;
+        }
+        goto UPDATE;
+    }
+
+    if ($reject_mean > 0 and $ensMeanStdev > 0) {
+        my $delta = abs($mean - $ensMeanMean);
+        if ($delta > ($reject_mean * $ensMeanStdev)) {
+            print "Rejecting $expID based on ensemble mean value: ";
+            $reject = 1;
+            #goto UPDATE;
+        } else {
+            print "$expID OK against ensemble mean: ";
+        }
+        print "$mean --> $delta vs " . $reject_mean * $ensMeanStdev . "\n";
+    } else {
+        print "No rejection of $expID for ensemble mean\n";
+    }
+
+    if ($reject_stdev > 0 and $ensStdevStdev > 0) {
+        my $delta = abs($stdev - $ensStdevMean);
+        if ($delta > ($reject_stdev * $ensStdevStdev)) {
+            print "Rejecting $expID based on ensemble stdev: ";
+            $reject = 1;
+            #goto UPDATE;
+        } else {
+            print "$expID OK against ensemble stdev: ";
+        }
+        print "$stdev --> $delta sigma vs " . $reject_stdev * $ensStdevStdev . "\n";
+    } else {
+        print "No rejection of $expID for ensemble stdev\n";
+    }
+
+    if ($reject_meanstdev > 0 and $ensMeanStdevStdev > 0) {
+        my $delta = abs($meanStdev - $ensMeanStdevMean);
+        if ($delta > ($reject_meanstdev * $ensMeanStdevStdev)) {
+            print "Rejecting $expID based on ensemble mean stdev: ";
+            $reject = 1;
+            #goto UPDATE;
+        } else {
+            print "$expID OK against ensemble mean stdev: ";
+        }
+        print "$meanStdev --> $delta sigma vs " . $reject_meanstdev * $ensMeanStdevStdev. "\n";
+    } else {
+        print "No rejection of $expID for ensemble mean stdev\n";
+    }
+
+  UPDATE:
+    if ($reject) {
+        $command .= ' -reject';
+        $numReject++;
+    }
+
+    # Check for status changes
+    if ((not $include and not $reject) or ($include and $reject)) {
+        unless ($no_op) {
+            print "Status of $expID has changed.\n";
+        }
+        $numChanges++;
+    }
+
+    unless ($no_update) {
+        # Update
+        ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+            run(command => $command, verbose => $verbose);
+        unless ($success) {
+            $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+            &my_die("Unable to perform dettool -updateresidexp: $error_code", $det_id, $iter, $error_code);
+        }
+    }
+}
+
+# Decide if the current is sufficient to use as a master, and if we can stop iterating
+my $master = 1;                 # This is good enough for a master
+my $stop = 1;                   # Stop iterating
+
+if ($numChanges > 0) {
+    $master = 0;
+    $stop = 0;
+}
+
+# Rejecting everything --- stop before something bad happens!
+if ($numReject == scalar @$exposures) {
+    $master = 0;
+    $stop = 1;
+    carp "All inputs rejected!\n";
+}
+
+unless ($no_op) {
+    print "Master: $master\n";
+    print "Stop: $stop\n";
+}
+
+# Allow iteration to be turned off
+my $allow_iter = metadataLookupBool($ipprc->{rejection}, "ITERATION"); # Allow iteration?
+my $force_master = metadataLookupBool($ipprc->{rejection}, "MASTER"); # Force the stack to be accepted
+$stop = 1 unless $allow_iter;
+$master = 1 if $force_master and $stop;
+
+# attempt to prevent endless, pathological iterations
+if ($iter >= $ITER_LIMIT) {
+    warn("iteration limit reached -- bailing out");
+    exit($PS_EXIT_PROG_ERROR);
+}
+
+## add the summary statistics, and request a new iteration if needed
+$command = "$dettool -adddetrunsummary";
+$command .= " -det_id $det_id";
+$command .= " -iteration $iter";
+$command .= " -accept" if $master;
+$command .= " -dbname $dbname" if defined $dbname;
+$command .= " -again" unless $stop;
+$command .= $stats->cmdflags();
+
+# Put results into the database
+unless ($no_update) {
+    ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        warn("Unable to perform dettool -adddetrunsummary: $error_code");
+        exit($error_code);
+    }
+} else {
+    print "skipping command: $command\n";
+}
+
+END {
+    my $status = $?;
+    system("sync") == 0
+        or die "failed to execute sync: $!" ;
+    $? = $status;
+}
+
+
+sub my_die
+{
+    my $msg = shift; # Warning message on die
+    my $det_id = shift;         # Detrend identifier
+    my $iter = shift;           # Iteration
+    my $exit_code = shift; # Exit code to add
+
+    carp($msg);
+    if (defined $det_id and defined $iter and not $no_update) {
+        my $command = "$dettool -adddetrunsummary";
+        $command .= " -det_id $det_id";
+        $command .= " -iteration $iter";
+	# XXX EAM : we should add this to the db : $command .= " -path_base $outroot";
+        $command .= " -code $exit_code";
+        $command .= " -dbname $dbname" if defined $dbname;
+        system ($command);
+    }
+    exit $exit_code;
+}
+
+# Retrieve the requested rejection limit, dying unless extant
+sub rejection_limit
+{
+    my $name = shift;           # Rejection limit to
+    my $type = shift;           # Type of exposure
+    my $filter = shift;         # Filter
+
+    my $value = $ipprc->rejection( $name, $det_type, $filter );
+    if (not defined $value) {
+        $filter = "(no filter)" unless defined $filter;
+        die "Unable to determine $name rejection limit for $det_type with $filter.\n";
+    }
+
+    return $value;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_resid_exp.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_resid_exp.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_resid_exp.pl	(revision 22157)
@@ -0,0 +1,601 @@
+#!/usr/bin/env perl
+
+# this program has two jobs:
+# 1: for the given exp_tag, generate the binned & mosaiced JPEG images
+# 2: examine the collection of per-imfile statistics and reject.  At the moment,
+#    this program (and the database) only allows rejection at the exposure level,
+#    not at the imfile level.
+
+use Carp;
+use warnings;
+use strict;
+
+## report the program and machine
+use Sys::Hostname;
+my $host = hostname();
+print "\n\n";
+print "Starting script $0 on $host\n\n";
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use IPC::Cmd 0.36 qw( can_run run );             # tools to run UNIX programs with control over I/O
+use PS::IPP::Metadata::Config;                   # tools to parse the psMetadataConfig files
+use PS::IPP::Metadata::Stats;
+
+use PS::IPP::Metadata::List qw( parse_md_list ); # tools to parse a metadata into a hash list
+use Statistics::Descriptive;                     # tools for calculating basic statistical quantities
+use File::Temp qw( tempfile );                   # tools to construct temp files
+use PS::IPP::Config 1.01 qw( :standard );
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt ); # option parsing
+use Pod::Usage qw( pod2usage );
+
+# parse the command-line options
+my ( $det_id, $iter, $exp_id, $exp_tag, $det_type, $camera, $filter, $reject, $outroot, $dbname, $reduction,
+     $verbose, $no_update, $no_op, $save_temps, $redirect );
+GetOptions(
+           'det_id|d=s'        => \$det_id,
+           'iteration=s'       => \$iter,
+           'exp_id|e=s'        => \$exp_id,
+           'exp_tag|=s'        => \$exp_tag,
+           'det_type|t=s'      => \$det_type,
+           'camera=s'          => \$camera,
+           'outroot|w=s'       => \$outroot,   # output file base name
+           'filter=s'          => \$filter,
+           'reject'            => \$reject,
+           'dbname|d=s'        => \$dbname, # Database name
+           'reduction|=s'      => \$reduction,
+           'verbose'           => \$verbose,   # Print to stdout
+           'no-update'         => \$no_update,
+           'no-op'             => \$no_op,
+           'save-temps'        => \$save_temps, # Save temporary files?
+           'redirect-output'   => \$redirect,   # redirect output to LOG.EXP
+           ) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options: --det_id --iteration --exp_id --exp_tag --det_type --camera --outroot",
+           -exitval => 3) unless
+    defined $det_id   and
+    defined $iter     and
+    defined $exp_id   and
+    defined $exp_tag  and
+    defined $det_type and
+    defined $camera   and
+    defined $outroot;
+
+# load IPP config information for the specified camera
+$ipprc->define_camera($camera);
+if ($redirect) {
+    my $logDest = $ipprc->filename("LOG.EXP", $outroot)
+       or &my_die("Missing entry from camera config", $det_id, $iter, $exp_id, $PS_EXIT_CONFIG_ERROR);
+    $ipprc->redirect_output($logDest);
+}
+
+# Recipes to use based on reduction class
+$reduction = 'DETREND' unless defined $reduction;
+
+my $recipe1 = $ipprc->reduction($reduction, 'JPEG_BIN1_RESID_' . uc($det_type)); # Recipe to use
+&my_die("Unrecognised detrend type: $det_type", $det_id, $iter, $PS_EXIT_PROG_ERROR) unless defined $recipe1;
+
+my $recipe2 = $ipprc->reduction($reduction, 'JPEG_BIN2_RESID_' . uc($det_type)); # Recipe to use
+&my_die("Unrecognised detrend type: $det_type", $det_id, $iter, $PS_EXIT_PROG_ERROR) unless defined $recipe2;
+
+# values to extract from output metadata and the stats to calculate
+# XXX -bg_mean_stdev should take rms of bg_mean_stdev if bg_mean_stdev != 0 (A)
+# XXX -bg_mean_stdev should take stdev of bg_mean if bg_mean_stdev == 0     (B)
+# XXX  (A) if imfile.Ncomp > 1, (B) if imfile.Ncomp == 1
+my $STATS =
+    [
+        #          KEYWORD                 STATISTIC          CHIPTOOL FLAG
+        { name => "bg",             type => "mean",  flag => "-bg",            dtype => "float" },
+        { name => "bg_mean_stdev",  type => "stdev", flag => "-bg_mean_stdev", dtype => "float" },
+        { name => "bg_stdev",       type => "rms",   flag => "-bg_stdev",      dtype => "float" },
+        { name => "bg_skewness",    type => "mean",  flag => "-bg_skewness",   dtype => "float" },
+        { name => "bg_kurtosis",    type => "mean",  flag => "-bg_kurtosis",   dtype => "float" },
+        { name => "bin_stdev",      type => "rms",   flag => "-bin_stdev",     dtype => "float" },
+        { name => "fringe_0",       type => "mean",  flag => "-fringe_0",      dtype => "float" },
+        { name => "fringe_1",       type => "rms",   flag => "-fringe_1",      dtype => "float" },
+        { name => "fringe_0",       type => "stdev", flag => "-fringe_2",      dtype => "float" },
+        { name => "user_1",         type => "mean",  flag => "-user_1",        dtype => "float" }, # fringe residual
+        { name => "user_2",         type => "rms",   flag => "-user_2",        dtype => "float" }, # fringe residual
+        { name => "user_1",         type => "stdev", flag => "-user_3",        dtype => "float" }, # fringe residual
+        ];
+my $stats = PS::IPP::Metadata::Stats->new($STATS); # Stats parser
+
+# Look for programs we need
+my $missing_tools;
+my $dettool = can_run('dettool') or (warn "Can't find dettool" and $missing_tools = 1);
+my $ppImage = can_run('ppImage') or (warn "Can't find ppImage" and $missing_tools = 1);
+if ($missing_tools) {
+    warn("Can't find required tools.");
+    exit($PS_EXIT_CONFIG_ERROR);
+}
+
+# Get list of imfile files
+my ($files, $command, $success, $error_code, $full_buf, $stdout_buf, $stderr_buf);
+{
+    # dettool command to select imfile data for this exp_id
+    $command  = "$dettool -residimfile";
+    $command .= " -det_id $det_id";
+    $command .= " -iteration $iter";
+    $command .= " -exp_id $exp_id";
+    $command .= " -dbname $dbname" if defined $dbname;
+    ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        warn("Unable to perform dettool -residimfile: $error_code\n");
+        exit($error_code);
+    }
+    # XXX report an error message if stdout_buf is empty
+
+    # Parse the stdout buffer into a metadata
+    my $mdcParser = PS::IPP::Metadata::Config->new;
+    my $metadata = $mdcParser->parse(join "", @$stdout_buf) or
+        &my_die("Unable to parse metadata config doc", $det_id, $iter, $exp_id, $PS_EXIT_PROG_ERROR);
+
+    # parse the file info in the metadata
+    $files = parse_md_list($metadata) or
+        &my_die("Unable to parse metadata list", $det_id, $iter, $exp_id, $PS_EXIT_PROG_ERROR);
+
+    # Parse the statistics on the residual image
+    $stats->parse($metadata) or &my_die("Unable to find all values in statistics output.", $det_id, $iter, $exp_id, $PS_EXIT_PROG_ERROR);
+}
+
+# outroot examples (HOST components must be set)
+# file://data/ipp004.0/gpc1/20080130
+# neb:///ipp004-v1/gpc1/20080130
+# neb:///*/gpc1/20080130 (volume not specified)
+
+# check for existing directory, generate if needed
+$ipprc->outroot_prepare($outroot);
+
+my $jpeg1Name  = $ipprc->filename("PPIMAGE.JPEG1", $outroot); # Binned JPEG #1
+my $jpeg2Name  = $ipprc->filename("PPIMAGE.JPEG2", $outroot); # Binned JPEG #2
+
+# XXX in debug mode, unlink = 0
+my ($list1File, $list1Name) = tempfile( "$exp_tag.detresid.$det_id.$iter.b1.list.XXXX", UNLINK => !$save_temps );
+my ($list2File, $list2Name) = tempfile( "$exp_tag.detresid.$det_id.$iter.b2.list.XXXX", UNLINK => !$save_temps );
+foreach my $file (@$files) {
+    print $list1File ($ipprc->filename( "PPIMAGE.BIN1", $file->{path_base}, $file->{class_id} ) . "\n");
+    print $list2File ($ipprc->filename( "PPIMAGE.BIN2", $file->{path_base}, $file->{class_id} ) . "\n");
+}
+close $list1File;
+close $list2File;
+
+
+# build the JPEG images
+unless ($no_op) {
+    # Make the jpeg for binning 1
+    $command = "$ppImage -list $list1Name $outroot -recipe PPIMAGE $recipe1"; # Command to run
+    $command .= " -dbname $dbname" if defined $dbname;
+    ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to run ppImage: $error_code", $det_id, $iter, $exp_id, $error_code);
+    }
+    &my_die("Unable to find expected output file: $jpeg1Name", $det_id, $iter, $exp_id, $PS_EXIT_SYS_ERROR) unless -f $ipprc->file_resolve($jpeg1Name);
+
+    # Make the jpeg for binning 2
+    $command = "$ppImage -list $list2Name $outroot -recipe PPIMAGE $recipe2"; # Command to run
+    $command .= " -dbname $dbname" if defined $dbname;
+    ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to run ppImage: $error_code", $det_id, $iter, $exp_id, $error_code);
+    }
+    &my_die("Unable to find expected output file: $jpeg2Name", $det_id, $iter, $exp_id, $PS_EXIT_SYS_ERROR) unless -f $ipprc->file_resolve($jpeg2Name);
+}
+
+#### measure stats and reject if needed ####
+
+# Rejection thresholds & related data
+my $expected                = rejection_limit( 'EXPECTED',         $det_type, $filter );
+my $reject_imfile_mean      = rejection_limit( 'IMFILE.MEAN',      $det_type, $filter );
+my $reject_imfile_flux      = rejection_limit( 'IMFILE.FLUX',      $det_type, $filter );
+my $reject_imfile_stdev     = rejection_limit( 'IMFILE.STDEV',     $det_type, $filter );
+my $reject_imfile_skewness  = rejection_limit( 'IMFILE.SKEWNESS',  $det_type, $filter );
+my $reject_imfile_kurtosis  = rejection_limit( 'IMFILE.KURTOSIS',  $det_type, $filter );
+my $reject_imfile_meanstdev = rejection_limit( 'IMFILE.MEANSTDEV', $det_type, $filter );
+my $reject_imfile_snr       = rejection_limit( 'IMFILE.SNR',       $det_type, $filter );
+my $reject_imfile_bin_stdev = rejection_limit( 'IMFILE.BIN.STDEV', $det_type, $filter );
+my $reject_imfile_bin_snr   = rejection_limit( 'IMFILE.BIN.SNR',   $det_type, $filter );
+my $reject_exp_mean         = rejection_limit( 'EXP.MEAN',         $det_type, $filter );
+my $reject_exp_flux         = rejection_limit( 'EXP.FLUX',         $det_type, $filter );
+my $reject_exp_stdev        = rejection_limit( 'EXP.STDEV',        $det_type, $filter );
+my $reject_exp_meanstdev    = rejection_limit( 'EXP.MEANSTDEV',    $det_type, $filter );
+my $reject_exp_snr          = rejection_limit( 'EXP.SNR',          $det_type, $filter );
+my $reject_exp_bin_stdev    = rejection_limit( 'EXP.BIN.STDEV',    $det_type, $filter );
+my $reject_exp_bin_snr      = rejection_limit( 'EXP.BIN.SNR',      $det_type, $filter );
+
+# storage array
+my @fluxes;
+
+foreach my $file (@$files) {
+    my $name      = $file->{class_id};
+    my $mean      = $file->{bg};        # Mean for this imfile
+    my $stdev     = $file->{bg_stdev}; # Stdev for this imfile
+    my $skewness  = $file->{bg_skewness}; # Skewness for this imfile
+    my $kurtosis  = $file->{bg_kurtosis}; # Kurtosis for this imfile
+    my $meanStdev = $file->{bg_mean_stdev}; # Stdev of Means for this imfile
+    my $binStdev  = $file->{bin_stdev}; # Binned Stdev for this imfile
+
+    # calculate and save the fluxes
+    my $flux;
+    if ($file->{exp_time} == 0.0) {
+        $flux = $mean;
+    } else {
+        $flux = $mean / $file->{exp_time};
+    }
+    push @fluxes, $flux;
+
+    $mean -= $expected;
+
+    last if $no_op;
+
+    # reject exposure if, for any imfiles, the mean residual counts
+    # deviate from the expected value by more than N sigma, (sigma =
+    # total pixel variance).  this test is sensible for images which
+    # should have a predictable nominal residual mean count value (eg,
+    # 0.0 for a bias).   in general, use with ADDITIVE components
+    if ($reject_imfile_mean > 0) {
+        if (abs($mean) > $reject_imfile_mean * $stdev) {
+            print "Rejecting exposure based on bad imfile mean for $name: ";
+            $reject = 1;
+        } else {
+            print "Imfile mean for $name meets requirements: ";
+        }
+        print "$mean vs $reject_imfile_mean * $stdev\n";
+    }  else {
+        print "No rejection on imfile mean for $name\n";
+    }
+
+    # reject exposure if, for any imfiles, the mean residual flux
+    # deviates from the expected value by more than N sigma, (sigma =
+    # total pixel variance).  this test is sensible for images which
+    # should have a predictable nominal residual flux value (eg, 0.0
+    # for a bias).  in general, use with ADDITIVE components
+    if ($reject_imfile_flux > 0) {
+        if (abs($flux) > $reject_imfile_flux) {
+            print "Rejecting exposure based on bad imfile flux for $name: ";
+            $reject = 1;
+        } else {
+            print "Imfile flux for $name meets requirements: ";
+        }
+        print "$flux vs $reject_imfile_flux\n";
+    }  else {
+        print "No rejection on imfile flux for $name\n";
+    }
+
+    # reject exposure if, for any imfiles, the total pixel variance is
+    # larger than the limit
+    if ($reject_imfile_stdev > 0) {
+        if ($stdev > $reject_imfile_stdev) {
+            print "Rejecting exposure based on bad imfile stdev for $name: ";
+            $reject = 1;
+        } else {
+            print "Imfile stdev for $name meets requirements: ";
+        }
+        print "$stdev vs $reject_imfile_stdev\n";
+
+    } else {
+        print "No rejection on imfile stdev for $name\n";
+    }
+
+    # reject exposure if, for any imfiles, the skewness is
+    # larger than the limit
+    if ($reject_imfile_skewness > 0) {
+        if ($stdev > $reject_imfile_skewness) {
+            print "Rejecting exposure based on bad imfile skewness for $name: ";
+            $reject = 1;
+        } else {
+            print "Imfile skewness for $name meets requirements: ";
+        }
+        print "$skewness vs $reject_imfile_skewness\n";
+
+    } else {
+        print "No rejection on imfile skewness for $name\n";
+    }
+
+    # reject exposure if, for any imfiles, the kurtosis is
+    # larger than the limit
+    if ($reject_imfile_kurtosis > 0) {
+        if ($stdev > $reject_imfile_kurtosis) {
+            print "Rejecting exposure based on bad imfile kurtosis for $name: ";
+            $reject = 1;
+        } else {
+            print "Imfile kurtosis for $name meets requirements: ";
+        }
+        print "$kurtosis vs $reject_imfile_kurtosis\n";
+
+    } else {
+        print "No rejection on imfile kurtosis for $name\n";
+    }
+
+    # reject exposure if, for any imfiles, the variance of the imfile
+    # component means is larger than the limit
+    if ($reject_imfile_meanstdev > 0) {
+        if ($meanStdev > $reject_imfile_meanstdev) {
+            print "Rejecting exposure based on bad imfile mean stdev for $name: ";
+            $reject = 1;
+        } else {
+            print "Imfile mean stdev for $name meets requirements: ";
+        }
+        print "$meanStdev vs $reject_imfile_meanstdev\n";
+    } else {
+        print "No rejection on imfile mean stdev for $name\n";
+    }
+
+    # reject exposure if, for any imfiles, the signal-to-noise (ie,
+    # the mean counts / total pixel variance) of the imfile component
+    # means are less than the limit.  this test is sensible for images
+    # which have finite residual flux such as a flat-field image.
+    if ($reject_imfile_snr > 0) {
+        if ($mean < $stdev * $reject_imfile_snr) {
+            print "Rejecting exposure based on bad imfile S/N for $name: ";
+            $reject = 1;
+        } else {
+            print "Imfile S/N for $name meets requirements: ";
+        }
+        print "mean: $mean vs stdev*SNlimit: " . $stdev * $reject_imfile_snr . "\n";
+    } else {
+        print "No rejection on imfile S/N for $name\n";
+    }
+
+    # reject exposure if, for any imfiles, the signal-to-noise of the
+    # imfile component means, based on the stdev of the binned image
+    # is less than the limit.  this test is sensible for images which
+    # have finite residual flux such as a flat-field image.
+    if ($reject_imfile_bin_stdev > 0) {
+        if ($binStdev > $reject_imfile_bin_stdev) {
+            print "Rejecting exposure based on bad imfile binned stdev for $name: ";
+            $reject = 1;
+        } else {
+            print "Imfile binned stdev for $name meets requirements: ";
+        }
+        print "$binStdev vs $reject_imfile_bin_stdev\n";
+    } else {
+        print "No rejection on imfile binned stdev for $name\n";
+    }
+    if ($reject_imfile_bin_snr > 0) {
+        if ($mean < $binStdev * $reject_imfile_bin_snr) {
+            print "Rejecting exposure based on bad imfile binned S/N for $name: ";
+            $reject = 1;
+        } else {
+            print "Imfile binned S/N for $name meets requirements: ";
+        }
+        print "mean: $mean vs binStdev*SNlimit: " . $binStdev * $reject_imfile_bin_snr . "\n";
+    } else {
+        print "No rejection on imfile binned S/N for $name\n";
+    }
+}
+
+# basic ensemble stats
+my $mean               = $stats->value_for_flag ("-bg");
+my $meanStdev          = $stats->value_for_flag ("-bg_mean_stdev");
+my $stdev              = $stats->value_for_flag ("-bg_stdev");
+my $binStdev           = $stats->value_for_flag ("-bin_stdev");
+my $fringe_mean        = $stats->value_for_flag ("-fringe_0");
+my $fringe_err         = $stats->value_for_flag ("-fringe_1");
+my $fringe_mean_stdev  = $stats->value_for_flag ("-fringe_2");
+my $dfringe_mean       = $stats->value_for_flag ("-fringe_resid_0");
+my $dfringe_err        = $stats->value_for_flag ("-fringe_resid_1");
+my $dfringe_mean_stdev = $stats->value_for_flag ("-fringe_resid_2");
+
+# other stats (flux depends on bg and exp_time)
+my $fluxStats = Statistics::Descriptive::Sparse->new(); # Statistics calculator for means
+$fluxStats->add_data(@fluxes);
+my $flux = $fluxStats->mean();  # Mean of the imfile means
+
+# other stats
+my $exp_snr = 0.0;
+if ($stdev > 0) { $exp_snr = $mean / $stdev; }
+
+## Reject based on the exposure ensemble stats
+# reject if the exposure ensemble mean is deviant
+unless ($no_op) {
+    print "Exposure mean $mean, stdev $stdev, mean stdev $meanStdev, exp s/n: $exp_snr\n";
+
+    # reject exposure if the ensemble mean residual counts deviate
+    # from the expected value by more than N sigma, (sigma = total
+    # pixel variance).  this test is sensible for images which should
+    # have a predictable nominal residual mean count value (eg, 0.0
+    # for a bias).  in general, use with ADDITIVE components
+    if ($reject_exp_mean > 0) {
+        if (abs($mean) > $reject_exp_mean * $stdev) {
+            print "Rejecting exposure based on bad mean counts: ";
+            $reject = 1;
+        } else {
+            print "Exposure mean counts meets requirements: ";
+        }
+        print "mean: $mean, stdev: $stdev (limit is: " . $reject_exp_mean * $stdev . ") \n";
+    } else {
+        print "No rejection for exp mean\n";
+    }
+
+    # reject exposure if, for any imfiles, the mean residual flux
+    # deviates from the expected value by more than N sigma, (sigma =
+    # total pixel variance).  this test is sensible for images which
+    # should have a predictable nominal residual flux value (eg, 0.0
+    # for a bias).  in general, use with ADDITIVE components
+    if ($reject_exp_flux > 0) {
+        if (abs($flux) > $reject_exp_flux * $stdev) {
+            print "Rejecting exposure based on bad mean flux: ";
+            $reject = 1;
+        } else {
+            print "Exposure mean flux meets requirements: ";
+        }
+        print "flux: $flux, stdev: $stdev (limit is: " . $reject_exp_flux * $stdev . ")\n";
+    } else {
+        print "No rejection for exp mean\n";
+    }
+
+    # reject exposure if the total pixel variance is larger than the
+    # limit
+    if ($reject_exp_stdev > 0) {
+        if ($stdev > $reject_exp_stdev) {
+            print "Rejecting exposure based on bad stdev: ";
+            $reject = 1;
+        } else {
+            print "Exposure stdev meets requirements: ";
+        }
+        print "$stdev vs $reject_exp_stdev\n";
+    } else {
+        print "No rejection for exp stdev\n";
+    }
+
+    # reject exposure if the variance of the imfile means is larger
+    # than the limit
+    if ($reject_exp_meanstdev > 0) {
+        if ($meanStdev > $reject_exp_meanstdev) {
+            print "Rejecting exposure based on bad mean stdev: ";
+            $reject = 1;
+        } else {
+            print "Exposure mean stdev meets requirements: ";
+        }
+        print "$meanStdev vs $reject_exp_meanstdev\n";
+    } else {
+        print "No rejection for exp mean stdev\n";
+    }
+
+    # reject if the signal-to-noise is insufficient
+    if ($reject_exp_snr > 0) {
+        if (abs($mean) < abs($stdev * $reject_exp_snr)) {
+            print "Rejecting exposure based on poor S/N: \n";
+            $reject = 1;
+        } else {
+            print "Exposure S/N meets requirements: \n";
+        }
+        print "signal: $mean vs noise: $stdev (s/n limit is: $reject_exp_snr)\n";
+    } else {
+        print "No rejection for exp S/N\n";
+    }
+    # reject if the exposure ensemble stdev is deviant
+    if ($reject_exp_bin_stdev > 0) {
+        if ($binStdev > $reject_exp_bin_stdev) {
+            print "Rejecting exposure based on bad binned stdev: ";
+            $reject = 1;
+        } else {
+            print "Exposure binned stdev meets requirements: ";
+        }
+        print "$stdev vs $reject_exp_bin_stdev\n";
+    } else {
+        print "No rejection for exp binned stdev\n";
+    }
+    # reject if the signal-to-noise is insufficient
+    if ($reject_exp_bin_snr > 0) {
+        if (abs($mean) < abs($binStdev * $reject_exp_bin_snr)) {
+            print "Rejecting exposure based on poor binned S/N: \n";
+            $reject = 1;
+        } else {
+            print "Exposure binned S/N meets requirements: \n";
+        }
+        print "signal: $mean vs noise: $binStdev (s/n limit is: $reject_exp_bin_snr)\n";
+    } else {
+        print "No rejection for exp binned S/N\n";
+    }
+}
+
+$command  = "$dettool -addresidexp -det_id $det_id -iteration $iter -exp_id $exp_id";
+$command .= " -recip $recipe1,$recipe2 -path_base $outroot ";
+$command .= ' -reject' if $reject;
+$command .= " -dbname $dbname" if defined $dbname;
+$command .= $stats->cmdflags();
+
+unless ($no_update) {
+    ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        warn("Unable to perform dettool -addresidexp: $error_code\n");
+        exit($error_code);
+    }
+} else {
+    print "skipping command: $command\n";
+}
+
+END {
+    my $status = $?;
+    system("sync") == 0
+        or die "failed to execute sync: $!" ;
+    $? = $status;
+}
+
+
+sub my_die
+{
+    my $msg = shift; # Warning message on die
+    my $det_id = shift;         # Detrend identifier
+    my $iter = shift;           # Iteration
+    my $exp_id = shift; # Exposure tag
+    my $exit_code = shift; # Exit code to add
+
+    carp($msg);
+    if (defined $det_id and defined $iter and defined $exp_id and not $no_update) {
+        my $command = "$dettool -addresidexp";
+        $command .= " -det_id $det_id";
+        $command .= " -iteration $iter";
+        $command .= " -exp_id $exp_id";
+	$command .= " -path_base $outroot";
+        $command .= " -code $exit_code";
+        $command .= " -dbname $dbname" if defined $dbname;
+        system ($command);
+    }
+    exit $exit_code;
+}
+
+# Retrieve the requested rejection limit, dying if not extant
+sub rejection_limit
+{
+    my $name = shift;           # Rejection limit to
+    my $type = shift;           # Type of exposure
+    my $filter = shift;         # Filter
+
+    my $value = $ipprc->rejection( $name, $det_type, $filter );
+    if (not defined $value) {
+        $filter = "(no filter)" if not defined $filter;
+        die "Unable to determine $name rejection limit for $det_type with $filter.\n";
+    }
+
+    return $value;
+}
+
+__END__
+
+####
+
+## this function is not well though out.  it loads a collection of
+## values (background mean and stdev) for a collection of imfile
+## subcomponents.  it then measures the mean background, the
+## background stdev (rms of ensemble stdevs), and the background mean
+## stdev (rms of the means).  it then tries to reject based on the
+## following criteria:
+## (background / stdev > limit) for a single component
+##    ---> this will ACCEPT for a poor images (large stdev)
+## (stdev > limit)
+## (mean background / stdev > limit) same problem
+## (mean stdev > limit)
+
+### I would suggest the following:
+## 1) calculate the ensemble mean and stdev
+## 2) for each component, is the (back - mean) / mean stdev > limit?
+##    (ie, is it an outlier?)
+## 3) is the component stdev > limit? (absolute test)
+## 4) calculate the background stdev for the ensemble (excluding rejects)
+## 5) is the mean stdev > limit (poor images)
+## 6) calculate the ensemble stdev (rms) of remaining (excluding rejects)
+## 7) is the ensemble stdev > limit
+
+## for flats, the value used for stdev should be the fractional stdev
+## (stdev/mean) and no rejection should be performed on the basis of
+## the mean value, but could still be done on the mean stdev
+
+## the same logical cuts above can be applied to components in an
+## imfile, imfiles in an exposure, and exposures in a stack
+
+### this function needs to avoid div by zero: compare Signal vs Noise*limit
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_resid_imfile.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_resid_imfile.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_resid_imfile.pl	(revision 22157)
@@ -0,0 +1,265 @@
+#!/usr/bin/env perl
+
+use Carp;
+use warnings;
+use strict;
+
+## report the program and machine
+use Sys::Hostname;
+my $host = hostname();
+print "\n\n";
+print "Starting script $0 on $host\n\n";
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::Stats;
+use PS::IPP::Config 1.01 qw( :standard );
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+my ( $det_id, $iter, $exp_id, $exp_tag, $class_id, $det_type, $detrend, $input_uri, $camera, $mode, $outroot,
+     $dbname, $reduction, $verbose, $no_update, $no_op, $redirect );
+GetOptions(
+    'det_id|d=s'        => \$det_id,
+    'iteration=s'       => \$iter,
+    'exp_id|e=s'        => \$exp_id,
+    'exp_tag|=s'        => \$exp_tag,
+    'class_id|i=s'      => \$class_id,
+    'det_type|t=s'      => \$det_type,
+    'detrend=s'         => \$detrend,
+    'input_uri|u=s'     => \$input_uri,
+    'camera|c=s'        => \$camera,
+    'mode|m=s'          => \$mode,
+    'outroot|w=s'       => \$outroot,   # output file base name
+    'dbname|d=s'        => \$dbname, # Database name
+    'reduction=s'       => \$reduction, # Reduction class
+    'verbose'           => \$verbose,   # Print to stdout
+    'no-update'         => \$no_update,
+    'no-op'             => \$no_op,
+    'redirect-output'   => \$redirect,
+) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options: --det_id --iteration --exp_id --exp_tag --class_id --det_type --camera --input_uri --mode --detrend --outroot (not for 'verify' mode)",
+           -exitval => 3) unless
+    defined $det_id    and
+    defined $iter      and
+    defined $exp_id    and
+    defined $exp_tag   and
+    defined $class_id  and
+    defined $det_type  and
+    defined $input_uri and
+    defined $camera    and
+    defined $mode      and
+    defined $outroot   and
+    (defined $detrend or lc($mode) eq 'verify');
+
+$ipprc->define_camera($camera);
+my $logDest     = $ipprc->filename("LOG.IMFILE", $outroot, $class_id);
+if ($redirect) {
+    $ipprc->redirect_output($logDest);
+}
+
+# Recipes to use as a function of detrend type and mode
+$reduction = 'DETREND' unless defined $reduction;
+my $recipe;                     # Name of recipe to use
+if ($mode eq 'master') {
+    $recipe = uc($det_type) . '_RESID';
+} elsif ($mode eq 'verify') {
+    $recipe = uc($det_type) . '_VERIFY';
+} else {
+    &my_die("Unrecognised mode: $mode", $det_id, $iter, $exp_id, $class_id, $PS_EXIT_PROG_ERROR);
+}
+$recipe = $ipprc->reduction($reduction, $recipe);
+
+# values to extract from output metadata and the stats to calculate
+my $STATS =
+   [
+       #          PPSTATS KEYWORD         STATISTIC          CHIPTOOL FLAG
+       { name => "ROBUST_MEDIAN",      type => "mean",  flag => "-bg",             dtype => "float" },
+       { name => "ROBUST_MEDIAN",      type => "stdev", flag => "-bg_mean_stdev",  dtype => "float" },
+       { name => "ROBUST_STDEV",       type => "rms",   flag => "-bg_stdev",       dtype => "float" },
+       { name => "SAMPLE_SKEWNESS",    type => "mean",  flag => "-bg_skewness",    dtype => "float" },
+       { name => "SAMPLE_KURTOSIS",    type => "mean",  flag => "-bg_kurtosis",    dtype => "float" },
+       { name => "FRINGE_0",           type => "mean",  flag => "-fringe_0",       dtype => "float" },
+       { name => "FRINGE_ERR_0",       type => "rms",   flag => "-fringe_1",       dtype => "float" },
+       { name => "FRINGE_0",           type => "stdev", flag => "-fringe_2",       dtype => "float" },
+       { name => "FRINGE_RESID_0",     type => "mean",  flag => "-fringe_resid_0", dtype => "float" },
+       { name => "FRINGE_RESID_ERR_0", type => "rms",   flag => "-fringe_resid_1", dtype => "float" },
+       { name => "FRINGE_RESID_0",     type => "stdev", flag => "-fringe_resid_2", dtype => "float" },
+   ];
+my $stats = PS::IPP::Metadata::Stats->new($STATS); # Stats parser
+
+my $BINNED_STATS =
+   [
+       { name => "ROBUST_STDEV",   type => "rms",   flag => "-bin_stdev" },
+   ];
+my $binnedStats = PS::IPP::Metadata::Stats->new($BINNED_STATS); # Stats parser
+
+# Flags to specify the particular detrend to use
+use constant DETRENDS => {
+    'bias'     	       => '-bias',      # Specify the bias frame
+    'dark'     	       => '-dark',      # Specify the dark frame
+    'dark_premask'     => '-dark',      # Specify the dark frame
+    'shutter'  	       => '-shutter',   # Specify the shutter frame
+    'flat'     	       => '-flat',      # Specify the flat frame
+    'domeflat' 	       => '-flat',      # Specify the flat frame
+    'skyflat'  	       => '-flat',      # Specify the flat frame
+    'flat_premask'     => '-flat',      # Specify the flat frame
+    'domeflat_premask' => '-flat',      # Specify the flat frame
+    'skyflat_premask'  => '-flat',      # Specify the flat frame
+    'fringe'   	       => '-fringe',    # Specify the fringe frame
+    'mask'     	       => '-mask',      # Specify the mask frame
+    'darkmask' 	       => '-mask',      # Specify the mask frame
+    'flatmask' 	       => '-mask',      # Specify the mask frame
+};
+
+use constant DELETE_STATS => 0; # Delete the statistics file when done?
+
+# Look for programs we need
+my $missing_tools;
+my $dettool = can_run('dettool') or (warn "Can't find dettool" and $missing_tools = 1);
+my $ppImage = can_run('ppImage') or (warn "Can't find ppImage" and $missing_tools = 1);
+my $ppStats = can_run('ppStats') or (warn "Can't find ppStats" and $missing_tools = 1);
+if ($missing_tools) {
+    warn("Can't find required tools.");
+    exit($PS_EXIT_CONFIG_ERROR);
+}
+
+# outroot examples (HOST components must be set)
+# file://data/ipp004.0/gpc1/20080130
+# neb:///ipp004-v1/gpc1/20080130
+# neb:///*/gpc1/20080130 (volume not specified)
+
+# check for existing directory, generate if needed
+$ipprc->outroot_prepare($outroot);
+
+# XXX use PPIMAGE.OUTPUT.RESID for output file (compressed, with pos & neg range allowed)
+# my $outputName  = $ipprc->filename("PPIMAGE.OUTPUT", $outroot, $class_id);
+
+my $outputName  = $ipprc->filename("PPIMAGE.OUTPUT.RESID", $outroot, $class_id);
+my $bin1Name    = $ipprc->filename("PPIMAGE.BIN1",   $outroot, $class_id);
+my $bin2Name    = $ipprc->filename("PPIMAGE.BIN2",   $outroot, $class_id);
+my $outputStats = $ipprc->filename("PPIMAGE.STATS",  $outroot, $class_id);
+my $traceDest   = $ipprc->filename("TRACE.IMFILE",   $outroot, $class_id);
+
+# Run ppImage & ppStats
+unless ($no_op) {
+    my $command = "$ppImage -file $input_uri $outroot";
+    $command .= " -recipe PPIMAGE $recipe";
+    $command .= " -recipe PPSTATS RESIDUAL";
+    $command .= " -F PPIMAGE.OUTPUT PPIMAGE.OUTPUT.RESID";
+    $command .= " -stats $outputStats";
+    $command .= " -tracedest $traceDest -log $logDest";
+    $command .= " -dbname $dbname" if defined $dbname;
+
+    # Detrend to use in processing
+    if (lc($mode) ne 'verify') {
+        my $detFlag = DETRENDS->{lc($det_type)};
+        &my_die("Unrecognised detrend type: $det_type", $det_id, $iter, $exp_id, $class_id, $PS_EXIT_PROG_ERROR) unless defined $detFlag;
+        $command .= " $detFlag $detrend";
+    }
+
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to perform ppImage: $error_code", $det_id, $iter, $exp_id, $class_id, $error_code);
+    }
+    &my_die("Couldn't find expected output file: $outputName", $det_id, $iter, $exp_id, $class_id, $PS_EXIT_SYS_ERROR) unless -f $ipprc->file_resolve($outputName);
+    &my_die("Couldn't find expected output file: $outputStats", $det_id, $iter, $exp_id, $class_id, $PS_EXIT_SYS_ERROR) unless -f $ipprc->file_resolve($outputStats);
+    &my_die("Couldn't find expected output file: $bin1Name", $det_id, $iter, $exp_id, $class_id, $PS_EXIT_SYS_ERROR) unless -f $ipprc->file_resolve($bin1Name);
+    &my_die("Couldn't find expected output file: $bin2Name", $det_id, $iter, $exp_id, $class_id, $PS_EXIT_SYS_ERROR) unless -f $ipprc->file_resolve($bin2Name);
+
+    # Load the raw output stats file
+    my $statsFile;              # File handle
+    open $statsFile, $ipprc->file_resolve($outputStats) or &my_die("Can't open statistics file $outputStats: $!", $det_id, $iter, $exp_id, $class_id, $PS_EXIT_SYS_ERROR);
+    my @contents = <$statsFile>; # Contents of file
+    close $statsFile;
+
+    # Parse the stats file contents into a metadata
+    my $mdcParser = PS::IPP::Metadata::Config->new;     # Parser for metadata config files
+    my $metadata = $mdcParser->parse(join "", @contents) or &my_die("Unable to parse metadata config doc", $det_id, $iter, $exp_id, $class_id, $PS_EXIT_PROG_ERROR);
+
+    # Parse the statistics on the residual image
+    $stats->parse($metadata) or &my_die("Unable to find all values in statistics output.", $det_id, $iter, $exp_id, $class_id, $PS_EXIT_PROG_ERROR);
+
+    # run ppStats on the binned image
+    $command = "$ppStats -recipe PPSTATS RESIDUAL $bin2Name";
+    ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to perform ppStats: $error_code", $det_id, $iter, $exp_id, $class_id, $error_code);
+    }
+
+    # Parse the output contents into a metadata
+    my $binnedMetadata = $mdcParser->parse(join "", @$stdout_buf) or &my_die("Unable to parse metadata output", $det_id, $iter, $exp_id, $class_id, $PS_EXIT_PROG_ERROR);
+
+    # parse the binned image statistics
+    $binnedStats->parse($binnedMetadata) or &my_die("Unable to find all values in statistics output.", $det_id, $iter, $exp_id, $class_id, $PS_EXIT_PROG_ERROR);
+}
+
+# Command to update the database
+my $command = "$dettool -addresidimfile";
+$command .= " -det_id $det_id";
+$command .= " -iteration $iter";
+$command .= " -exp_id $exp_id";
+$command .= " -class_id $class_id";
+$command .= " -recip $recipe";
+$command .= " -uri $outputName";
+$command .= " -path_base $outroot";
+$command .= " -dbname $dbname" if defined $dbname;
+$command .= $stats->cmdflags();
+$command .= $binnedStats->cmdflags();
+
+# Add the processed file to the database
+unless ($no_update) {
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        warn("Unable to perform dettool -addresidimfile: $error_code\n");
+        exit($error_code);
+    }
+} else {
+    print "skipping command: $command\n";
+}
+
+sub my_die
+{
+    my $msg = shift; # Warning message on die
+    my $det_id = shift;         # Detrend identifier
+    my $iter = shift;           # Iteration
+    my $exp_id = shift; # Exposure tag
+    my $class_id = shift; # Class identifier
+    my $exit_code = shift; # Exit code to add
+
+    carp($msg);
+    if (defined $det_id and defined $iter and defined $exp_id and not $no_update) {
+        my $command = "$dettool -addresidimfile";
+        $command .= " -det_id $det_id";
+        $command .= " -iteration $iter";
+        $command .= " -exp_id $exp_id";
+        $command .= " -class_id $class_id";
+	$command .= " -path_base $outroot";
+        $command .= " -code $exit_code";
+        $command .= " -dbname $dbname" if defined $dbname;
+        system ($command);
+    }
+    exit $exit_code;
+}
+
+END {
+    my $exit = $?;
+    system("sync") == 0 or die "failed to execute sync: $!";
+    $? = $exit;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_stack.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_stack.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/detrend_stack.pl	(revision 22157)
@@ -0,0 +1,261 @@
+#!/usr/bin/env perl
+
+use Carp;
+use warnings;
+use strict;
+
+## report the program and machine
+use Sys::Hostname;
+my $host = hostname();
+print "\n\n";
+print "Starting script $0 on $host\n\n";
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::Stats;
+use PS::IPP::Metadata::List qw( parse_md_list );
+use PS::IPP::Config 1.01 qw( :standard );
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+my ( $det_id, $iter, $class_id, $det_type, $camera, $outroot, $dbname, $reduction, $verbose, $save_temps,
+     $no_update, $no_op, $redirect );
+GetOptions(
+    'det_id|d=s'        => \$det_id,
+    'iteration=s'       => \$iter,
+    'class_id|i=s'      => \$class_id,
+    'det_type|t=s'      => \$det_type,
+    'camera|c=s'        => \$camera,
+    'outroot|w=s'       => \$outroot,   # output file base name
+    'dbname|d=s'        => \$dbname,    # Database name
+    'reduction=s'       => \$reduction, # Reduction class for processing
+    'verbose'           => \$verbose,   # Print to stdout
+    'save-temps'        => \$save_temps, # Save temporary files?
+    'no-update'         => \$no_update,
+    'no-op'             => \$no_op,
+    'redirect-output'   => \$redirect,
+) or pod2usage( 2 );
+
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options: --det_id --iteration --class_id --det_type --camera --outroot",
+           -exitval => 3) unless
+    defined $det_id   and
+    defined $iter     and
+    defined $class_id and
+    defined $det_type and
+    defined $camera   and
+    defined $outroot;
+
+$ipprc->define_camera($camera);
+$det_type = uc($det_type);
+
+my $logDest = $ipprc->filename("LOG.IMFILE", $outroot, $class_id)
+    or &my_die("Missing entry in file rules", $det_id, $iter, $class_id, $PS_EXIT_CONFIG_ERROR);
+
+# optionally redirect the outputs from this script to LOG.IMFILE
+$ipprc->redirect_output($logDest) if $redirect;
+
+# Recipes to use as a function of detrend type
+$reduction = "DETREND" unless defined $reduction;
+my $recipe = $ipprc->reduction($reduction, $det_type . '_STACK'); # Recipe name to use
+
+# values to extract from output metadata and the stats to calculate
+# XXX -bg_mean_stdev should take rms of bg_mean_stdev if bg_mean_stdev != 0 (A)
+# XXX -bg_mean_stdev should take stdev of bg_mean if bg_mean_stdev == 0     (B)
+# XXX  (A) if imfile.Ncomp > 1, (B) if imfile.Ncomp == 1
+my $STATS =
+   [
+       #          KEYWORD                 STATISTIC          CHIPTOOL FLAG
+       { name => "ROBUST_MEDIAN",  type => "mean",  flag => "-bg",             dtype => "float" },
+       { name => "ROBUST_MEDIAN",  type => "stdev", flag => "-bg_mean_stdev",  dtype => "float" },
+       { name => "ROBUST_STDEV",   type => "rms",   flag => "-bg_stdev",       dtype => "float" },
+   ];
+my $stats = PS::IPP::Metadata::Stats->new($STATS); # Stats parser
+
+# Look for programs we need
+my $missing_tools;
+my $dettool = can_run('dettool') or (warn "Can't find dettool" and $missing_tools = 1);
+my $ppMerge = can_run('ppMerge') or (warn "Can't find ppMerge" and $missing_tools = 1);
+if ($missing_tools) {
+    warn("Can't find required tools.");
+    exit($PS_EXIT_CONFIG_ERROR);
+}
+
+# The output file rule name depends on the detrend type
+my $FILERULES = { 'FLATMASK'     => 'PPMERGE.OUTPUT.MASK',
+                  'DARKMASK'     => 'PPMERGE.OUTPUT.MASK',
+                  'MASK'         => 'PPMERGE.OUTPUT.MASK',
+                  'BIAS'         => 'PPMERGE.OUTPUT.BIAS',
+                  'DARK'         => 'PPMERGE.OUTPUT.DARK',
+                  'DARK_PREMASK' => 'PPMERGE.OUTPUT.DARK',
+                  'SHUTTER'      => 'PPMERGE.OUTPUT.SHUTTER',
+                  'FLAT'         => 'PPMERGE.OUTPUT.FLAT',
+                  'FLAT_PREMASK' => 'PPMERGE.OUTPUT.FLAT',
+                  'DOMEFLAT'     => 'PPMERGE.OUTPUT.FLAT',
+                  'SKYFLAT'      => 'PPMERGE.OUTPUT.FLAT',
+                  'FRINGE'       => 'PPMERGE.OUTPUT.FRINGE',
+              };
+my $output_filerule = $FILERULES->{$det_type}; # File rule for output
+&my_die("Unrecognised detrend type: $det_type", $det_id, $iter, $class_id, $PS_EXIT_SYS_ERROR) unless defined $output_filerule;
+
+
+# Get list of files to stack
+my ($files, $command, $success, $error_code, $full_buf, $stdout_buf, $stderr_buf);
+{
+    $command = "$dettool -processedimfile -included";
+    $command .= " -det_id $det_id";
+    $command .= " -class_id $class_id";
+    $command .= " -dbname $dbname" if defined $dbname;
+
+    ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to perform dettool -processedimfile: $error_code", $det_id, $iter, $class_id, $error_code);
+    }
+
+    my $mdcParser = PS::IPP::Metadata::Config->new;     # Parser for metadata config files
+    my $metadata = $mdcParser->parse(join "", @$stdout_buf) or
+        &my_die("Unable to parse metadata config doc", $det_id, $iter, $class_id, $PS_EXIT_PROG_ERROR);
+    $files = parse_md_list($metadata) or
+        &my_die("Unable to parse metadata list", $det_id, $iter, $class_id, $PS_EXIT_PROG_ERROR);
+}
+
+# Generate MDC file with the inputs
+my ($listFile, $listName) = $ipprc->create_temp_file("$outroot.$class_id.list", $save_temps);
+
+my $num = 0;
+foreach my $file (@$files) {
+    if ($file->{ignored}) { next; }
+
+    print $listFile "INPUT$num\tMETADATA\n";
+    $num++;
+
+    my $image = $file->{uri};   # Image name
+    my $mask = $ipprc->filename( "PPIMAGE.OUTPUT.MASK", $file->{path_base}, $class_id ); # Mask name
+    my $weight = $ipprc->filename( "PPIMAGE.OUTPUT.WEIGHT", $file->{path_base}, $class_id ); # Weight name
+
+    &my_die("Image $image does not exist", $det_id, $iter, $class_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists( $image );
+    print $listFile "\tIMAGE\tSTR\t" . $image . "\n";
+
+    if ($ipprc->file_exists( $mask )) {
+        print $listFile "\tMASK\tSTR\t" . $mask . "\n";
+    }
+    if ($ipprc->file_exists( $weight )) {
+        print $listFile "\tWEIGHT\tSTR\t" . $weight . "\n";
+    }
+
+    print $listFile "END\n\n";
+}
+close $listFile;
+
+
+# outroot examples (HOST components must be set)
+# file://data/ipp004.0/gpc1/20080130
+# neb:///ipp004-v1/gpc1/20080130
+# neb:///*/gpc1/20080130 (volume not specified)
+
+# check for existing directory, generate if needed
+$ipprc->outroot_prepare($outroot);
+
+my $outputStack = $ipprc->filename($output_filerule, $outroot, $class_id) or &my_die("Missing entry in file rules", $det_id, $iter, $class_id, $PS_EXIT_CONFIG_ERROR); # Output name
+my $outputCount = $ipprc->filename("PPMERGE.OUTPUT.COUNT", $outroot, $class_id) or &my_die("Missing entry in file rules", $det_id, $iter, $class_id, $PS_EXIT_CONFIG_ERROR); # Count image
+my $outputSigma = $ipprc->filename("PPMERGE.OUTPUT.SIGMA", $outroot, $class_id) or &my_die("Missing entry in file rules", $det_id, $iter, $class_id, $PS_EXIT_CONFIG_ERROR); # Stdev image
+my $outputStats = $ipprc->filename("PPIMAGE.STATS",  $outroot, $class_id) or &my_die("Missing entry in file rules", $det_id, $iter, $class_id, $PS_EXIT_CONFIG_ERROR); # Statistics name
+my $traceDest   = $ipprc->filename("TRACE.IMFILE",   $outroot, $class_id) or &my_die("Missing entry in file rules", $det_id, $iter, $class_id, $PS_EXIT_CONFIG_ERROR); # Trace messages
+
+$command = "$ppMerge $listName $outroot"; # Command to run
+$command .= " -recipe PPMERGE $recipe";
+$command .= ' -type ' . uc($det_type); # Type of stacking to perform
+$command .= " -stats $outputStats";     # Statistics output filename
+$command .= " -recipe PPSTATS CHIPSTATS";
+$command .= " -tracedest $traceDest -log $logDest";
+$command .= " -dbname $dbname" if defined $dbname;
+
+# Stack the files
+unless ($no_op) {
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to perform ppMerge: $error_code", $det_id, $iter, $class_id, $error_code);
+    }
+    &my_die("Unable to find expected output file: $outputStack\n", $det_id, $iter, $class_id, $PS_EXIT_SYS_ERROR) unless -f $ipprc->file_resolve($outputStack);
+    &my_die("Unable to find expected output file: $outputCount\n", $det_id, $iter, $class_id, $PS_EXIT_SYS_ERROR) unless -f $ipprc->file_resolve($outputCount);
+    &my_die("Unable to find expected output file: $outputSigma\n", $det_id, $iter, $class_id, $PS_EXIT_SYS_ERROR) unless -f $ipprc->file_resolve($outputSigma);
+    &my_die("Unable to find expected output file: $outputStats\n", $det_id, $iter, $class_id, $PS_EXIT_SYS_ERROR) unless -f $ipprc->file_resolve($outputStats);
+
+    # Get the statistics on the stacked image
+    open(my $statsFile, $ipprc->file_resolve("$outputStats")) or
+        &my_die("Can't open statistics file $outputStats: $!", $det_id, $iter, $class_id, $PS_EXIT_SYS_ERROR);
+    my $contents = do { local $/; <$statsFile> }; # Contents of file
+    close($statsFile);
+
+    my $mdcParser = PS::IPP::Metadata::Config->new;     # Parser for metadata config files
+    my $metadata = $mdcParser->parse($contents) or
+        &my_die("Unable to parse metadata config doc", $det_id, $iter, $class_id, $PS_EXIT_SYS_ERROR);
+
+    $stats->parse($metadata)  or
+        &my_die("Unable to find all values in statistics output.", $det_id, $iter, $class_id, $PS_EXIT_SYS_ERROR);
+}
+
+# Command to update the database
+$command  = "$dettool -addstacked";
+$command .= " -det_id $det_id";
+$command .= " -iteration $iter";
+$command .= " -class_id $class_id";
+$command .= " -uri $outputStack";
+$command .= " -recip $recipe";
+$command .= " -dbname $dbname" if defined $dbname;
+$command .= $stats->cmdflags();
+
+# Add the resultant into the database
+unless ($no_update) {
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        warn("Unable to perform dettool -addstacked: $error_code\n");
+        exit($error_code);
+    }
+} else {
+    print "skipping command: $command\n";
+}
+
+sub my_die
+{
+    my $msg = shift; # Warning message on die
+    my $det_id = shift;         # Detrend identifier
+    my $iter = shift;           # Iteration
+    my $class_id = shift; # Class identifier
+    my $exit_code = shift; # Exit code to add
+
+    carp($msg);
+    if (defined $det_id and defined $iter and defined $class_id and not $no_update) {
+        my $command = "$dettool -addstacked";
+        $command .= " -det_id $det_id";
+        $command .= " -iteration $iter";
+        $command .= " -class_id $class_id";
+	# XXX EAM : we should add this to the db : $command .= " -path_base $outroot";
+        $command .= " -code $exit_code";
+        $command .= " -dbname $dbname" if defined $dbname;
+        system ($command);
+    }
+    exit $exit_code;
+}
+
+END {
+    my $status = $?;
+    system("sync") == 0
+        or die "failed to execute sync: $!" ;
+    $? = $status;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/diff_skycell.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/diff_skycell.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/diff_skycell.pl	(revision 22157)
@@ -0,0 +1,261 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+## report the program and machine
+use Sys::Hostname;
+my $host = hostname();
+print "\n\n";
+print "Starting script $0 on $host\n\n";
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::Stats;
+use PS::IPP::Metadata::List qw( parse_md_list );
+use Data::Dumper;
+use PS::IPP::Config 1.01 qw( :standard );
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+my ($diff_id, $dbname, $outroot, $verbose, $no_update, $no_op);
+GetOptions(
+    'diff_id|d=s'       => \$diff_id, # Diff identifier
+    'dbname|d=s'        => \$dbname, # Database name
+    'outroot=s'         => \$outroot, # Output root name
+    'verbose'           => \$verbose,   # Print to stdout
+    'no-update'         => \$no_update, # Don't update the database?
+    'no-op'             => \$no_op, # Don't do any operations?
+) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage(
+    -msg => "Required options: --diff_id --outroot",
+    -exitval => 3,
+          ) unless defined $diff_id
+    and defined $outroot;
+
+my $STATS =
+   [
+       #          PPSTATS KEYWORD         STATISTIC          DIFFTOOL FLAG
+       { name => "ROBUST_MEDIAN",   type => "mean", flag => "-bg",         dtype => "float" },
+       { name => "ROBUST_STDEV",    type => "rms",  flag => "-bg_stdev",   dtype => "float" },
+       { name => "DT_SUB",          type => "sum",  flag => "-dtime_diff", dtype => "float" },
+       { name => "SUBTRACTION.NUM", type => "mean", flag => "-stamps_num", dtype => "int" },
+       { name => "SUBTRACTION.RMS", type => "mean", flag => "-stamps_rms", dtype => "float" },
+       { name => "NUM_SOURCES",     type => "sum",  flag => "-sources",    dtype => "int" },
+       { name => "GOOD_PIXEL_FRAC", type => "mean", flag => "-good_frac",  dtype => "float" },
+   ];
+my $stats = PS::IPP::Metadata::Stats->new($STATS); # Stats parser
+
+# Look for programs we need
+my $missing_tools;
+my $difftool = can_run('difftool') or (warn "Can't find difftool" and $missing_tools = 1);
+my $ppSub = can_run('ppSub') or (warn "Can't find ppSub" and $missing_tools = 1);
+if ($missing_tools) {
+    warn("Can't find required tools.");
+    exit($PS_EXIT_CONFIG_ERROR);
+}
+
+# Get list of components for subtraction
+my $mdcParser = PS::IPP::Metadata::Config->new; # Parser for metadata config files
+my $files;
+{
+    my $command = "$difftool -inputskyfile -diff_id $diff_id";
+    $command .= " -dbname $dbname" if defined $dbname;
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to perform difftool -inputskyfile: $error_code", $diff_id, $error_code);
+    }
+
+    my $metadata = $mdcParser->parse(join "", @$stdout_buf) or
+        &my_die("Unable to parse metadata config doc", $diff_id, $PS_EXIT_PROG_ERROR);
+    $files = parse_md_list($metadata) or
+        &my_die("Unable to parse metadata list", $diff_id, $PS_EXIT_PROG_ERROR);
+}
+
+&my_die("Subtraction list contains more than two elements", $diff_id, $PS_EXIT_SYS_ERROR) unless
+    scalar @$files == 2;
+
+# Identify the input and the template
+my ($input, $inputMask, $inputWeight, $inputPath); # Input files and path
+my ($template, $templateMask, $templateWeight, $templatePath, $templateSources); # Template files and path
+my $tess_id;                    # Tesselation identifier
+my $skycell_id;                 # Skycell identifier
+my $camera;                     # Camera
+foreach my $file (@$files) {
+    if (defined $file->{template} and $file->{template}) {
+        $template = $file->{uri};
+        $templatePath = $file->{path_base};
+        if ($file->{warp_id} == 0) {
+            $templateMask = "PPSTACK.OUTPUT.MASK";
+            $templateWeight = "PPSTACK.OUTPUT.WEIGHT";
+            $templateSources = "PSPHOT.OUTPUT";
+        } else {
+            $templateMask = "PSWARP.OUTPUT.MASK";
+            $templateWeight = "PSWARP.OUTPUT.WEIGHT";
+            $templateSources = "PSWARP.OUTPUT.SOURCES";
+        }
+    } else {
+        $input = $file->{uri};
+        $inputPath = $file->{path_base};
+        if ($file->{warp_id} == 0) {
+            $inputMask = "PPSTACK.OUTPUT.MASK";
+            $inputWeight = "PPSTACK.OUTPUT.WEIGHT";
+        } else {
+            $inputMask = "PSWARP.OUTPUT.MASK";
+            $inputWeight = "PSWARP.OUTPUT.WEIGHT";
+        }
+    }
+    if (defined $tess_id) {
+        &my_die("Tesselation identifiers don't match", $diff_id, $PS_EXIT_SYS_ERROR) unless
+            $file->{tess_id} eq $tess_id;
+    } else {
+        $tess_id = $file->{tess_id};
+    }
+    if (defined $skycell_id) {
+        &my_die("Skycell identifiers don't match", $diff_id, $PS_EXIT_SYS_ERROR) unless
+            $file->{skycell_id} eq $skycell_id;
+    } else {
+        $skycell_id = $file->{skycell_id};
+    }
+    if (defined $camera) {
+        &my_die("Cameras don't match", $diff_id, $PS_EXIT_SYS_ERROR) unless $file->{camera} eq $camera;
+    } else {
+        $camera = $file->{camera};
+    }
+
+}
+
+&my_die("Unable to identify template", $diff_id, $PS_EXIT_SYS_ERROR) unless defined $template;
+&my_die("Unable to identify input", $diff_id, $PS_EXIT_SYS_ERROR) unless defined $input;
+&my_die("Unable to identify camera", $diff_id, $PS_EXIT_SYS_ERROR) unless defined $camera;
+$ipprc->define_camera($camera);
+
+$templateMask = $ipprc->filename($templateMask, $templatePath);
+$inputMask = $ipprc->filename($inputMask, $inputPath);
+$templateWeight = $ipprc->filename($templateWeight, $templatePath);
+$inputWeight = $ipprc->filename($inputWeight, $inputPath);
+$templateSources = $ipprc->filename($templateSources, $templatePath);
+
+&my_die("Couldn't find input: $template", $diff_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($template);
+&my_die("Couldn't find input: $templateMask", $diff_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($templateMask);
+&my_die("Couldn't find input: $templateWeight", $diff_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($templateWeight);
+&my_die("Couldn't find input: $input", $diff_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($input);
+&my_die("Couldn't find input: $inputMask", $diff_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($inputMask);
+&my_die("Couldn't find input: $inputWeight", $diff_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($inputWeight);
+&my_die("Couldn't find input: $templateSources", $diff_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($templateSources);
+
+# Get the output filenames
+my $outputName = $ipprc->filename("PPSUB.OUTPUT", $outroot);
+my $outputMask = $ipprc->filename("PPSUB.OUTPUT.MASK", $outroot);
+my $outputWeight = $ipprc->filename("PPSUB.OUTPUT.WEIGHT", $outroot);
+my $outputSources = $ipprc->filename("PSPHOT.OUTPUT", $outroot);
+#my $bin1Name =  $ipprc->filename("PPSUB.BIN1", $outroot);
+#my $bin2Name =  $ipprc->filename("PPSUB.BIN2", $outroot);
+my $outputStats = $ipprc->filename("SKYCELL.STATS", $outroot);
+my $traceDest = $ipprc->filename("TRACE.EXP", $outroot);
+my $logDest = $ipprc->filename("LOG.EXP", $outroot);
+
+# Perform subtraction
+unless ($no_op) {
+    my $command = "$ppSub $input $template $outroot";
+    $command .= " -inmask $inputMask";
+    $command .= " -refmask $templateMask";
+    $command .= " -inweight $inputWeight";
+    $command .= " -refweight $templateWeight";
+    $command .= " -stats $outputStats";
+    $command .= " -recipe PPSTATS WARPSTATS";
+    $command .= " -sources $templateSources";
+    $command .= " -photometry";
+    $command .= " -tracedest $traceDest -log $logDest";
+    $command .= " -dbname $dbname" if defined $dbname;
+
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to perform ppSub: $error_code", $diff_id, $error_code);
+    }
+    &my_die("Couldn't find expected output file: $outputName", $diff_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($outputName);
+    &my_die("Couldn't find expected output file: $outputMask", $diff_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($outputMask);
+    &my_die("Couldn't find expected output file: $outputWeight", $diff_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($outputWeight);
+    &my_die("Couldn't find expected output file: $outputSources", $diff_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($outputSources);
+#    &my_die("Couldn't find expected output file: $bin1Name",    $diff_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($bin1Name);
+#    &my_die("Couldn't find expected output file: $bin2Name",    $diff_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($bin2Name);
+    &my_die("Couldn't find expected output file: $outputStats", $diff_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($outputStats);
+
+    # Get the statistics on the residual image
+    my $statsFile;              # File handle
+    open $statsFile, $ipprc->file_resolve($outputStats) or &my_die("Can't open statistics file $outputStats: $!", $diff_id, $PS_EXIT_SYS_ERROR);
+    my @contents = <$statsFile>; # Contents of file
+    close $statsFile;
+    my $metadata = $mdcParser->parse(join "", @contents) or
+        &my_die("Unable to parse metadata config doc", $diff_id, $PS_EXIT_PROG_ERROR);
+    $stats->parse($metadata) or &my_die("Unable to find all values in statistics output.", $diff_id, $PS_EXIT_PROG_ERROR);
+}
+
+unless ($no_update) {
+
+    # Add the subtraction result
+    {
+        my $command = "$difftool -adddiffskyfile -diff_id $diff_id -uri $outputName -path_base $outroot";
+        $command .= $stats->cmdflags();
+        $command .= " -hostname $host" if defined $host;
+        $command .= " -dbname $dbname" if defined $dbname;
+
+        my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+            run(command => $command, verbose => $verbose);
+        unless ($success) {
+            $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+            &my_die("Unable to perform difftool -adddiffskyfile: $error_code", $diff_id, $error_code);
+        }
+    }
+
+    # Register the run as completed
+    {
+        my $command = "$difftool -updaterun -diff_id $diff_id -state stop"; # Command to run difftool
+        $command .= " -dbname $dbname" if defined $dbname;
+
+        my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+            run(command => $command, verbose => $verbose);
+        unless ($success) {
+            $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+            warn("Unable to perform difftool -updaterun: $error_code\n");
+            exit($error_code);
+        }
+    }
+}
+
+
+sub my_die
+{
+    my $msg = shift;            # Warning message on die
+    my $diff_id = shift;        # Diff identifier
+    my $exit_code = shift;      # Exit code to add
+
+    warn($msg);
+    if (defined $diff_id and not $no_update) {
+        my $command = "$difftool -adddiffskyfile -diff_id $diff_id -code $exit_code";
+        $command .= " -hostname $host" if defined $host;
+        $command .= " -dbname $dbname" if defined $dbname;
+        run(command => $command, verbose => $verbose);
+    }
+    exit $exit_code;
+}
+
+END {
+    my $exit = $?;
+    system("sync") == 0 or die "failed to execute sync: $!";
+    $? = $exit;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ds9_cmf_regions.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ds9_cmf_regions.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ds9_cmf_regions.pl	(revision 22157)
@@ -0,0 +1,134 @@
+#!/usr/bin/env perl
+
+use strict;
+use Astro::FITS::CFITSIO qw( :constants );
+use File::Temp qw( tempfile );
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+use Data::Dumper;
+use Carp;
+
+Astro::FITS::CFITSIO::PerlyUnpacking(1);
+
+my $xpaset = `which xpaset`;
+my $xpaget = `which xpaget`;
+
+die "Unable to find xpaget and xpaset.\n" unless ($xpaset =~ /\S+/ and $xpaget =~ /\S+/);
+
+
+my ( $filename,                 # Filename containing photometry
+     $extname,                  # Extension name containing photometry
+     $frame,                    # Frame number in ds9
+     $colour,                   # Region colour
+     $flag_colour,              # Flagged source region colour
+     $flag,                     # Flags
+     $radius,                   # Radius for circle
+     $save_temps
+     );
+
+# Defaults
+$colour = "blue";
+$flag_colour = "red";
+$radius = 5;
+$flag = 0x3888;
+
+GetOptions(
+           'file=s' => \$filename,
+           'ext=s' => \$extname,
+           'frame=s' => \$frame,
+           'colour=s' => \$colour,
+           'flag-colour=s' => \$flag_colour,
+           'flag=o' => \$flag,
+           'radius=f' => \$radius,
+           'save-temps'        => \$save_temps, # Save temporary files?
+) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options: --file --ext",
+           -exitval => 3)
+    unless defined $filename
+    and defined $extname;
+
+my $status;                     # Status of FITSIO calls
+my $fits = Astro::FITS::CFITSIO::open_file( $filename, READONLY, $status ); # FITS file handle
+check_fitsio($status);
+$fits->movnam_hdu(BINARY_TBL, $extname, 0, $status) and check_fitsio($status);
+my $numRows;                    # Number of rows in table
+$fits->get_num_rows($numRows, $status) and check_fitsio($status);
+
+my ($xCol, $yCol, $flagCol);     # Column numbers for x,y, flag
+$fits->get_colnum(0, 'X_PSF', $xCol, $status) and check_fitsio($status);
+$fits->get_colnum(0, 'Y_PSF', $yCol, $status) and check_fitsio($status);
+$fits->get_colnum(0, 'FLAGS', $flagCol, $status) and check_fitsio($status);
+
+my ($x, $y, $flags);            # Coordinates and flags read from table
+$fits->read_col(TFLOAT, $xCol, 1, 1, $numRows, 0, $x, undef, $status) and check_fitsio($status);
+$fits->read_col(TFLOAT, $yCol, 1, 1, $numRows, 0, $y, undef, $status) and check_fitsio($status);
+$fits->read_col(TINT, $flagCol, 1, 1, $numRows, 0, $flags, undef, $status) and check_fitsio($status);
+$fits->close_file($status);
+
+my ($coordFile, $coordName) = tempfile( "/tmp/ds9_cmf_regions.XXXX", UNLINK => !$save_temps );
+for (my $i = 0; $i < $numRows; $i++) {
+    print $coordFile "image; circle(" . ($$x[$i] + 1) . ',' . ($$y[$i] + 1) . ",$radius \# color = " .
+        (($$flags[$i] & $flag) ? $flag_colour : $colour) . "\n";
+}
+close $coordFile;
+
+my @settings = settings_save("regions format",
+                             "regions system"); # Settings to save
+
+xpaset("frame $frame") if defined $frame;
+xpaset("regions format ds9");
+xpaset("regions system image");
+xpaset("regions load $coordName");
+
+xpaset(@settings);
+
+print "Plotted $numRows sources.\n";
+
+### Pau.
+
+
+
+
+# From Astro::FITS::CFITSIO demo
+sub check_fitsio
+{
+    my $status = shift;         # Status of FITSIO calls
+
+    if ($status != 0) {
+        my $msg;                # Message to output
+        Astro::FITS::CFITSIO::fits_get_errstatus( $status , $msg );
+        croak "CFITSIO error: $msg\n";
+    }
+}
+
+# Save specified settings
+sub settings_save
+{
+    my @settings;               # Values of settings
+    foreach my $setting (@_) {
+        my @values = xpaget($setting);
+        push @settings, $setting . ' ' . shift @values;
+    }
+    return @settings;
+}
+
+
+# XPA subroutines courtesy Derek Fox
+sub xpaset {
+    foreach my $cmd (@_) {
+        system("xpaset -p ds9 $cmd");
+    }
+}
+sub xpaget {
+    my @out;
+    foreach my $cmd (@_) {
+        my $output = `xpaget ds9 $cmd`;
+        push @out, $output;
+    }
+    return @out;
+}
+
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/fake_imfile.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/fake_imfile.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/fake_imfile.pl	(revision 22157)
@@ -0,0 +1,241 @@
+#!/usr/bin/env perl
+
+## this script is run on every imfile to perform the fake source
+## analysis and the forced photometry analysis.  
+
+## For the fake source analysis, we load the image, inject a number of
+## fake source (with a flat mag distribution), save the input source,
+## then perform photometry on the image to recover the input sources.
+## we should save a file with the matched input and recovered
+## sources.  the output metadata should include an analysis of the
+## recovery rate and measurement error as a function of magnitude
+
+## For the forced photometry, we need to use the measured astrometry
+## to select the desired locations in pixel coord.  
+
+# XXX : seeing needs to be determined from the input PSF (not currently done in ppSim)
+# ppSim output -input input.fits -cmf input.cmf -psf input.psf -seeing 0.563760 -recipe PPSIM FAKEPHOT
+
+use Carp;
+use warnings;
+use strict;
+
+## report the program and machine
+use Sys::Hostname;
+my $host = hostname();
+print "\n\n";
+print "Starting script $0 on $host\n\n";
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::Stats;
+use PS::IPP::Config 1.01 qw( :standard );
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+# Parse the command-line arguments
+my ( $exp_id, $fake_id, $class_id, $chiproot, $camroot, $camera, $outroot, 
+     $dbname, $reduction, $verbose, $no_update, $no_op, $redirect  );
+GetOptions(
+    'exp_id=s'      	=> \$exp_id,    # Exposure identifier
+    'fake_id=s'     	=> \$fake_id,   # Chiptool identifier
+    'class_id=s'    	=> \$class_id,  # Class identifier
+    'chiproot=s'    	=> \$chiproot,  # Input Chip files (root)
+    'camroot=s'     	=> \$camroot,   # Input Camera files (root)
+    'camera|c=s'    	=> \$camera,	   # Camera
+    'outroot|w=s'   	=> \$outroot,   # output file base name
+    'dbname|d=s'    	=> \$dbname,    # Database name
+    'reduction=s'   	=> \$reduction, # Reduction class
+    'verbose'       	=> \$verbose,   # Print to stdout
+    'no-update'     	=> \$no_update, # Don't update the database?
+    'no-op'         	=> \$no_op,	   # Don't do any operations?
+    'redirect-output'   => \$redirect,
+    ) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options: --exp_id --fake_id --class_id --chiproot --camroot --camera --outroot",
+	   -exitval => 3) unless
+    defined $exp_id and
+    defined $fake_id and
+    defined $class_id and
+    defined $chiproot and
+    defined $camroot and
+    defined $camera and
+    defined $outroot;
+
+$ipprc->define_camera($camera);
+
+my $logDest = $ipprc->filename("LOG.IMFILE", $outroot, $class_id)  or &my_die("Missing entry from camera config", $exp_id, $fake_id, $class_id, $PS_EXIT_CONFIG_ERROR);
+
+$ipprc->redirect_output($logDest) if $redirect;
+
+# Recipes to use based on reduction class
+$reduction = 'DEFAULT' unless defined $reduction;
+my $recipe = $ipprc->reduction($reduction, 'FAKEPHOT'); # Recipe to use
+unless ($recipe) {
+    &my_die("Couldn't find selected reduction for FAKE: $reduction\n", $exp_id, $fake_id, $class_id, $PS_EXIT_CONFIG_ERROR);
+}
+
+# values to extract from output metadata and the stats to calculate
+# XXX not sure if this is needed by fake_imfile.pl
+# my $STATS =
+#    [
+#        #          PPSTATS KEYWORD         STATISTIC          CHIPTOOL FLAG
+#        { name => "ROBUST_MEDIAN",  type => "mean",  flag => "-bg",             dtype => "float" },
+#        { name => "ROBUST_MEDIAN",  type => "stdev", flag => "-bg_mean_stdev",  dtype => "float" },
+#        { name => "ROBUST_STDEV",   type => "rms",   flag => "-bg_stdev",       dtype => "float" },
+#    ];
+# my $stats = PS::IPP::Metadata::Stats->new($STATS); # Stats parser
+
+# Look for programs we need
+my $missing_tools;
+my $faketool = can_run('faketool') or (warn "Can't find faketool" and $missing_tools = 1);
+my $ppSim = can_run('ppSim') or (warn "Can't find ppSim" and $missing_tools = 1);
+my $ppConfigDump = can_run('ppConfigDump') or (warn "Can't find ppConfigDump" and $missing_tools = 1);
+if ($missing_tools) {
+    warn("Can't find required tools.");
+    exit($PS_EXIT_CONFIG_ERROR);
+}
+
+my $mdcParser = PS::IPP::Metadata::Config->new;	# Parser for metadata config files
+
+# outroot examples (HOST components must be set)
+# file://data/ipp004.0/gpc1/20080130
+# neb:///ipp004-v1/gpc1/20080130
+# neb:///*/gpc1/20080130 (volume not specified)
+
+# check for existing directory, generate if needed
+$ipprc->outroot_prepare($outroot);
+
+## these names are used in ppImage, and thus may be URIs
+my $chipImage     = $ipprc->filename("PPIMAGE.CHIP",        $chiproot, $class_id) or &my_die("Missing entry from camera config", $exp_id, $fake_id, $class_id, $PS_EXIT_CONFIG_ERROR);
+my $chipMask      = $ipprc->filename("PPIMAGE.CHIP.MASK",   $chiproot, $class_id) or &my_die("Missing entry from camera config", $exp_id, $fake_id, $class_id, $PS_EXIT_CONFIG_ERROR);
+my $chipWeight    = $ipprc->filename("PPIMAGE.CHIP.WEIGHT", $chiproot, $class_id) or &my_die("Missing entry from camera config", $exp_id, $fake_id, $class_id, $PS_EXIT_CONFIG_ERROR);
+my $chipPSF       = $ipprc->filename("PSPHOT.PSF.SAVE",     $chiproot, $class_id) or &my_die("Missing entry from camera config", $exp_id, $fake_id, $class_id, $PS_EXIT_CONFIG_ERROR);
+my $cameraObjects = $ipprc->filename("PSASTRO.OUTPUT",      $camroot)             or &my_die("Missing entry from camera config", $exp_id, $fake_id, $class_id, $PS_EXIT_CONFIG_ERROR);
+my $traceDest     = $ipprc->filename("TRACE.IMFILE", 	    $outroot, $class_id)  or &my_die("Missing entry from camera config", $exp_id, $fake_id, $class_id, $PS_EXIT_CONFIG_ERROR);
+
+# XXX check for existence of input data
+# &my_die("Couldn't find input file: $uri\n", $exp_id, $fake_id, $class_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($uri);
+
+## get the PPSIM recipe for this camera and FAKEPHOT reduction
+my $command = "$ppConfigDump -camera $camera -dump-recipe PPSIM -recipe PPSIM $recipe -";
+my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run(command => $command, verbose => $verbose);
+unless ($success) {
+	$error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+	&my_die("Unable to perform ppConfigDump: $error_code", $exp_id, $fake_id, $class_id, $PS_EXIT_SYS_ERROR);
+}
+my $recipeData = $mdcParser->parse(join "", @$stdout_buf) or
+	&my_die("Unable to parse metadata config doc", $exp_id, $fake_id, $class_id, $PS_EXIT_SYS_ERROR);
+
+## allow the output images to be optional, depending on the recipe / reduction class
+my $skipFake = metadataLookupBool($recipeData, 'SKIP.FAKE');
+
+# Run ppSim
+unless ($no_op || $skipFake) {
+    # examine the PPSIM recipe to decide if we need to run this or opt out
+
+    my $command = "$ppSim $outroot";
+    $command .= " -input $chipImage";
+    # XXX add input mask and weight to ppSim
+    # $command .= " -mask $chipMask";
+    # $command .= " -weight $chipWeight";
+    $command .= " -cmf $cameraObjects";
+    $command .= " -psf $chipPSF";
+    $command .= " -recipe PPSIM $recipe";
+    $command .= " -dbname $dbname" if defined $dbname;
+    $command .= " -tracedest $traceDest";
+    $command .= " -log $logDest";
+
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run(command => $command, verbose => $verbose);
+    unless ($success) {
+	$error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+	&my_die("Unable to perform ppSim: $error_code", $exp_id, $fake_id, $class_id, $error_code);
+    }
+
+    # XXX check for output files?
+    # &my_die("Couldn't find expected output file: $outputBin1\n",   $exp_id, $fake_id, $class_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($outputBin1);
+
+    # XXX use this to parse the output metadata : eg, detection limits, Nfakes, Nforced
+    # Get the statistics on the processed image
+    # my $statsFile;		# File handle
+    # open $statsFile, $ipprc->file_resolve($outputStats) or &my_die("Can't open statistics file $outputStats: $!", $exp_id, $fake_id, $class_id, $PS_EXIT_SYS_ERROR);
+    # my @contents = <$statsFile>; # Contents of file
+    # close $statsFile;
+    # 
+    # # parse the statistics MDC file
+    # my $mdcParser = PS::IPP::Metadata::Config->new();	# Parser for metadata config files
+    # my $metadata = $mdcParser->parse(join "", @contents);
+    # unless ($metadata) {
+    # 	&my_die("Unable to parse metadata config doc", $exp_id, $fake_id, $class_id, $PS_EXIT_PROG_ERROR);
+    # }
+    # 
+    # # extract the stats from the metadata
+    # unless ($stats->parse($metadata)) {
+    # 	&my_die("Failure extracting metadata from the statistics output file.\n", $exp_id, $fake_id, $class_id, $PS_EXIT_PROG_ERROR);
+    # }
+} else {
+    print "skipping ppSim processing\n";
+}
+
+# command to update database
+$command = "$faketool -addprocessedimfile";
+$command .= " -fake_id $fake_id";
+$command .= " -exp_id $exp_id";
+$command .= " -class_id $class_id";
+$command .= " -path_base $outroot";
+$command .= " -dbname $dbname" if defined $dbname;
+# XXX add this after defined
+# $command .= $stats->cmdflags();
+
+# Add the processed file to the database
+unless ($no_update) {
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run(command => $command, verbose => $verbose);
+    unless ($success) {
+	$error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+	warn("Unable to perform faketool -addprocessedimfile: $error_code\n");
+	exit($error_code);
+    }
+} else {
+    print "skipping command: $command\n";
+}
+
+sub my_die
+{
+    my $msg = shift; # Warning message on die
+    my $exp_id = shift; # rawExp identifier
+    my $fake_id = shift; # fakeRun identifier
+    my $class_id = shift; # Class identifier
+    my $exit_code = shift; # Exit code to add
+
+    carp($msg);
+    if (defined $exp_id and defined $fake_id and defined $class_id and not $no_update) {
+	my $command = "$faketool -addprocessedimfile";
+	$command .= " -fake_id $fake_id";
+	$command .= " -exp_id $exp_id";
+	$command .= " -class_id $class_id";
+	$command .= " -path_base $outroot";
+	$command .= " -code $exit_code";
+	$command .= " -dbname $dbname" if defined $dbname;
+        system ($command);
+    }
+    exit $exit_code;
+}
+
+END {
+    my $status = $?;
+    system("sync") == 0
+        or die "failed to execute sync: $!" ;
+    $? = $status;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/flatcorr_init.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/flatcorr_init.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/flatcorr_init.pl	(revision 22157)
@@ -0,0 +1,212 @@
+#!/usr/bin/env perl
+
+## USAGE:flatcorr_init.pl 
+## given dbname, dvodb, filter, time range, camera, telescope, etc?
+## select matching images and register as a new flatcorr run
+
+## should this be implemented as a ippTool program?
+## this is a lot like dettool -definebyquery
+## flatcorr -definebyquery
+## this should queue the selected images with a mode that stops at camera
+## ?? how do we block this batch from running through warp ??
+
+## we also need the test / query tool:
+## flatcorr -pendingcorr
+## examine the chip & camera stages and check for all images to have completed (successfully or not)
+
+## allow a certain fraction of failures...
+
+use Carp;
+use warnings;
+use strict;
+
+## report the program and machine
+use Sys::Hostname;
+my $host = hostname();
+print "\n\n";
+print "Starting script $0 on $host\n\n";
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use Storable qw(freeze thaw);
+use File::Basename qw( basename);
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::Stats;
+use PS::IPP::Config 1.01 qw( :standard );
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+use File::Spec;
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+my ($dvo_id, $catdir, $region, $dbname, $workdir, $no_update, $no_op);
+GetOptions(
+    'dvo_id|i=s'       => \$dvo_id,
+    'catdir|c=s'       => \$catdir,
+    'region|r=s'       => \$region,
+    'dbname|d=s'       => \$dbname,# Database name
+    'workdir|w=s'      => \$workdir, # Working directory for output files
+    'no-update'        => \$no_update,
+    'no-op'            => \$no_op,
+) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options: --dvo_id --catdir --region",
+           -exitval => 3) unless
+    defined $dvo_id and
+    defined $catdir and
+    defined $region;
+
+# Look for programs we need
+my $missing_tools;
+my $addstar  = can_run('addstar')  or (warn "Can't find addstar"  and $missing_tools = 1);
+my $relphot  = can_run('relphot')  or (warn "Can't find relphot"  and $missing_tools = 1);
+my $uniphot  = can_run('uniphot')  or (warn "Can't find uniphot"  and $missing_tools = 1);
+my $relastro = can_run('relastro') or (warn "Can't find relastro" and $missing_tools = 1);
+my $caltool  = can_run('caltool')  or (warn "Can't find caltool"  and $missing_tools = 1);
+
+if ($missing_tools) { 
+    warn ("Can't find required tools");
+    exit($PS_EXIT_CONFIG_ERROR); 
+}
+
+# select the primary filters from DVO query?
+my (@filters) = `photcodeList -average`;
+
+# parse the region (RAs,RAe:DECs,DECe) : item = +/-NNN.NNNN
+my @coords = split (":", $region);
+my ($RAs, $RAe) = split (",", $coords[0]);
+my ($DECs, $DECe) = split (",", $coords[1]);
+
+# Run addstar -resort
+{
+    my $command = "$addstar -resort";
+    $command .= "-D CATDIR $catdir";
+    $command .= "-region $RAs $RAe $DECs $DECe";
+
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        cache_run(command => $command, verbose => 1);
+
+    unless ($success) { 
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die ("Unable to perform addstar -resort on region $region: $error_code", $dvo_id, $region, "RESORT", $status, $dbname);
+    }
+}
+
+# Run relphot (filter) for each filter
+{
+    foreach my $filter (@filters) {
+	my $command = "$relphot $filter";
+	$command .= "-D CATDIR $catdir";
+	$command .= "-region $RAs $RAe $DECs $DECe";
+
+	my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	    cache_run(command => $command, verbose => 1);
+
+	unless ($success) { 
+	    $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+	    &my_die ("Unable to perform addstar -resort on region $region: $error_code", $dvo_id, $region, "RELPHOT", $status, $dbname);
+	}
+    }
+}
+
+# Run uniphot (filter) for each filter
+# XXX skip this one?  run less frequently?
+if (0) {
+    foreach my $filter (@filters) {
+	my $command = "$uniphot $filter";
+	$command .= "-D CATDIR $catdir";
+	$command .= "-region $RAs $RAe $DECs $DECe";
+
+	my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	    cache_run(command => $command, verbose => 1);
+
+	unless ($success) { 
+	    $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+	    &my_die ("Unable to perform addstar -resort on region $region: $error_code", $dvo_id, $region, "UNIPHOT", $status, $dbname);
+	}
+    }
+}
+
+{
+    my $command = "$relastro -objects";
+    $command .= "-D CATDIR $catdir";
+    $command .= "-region $RAs $RAe $DECs $DECe";
+
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	cache_run(command => $command, verbose => 1);
+
+    unless ($success) { 
+	$error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+	&my_die ("Unable to perform addstar -resort on region $region: $error_code", $dvo_id, $region, "RELASTRO.OBJECTS", $status, $dbname);
+    }
+}
+
+{
+    my $command = "$relastro -images";
+    $command .= "-D CATDIR $catdir";
+    $command .= "-region $RAs $RAe $DECs $DECe";
+
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	cache_run(command => $command, verbose => 1);
+
+    unless ($success) { 
+	$error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+	&my_die ("Unable to perform addstar -resort on region $region: $error_code", $dvo_id, $region, "RELASTRO.IMAGES", $status, $dbname);
+    }
+}
+
+my $command = "$caltool -addcalrun";
+$command .= " -dvo_id $dvo_id";
+$command .= " -region $region";
+$command .= " -last_step RELASTRO.IMAGES";
+$command .= " -status SUCCESS";
+$command .= " -dbname $dbname" if defined $dbname;
+
+# Push the results into the database
+unless ($no_update) {
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => 1);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        warn ("Unable to perform regtool -addprocessedimfile: $error_code");
+        exit($error_code);
+    }
+} else {
+    print "skipping command: $command\n";
+}
+
+sub my_die
+{
+    my $msg = shift; # Warning message on die
+    my $dvo_id    = shift;
+    my $region    = shift;
+    my $last_step = shift;
+    my $status 	  = shift;
+    my $dbname 	  = shift;
+
+    carp($msg);
+    if (defined $dvo_id && defined $region && defined $last_step && defined $status and not $no_update) {
+        my $command = "$caltool -addcalrun";
+	$command .= " -dvo_id $dvo_id";
+        $command .= " -region $region";
+	$command .= " -last_step $last_step";
+	$command .= " -status $status";
+        $command .= " -dbname $dbname" if defined $dbname;
+        system ($command);
+    }
+    exit $exit_code;
+}
+
+# Pau.
+
+END {
+    my $exit = $?;
+    system("sync") == 0 or die "failed to execute sync: $!";
+    $? = $exit;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/flatcorr_proc.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/flatcorr_proc.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/flatcorr_proc.pl	(revision 22157)
@@ -0,0 +1,205 @@
+#!/usr/bin/env perl
+
+## USAGE:flatcorr_proc.pl --dbname --corr_id
+
+## this script does the following steps:
+
+# extract the details of the flatcorr run: dvodb, filter, camera, etc?
+
+# relphot -D CATDIR $dvodb -grid (outgrid.fits) (filter) -region 0 360 -90 90 (other parameters?)
+
+# dvoMakeCorr -file outgrid.fits -ref ref.fits outcorr
+
+# dettool -register -det_type FLATCORR -filelevel (level) -workdir -inst, etc 
+# foreach $imfile () 
+#   dettool -register_imfile -uri, etc, etc
+
+use Carp;
+use warnings;
+use strict;
+
+## report the program and machine
+use Sys::Hostname;
+my $host = hostname();
+print "\n\n";
+print "Starting script $0 on $host\n\n";
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use Storable qw(freeze thaw);
+use File::Basename qw( basename);
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::Stats;
+use PS::IPP::Config 1.01 qw( :standard );
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+use File::Spec;
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+my ($corr_id, $dvodb, $region, $filter, $dbname, $workdir, $no_update, $no_op);
+GetOptions(
+    'corr_id|i=s'      => \$corr_id,
+    'dvodb|c=s'        => \$dvodb,
+    'region|r=s'       => \$region,
+    'filter|f=s'       => \$filter,
+    'dbname|d=s'       => \$dbname,# Database name
+    'workdir|w=s'      => \$workdir, # Working directory for output files
+    'no-update'        => \$no_update,
+    'no-op'            => \$no_op,
+) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options: --corr_id --dvodb --region --filter",
+           -exitval => 3) unless
+    defined $corr_id and
+    defined $dvodb and
+    defined $region and
+    defined $filter;
+
+# Look for programs we need
+my $missing_tools;
+my $relphot     = can_run('relphot')      or (warn "Can't find relphot"      and $missing_tools = 1);
+my $dvoMakeCorr = can_run('dvoMakeCorr')  or (warn "Can't find dvoMakeCorr"  and $missing_tools = 1);
+my $detselect   = can_run('detselect') 	  or (warn "Can't find detselect"    and $missing_tools = 1);
+my $dettool     = can_run('dettool')   	  or (warn "Can't find dettool"      and $missing_tools = 1);
+my $flatcorr    = can_run('flatcorr')  	  or (warn "Can't find flatcorr"     and $missing_tools = 1);
+
+if ($missing_tools) { 
+    warn ("Can't find required tools");
+    exit($PS_EXIT_CONFIG_ERROR); 
+}
+
+my($outgrid, $outcorr);
+
+$outgrid = "$workdir/grid.$corr_id.fits";
+$outcorr = "$workdir/corr.$corr_id.fits";
+
+# parse the region (RAs,RAe:DECs,DECe) : item = +/-NNN.NNNN
+my @coords = split (":", $region);
+my ($RAs, $RAe) = split (",", $coords[0]);
+my ($DECs, $DECe) = split (",", $coords[1]);
+
+# Run relphot (filter) for the specified region (need to clarify the options like imfreeze)
+{
+    my $command = "$relphot $filter";
+    $command .= "-D CATDIR $dvodb";
+    $command .= "-region $RAs $RAe $DECs $DECe";
+    $command .= "-grid $outgrid";
+    $command .= "-imfreeze?";
+
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	cache_run(command => $command, verbose => 1);
+
+    unless ($success) { 
+	$error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+	&my_die ("Unable to perform relphot -grid on region $region: $error_code", $dvo_id, $region, "RELPHOT", $status, $dbname);
+    }
+}
+
+# use one of the input raw images as a reference image
+# XXX I'm not sure how this works: do I run this once per imfile for a given exp?
+my ($reffile)
+{
+    my $command = "$flatcoor -flatcorrimfile -limit 1";
+
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	cache_run(command => $command, verbose => 1);
+
+    unless ($success) { 
+	$error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+	&my_die ("Unable to perform flatcorr -flatcorrimfile: $error_code", $dvo_id, $region, "RELPHOT", $status, $dbname);
+    }
+
+    my $metadata = $mdcParser->parse(join "", @$stdout_buf) or
+	&my_die("Unable to parse metadata config doc", $dvo_id, $region, "RELPHOT", $status, $dbname, $PS_EXIT_PROG_ERROR););
+
+    my $imfiles = parse_md_list($metadata) or 
+	&my_die("Unable to parse metadata list", $dvo_id, $region, "RELPHOT", $status, $dbname, $PS_EXIT_PROG_ERROR););
+
+    my $imfile = $imfiles->[0];
+    $reffile = $imfile->{uri};
+}
+
+{
+    my $command = "$dvoMakeCorr -file $outgrid -ref $reffile $outcorr";
+
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	cache_run(command => $command, verbose => 1);
+
+    unless ($success) { 
+	$error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+	&my_die ("Unable to perform dvoMakeCorr: $error_code", $dvo_id, $region, "RELASTRO.OBJECTS", $status, $dbname);
+    }
+}
+
+{
+    my $command = "$dettool -register";
+    $command .= "-det_type FLATCORR";
+    $command .= "-file_level $fileLevel";
+    $command .= "-workdir $workdir";
+    $command .= "-inst $inst";
+    $command .= " -dbname $dbname" if defined $dbname;
+    ## XXX what else do we need, and where do we get it?
+
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	cache_run(command => $command, verbose => 1);
+
+    unless ($success) { 
+	$error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+	&my_die ("Unable to perform addstar -resort on region $region: $error_code", $dvo_id, $region, "RELASTRO.IMAGES", $status, $dbname);
+    }
+}
+
+my $command = "$flatcorr -done";
+$command .= " -corr_id $corr_id";
+$command .= " -stats UNKNOWN";
+$command .= " -dbname $dbname" if defined $dbname;
+
+# Push the results into the database
+unless ($no_update) {
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => 1);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        warn ("Unable to perform regtool -addprocessedimfile: $error_code");
+        exit($error_code);
+    }
+} else {
+    print "skipping command: $command\n";
+}
+
+sub my_die
+{
+    my $msg = shift; # Warning message on die
+    my $dvo_id    = shift;
+    my $region    = shift;
+    my $last_step = shift;
+    my $status 	  = shift;
+    my $dbname 	  = shift;
+
+    carp($msg);
+    if (defined $dvo_id && defined $region && defined $last_step && defined $status and not $no_update) {
+        my $command = "$caltool -addcalrun";
+	$command .= " -dvo_id $dvo_id";
+        $command .= " -region $region";
+	$command .= " -last_step $last_step";
+	$command .= " -status $status";
+        $command .= " -dbname $dbname" if defined $dbname;
+        system ($command);
+    }
+    exit $exit_code;
+}
+
+# Pau.
+
+END {
+    my $exit = $?;
+    system("sync") == 0 or die "failed to execute sync: $!";
+    $? = $exit;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/gpc_seeing.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/gpc_seeing.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/gpc_seeing.pl	(revision 22157)
@@ -0,0 +1,28 @@
+#!/usr/bin/env perl
+# basic ISP transmission analysis:
+
+if (@ARGV != 1) { die "USAGE: gpc_seeing.pl (input.fits)\n"; }
+$input = $ARGV[0];
+
+# for input file /path/foo.fits, use /path/foo for output
+
+@words = split ('\.', $input);
+if (@words > 1) { pop @words; }
+$output = join (".", @words);
+
+# use constant RECIPE => 'PPIMAGE_OBDSFRA'; # Recipe to use
+$RECIPE_PPIMAGE  = 'PPIMAGE_OP';
+$RECIPE_PSPHOT   = 'PSPHOT.SEEING';
+
+# recommend only processing to PSFMODEL
+vsystem ("ppImage -file $input $output -recipe PPIMAGE $RECIPE_PPIMAGE -recipe PSPHOT $RECIPE_PSPHOT");
+if ($status) { die "failure running ppImage\n"; }
+
+# XXX otis can read the output psf model, or we can supply a program to interpret the model
+
+sub vsystem {
+    print STDERR "@_\n";
+    my $status = system ("@_");
+    $status;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_cleanup.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_cleanup.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_cleanup.pl	(revision 22157)
@@ -0,0 +1,185 @@
+#!/usr/bin/env perl
+
+# this script is used to cleanup the files from the different ipp
+# stages.  It can be called with one of two cleanup modes: clean and
+# purge.  the former removes temporary data files, leaving behind
+# enough information for the results to be rebuilt.  The latter
+# removes all but basic logging data.
+
+use warnings;
+use strict;
+use Carp;
+
+use IPC::Cmd 0.36 qw( can_run run );
+use File::Spec;
+use PS::IPP::Config 1.01 qw( :standard );
+use PS::IPP::Metadata::List qw( parse_md_list );
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+my $ipprc = PS::IPP::Config->new(); # this is used for PATH, NEB filename conversions
+
+# Parse the command-line arguments
+my ($stage, $camera, $stage_id, $mode, $path_base, $dbname, $verbose, $no_op, $helplist);
+GetOptions('stage=s'        => \$stage,     # which analysis stage to clean?
+	   'camera|i=s'     => \$camera,    # user-supplied camera name
+	   'stage_id=s'     => \$stage_id,  # id for this stage (only needed for certain stages)
+	   'mode|m=s'       => \$mode,      # cleanup mode (clean / purge)
+	   'path_base=s'    => \$path_base, # basename for files
+	   'dbname|d=s'     => \$dbname,    # Database name
+           'verbose'        => \$verbose,   # Print to stdout
+	   'no-op'          => \$no_op,     # pretend but don't actually inject
+	   'helplist'       => \$helplist   # give help listing
+	   ) or pod2usage( 2 );
+
+pod2usage( -msg => "remove temporary / all data files for an IPP analysis stage", 
+	   -exitval => 2) if defined $helplist;
+
+pod2usage( -msg => "Usage: $0 --camera (name) --stage (stage) --stage_id (stage_id) --mode (mode) [--path_base (path)] [--dbname dbname] [--no-op] [--help]", 
+	   -exitval => 2 ) if scalar @ARGV;
+
+pod2usage( -msg => "Required options:--camera (name) --stage (stage) --mode (mode)",
+	   -exitval => 3) unless 
+    defined $camera and
+    defined $stage and
+    defined $mode;
+
+# $mode must be one of "goto_cleaned" or "goto_purged"
+unless (($mode eq "goto_cleaned") || ($mode eq "goto_purged")) {
+    die "invalid cleanup mode $mode\n";    
+}
+
+$ipprc->define_camera($camera);
+
+my $mdcParser = PS::IPP::Metadata::Config->new; # Parser for metadata config files
+
+my $fault = 0;
+
+# choice of files to delete depends on the stage
+if ($stage eq "chip") {
+    
+    ### select the imfiles for this entry
+
+    # this stage uses 'chiptool'
+    my $chiptool = can_run('chiptool') or die "Can't find chiptool";
+
+    # Get list of component imfiles
+    # XXX may need a different my_die for each stage
+    my $imfiles;                      # Array of component files
+    my $command = "$chiptool -pendingcleanupimfile -chip_id $stage_id"; # Command to run
+    $command .= " -dbname $dbname" if defined $dbname;
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) = run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to perform chiptool: $error_code", "chip", $stage_id, $error_code);
+    }
+    my $metadata = $mdcParser->parse(join "", @$stdout_buf) or
+        &my_die("Unable to parse metadata config doc", "chip", $stage_id, $PS_EXIT_PROG_ERROR);
+
+    # extract the metadata for the files into a hash list
+    $imfiles = parse_md_list($metadata) or
+        &my_die("Unable to parse metadata list", "chip", $stage_id, $PS_EXIT_PROG_ERROR);
+
+    # loop over all of the imfiles, determine the path_base and class_id for each
+    foreach my $imfile (@$imfiles) {
+	my $class_id = $imfile->{class_id};
+	my $path_base = $imfile->{path_base};
+
+	# array of actual filenames to delete
+	my @files = ();
+
+	# delete the temporary image datafiles
+	addFilename (\@files, "PPIMAGE.OUTPUT", $path_base, $class_id);
+	addFilename (\@files, "PPIMAGE.OUTPUT.MASK", $path_base, $class_id);
+	addFilename (\@files, "PPIMAGE.OUTPUT.WEIGHT", $path_base, $class_id);
+	addFilename (\@files, "PPIMAGE.CHIP", $path_base, $class_id);
+	addFilename (\@files, "PPIMAGE.CHIP.MASK", $path_base, $class_id);
+	addFilename (\@files, "PPIMAGE.CHIP.WEIGHT", $path_base, $class_id);
+	if ($mode eq "goto_purged") {
+	    # additional files to remove for 'purge' mode
+	    addFilename (\@files, "PPIMAGE.OUTPUT.FPA1", $path_base, $class_id);
+	    addFilename (\@files, "PPIMAGE.OUTPUT.FPA2", $path_base, $class_id);
+	    addFilename (\@files, "PPIMAGE.BIN1", $path_base, $class_id);
+	    addFilename (\@files, "PPIMAGE.BIN2", $path_base, $class_id);
+	    addFilename (\@files, "PPIMAGE.JPEG1", $path_base, $class_id);
+	    addFilename (\@files, "PPIMAGE.JPEG", $path_base, $class_id);
+	    addFilename (\@files, "PPIMAGE.STATS", $path_base, $class_id);
+	}
+	
+	# actual command to delete the files
+	my $status = &delete_files (\@files);
+
+	if (! $status)  {
+	    my $command = "$chiptool -updateimfile -chip_id $stage_id -class_id $class_id -code 1"; # Command to run
+	    $command .= " -dbname $dbname" if defined $dbname;
+
+	    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) = run(command => $command, verbose => $verbose);
+	    unless ($success) {
+		$error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+		&my_die("Unable to perform chiptool: $error_code", "chip", $stage_id, $error_code);
+	    }
+	    
+	    $fault = 1;
+	}
+    }
+
+    if (! $fault) {
+	# set the state of the chipRun entry to "cleaned" or "purged" as needed
+	my $command = "$chiptool -updaterun -chip_id $stage_id";
+	if ($mode eq "goto_purged") {
+	    $command .= " -state purged";
+	} else {
+	    $command .= " -state cleaned";
+	}
+	$command .= " -dbname $dbname" if defined $dbname;
+
+	my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) = run(command => $command, verbose => $verbose);
+	unless ($success) {
+	    $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+	    &my_die("Unable to perform chiptool: $error_code", "chip", $stage_id, $error_code);
+	}
+    }
+    exit 0;
+}
+
+die "unknown stage for ipp_cleanup.pl\n";
+exit 1;
+
+sub delete_files 
+{
+    my $files = shift; # reference to a list of files to unlink
+    
+    # this script is, of course, very dangerous.  
+    foreach my $file (@$files) {
+	print STDERR "unlinking $file\n";
+	unlink $file;
+    }
+    return 1;
+}
+
+sub addFilename 
+{
+    my $files      = shift; # reference to a list of files to unlink
+    my $filerule   = shift; # filerule to add
+    my $path_base  = shift; # base filename
+    my $class_id   = shift; # class_id, if needed
+
+    my $file = $ipprc->filename($filerule, $path_base, $class_id);
+
+    push @$files, $file;
+    return 1;
+}
+
+sub my_die
+{
+    my $msg = shift; # Warning message on die
+    my $stage = shift; # Chiptool identifier
+    my $stage_id = shift; # Chiptool identifier
+    my $exit_code = shift; # Exit code to add
+    # outputImage and outroot are globals
+
+    carp($msg);
+    exit $exit_code;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_darkstats.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_darkstats.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_darkstats.pl	(revision 22157)
@@ -0,0 +1,188 @@
+#!/usr/bin/env perl
+
+# use warnings;
+# use strict;
+use Carp;
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+use IPC::Cmd 0.36 qw( can_run run );
+use IO::Handle;
+use PS::IPP::Metadata::List qw( parse_md_list );
+use PS::IPP::Config 1.01 qw( :standard );
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+my ($dbname, $det_id, $camera);
+
+GetOptions('dbname=s'    => \$dbname,
+	   'det_id=s'    => \$det_id,
+	   'camera|c=s'  => \$camera,
+	   ) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+
+pod2usage(
+	  -msg => "USAGE: ipp_darkstats.pl --dbname (name) --det_id (id) --camera (name)",
+	  -exitval => 3,
+	  ) unless defined $dbname and defined $det_id and defined $camera;
+
+$ipprc->define_camera($camera);
+
+###  Get list of dark imfile results
+
+# define the dettool command
+my $command = "dettool -processedimfile -select_state stop"; # Command to run
+$command .= " -det_id $det_id";
+$command .= " -dbname $dbname" if defined $dbname;
+
+# run the dettool command and catch the output
+my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+    run(command => $command, verbose => 0);
+unless ($success) {
+    $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+    &my_die("Unable to perform dettool: $error_code", $error_code);
+}
+
+# parse the output into a list
+my $mdcParser = PS::IPP::Metadata::Config->new;	# Parser for metadata config files
+my $metadata = $mdcParser->parse(join "", @$stdout_buf) or
+    &my_die("Unable to parse metadata config doc", $PS_EXIT_PROG_ERROR);
+my $list = parse_md_list($metadata) or
+    &my_die("Unable to parse metadata list", $PS_EXIT_PROG_ERROR);
+
+my @bg_data;
+my @bg_stdev_data;
+my @bg_name;
+my @bg_exptime;
+my %components;
+
+print STDERR "extracted the data from the database\n";
+
+# we now have a list of imfiles; we need to extract the background for each cell
+# from the stats files for each imfile
+foreach my $item (@$list) {
+    my $path_base = $item->{path_base};
+    my $class_id = $item->{class_id};
+    my $exp_time = $item->{exp_time};
+
+    my $rootName  = $ipprc->file_resolve ($path_base);
+    my $statsName = "$rootName.$class_id.stats";
+
+    # print STDERR "rootName: $rootName : $exp_time\n";
+    print STDERR "statsName: $statsName : $exp_time\n";
+
+    my $statsFile;
+    open $statsFile, $statsName;
+    my @contents = <$statsFile>;
+    close ($statsFile);
+
+    # print STDERR "contents: @contents\n";
+
+    my $parser = PS::IPP::Metadata::Config->new;	# Parser for metadata config files
+    my $statsList = $parser->parse(join "", @contents) or &my_die("Unable to parse metadata for imfile stats", $PS_EXIT_SYS_ERROR);
+
+    &parse_stats_table ($exp_time, $class_id, $statsList);
+}
+
+print STDERR "parsed the stats from the data files\n";
+
+for (my $i = 0; $i < @bg_data; $i++) {
+    $nameX = "$bg_name[$i].exp";
+    $nameY = "$bg_name[$i].bg";
+    push @{$nameX}, $bg_exptime[$i];
+    push @{$nameY}, $bg_data[$i];
+}
+
+if (-e "output.dat") { unlink "output.dat"; }
+
+print STDERR "dumping stats\n";
+open (MANA, "|mana --norc");
+MANA->autoflush;
+
+foreach my $component (@components) {
+    $nameX = "$component.exp";
+    $nameY = "$component.bg";
+
+    print MANA "delete X Y\n";
+
+    open (DATA, ">$component.dat");
+    for (my $i = 0; $i < @{$nameX}; $i++) {
+	print DATA "${$nameX}[$i] ${$nameY}[$i]\n";
+    }
+    close (DATA);
+
+    print MANA "data $component.dat\n";
+    print MANA "read X 1 Y 2\n";
+    print MANA "fit X Y 2 -clip 3 3\n";
+    print MANA "output output.dat\n";
+    print MANA "echo $component METADATA\n";
+    print MANA "echo \"   NORDER_X  S32 2   \"\n";
+    print MANA "echo \"   VAL_X00   F64 \$C0\"\n";
+    print MANA "echo \"   VAL_X01   F64 \$C1\"\n";
+    print MANA "echo \"   VAL_X02   F64 \$C2\"\n";
+    print MANA "echo \"   NELEMENTS S32 3    \"\n";
+    print MANA "echo END\n";
+    print MANA "echo\n";
+    print MANA "output stdout\n";
+
+    print MANA "applyfit X Yf\n";
+    print MANA "lim X Y\n";
+    print MANA "clear\n";
+    print MANA "box\n";
+    print MANA "plot -x 2 -pt 2 -sz 1.0 -c black X Y\n";
+    print MANA "plot -x 2 -pt 7 -sz 1.0 -c red X Yf\n";
+
+    print STDERR "hit return to continue\n";
+    $answer = <STDIN>;
+}
+
+close (MANA);
+
+exit 0;
+
+sub parse_stats_table
+{
+    my ($exp_time, $tag, $md) = @_;
+
+    # descend through the fpa        
+    foreach my $entry (@$md) {
+	# print STDERR "name: $entry->{name}, class: $entry->{class}\n";
+        # recurse on nested metadata
+        if ($entry->{class} eq 'metadata') {
+	    my $newtag = $tag . "_" . $entry->{name};
+            &parse_stats_table ($exp_time, $newtag, $entry->{value});
+        }
+
+        if ($entry->{name} =~ /^(SAMPLE|ROBUST|FITTED|CLIPPED)/) {
+            # It's a statistic of some sort
+            if ($entry->{name} =~ /_STDEV$/) {
+                push @bg_stdev_data, $entry->{value};
+            } else {
+		push @bg_name,    $tag;
+                push @bg_data,    $entry->{value};
+		push @bg_exptime, $exp_time;
+		# print STDERR "$tag $exp_time $entry->{value}\n";
+            }
+	    if (!$componentsHash{$tag}) {
+		push @components, $tag;
+		$componentsHash{$tag} = 1;
+	    }
+	    next;
+	} 
+    }
+    return 1;
+}
+
+sub my_die
+{
+    my $msg = shift; # Warning message on die
+    my $exit_code = shift; # Exit code to add
+
+    carp($msg);
+    exit $exit_code;
+}
+
+# - get the exp_time as well from dettool
+# - build an array of bg & exptime for each cell
+# - fit the trend (in mana? pslib functions?)
+# - write the polynomial for each cell
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_datapath.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_datapath.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_datapath.pl	(revision 22157)
@@ -0,0 +1,25 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+
+use PS::IPP::Config;
+my $ipprc = PS::IPP::Config->new();
+
+my $touch;
+
+GetOptions(
+    'touch'     => \$touch,
+);
+
+die "No filename specified.\n" if scalar @ARGV != 1;
+
+my $filename = shift @ARGV;
+
+my $resolved = $ipprc->file_resolve($filename, $touch);
+print "$resolved\n" if ($resolved);
+
+1;
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_detrend_combine.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_detrend_combine.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_detrend_combine.pl	(revision 22157)
@@ -0,0 +1,220 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+## report the program and machine
+use Sys::Hostname;
+my $host = hostname();
+print "\n\n";
+print "Starting script $0 on $host\n\n";
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use Data::Dumper;
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::List qw( parse_md_list );
+use PS::IPP::Config 1.01 qw( :standard );
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+use PS::IPP::Metadata::Stats;
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+my $RECIPE_PPSTATS = 'CHIPSTATS'; # Recipe to use with ppStats
+
+# Parse command-line arguments
+my ($det_type, $filelevel, $inst, $telescope, $filter,
+    $det_id1, $iter1, $det_id2, $iter2, $operation, $mask,
+    $workdir, $dbname, $no_update);
+GetOptions(
+	   'det_type=s'    => \$det_type, # Detrend type for new detrend
+	   'filelevel=s'   => \$filelevel, # File level for new detrend
+	   'inst=s'        => \$inst, # Instrument for new detrend
+	   'telescope=s'   => \$telescope, # Telescope for new detrend
+	   'filter=s'      => \$filter,	# Filter name for new detrend
+	   'det_id1=s'	   => \$det_id1, # Detrend id for detrend 1
+	   'iteration1=s'  => \$iter1, # Iteration for detrend 1
+	   'det_id2=s'	   => \$det_id2, # Detrend id for detrend 2
+	   'iteration2=s'  => \$iter2, # Iteration for detrend 2
+	   'operation=s'   => \$operation, # Operation to perform on files
+	   'mask'          => \$mask, # Operation is on a mask
+	   'workdir=s'     => \$workdir, # Working directory for output files
+	   'dbname=s'      => \$dbname,	# Database name
+	   'no-update'     => \$no_update, # Don't update the database
+	   ) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options --det_type --filelevel --inst --telescope --det_id1 --iteration1 --det_id2 --iteration2 --workdir",
+	   -exitval => 3,
+	   )
+    unless defined $det_type
+    and defined $filelevel
+    and defined $inst
+    and defined $telescope
+    and defined $det_id1
+    and defined $iter1
+    and defined $det_id2
+    and defined $iter2
+    and defined $operation
+    and defined $workdir;
+
+$ipprc->define_camera($inst);
+
+my $STATS = 
+   [   
+       #          PPSTATS KEYWORD         STATISTIC          CHIPTOOL FLAG
+       { name => "ROBUST_MEDIAN",  type => "mean",  flag => "-bg",             dtype => "float" },
+       { name => "ROBUST_MEDIAN",  type => "stdev", flag => "-bg_mean_stdev",  dtype => "float" },
+       { name => "ROBUST_STDEV",   type => "rms",   flag => "-bg_stdev",       dtype => "float" },
+   ];
+
+# Look for programs we need
+my $missing_tools;
+my $detselect = can_run('detselect') or (warn "Can't find detselect" and $missing_tools = 1);
+my $dettool = can_run('dettool') or (warn "Can't find dettool" and $missing_tools = 1);
+my $ppArith = can_run('ppArith') or (warn "Can't find ppArith" and $missing_tools = 1);
+if ($missing_tools) {
+    warn("Can't find required tools.");
+    exit($PS_EXIT_CONFIG_ERROR);
+}
+
+my $mdcParser = PS::IPP::Metadata::Config->new; # Parser for metadata config files
+
+# Get the list of inputs
+my $files1 = filelist($det_id1, $iter1); # Hash of input files for detrend 1
+my $files2 = filelist($det_id2, $iter2); # Hash of input files for detrend 2
+die("File lists for detrends have differing lengths") unless scalar keys %$files1 == scalar keys %$files2;
+
+my ($det_id, $iter);	      # Detrend identifier for the new detrend
+unless ($no_update) {
+    my $command = "$dettool -register_detrend -det_type $det_type -filelevel $filelevel -workdir $workdir " .
+	"-inst $inst -telescope $telescope"; # Command to run
+    $command .= " -filter $filter" if defined $filter;
+    $command .= " -dbname $dbname" if defined $dbname;
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run(command => $command, verbose => 1);
+    unless ($success) {
+	$error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+	die("Unable to run dettool -register_detrend: $error_code");
+    }
+
+    my $metadata = $mdcParser->parse(join "", @$stdout_buf) or die("Unable to parse metadata config doc\n");
+    my $md = parse_md_list($metadata) or die("Unable to parse metadata list\n");
+
+    $det_id = $$md[0]->{det_id};
+    $iter = $$md[0]->{iteration};
+
+    die("Unable to get det_id and iteration for new detrend.\n") unless defined $det_id and defined $iter;
+} else {
+    $det_id = 'DUMMY_DET_ID';
+    $iter = 'DUMMY_ITER';
+}
+
+my $outRoot = caturi($workdir, "$inst.$det_id.$iter"); # Output root name
+my $filerule = (defined $mask ? "PPARITH.OUTPUT.MASK" : "PPARITH.OUTPUT.IMAGE"); # File rule for ppArith
+
+foreach my $class_id ( keys %$files1 ) {
+    my $md1 = $$files1{$class_id};
+    my $md2 = $$files2{$class_id};
+    die("Class_id=$class_id not defined for det_id=$det_id2") unless defined $md2;
+
+    my $uri1 = $$md1[0]->{uri};
+    my $uri2 = $$md2[0]->{uri};
+
+    die("Unable to find input file $uri1\n") unless $ipprc->file_exists($uri1);
+    die("Unable to find input file $uri2\n") unless $ipprc->file_exists($uri2);
+
+    my $outName = $ipprc->filename($filerule, $outRoot, $class_id);
+    my $outStats = $outRoot . '.stats';
+
+    my $command = "$ppArith -file1 $uri1 -op \'$operation\' -file2 $uri2 $outRoot"; # Command to run
+    $command .= " -stats $outStats -recipe PPSTATS $RECIPE_PPSTATS";
+    $command .= ' -mask' if defined $mask;
+    $command .= " -dbname $dbname" if defined $dbname;
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run(command => $command, verbose => 1);
+    unless ($success) {
+	$error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+	die("Unable to run ppArith: $error_code");
+    }
+
+    die("Unable to find ppArith product: $outName\n") unless $ipprc->file_exists($outName);
+    die("Unable to find ppArith product: $outStats\n") unless $ipprc->file_exists($outStats);
+
+    # Get the statistics on the processed image
+    my $stats = PS::IPP::Metadata::Stats->new($STATS); # Stats parser
+    {
+	my $statsFile;		# File handle
+	open $statsFile, $ipprc->file_resolve($outStats) or die("Can't open stats file $outStats: $!");
+	my @contents = <$statsFile>; # Contents of file
+	close $statsFile;
+	
+	my $metadata = $mdcParser->parse(join "", @contents) or die("Unable to parse metadata config doc");
+
+	unless ($stats->parse($metadata)) {
+	    &my_die("Failure extracting metadata from the statistics output file.\n");
+	}
+    }
+
+    # Register the imfile
+    unless ($no_update) {
+	my $command = "$dettool -register_detrend_imfile -det_id $det_id "; # Command to run
+	$command .= " -class_id $class_id -uri $outName -path_base $outRoot";
+	$command .= $stats->cmdflags();
+	$command .= " -dbname $dbname" if defined $dbname;
+	my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	    run(command => $command, verbose => 1);
+	unless ($success) {
+	    $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+	    die("Unable to run dettool -register_detrend_imfile: $error_code");
+	}
+    }
+}
+
+
+### Pau.
+
+
+# Get a list of files for the given detrend
+sub filelist
+{
+    my $det_id = shift;		# Detrend identifier
+    my $iter = shift;		# Iteration
+
+    my $command = "$detselect -select -det_id $det_id -iteration $iter"; # Command to run
+    $command .= " -dbname $dbname" if defined $dbname;
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run(command => $command, verbose => 1);
+    unless ($success) {
+	$error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+	die("Unable to run detselect: $error_code");
+    }
+
+    # Because of the length, need to split into individual metadatas --- it parses SO much quicker!
+    my %files;
+
+    my $md = $mdcParser->parse( join( "", @$stdout_buf ) ); # Parsed metadata
+    my $list = parse_md_list( $md );
+
+    foreach my $item ( @$list ) {
+	my $class_id = $item->{class_id};
+	die("Multiple definitions of class_id=$class_id found for det_id=$det_id, iteration=$iter\n") if
+	    defined $files{$class_id};
+	$files{$class_id} = parse_md_list($md);
+    }
+
+    return \%files;
+}
+
+END {
+    my $status = $?;
+    system("sync") == 0
+        or die "failed to execute sync: $!" ;
+    $? = $status;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_filename.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_filename.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_filename.pl	(revision 22157)
@@ -0,0 +1,52 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+ 
+# use these to check the apache environment
+#print "$ENV{'PATH'}\n";
+#print "$ENV{'PERL5LIB'}\n";
+
+use PS::IPP::Config;
+my $ipprc = PS::IPP::Config->new();
+  
+my ($filerule, $class_id, $basename, $camera, $touch);
+
+GetOptions('filerule=s'    => \$filerule,
+	   'class_id=s'    => \$class_id,
+	   'basename=s'    => \$basename,
+	   'camera|c=s'    => \$camera,
+           'touch'         => \$touch,
+	   ) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage(
+    -msg => "Required options: --filerule --class_id --basename --camera",
+    -exitval => 3,
+) unless defined $basename
+    and defined $filerule 
+    and defined $class_id 
+    and defined $camera;
+
+$touch = 0 unless (defined $touch);
+
+$ipprc->define_camera($camera);
+
+# print "$filerule\n";
+# print "$basename\n";
+# print "$camera\n";
+# print "$class_id\n";
+
+my $filename = $ipprc->filename($filerule, $basename, $class_id);
+# print "$filename\n";
+# print "touch: $touch\n";
+
+my $realname = $ipprc->file_resolve( $filename, $touch );
+print "$realname\n";
+
+1;
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_image_path.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_image_path.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_image_path.pl	(revision 22157)
@@ -0,0 +1,162 @@
+#!/usr/bin/env perl
+
+# ipp_image_path.pl print out the unix path name(s) for rawImage files for an exposure
+#
+
+use warnings;
+use strict;
+
+# get images that are on ipp008 from alternative location until the
+# dataase is updated
+my $use_008_workaround  = 1;
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use DBI;
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Config 1.01 qw( :standard );
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+my ($exp_name, $class_id, $from_registered, $dbname, $verbose);
+
+GetOptions(
+    'exp_name|e=s'  => \$exp_name,
+    'class_id|c=s'  => \$class_id,
+    'registered|r'  => \$from_registered,
+    'dbname|d=s'    => \$dbname, # Database name    
+    'verbose'       => \$verbose,   # Print to stdout
+) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options: --exp_name",
+	   -exitval => 3) unless
+    defined $exp_name;
+
+my $ota_num;
+if ($class_id) {
+    $class_id = lc($class_id);
+    my $extra;
+    (undef, $ota_num, $extra) = $class_id =~ m/(\D*)(\d\d)(.*)/;
+    if ($extra || !$ota_num) {
+        print STDERR "$class_id is not a valid class_id\n";
+        exit 1;
+    }
+}
+
+# Look for commands we need
+my $missing_tools;
+my $neb_locate = can_run('neb-locate')
+    or (warn "can't find neb-locate" and $missing_tools = 1);
+
+if ($missing_tools) { 
+    warn ("Can't find required tools");
+    exit($PS_EXIT_CONFIG_ERROR); 
+}
+
+my $ipprc = PS::IPP::Config->new();
+my $dbh = getDBHandle();
+
+# Get the list of imfiles
+{
+    # XXX: If there are multiple exposures with the same exposure name
+    # this query will return them all
+    my $query;
+    if ($from_registered) {
+        $query = "SELECT exp_id, class_id, uri FROM rawImfile"
+                . " WHERE exp_name = \'$exp_name\'";
+        $query .= " AND class_id = \'xy$ota_num\'" if ($ota_num);
+    } else {
+        $query = "SELECT exp_id FROM newExp WHERE tmp_exp_name = ?";
+        my $stmt = $dbh->prepare($query);
+        $stmt->execute($exp_name);
+        my $exp_ref = $stmt->fetchrow_hashref();
+        if (!$exp_ref) {
+            print STDERR "exposure $exp_name not found\n";
+            exit 1;
+        }
+        $stmt->finish();
+        $query = "SELECT exp_id, tmp_class_id, uri FROM newImfile WHERE exp_id = $exp_ref->{exp_id}";
+        $query .= " AND tmp_class_id = \'ota$ota_num\'" if ($ota_num);
+    }
+
+    my $stmt = $dbh->prepare($query);
+    $stmt->execute();
+    my $nfiles = 0;
+    while (my $ref = $stmt->fetchrow_hashref()) {
+        my $uri = $ref->{uri};
+        my $path;
+        my ($scheme) = $uri =~/^(path|neb|file):/;
+
+        if (!$scheme) {
+            $path =  $uri;
+        } else {
+            if ($use_008_workaround && ($uri =~ /ipp008/)) {
+                $path = resolve_ipp008_file($uri);
+            } else {
+                $path = $ipprc->file_resolve($uri);
+            }
+        }
+        if ($path) {
+            # remove the leading "file://" if present
+            $path =~ s/^file\:\/\///;
+        }
+        $nfiles++;
+        print "$path\n";
+    }
+    if (!$nfiles) {
+        my $cstr = defined($class_id) ? $class_id . " " : "";
+        print STDERR "exposure $exp_name " . $cstr . "not found\n";
+        exit 2;
+    }
+}
+
+sub getDBHandle {
+    my $dbserver = metadataLookupStr($ipprc->{_siteConfig}, "DBSERVER");
+    my $dbuser = metadataLookupStr($ipprc->{_siteConfig}, "DBUSER");
+    my $dbpassword = metadataLookupStr($ipprc->{_siteConfig}, "DBPASSWORD");
+    if (!$dbname) {
+        $dbname = metadataLookupStr($ipprc->{_siteConfig}, "DBNAME");
+    }
+
+    die "database configuration set up" unless defined($dbserver) and defined($dbuser)
+        and defined($dbpassword) and defined($dbname);
+
+    my $dsn = "DBI:mysql:host=$dbserver;database=$dbname";
+
+    my $dbh = DBI->connect($dsn, $dbuser, $dbpassword) 
+        or die "Cannot connect to database.\n";
+
+    return $dbh;
+}
+
+sub resolve_ipp008_file {
+    my %locations = ( "ota24" => "ipp006",
+                      "ota25" => "ipp009",
+                      "ota26" => "ipp011",
+                      "ota27" => "ipp019",
+                      "ota30" => "ipp020",
+                      "ota31" => "ipp021"
+                    );
+
+    my $uri = shift;
+
+    # uri's look like neb://ipp008.0/gpc1/20080625/o4642g0400o/o4642g0400o.ota24.fits
+    
+    my ($left, $middle, $ota, $fits) = split '\.', $uri;
+    
+    my $node = $locations{$ota};
+
+    die "can't find node for $ota: $uri" if (!$node);
+
+    my $path =  $uri;
+    
+    $path =~  s%neb://ipp008.0%/data/${node}.0/recover08%;
+
+    die "sorry backup image for $uri not found\n" if (!-e $path);
+
+    return $path;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_inject_expname.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_inject_expname.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_inject_expname.pl	(revision 22157)
@@ -0,0 +1,190 @@
+#!/usr/bin/env perl
+
+# this program injects a set of files the db, assuming the list of
+# files all belong to a single exposure the user supplies a temporary
+# telescope and camera name.  these are used for informational
+# purposes only until the registration step can determine the true
+# telescope and camera name from the image headers.
+
+# this program should not fail because of the data format or the
+# configuration, except for the very basic database setup.
+
+use warnings;
+use strict;
+
+use IPC::Cmd 0.36 qw( can_run run );
+use File::Spec;
+use PS::IPP::Config 1.01 qw( :standard );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::List qw( parse_md_list );
+
+my $ipprc = PS::IPP::Config->new(); # this is used for PATH, NEB filename conversions
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+# Parse the command-line arguments
+my ($camera, $telescope, $workdir, $reduction, $dvo_db, $tess_id, $end_stage, $label, $from_db, $dbname, $no_op, $verbose, $help);
+GetOptions('camera|i=s'     => \$camera,    # user-supplied camera name
+	   'telescope|t=s'  => \$telescope, # user-supplied telescope name
+	   'workdir|w=s'    => \$workdir,   # working directory for output files
+	   'reduction=s'    => \$reduction, # user-supplied camera name
+	   'dvodb=s'        => \$dvo_db,    # target dvo database 
+	   'tess_id=s'      => \$tess_id,   # tessalation for warping
+	   'end_stage=s'    => \$end_stage, # stop processing at this step
+	   'label=s'        => \$label,     # set chip label
+	   'from=s'         => \$from_db,   # Source Database name
+	   'dbname|d=s'     => \$dbname,    # Database name
+           'verbose'        => \$verbose,   # Print to stdout
+	   'no-op'          => \$no_op,     # pretend but don't actually inject
+	   'help'           => \$help       # give help listing
+	   ) or pod2usage( 2 );
+
+pod2usage( -msg => "inject files based on query to another IPP db", 
+	   -exitval => 2) if 
+    defined $help;
+
+pod2usage( -msg => "Usage: $0 [--from dbname] [--workdir path] [--reduction class] [--dvodb db] [--tess_id tess] [--end_stage stage] [--label label] [--dbname dbname] (exp_name)", 
+	   -exitval => 2 ) if 
+    scalar @ARGV == 0;
+
+pod2usage(
+          -msg => "Required options: --exp_tag --cam_id --camera --outroot",
+          -exitval => 3,
+          ) unless
+    defined $from_db;
+
+my $exp_name = $ARGV[0];
+
+my $pxinject = can_run('pxinject') or die "Can't find pxinject\n";
+my $regtool = can_run('regtool') or die "Can't find regtool\n";
+
+# tess_id needs to be an absolute path, or it must start with file://, path://, or neb://
+if ($tess_id) {
+    $tess_id = &fixpath ($tess_id);
+}
+
+# dvodb needs to be an absolute path, or it must start with file://, path://, or neb://
+if ($dvo_db) {
+    $dvo_db = &fixpath ($dvo_db);
+}
+
+# workdir needs to be an absolute path, or it must start with file://, path://, or neb://
+if ($workdir) {
+    $workdir = &fixpath ($workdir);
+}
+
+# if workdir is not defined, assign the current path
+if (! $workdir) {
+    $workdir = File::Spec->rel2abs( "." );
+}
+
+if (! $telescope) {
+    $telescope = "UNKNOWN";
+}
+
+if (! $camera) {
+    $camera = "UNKNOWN";
+}
+
+my $mdcParser = PS::IPP::Metadata::Config->new; # Parser for metadata config files
+
+# query the from_db for the desired imfiles:
+# regtool -dbname $from_db -processedimfile -exp_name $exp_name
+
+# Get list of component files
+my $files;                      # Array of component files
+{
+    my $command = "$regtool -dbname $from_db -processedimfile -exp_name $exp_name"; # Command to run
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        die "Unable to perform regtool for $exp_name: $error_code";
+    }
+    my $metadata = $mdcParser->parse(join "", @$stdout_buf) or
+        die "Unable to parse metadata config doc $exp_name: $error_code";
+
+    # extract the metadata for the files into a hash list
+    $files = parse_md_list($metadata) or
+        die "Unable to parse metadata list: $exp_name: $error_code";
+}
+
+## inject the exposure
+
+# the telescope, instrument, and exp_name used here are temporary : register replaces them with the true values
+my $command_exp = "$pxinject -newExp";
+$command_exp .= " -tmp_exp_name $exp_name";
+$command_exp .= " -tmp_inst $camera";
+$command_exp .= " -tmp_telescope $telescope";
+$command_exp .= " -workdir $workdir";
+$command_exp .= " -reduction $reduction" if defined $reduction;
+$command_exp .= " -dvodb $dvo_db"        if defined $dvo_db;
+$command_exp .= " -tess_id $tess_id"     if defined $tess_id;
+$command_exp .= " -end_stage $end_stage" if defined $end_stage;
+$command_exp .= " -label $label"         if defined $label;
+$command_exp .= " -dbname $dbname"       if defined $dbname;
+
+my $exp_id = 0;
+unless ($no_op) {
+    my ( $success_exp, $error_code_exp, $full_buf_exp, $stdout_buf_exp, $stderr_buf_exp ) =
+	run( command => $command_exp, verbose => 1 );
+    die "Unable to inject $exp_name: $error_code_exp\n" if not $success_exp;
+
+    my @line = split(/\s+/, $$stdout_buf_exp[0]); # The output line, containing the exposure tag
+    $exp_id = $line[2];	# The exposure tag
+} else {
+    print "skipping command: $command_exp\n";
+}
+
+# now inject the imfiles one at a time
+foreach my $file (@$files) {
+
+    my $uri = $file->{uri};
+    my $class_id = $file->{class_id};
+
+    # the class_id used here is temporary : register replaces it with the true class_id
+    my $command_imfile = "$pxinject -newImfile";
+    $command_imfile .= " -exp_id $exp_id";
+    $command_imfile .= " -tmp_class_id $class_id";
+    $command_imfile .= " -uri $uri";
+    $command_imfile .= " -dbname $dbname" if defined ($dbname);
+    
+    unless ($no_op) {
+	my ( $success_imfile, $error_code_imfile, $full_buf_imfile, $stdout_buf_imfile, $stderr_buf_imfile ) = run( command => $command_imfile, verbose => 1 );
+	die "Unable to inject $exp_name imfile: $error_code_imfile\n" if not $success_imfile;
+    } else {
+	print "skipping command: $command_imfile\n";
+    }
+}
+
+# the class_id used here is temporary : register replaces it with the true class_id
+my $command_update = "$pxinject -updatenewExp";
+$command_update .= " -exp_id $exp_id";
+$command_update .= " -state run";
+$command_update .= " -dbname $dbname" if defined ($dbname);
+
+unless ($no_op) {
+    my ( $success_update, $error_code_update, $full_buf_update, $stdout_buf_update, $stderr_buf_update ) = run( command => $command_update, verbose => 1 );
+    die "Unable to update $exp_name: $error_code_update\n" if not $success_update;
+} else {
+    print "skipping command: $command_update\n";
+}
+
+exit 0;
+
+sub fixpath {
+    my $path = shift;
+    my $valid = 0;
+    $valid |= ($path =~ m|^file://|);
+    $valid |= ($path =~ m|^path://|);
+    $valid |= ($path =~ m|^neb://|);
+    $valid |= ($path =~ m|^/|);
+
+    if (!$valid) {
+	$path = File::Spec->rel2abs( $path );
+    }
+    return $path;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_inject_fileset.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_inject_fileset.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_inject_fileset.pl	(revision 22157)
@@ -0,0 +1,177 @@
+#!/usr/bin/env perl
+
+# this program injects a set of files the db, assuming the list of
+# files all belong to a single exposure the user supplies a temporary
+# telescope and camera name.  these are used for informational
+# purposes only until the registration step can determine the true
+# telescope and camera name from the image headers.
+
+# this program should not fail because of the data format or the
+# configuration, except for the very basic database setup.
+
+use warnings;
+use strict;
+
+use IPC::Cmd 0.36 qw( can_run run );
+use File::Spec;
+use PS::IPP::Config;
+
+my $ipprc = PS::IPP::Config->new(); # this is used for PATH, NEB filename conversions
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+# Parse the command-line arguments
+my ($camera, $telescope, $workdir, $reduction, $dvo_db, $tess_id, $end_stage, $label, $dbname, $no_op, $help);
+GetOptions('camera|i=s'     => \$camera,    # user-supplied camera name
+	   'telescope|t=s'  => \$telescope, # user-supplied telescope name
+	   'workdir|w=s'    => \$workdir,   # working directory for output files
+	   'reduction=s'    => \$reduction, # user-supplied camera name
+	   'dvodb=s'        => \$dvo_db,    # target dvo database 
+	   'tess_id=s'      => \$tess_id,   # tessalation for warping
+	   'end_stage=s'    => \$end_stage, # stop processing at this step
+	   'label=s'        => \$label,     # set chip label
+	   'dbname|d=s'     => \$dbname,    # Database name
+	   'no-op'          => \$no_op,     # pretend but don't actually inject
+	   'help'           => \$help       # give help listing
+	   ) or pod2usage( 2 );
+
+pod2usage( -msg => "inject one or many files into the IPP pipeline database", 
+	   -exitval => 2) if 
+    defined $help;
+
+pod2usage( -msg => "Usage: $0 --telescope (name) --camera (name) [--workdir path] [--reduction class] [--dvodb db] [--tess_id tess] [--end_stage stage] [--label label] [--dbname dbname] (files)", 
+	   -exitval => 2 ) if 
+    scalar @ARGV == 0;
+
+# XXX why are these required?
+pod2usage( -msg => "Required options: --telescope (name) --camera (name)",
+	   -exitval => 3) unless @ARGV > 0;
+
+my $pxinject = can_run('pxinject') or die "Can't find pxinject\n";
+
+# tess_id needs to be an absolute path, or it must start with file://, path://, or neb://
+if ($tess_id) {
+    $tess_id = &fixpath ($tess_id);
+}
+
+# dvodb needs to be an absolute path, or it must start with file://, path://, or neb://
+if ($dvo_db) {
+    $dvo_db = &fixpath ($dvo_db);
+}
+
+# workdir needs to be an absolute path, or it must start with file://, path://, or neb://
+if ($workdir) {
+    $workdir = &fixpath ($workdir);
+}
+
+# if workdir is not defined, assign the current path
+if (! $workdir) {
+    $workdir = File::Spec->rel2abs( "." );
+}
+
+if (! $telescope) {
+    $telescope = "UNKNOWN";
+}
+
+if (! $camera) {
+    $camera = "UNKNOWN";
+}
+
+# use the first file name as the exp_name (strip off .fits)
+my $num = 0;
+my $exp_name;
+foreach my $file ( @ARGV ) {
+    # check for file existence
+    if (! -e $file) { die "file $file not found\n"; }
+    if (! $exp_name) { 
+	# strip off the extension
+	my ( $vol, $path, $name ) = File::Spec->splitpath( $file );
+	( $exp_name ) = $name =~ /(.*)\.(fits|fit|fts)(|.gz)/;
+	print "exp_name : $exp_name.\n";
+    }
+    $num ++;
+}
+
+print "$num files in fileset.\n";
+
+## inject the exposure
+
+# the telescope, instrument, and exp_name used here are temporary : register replaces them with the true values
+my $command_exp = "$pxinject -newExp";
+$command_exp .= " -tmp_exp_name $exp_name";
+$command_exp .= " -tmp_inst $camera";
+$command_exp .= " -tmp_telescope $telescope";
+$command_exp .= " -workdir $workdir";
+$command_exp .= " -reduction $reduction" if defined $reduction;
+$command_exp .= " -dvodb $dvo_db"        if defined $dvo_db;
+$command_exp .= " -tess_id $tess_id"     if defined $tess_id;
+$command_exp .= " -end_stage $end_stage" if defined $end_stage;
+$command_exp .= " -label $label"         if defined $label;
+$command_exp .= " -dbname $dbname"       if defined $dbname;
+
+my $exp_id = 0;
+unless ($no_op) {
+    my ( $success_exp, $error_code_exp, $full_buf_exp, $stdout_buf_exp, $stderr_buf_exp ) =
+	run( command => $command_exp, verbose => 1 );
+    die "Unable to inject $exp_name: $error_code_exp\n" if not $success_exp;
+
+    my @line = split(/\s+/, $$stdout_buf_exp[0]); # The output line, containing the exposure tag
+    $exp_id = $line[2];	# The exposure tag
+} else {
+    print "skipping command: $command_exp\n";
+}
+
+# now inject the imfiles one at a time
+for (my $i = 0; $i < @ARGV; $i++) {
+
+    my $file = $ARGV[$i];
+
+    my $absfile = File::Spec->rel2abs( $file );
+    my $relfile = $ipprc->convert_filename_relative( $absfile );
+
+    # the class_id used here is temporary : register replaces it with the true class_id
+    my $command_imfile = "$pxinject -newImfile";
+    $command_imfile .= " -exp_id $exp_id";
+    $command_imfile .= " -tmp_class_id file.$i";
+    $command_imfile .= " -uri $relfile";
+    $command_imfile .= " -dbname $dbname" if defined ($dbname);
+    
+    unless ($no_op) {
+	my ( $success_imfile, $error_code_imfile, $full_buf_imfile, $stdout_buf_imfile, $stderr_buf_imfile ) = run( command => $command_imfile, verbose => 1 );
+	die "Unable to inject $exp_name imfile: $error_code_imfile\n" if not $success_imfile;
+    } else {
+	print "skipping command: $command_imfile\n";
+    }
+}
+
+# the class_id used here is temporary : register replaces it with the true class_id
+my $command_update = "$pxinject -updatenewExp";
+$command_update .= " -exp_id $exp_id";
+$command_update .= " -state run";
+$command_update .= " -dbname $dbname" if defined ($dbname);
+
+unless ($no_op) {
+    my ( $success_update, $error_code_update, $full_buf_update, $stdout_buf_update, $stderr_buf_update ) = run( command => $command_update, verbose => 1 );
+    die "Unable to update $exp_name: $error_code_update\n" if not $success_update;
+} else {
+    print "skipping command: $command_update\n";
+}
+
+exit 0;
+
+sub fixpath {
+    my $path = shift;
+    my $valid = 0;
+    $valid |= ($path =~ m|^file://|);
+    $valid |= ($path =~ m|^path://|);
+    $valid |= ($path =~ m|^neb://|);
+    $valid |= ($path =~ m|^/|);
+
+    if (!$valid) {
+	$path = File::Spec->rel2abs( $path );
+    }
+    return $path;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_maskscript.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_maskscript.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_maskscript.pl	(revision 22157)
@@ -0,0 +1,190 @@
+#!/usr/bin/env perl
+
+# use warnings;
+# use strict;
+use Carp;
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+use IPC::Cmd 0.36 qw( can_run run );
+use IO::Handle;
+
+use PS::IPP::Metadata::List qw( parse_md_list );
+use PS::IPP::Config 1.01 qw( :standard );
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+my ($dbname, $det_id, $camera);
+
+GetOptions('dbname=s'    => \$dbname,
+	   'det_id=s'    => \$det_id,
+	   'camera|c=s'  => \$camera,
+	   ) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+
+pod2usage(
+	  -msg => "USAGE: ipp_maskscript.pl --dbname (name) --det_id (id) --iter (iteration) --camera (name)",
+	  -exitval => 3,
+	  ) unless defined $dbname and defined $det_id and defined $camera;
+
+# I could determine the camera from a query for the detrun
+$ipprc->define_camera($camera);
+
+###  Get list of dark imfile results
+
+# define the dettool command
+my $command = "dettool -processedimfile -select_state stop"; # Command to run
+$command .= " -det_id $det_id";
+$command .= " -dbname $dbname" if defined $dbname;
+
+# run the dettool command and catch the output
+my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+    run(command => $command, verbose => 0);
+unless ($success) {
+    $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+    &my_die("Unable to perform dettool: $error_code", $error_code);
+}
+
+# parse the output into a list
+my $mdcParser = PS::IPP::Metadata::Config->new;	# Parser for metadata config files
+my $metadata = $mdcParser->parse(join "", @$stdout_buf) or
+    &my_die("Unable to parse metadata config doc", $PS_EXIT_PROG_ERROR);
+my $list = parse_md_list($metadata) or
+    &my_die("Unable to parse metadata list", $PS_EXIT_PROG_ERROR);
+
+my @bg_data;
+my @bg_stdev_data;
+my @bg_name;
+my @bg_exptime;
+my %components;
+
+print STDERR "extracted the data from the database\n";
+
+# we now have a list of imfiles; we need to extract the background for each cell
+# from the stats files for each imfile
+foreach my $item (@$list) {
+    my $path_base = $item->{path_base};
+    my $class_id = $item->{class_id};
+    my $exp_time = $item->{exp_time};
+
+    my $rootName  = $ipprc->file_resolve ($path_base);
+    my $statsName = "$rootName.$class_id.stats";
+
+    # print STDERR "rootName: $rootName : $exp_time\n";
+    print STDERR "statsName: $statsName : $exp_time\n";
+
+    my $statsFile;
+    open $statsFile, $statsName;
+    my @contents = <$statsFile>;
+    close ($statsFile);
+
+    # print STDERR "contents: @contents\n";
+
+    my $parser = PS::IPP::Metadata::Config->new;	# Parser for metadata config files
+    my $statsList = $parser->parse(join "", @contents) or &my_die("Unable to parse metadata for imfile stats", $PS_EXIT_SYS_ERROR);
+
+    &parse_stats_table ($exp_time, $class_id, $statsList);
+}
+
+print STDERR "parsed the stats from the data files\n";
+
+for (my $i = 0; $i < @bg_data; $i++) {
+    $nameX = "$bg_name[$i].exp";
+    $nameY = "$bg_name[$i].bg";
+    push @{$nameX}, $bg_exptime[$i];
+    push @{$nameY}, $bg_data[$i];
+}
+
+if (-e "output.dat") { unlink "output.dat"; }
+
+print STDERR "dumping stats\n";
+open (MANA, "|mana --norc");
+MANA->autoflush;
+
+foreach my $component (@components) {
+    $nameX = "$component.exp";
+    $nameY = "$component.bg";
+
+    print MANA "delete X Y\n";
+
+    open (DATA, ">$component.dat");
+    for (my $i = 0; $i < @{$nameX}; $i++) {
+	print DATA "${$nameX}[$i] ${$nameY}[$i]\n";
+    }
+    close (DATA);
+
+    print MANA "data $component.dat\n";
+    print MANA "read X 1 Y 2\n";
+    print MANA "fit X Y 2 -clip 3 3\n";
+    print MANA "output output.dat\n";
+    print MANA "echo $component METADATA\n";
+    print MANA "echo \"   NORDER_X  S32 2   \"\n";
+    print MANA "echo \"   VAL_X00   F64 \$C0\"\n";
+    print MANA "echo \"   VAL_X01   F64 \$C1\"\n";
+    print MANA "echo \"   VAL_X02   F64 \$C2\"\n";
+    print MANA "echo \"   NELEMENTS S32 3    \"\n";
+    print MANA "echo END\n";
+    print MANA "echo\n";
+    print MANA "output stdout\n";
+
+    print MANA "applyfit X Yf\n";
+    print MANA "lim X Y\n";
+    print MANA "clear\n";
+    print MANA "box\n";
+    print MANA "plot -x 2 -pt 2 -sz 1.0 -c black X Y\n";
+    print MANA "plot -x 2 -pt 7 -sz 1.0 -c red X Yf\n";
+
+    print STDERR "hit return to continue\n";
+    $answer = <STDIN>;
+}
+
+close (MANA);
+
+exit 0;
+
+sub parse_stats_table
+{
+    my ($exp_time, $tag, $md) = @_;
+
+    # descend through the fpa        
+    foreach my $entry (@$md) {
+	# print STDERR "name: $entry->{name}, class: $entry->{class}\n";
+        # recurse on nested metadata
+        if ($entry->{class} eq 'metadata') {
+	    my $newtag = $tag . "_" . $entry->{name};
+            &parse_stats_table ($exp_time, $newtag, $entry->{value});
+        }
+
+        if ($entry->{name} =~ /^(SAMPLE|ROBUST|FITTED|CLIPPED)/) {
+            # It's a statistic of some sort
+            if ($entry->{name} =~ /_STDEV$/) {
+                push @bg_stdev_data, $entry->{value};
+            } else {
+		push @bg_name,    $tag;
+                push @bg_data,    $entry->{value};
+		push @bg_exptime, $exp_time;
+		# print STDERR "$tag $exp_time $entry->{value}\n";
+            }
+	    if (!$componentsHash{$tag}) {
+		push @components, $tag;
+		$componentsHash{$tag} = 1;
+	    }
+	    next;
+	} 
+    }
+    return 1;
+}
+
+sub my_die
+{
+    my $msg = shift; # Warning message on die
+    my $exit_code = shift; # Exit code to add
+
+    carp($msg);
+    exit $exit_code;
+}
+
+# - get the exp_time as well from dettool
+# - build an array of bg & exptime for each cell
+# - fit the trend (in mana? pslib functions?)
+# - write the polynomial for each cell
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_mops_translate.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_mops_translate.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_mops_translate.pl	(revision 22157)
@@ -0,0 +1,255 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+use Astro::FITS::CFITSIO qw( :constants );
+Astro::FITS::CFITSIO::PerlyUnpacking(1);
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+use Math::Trig;
+use Data::Dumper;
+
+use constant EXTNAME => 'MOPS_TRANSIENT_DETECTIONS'; # Extension name for output table
+use constant POSITION_ERROR => 0.2 / 3600; # Error in positions, degrees
+use constant ZERO_POINT => 25;  # Magnitude zero point
+use constant MAG_ERROR => 0.1;  # Error in magnitudes
+use constant OBSERVATORY_CODE => 566; # IAU Observatory Code
+use constant FAKE_LIMITING_MAG => 23.0; # Fake limiting magnitude to report
+use constant FAKE_DETECTION_EFFICIENCY => 0.0; # Fake detection efficiency to report
+
+my ( $input,                    # Name of input file with IPP photometry
+     $extname,                  # Name of extension containing photometry
+     $skycell,                  # Skycell file with WCS
+     $output,                   # Name of output file
+     $save_temps,               # Save temporary files?
+     );
+
+GetOptions(
+           'input=s'    => \$input,
+           'extname=s'  => \$extname,
+           'skycell=s'  => \$skycell,
+           'output=s'   => \$output,
+           'save-temps' => \$save_temps,
+) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options: --input --extname --skycell --output",
+           -exitval => 3)
+    unless defined $input
+    and defined $extname
+    and defined $skycell
+    and defined $output;
+
+# Specification of columns to write
+my $columns = [ { name => 'RA_DEG',   type => 'D' }, # Right ascension
+                { name => 'DEC_DEG',  type => 'D' }, # Declination
+                { name => 'RA_SIG',   type => 'D' }, # Error in right ascension
+                { name => 'DEC_SIG',  type => 'D' }, # Error in declination
+                { name => 'FLUX',     type => 'D' }, # Flux
+                { name => 'FLUX_SIG', type => 'D' }, # Error in flux
+                { name => 'ANG',      type => 'D' }, # Angle
+                { name => 'ANG_SIG',  type => 'D' }, # Error in angle
+                { name => 'LEN',      type => 'D' }, # Length
+                { name => 'LEN_SIG',  type => 'D' }, # Error in length
+                ];
+
+# Header translation table
+my $headers = {
+    'FPA.RA'       => { name => 'RA',       type => TSTRING, comment => 'Right ascension of boresight'},
+    'FPA.DEC'      => { name => 'DEC',      type => TSTRING, comment => 'Declination of boresight' },
+    'FPA.OBS'      => { name => 'FPA_ID',   type => TSTRING, comment => 'Exposure identifier' },
+    'FPA.FILTER'   => { name => 'FILTER',   type => TSTRING, comment => 'Filter name' },
+    'EXPTIME'      => { name => 'EXPTIME',  type => TDOUBLE, comment => 'Exposure time' },
+    'FPA.POSANGLE' => { name => 'ROTANGLE', type => TDOUBLE, comment => 'Position angle' },
+    'FPA.ALT'      => { name => 'TEL_ALT',  type => TDOUBLE, comment => 'Telescope altitude' },
+    'FPA.AZ'       => { name => 'TEL_AZ',   type => TDOUBLE, comment => 'Telescope azimuth' },
+    'MJD-OBS'      => { name => 'MJD-OBS',  type => TDOUBLE, comment => 'Time of exposure' },
+            };
+
+
+# Read the skycell header for the WCS
+my ($wcsheader, $status) = Astro::FITS::CFITSIO::fits_read_header( $skycell );
+
+# Get the useful header keywords
+my $naxis1 = $$wcsheader{'NAXIS1'} or die("Can't find NAXIS1");
+my $naxis2 = $$wcsheader{'NAXIS2'} or die("Can't find NAXIS2");
+my $ctype1 = $$wcsheader{'CTYPE1'} or die("Can't find CTYPE1");
+my $ctype2 = $$wcsheader{'CTYPE2'} or die("Can't find CTYPE2");
+my $cdelt1 = $$wcsheader{'CDELT1'} or die("Can't find CDELT1");
+my $cdelt2 = $$wcsheader{'CDELT2'} or die("Can't find CDELT2");
+my $crval1 = deg2rad($$wcsheader{'CRVAL1'}) or die("Can't find CRVAL1");
+my $crval2 = deg2rad($$wcsheader{'CRVAL2'}) or die("Can't find CRVAL2");
+my $crpix1 = $$wcsheader{'CRPIX1'} or die("Can't find CRPIX1");
+my $crpix2 = $$wcsheader{'CRPIX2'} or die("Can't find CRPIX2");
+my $pc11 = $$wcsheader{'PC001001'} or die("Can't find PC001001");
+my $pc12 = $$wcsheader{'PC001002'} or die("Can't find PC001002");
+my $pc21 = $$wcsheader{'PC002001'} or die("Can't find PC002001");
+my $pc22 = $$wcsheader{'PC002002'} or die("Can't find PC002002");
+my $crota1 = $$wcsheader{'CROTA1'};
+my $crota2 = $$wcsheader{'CROTA2'};
+
+die("Unexpected projection: $ctype1 and $ctype2.") unless $ctype1 =~ /^\'RA---TAN\s*\'$/ and
+    $ctype2 =~ /^\'DEC--TAN\s*\'$/;
+die("Can't determine size of skycell ($naxis1,$naxis2)") unless $naxis1 > 0 and $naxis2 > 0;
+die("Can't determine scale of skycell ($cdelt1,$cdelt2)") if not defined $cdelt1 or $cdelt1 == 0 or
+    not defined $cdelt2 or $cdelt2 == 0;
+die("We don't know how to handle rotations ($crota1,$crota2)") if defined $crota1 or defined $crota2;
+
+
+# Read the input table
+my $inFits = Astro::FITS::CFITSIO::open_file( $input, READONLY, $status ); # FITS file handle
+my $inHeader = $inFits->read_header(); # Header for input
+check_fitsio($status);
+$inFits->movnam_hdu(BINARY_TBL, $extname, 0, $status) and check_fitsio($status);
+my $numRows;                    # Number of rows in table
+$inFits->get_num_rows($numRows, $status) and check_fitsio($status);
+
+my ($xCol, $yCol, $magCol);             # Column numbers for x and y
+$inFits->get_colnum(0, 'X_PSF', $xCol, $status) and check_fitsio($status);
+$inFits->get_colnum(0, 'Y_PSF', $yCol, $status) and check_fitsio($status);
+$inFits->get_colnum(0, 'PSF_INST_MAG', $magCol, $status) and check_fitsio($status);
+
+my ($xType, $yType, $magType);  # Column types
+$inFits->get_coltype($xCol, $xType, undef, undef, $status) and check_fitsio($status);
+$inFits->get_coltype($yCol, $yType, undef, undef, $status) and check_fitsio($status);
+$inFits->get_coltype($magCol, $magType, undef, undef, $status) and check_fitsio($status);
+
+my ($x, $y, $mag);              # Sources arrays
+$inFits->read_col($xType, $xCol, 1, 1, $numRows, 0, $x, undef, $status) and check_fitsio($status);
+$inFits->read_col($yType, $yCol, 1, 1, $numRows, 0, $y, undef, $status) and check_fitsio($status);
+$inFits->read_col($magType, $magCol, 1, 1, $numRows, 0, $mag, undef, $status) and check_fitsio($status);
+
+$inFits->close_file( $status );
+
+# Parse the list of columns
+my @colNames;                   # Names of columns
+my @colTypes;                   # Types of columns
+my %colData;                    # Data for each column
+foreach my $colSpec ( @$columns) {
+    push @colNames, $colSpec->{name};
+    push @colTypes, $colSpec->{type};
+    $colData{$colSpec->{name}} = [];
+}
+
+
+# Convert the input data into the output formats
+for (my $i = 0; $i < $numRows; $i++) {
+    my ($ra, $dec) = pixels_to_sky($$x[$i], $$y[$i]); # Sky coordinates
+
+    push @{$colData{'RA_DEG'}}, $ra;
+    push @{$colData{'DEC_DEG'}}, $dec;
+    push @{$colData{'RA_SIG'}}, POSITION_ERROR;
+    push @{$colData{'DEC_SIG'}}, POSITION_ERROR;
+    push @{$colData{'FLUX'}}, $$mag[$i] + ZERO_POINT;
+    push @{$colData{'FLUX_SIG'}}, MAG_ERROR;
+    push @{$colData{'ANG'}}, 0.0;
+    push @{$colData{'ANG_SIG'}}, 0.0;
+    push @{$colData{'LEN'}}, 0.0;
+    push @{$colData{'LEN_SIG'}}, 0.0;
+}
+
+
+# Write the output
+unlink "$output" if -e "$output";
+my $outFits = Astro::FITS::CFITSIO::create_file( $output, $status ); # Output file handle
+check_fitsio( $status );
+
+# Write the table
+$outFits->create_tbl( BINARY_TBL(), $numRows, scalar @colNames, \@colNames, \@colTypes, undef, EXTNAME,
+                      $status );
+check_fitsio( $status );
+
+# Write the header keywords
+foreach my $keyword ( keys %$headers ) {
+    my $value = $inHeader->{$keyword}; # Header keyword value
+    unless (defined $value) {
+        print "Can't find header keyword $keyword\n";
+        next;
+    }
+    $value =~ s/\'//g;
+    my $name = $headers->{$keyword}->{name}; # New name
+    my $type = $headers->{$keyword}->{type}; # Type
+    my $comment = $headers->{$keyword}->{comment}; # Comment
+    $outFits->write_key( $type, $name, $value, $comment, $status );
+    check_fitsio( $status );
+}
+
+# Stellar PSF
+my $fwhm = 0.5 * 3600 * ($inHeader->{'FWHM_X'} * $cdelt1 + $inHeader->{'FWHM_Y'} * $cdelt2); # FWHM for star
+$outFits->write_key( TSTRING, 'STARPSF', $fwhm, 'Stellar PSF (arcsec)', $status );
+check_fitsio( $status );
+
+# Observatory code
+$outFits->write_key( TINT, 'OBSCODE', OBSERVATORY_CODE, 'IAU observatory code', $status );
+
+# Limiting magnitude
+$outFits->write_key( TINT, 'LIMITMAG', FAKE_LIMITING_MAG, 'Limiting magnitude (FAKE)', $status );
+
+# Detection efficiency
+$outFits->write_key( TINT, 'DE1', FAKE_DETECTION_EFFICIENCY, 'Detection efficiency (FAKE)', $status );
+$outFits->write_key( TINT, 'DE2', FAKE_DETECTION_EFFICIENCY, 'Detection efficiency (FAKE)', $status );
+$outFits->write_key( TINT, 'DE3', FAKE_DETECTION_EFFICIENCY, 'Detection efficiency (FAKE)', $status );
+$outFits->write_key( TINT, 'DE4', FAKE_DETECTION_EFFICIENCY, 'Detection efficiency (FAKE)', $status );
+$outFits->write_key( TINT, 'DE5', FAKE_DETECTION_EFFICIENCY, 'Detection efficiency (FAKE)', $status );
+$outFits->write_key( TINT, 'DE6', FAKE_DETECTION_EFFICIENCY, 'Detection efficiency (FAKE)', $status );
+$outFits->write_key( TINT, 'DE7', FAKE_DETECTION_EFFICIENCY, 'Detection efficiency (FAKE)', $status );
+$outFits->write_key( TINT, 'DE8', FAKE_DETECTION_EFFICIENCY, 'Detection efficiency (FAKE)', $status );
+$outFits->write_key( TINT, 'DE9', FAKE_DETECTION_EFFICIENCY, 'Detection efficiency (FAKE)', $status );
+$outFits->write_key( TINT, 'DE10', FAKE_DETECTION_EFFICIENCY, 'Detection efficiency (FAKE)', $status );
+
+# Write the data
+for (my $i = 0; $i < scalar @colNames; $i++) {
+    my $colName = $colNames[$i];# Column name
+    $outFits->write_col( TDOUBLE, $i + 1, 1, 1, $numRows, $colData{$colName}, $status );
+    check_fitsio( $status );
+}
+
+$outFits->close_file( $status );
+
+### Pau.
+
+
+
+# From Astro::FITS::CFITSIO demo
+sub check_fitsio
+{
+    my $status = shift;         # Status of FITSIO calls
+
+    if ($status != 0) {
+        my $msg;                # Message to output
+        Astro::FITS::CFITSIO::fits_get_errstatus( $status , $msg );
+        die "CFITSIO error: $msg\n";
+    }
+}
+
+# Convert pixel coordinates to sky coordinates
+sub pixels_to_sky
+{
+    my ($x, $y) = @_;           # Coordinates
+
+    # Pixel coordinate relative to reference
+    $x -= $crpix1;
+    $y -= $crpix2;
+
+    # Coordinates on tangent plane
+    my $xi = $pc11 * ($x) + $pc12 * ($y);
+    my $eta = $pc21 * ($x) + $pc22 * ($y);
+    $xi *= $cdelt1;
+    $eta *= $cdelt2;
+
+    # Coordinates on rotated celestial sphere
+    my $phi = atan2($eta,$xi) + pi/2;
+    my $theta = atan(180 / pi / sqrt($xi**2 + $eta**2));
+
+    # Coordinates on celestial sphere
+    my $ra = $crval1 + atan2(cos($theta) * sin($phi),
+                             sin($theta) * cos($crval2) + cos($theta) * sin($crval2) * cos($phi));
+    my $dec = asin(sin($theta) * sin($crval2) - cos($theta) * cos($crval2) * cos($phi));
+
+    return (rad2deg($ra), rad2deg($dec));
+}
+
+
+__END__
+
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_camera.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_camera.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_camera.pl	(revision 22157)
@@ -0,0 +1,85 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::List qw( parse_md_list );
+use PS::IPP::Config qw( caturi );
+use Data::Dumper;
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+my ($dbname,			# Database name to use
+    $workdir_default,		# Default working directory
+    $verbose,			# Verbose operations?
+    $no_op,			# No operations?
+    $no_update,			# No updating?
+    );
+GetOptions(
+	   'dbname=s' => \$dbname,
+	   'workdir=s' => \$workdir_default,
+	   'verbose' => \$verbose,
+	   'no-op' => \$no_op,
+	   'no-update' => \$no_update,
+) or pod2usage( 2 );
+
+pod2usage(
+	  -msg => "Required options: --dbname",
+	  -exitval => 3,
+	  ) unless defined $dbname;
+
+$workdir_default = `pwd` unless defined $workdir_default;
+
+my $mdcParser = PS::IPP::Metadata::Config->new;	# Metadata config parser
+my $ipprc = PS::IPP::Config->new; # IPP Configuration
+
+# Look for programs we need
+my $missing_tools;
+my $camtool = can_run('camtool') or (warn "Can't find camtool" and $missing_tools = 1);
+my $camera_exp = can_run('camera_exp.pl') or (warn "Can't find camera_exp.pl" and $missing_tools = 1);
+die "Can't find required tools.\n" if $missing_tools;
+
+# Camera exposure processing
+my $list;
+{
+    my $command = "$camtool -pendingexp -dbname $dbname"; # Command to run
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run( command => $command, verbose => 1 );
+    die "Unable to get camera exposure list: $error_code\n" if not $success;
+    $list = parse_md_list( $mdcParser->parse( join( '', @$stdout_buf ) ) ) or
+	die "Unable to parse output from camtool.\n";
+}
+
+foreach my $item (@$list) {
+    my $cam_id = $item->{cam_id};
+    my $exp_tag = $item->{exp_tag};
+    my $camera = $item->{camera};
+    my $workdir = $item->{workdir};
+    
+    $workdir = $workdir_default unless (defined $workdir or $workdir ne "NULL");
+    my $outroot = caturi( $workdir, $exp_tag, "$exp_tag.cm.$cam_id" );
+    $ipprc->outroot_prepare( $outroot );
+
+    my $command = "$camera_exp --cam_id $cam_id --exp_tag $exp_tag --camera $camera --dbname $dbname --outroot $outroot";
+    $command .= " --verbose" if defined $verbose;
+    $command .= " --no-op" if defined $no_op;
+    $command .= " --no-update" if defined $no_update;
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run( command => $command, verbose => 1 );
+    die "Unable to do camera processing on $cam_id: $error_code\n" if not $success;
+}
+
+END {
+    my $status = $?;
+    system("sync") == 0
+        or die "failed to execute sync: $!" ;
+    $? = $status;
+}
+
+
+__END__
+
+
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_chip.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_chip.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_chip.pl	(revision 22157)
@@ -0,0 +1,105 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::List qw( parse_md_list );
+use PS::IPP::Config qw( caturi );
+use Data::Dumper;
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+my ($dbname,			# Database name to use
+    $workdir_default,		# Default working directory
+    $verbose,			# Verbose operations?
+    $no_op,			# No operations?
+    $no_update,			# No updating?
+    );
+GetOptions(
+	   'dbname=s' => \$dbname,
+	   'workdir=s' => \$workdir_default,
+	   'verbose' => \$verbose,
+	   'no-op' => \$no_op,
+	   'no-update' => \$no_update,
+) or pod2usage( 2 );
+
+pod2usage( -msg => "Required options: --dbname --workdir",
+	   -exitval => 3,
+	   ) unless
+    defined $dbname;
+
+$workdir_default = `pwd` unless defined $workdir_default;
+
+my $mdcParser = PS::IPP::Metadata::Config->new;	# Metadata config parser
+my $ipprc = PS::IPP::Config->new; # IPP Configuration
+
+# Look for programs we need
+my $missing_tools;
+my $chiptool = can_run('chiptool') or (warn "Can't find chiptool" and $missing_tools = 1);
+my $chip = can_run('chip_imfile.pl') or (warn "Can't find chip_imfile.pl" and $missing_tools = 1);
+die "Can't find required tools.\n" if $missing_tools;
+
+# Imfile processing
+my @whole;			# The whole list for processing
+{
+    my $command = "$chiptool -pendingimfile -dbname $dbname"; # Command to run
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run( command => $command, verbose => 1 );
+    die "Unable to get phase 2 imfile list: $error_code\n" if not $success;
+    @whole = split /\n/, join( '', @$stdout_buf );
+}
+
+my @single = ();
+
+while ( scalar @whole > 0 ) {
+    my $value = shift @whole;
+    push @single, $value;
+    if ($value =~ /^\s*END\s*$/) {
+	push @single, "\n";
+	
+	my $list = parse_md_list( $mdcParser->parse( join( "\n", @single ) ) ) or
+	    die "Unable to parse output from chiptool.\n";
+	    
+	foreach my $item (@$list) {
+	    my $chip_id = $item->{chip_id};
+	    my $exp_id = $item->{exp_id};
+	    my $exp_tag = $item->{exp_tag};
+	    my $camera = $item->{camera};
+	    my $class_id = $item->{class_id};
+	    my $uri = $item->{uri};
+	    my $reduction = $item->{reduction};
+	    my $workdir = $item->{workdir};
+	    $workdir = $workdir_default unless (defined $workdir or $workdir ne "NULL");
+
+	    my $outroot = caturi( $workdir, $exp_tag, "$exp_tag.ch.$chip_id" );
+	    $ipprc->outroot_prepare( $outroot );
+
+	    my $command = "$chip --chip_id $chip_id --exp_id $exp_id --class_id $class_id --uri $uri --dbname $dbname --camera $camera --outroot $outroot";
+	    $command .= " --reduction $reduction" if defined $reduction;
+	    $command .= " --verbose" if defined $verbose;
+	    $command .= " --no-op" if defined $no_op;
+	    $command .= " --no-update" if defined $no_update;
+	    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+		run( command => $command, verbose => 1 );
+	    die "Unable to do phase 2 processing on $chip_id $class_id: $error_code\n" if not $success;
+	}
+
+	@single = ();
+
+    }
+}
+
+END {
+    my $status = $?;
+    system("sync") == 0
+        or die "failed to execute sync: $!" ;
+    $? = $status;
+}
+
+
+__END__
+
+
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_detrend.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_detrend.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_detrend.pl	(revision 22157)
@@ -0,0 +1,377 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+use Getopt::Long;
+use Pod::Usage qw( pod2usage );
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::List qw( parse_md_list );
+use PS::IPP::Config qw( caturi );
+use Data::Dumper;
+
+my ($dbname,			# Database name to use
+    $workdir_global,		# Global working directory
+    $verbose,			# Produce verbose output?
+    );
+GetOptions(
+	   'dbname=s'  => \$dbname,
+	   'workdir=s' => \$workdir_global,
+	   'verbose'   => \$verbose,
+) or pod2usage( 2 );
+
+pod2usage(
+	  -msg => "Required options: --dbname",
+	  -exitval => 3,
+	  ) unless defined $dbname;
+
+my $mdcParser = PS::IPP::Metadata::Config->new;	# Metadata config parser
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+# Look for programs we need
+my $missing_tools;
+my $dettool = can_run('dettool') or (warn "Can't find dettool" and $missing_tools = 1);
+my $detrend_process_imfile = can_run('detrend_process_imfile.pl')  or (warn "Can't find detrend_process_imfile.pl" and $missing_tools = 1);
+my $detrend_process_exp = can_run('detrend_process_exp.pl') or (warn "Can't find detrend_process_exp.pl" and $missing_tools = 1);
+my $detrend_stack = can_run('detrend_stack.pl') or (warn "Can't find detrend_stack.pl" and $missing_tools = 1);
+my $detrend_norm_calc = can_run('detrend_norm_calc.pl') or (warn "Can't find detrend_norm_calc.pl" and $missing_tools = 1);
+my $detrend_norm_apply = can_run('detrend_norm_apply.pl') or (warn "Can't find detrend_norm_apply.pl" and $missing_tools = 1);
+my $detrend_norm_exp = can_run('detrend_norm_exp.pl') or (warn "Can't find detrend_norm_exp.pl" and $missing_tools = 1);
+my $detrend_resid = can_run('detrend_resid.pl') or (warn "Can't find detrend_resid.pl" and $missing_tools = 1);
+my $detrend_reject_imfile = can_run('detrend_reject_imfile.pl') or (warn "Can't find detrend_reject_imfile.pl" and $missing_tools = 1);
+my $detrend_reject_exp = can_run('detrend_reject_exp.pl') or (warn "Can't find detrend_reject_exp.pl" and $missing_tools = 1);
+die "Can't find required tools.\n" if $missing_tools;
+
+# Process raw imfiles
+{
+    my $command = "$dettool -toprocessedimfile -dbname $dbname";
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run( command => $command, verbose => 1 );
+    die "Unable to get detrend raw list: $error_code\n" if not $success;
+
+    my @whole = split /\n/, join( '', @$stdout_buf );
+    my @single = ();
+
+    while ( scalar @whole > 0 ) {
+	my $value = shift @whole;
+	push @single, $value;
+	if ($value =~ /^\s*END\s*$/) {
+	    push @single, "\n";
+
+	    my $list = parse_md_list( $mdcParser->parse( join( "\n", @single ) ) ) or
+		die "Unable to parse output from dettool.\n";
+	    
+	    foreach my $item (@$list) {
+		my $det_id = $item->{det_id};
+		my $det_type = $item->{det_type};
+		my $exp_tag = $item->{exp_tag};
+		my $exp_id = $item->{exp_id};
+		my $class_id = $item->{class_id};
+		my $uri = $item->{uri};
+		my $camera = $item->{camera};
+		my $workdir = $item->{workdir};
+		my $reduction = $item->{reduction};
+
+		$workdir = $workdir_global unless defined $workdir and $workdir ne "NULL";
+		die "No working directory specified.\n" unless defined $workdir;
+
+		my $outroot = caturi( $workdir, "$camera.$det_type.$det_id", $exp_tag, "$exp_tag.detproc.$det_id" );
+		$ipprc->outroot_prepare( $outroot );
+
+		my $command = "$detrend_process_imfile --det_id $det_id --exp_tag $exp_tag --exp_id $exp_id --class_id $class_id --det_type $det_type --input_uri $uri --camera $camera --dbname $dbname --outroot $outroot";
+		$command .= " --reduction $reduction" if defined $reduction;
+		$command .= " --verbose" if defined $verbose;
+		my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+		    run( command => $command, verbose => 1 );
+		die "Unable to do raw imfile processing on $exp_tag $class_id: $error_code\n" if not $success;
+	    }
+
+	    @single = ();
+
+	}	
+    }
+}
+
+# Process raw exposures
+{
+    my $command = "$dettool -toprocessedexp -dbname $dbname";
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run( command => $command, verbose => 1 );
+    die "Unable to get detrend raw list: $error_code\n" if not $success;
+    my $list = parse_md_list( $mdcParser->parse( join( '', @$stdout_buf ) ) ) or
+	die "Unable to parse output from dettool.\n";
+
+    foreach my $item (@$list) {
+	my $exp_tag = $item->{exp_tag};
+	my $exp_id = $item->{exp_id};
+	my $camera = $item->{camera};
+	my $det_id = $item->{det_id};
+	my $det_type = $item->{det_type};
+	my $workdir = $item->{workdir};
+
+	$workdir = $workdir_global unless defined $workdir and $workdir ne "NULL";
+	die "No working directory specified.\n" unless defined $workdir;
+	
+	my $outroot = caturi( $workdir, "$camera.$det_type.$det_id", $exp_tag, "$exp_tag.detproc.$det_id" );
+	$ipprc->outroot_prepare( $outroot );
+
+	my $command = "$detrend_process_exp --det_id $det_id --det_type $det_type --exp_tag $exp_tag --exp_id $exp_id --camera $camera --dbname $dbname --outroot $outroot";
+	$command .= " --verbose" if defined $verbose;
+    	my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	    run( command => $command, verbose => 1 );
+	die "Unable to do raw exposure processing on $det_id $exp_tag: $error_code\n" if not $success;
+    }
+}
+
+# Stack
+{
+    my $command = "$dettool -tostacked -dbname $dbname";
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run( command => $command, verbose => 1 );
+    die "Unable to get stack list: $error_code\n" if not $success;
+    my $list = parse_md_list( $mdcParser->parse( join( '', @$stdout_buf ) ) ) or
+	die "Unable to parse output from dettool.\n";
+
+    foreach my $item (@$list) {
+	my $det_id = $item->{det_id};
+	my $iteration = $item->{iteration};
+	my $class_id = $item->{class_id};
+	my $det_type = $item->{det_type};
+	my $camera = $item->{camera};
+	my $workdir = $item->{workdir};
+	my $reduction = $item->{reduction};
+
+	$workdir = $workdir_global unless defined $workdir and $workdir ne "NULL";
+	die "No working directory specified.\n" unless defined $workdir;
+	
+	my $outroot = caturi( $workdir, "$camera.$det_type.$det_id", "$camera.$det_type.$det_id.$iteration" );
+	$ipprc->outroot_prepare( $outroot );
+
+
+	my $command = "$detrend_stack --det_id $det_id --iteration $iteration --class_id $class_id --det_type $det_type --camera $camera --dbname $dbname --outroot $outroot";
+	$command .= " --reduction $reduction" if defined $reduction;
+	$command .= " --verbose" if defined $verbose;
+    	my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	    run( command => $command, verbose => 1 );
+	die "Unable to stack detrend $det_id $iteration $class_id: $error_code\n" if not $success;
+    }
+}
+
+# Calculate normalisation
+{
+    my $command = "$dettool -tonormalizedstat -dbname $dbname";
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run( command => $command, verbose => 1 );
+    die "Unable to get normalise calculation list: $error_code\n" if not $success;
+    my $list = parse_md_list( $mdcParser->parse( join( '', @$stdout_buf ) ) ) or
+	die "Unable to parse output from dettool.\n";
+
+    foreach my $item (@$list) {
+	my $det_id = $item->{det_id};
+	my $iteration = $item->{iteration};
+	my $det_type = $item->{det_type};
+	my $camera = $item->{camera};
+	my $workdir = $item->{workdir};
+
+	$workdir = $workdir_global unless defined $workdir and $workdir ne "NULL";
+	die "No working directory specified.\n" unless defined $workdir;
+	
+	my $outroot = caturi( $workdir, "$camera.$det_type.$det_id", "$camera.$det_type.normstat.$det_id.$iteration" );
+	$ipprc->outroot_prepare( $outroot );
+
+	my $command = "$detrend_norm_calc --det_id $det_id --iteration $iteration --det_type $det_type --dbname $dbname --outroot $outroot";
+	$command .= " --verbose" if defined $verbose;
+    	my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	    run( command => $command, verbose => 1 );
+	die "Unable to calculate normalisation for $det_id $iteration: $error_code\n" if not $success;
+    }
+}
+
+# Apply normalisation
+{
+    my $command = "$dettool -tonormalize -dbname $dbname";
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run( command => $command, verbose => 1 );
+    die "Unable to get normalisation list: $error_code\n" if not $success;
+    my $list = parse_md_list( $mdcParser->parse( join( '', @$stdout_buf ) ) ) or
+	die "Unable to parse output from dettool.\n";
+
+    foreach my $item (@$list) {
+	my $det_id = $item->{det_id};
+	my $iteration = $item->{iteration};
+	my $det_type = $item->{det_type};
+	my $class_id = $item->{class_id};
+	my $value = $item->{norm};
+	my $uri = $item->{uri};
+	my $camera = $item->{camera};
+	my $workdir = $item->{workdir};
+
+	$workdir = $workdir_global unless defined $workdir and $workdir ne "NULL";
+	die "No working directory specified.\n" unless defined $workdir;
+	
+	my $outroot = caturi( $workdir, "$camera.$det_type.$det_id", "$camera.$det_type.norm.$det_id.$iteration" );
+	$ipprc->outroot_prepare( $outroot );
+
+	my $command = "$detrend_norm_apply --det_id $det_id --iteration $iteration --class_id $class_id --value $value --input_uri $uri --camera $camera --det_type $det_type --dbname $dbname --outroot $outroot";
+	$command .= " --verbose" if defined $verbose;
+    	my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	    run( command => $command, verbose => 1 );
+	die "Unable to apply normalisation for $det_id $iteration $class_id: $error_code\n" if not $success;
+    }
+}
+
+# Examine normalised exposure
+{
+    my $command = "$dettool -tonormalizedexp -dbname $dbname";
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run( command => $command, verbose => 1 );
+    die "Unable to get normalised exposures list: $error_code\n" if not $success;
+    my $list = parse_md_list( $mdcParser->parse( join( '', @$stdout_buf ) ) ) or
+	die "Unable to parse output from dettool.\n";
+
+    foreach my $item (@$list) {
+	my $det_id = $item->{det_id};
+	my $iteration = $item->{iteration};
+	my $det_type = $item->{det_type};
+	my $class_id = $item->{class_id};
+	my $value = $item->{norm};
+	my $uri = $item->{uri};
+	my $camera = $item->{camera};
+	my $workdir = $item->{workdir};
+
+	$workdir = $workdir_global unless defined $workdir and $workdir ne "NULL";
+	die "No working directory specified.\n" unless defined $workdir;
+	
+	my $outroot = caturi( $workdir, "$camera.$det_type.$det_id", "$camera.$det_type.normexp.$det_id.$iteration" );
+	$ipprc->outroot_prepare( $outroot );
+
+	my $command = "$detrend_norm_exp --det_id $det_id --iteration $iteration --camera $camera --det_type $det_type --dbname $dbname --outroot $outroot";
+	$command .= " --verbose" if defined $verbose;
+    	my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	    run( command => $command, verbose => 1 );
+	die "Unable to examine normalised exposure for $det_id $iteration: $error_code\n" if not $success;
+    }
+}
+
+# Get residuals
+{
+    my $command = "$dettool -toresidimfile -dbname $dbname";
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run( command => $command, verbose => 1 );
+    die "Unable to get residual processing list: $error_code\n" if not $success;
+
+    my @whole = split /\n/, join( '', @$stdout_buf );
+    my @single = ();
+
+    while ( scalar @whole > 0 ) {
+	my $value = shift @whole;
+	push @single, $value;
+	if ($value =~ /^\s*END\s*$/) {
+	    push @single, "\n";
+
+	    my $list = parse_md_list( $mdcParser->parse( join( "\n", @single ) ) ) or
+		die "Unable to parse output from dettool.\n";
+	    
+	    foreach my $item (@$list) {
+		my $exp_tag = $item->{exp_tag};
+		my $exp_id = $item->{exp_id};
+		my $camera = $item->{camera};
+		my $det_id = $item->{det_id};
+		my $iteration = $item->{iteration};
+		my $class_id = $item->{class_id};
+		my $det_type = $item->{det_type};
+		my $detrend = $item->{det_uri};
+		my $uri = $item->{uri};
+		my $mode = $item->{mode};
+		my $workdir = $item->{workdir};
+		my $reduction = $item->{reduction};
+
+		$workdir = $workdir_global unless defined $workdir and $workdir ne "NULL";
+		die "No working directory specified.\n" unless defined $workdir;
+		
+		my $outroot = caturi( $workdir, "$camera.$det_type.$det_id", $exp_tag, "$exp_tag.detresid.$det_id.$iteration" );
+		$ipprc->outroot_prepare( $outroot );
+
+		my $command = "$detrend_resid --det_id $det_id --iteration $iteration --exp_tag $exp_tag --exp_id $exp_id --class_id $class_id --det_type $det_type --camera $camera --input_uri $uri --mode $mode --dbname $dbname --outroot $outroot";
+		$command .= " --reduction $reduction" if defined $reduction;
+		$command .= " --detrend $detrend" if defined $detrend;
+		$command .= " --verbose" if defined $verbose;
+
+		my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+		    run( command => $command, verbose => 1 );
+		die "Unable to do residual processing on $exp_tag $class_id: $error_code\n" if not $success;
+	    }
+
+	    @single = ();
+
+	}
+    }
+}
+
+# Reject based on imfiles
+{
+    my $command = "$dettool -toresidexp -dbname $dbname";
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run( command => $command, verbose => 1 );
+    die "Unable to get residual imfile list: $error_code\n" if not $success;
+    my $list = parse_md_list( $mdcParser->parse( join( '', @$stdout_buf ) ) ) or
+	die "Unable to parse output from dettool.\n";
+
+    foreach my $item (@$list) {
+	my $exp_tag = $item->{exp_tag};
+	my $exp_id = $item->{exp_id};
+	my $camera = $item->{camera};
+	my $det_id = $item->{det_id};
+	my $iteration = $item->{iteration};
+	my $det_type = $item->{det_type};
+	my $workdir = $item->{workdir};
+
+	$workdir = $workdir_global unless defined $workdir and $workdir ne "NULL";
+	die "No working directory specified.\n" unless defined $workdir;
+	
+	my $outroot = caturi( $workdir, "$camera.$det_type.$det_id", $exp_tag, "$exp_tag.detresid.$det_id.$iteration" );
+	$ipprc->outroot_prepare( $outroot );
+
+	my $command = "$detrend_reject_imfile --det_id $det_id --iteration $iteration --exp_tag $exp_tag --exp_id $exp_id --det_type $det_type --camera $camera --dbname $dbname --outroot $outroot";
+	$command .= " --verbose" if defined $verbose;
+    	my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	    run( command => $command, verbose => 1 );
+	die "Unable to do imfile rejection on $exp_tag $det_id $iteration: $error_code\n" if not $success;
+    }
+}
+
+# Reject based on exposures
+{
+    my $command = "$dettool -todetrunsummary -dbname $dbname";
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run( command => $command, verbose => 1 );
+    die "Unable to get residual exposure list: $error_code\n" if not $success;
+    my $list = parse_md_list( $mdcParser->parse( join( '', @$stdout_buf ) ) ) or
+	die "Unable to parse output from dettool.\n";
+
+    foreach my $item (@$list) {
+	my $camera = $item->{camera};
+	my $det_id = $item->{det_id};
+	my $iteration = $item->{iteration};
+	my $det_type = $item->{det_type};
+	my $workdir = $item->{workdir};
+
+	$workdir = $workdir_global unless defined $workdir and $workdir ne "NULL";
+	die "No working directory specified.\n" unless defined $workdir;
+	
+	my $outroot = caturi( $workdir, "$camera.$det_type.$det_id", "$camera.$det_type.$det_id.$iteration.detreject" );
+	$ipprc->outroot_prepare( $outroot );
+
+	my $command = "$detrend_reject_exp --det_id $det_id --iteration $iteration --det_type $det_type --camera $camera --dbname $dbname --outroot $outroot";
+	$command .= " --verbose" if defined $verbose;
+    	my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	    run( command => $command, verbose => 1 );
+	die "Unable to do exposure rejection on $det_id $iteration: $error_code\n" if not $success;
+    }
+}
+
+
+__END__
+
+
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_diff.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_diff.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_diff.pl	(revision 22157)
@@ -0,0 +1,96 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::List qw( parse_md_list );
+use PS::IPP::Config 1.01 qw( :standard );
+use File::Basename;
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+my ($dbname,			# Database name to use
+    $verbose,			# Verbose operations?
+    $workdir_global,		# Global working directory
+    $no_op,			# No operations?
+    $no_update,			# No updating?
+    );
+GetOptions(
+	   'dbname=s' => \$dbname,
+	   'verbose' => \$verbose,
+	   'workdir' => \$workdir_global,
+	   'no-op' => \$no_op,
+	   'no-update' => \$no_update,
+) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+
+pod2usage(
+	  -msg => "Required options: --dbname",
+	  -exitval => 3,
+	  ) unless defined $dbname;
+
+my $mdcParser = PS::IPP::Metadata::Config->new;	# Metadata config parser
+
+# Look for programs we need
+my $missing_tools;
+my $difftool = can_run('difftool') or
+    (warn "Can't find difftool" and $missing_tools = 1);
+my $diff_skycell = can_run('diff_skycell.pl') or
+    (warn "Can't find diff_skycell.pl" and $missing_tools = 1);
+
+if ($missing_tools) {
+    warn ("Can't find required tools");
+    exit($PS_EXIT_CONFIG_ERROR); 
+}
+
+
+# Image differencing
+{
+    my $list;
+    my $command = "$difftool -todiffskyfile -dbname $dbname"; # Command to run
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run( command => $command, verbose => 1 );
+    die "Unable to get list of diffs: $error_code\n" if not $success;
+    $list = parse_md_list( $mdcParser->parse( join( '', @$stdout_buf ) ) ) or
+	die "Unable to parse output from difftool.\n";
+
+    foreach my $item (@$list) {
+	my $diff_id = $item->{diff_id};
+	my $skycell_id = $item->{skycell_id};
+	my $tess_id = basename($item->{tess_id});
+	my $workdir = $item->{workdir};
+	
+	$workdir = $workdir_global unless defined $workdir and $workdir ne "NULL";
+	die "No working directory specified.\n" unless defined $workdir;
+	
+	my $outroot = caturi( $workdir, $tess_id, $skycell_id, "$tess_id.$skycell_id.dif.$diff_id" );
+	$ipprc->outroot_prepare( $outroot );
+
+	my $command = "$diff_skycell --diff_id $diff_id --dbname $dbname --outroot $outroot";
+	$command .= " --verbose" if defined $verbose;
+	$command .= " --no-op" if defined $no_op;
+	$command .= " --no-update" if defined $no_update;
+	my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	    run( command => $command, verbose => 1 );
+	die "Unable to do difference for $diff_id: $error_code\n" if not $success;
+    }
+}
+
+
+END {
+    my $status = $?;
+    system("sync") == 0
+        or die "failed to execute sync: $!" ;
+    $? = $status;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_inject.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_inject.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_inject.pl	(revision 22157)
@@ -0,0 +1,142 @@
+#!/usr/bin/env perl
+
+# this program injects a list of single-file exposures into the db,
+# taking the filename (without .fits) as the exp_tag.  the user
+# supplies a temporary telescope and camera name.  these are used
+# for informational purposes only until the registration step can
+# determine the true telescope and camera name from the image
+# headers.
+
+# this program should not fail because of the data format or the
+# configuration, except for the very basic database setup.
+
+use warnings;
+use strict;
+
+use IPC::Cmd 0.36 qw( can_run run );
+use File::Spec;
+use PS::IPP::Config;
+
+my $ipprc = PS::IPP::Config->new(); # this is used for PATH, NEB filename conversions
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+# Parse the command-line arguments
+my ($camera, $telescope, $workdir, $reduction, $dvo_db, $tess_id, $end_stage, $label, $dbname, $no_op, $help);
+GetOptions('camera|i=s'     => \$camera,    # user-supplied camera name
+	   'telescope|t=s'  => \$telescope, # user-supplied telescope name
+	   'workdir|w=s'    => \$workdir,   # working directory for output files
+	   'reduction=s'    => \$reduction, # user-supplied camera name
+	   'dvodb=s'        => \$dvo_db,    # target dvo database 
+	   'tess_id=s'      => \$tess_id,   # tessalation for warping
+	   'end_stage=s'    => \$end_stage, # stop processing at this step
+	   'label=s'        => \$label,     # set chip label
+	   'dbname|d=s'     => \$dbname,    # Database name
+	   'no-op'          => \$no_op,     # pretend but don't actually inject
+	   'help'           => \$help       # give help listing
+) or pod2usage( 2 );
+
+pod2usage( -msg => "inject one or many files into the IPP pipeline database", 
+	   -exitval => 2) if 
+    defined $help;
+
+pod2usage( -msg => "Usage: $0 --telescope (name) --camera (name) [--workdir path] [--reduction class] [--dvodb db] [--tess_id tess] [--end_stage stage] [--label label] [--dbname dbname] (files)", 
+	   -exitval => 2 ) if 
+    scalar @ARGV == 0;
+
+pod2usage( -msg => "Required options: --telescope (name) --camera (name)",
+	   -exitval => 3) unless
+    defined $telescope and
+    defined $camera;
+
+my $pxinject = can_run('pxinject') or die "Can't find pxinject\n";
+
+# if workdir is not defined, assign the current path
+# XXX we need to handle relative paths for workdir (not allowed)
+if (! $workdir) {
+    $workdir = File::Spec->rel2abs( "." );
+}
+
+my $num = 0;
+foreach my $file ( @ARGV ) {
+    # check for file existence
+    if (! -e $file) { die "file $file not found\n"; }
+    my $absfile = File::Spec->rel2abs( $file );
+    inject($absfile, $workdir, $dbname, $telescope, $camera);
+    $num ++;
+}
+
+print "$num files injected.\n";
+
+sub inject
+{
+    my $absfile = shift;	# absolute path for this file
+    my $workdir  = shift;	# absolute path for output directory
+    my $dbname = shift;		# IPP database to use
+    my $telescope = shift;	# user-specified telescope
+    my $camera = shift;	# user-specified camera
+
+    # XXX provide an option for an alternative extension
+    my ( $vol, $path, $name ) = File::Spec->splitpath( $absfile );
+    my ( $exp_name ) = $name =~ /(.*)\.(fits|fit|fts)(|.gz)/;
+
+    my $relfile = $ipprc->convert_filename_relative( $absfile );
+
+    # the telescope, instrument, and exp_name used here are temporary : register replaces them with the true values
+    my $command_exp = "$pxinject -newExp";
+    $command_exp .= " -tmp_exp_name $exp_name";
+    $command_exp .= " -tmp_inst $camera";
+    $command_exp .= " -tmp_telescope $telescope";
+    $command_exp .= " -workdir $workdir";
+    $command_exp .= " -reduction $reduction" if defined $reduction;
+    $command_exp .= " -dvodb $dvo_db"       if defined $dvo_db;
+    $command_exp .= " -tess_id $tess_id"     if defined $tess_id;
+    $command_exp .= " -end_stage $end_stage" if defined $end_stage;
+    $command_exp .= " -label $label"         if defined $label;
+    $command_exp .= " -dbname $dbname"       if defined $dbname;
+
+    my $exp_id = 0;
+    unless ($no_op) {
+	my ( $success_exp, $error_code_exp, $full_buf_exp, $stdout_buf_exp, $stderr_buf_exp ) =
+	    run( command => $command_exp, verbose => 1 );
+	die "Unable to inject $exp_name: $error_code_exp\n" if not $success_exp;
+	
+	my @line = split(/\s+/, $$stdout_buf_exp[0]); # The output line, containing the exposure tag
+	$exp_id = $line[2];	# The exposure tag
+    } else {
+	print "skipping command: $command_exp\n";
+    }
+    
+    # the class_id used here is temporary : register replaces it with the true class_id
+    my $command_imfile = "$pxinject -newImfile";
+    $command_imfile .= " -exp_id $exp_id";
+    $command_imfile .= " -tmp_class_id fpa";
+    $command_imfile .= " -uri $relfile";
+    $command_imfile .= " -dbname $dbname" if defined ($dbname);
+    
+    unless ($no_op) {
+	my ( $success_imfile, $error_code_imfile, $full_buf_imfile, $stdout_buf_imfile, $stderr_buf_imfile ) = run( command => $command_imfile, verbose => 1 );
+	die "Unable to inject $exp_name imfile: $error_code_imfile\n" if not $success_imfile;
+    } else {
+	print "skipping command: $command_imfile\n";
+    }
+
+    # the class_id used here is temporary : register replaces it with the true class_id
+    my $command_update = "$pxinject -updatenewExp";
+    $command_update .= " -exp_id $exp_id";
+    $command_update .= " -state run";
+    $command_update .= " -dbname $dbname" if defined ($dbname);
+    
+    unless ($no_op) {
+	my ( $success_update, $error_code_update, $full_buf_update, $stdout_buf_update, $stderr_buf_update ) = run( command => $command_update, verbose => 1 );
+	die "Unable to update $exp_name: $error_code_update\n" if not $success_update;
+    } else {
+	print "skipping command: $command_update\n";
+    }
+
+    return 1;
+}
+
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_inject_mosaic.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_inject_mosaic.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_inject_mosaic.pl	(revision 22157)
@@ -0,0 +1,168 @@
+#!/usr/bin/env perl
+
+# this program injects a set of multi-file exposures into the db.  the
+# program takes a list of base exposure names and injects all files
+# associated with the exposure.  It constructs the expected filenames
+# from the exposure tag and rules for the camera.  This program is for
+# the test only since it requires too much information at the inject
+# stage.  use 'ipp_serial_inject.pl' for single-file images and
+# 'ipp_serial_inject_split.pl' for multiple file images in split
+# format
+
+# this program should not fail because of the data format or the
+# configuration, except for the very basic database setup.
+
+use warnings;
+use strict;
+
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Config qw( caturi );
+use Data::Dumper;
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+my ($camera,                    # Camera used
+    $telescope,                 # Telescope used
+    $tess_id,                   # Tessellation identifier
+    $dbname,                    # Database name
+    $workdir,                   # Working directory
+    $path,                      # Path to data
+    );
+GetOptions(
+           'camera|c=s'    => \$camera,
+           'telescope|t=s' => \$telescope,
+           'workdir=s'     => \$workdir,
+           'tess_id=s'     => \$tess_id,
+           'path=s'        => \$path,
+           'dbname=s'      => \$dbname,
+) or pod2usage( 2 );
+
+pod2usage(
+          -msg => "Required options: --camera --telescope --workdir --path --dbname",
+          -exitval => 3,
+          ) unless defined $camera
+    and defined $telescope
+    and defined $workdir
+    and defined $path
+    and defined $dbname;
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+# Look for programs we need
+my $missing_tools;
+my $pxinject = can_run('pxinject')  or (warn "Can't find pxinject" and $missing_tools = 1);
+
+if (scalar @ARGV == 0) {
+    die "No exposures provided.\n";
+}
+
+# Inject new data into the database
+my @classes;                    # Names of the classes
+my @files;                      # What to add to the filename for each class
+my $imfiles;
+my $add_dir;                    # Add directory name to get file name?
+if ($camera eq "MEGACAM") {
+    for (my $i = 0; $i < 36; $i++) {
+        push @classes, sprintf("ccd%02d", $i);
+        push @files, sprintf(".ccd%02d", $i);
+    }
+} elsif ($camera eq "MCSHORT") {
+    @classes = ( 'ccd12', 'ccd13', 'ccd14', 'ccd21', 'ccd22', 'ccd23' );
+    @files   = ( '.ccd12', '.ccd13', '.ccd14', '.ccd21', '.ccd22', '.ccd23' );
+} elsif ($camera eq "CTIO_MOSAIC2") {
+    @classes = ();
+    @files = ();
+} elsif ($camera eq "TC3") {
+    @classes = ( 'CCID58-1-06b2', 'CCID45-1-14A', 'CCID45-1-11A', 'CCID45-1-22A',
+                 'CCID45-1-04C', 'CCID45-1-13A', 'CCID45-1-05A', 'CCID45-1-19A' );
+    @files = ( '00', '01', '10', '11', '20', '21', '30', '31' );
+    $add_dir = 1;
+} elsif ($camera eq "SIMMOSAIC") {
+    @classes = ( 'Chip00', 'Chip01', 'Chip10', 'Chip11' );
+    @files   = ( '.Chip00', '.Chip01', '.Chip10', '.Chip11' );
+} elsif ($camera eq "SIMTEST") {
+    @classes = ();
+    @files = ();
+} elsif ($camera eq "GPC1") {
+    for (my $i = 0; $i < 8; $i++) {
+        for (my $j = 0; $j < 8; $j++) {
+            if (($i == 0 or $i == 7) and ($j == 0 or $j == 7)) {
+                # Excluding corner chips
+                next;
+            }
+            push @classes, "XY$i$j";
+            push @files, "$i$j";
+        }
+    }
+    $add_dir = 1;
+} else {
+    die "Unrecognised camera name: $camera.\nDid you mean to use ipp_serial_inject.pl?\n";
+}
+
+foreach my $exp_name ( @ARGV ) {
+    my $command = "$pxinject -newExp -tmp_exp_name $exp_name -tmp_inst $camera -tmp_telescope $telescope -workdir $workdir -tess_id $tess_id"; # Command to run
+    $command .= " -dbname $dbname" if defined $dbname;
+
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run( command => $command, verbose => 1 );
+    die "Unable to inject $exp_name: $error_code\n" if not $success;
+
+    my @line = split(/\s+/, $$stdout_buf[0]); # The output line, containing the exposure tag
+    my $exp_id = $line[2];      # The exposure tag
+    for (my $i = 0; $i < scalar @classes; $i++) {
+        my $class_id = $classes[$i];
+        my $file_id = $files[$i];
+        my $filename = $exp_name . $file_id . '.fits';
+        $filename = caturi( $exp_name, $filename ) if defined $add_dir;
+        $filename = caturi( $path, $filename );
+
+        die "Unable to find file $filename" unless -f $ipprc->file_resolve( $filename );
+
+        $filename = $ipprc->convert_filename_relative( $filename );
+        my $command = "$pxinject -newImfile -exp_id $exp_id -tmp_class_id $class_id -uri $filename"; # Command to run
+        $command .= " -dbname $dbname" if defined ($dbname);
+
+        my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+            run( command => $command, verbose => 1 );
+        die "Unable to inject $exp_name $class_id: $error_code\n" if not $success;
+    }
+
+    if (scalar @classes == 0) {
+        my $filename = $exp_name . '.fits';
+        $filename = caturi( $exp_name, $filename ) if defined $add_dir;
+        $filename = caturi( $path, $filename );
+
+        die "Unable to find file $filename" unless -f $ipprc->file_resolve( $filename );
+
+        $filename = $ipprc->convert_filename_relative( $filename );
+        my $command = "$pxinject -newImfile -exp_id $exp_id -tmp_class_id fpa -uri $filename"; # Command to run
+        $command .= " -dbname $dbname" if defined ($dbname);
+        my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+            run( command => $command, verbose => 1 );
+        die "Unable to inject $exp_name imfile: $error_code\n" if not $success;
+    }
+
+    # Update the exposure to run
+    {
+        my $command = "$pxinject -updatenewExp -exp_id $exp_id -state run"; # Command to run
+        $command .= " -dbname $dbname" if defined ($dbname);
+
+        my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+            run( command => $command, verbose => 1 );
+        die "Unable to activate $exp_name: $error_code\n" if not $success;
+    }
+
+}
+
+END {
+    my $status = $?;
+system("sync") == 0
+    or die "failed to execute sync: $!" ;
+$? = $status;
+}
+
+
+__END__
+
+
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_inject_split.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_inject_split.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_inject_split.pl	(revision 22157)
@@ -0,0 +1,138 @@
+#!/usr/bin/env perl
+
+# this program injects a list of multi-file exposures into the db.
+# the program takes a list of directory names (dir), and injects all
+# dir/*.fits.  It takes the directory name as the exp_tag.  the user
+# supplies a temporary telescope and instrument name.  these are used
+# for informational purposes only until the registration step can
+# determine the true telescope and instrument name from the image
+# headers.
+
+# this program should not fail because of the data format or the
+# configuration, except for the very basic database setup.
+
+use warnings;
+use strict;
+
+use IPC::Cmd 0.36 qw( can_run run );
+use File::Spec;
+use PS::IPP::Config;
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+# Parse the command-line arguments
+my ($camera, $telescope, $workdir, $reduction, $dvo_db, $tess_id, $end_stage, $dbname, $help);
+GetOptions('camera|c=s'    => \$camera,    # Camera used	      
+           'telescope|t=s' => \$telescope, # Telescope used      
+           'workdir|w=s'   => \$workdir,   # working directory for output files
+	   'reduction=s'   => \$reduction, # user-supplied camera name
+	   'dvodb=s'       => \$dvo_db,    # target dvo database 
+	   'tess_id=s'     => \$tess_id,   # tessalation for warping
+	   'end_stage=s'   => \$end_stage, # stop processing at this step
+    	   'dbname|d=s'    => \$dbname, # Database name
+	   'help'          => \$help # give help listing
+) or pod2usage( 2 );
+
+pod2usage( -msg => "inject one or many image exposures (directories) into the IPP pipeline database", 
+	   -exitval => 2) if 
+    defined $help;
+
+pod2usage( -msg => "Usage: $0 --telescope (name) --camera (name) [--workdir path] [--reduction class] [--dvodb db] [--tess_id tess] [--end_stage stage] [--dbname name] (files)", 
+	   -exitval => 2 ) if 
+    scalar @ARGV == 0;
+
+pod2usage(
+	  -msg => "Required options: --camera --telescope",
+	  -exitval => 3) unless 
+    defined $telescope and
+    defined $camera;
+
+# Look for programs we need
+my $pxinject = can_run('pxinject') or die "Can't find pxinject\n";
+
+# if workdir is not defined, assign the current path
+# we need to handle relative paths for workdir (not allowed)
+if (! $workdir) {
+    $workdir = File::Spec->rel2abs( "." );
+}
+
+my $num = 0;
+foreach my $filedir ( @ARGV ) {
+    # check for filedir existence
+    if (! -e $filedir) { die "file dir $filedir not found\n"; }
+    my $absfiledir = File::Spec->rel2abs( $filedir );
+    inject($absfiledir, $workdir, $dbname, $telescope, $camera);
+    $num ++;
+}
+
+sub inject
+{
+    my $absfiledir = shift;	# absolute path for this file
+    my $workdir  = shift;	# absolute path for output directory
+    my $dbname = shift;		# IPP database to use
+    my $telescope = shift;	# user-specified telescope
+    my $camera = shift;	# user-specified camera
+
+    # XXX provide an option for an alternative extension
+    my @files = <$absfiledir/*.fits>;
+    my $Nfiles = scalar @files;
+
+    print "absfiledir: $absfiledir\n";
+    print "Nfiles: $Nfiles\n";
+
+    # the absfiledir is of the form path://PATH/data/foo/expname
+    my ( $vol, $path, $exp_name ) = File::Spec->splitpath( $absfiledir );
+
+    # the telescope, instrument, and exp_name used here are temporary : register replaces them with the true values
+    my $command_exp = "$pxinject -newExp";
+    $command_exp .= " -tmp_exp_name $exp_name";
+    $command_exp .= " -tmp_inst $camera";
+    $command_exp .= " -tmp_telescope $telescope";
+    $command_exp .= " -workdir $workdir";
+    $command_exp .= " -reduction $reduction" if defined $reduction;
+    $command_exp .= " -dvodb $dvo_db"       if defined $dvo_db;
+    $command_exp .= " -tess_id $tess_id"     if defined $tess_id;
+    $command_exp .= " -end_stage $end_stage" if defined $end_stage;
+    $command_exp .= " -dbname $dbname"       if defined $dbname;
+
+    my ( $success_exp, $error_code_exp, $full_buf_exp, $stdout_buf_exp, $stderr_buf_exp ) =
+	run( command => $command_exp, verbose => 1 );
+    die "Unable to inject $exp_name: $error_code_exp\n" if not $success_exp;
+    
+    my @line = split(/\s+/, $$stdout_buf_exp[0]); # The output line, containing the exposure tag
+    my $exp_id = $line[2];	# The exposure tag
+
+    foreach my $absfile (@files) {
+
+	my $relfile = $ipprc->convert_filename_relative( $absfile );
+
+	my ( $tmpvol, $tmppath, $filename ) = File::Spec->splitpath( $absfile );
+	my ( $class_name ) = $filename =~ /(.*)\.fits/;
+
+	# the class_id used here is temporary : register replaces it with the true class_id
+	my $command_imfile = "$pxinject -newImfile";
+	$command_imfile .= " -exp_id $exp_id";
+	$command_imfile .= " -tmp_class_id $class_name";
+	$command_imfile .= " -uri $relfile";
+	$command_imfile .= " -dbname $dbname" if defined ($dbname);
+	
+	my ( $success_imfile, $error_code_imfile, $full_buf_imfile, $stdout_buf_imfile, $stderr_buf_imfile ) = run( command => $command_imfile, verbose => 1 );
+	die "Unable to inject $exp_name imfile: $error_code_imfile\n" if not $success_imfile;
+    }
+
+    # the class_id used here is temporary : register replaces it with the true class_id
+    my $command_update = "$pxinject -updatenewExp";
+    $command_update .= " -exp_id $exp_id";
+    $command_update .= " -state run";
+    $command_update .= " -dbname $dbname" if defined ($dbname);
+    
+    my ( $success_update, $error_code_update, $full_buf_update, $stdout_buf_update, $stderr_buf_update ) = run( command => $command_update, verbose => 1 );
+    die "Unable to update $exp_name: $error_code_update\n" if not $success_update;
+
+    return 1;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_mops.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_mops.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_mops.pl	(revision 22157)
@@ -0,0 +1,121 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+use DBI;
+
+use IPC::Cmd 0.36 qw( can_run run );
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+use Data::Dumper;
+use File::Temp qw( tempfile );
+
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::List qw( parse_md_list );
+use PS::IPP::Config qw( caturi );
+
+# Look for programs we need
+my $missing_tools;
+my $mops = can_run('ipp_mops_translate.pl') or (warn "Can't find ipp_mops_translate.pl" and $missing_tools = 1);
+my $dsreg = can_run('dsreg') or (warn "Can't find dsreg" and $missing_tools = 1);
+die "Can't find required tools.\n" if $missing_tools;
+
+my $ipprc = PS::IPP::Config->new; # IPP Configuration
+
+my ( $dbhost,                   # Database host
+     $dbname,                   # Database name
+     $dbuser,                   # Database user
+     $dbpass,                   # Database p/w
+     $camera,                   # Camera used
+     $outroot,                  # Output directory
+     $fileset,                  # File set
+     $verbose,                  # Verbose output?
+     $no_update,                # Don't update state?
+     $no_op,                    # Don't do any operations?
+     $save_temps                # Save temporary files?
+     );
+
+GetOptions(
+           'dbhost=s'   => \$dbhost,
+           'dbname=s'   => \$dbname,
+           'dbuser=s'   => \$dbuser,
+           'dbpass=s'   => \$dbpass,
+           'camera=s'   => \$camera,
+           'outroot=s'  => \$outroot,
+           'fileset=s'  => \$fileset,
+           'verbose'    => \$verbose,
+           'no-update'  => \$no_update, # Don't update the database?
+           'no-op'      => \$no_op, # Don't do any operations?
+           'save-temps' => \$save_temps, # Save temporary files?
+) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options: --dbhost --dbname --dbuser --dbpass --camera --outroot --fileset",
+           -exitval => 3)
+    unless defined $dbhost
+    and defined $dbname
+    and defined $dbuser
+    and defined $dbpass
+    and defined $camera
+    and defined $outroot
+    and defined $fileset;
+
+$ipprc->define_camera($camera);
+
+my $dbsrc = 'DBI:mysql:database=' . $dbname . ';host=' . $dbhost .
+    ';mysql_socket=/var/run/mysqld/mysqld.sock';
+my $db = DBI->connect($dbsrc, $dbuser, $dbpass, { RaiseError => 1, AutoCommit => 1 } ) or
+    die "Unable to connect to database: $DBI::errstr";
+
+my $sql = "SELECT exp_id, diff_id, skycell_id, warp_id, diffSkyfile.path_base AS diff_path_base, warpSkyfile.path_base AS warp_path_base FROM diffSkyfile JOIN diffRun using(diff_id) JOIN diffInputSkyfile USING(diff_id,tess_id,skycell_id) JOIN warpRun USING(warp_id,tess_id) JOIN warpSkyfile USING(warp_id,skycell_id,tess_id) JOIN fakeRun using(fake_id) JOIN camRun USING(cam_id) JOIN chipRun USING(chip_id) JOIN rawExp USING(exp_id) WHERE diffSkyfile.fault = 0 AND rawExp.camera = \'$camera\';";
+
+my $rows = $db->selectall_arrayref( $sql, { Slice => {} } ) or die "Unable to execute SQL: $DBI::errstr";
+$db->disconnect;
+
+print "Selected " . scalar @$rows . " rows.\n";
+
+$ipprc->outroot_prepare( $outroot );
+my $outrootResolved = $ipprc->file_resolve( $outroot );
+my ($dsFile, $dsName) = tempfile( "$outrootResolved.dslist.XXXX", UNLINK => !$save_temps);
+
+foreach my $row ( @$rows ) {
+    my $exp_id = $row->{exp_id};
+    my $diff_id = $row->{diff_id};
+    my $skycell_id = $row->{skycell_id};
+    my $warp_id = $row->{warp_id};
+    my $diff_base = $row->{diff_path_base};
+    my $warp_base = $row->{warp_path_base};
+
+    my $input = $ipprc->filename("PSPHOT.OUTPUT", $diff_base);
+    die "Can't find $input\n" unless $ipprc->file_exists($input);
+    $input = $ipprc->file_resolve($input);
+
+    my $skycell = $ipprc->filename("SKYCELL.TEMPLATE", $warp_base);
+    die "Can't find $skycell\n" unless $ipprc->file_exists($skycell);
+    $skycell = $ipprc->file_resolve($skycell);
+
+    my $output = caturi( $outroot, "mops.$exp_id.$skycell_id.$diff_id.fits" );
+    $output = $ipprc->file_resolve($output);
+
+    unless ($no_op) {
+        $ipprc->file_prepare($output);
+        my $command = "$mops --input $input --extname SkyChip.psf --skycell $skycell --output $output";
+        my $success = run( command => $command, verbose => $verbose );
+        die "Couldn't translate $input\n" unless $success;
+    }
+
+    print $dsFile "${output}|ipp-mops\n";
+}
+close $dsFile;
+
+# Register new files with the data store
+unless ($no_update) {
+    my $command = "$dsreg --add --product mops_transient_detections --type MOPS_TRANSIENT_DETECTIONS --fileset $fileset --list $dsName --copy --abspath --dbname DataStore";
+    my $success = run( command => $command, verbose => $verbose );
+    die "Couldn't register files with data store.\n" unless $success;
+}
+
+
+__END__
+
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_register.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_register.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_register.pl	(revision 22157)
@@ -0,0 +1,118 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::List qw( parse_md_list );
+use Data::Dumper;
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+my ($dbname,			# Database name to use
+    );
+GetOptions(
+	   'dbname|d=s'  => \$dbname,
+) or pod2usage( 2 );
+
+pod2usage(
+	  -msg => "Required options: --dbname",
+	  -exitval => 3,
+	  ) unless defined $dbname;
+
+my $mdcParser = PS::IPP::Metadata::Config->new;	# Metadata config parser
+
+# Look for programs we need
+my $missing_tools;
+my $regtool = can_run('regtool') or (warn "Can't find regtool" and $missing_tools = 1);
+my $register_imfile = can_run('register_imfile.pl') or (warn "Can't find register_imfile.pl" and $missing_tools = 1);
+my $register_exp = can_run('register_exp.pl') or (warn "Can't find register_exp.pl" and $missing_tools = 1);
+die "Can't find required tools.\n" if $missing_tools;
+
+# Phase 0 imfile processing
+{
+    my $command = "$regtool -pendingimfile -dbname $dbname"; # Command to run
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run( command => $command, verbose => 1 );
+    die "Unable to get phase 0 imfile list: $error_code\n" if not $success;
+
+    my @whole = split /\n/, join( '', @$stdout_buf );
+    my @single = ();
+
+    while ( scalar @whole > 0 ) {
+	my $value = shift @whole;
+	push @single, $value;
+	if ($value =~ /^\s*END\s*$/) {
+	    push @single, "\n";
+
+	    my $list = parse_md_list( $mdcParser->parse( join( "\n", @single ) ) ) or
+		die "Unable to parse output from regtool.\n";
+
+	    foreach my $item (@$list) {
+		my $exp_id = $item->{exp_id};
+		my $exp_name = $item->{tmp_exp_name};
+		my $class_id = $item->{tmp_class_id};
+		my $uri = $item->{uri};
+		
+		my $command = "$register_imfile --exp_id $exp_id --tmp_class_id $class_id --tmp_exp_name $exp_name --uri $uri --dbname $dbname";
+		my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+		    run( command => $command, verbose => 1 );
+		die "Unable to do phase 0 imfile processing on $exp_name $class_id: $error_code\n" if not $success;
+	    }
+
+
+	    @single = ();
+
+	}	
+    }
+}
+
+# Phase 0 exposure processing
+{
+    my $command = "$regtool -pendingexp -dbname $dbname"; # Command to run
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run( command => $command, verbose => 1 );
+    die "Unable to get phase 0 exposure list: $error_code\n" if not $success;
+
+    my @whole = split /\n/, join( '', @$stdout_buf );
+    my @single = ();
+
+    while ( scalar @whole > 0 ) {
+	my $value = shift @whole;
+	push @single, $value;
+	if ($value =~ /^\s*END\s*$/) {
+	    push @single, "\n";
+
+	    my $list = parse_md_list( $mdcParser->parse( join( "\n", @single ) ) ) or
+		die "Unable to parse output from regtool.\n";
+
+	    foreach my $item (@$list) {
+		my $exp_tag = $item->{tmp_exp_name} . '.' . $item->{exp_id};
+		my $exp_id = $item->{exp_id};
+		
+		my $command = "$register_exp --exp_tag $exp_tag --exp_id $exp_id --dbname $dbname";
+		my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+		    run( command => $command, verbose => 1 );
+		die "Unable to do phase 0 exposure processing on $exp_tag: $error_code\n" if not $success;
+	    }
+
+	    @single = ();
+
+	}	
+    }
+}
+
+
+END {
+    my $status = $?;
+    system("sync") == 0
+        or die "failed to execute sync: $!" ;
+    $? = $status;
+}
+
+
+__END__
+
+
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_stack.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_stack.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_stack.pl	(revision 22157)
@@ -0,0 +1,99 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::List qw( parse_md_list );
+use PS::IPP::Config 1.01 qw( :standard );
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+use File::Basename;
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+my ($dbname,			# Database name to use
+    $verbose,			# Verbose operations?
+    $workdir_global,		# Global working directory
+    $no_op,			# No operations?
+    $no_update,			# No updating?
+    $save_temps,		# Save temporary files?
+    );
+GetOptions(
+	   'dbname=s' => \$dbname,
+	   'verbose' => \$verbose,
+	   'workdir' => \$workdir_global,
+	   'no-op' => \$no_op,
+	   'no-update' => \$no_update,
+	   'save-temps' => \$save_temps,
+) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+
+pod2usage(
+	  -msg => "Required options: --dbname",
+	  -exitval => 3,
+	  ) unless defined $dbname;
+
+my $mdcParser = PS::IPP::Metadata::Config->new;	# Metadata config parser
+
+# Look for programs we need
+my $missing_tools;
+my $stacktool = can_run('stacktool') or
+    (warn "Can't find difftool" and $missing_tools = 1);
+my $stack_skycell = can_run('stack_skycell.pl') or
+    (warn "Can't find stack_skycell.pl" and $missing_tools = 1);
+
+if ($missing_tools) {
+    warn ("Can't find required tools");
+    exit($PS_EXIT_CONFIG_ERROR); 
+}
+
+
+# Image stacking
+{
+    my $list;
+    my $command = "$stacktool -tosum -dbname $dbname"; # Command to run
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run( command => $command, verbose => 1 );
+    die "Unable to get list of stacks: $error_code\n" if not $success;
+    $list = parse_md_list( $mdcParser->parse( join( '', @$stdout_buf ) ) ) or
+	die "Unable to parse output from stacktool.\n";
+
+    foreach my $item (@$list) {
+	my $stack_id = $item->{stack_id};
+	my $workdir = $item->{workdir};
+	my $tess_id = basename($item->{tess_id});
+	my $skycell_id = $item->{skycell_id};
+	
+	$workdir = $workdir_global unless defined $workdir and $workdir ne "NULL";
+	die "No working directory specified.\n" unless defined $workdir;
+	
+	my $outroot = caturi( $workdir, $tess_id, $skycell_id, "$tess_id.$skycell_id.stk.$stack_id" );
+	$ipprc->outroot_prepare( $outroot );
+
+	my $command = "$stack_skycell --stack_id $stack_id --dbname $dbname --outroot $outroot";
+	$command .= " --verbose" if defined $verbose;
+	$command .= " --no-op" if defined $no_op;
+	$command .= " --no-update" if defined $no_update;
+	$command .= " --save-temps" if defined $save_temps;
+	my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	    run( command => $command, verbose => 1 );
+###	die "Unable to do stack for $stack_id: $error_code\n" if not $success;
+    }
+}
+
+
+END {
+    my $status = $?;
+    system("sync") == 0
+        or die "failed to execute sync: $!" ;
+    $? = $status;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_warp.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_warp.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_serial_warp.pl	(revision 22157)
@@ -0,0 +1,129 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::List qw( parse_md_list );
+use PS::IPP::Config 1.01 qw( :standard );
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+use File::Basename;
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+my ($dbname,			# Database name to use
+    $verbose,			# Verbose operations?
+    $workdir_global,		# Global working directory
+    $no_op,			# No operations?
+    $no_update,			# No updating?
+    );
+GetOptions(
+	   'dbname=s' => \$dbname,
+	   'verbose' => \$verbose,
+	   'workdir' => \$workdir_global,
+	   'no-op' => \$no_op,
+	   'no-update' => \$no_update,
+) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+
+pod2usage(
+	  -msg => "Required options: --dbname",
+	  -exitval => 3,
+	  ) unless defined $dbname;
+
+my $mdcParser = PS::IPP::Metadata::Config->new;	# Metadata config parser
+
+# Look for programs we need
+my $missing_tools;
+my $warptool = can_run('warptool') or
+    (warn "Can't find warptool" and $missing_tools = 1);
+my $warp_skycell = can_run('warp_skycell.pl') or
+    (warn "Can't find warp_skycell.pl" and $missing_tools = 1);
+my $warp_overlap = can_run('warp_overlap.pl') or
+    (warn "Can't find warp_overlap.pl" and $missing_tools = 1);
+
+if ($missing_tools) {
+    warn ("Can't find required tools");
+    exit($PS_EXIT_CONFIG_ERROR); 
+}
+
+
+# Calculate overlaps
+{
+    my $list;
+    my $command = "$warptool -tooverlap -dbname $dbname"; # Command to run
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run( command => $command, verbose => 1 );
+    die "Unable to get warps for which to calculate overlaps: $error_code\n" if not $success;
+    $list = parse_md_list( $mdcParser->parse( join( '', @$stdout_buf ) ) ) or
+	die "Unable to parse output from warptool.\n";
+
+    foreach my $item (@$list) {
+	my $warp_id = $item->{warp_id};
+	my $cam_id = $item->{cam_id};
+	my $workdir = $item->{workdir};
+	my $camera = $item->{camera};
+	my $tess_id = $item->{tess_id};
+	
+	my $command = "$warp_overlap --warp_id $warp_id --camera $camera --tess_id $tess_id --dbname $dbname";
+	$command .= " --verbose" if defined $verbose;
+	$command .= " --no-op" if defined $no_op;
+	$command .= " --no-update" if defined $no_update;
+	my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	    run( command => $command, verbose => 1 );
+	die "Unable to get warp overlaps on $warp_id: $error_code\n" if not $success;
+    }
+}
+
+
+# Warping proper
+{
+    my $list;
+    my $command = "$warptool -towarped -dbname $dbname"; # Command to run
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run( command => $command, verbose => 1 );
+    die "Unable to get warps for warping: $error_code\n" if not $success;
+    $list = parse_md_list( $mdcParser->parse( join( '', @$stdout_buf ) ) ) or
+	die "Unable to parse output from warptool.\n";
+
+    foreach my $item (@$list) {
+	my $warp_id = $item->{warp_id};
+	my $skycell_id = $item->{skycell_id};
+	my $tess_id = basename($item->{tess_id});
+	my $cam_id = $item->{cam_id};
+	my $workdir = $item->{workdir};
+	my $camera = $item->{camera};
+	
+	$workdir = $workdir_global unless defined $workdir and $workdir ne "NULL";
+	die "No working directory specified.\n" unless defined $workdir;
+	
+	my $outroot = caturi( $workdir, $tess_id, $skycell_id, "$tess_id.$skycell_id.wrp.$warp_id" );
+	$ipprc->outroot_prepare( $outroot );
+
+	my $command = "$warp_skycell --warp_id $warp_id --skycell_id $skycell_id --tess_id $tess_id --camera $camera --dbname $dbname --outroot $outroot";
+	$command .= " --verbose" if defined $verbose;
+	$command .= " --no-op" if defined $no_op;
+	$command .= " --no-update" if defined $no_update;
+	my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	    run( command => $command, verbose => 1 );
+	die "Unable to do warp processing on $warp_id,$skycell_id: $error_code\n" if not $success;
+    }
+}
+
+
+
+END {
+    my $status = $?;
+    system("sync") == 0
+        or die "failed to execute sync: $!" ;
+    $? = $status;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_simulation_data.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_simulation_data.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipp_simulation_data.pl	(revision 22157)
@@ -0,0 +1,215 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use IPC::Cmd 0.36 qw( can_run run );
+use Math::Trig;
+use File::Spec;
+use PS::IPP::Config 1.01 qw( :standard );
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+my ($name,                      # Base name for output images
+    $camera,                    # Name of camera to use
+    $telescope,                 # Telescope name
+    $tess_id,                   # Tessellation identifier
+    $dbname,                    # Database name
+    $path,                      # Path to data
+    $workdir,                   # Working directory for data
+    $no_cal,                    # Don't produce calibration files
+    $no_update                  # Don't update the database
+    );
+
+GetOptions(
+           'name=s'        => \$name,
+           'camera=s'      => \$camera,
+           'telescope=s'   => \$telescope,
+           'tess_id=s'     => \$tess_id,
+           'dbname=s'      => \$dbname,
+           'path=s'        => \$path,
+           'workdir=s'     => \$workdir,
+           'no-cal'        => \$no_cal,
+           'no-update'     => \$no_update,
+           ) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+
+pod2usage(
+          -msg => "Required options: --name --path --camera --telescope --dbname",
+            -exitval => 3,
+          ) unless
+    defined $name and
+    defined $path and
+    defined $camera and
+    defined $telescope and
+    defined $dbname;
+
+$workdir = $path if not defined $workdir;
+
+# Look for programs we need
+my $missing_tools;
+my $ppSim = can_run('ppSim')
+    or (warn "Can't find ppSim" and $missing_tools = 1);
+my $inject = can_run('ipp_serial_inject_mosaic.pl')
+    or (warn "Can't find ipp_serial_inject_mosaic.pl" and $missing_tools = 1);
+
+if ($missing_tools) {
+    warn ("Can't find required tools");
+    exit($PS_EXIT_CONFIG_ERROR);
+}
+
+# Number of bias images
+use constant BIAS => 20;
+# Dark exposure times
+use constant DARK => [ 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
+                       300, 300, 300, 300, 300, 300, 300, 300, 300, 300 ];
+# Flat-field image characteristics
+use constant FLAT => [
+                      {
+                          filter => 'r',
+                          exptime => [ 0.1, 0.1, 0.1, 0.1, 0.1, 0.5, 1, 2, 5, 10, 20, 20, 20, 20, 20 ],
+                      },
+                      {
+                          filter => 'i',
+                          exptime => [ 20, 20, 20, 20, 20 ],
+                      }
+                      ];
+# Object image characteristics
+use constant OBJECT => [
+                        {
+                            filter => 'r',
+                            exptime => [ 5, 10, 10, 10, 10, 10, 240 ],
+                            seeing => [ 0.8, 0.4, 0.6, 0.8, 1.2, 1.5, 0.7 ],
+                            ra => 150.119167,
+                            dec => 2.205833,
+                            pa => 0,
+                            zp => 25.15,
+                            sky => 20.86,
+                            dither => 40,
+                        },
+                        {
+                            filter => 'i',
+                            exptime => [ 5, 30, 30, 30, 30, 30, 240 ],
+                            seeing => [ 0.8, 0.4, 0.6, 0.8, 1.2, 1.5, 0.7 ],
+                            ra => 150.119167,
+                            dec => 2.205833,
+                            pa => 0,
+                            zp => 25.00,
+                            sky => 20.15,
+                            dither => 40,
+                        },
+                        ];
+use constant SCALE => 0.2;      # Plate scale
+
+#############################################################################################################
+### Now do the work
+#############################################################################################################
+
+my $counter = 0;
+my $tessellation = (defined $tess_id) ? "--tess_id $tess_id" : ""; # Additional switch for tessellation
+
+# Generate bias images
+for (my $i = 0; $i < BIAS; $i++) {
+    my $basename;               # Output base filename
+    ( $basename, $counter ) = filename( $name, $counter );
+    my $filename = caturi( $path, $basename );
+    unless ($no_cal) {
+        run( command => "$ppSim -camera $camera -type BIAS $filename",
+             verbose => 1 ) or die "Unable to run ppSim";
+        unless ($no_update) {
+            run( command => "$inject --camera $camera --telescope $telescope --path $path " .
+                 "$tessellation --workdir $workdir --dbname $dbname $basename",
+                 verbose => 1 ) or die "Unable to inject file.";
+        }
+    }
+}
+
+# Generate dark images
+foreach my $exptime ( @{DARK()} ) {
+    my $basename;               # Output base filename
+    ( $basename, $counter ) = filename( $name, $counter );
+    my $filename = caturi( $path, $basename );
+    unless ($no_cal) {
+        run ( command => "$ppSim -camera $camera -type DARK -exptime $exptime $filename",
+              verbose => 1 ) or die "Unable to run ppSim";
+        unless ($no_update) {
+            run( command => "$inject --camera $camera --telescope $telescope --path $path " .
+                 "$tessellation --workdir $workdir --dbname $dbname $basename",
+                 verbose => 1 ) or die "Unable to inject file.";
+        }
+    }
+}
+
+# Generate flat images
+foreach my $set ( @{FLAT()} ) {
+    my $filter = $set->{filter}; # Name of filter
+    foreach my $exptime ( @{$set->{exptime}} ) {
+        my $basename;           # Output base filename
+        ( $basename, $counter ) = filename( $name, $counter );
+        my $filename = caturi( $path, $basename );
+        unless ($no_cal) {
+            run( command => "$ppSim -camera $camera -type FLAT -filter $filter -exptime $exptime $filename",
+                 verbose => 1 ) or die "Unable to run ppSim";
+            unless ($no_update) {
+                run( command => "$inject --camera $camera --telescope $telescope --path $path " .
+                     "$tessellation --workdir $workdir --dbname $dbname $basename",
+                     verbose => 1 ) or die "Unable to inject file.";
+            }
+        }
+    }
+}
+
+# Generate object images
+foreach my $set ( @{OBJECT()} ) {
+    my $filter = $set->{filter}; # Name of filter
+    my $ra0 = $set->{ra};       # Base Right Ascension (deg)
+    my $dec0 = $set->{dec};     # Base Declination (deg)
+    my $pa = $set->{pa};        # Position angle (deg)
+    my $zp = $set->{zp};        # Zero point
+    my $scale = SCALE();        # Plate scale (arcsec/pix)
+    my $sky = 10**( -0.4 * ( $set->{sky} - $zp ) ) * $scale**2; # Sky background (counts/s)
+    my $dither = $set->{dither} / 3600; # Dither size (deg)
+
+    for (my $i = 0; $i < scalar @{$set->{exptime}}; $i++) {
+        my $exptime = ${$set->{exptime}}[$i]; # Exposure time
+        my $seeing = ${$set->{seeing}}[$i]; # Seeing (pix)
+        my $ra = $ra0 + (2*rand() - 1) * $dither * cos(deg2rad($dec0)); # RA with dither
+        my $dec = $dec0 + (2*rand() - 1) * $dither; # Dec with dither
+
+        my $basename;           # Output base filename
+        ( $basename, $counter ) = filename( $name, $counter );
+        my $filename = caturi( $path, $basename );
+        run( command => "$ppSim -camera $camera -type OBJECT -filter $filter -exptime $exptime " .
+             "-skyrate $sky -ra $ra -dec $dec -pa $pa -scale $scale -zp $zp -seeing $seeing $filename",
+             verbose => 1 ) or die "Unable to run ppSim";
+        unless ($no_update) {
+            run( command => "$inject --camera $camera --telescope $telescope --path $path " .
+                 "$tessellation --workdir $workdir --dbname $dbname $basename",
+                 verbose => 1 ) or die "Unable to inject file.";
+        }
+    }
+}
+
+
+### Pau.
+
+
+# Generate a filename from the base name and counter
+sub filename
+{
+    my $base = shift;           # Base name
+    my $num = shift;            # Number
+    my $workdir = shift;        # Working directory
+    my $name = sprintf("$base%04d", $num);
+    $num++;
+    return ( $name, $num );
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipprc.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipprc.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/ipprc.txt	(revision 22157)
@@ -0,0 +1,16 @@
+
+these scripts use the following library functions to work with the IPP Config system:
+
+$ipprc->convert_filename_absolute( $tess_dir );
+$ipprc->convert_filename_relative( $absfile );
+$ipprc->define_camera($camera);
+$ipprc->extname_rule("CMF.HEAD", $class_id); # MEF psastro output
+$ipprc->file_create_append( $logName );
+$ipprc->file_exists( $skyfile );
+$ipprc->file_prepare( "$exp_tag/$exp_tag.detproc.$det_id", $workdir, $input_uri );
+$ipprc->file_resolve ($path_base);
+$ipprc->filename( "PPIMAGE.BIN1", $file->{path_base}, $file->{class_id} ) . "\n");
+$ipprc->outroot_prepare($outroot);
+$ipprc->reduction($reduction, 'JPEG_BIN1_IMAGE_' . uc($det_type)); # Recipe to use
+$ipprc->rejection( $name, $det_type, $filter );
+$ipprc->tessellation_catdir( $tess_id ); # Tessellation catdir for DVO
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/isp_trans.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/isp_trans.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/isp_trans.pl	(revision 22157)
@@ -0,0 +1,30 @@
+#!/usr/bin/env perl
+# basic ISP transmission analysis:
+
+if (@ARGV != 1) { die "USAGE: isp_trans.pl (input.fits)\n"; }
+$input = $ARGV[0];
+
+# for input file /path/foo.fits, use /path/foo for output
+
+@words = split ('\.', $input);
+if (@words > 1) { pop @words; }
+$output = join (".", @words);
+
+# use constant RECIPE => 'PPIMAGE_OBDSFRA'; # Recipe to use
+$RECIPE_PPIMAGE  = 'PPIMAGE_OA'; # Recipe to use (switch to OBDSFRA when detrend images are ready)
+$RECIPE_PSPHOT   = 'PSPHOT.SUMMIT'; 
+$CALDIR  = '/data/alala.0/ipp/ippRefs/catdir.synth.bright'; # source of photometric calibration data
+$IMTABLE = 'images.dat'; # source of photometric calibration data
+
+vsystem ("ppImage -file $input $output -recipe PPIMAGE $RECIPE_PPIMAGE -recipe PSPHOT $RECIPE_PSPHOT");
+if ($status) { die "failure running ppImage\n"; }
+
+vsystem ("addstar -incal -image -D CAMERA isp -D IMAGE_TABLE $IMTABLE -D CATDIR $CALDIR $output.smf");
+if ($status) { die "failure getting calibration from addstar\n"; }
+
+sub vsystem {
+    print STDERR "@_\n";
+    my $status = system ("@_");
+    $status;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/magic_tree.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/magic_tree.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/magic_tree.pl	(revision 22157)
@@ -0,0 +1,329 @@
+#!/usr/bin/env perl
+
+use Carp;
+use warnings;
+use strict;
+
+## report the program and machine
+use Sys::Hostname;
+my $host = hostname();
+print "\n\n";
+print "Starting script $0 on $host\n\n";
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::List qw( parse_md_list );
+
+use Astro::FITS::CFITSIO qw( :constants );
+Astro::FITS::CFITSIO::PerlyUnpacking(1);
+
+use Math::Trig;
+use File::Temp qw( tempfile );
+use PS::IPP::Config 1.01 qw( :standard );
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+use constant MAX_FIELDS => 4;   # Maximum number of fields to be in a node
+
+# Parse the command-line arguments
+my ($magic_id, $tess_id, $camera, $ra0, $dec0, $dbname, $outroot, $save_temps, $verbose, $no_update, $no_op);
+GetOptions(
+           'magic_id=s'    => \$magic_id,   # Magic identifier
+           'tess_id=s'     => \$tess_id,    # Tessellation identifier
+           'camera=s'      => \$camera,     # Camera name
+           'ra=f'          => \$ra0,        # Boresight right ascension, radians
+           'dec=f'         => \$dec0,       # Boresight declination, radians
+           'dbname=s'      => \$dbname,     # Database name
+           'outroot=s'     => \$outroot,    # Output root name
+           'save-temps'    => \$save_temps, # Save temporary files?
+           'verbose'       => \$verbose,    # Print stuff?
+           'no-update'     => \$no_update,  # Don't update the database?
+           'no-op'         => \$no_op,      # Don't do any operations?
+           ) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options: --magic_id --camera --outroot",
+           -exitval => 3) unless
+    defined $magic_id and
+    defined $tess_id and
+    defined $ra0 and
+    defined $dec0 and
+    defined $camera and
+    defined $outroot;
+
+$ipprc->define_camera($camera);
+
+
+# Look for programs we need
+my $missing_tools;
+my $magictool = can_run('magictool') or (warn "Can't find magictool" and $missing_tools = 1);
+my $ppImage = can_run('ppImage') or (warn "Can't find ppImage" and $missing_tools = 1);
+if ($missing_tools) {
+    warn("Can't find required tools.");
+    exit($PS_EXIT_CONFIG_ERROR);
+}
+
+my $mdcParser = PS::IPP::Metadata::Config->new; # Parser for metadata config files
+
+### Get a list of skycells
+my @skycells;                   # List of skycells
+{
+    my $command = "$magictool -inputskyfile -magic_id $magic_id"; # Command to run
+    $command .= " -dbname $dbname" if defined $dbname;
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to perform magictool -inputfile: $error_code", $magic_id, $error_code);
+    }
+
+    my $metadata = $mdcParser->parse(join "", @$stdout_buf) or
+        &my_die("Unable to parse metadata config doc", $magic_id, $PS_EXIT_PROG_ERROR);
+
+    my $inputs = parse_md_list($metadata) or
+        &my_die("Unable to parse metadata list", $magic_id, $PS_EXIT_PROG_ERROR);
+
+    foreach my $input ( @$inputs ) {
+        push @skycells, $input->{skycell_id};
+    }
+}
+
+### For each skycell, project centre of skycell onto tangent plane of boresight
+my @fields;
+foreach my $skycell_id ( @skycells ) {
+    my $skyfile = $ipprc->filename("SKYCELL.TEMPLATE", $outroot, $skycell_id );
+    $ipprc->skycell_file($tess_id, $skycell_id, $skyfile, $verbose) or &my_die("Unable to generate skycells $skycell_id", $magic_id, $PS_EXIT_PROG_ERROR);
+    my $skyfileResolved = $ipprc->file_resolve( $skyfile );
+    my ($header, $status) = Astro::FITS::CFITSIO::fits_read_header( $skyfileResolved );
+    &my_die("Unable to read skycell header: $status", $magic_id, $PS_EXIT_SYS_ERROR) if $status;
+
+    # Get the useful header keywords
+    my $naxis1 = $$header{'NAXIS1'} or &my_die("Can't find NAXIS1", $magic_id, $PS_EXIT_SYS_ERROR);
+    my $naxis2 = $$header{'NAXIS2'} or &my_die("Can't find NAXIS2", $magic_id, $PS_EXIT_SYS_ERROR);
+    my $ctype1 = $$header{'CTYPE1'} or &my_die("Can't find CTYPE1", $magic_id, $PS_EXIT_SYS_ERROR);
+    my $ctype2 = $$header{'CTYPE2'} or &my_die("Can't find CTYPE2", $magic_id, $PS_EXIT_SYS_ERROR);
+    my $cdelt1 = $$header{'CDELT1'} or &my_die("Can't find CDELT1", $magic_id, $PS_EXIT_SYS_ERROR);
+    my $cdelt2 = $$header{'CDELT2'} or &my_die("Can't find CDELT2", $magic_id, $PS_EXIT_SYS_ERROR);
+    my $crval1 = $$header{'CRVAL1'} or &my_die("Can't find CRVAL1", $magic_id, $PS_EXIT_SYS_ERROR);
+    my $crval2 = $$header{'CRVAL2'} or &my_die("Can't find CRVAL2", $magic_id, $PS_EXIT_SYS_ERROR);
+    my $crpix1 = $$header{'CRPIX1'} or &my_die("Can't find CRPIX1", $magic_id, $PS_EXIT_SYS_ERROR);
+    my $crpix2 = $$header{'CRPIX2'} or &my_die("Can't find CRPIX2", $magic_id, $PS_EXIT_SYS_ERROR);
+    my $pc11 = $$header{'PC001001'} or &my_die("Can't find PC001001", $magic_id, $PS_EXIT_SYS_ERROR);
+    my $pc12 = $$header{'PC001002'} or &my_die("Can't find PC001002", $magic_id, $PS_EXIT_SYS_ERROR);
+    my $pc21 = $$header{'PC002001'} or &my_die("Can't find PC002001", $magic_id, $PS_EXIT_SYS_ERROR);
+    my $pc22 = $$header{'PC002002'} or &my_die("Can't find PC002002", $magic_id, $PS_EXIT_SYS_ERROR);
+    my $crota1 = $$header{'CROTA1'};
+    my $crota2 = $$header{'CROTA2'};
+
+    &my_die("Unexpected projection: $ctype1 and $ctype2.", $magic_id, $PS_EXIT_SYS_ERROR) unless
+        $ctype1 =~ /^\'RA---TAN\s*\'$/ and $ctype2 =~ /^\'DEC--TAN\s*\'$/;
+    &my_die("Can't determine size of skycell ($naxis1,$naxis2)", $magic_id, $PS_EXIT_SYS_ERROR) unless
+        $naxis1 > 0 and $naxis2 > 0;
+    &my_die("Can't determine scale of skycell ($cdelt1,$cdelt2)", $magic_id, $PS_EXIT_SYS_ERROR) if
+        not defined $cdelt1 or $cdelt1 == 0 or not defined $cdelt2 or $cdelt2 == 0;
+    &my_die("We don't know how to handle rotations ($crota1,$crota2)", $magic_id, $PS_EXIT_SYS_ERROR)
+        if defined $crota1 or defined $crota2;
+
+    # Relative coordinates of centre of the field
+    my $x = $naxis1 - $crpix1;
+    my $y = $naxis2 - $crpix2;
+
+    # Coordinates on tangent plane
+    my $xi = $pc11 * ($x) + $pc12 * ($y);
+    my $eta = $pc21 * ($x) + $pc22 * ($y);
+    $xi *= $cdelt1;
+    $eta *= $cdelt2;
+
+    # Coordinates on rotated celestial sphere
+    my $phi = atan2($eta,$xi) + pi/2;
+    my $theta = atan(180 / pi / sqrt($xi**2 + $eta**2));
+
+    # Coordinates on celestial sphere
+    $crval1 = deg2rad($crval1);
+    $crval2 = deg2rad($crval2);
+    my $ra = $crval1 + atan2(cos($theta) * sin($phi),
+                             sin($theta) * cos($crval2) + cos($theta) * sin($crval2) * cos($phi));
+    my $dec = asin(sin($theta) * sin($crval2) - cos($theta) * cos($crval2) * cos($phi));
+
+    # Rotate to boresight
+    my $phi_new = atan2(cos($dec) * sin($ra - $ra0),
+                        sin($dec) * cos($dec0) + cos($dec) * sin($dec0) * cos($ra - $ra0));
+    my $theta_new = asin(sin($dec) * sin($dec0) - cos($dec) * cos($dec0) * cos($ra - $ra0));
+
+    # Project
+    my $rad = 180 / pi * cot($theta_new);
+    my $xi_new = $rad * sin($phi);
+    my $eta_new = - $rad * cos($phi);
+
+    my $field = { id => $skycell_id,
+                  xi => $xi_new,
+                  eta => $eta_new,
+              };
+
+    push @fields, $field;
+}
+
+### Subdivide list of positions into kd-tree
+my $root = {                    # Root node of tree
+    contents => \@fields,       # Contents of node
+    position => 'root',         # Position in tree
+    children => {},             # Children of node
+};
+my @tasks = ( $root );
+while (scalar @tasks > 0) {
+    my $node = shift @tasks;
+    divide_node($node, \@tasks);
+}
+
+### Format tree for magictool
+my $mdcTree = print_node($root); # The tree in MDC format
+my ($treeFile, $treeName) = tempfile( "magictree.${magic_id}.XXXX", UNLINK => !$save_temps );
+print $treeFile, $mdcTree;
+close $treeFile;
+
+### Input tree into database
+{
+    my $command = "$magictool -inputtree";
+    $command   .= " -magic_id $magic_id";
+    $command   .= " -dep_file $treeName";
+    $command   .= " -dbname $dbname" if defined $dbname;
+
+    # Add the processed file to the database
+    unless ($no_update) {
+        my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+            run(command => $command, verbose => $verbose);
+        unless ($success) {
+            $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+            warn("Unable to perform magictool -inputtree: $error_code");
+            exit($error_code);
+        }
+    } else {
+        print "Skipping command: $command\n";
+    }
+}
+
+### Pau.
+
+sub my_die
+{
+    my $msg = shift;            # Warning message on die
+    my $magic_id = shift;       # Magic identifier
+    my $exit_code = shift;      # Exit code to add
+
+    carp($msg);
+    if (defined $magic_id and not $no_update) {
+        my $command = "$magictool -inputtree";
+        $command .= " -magic_id $magic_id";
+        $command .= " -code $exit_code";
+        $command .= " -dbname $dbname" if defined $dbname;
+        system($command);
+    }
+    exit $exit_code;
+}
+
+# Divide a list into two, returning the lower and upper parts
+sub divide_list
+{
+    my $list = shift;           # List to divide
+    my $index = shift;          # Name of index for sorting
+
+    my @sorted = sort { $$a{$index} <=> $$b{$index} } @$list; # Sorted list
+    my $median = int(scalar @sorted / 2); # Median point of list
+    my @upper = splice(@sorted, $median); # Upper part of the sorted list
+
+    return (\@sorted, \@upper);
+}
+
+# Create a new node, add it to the parent, and add it to the task list if required
+sub new_node
+{
+    my $parent = shift;         # The parent node
+    my $contents = shift;       # Contents of the new node
+    my $position = shift;       # Position description
+    my $tasks = shift;          # Tasks to do
+
+    my $node = {
+        contents => $contents,
+        position => $parent->{position} . '_' . $position,
+        children => {},
+    };
+
+    $parent->{children}->{$position} = $node;
+
+    push @$tasks, $node if scalar @$contents > 4;
+
+    return $node;
+}
+
+# Divide a node
+sub divide_node
+{
+    my $node = shift;           # Node to divide
+    my $tasks = shift;          # Tasks to do
+
+    my $position = $node->{position};
+
+    my $contents = $node->{contents} or die "Can't find contents of node."; # Contents of node
+
+    my ($lower, $upper) = divide_list($contents, 'xi');
+
+    if (scalar @$lower > 4) {
+        my ($ll, $lr) = divide_list($lower, 'eta');
+        new_node($node, $ll, 'll', $tasks);
+        new_node($node, $lr, 'lr', $tasks);
+    } else {
+        new_node($node, $lower, 'L', $tasks);
+    }
+
+    if (scalar @$upper > 4) {
+        my ($ul, $ur) = divide_list($upper, 'eta');
+        new_node($node, $ul, 'ul', $tasks);
+        new_node($node, $ur, 'ur', $tasks);
+    } else {
+        new_node($node, $upper, 'U', $tasks);
+    }
+
+    $node->{contents} = undef;
+
+    return $node;
+}
+
+# Print the contents of a node
+sub print_node
+{
+    my $node = shift;           # Node to print
+
+    my $position = $node->{position}; # Position of node
+
+    my $output = "$position\t\tMULTI\n"; # Output text
+
+    if (defined $node->{contents}) {
+        foreach my $field ( @{$node->{contents}} ) {
+            my $skycell_id = $field->{id};      # Skycell name
+            $output .= "$position\t\tSTR\t$skycell_id\n";
+            $output .= "$skycell_id\t\tSTR\tNULL\t\# $field->{xi},$field->{eta}\n";
+        }
+    } else {
+        foreach my $div ( keys %{$node->{children}} ) {
+            $output .= "$position\t\tSTR\t${position}_$div\n";
+            $output .= print_node($node->{children}->{$div});
+        }
+    }
+
+    return $output;
+}
+
+END {
+    my $status = $?;
+    system("sync") == 0
+        or die "failed to execute sync: $!" ;
+    $? = $status;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/mdc2list.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/mdc2list.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/mdc2list.pl	(revision 22157)
@@ -0,0 +1,77 @@
+#!/usr/bin/env perl
+
+# Program to convert a PS metadata config syntax (usually from one of
+# the IPP tools) into a flat tab-delimited list suitable for parsing
+# using "split".
+
+# Copyright (C) 2006  Joshua Hoblitt, Paul A. Price.
+
+use strict;
+use warnings;
+
+use PS::IPP::Metadata::Config;	# Supplies the metadata config parser.
+
+die "Program to convert a PS metadata config file (usually from one of the IPP\n" .
+    "tools) into a flat tab-delimited list suitable for parsing using \'split\'.\n\n" .
+    "Usage: $0 [INFILE [OUTFILE]]\n" if ((join "", @ARGV) =~ /--help/ or scalar @ARGV > 2);
+
+my $inFile;			# Input file
+my $outFile;			# Output file
+if (scalar @ARGV >= 1) {
+    my $inName = shift @ARGV;
+    open $inFile, $inName or die "Can't open $inName: $!\n";
+    if (scalar @ARGV == 1) {
+	my $outName = shift @ARGV;
+	open $outFile, ">", $outName or die "Can't open $outName: $!\n";
+    } else {
+	$outFile = *STDOUT;
+    }
+} else {
+    $inFile = *STDIN;
+    $outFile = *STDOUT;
+}
+
+my @input = <$inFile>;		# Contents of the metadata config file
+my $mdcParser = PS::IPP::Metadata::Config->new; # Parser for metadata config files
+my $md = $mdcParser->parse(join "", @input)
+        or die "unable to parse metadata config doc";
+my $hashes = mds2hashes($md);	# An array of hashes
+foreach my $pending (@$hashes) {
+    foreach my $key (keys %$pending) {
+	print $outFile ( $pending->{$key} . "\t");
+    }
+    print $outFile "\n";
+}
+
+### Pau.
+
+
+# Given an array of MDs, return an array of hashes
+sub mds2hashes
+{
+    my $mds = shift;		# Reference to the metadatas
+    my @array;			# The array of hashes, to be returned
+    foreach my $md (@$mds) {
+        my $values = md2hash($md->{value});
+        push @array, $values;
+    }
+    return \@array;
+}
+
+# Convert the metadata to a hash; in effect, strips out the comment, type and class fields.
+sub md2hash
+{
+    my $values = shift;		# Reference to the metadata
+    my %hash;			# Hash, to be returned
+    foreach my $data (@$values) {
+        $hash{$data->{name}} = $data->{value};
+    }
+    return \%hash;
+}
+
+END {
+    my $status = $?;
+    system("sync") == 0
+        or die "failed to execute sync: $!" ;
+    $? = $status;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/register_exp.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/register_exp.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/register_exp.pl	(revision 22157)
@@ -0,0 +1,222 @@
+#!/usr/bin/env perl
+
+use Carp;
+use warnings;
+use strict;
+
+## report the program and machine
+use Sys::Hostname;
+my $host = hostname();
+print "\n\n";
+print "Starting script $0 on $host\n\n";
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use Cache::File;
+use Storable qw( freeze thaw );
+use File::Basename qw( basename );
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::Stats;
+use PS::IPP::Config 1.01 qw( :standard );
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+my $ipprc = PS::IPP::Config->new();
+
+my ($cache, $exp_id, $exp_tag, $dbname, $verbose, $no_update, $no_op, $logfile);
+GetOptions(
+    'caches'        => \$cache,
+    'exp_id|e=s'    => \$exp_id,
+    'exp_tag|t=s'   => \$exp_tag,
+    'dbname|d=s'    => \$dbname, # Database name
+    'verbose'       => \$verbose,   # Print to stdout
+    'no-update'     => \$no_update,
+    'no-op'         => \$no_op,
+    'logfile=s'     => \$logfile,
+) or pod2usage( 2 );
+
+$ipprc->redirect_output($logfile) if $logfile;
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options: --exp_id --exp_tag",
+           -exitval => 3) unless
+    defined $exp_id and
+    defined $exp_tag;
+
+# add -detrend UNLESS type is one of SCIENCE listed below (eg, OBJECT)
+my @SCIENCE = ( "object", "science" ); # Observation types to NOT mark as detrend
+my $DETREND_FLAG = "-end_stage reg"; # Flag to use to mark detrend exposure
+
+# values to extract from output metadata and the stats to calculate
+my $STATS =
+   [   #          register imfile
+       #          label             STATISTIC          CHIPTOOL FLAG
+       { name => "exp_name",        type => "constant",   flag => "-exp_name",        dtype => "string" }, # File level
+       { name => "telescope",       type => "constant",   flag => "-telescope",       dtype => "string" }, # File level
+       { name => "camera",          type => "constant",   flag => "-inst",            dtype => "string" }, # File level
+       { name => "filelevel",       type => "constant",   flag => "-filelevel",       dtype => "string" }, # File level
+       { name => "object",          type => "constant",   flag => "-object",          dtype => "string" },
+       { name => "exp_type",        type => "constant",   flag => "-exp_type",        dtype => "string" }, # File level
+       { name => "filter",          type => "constant",   flag => "-filter",          dtype => "string" }, # File level
+       { name => "comment",         type => "constant",   flag => "-comment",         dtype => "string" }, # ObsComment
+       { name => "dateobs",         type => "constant",   flag => "-dateobs",         dtype => "string" }, # File level
+       { name => "ccd_temp",        type => "mean",       flag => "-ccd_temp",        dtype => "float"  }, # CCD temperature
+       { name => "exp_time",        type => "mean",       flag => "-exp_time",        dtype => "float"  }, # Exposure time
+       { name => "sat_pixel_frac",  type => "mean",       flag => "-sat_pixel_frac",  dtype => "float"  }, # Fraction of saturated pixels
+       { name => "airmass",         type => "mean",       flag => "-airmass",         dtype => "float"  }, # Airmass
+       { name => "ra",              type => "mean",       flag => "-ra",              dtype => "float"  }, # Right ascension
+       { name => "decl",            type => "mean",       flag => "-decl",            dtype => "float"  }, # Declination
+       { name => "posang",          type => "mean",       flag => "-posang",          dtype => "float"  }, # Position angle
+       { name => "alt",             type => "mean",       flag => "-alt",             dtype => "float"  }, # Altitude
+       { name => "az",              type => "mean",       flag => "-az",              dtype => "float"  }, # Azimuth
+       { name => "m1_x",            type => "constant",   flag => "-m1_x",            dtype => "float"  }, # M1X
+       { name => "m1_y",            type => "constant",   flag => "-m1_y",            dtype => "float"  }, # M1Y
+       { name => "m1_z",            type => "constant",   flag => "-m1_z",            dtype => "float"  }, # M1Z
+       { name => "m1_tip",          type => "constant",   flag => "-m1_tip",          dtype => "float"  }, # M1TIP
+       { name => "m1_tilt",         type => "constant",   flag => "-m1_tilt",         dtype => "float"  }, # M1TILT
+       { name => "m2_x",            type => "constant",   flag => "-m2_x",            dtype => "float"  }, # M2X
+       { name => "m2_y",            type => "constant",   flag => "-m2_y",            dtype => "float"  }, # M2Y
+       { name => "m2_z",            type => "constant",   flag => "-m2_z",            dtype => "float"  }, # M2Z
+       { name => "m2_tip",          type => "constant",   flag => "-m2_tip",          dtype => "float"  }, # M2TIP
+       { name => "m2_tilt",         type => "constant",   flag => "-m2_tilt",         dtype => "float"  }, # M2TILT
+       { name => "env_temperature", type => "constant",   flag => "-env_temperature", dtype => "float"  }, # external temp
+       { name => "env_humidity",    type => "constant",   flag => "-env_humidity",    dtype => "float"  }, # external humidity
+       { name => "env_wind_speed",  type => "constant",   flag => "-env_wind_speed",  dtype => "float"  }, # external wind speed
+       { name => "env_wind_dir",    type => "constant",   flag => "-env_wind_dir",    dtype => "float"  }, # external wind direction
+
+       { name => "-teltemp_m1",     type => "constant",   flag => "-teltemp_m1",      dtype => "float"  }, # Primary mirror temps (C)
+       { name => "-teltemp_m1cell", type => "constant",   flag => "-teltemp_m1cell",  dtype => "float"  }, # Primary mirror support temps (C)
+       { name => "-teltemp_m2",     type => "constant",   flag => "-teltemp_m2",      dtype => "float"  }, # Secondary mirror temps (C
+       { name => "-teltemp_spider", type => "constant",   flag => "-teltemp_spider",  dtype => "float"  }, # Spider temperatures (C)
+       { name => "-teltemp_truss",  type => "constant",   flag => "-teltemp_truss",   dtype => "float"  }, # Mid truss temperatures (C
+       { name => "-teltemp_extra",  type => "constant",   flag => "-teltemp_extra",   dtype => "float"  }, # Miscellaneous temperatures (C)
+
+       { name => "pon_time",        type => "mean",       flag => "-pon_time",        dtype => "float"  }, # time since last power on
+       { name => "bg",              type => "mean",       flag => "-bg",              dtype => "float"  }, # background
+       { name => "bg",              type => "stdev",      flag => "-bg_mean_stdev",   dtype => "float"  }, # Azimuth
+       { name => "bg_stdev",        type => "rms",        flag => "-bg_stdev",        dtype => "float"  }, # Azimuth
+       ];
+my $stats = PS::IPP::Metadata::Stats->new($STATS); # Stats parser
+
+# Look for commands we need
+my $missing_tools;
+my $regtool = can_run('regtool')
+    or (warn "can't find regtool" and $missing_tools = 1);
+
+if ($missing_tools) {
+    warn ("Can't find required tools");
+    exit($PS_EXIT_CONFIG_ERROR);
+}
+
+# setup cache interface
+my $c = Cache::File->new(
+    cache_root => File::Spec->catdir($ENV{'HOME'}, '.pxtools', basename($0)),
+    default_expires => '7200 sec',
+);
+
+# Get the list of imfiles & their stats
+{
+    my $command = "$regtool -processedimfile -exp_id $exp_id";
+    $command .= " -dbname $dbname" if defined $dbname;
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        cache_run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        warn ("Unable to perform regtool -processedimfile on exposure id $exp_id: $error_code");
+        exit ($error_code);
+    }
+
+    # Parse the output
+    my $mdcParser = PS::IPP::Metadata::Config->new;        # Parser for metadata config files
+    my $metadata = $mdcParser->parse(join "", @$stdout_buf);
+    unless ($metadata) {
+        &my_die ("Unable to parse metadata config doc", $exp_id, $PS_EXIT_PROG_ERROR);
+    }
+
+    # extract the stats from the metadata
+    unless ($stats->parse($metadata)) {
+        &my_die ("Unable to find all values", $exp_id, $PS_EXIT_CONFIG_ERROR);
+    }
+}
+
+# we require at a minimum: -telescope, -inst, -filelevel, -class_id, -exp_type
+if (uc($stats->value_for_flag ("-telescope")) eq "NULL") { &my_die ("telescope not found", $exp_id, $PS_EXIT_CONFIG_ERROR); }
+if (uc($stats->value_for_flag ("-inst"))      eq "NULL") { &my_die ("inst      not found", $exp_id, $PS_EXIT_CONFIG_ERROR); }
+if (uc($stats->value_for_flag ("-filelevel")) eq "NULL") { &my_die ("filelevel not found", $exp_id, $PS_EXIT_CONFIG_ERROR); }
+if (uc($stats->value_for_flag ("-exp_type"))  eq "NULL") { &my_die ("exp_type  not found", $exp_id, $PS_EXIT_CONFIG_ERROR); }
+if (uc($stats->value_for_flag ("-exp_name"))  eq "NULL") { &my_die ("exp_name  not found", $exp_id, $PS_EXIT_CONFIG_ERROR); }
+
+my $command = "$regtool -addprocessedexp";
+$command .= " -exp_id $exp_id";
+$command .= " -exp_tag $exp_tag";
+$command .= " -hostname $host" if defined $host;
+$command .= " -dbname $dbname" if defined $dbname;
+$command .= $stats->cmdflags();
+
+my $exp_type = $stats->value_for_flag ("-exp_type");
+
+# Add the detrend flag, if needed
+{
+    my $object = 0;             # Is it an object exposure?
+    foreach my $scienceType (@SCIENCE) {
+        if (lc($exp_type) =~ /$scienceType/) {
+            $object = 1;
+            last;
+        }
+    }
+    $command .= " $DETREND_FLAG" unless $object;
+}
+
+# Output results to the database
+unless ($no_update) {
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        cache_run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        warn ("Unable to run regtool -addprocessedexp for $exp_id: $error_code");
+        exit($error_code);
+    }
+} else {
+    print "skipping command: $command\n";
+}
+
+### Pau.
+
+sub cache_run
+{
+    my %p = @_;
+
+    my $cmd_output = $c->get($p{command}) if $cache;
+    if (defined $cmd_output) {
+        return @{thaw $cmd_output};
+    } else {
+        my @output = run(%p);
+        $c->set($p{command}, freeze \@output) if $cache;
+        return @output;
+    }
+}
+
+sub my_die
+{
+    my $msg = shift; # Warning message on die
+    my $exp_id = shift;
+    my $exit_code = shift;
+
+    carp($msg);
+    if (defined $exp_id and not $no_update) {
+        my $command = "$regtool -addprocessedexp -exp_id $exp_id -code $exit_code";
+        $command .= " -hostname $host" if defined $host;
+        $command .= " -dbname $dbname" if defined $dbname;
+        system($command);
+    }
+    exit $exit_code;
+}
+
+END {
+    my $exit = $?;
+    system("sync") == 0 or die "failed to execute sync: $!";
+    $? = $exit;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/register_imfile.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/register_imfile.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/register_imfile.pl	(revision 22157)
@@ -0,0 +1,244 @@
+#!/usr/bin/env perl
+
+use Carp;
+use warnings;
+use strict;
+
+## report the program and machine
+use Sys::Hostname;
+my $host = hostname();
+print "\n\n";
+print "Starting script $0 on $host\n\n";
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use Cache::File;
+use Storable qw(freeze thaw);
+use File::Basename qw( basename);
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Config 1.01 qw( :standard );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::Stats;
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+use File::Spec;
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+my ($cache, $exp_id, $tmp_class_id, $tmp_exp_name, $uri, $dbname, $verbose, $no_update, $no_op, $logfile);
+GetOptions(
+    'caches'           => \$cache,
+    'exp_id|e=s'       => \$exp_id,
+    'tmp_class_id|i=s' => \$tmp_class_id,
+    'tmp_exp_name|n=s' => \$tmp_exp_name,
+    'uri|u=s'          => \$uri,
+    'dbname|d=s'       => \$dbname,    # Database name
+    'verbose'          => \$verbose,   # Print to stdout
+    'no-update'        => \$no_update,
+    'no-op'            => \$no_op,
+    'logfile=s'        => \$logfile,
+) or pod2usage( 2 );
+
+$ipprc->redirect_output($logfile) if $logfile;
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options: --exp_id --tmp_class_id --tmp_exp_name --uri",
+           -exitval => 3) unless
+    defined $exp_id and
+    defined $tmp_class_id and
+    defined $tmp_exp_name and
+    defined $uri;
+
+
+my $RECIPE = "REGISTER"; # Recipe to use for ppStats
+
+# values to extract from output metadata and the stats to calculate
+my $STATS =
+   [
+       #          PPSTATS KEYWORD         STATISTIC          CHIPTOOL FLAG
+       { name => "FILE.LEVEL",     type => "constant", flag => "-filelevel",       dtype => "string" }, # File level
+       { name => "CLASS.ID",       type => "constant", flag => "-class_id",        dtype => "string" }, # Real Class ID
+       { name => "FPA.OBJECT",     type => "constant", flag => "-object",          dtype => "string" }, # Object
+       { name => "FPA.OBSTYPE",    type => "constant", flag => "-exp_type",        dtype => "string" }, # Exposure type
+       { name => "FPA.FILTER",     type => "constant", flag => "-filter",          dtype => "string" }, # Filter used
+       { name => "FPA.COMMENT",    type => "constant", flag => "-comment",         dtype => "string" }, # Obs Comment
+       { name => "FPA.AIRMASS",    type => "constant", flag => "-airmass",         dtype => "float"  }, # Airmass
+       { name => "FPA.RA",         type => "constant", flag => "-ra",              dtype => "float"  }, # Right ascension
+       { name => "FPA.DEC",        type => "constant", flag => "-decl",            dtype => "float"  }, # Declination
+       { name => "FPA.ALT",        type => "constant", flag => "-alt",             dtype => "float"  }, # Altitude
+       { name => "FPA.AZ",         type => "constant", flag => "-az",              dtype => "float"  }, # Azimuth
+       { name => "FPA.POSANGLE",   type => "constant", flag => "-posang",          dtype => "float"  }, # Position angle
+       { name => "FPA.TIME",       type => "constant", flag => "-dateobs",         dtype => "string" }, # Date of observation (UTC)
+       { name => "FPA.TELESCOPE",  type => "constant", flag => "-telescope",       dtype => "string" }, # Telescope
+       { name => "FPA.CAMERA",     type => "constant", flag => "-inst",            dtype => "string" }, # Instrument
+       { name => "FPA.M1X",        type => "constant", flag => "-m1_x",            dtype => "float"  }, # M1X
+       { name => "FPA.M1Y",        type => "constant", flag => "-m1_y",            dtype => "float"  }, # M1Y
+       { name => "FPA.M1Z",        type => "constant", flag => "-m1_z",            dtype => "float"  }, # M1Z
+       { name => "FPA.M1TIP",      type => "constant", flag => "-m1_tip",          dtype => "float"  }, # M1TIP
+       { name => "FPA.M1TILT",     type => "constant", flag => "-m1_tilt",         dtype => "float"  }, # M1TILT
+       { name => "FPA.M2X",        type => "constant", flag => "-m2_x",            dtype => "float"  }, # M2X
+       { name => "FPA.M2Y",        type => "constant", flag => "-m2_y",            dtype => "float"  }, # M2Y
+       { name => "FPA.M2Z",        type => "constant", flag => "-m2_z",            dtype => "float"  }, # M2Z
+       { name => "FPA.M2TIP",      type => "constant", flag => "-m2_tip",          dtype => "float"  }, # M2TIP
+       { name => "FPA.M2TILT",     type => "constant", flag => "-m2_tilt",         dtype => "float"  }, # M2TILT
+       { name => "FPA.ENV.TEMP",   type => "constant", flag => "-env_temperature", dtype => "float"  }, # external temp
+       { name => "FPA.ENV.HUMID",  type => "constant", flag => "-env_humidity",    dtype => "float"  }, # external humidity
+       { name => "FPA.ENV.WIND",   type => "constant", flag => "-env_wind_speed",  dtype => "float"  }, # external wind speed
+       { name => "FPA.ENV.DIR",    type => "constant", flag => "-env_wind_dir",    dtype => "float"  }, # external wind direction
+
+       { name => "FPA.TELTEMP.M1",     type => "constant", flag => "-teltemp_m1",     dtype => "float"  }, # Primary mirror temps (C)
+       { name => "FPA.TELTEMP.M1CELL", type => "constant", flag => "-teltemp_m1cell", dtype => "float"  }, # Primary mirror support temps (C)
+       { name => "FPA.TELTEMP.M2",     type => "constant", flag => "-teltemp_m2",     dtype => "float"  }, # Secondary mirror temps (C
+       { name => "FPA.TELTEMP.SPIDER", type => "constant", flag => "-teltemp_spider", dtype => "float"  }, # Spider temperatures (C)
+       { name => "FPA.TELTEMP.TRUSS",  type => "constant", flag => "-teltemp_truss",  dtype => "float"  }, # Mid truss temperatures (C
+       { name => "FPA.TELTEMP.EXTRA",  type => "constant", flag => "-teltemp_extra",  dtype => "float"  }, # Miscellaneous temperatures (C)
+
+       { name => "FPA.PON.TIME",   type => "constant", flag => "-pon_time",        dtype => "float"  }, # time since last power on
+       { name => "CHIP.TEMP",      type => "mean",     flag => "-ccd_temp",        dtype => "float"  }, # CCD temperature
+       { name => "CELL.EXPOSURE",  type => "mean",     flag => "-exp_time",        dtype => "float"  }, # Exposure time
+       { name => "SAT_PIXEL_FRAC", type => "mean",     flag => "-sat_pixel_frac",  dtype => "float"  }, # fraction of saturated pixels
+       { name => "ROBUST_MEDIAN",  type => "mean",     flag => "-bg",              dtype => "float"  },
+       { name => "ROBUST_MEDIAN",  type => "stdev",    flag => "-bg_mean_stdev",   dtype => "float"  },
+       { name => "ROBUST_STDEV",   type => "rms",      flag => "-bg_stdev",        dtype => "float"  },
+   ];
+my $stats = PS::IPP::Metadata::Stats->new($STATS); # Stats parser
+
+# Look for programs we need
+my $missing_tools;
+my $regtool = can_run('regtool')
+    or (warn "Can't find regtool" and $missing_tools = 1);
+my $ppStats = can_run('ppStats')
+    or (warn "Can't find ppStats" and $missing_tools = 1);
+
+if ($missing_tools) {
+    warn ("Can't find required tools");
+    exit($PS_EXIT_CONFIG_ERROR);
+}
+
+# setup cache interface
+# XXX why is this being cached?
+my $c = Cache::File->new(
+    cache_root => File::Spec->catdir($ENV{'HOME'}, '.pxtools', basename($0)),
+    default_expires => '7200 sec',
+);
+
+my $now_time = localtime();
+printf STDERR "\nstarting ppStats: %s\n", $now_time if $verbose;
+
+# Run ppStats on the input file
+{
+    # extract the data from the image header; -level is used to get FILE.LEVEL and CLASS.ID
+    my $command = "$ppStats $uri -recipe PPSTATS $RECIPE -level"; # Command to run ppStats
+    $command .= " -dbname $dbname" if defined $dbname;
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        cache_run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die ("Unable to perform ppStats on exposure id $exp_id: $error_code", $exp_id, $tmp_exp_name, $tmp_class_id, $uri, $error_code);
+    }
+
+    # Parse the output
+    my $mdcParser = PS::IPP::Metadata::Config->new;        # Parser for metadata config files
+    my $metadata = $mdcParser->parse(join "", @$stdout_buf); # XXX is this join necessary?
+    unless ($metadata) {
+        &my_die ("Unable to parse metadata config doc", $exp_id, $tmp_exp_name, $tmp_class_id, $uri, $PS_EXIT_PROG_ERROR);
+    }
+
+    # extract the stats from the metadata
+    unless ($stats->parse($metadata)) {
+        &my_die ("Unable to find all values", $exp_id, $tmp_exp_name, $tmp_class_id, $uri, $PS_EXIT_CONFIG_ERROR);
+    }
+}
+
+$now_time = localtime();
+printf STDERR "\ndone with ppStats: %s\n", $now_time if $verbose;
+
+# we require at a minimum: -telescope, -inst, -filelevel, -class_id, -exp_type
+if (uc($stats->value_for_flag ("-telescope")) eq "NULL") { &my_die ("telescope not found", $exp_id, $tmp_exp_name, $tmp_class_id, $uri, $PS_EXIT_CONFIG_ERROR); }
+if (uc($stats->value_for_flag ("-inst"))      eq "NULL") { &my_die ("inst      not found", $exp_id, $tmp_exp_name, $tmp_class_id, $uri, $PS_EXIT_CONFIG_ERROR); }
+if (uc($stats->value_for_flag ("-filelevel")) eq "NULL") { &my_die ("filelevel not found", $exp_id, $tmp_exp_name, $tmp_class_id, $uri, $PS_EXIT_CONFIG_ERROR); }
+if (uc($stats->value_for_flag ("-class_id"))  eq "NULL") { &my_die ("class_id  not found", $exp_id, $tmp_exp_name, $tmp_class_id, $uri, $PS_EXIT_CONFIG_ERROR); }
+if (uc($stats->value_for_flag ("-exp_type"))  eq "NULL") { &my_die ("exp_type  not found", $exp_id, $tmp_exp_name, $tmp_class_id, $uri, $PS_EXIT_CONFIG_ERROR); }
+
+my $command = "$regtool -addprocessedimfile";
+$command .= " -exp_id $exp_id";
+$command .= " -exp_name $tmp_exp_name"; # keep the supplied exp_name (could be derived from the file)
+$command .= " -tmp_class_id $tmp_class_id"; # the original class_id supplied by the user, replace by ppStats CLASS.ID
+$command .= " -uri $uri ";
+$command .= " -hostname $host" if defined $host;
+$command .= " -dbname $dbname" if defined $dbname;
+$command .= $stats->cmdflags();
+
+$now_time = localtime();
+printf STDERR "\nrunning regtool update: %s\n", $now_time if $verbose;
+
+# Push the results into the database
+unless ($no_update) {
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        warn ("Unable to perform regtool -addprocessedimfile: $error_code");
+        exit($error_code);
+    }
+} else {
+    print "skipping command: $command\n";
+}
+
+$now_time = localtime();
+printf STDERR "\ndone with regtool update: %s\n", $now_time if $verbose;
+
+sub cache_run
+{
+    my %p = @_;
+
+    my $cmd_output = $c->get($p{command}) if $cache;
+    if (defined $cmd_output) {
+        return @{thaw $cmd_output};
+    } else {
+        my @output = run(%p);
+        $c->set($p{command}, freeze \@output) if $cache;
+        return @output;
+    }
+}
+
+sub my_die
+{
+    my $msg = shift; # Warning message on die
+    my $exp_id = shift;
+    my $exp_name = shift;
+    my $tmp_class_id = shift;
+    my $uri = shift;
+    my $exit_code = shift;
+
+    # for failed imfiles, we insert UNKNOWN for inst, telescope, class_id
+
+    carp($msg);
+    if (defined $exp_id && defined $tmp_class_id and not $no_update) {
+        my $command = "$regtool -addprocessedimfile";
+        $command .= " -exp_id $exp_id";
+        $command .= " -exp_name $exp_name";
+        $command .= " -tmp_class_id $tmp_class_id";
+        $command .= " -uri $uri ";
+        $command .= " -telescope UNKNOWN";
+        $command .= " -inst UNKNOWN";
+        $command .= " -class_id $tmp_class_id";
+        $command .= " -code $exit_code";
+        $command .= " -hostname $host" if defined $host;
+        $command .= " -dbname $dbname" if defined $dbname;
+        system ($command);
+    }
+    exit $exit_code;
+}
+
+# Pau.
+
+END {
+    my $exit = $?;
+    system("sync") == 0 or die "failed to execute sync: $!";
+    $? = $exit;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/stack_skycell.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/stack_skycell.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/stack_skycell.pl	(revision 22157)
@@ -0,0 +1,251 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+## report the program and machine
+use Sys::Hostname;
+my $host = hostname();
+print "\n\n";
+print "Starting script $0 on $host\n\n";
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::Stats;
+use PS::IPP::Metadata::List qw( parse_md_list );
+use Data::Dumper;
+use File::Temp qw( tempfile );
+use PS::IPP::Config 1.01 qw( :standard );
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+my ($stack_id, $dbname, $outroot, $verbose, $no_update, $no_op, $save_temps);
+GetOptions(
+    'stack_id|d=s'      => \$stack_id, # Stack identifier
+    'dbname|d=s'        => \$dbname, # Database name
+    'outroot=s'         => \$outroot, # Output root name
+    'verbose'           => \$verbose,   # Print to stdout
+    'no-update'         => \$no_update, # Don't update the database?
+    'no-op'             => \$no_op, # Don't do any operations?
+    'save-temps'        => \$save_temps, # Save temporary files?
+) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage(
+    -msg => "Required options: --stack_id --outroot",
+    -exitval => 3,
+          ) unless defined $stack_id
+    and defined $outroot;
+
+my $STATS =
+   [
+       #          PPSTATS KEYWORD         STATISTIC          STACKTOOL FLAG
+       { name => "ROBUST_MEDIAN",   type => "mean", flag => "-bg",          dtype => "float" },
+       { name => "ROBUST_STDEV",    type => "rms",  flag => "-bg_stdev",    dtype => "float" },
+#      { name => "DT_STACK",        type => "sum",  flag => "-dtime_stack", dtype => "float" },
+       { name => "GOOD_PIXEL_FRAC", type => "mean", flag => "-good_frac",   dtype => "float" },
+   ];
+my $stats = PS::IPP::Metadata::Stats->new($STATS); # Stats parser
+
+# Look for programs we need
+my $missing_tools;
+my $stacktool = can_run('stacktool') or (warn "Can't find stacktool" and $missing_tools = 1);
+my $ppStack = can_run('ppStack') or (warn "Can't find ppStack" and $missing_tools = 1);
+if ($missing_tools) {
+    warn("Can't find required tools.");
+    exit($PS_EXIT_CONFIG_ERROR);
+}
+
+# Get list of components for stacking
+my $mdcParser = PS::IPP::Metadata::Config->new; # Parser for metadata config files
+my $files;
+{
+    my $command = "$stacktool -inputskyfile -stack_id $stack_id";
+    $command .= " -dbname $dbname" if defined $dbname;
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to perform stacktool -inputskyfile: $error_code", $stack_id, $error_code);
+    }
+
+    my $metadata = $mdcParser->parse(join "", @$stdout_buf) or
+        &my_die("Unable to parse metadata config doc", $stack_id, $PS_EXIT_PROG_ERROR);
+    $files = parse_md_list($metadata) or
+        &my_die("Unable to parse metadata list", $stack_id, $PS_EXIT_PROG_ERROR);
+}
+
+&my_die("Stack list contains less than two elements", $stack_id, $PS_EXIT_SYS_ERROR) unless
+    scalar @$files >= 2;
+
+# Parse the list of input files to get the tesselation, skycell identifiers and camera
+my $skycell_id;                 # Skycell identifier
+my $tess_id;                    # Tesselation identifier
+my $camera;                     # Camera
+foreach my $file (@$files) {
+    # skip warps which are speicified as 'ignored'
+    if ($file->{ignored}) { next; }
+    if (defined $tess_id) {
+        &my_die("Tesselation identifiers don't match", $stack_id, $PS_EXIT_SYS_ERROR) unless
+            $file->{tess_id} eq $tess_id;
+    } else {
+        $tess_id = $file->{tess_id};
+    }
+    if (defined $skycell_id) {
+        &my_die("Skycell identifiers don't match", $stack_id, $PS_EXIT_SYS_ERROR) unless
+            $file->{skycell_id} eq $skycell_id;
+    } else {
+        $skycell_id = $file->{skycell_id};
+    }
+    if (defined $camera) {
+        &my_die("Cameras don't match", $stack_id, $PS_EXIT_SYS_ERROR) unless $file->{camera} eq $camera;
+    } else {
+        $camera = $file->{camera};
+    }
+}
+
+&my_die("Can't find camera", $stack_id, $PS_EXIT_SYS_ERROR) unless defined $camera;
+$ipprc->define_camera($camera);
+
+# Generate MDC file with the inputs
+my ($listFile, $listName) = tempfile( "$tess_id.$skycell_id.stk$stack_id.list.XXXX",
+                                      UNLINK => !$save_temps );
+my $num = 0;
+my $inputSources;               # Sources to use as stamps
+foreach my $file (@$files) {
+    if ($file->{ignored}) { next; }
+
+    print $listFile "INPUT$num\tMETADATA\n";
+    $num++;
+
+    my $image = $file->{uri};   # Image name
+    my $mask = $ipprc->filename( "PSWARP.OUTPUT.MASK", $file->{path_base} ); # Mask name
+    my $weight = $ipprc->filename( "PSWARP.OUTPUT.WEIGHT", $file->{path_base} ); # Weight name
+    my $psf = $ipprc->filename( "PSPHOT.PSF.SAVE", $file->{path_base} ); # PSF name
+    my $sources = $ipprc->filename("PSWARP.OUTPUT.SOURCES", $file->{path_base}); # Sources name
+
+    &my_die("Image $image does not exist", $stack_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists( $image );
+    &my_die("Mask $mask does not exist", $stack_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists( $mask );
+    &my_die("Weight $weight does not exist", $stack_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists( $weight );
+    &my_die("PSF $psf does not exist", $stack_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists( $psf );
+    &my_die("Sources $sources does not exist", $stack_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists( $sources );
+
+    print $listFile "\tIMAGE\tSTR\t" . $image . "\n";
+    print $listFile "\tMASK\tSTR\t" . $mask . "\n";
+    print $listFile "\tWEIGHT\tSTR\t" . $weight . "\n";
+    print $listFile "\tPSF\tSTR\t" . $psf . "\n";
+    print $listFile "\tSOURCES\tSTR\t" . $sources . "\n";
+
+    ### XXX NEED TO UPDATE THESE appropriately
+    print $listFile "\tWEIGHTING\tF32\t" . 1.0 . "\n";
+
+    print $listFile "END\n\n";
+}
+
+# Get the output filenames
+my $outputName = $ipprc->filename("PPSTACK.OUTPUT", $outroot);
+my $outputMask = $ipprc->filename("PPSTACK.OUTPUT.MASK", $outroot);
+my $outputWeight = $ipprc->filename("PPSTACK.OUTPUT.WEIGHT", $outroot);
+my $outputSources = $ipprc->filename("PSPHOT.OUTPUT", $outroot, "none");
+#my $bin1Name =  $ipprc->filename("PPSTACK.BIN1", $outroot);
+#my $bin2Name =  $ipprc->filename("PPSTACK.BIN2", $outroot);
+my $outputStats = $ipprc->filename("SKYCELL.STATS", $outroot);
+my $traceDest = $ipprc->filename("TRACE.EXP", $outroot);
+my $logDest = $ipprc->filename("LOG.EXP", $outroot);
+
+# Perform stacking
+unless ($no_op) {
+    my $command = "$ppStack $listName $outroot";
+    $command .= " -stats $outputStats";
+    $command .= " -recipe PPSUB STACK";
+    $command .= " -recipe PPSTATS WARPSTATS";
+    $command .= " -photometry";
+    $command .= " -tracedest $traceDest -log $logDest";
+    $command .= " -dbname $dbname" if defined $dbname;
+
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to perform ppStack: $error_code", $stack_id, $error_code);
+    }
+    &my_die("Couldn't find expected output file: $outputName", $stack_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($outputName);
+    &my_die("Couldn't find expected output file: $outputMask", $stack_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($outputMask);
+    &my_die("Couldn't find expected output file: $outputWeight", $stack_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($outputWeight);
+    &my_die("Couldn't find expected output file: $outputSources", $stack_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($outputSources);
+#   &my_die("Couldn't find expected output file: $bin1Name",    $stack_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($bin1Name);
+#   &my_die("Couldn't find expected output file: $bin2Name",    $stack_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($bin2Name);
+    &my_die("Couldn't find expected output file: $outputStats", $stack_id, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($outputStats);
+
+    # Get the statistics on the stacked image
+    my $statsFile;              # File handle
+    open $statsFile, $ipprc->file_resolve($outputStats) or &my_die("Can't open statistics file $outputStats: $!", $stack_id, $PS_EXIT_SYS_ERROR);
+    my @contents = <$statsFile>; # Contents of file
+    close $statsFile;
+    my $metadata = $mdcParser->parse(join "", @contents) or
+        &my_die("Unable to parse metadata config doc", $stack_id, $PS_EXIT_PROG_ERROR);
+    $stats->parse($metadata) or &my_die("Unable to find all values in statistics output.", $stack_id, $PS_EXIT_PROG_ERROR);
+}
+
+unless ($no_update) {
+
+    # Add the stack result
+    {
+        my $command = "$stacktool -addsumskyfile -stack_id $stack_id -uri $outputName -path_base $outroot";
+        $command .= $stats->cmdflags();
+        $command .= " -hostname $host" if defined $host;
+        $command .= " -dbname $dbname" if defined $dbname;
+
+        my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+            run(command => $command, verbose => $verbose);
+        unless ($success) {
+            $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+            &my_die("Unable to perform stacktool -addsumskyfile: $error_code", $stack_id, $error_code);
+        }
+    }
+
+    # Register the run as completed
+    {
+        my $command = "$stacktool -updaterun -stack_id $stack_id -state stop"; # Command to run stacktool
+        $command .= " -dbname $dbname" if defined $dbname;
+
+        my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+            run(command => $command, verbose => $verbose);
+        unless ($success) {
+            $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+            warn("Unable to perform stacktool -updaterun: $error_code\n");
+            exit($error_code);
+        }
+    }
+}
+
+
+sub my_die
+{
+    my $msg = shift;            # Warning message on die
+    my $stack_id = shift;       # Stack identifier
+    my $exit_code = shift;      # Exit code to add
+
+    warn($msg);
+    if (defined $stack_id and not $no_update) {
+        my $command = "$stacktool -addsumskyfile -stack_id $stack_id -code $exit_code";
+        $command .= " -hostname $host" if defined $host;
+        $command .= " -dbname $dbname" if defined $dbname;
+        system ($command);
+    }
+    exit $exit_code;
+}
+
+END {
+    my $exit = $?;
+    system("sync") == 0 or die "failed to execute sync: $!";
+    $? = $exit;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/summit_copy.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/summit_copy.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/summit_copy.pl	(revision 22157)
@@ -0,0 +1,163 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Config 1.01 qw( :standard );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::Stats;
+use Sys::Hostname;
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+## report the program and machine
+my $host = hostname();
+print "\n\n";
+print "Starting script $0 on $host\n\n";
+
+# Parse the command-line arguments
+my ( $uri, $filename, $compress, $bytes, $md5, $nebulous, $exp_name, $inst, $telescope, $class, $class_id, $end_stage, $workdir,
+     $dbname, $verbose, $no_update, $no_op, $timeout );
+GetOptions(
+       'uri=s'          => \$uri,       # source location of file on data store
+       'filename=s'     => \$filename, # target location of file on local system
+       'compress'       => \$compress,  # request file in compressed format
+       'bytes=s'        => \$bytes,     # reported file size in bytes
+       'md5=s'          => \$md5,       # reported md5 checksum
+       'nebulous'       => \$nebulous,  # use nebulous for the target file
+       'exp_name=s'     => \$exp_name,  # Exposure name
+       'inst=s'         => \$inst,      # Instrument
+       'telescope=s'    => \$telescope, # Telescope
+       'class=s'        => \$class,     # Class level
+       'class_id=s'     => \$class_id,  # Class identifier
+       'end_stage=s'    => \$end_stage, # end of processing
+       'workdir=s'      => \$workdir,   # workdir
+       'dbname=s'       => \$dbname,    # Database name
+       'verbose'        => \$verbose,   # Print to stdout
+       'no-update'      => \$no_update, # Don't update the database?
+       'no-op'          => \$no_op,     # Don't do any operations?
+       'timeout=s'      => \$timeout,   # passed through to dsget
+) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options: --uri --filename --exp_name --inst --telescope --class --class_id --workdir",
+       -exitval => 3)
+    unless defined $uri
+    and defined $filename
+    and defined $exp_name
+    and defined $inst
+    and defined $telescope
+    and defined $class
+    and defined $class_id
+    and defined $workdir;
+
+# Look for programs we need
+my $missing_tools;
+my $dsget = can_run('dsget')
+    or (warn "Can't find dsget" and $missing_tools = 1);
+my $pztool = can_run('pztool')
+    or (warn "Can't find pztool" and $missing_tools = 1);
+if ($missing_tools) {
+    warn("Can't find required tools.");
+    exit($PS_EXIT_CONFIG_ERROR);
+}
+
+# dsget command
+my $command;
+$command  = "$dsget --uri $uri --filename $filename";
+$command .= " --compress"           if defined $compress;
+$command .= " --bytes $bytes"       if defined $bytes;
+$command .= " --nebulous"           if defined $nebulous;
+$command .= " --md5 $md5"           if defined $md5;
+$command .= " --timeout $timeout"   if defined $timeout;
+
+# run command
+unless ($no_op) {
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf )
+        = run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        my_die("Unable to perform dsget: $error_code",
+            $exp_name,
+            $inst,
+            $telescope,
+            $class,
+            $class_id,
+            $uri,
+            $error_code
+        );
+    }
+} else {
+    print "skipping command: $command\n";
+}
+
+# command to update database
+$command  = "$pztool -copydone";
+$command .= " -exp_name $exp_name";
+$command .= " -inst $inst";
+$command .= " -telescope $telescope";
+$command .= " -class $class";
+$command .= " -class_id $class_id";
+$command .= " -uri $filename";
+$command .= " -workdir $workdir";
+$command .= " -end_stage $end_stage" if defined $end_stage;
+$command .= " -dbname $dbname" if defined $dbname;
+
+# update the database
+unless ($no_update) {
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf )
+        = run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        warn("Unable to perform $command: $error_code\n");
+        exit($error_code);
+    }
+} else {
+    print "skipping command: $command\n";
+}
+
+sub my_die
+{
+    my $msg       = shift; # Warning message on die
+    my $exp_name  = shift; # Chiptool identifier
+    my $inst      = shift; # Chiptool identifier
+    my $telescope = shift; # Class identifier
+    my $class     = shift; # Class identifier
+    my $class_id  = shift; # Class identifier
+    my $uri       = shift; # Class identifier
+    my $exit_code = shift; # Exit code to add
+
+    warn $msg;
+    unless ($no_update) {
+        # command to update database
+        my $command;
+        $command  = "$pztool -copydone";
+        $command .= " -exp_name $exp_name";
+        $command .= " -inst $inst";
+        $command .= " -telescope $telescope";
+        $command .= " -class $class";
+        $command .= " -class_id $class_id";
+        $command .= " -uri $uri";
+        $command .= " -code $exit_code";
+        $command .= " -dbname $dbname" if defined $dbname;
+
+        system ($command);
+    }
+
+    exit $exit_code;
+}
+
+# XXX: I don't think that we need this - JH
+#END {
+#    my $status = $?;
+#    system("sync") == 0
+#        or die "failed to execute sync: $!" ;
+#    $? = $status;
+#}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/warp_overlap.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/warp_overlap.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/warp_overlap.pl	(revision 22157)
@@ -0,0 +1,303 @@
+#!/usr/bin/env perl
+
+use Carp;
+use warnings;
+use strict;
+
+## report the program and machine
+use Sys::Hostname;
+my $host = hostname();
+print "\n\n";
+print "Starting script $0 on $host\n\n";
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::Stats;
+use PS::IPP::Metadata::List qw( parse_md_list );
+use File::Temp qw( tempfile );
+use File::Spec;
+use PS::IPP::Config 1.01 qw( :standard );
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+my ($warp_id, $camera, $tess_dir, $dbname, $verbose, $no_update, $no_op, $logfile, $save_temps);
+GetOptions(
+    'warp_id|i=s'       => \$warp_id, # Warp identifier
+    'camera|c=s'        => \$camera, # Camera name
+    'tess_dir=s'        => \$tess_dir, # Tessellation directory
+    'dbname|d=s'        => \$dbname, # Database name
+    'verbose'           => \$verbose,   # Print to stdout
+    'no-update'         => \$no_update, # Don't update the database?
+    'no-op'             => \$no_op, # Don't do any operations
+    'logfile=s'         => \$logfile,
+    'save-temps'        => \$save_temps, # Save temporary files?
+) or pod2usage( 2 );
+
+$ipprc->redirect_output($logfile) if $logfile;
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage(
+    -msg => "Required options: --warp_id --camera --tess_dir",
+    -exitval => 3,
+) unless defined $warp_id
+    and defined $camera
+    and defined $tess_dir;
+
+$ipprc->define_camera($camera);
+
+# Look for programs we need
+my $missing_tools;
+my $warptool = can_run('warptool') or (warn "Can't find warptool" and $missing_tools = 1);
+my $dvoImageOverlaps = can_run('dvoImageOverlaps') or (warn "Can't find dvoImageOverlaps" and $missing_tools = 1);
+my $ppConfigDump = can_run('ppConfigDump') or (warn "Can't find ppConfigDump" and $missing_tools = 1);
+if ($missing_tools) {
+    warn("Can't find required tools.");
+    exit($PS_EXIT_CONFIG_ERROR);
+}
+
+my $mdcParser = PS::IPP::Metadata::Config->new; # Parser for metadata config files
+
+# Get list of component imfiles for exposure
+my $imfiles;
+{
+    my $command = "$warptool -imfile -warp_id $warp_id";
+    $command .= " -dbname $dbname" if defined $dbname;
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to perform warptool -imfile: $error_code", $warp_id, $error_code);
+    }
+
+    my $metadata = $mdcParser->parse(join "", @$stdout_buf) or
+        &my_die("Unable to parse metadata config doc", $warp_id, $PS_EXIT_PROG_ERROR);
+    $imfiles = parse_md_list($metadata) or
+        &my_die("Unable to parse metadata list", $warp_id, $PS_EXIT_PROG_ERROR);
+}
+
+# Where do we get the astrometry source from? 
+my $astromSource;               # The astrometry source filerule (eg, PSASTRO.OUTPUT, PSASTRO.OUTPUT.MEF)
+my $astromAccept;               # Accept the astrometry unconditionally?
+{
+    my $command = "$ppConfigDump -camera $camera -dump-recipe PSWARP -";
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to perform ppConfigDump: $error_code", $warp_id, $error_code);
+    }
+    my $metadata = $mdcParser->parse(join "", @$stdout_buf) or
+        &my_die("Unable to parse metadata config doc", $warp_id, $PS_EXIT_PROG_ERROR);
+    $astromSource = metadataLookupStr($metadata, 'ASTROM.SOURCE');
+    $astromAccept = metadataLookupBool($metadata, 'ASTROM.ACCEPT');
+}
+
+# Determine the imfile/skycell overlaps
+my @overlaps = ();
+unless ($no_op) {
+    # Calculate the overlaps between imfiles and skycells
+
+    # tess_dir is the DVO db holding the tessalations
+    # this may be an abstract name in site.config:TESSELLATIONS, a URI, or an absolute path.
+    # convert this to an absolute path
+    my $tess_dir_abs = $ipprc->tessellation_catdir( $tess_dir ); # Tessellation catdir for DVO
+    $tess_dir_abs = $ipprc->convert_filename_absolute( $tess_dir_abs );
+
+    my %unique_skycells = (); # Identified skycells (all unique by virtue of hash property)
+
+    # astrometry is always determined at the camera stage; we have a MEF astrometry file from psastro
+    my $imfile = $imfiles->[0];
+    my $camRoot = $imfile->{cam_path_base};
+    my $astromFile = $ipprc->filename($astromSource, $camRoot); # Astrometry file
+
+    my @matchlist = get_overlaps($astromFile, $tess_dir_abs, $astromAccept); # List of overlaps
+    if (! @matchlist) {
+	&my_die("Unable to perform dvoImageOverlaps: missing astrometry", $warp_id, $PS_EXIT_DATA_ERROR);
+    }
+    # Match each of the imfiles to this list (the input images may be split, but the astrometry is not)
+    # tess_dir (not tess_dir_abs) is supplied here so it may be written to the db
+    foreach my $imfile (@$imfiles) {
+	extract_overlaps(\@matchlist, $imfile, $astromFile, $tess_dir, \@overlaps, \%unique_skycells);
+    }
+} else {
+    # create an overlap with an entry for each skycell:imfile match
+    foreach my $imfile (@$imfiles) {
+        my %overlap = ();
+        $overlap{skycell_id} = 'default';
+        $overlap{tess_dir}   = 'default';
+        $overlap{cam_id}     = $imfile->{cam_id};
+        $overlap{class_id}   = $imfile->{class_id};
+        $overlap{fault}      = $imfile->{fault};
+        push @overlaps, \%overlap;
+    }
+}
+
+# If no overlaps are found, we
+# keep running this step over and over (a successful warptool -addoverlap prevents
+# successive warptime -imfile from running.
+&my_die("Unable to find any overlaps", $warp_id, $PS_EXIT_PROG_ERROR) if scalar @overlaps == 0;
+
+# Generate a MDC file with the overlaps
+my ($overlapFile, $overlapName) = tempfile( 'overlaps.wrp' . $warp_id . '.mdc.XXXX', UNLINK => !$save_temps );
+print $overlapFile "warpSkyCellMap MULTI\n\n";
+foreach my $overlap (@overlaps) {
+    print $overlapFile "warpSkyCellMap   METADATA\n";
+    print $overlapFile "  warp_id        S32    $warp_id\n";
+    print $overlapFile "  skycell_id     STR    $overlap->{skycell_id}\n";
+    # XXX convert tess_id here to tess_dir when db scheme is updated
+    print $overlapFile "  tess_id        STR    $overlap->{tess_dir}\n"; 
+    print $overlapFile "  cam_id         S32    $overlap->{cam_id}\n";
+    print $overlapFile "  class_id       STR    $overlap->{class_id}\n";
+    print $overlapFile "  fault          S16    $overlap->{fault}\n";
+    print $overlapFile "END\n\n";
+}
+close $overlapFile;
+
+system "cat $overlapName" if $verbose;
+
+# Add the processed file to the database
+unless ($no_update) {
+    my $command = "$warptool -addoverlap -mapfile $overlapName"; # Command to run warptool
+    $command .= " -dbname $dbname" if defined $dbname;
+
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        warn("Unable to perform warptool -addoverlap: $error_code\n");
+        exit($error_code);
+    }
+}
+
+sub my_die
+{
+    my $msg = shift;            # Warning message on die
+    my $warp_id = shift;        # Warp identifier
+    my $exit_code = shift;      # Exit code to add
+
+    carp($msg);
+    if (defined $warp_id and not $no_update) {
+        my $command = "$warptool -addoverlap";
+        $command .= " -warp_id $warp_id";
+        $command .= " -code $exit_code";
+        $command .= " -dbname $dbname" if defined $dbname;
+        system ($command);
+    }
+    exit $exit_code;
+}
+
+# Run dvoImageOverlaps to get the overlaps; return the output
+sub get_overlaps
+{
+    my $filename = shift;       # Filename on which to run dvoImageOverlaps
+    my $tess_dir_abs = shift;       # Tessellation directory
+    my $accept = shift;         # Do we use the -accept-astrom flag?
+
+    my $command = "$dvoImageOverlaps -D CATDIR $tess_dir_abs " . $ipprc->file_resolve($filename);
+    $command .= ' -accept-astrom' if $accept;
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    if (!$success) {
+        print "Unable to calculate overlaps from $filename\n";
+        print "STDOUT:\n";
+        foreach my $line (@$stdout_buf) {
+            print ">>>$line";
+        }
+        print "STDERR:\n";
+        foreach my $line (@$stderr_buf) {
+            print ">>>$line";
+        }
+        return 0;
+    }
+    return split ('\n', (join "", @$stdout_buf));
+}
+
+# Extract a list of overlaps for an imfile
+#
+# The command "dvoImageOverlaps -accept-astrom -D CATDIR tessellation (megacam)" returns:
+# 729534pa.cmf[ccd00.hdr]  :  skycell.051.fits
+# PSASTRO.OUTPUT[CMF.HEAD] : SKYCELL
+# [CMF.HEAD] is optionally used for MEF files (SIMPLE or MOSAIC)
+sub extract_overlaps
+{
+    my $matches = shift;        # Reference to list of skycells from dvoImageOverlaps
+    my $imfile = shift;         # Imfile information
+    my $filename = shift;       # Filename used with dvoImageOverlaps
+    my $tess_dir = shift;        # Tessellation identifier
+    my $overlaps = shift;       # Reference to list of overlaps
+    my $unique_skycells = shift; # Reference to hash of found skycells
+
+    # Get rid of the path
+    my @dirlist = File::Spec->splitdir( $filename ); # The elements of the full path
+    $filename = pop @dirlist;
+
+    # Work out how to identify this imfile in the output
+    my $fileLevel = $imfile->{filelevel};
+    my $entry;  # How to identify this imfile in the dvoImageOverlaps output
+    if (lc($fileLevel) eq "chip") {
+        my $class_id = $imfile->{class_id};
+        my $chipRoot = $ipprc->file_resolve( $imfile->{chip_path_base} );
+        my $extname = $ipprc->extname_rule("CMF.HEAD", $class_id); # MEF psastro output
+
+        $entry = $filename . '\[' . $extname . '\]';
+        print STDERR "entry: $entry, class: $class_id, extname: $extname, chiproot: $chipRoot\n" if $verbose;
+    } else {
+        $entry = $filename;
+        print STDERR "entry: $entry\n" if $verbose;
+    }
+
+    my @skycells = &select_skycells($entry, @$matches); # Matching skycells
+    my $Nskycells = @skycells;
+    printf STDERR "Nskycells: $Nskycells\n" if $verbose;
+    foreach my $skycell (@skycells) {
+        my %overlap = ();       # Overlap information for warptool
+        $overlap{skycell_id} = $skycell;
+        $overlap{tess_dir}   = $tess_dir;
+        $overlap{cam_id}     = $imfile->{cam_id};
+        $overlap{class_id}   = $imfile->{class_id};
+        $overlap{fault}      = $imfile->{fault};
+        push @$overlaps, \%overlap;
+
+        printf STDERR "overlap: %s : %s , %s\n", $skycell, $imfile->{cam_id}, $imfile->{class_id} if $verbose;
+
+        $unique_skycells->{$skycell} = 1;
+    }
+
+    return;
+}
+
+# Find skycells in the list that come from a particular entry
+sub select_skycells
+{
+    my $entry = shift;          # File+Ext to search for
+    my @list = @_;              # List of "File+Ext : skycell"
+
+    my @skycells = ();
+    my %unique = ();            # Ensure we only return unique skycells for this entry
+
+    foreach my $line (@list) {
+        if ($line =~ m|$entry|) {
+            my ($skycell) = $line =~ m|$entry\S*\s+:\s+(\S+)|;
+            if (not defined $unique{$skycell}) {
+                push @skycells, $skycell;
+                $unique{$skycell} = 1;
+            }
+        }
+    }
+    return @skycells;
+}
+
+END {
+    my $status = $?;
+    system("sync") == 0
+        or die "failed to execute sync: $!" ;
+    $? = $status;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/warp_skycell.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/warp_skycell.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/scripts/warp_skycell.pl	(revision 22157)
@@ -0,0 +1,266 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+## report the program and machine
+use Sys::Hostname;
+my $host = hostname();
+print "\n\n";
+print "Starting script $0 on $host\n\n";
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use IPC::Cmd 0.36 qw( can_run run );
+use File::Spec;
+use File::Temp qw( tempfile );
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::Stats;
+use PS::IPP::Metadata::List qw( parse_md_list );
+use PS::IPP::Config 1.01 qw( :standard );
+
+my $ipprc = PS::IPP::Config->new(); # IPP configuration
+
+my ($warp_id, $skycell_id, $tess_dir, $camera, $dbname, $outroot, $verbose, $no_update, $no_op, $redirect, $save_temps);
+GetOptions(
+    'warp_id|i=s'       => \$warp_id, # Warp identifier
+    'skycell_id|s=s'    => \$skycell_id, # Skycell identifier
+    'tess_dir|s=s'       => \$tess_dir, # Tesselation identifier
+    'camera|c=s'        => \$camera, # Camera name
+    'dbname|d=s'        => \$dbname, # Database name
+    'outroot=s'         => \$outroot, # Output root name
+    'verbose'           => \$verbose,   # Print to stdout
+    'no-update'         => \$no_update, # Don't update the database?
+    'no-op'             => \$no_op, # Don't do any operations?
+    'redirect-output'   => \$redirect,
+    'save-temps'        => \$save_temps, # Save temporary files?
+) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage(
+    -msg => "Required options: --warp_id --skycell_id --tess_dir --camera --outroot",
+    -exitval => 3,
+) unless defined $warp_id
+    and defined $skycell_id
+    and defined $tess_dir
+    and defined $camera
+    and defined $outroot;
+
+$ipprc->define_camera($camera);
+
+my $logDest = $ipprc->filename("TRACE.EXP", $outroot, $skycell_id);
+
+$ipprc->redirect_output($logDest) if $redirect;
+
+my $STATS =
+   [
+       #          PPSTATS KEYWORD         STATISTIC          WARPTOOL FLAG
+       { name => "ROBUST_MEDIAN",   type => "mean", flag => "-bg",         dtype => "float" },
+       { name => "ROBUST_STDEV",    type => "rms",  flag => "-bg_stdev",   dtype => "float" },
+       { name => "DT_WARP",         type => "sum",  flag => "-dtime_warp", dtype => "float" },
+       { name => "GOOD_PIXEL_FRAC", type => "mean", flag => "-good_frac",  dtype => "float" },
+       { name => "RANGE.XMIN",      type => "mean", flag => "-xmin",       dtype => "int"   },
+       { name => "RANGE.XMAX",      type => "mean", flag => "-xmax",       dtype => "int"   },
+       { name => "RANGE.YMIN",      type => "mean", flag => "-ymin",       dtype => "int"   },
+       { name => "RANGE.YMAX",      type => "mean", flag => "-ymax",       dtype => "int"   },
+   ];
+my $stats = PS::IPP::Metadata::Stats->new($STATS); # Stats parser
+
+# Look for programs we need
+my $missing_tools;
+my $warptool = can_run('warptool') or (warn "Can't find warptool" and $missing_tools = 1);
+my $pswarp = can_run('pswarp') or (warn "Can't find pswarp" and $missing_tools = 1);
+my $ppConfigDump = can_run('ppConfigDump') or (warn "Can't find ppConfigDump" and $missing_tools = 1);
+if ($missing_tools) {
+    warn("Can't find required tools.");
+    exit($PS_EXIT_CONFIG_ERROR);
+}
+
+# Get list of component imfiles for exposure
+my $mdcParser = PS::IPP::Metadata::Config->new; # Parser for metadata config files
+my $imfiles;
+{
+    # XXX change -tess_id to -tess_dir when db schema is updated
+    my $command = "$warptool -scmap -warp_id $warp_id -skycell_id $skycell_id -tess_id $tess_dir";
+    $command .= " -dbname $dbname" if defined $dbname;
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to perform warptool -scmap: $error_code", $warp_id, $skycell_id, $tess_dir, $error_code);
+    }
+
+    my $metadata = $mdcParser->parse(join "", @$stdout_buf) or
+        &my_die("Unable to parse metadata config doc", $warp_id, $skycell_id, $tess_dir, $PS_EXIT_PROG_ERROR);
+    $imfiles = parse_md_list($metadata) or
+        &my_die("Unable to parse metadata list", $warp_id, $skycell_id, $tess_dir, $PS_EXIT_PROG_ERROR);
+}
+
+# Where do we get the astrometry source from?
+my $astromSource;               # The astrometry source
+{
+    my $command = "$ppConfigDump -camera $camera -dump-recipe PSWARP -";
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to perform ppConfigDump: $error_code", $warp_id, $error_code);
+    }
+    my $metadata = $mdcParser->parse(join "", @$stdout_buf) or
+        &my_die("Unable to parse metadata config doc", $warp_id, $PS_EXIT_PROG_ERROR);
+    $astromSource = metadataLookupStr($metadata, 'ASTROM.SOURCE');
+}
+
+my $outputImage = $ipprc->filename("PSWARP.OUTPUT", $outroot, $skycell_id );
+my $outputMask = $ipprc->filename("PSWARP.OUTPUT.MASK", $outroot, $skycell_id);
+my $outputWeight = $ipprc->filename("PSWARP.OUTPUT.WEIGHT", $outroot, $skycell_id);
+my $outputSources = $ipprc->filename("PSWARP.OUTPUT.SOURCES", $outroot, $skycell_id);
+my $outputPSF = $ipprc->filename("PSPHOT.PSF.SAVE", $outroot, $skycell_id);
+my $outputBin1 = $ipprc->filename("PSWARP.BIN1", $outroot, $skycell_id );
+my $outputBin2 = $ipprc->filename("PSWARP.BIN2", $outroot, $skycell_id );
+my $outputStats = $ipprc->filename("SKYCELL.STATS", $outroot, $skycell_id );
+my $traceDest = $ipprc->filename("TRACE.EXP", $outroot, $skycell_id);
+
+my $skyFile = $ipprc->filename("SKYCELL.TEMPLATE", $outroot, $skycell_id );
+$ipprc->skycell_file( $tess_dir, $skycell_id, $skyFile, $verbose ) or &my_die("Unable to generate template skycell", $warp_id, $skycell_id, $tess_dir, $PS_EXIT_SYS_ERROR);
+
+# Get list of filenames
+my $outrootResolved = $ipprc->file_resolve( $outroot );
+my ($imageFile,  $imageName)  = tempfile( "$outrootResolved.image.list.XXXX",  UNLINK => !$save_temps);
+my ($maskFile,   $maskName)   = tempfile( "$outrootResolved.mask.list.XXXX",   UNLINK => !$save_temps);
+my ($weightFile, $weightName) = tempfile( "$outrootResolved.weight.list.XXXX", UNLINK => !$save_temps);
+my ($astromFile, $astromName) = tempfile( "$outrootResolved.astrom.list.XXXX", UNLINK => !$save_temps);
+
+my $wrote_astrom = 0;
+foreach my $imfile (@$imfiles) {
+    my $image = $imfile->{uri}; # Image name
+    my $mask = $ipprc->filename("PPIMAGE.CHIP.MASK", $imfile->{chip_path_base}, $imfile->{class_id}); # Mask name
+    my $weight = $ipprc->filename("PPIMAGE.CHIP.WEIGHT", $imfile->{chip_path_base}, $imfile->{class_id}); # Mask name
+
+    &my_die("Couldn't find input file: $image", $warp_id, $skycell_id, $tess_dir, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($image);
+    &my_die("Couldn't find input file: $mask", $warp_id, $skycell_id, $tess_dir, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($mask);
+
+    my $astrom;         # Astrometry file
+    if ($astromSource eq 'PSASTRO.OUTPUT.MEF') {
+        $astrom = $ipprc->filename($astromSource, $imfile->{cam_path_base});
+    } elsif ($astromSource eq 'PSASTRO.OUTPUT') {
+        my $chipRoot = $imfile->{chip_path_base};
+        my $classID = $imfile->{class_id};
+        $astrom = $ipprc->filename($astromSource, $chipRoot, $classID); # Astrometry file
+    }
+
+    &my_die("Couldn't find input file: $astrom", $warp_id, $skycell_id, $tess_dir, $PS_EXIT_SYS_ERROR) unless defined $astrom and $ipprc->file_exists($astrom);
+
+    print $imageFile  "$image\n";
+    print $maskFile   "$mask\n";
+    print $weightFile "$weight\n";
+
+    if (!$wrote_astrom) {
+        print $astromFile "$astrom\n";
+        # XXX simplify this?  only ever have one file for astrometry (must be MEF)
+        if ($astromSource eq 'PSASTRO.OUTPUT.MEF') {
+            $wrote_astrom = 1;
+        }
+    }
+}
+close $imageFile;
+close $maskFile;
+close $weightFile;
+close $astromFile;
+
+
+# Run pswarp
+my $accept = 1;                 # Accept the skycell?
+unless ($no_op) {
+    my $command = "$pswarp";
+    $command .= " -list $imageName";
+    $command .= " -masklist $maskName";
+    $command .= " -weightlist $weightName";
+    $command .= " -astromlist $astromName";
+    $command .= " $outroot $skyFile";
+    $command .= " -stats $outputStats";
+    $command .= " -recipe PPSTATS WARPSTATS";
+    $command .= " -psf";        # Turn on PSF determination
+    $command .= " -tracedest $traceDest -log $logDest";
+    $command .= " -dbname $dbname" if defined $dbname;
+
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        &my_die("Unable to perform pswarp: $error_code", $warp_id, $skycell_id, $tess_dir, $error_code);
+    }
+
+    # Check first for the stats file, and if the ACCEPT flag is set.
+    &my_die("Couldn't find expected output file: $outputStats", $warp_id, $skycell_id, $tess_dir, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($outputStats);
+    # Get the statistics on the warped image
+    my $statsFile;              # File handle
+    open $statsFile, $ipprc->file_resolve($outputStats) or die "Can't open statistics file $outputStats: $!\n";
+    my @contents = <$statsFile>; # Contents of file
+    close $statsFile;
+    my $contents = join "", @contents;
+
+    my $metadata = $mdcParser->parse($contents)
+        or &my_die("Unable to parse metadata config", $warp_id, $skycell_id, $tess_dir, $PS_EXIT_PROG_ERROR);
+    $accept = metadataLookupBool($metadata, "ACCEPT");
+    if ($accept) {
+        $stats->parse($metadata) or &my_die("Unable to find all values in statistics output.", $warp_id, $skycell_id, $tess_dir, $PS_EXIT_PROG_ERROR);
+
+        &my_die("Couldn't find expected output file: $outputImage", $warp_id, $skycell_id, $tess_dir, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($outputImage);
+        &my_die("Couldn't find expected output file: $outputMask", $warp_id, $skycell_id, $tess_dir, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($outputMask);
+        &my_die("Couldn't find expected output file: $outputWeight", $warp_id, $skycell_id, $tess_dir, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($outputWeight);
+        &my_die("Couldn't find expected output file: $outputSources", $warp_id, $skycell_id, $tess_dir, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($outputSources);
+        &my_die("Couldn't find expected output file: $outputPSF", $warp_id, $skycell_id, $tess_dir, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($outputPSF);
+#    &my_die("Couldn't find expected output file: $outputBin1", $warp_id, $skycell_id, $tess_dir, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($outputBin1);
+#    &my_die("Couldn't find expected output file: $outputBin2", $warp_id, $skycell_id, $tess_dir, $PS_EXIT_SYS_ERROR) unless $ipprc->file_exists($outputBin2);
+    }
+}
+
+unless ($no_update) {
+    # XXX change -tess_id to -tess_dir when db is updated
+    my $command = "$warptool -addwarped -warp_id $warp_id -skycell_id $skycell_id -tess_id $tess_dir";
+    $command .= " -ignore" if not $accept; # Completed succesfully, but can't produce product
+    $command .= " -uri $outputImage -path_base $outroot" if $accept;
+    $command .= $stats->cmdflags() if $accept;
+    $command .= " -hostname $host" if defined $host;
+    $command .= " -dbname $dbname" if defined $dbname;
+
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+        run(command => $command, verbose => $verbose);
+    unless ($success) {
+        $error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+        warn("Unable to perform warptool -addwarped: $error_code\n");
+        exit($error_code);
+    }
+}
+
+sub my_die
+{
+    my $msg = shift;            # Warning message on die
+    my $warp_id = shift;        # Warp identifier
+    my $skycell_id = shift;     # Skycell identifier
+    my $tess_dir = shift;        # Tesselation identifier
+    my $exit_code = shift;      # Exit code to add
+
+    warn($msg);
+    if (defined $warp_id and defined $skycell_id and defined $tess_dir and not $no_update) {
+	# XXX change -tess_id to -tess_dir when db is updated
+        my $command = "$warptool -addwarped -warp_id $warp_id -skycell_id $skycell_id -tess_id $tess_dir -code $exit_code";
+        $command .= " -hostname $host" if defined $host;
+        $command .= " -dbname $dbname" if defined $dbname;
+        run(command => $command, verbose => $verbose);
+    }
+    exit $exit_code;
+}
+
+END {
+    my $status = $?;
+    system("sync") == 0
+        or die "failed to execute sync: $!" ;
+    $? = $status;
+}
+
+__END__
Index: /branches/eam_branches/eam_branch_20080706/ippScripts/t/00_distribution.t
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippScripts/t/00_distribution.t	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippScripts/t/00_distribution.t	(revision 22157)
@@ -0,0 +1,25 @@
+#!/usr/bin/env perl
+
+# Copyright (C) 2005  Joshua Hoblitt
+#
+# $Id: 00_distribution.t,v 1.1 2007-01-30 01:52:16 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use lib qw( ./lib ./t );
+
+use Test::More;
+
+# example taken from Test::Distribution Pod
+
+BEGIN {
+    eval {
+        require Test::Distribution;
+    };
+    if($@) {
+        plan skip_all => 'Test::Distribution not installed';
+    } else {
+        import Test::Distribution; # not => qw( podcover );
+    }
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/.cvsignore	(revision 22157)
@@ -0,0 +1,9 @@
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+config.log
+config.status
+configure
+install-sh
+missing
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/Makefile.am	(revision 22157)
@@ -0,0 +1,42 @@
+task_files = \
+	detrend.mkruns.pro \
+	detrend.norm.pro \
+	detrend.process.pro \
+	detrend.reject.pro \
+	detrend.resid.pro \
+	detrend.stack.pro \
+	automate.pro \
+	pantasks.pro \
+	register.pro \
+	chip.pro \
+	camera.pro \
+	fake.pro \
+	warp.pro \
+	magic.pro \
+	diff.pro \
+	stack.pro \
+	summit.copy.pro \
+	replicate.pro \
+	pstamp.pro
+
+other_files = \
+	chiphosts.manoa.config \
+	chiphosts.mhpcc.config \
+	simtest.basic.config \
+	site.manoa.pro \
+	site.mhpcc.pro \
+	simtest.pro \
+	simtest.auto \
+	simtest.det \
+	isp.det
+
+pantasksdir = $(datadir)/pantasks
+ipptasksdir = $(pantasksdir)/modules
+ipptasks_DATA = $(task_files) $(other_files)
+
+install-data-hook:
+	chmod 0755 $(panstasks) $(ipptasksdir)
+
+EXTRA_DIST = $(task_files) $(other_files)
+
+ACLOCAL_AMFLAGS = -I m4
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/autogen.sh
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/autogen.sh	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/autogen.sh	(revision 22157)
@@ -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=ippTasks
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL="aclocal $ACLOCAL_FLAGS"
+AUTOHEADER=autoheader
+AUTOMAKE=automake
+AUTOCONF=autoconf
+
+#($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+#        echo
+#        echo "You must have $LIBTOOlIZE installed to compile $PROJECT."
+#        echo "Download the appropriate package for your distribution,"
+#        echo "or get the source tarball at http://ftp.gnu.org/gnu/libtool/"
+#        DIE=1
+#}
+
+($ACLOCAL --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $ACLOCAL installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+#($AUTOHEADER --version) < /dev/null > /dev/null 2>&1 || {
+#        echo
+#        echo "You must have $AUTOHEADER installed to compile $PROJECT."
+#        echo "Download the appropriate package for your distribution,"
+#        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+#        DIE=1
+#}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOMAKE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOCONF installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+if test "$DIE" -eq 1; then
+        exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+        echo "You must run this script in the top-level $PROJECT directory"
+        exit 1
+}
+
+if test -z "$*"; then
+        echo "I am going to run ./configure with no arguments - if you wish "
+        echo "to pass any to it, please specify them on the $0 command line."
+fi
+
+#$LIBTOOLIZE --copy --force || echo "$LIBTOOlIZE failed"
+$ACLOCAL || echo "$ACLOCAL failed"
+#$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/automate.pro
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/automate.pro	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/automate.pro	(revision 22157)
@@ -0,0 +1,336 @@
+## automate.pro : autorun the detrend analysis : -*- sh -*-
+
+# example automation stage
+# automate METADATA
+#  name       STR DARK
+#  check      STR "detselect -search -inst SIMTEST -det_type BIAS -dbname eamtest"
+#  ncheck     S32 1
+#  launch     STR "dettool -definebyquery -workdir path://EAMWORK -inst SIMTEST -det_type DARK -select_exp_type DARK -dbname eamtest"
+#  block      STR "dettool -runs -active -det_type DARK -dbname eamtest"
+# END
+
+# an entry in the 'automate' book goes through the following states:
+# INIT (book loaded from input file)
+# CHECK (stage prereqs being checked)
+# READY (prereqs satisfield, ready to be launched)
+# LAUNCH (action being performed)
+# DONE (action finished)
+
+macro init.automate
+  ## probably should not always init
+  book init automate
+end
+
+macro load.automate
+  if ($0 != 3)
+    echo "USAGE: load.automate (filename) (dbname)"
+    break
+  end
+  queueload tmp -x "cat $MODULES:0/$1"
+
+  pwd -var cwd
+
+  ## interpolate standard values
+  queuesubstr tmp @DBNAME@ $2
+  queuesubstr tmp @CWD@ $cwd
+
+  ipptool2book tmp automate -key name -uniq -setword pantaskState INIT.BLOCK
+  queuedelete tmp
+end
+
+macro automate.on
+  task automate.block
+    active true
+  end
+  task automate.check
+    active true
+  end
+  task automate.launch
+    active true
+  end
+end
+
+macro automate.off
+  task automate.block
+    active false
+  end
+  task automate.check
+    active false
+  end
+  task automate.launch
+    active false
+  end
+end
+
+$automate_Nblock = -1
+task automate.block
+  host         local
+
+  periods      -poll 1
+  periods      -exec 5
+  periods      -timeout 30
+  active       true
+  npending     1
+
+  task.exec
+    local Npage pageName
+
+    # how many pages are waiting to be started?
+    book npages automate -var Npage -key pantaskState INIT.BLOCK
+    if ($Npage == 0) 
+      if ($VERBOSE >= 2) 
+        echo "no entries in INIT.BLOCK state"
+      end
+      break
+    end
+
+    # cycle over the number of INIT.BLOCK pages: no point to loop over the others
+    $automate_Nblock ++
+    if ($automate_Nblock >= $Npage) set automate_Nblock = 0
+
+    # search the automate book for an entry which is unstarted (state INIT.BLOCK) 
+    book getpage automate $automate_Nblock -var pageName -key pantaskState INIT.BLOCK
+    if ("$pageName" == "NULL") 
+      if ($VERBOSE >= 2) 
+        echo "entry $automate_Nblock not in INIT.BLOCK state"
+      end
+      break 
+    end
+ 
+    book getword automate $pageName block -var blockCommand
+    if (("$blockCommand" == "NULL") || ("$blockCommand" == "NONE"))
+      # if there is no block needed, we can immediate progress to the next stage (INIT.CHECK)
+      book setword automate $pageName pantaskState INIT.CHECK
+      if ($VERBOSE >= 2) 
+        echo "$pageName is ready : INIT.CHECK"
+      end
+      break
+    end
+
+    book setword automate $pageName pantaskState RUN.BLOCK
+
+    if ($VERBOSE >= 2)
+      echo "starting automate block for $pageName"
+      echo "command $blockCommand"
+    end
+
+    options $pageName
+    command $blockCommand
+  end
+
+  task.exit $EXIT_SUCCESS
+    local pageName Npage
+
+    $pageName = $options:0
+
+    # convert 'stdout' to book format
+    # XXX to use other tests, we'll need to modify this 
+    ipptool2book stdout tmpBlock
+    if ($VERBOSE > 2)
+      book listbook tmpBlock
+    end
+
+    # if the block test returns any valid pages (valid results), the block is set, don't move to check
+    book npages tmpBlock -var Npage 
+    if ($Npage == 0) 
+      if ($VERBOSE >= 2)
+        echo "$pageName is not blocked, ready for CHECK"
+      end
+      book setword automate $pageName pantaskState INIT.CHECK
+    else
+      if ($VERBOSE >= 2)
+        echo "$pageName is blocked, not ready for CHECK"
+      end
+      book setword automate $pageName pantaskState INIT.BLOCK
+    end
+
+    # drop the detExt book after we've grabbed the state
+    book delete tmpBlock
+  end
+
+  # all failures here (what state?)
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
+$automate_Ncheck = -1
+task automate.check
+  host         local
+
+  periods      -poll 1
+  periods      -exec 5
+  periods      -timeout 30
+  active       true
+  npending     1
+
+  task.exec
+    local Npage pageName
+
+    # how many pages are waiting to be started?
+    book npages automate -var Npage -key pantaskState INIT.CHECK
+    if ($Npage == 0) 
+      if ($VERBOSE >= 2) 
+        echo "no entries in INIT.CHECK state"
+      end
+      break
+    end
+
+    # cycle over the number of INIT.CHECK pages: no point to loop over the others
+    $automate_Ncheck ++
+    if ($automate_Ncheck >= $Npage) set automate_Ncheck = 0
+
+    # search the automate book for an entry which is unstarted (state INIT.CHECK) 
+    book getpage automate $automate_Ncheck -var pageName -key pantaskState INIT.CHECK
+    if ("$pageName" == "NULL") 
+      if ($VERBOSE >= 2) 
+        echo "entry $automate_Ncheck not in INIT.CHECK state"
+      end
+      break 
+    end
+ 
+    book getword automate $pageName check -var checkCommand
+    if (("$checkCommand" == "NULL") || ("$checkCommand" == "NONE"))
+      # if there is no check needed, we can immediate progress to the next stage (INIT.LAUNCH)
+      book setword automate $pageName pantaskState INIT.LAUNCH
+      if ($VERBOSE >= 2) 
+        echo "$pageName is ready : INIT.LAUNCH"
+      end
+      break
+    end
+
+    book setword automate $pageName pantaskState RUN.CHECK
+
+    if ($VERBOSE >= 2)
+      echo "starting automate check for $pageName"
+      echo "command $checkCommand"
+    end
+
+    options $pageName
+    command $checkCommand
+  end
+
+  task.exit $EXIT_SUCCESS
+    local pageName Npage
+
+    $pageName = $options:0
+
+    # convert 'stdout' to book format
+    # XXX to use other tests, we'll need to modify this 
+    ipptool2book stdout tmpCheck
+    if ($VERBOSE > 2)
+      book listbook tmpCheck
+    end
+
+    book getword automate $pageName ncheck -var Ncheck
+    if ("$Ncheck" == "NULL")
+      $Ncheck = 1
+    end 
+    book npages tmpCheck -var Npage 
+    if ($Npage < $Ncheck) 
+      if ($VERBOSE >= 2) 
+        echo "$pageName not ready for LAUNCH"
+      end
+      book setword automate $pageName pantaskState INIT.CHECK
+    else
+      if ($VERBOSE >= 2) 
+        echo "$pageName is ready for LAUNCH"
+      end
+      book setword automate $pageName pantaskState INIT.LAUNCH
+    end
+
+    # drop the detExt book after we've grabbed the state
+    book delete tmpCheck
+  end
+
+  # all failures here (what state?)
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
+$automate_Nlaunch = 0
+task automate.launch
+  host         local
+
+  periods      -poll 1
+  periods      -exec 5
+  periods      -timeout 30
+  active       true
+  npending     1
+
+  task.exec
+    local Npage pageName
+
+    # how many pages are waiting to be started?
+    book npages automate -var Npage -key pantaskState INIT.LAUNCH
+    if ($Npage == 0) 
+      if ($VERBOSE >= 2)
+        echo "no entries in INIT.LAUNCH state"
+      end
+      break
+    end
+
+    # cycle over the number of INIT.LAUNCH pages: no point to loop over the others
+    $automate_Nlaunch ++
+    if ($automate_Nlaunch >= $Npage) set automate_Nlaunch = 0
+
+    # search the automate book for an entry which is unstarted (state INIT.LAUNCH) 
+    book getpage automate $automate_Nlaunch -var pageName -key pantaskState INIT.LAUNCH
+    if ("$pageName" == "NULL") 
+      if ($VERBOSE >= 2)
+        echo "entry $automate_Nlaunch not in INIT.LAUNCH state"
+      end
+      break 
+    end
+ 
+    book getword automate $pageName launch -var launchCommand
+    if (("$launchCommand" == "NULL") || ("$launchCommand" == "NONE"))
+      # if there is no launch needed, we can immediate progress to the next stage (INIT.LAUNCH)
+      book setword automate $pageName pantaskState INIT.LAUNCH
+      if ($VERBOSE >= 2)
+        echo "$pageName is ready : INIT.LAUNCH"
+      end
+      break
+    end
+
+    book setword automate $pageName pantaskState RUN.LAUNCH
+
+    if ($VERBOSE >= 2)
+      echo "starting automate launch for $pageName"
+      echo "command $launchCommand"
+    end
+
+    options $pageName
+    command $launchCommand
+  end
+
+  task.exit $EXIT_SUCCESS
+    local pageName Npage
+
+    $pageName = $options:0
+
+    book setword automate $pageName pantaskState DONE.LAUNCH
+  end
+
+  # all failures here (what state?)
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/calibration.pro
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/calibration.pro	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/calibration.pro	(revision 22157)
@@ -0,0 +1,173 @@
+## calibration.pro : globals and support macros : -*- sh -*-
+## this file contains the tasks for running the calibration stage
+## these tasks use the book 'calBook'
+
+# test for required global variables
+check.globals
+
+$LOGSUBDIR = $LOGDIR/calibration
+exec mkdir -p $LOGSUBDIR
+
+book init calBook
+
+macro calibration.reset
+  book init calBook
+end
+
+macro calibration.status
+  book listbook calBook
+end
+
+macro calibration.on
+  task calibration.init
+    active true
+  end
+end
+
+macro calibration.off
+  task calibration.init
+    active false
+  end
+end
+
+# these variables will cycle through the known ippdb database names
+$calInit_DB = 0
+
+# we have three steps here:
+# 1) get the list of dvo databases:  caltool -dbs
+# 2) define a new calibration run for each of the dvo dbs
+#    caltool 
+
+# create new calibration entries for the currently known DVO databases
+# run this multiple times once an hour - on pass for each db
+task	       calibration.load
+  host         local
+
+  # check the list of available dvo dbs regularly
+  periods      -poll 10
+  periods      -exec 900
+  periods      -timeout 60
+
+  # this is a strange construction; what is the purpose?
+  if ($DB:n == 0) 
+    npending $DB:n
+  else
+    npending 1
+  end
+
+  # define the command (does not depend on previous queries)
+  if ($DB:n == 0)
+    command caltool -dbs -active true
+  else
+    # save the DB name for the exit tasks
+    # note that this DB name refers to the ippdb, not the dvodb
+    option $DB:$calInit_DB
+    command caltool -dbs -dbname $DB:$calInit_DB
+    $calInit_DB ++
+    if ($calInit_DB >= $DB:n) set calInit_DB = 0
+  end
+
+  # silently drop stdout
+  stdout NULL
+  stderr $LOGSUBDIR/calibration.log
+
+  # success
+  task.exit $EXIT_SUCCESS
+    # convert 'stdout' to book format
+    # XXX have ippTools report the dbname?
+    ipptool2book stdout calBook -key dbname:cal_id -uniq -setword dbname $options:0 -setword pantaskState INIT
+    if ($VERBOSE > 2)
+      book listbook calBook
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup calBook
+  end
+
+  # locked list
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
+# create new calibration entries for the currently known DVO databases
+# run this multiple times once an hour - one pass for each db
+# can we include information that the db has been updated without a recent cal analysis?
+task	       calibration.resort
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 60
+  # trange     Hourly@00:00 Hourly@00:10
+  # every hour on the hour
+  npending     1
+
+  # silently drop stdout
+  stdout NULL
+  stderr $LOGSUBDIR/calibration.log
+
+  task.exec
+    book npages calBook -var N
+    if ($N == 0) break
+    if ($NETWORK == 0) break
+    
+    # look for new images in calBook
+    # the sequencing in this task set is by the pantasksState (see notes.txt)
+    book getpage calBook 0 -var pageName -key pantaskState INIT
+    if ("$pageName" == "NULL") break
+
+    # get the current ST: 
+    $RAs = 15*($ST - 1)
+    $RAe = 15*($ST - 2)
+    $DECs = -90.0
+    $DECe = +90.0
+
+    $REGION = "$RAs,$RAe:$DECs,$DECe"
+
+    book setword calBook $pageName pantaskState     RUN
+    book setword calBook $pageName region           $REGION
+
+    # XXX probably need to set the output / log based on WORKDIR...
+    book getword calBook $pageName cal_id      -var ID
+    book getword calBook $pageName dvodb       -var DVODB
+    book getword calBook $pageName dbname      -var DBNAME
+
+    # specify choice of remote host
+    # set a specific DVO host here 
+    if ($PARALLEL)
+      host anyhost
+    else
+      host local
+    end
+
+    # save the pageName for future reference below
+    options $pageName
+
+    # XXX do we modify relphot to loop over the filters?
+    $run = calibrate_dvo.pl --cal_id $ID --dvodb $DVODB --region $REGION
+    add_standard_args run
+
+    # create the command line
+    if ($VERBOSE > 1)
+      echo command $run
+    end
+    command $run
+  end
+
+  # default exit status
+  task.exit default
+    process_exit calBook $options:0 $JOB_STATUS
+  end
+
+  # operation timed out?
+  task.exit    timeout
+    showcommand timeout
+    book setword calBook $options:0 pantaskState TIMEOUT
+  end
+end
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/camera.pro
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/camera.pro	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/camera.pro	(revision 22157)
@@ -0,0 +1,166 @@
+## camera.pro : globals and support macros : -*- sh -*-
+## this file contains the tasks for running the camera analysis stage
+## these tasks use the book camPendingExp
+
+# test for required global variables
+check.globals
+
+book init camPendingExp
+
+macro camera.status
+  book listbook camPendingExp
+end
+
+macro camera.reset
+  book init camPendingExp
+end
+
+macro camera.on
+  task camera.exp.load
+    active true
+  end
+  task camera.exp.run
+    active true
+  end
+end
+
+macro camera.off
+  task camera.exp.load
+    active false
+  end
+  task camera.exp.run
+    active false
+  end
+end
+
+# this variable will cycle through the known database names
+$camera_DB = 0
+
+# select images ready for camera analysis
+# new entries are added to camPendingExp
+# skip already-present entries
+task	       camera.exp.load
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 30
+  npending     1
+
+  stdout NULL
+  stderr $LOGDIR/camera.exp.log
+
+  task.exec
+    if ($DB:n == 0)
+      option DEFAULT
+      command camtool -pendingexp -limit 20
+      # echo command camtool -pendingexp -limit 20
+    else
+      # save the DB name for the exit tasks
+      option $DB:$camera_DB
+      command camtool -pendingexp -limit 20 -dbname $DB:$camera_DB
+      # echo command camtool -pendingexp -limit 20 -dbname $DB:$camera_DB
+      $camera_DB ++
+      if ($camera_DB >= $DB:n) set camera_DB = 0
+    end
+  end
+
+  # success
+  task.exit    0
+    # convert 'stdout' to book format
+    ipptool2book stdout camPendingExp -key cam_id -uniq -setword dbname $options:0 -setword pantaskState INIT
+    if ($VERBOSE > 2)
+      book listbook camPendingExp
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup camPendingExp
+  end
+
+  # default exit status
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
+# run the cameraexposure script on pending images
+task	       camera.exp.run
+  periods      -poll $RUNPOLL
+  periods      -exec $RUNEXEC
+  periods      -timeout 10
+
+  task.exec
+    book npages camPendingExp -var N
+    if ($N == 0) break
+    if ($NETWORK == 0) break
+    
+    # look for new images in camPendingExp (pantaskState == INIT)
+    book getpage camPendingExp 0 -var pageName -key pantaskState INIT
+    if ("$pageName" == "NULL") break
+
+    book setword camPendingExp $pageName pantaskState RUN
+    book getword camPendingExp $pageName camera -var CAMERA
+    book getword camPendingExp $pageName exp_tag -var EXP_TAG
+    book getword camPendingExp $pageName cam_id -var CAM_ID
+    book getword camPendingExp $pageName workdir -var WORKDIR_TEMPLATE
+    book getword camPendingExp $pageName dvodb  -var DVODB
+    book getword camPendingExp $pageName dbname -var DBNAME
+    book getword camPendingExp $pageName reduction -var REDUCTION
+
+    # specify choice of remote host based on camera and chip (class_id)
+    set.host.for.camera $CAMERA FPA
+
+    # set the WORKDIR variable
+    set.workdir.by.camera $CAMERA FPA $WORKDIR_TEMPLATE $default_host WORKDIR
+
+    # notes on how this works:
+    # -- raw workdir examples:
+    # file://data/@HOST@.0/gpc1/20080130
+    # neb:///@HOST@-vol0/gpc1/20080130 (need to supply volname?, or are we re-defining this each time?)
+    # -- out workdir examples:
+    # file://data/ipp004.0/gpc1/20080130
+    # neb:///ipp004-vol0/gpc1/20080130
+
+    ## generate outroot specific to this exposure (& chip)
+    sprintf outroot "%s/%s/%s.cm.%s" $WORKDIR $EXP_TAG $EXP_TAG $CAM_ID
+
+    stdout $LOGDIR/camera.exp.log
+    stderr $LOGDIR/camera.exp.log
+
+    $run = camera_exp.pl --exp_tag $EXP_TAG --cam_id $CAM_ID --camera $CAMERA --outroot $outroot --redirect-output
+    if ("$REDUCTION" != "NULL")
+      $run = $run --reduction $REDUCTION
+    end
+    if ("$DVODB" != "NULL")
+      $run = $run --dvodb $DVODB
+    end
+    add_standard_args run
+
+    # save the pageName for future reference below
+    options $pageName
+
+    # create the command line
+    if ($VERBOSE > 1)
+      echo command $run
+    end
+    command $run
+  end
+
+  # success
+  task.exit default
+    process_exit camPendingExp $options:0 $JOB_STATUS
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+    book setword camPendingExp $options:0 pantaskState TIMEOUT
+  end
+end
+
+## XXX add a global path to output files  
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/chip.pro
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/chip.pro	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/chip.pro	(revision 22157)
@@ -0,0 +1,287 @@
+## chip.pro : globals and support macros : -*- sh -*-
+## this file contains the tasks for running the phase 0 stage
+## these tasks use the book chipPendingImfile
+
+## XXX the cleanup stage probably needs to mimic the chip processing stages, with an exposure and imfile layer
+## can we define the cleanup stages in a separate file derived from this file? 
+
+# test for required global variables
+check.globals
+
+book init chipPendingImfile
+book init chipPendingCleanup
+
+macro chip.status
+  book listbook chipPendingImfile
+  book listbook chipPendingCleanup
+end
+
+macro chip.reset
+  book init chipPendingImfile
+  book init chipPendingCleanup
+end
+
+macro chip.on
+  task chip.imfile.load
+    active true
+  end
+  task chip.imfile.run
+    active true
+  end
+  task chip.cleanup.load
+    active true
+  end
+  task chip.cleanup.run
+    active true
+  end
+end
+
+macro chip.off
+  task chip.imfile.load
+    active false
+  end
+  task chip.imfile.run
+    active false
+  end
+  task chip.cleanup.load
+    active false
+  end
+  task chip.cleanup.run
+    active false
+  end
+end
+
+# this variable will cycle through the known database names
+$chip_DB = 0
+
+# select images ready for chip analysis
+# new entries are added to chipPendingImfile
+# skip already-present entries
+task	       chip.imfile.load
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 30
+  npending     1
+
+  stdout NULL
+  stderr $LOGDIR/chip.imfile.log
+
+  task.exec
+    if ($DB:n == 0)
+      option DEFAULT
+      command chiptool -pendingimfile -limit 20
+    else
+      # save the DB name for the exit tasks
+      option $DB:$chip_DB
+      command chiptool -pendingimfile -limit 20 -dbname $DB:$chip_DB
+      $chip_DB ++
+      if ($chip_DB >= $DB:n) set chip_DB = 0
+    end
+  end
+
+  # success
+  task.exit    0
+    # convert 'stdout' to book format
+    ipptool2book stdout chipPendingImfile -key chip_id:class_id -uniq -setword dbname $options:0 -setword pantaskState INIT
+    if ($VERBOSE > 2)
+      book listbook chipPendingImfile
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup chipPendingImfile
+  end
+
+  # locked list
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
+# run the chip_imfile.pl script on pending images
+task	       chip.imfile.run
+  periods      -poll $RUNPOLL
+  periods      -exec $RUNEXEC
+  periods      -timeout 60
+
+  task.exec
+    book npages chipPendingImfile -var N
+    if ($N == 0) break
+    if ($NETWORK == 0) break
+    
+    # look for new images in chipPendingImfile (pantaskState == INIT)
+    book getpage chipPendingImfile 0 -var pageName -key pantaskState INIT
+    if ("$pageName" == "NULL") break
+
+    book setword chipPendingImfile $pageName pantaskState RUN
+    book getword chipPendingImfile $pageName camera -var CAMERA
+    book getword chipPendingImfile $pageName exp_id -var EXP_ID
+    book getword chipPendingImfile $pageName exp_tag -var EXP_TAG
+    book getword chipPendingImfile $pageName chip_id -var CHIP_ID
+    book getword chipPendingImfile $pageName workdir -var WORKDIR_TEMPLATE
+    book getword chipPendingImfile $pageName class_id -var CLASS_ID
+    book getword chipPendingImfile $pageName uri -var URI
+    book getword chipPendingImfile $pageName dbname -var DBNAME
+    book getword chipPendingImfile $pageName reduction -var REDUCTION
+
+    # specify choice of local or remote host based on camera and chip (class_id)
+    set.host.for.camera $CAMERA $CLASS_ID
+
+    # set the WORKDIR variable
+    set.workdir.by.camera $CAMERA $CLASS_ID $WORKDIR_TEMPLATE $default_host WORKDIR
+
+    # notes on how this works:
+    # -- raw workdir examples:
+    # file://data/@HOST@.0/gpc1/20080130
+    # neb:///@HOST@-vol0/gpc1/20080130 (need to supply volname?, or are we re-defining this each time?)
+    # -- out workdir examples:
+    # file://data/ipp004.0/gpc1/20080130
+    # neb:///ipp004-vol0/gpc1/20080130
+
+    ## generate outroot specific to this exposure (& chip)
+    sprintf outroot "%s/%s/%s.ch.%s" $WORKDIR $EXP_TAG $EXP_TAG $CHIP_ID
+
+    stdout $LOGDIR/chip.imfile.log
+    stderr $LOGDIR/chip.imfile.log
+
+    $run = chip_imfile.pl --exp_id $EXP_ID --chip_id $CHIP_ID --class_id $CLASS_ID --uri $URI --camera $CAMERA --outroot $outroot --redirect-output
+    if ("$REDUCTION" != "NULL")
+      $run = $run --reduction $REDUCTION
+    end
+    add_standard_args run
+
+    # save the pageName for future reference below
+    options $pageName
+
+    # create the command line
+    if ($VERBOSE > 1)
+      echo command $run
+    end
+    command $run
+  end
+
+  # default exit status
+  task.exit    default
+    process_exit chipPendingImfile $options:0 $JOB_STATUS
+  end
+
+  # operation timed out?
+  task.exit    timeout
+    showcommand timeout
+    book setword chipPendingImfile $options:0 pantaskState TIMEOUT
+  end
+end
+
+# this variable will cycle through the known database names
+$chip_cleanup_DB = 0
+
+# select images ready for chip analysis
+# new entries are added to chipPendingImfile
+# skip already-present entries
+task	       chip.cleanup.load
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 30
+  npending     1
+  active       true
+
+  stdout NULL
+  stderr $LOGDIR/chip.cleanup.log
+
+  task.exec
+    if ($DB:n == 0)
+      option DEFAULT
+      command chiptool -pendingcleanuprun -limit 20
+    else
+      # save the DB name for the exit tasks
+      option $DB:$chip_cleanup_DB
+      command chiptool -pendingcleanuprun -limit 20 -dbname $DB:$chip_cleanup_DB
+      $chip_cleanup_DB ++
+      if ($chip_cleanup_DB >= $DB:n) set chip_cleanup_DB = 0
+    end
+  end
+
+  # success
+  task.exit    0
+    # convert 'stdout' to book format
+    ipptool2book stdout chipPendingCleanup -key chip_id -uniq -setword dbname $options:0 -setword pantaskState INIT
+    if ($VERBOSE > 2)
+      book listbook chipPendingCleanup
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup chipPendingCleanup
+  end
+
+  # locked list
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
+# run the ipp_cleanup.pl script on pending images
+task	       chip.cleanup.run
+  periods      -poll $RUNPOLL
+  periods      -exec $RUNEXEC
+  periods      -timeout 60
+  active       true
+
+  task.exec
+    book npages chipPendingCleanup -var N
+    if ($N == 0) break
+    if ($NETWORK == 0) break
+    
+    # look for new images in chipPendingCleanup (pantaskState == INIT)
+    book getpage chipPendingCleanup 0 -var pageName -key pantaskState INIT
+    if ("$pageName" == "NULL") break
+
+    book setword chipPendingCleanup $pageName pantaskState RUN
+    book getword chipPendingCleanup $pageName camera -var CAMERA
+    book getword chipPendingCleanup $pageName state -var CLEANUP_MODE
+    book getword chipPendingCleanup $pageName chip_id -var CHIP_ID
+    book getword chipPendingCleanup $pageName dbname -var DBNAME
+
+    # specify choice of local or remote host based on camera and chip (class_id)
+    set.host.for.camera $CAMERA FPA
+
+    stdout $LOGDIR/chip.cleanup.log
+    stderr $LOGDIR/chip.cleanup.log
+
+    # XXX is everything listed here needed?
+    $run = ipp_cleanup.pl --stage chip --stage_id $CHIP_ID --camera $CAMERA --mode $CLEANUP_MODE
+    add_standard_args run
+
+    # save the pageName for future reference below
+    options $pageName
+
+    # create the command line
+    if ($VERBOSE > 1)
+      echo command $run
+    end
+    command $run
+  end
+
+  # default exit status
+  task.exit    default
+    process_exit chipPendingCleanup $options:0 $JOB_STATUS
+  end
+
+  # operation timed out?
+  task.exit    timeout
+    showcommand timeout
+    book setword chipPendingCleanup $options:0 pantaskState TIMEOUT
+  end
+end
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/chiphosts.manoa.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/chiphosts.manoa.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/chiphosts.manoa.config	(revision 22157)
@@ -0,0 +1,19 @@
+## basic chiphost table: use the defaults for everything
+
+chiphosts MULTI
+
+chiphosts METADATA
+  camera STR MEGACAM
+END
+
+chiphosts METADATA
+  camera STR CFH12K
+END
+
+chiphosts METADATA
+  camera STR GPC1
+END
+
+chiphosts METADATA
+  camera STR gpc1
+END
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/chiphosts.mhpcc.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/chiphosts.mhpcc.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/chiphosts.mhpcc.config	(revision 22157)
@@ -0,0 +1,170 @@
+chiphosts MULTI
+
+chiphosts METADATA
+  camera STR MEGACAM
+  ccd00 STR po00
+  ccd01 STR po01
+  ccd02 STR po02
+  ccd03 STR po03
+  ccd04 STR po04
+  ccd05 STR po05
+  ccd06 STR po06
+  ccd07 STR po07
+  ccd08 STR po08
+  ccd09 STR po09
+END
+
+chiphosts METADATA
+  camera STR CFH12K
+  ccd00 STR po10
+  ccd01 STR po11
+  ccd02 STR po12
+END
+
+chiphosts METADATA
+  camera STR GPC1
+
+  XY01  STR  ipp005
+  XY02  STR  ipp005
+  XY03  STR  ipp005
+  XY04  STR  ipp005
+  XY05  STR  ipp005
+  XY06  STR  ipp005
+
+  XY10  STR  ipp006
+  XY11  STR  ipp006
+  XY12  STR  ipp006
+  XY13  STR  ipp006
+  XY14  STR  ipp006
+  XY15  STR  ipp006
+
+  XY16  STR  ipp007
+  XY17  STR  ipp007
+  XY20  STR  ipp007
+  XY21  STR  ipp007
+  XY22  STR  ipp007
+  XY23  STR  ipp007
+
+  XY24  STR  ipp006 
+  XY25  STR  ipp009
+  XY26  STR  ipp010
+  XY27  STR  ipp011
+  XY30  STR  ipp016
+  XY31  STR  ipp020
+
+  XY32  STR  ipp009
+  XY33  STR  ipp009
+  XY34  STR  ipp009
+  XY35  STR  ipp009
+  XY36  STR  ipp009
+  XY37  STR  ipp009
+
+  XY40  STR  ipp010
+  XY41  STR  ipp010
+  XY42  STR  ipp010
+  XY43  STR  ipp010
+  XY44  STR  ipp010
+  XY45  STR  ipp010
+
+  XY46  STR  ipp011
+  XY47  STR  ipp011
+  XY50  STR  ipp011
+  XY51  STR  ipp011
+  XY52  STR  ipp011
+  XY53  STR  ipp011
+
+  XY54  STR  ipp016
+  XY55  STR  ipp016
+  XY56  STR  ipp016
+  XY57  STR  ipp016
+  XY60  STR  ipp016
+  XY61  STR  ipp016
+
+  XY62  STR  ipp020
+  XY63  STR  ipp020
+  XY64  STR  ipp020
+  XY65  STR  ipp020
+  XY66  STR  ipp020
+  XY67  STR  ipp020
+
+  XY71  STR  ipp021
+  XY72  STR  ipp021
+  XY73  STR  ipp021
+  XY74  STR  ipp021
+  XY75  STR  ipp021
+  XY76  STR  ipp021
+END
+
+chiphosts METADATA
+  camera STR gpc1
+
+  ota01  STR  ipp005
+  ota02  STR  ipp005
+  ota03  STR  ipp005
+  ota04  STR  ipp005
+  ota05  STR  ipp005
+  ota06  STR  ipp005
+
+  ota10  STR  ipp006
+  ota11  STR  ipp006
+  ota12  STR  ipp006
+  ota13  STR  ipp006
+  ota14  STR  ipp006
+  ota15  STR  ipp006
+
+  ota16  STR  ipp007
+  ota17  STR  ipp007
+  ota20  STR  ipp007
+  ota21  STR  ipp007
+  ota22  STR  ipp007
+  ota23  STR  ipp007
+
+  ota24  STR  ipp006
+  ota25  STR  ipp009
+  ota26  STR  ipp010
+  ota27  STR  ipp011
+  ota30  STR  ipp016
+  ota31  STR  ipp020
+
+  ota32  STR  ipp009
+  ota33  STR  ipp009
+  ota34  STR  ipp009
+  ota35  STR  ipp009
+  ota36  STR  ipp009
+  ota37  STR  ipp009
+
+  ota40  STR  ipp010
+  ota41  STR  ipp010
+  ota42  STR  ipp010
+  ota43  STR  ipp010
+  ota44  STR  ipp010
+  ota45  STR  ipp010
+
+  ota46  STR  ipp011
+  ota47  STR  ipp011
+  ota50  STR  ipp011
+  ota51  STR  ipp011
+  ota52  STR  ipp011
+  ota53  STR  ipp011
+
+  ota54  STR  ipp016
+  ota55  STR  ipp016
+  ota56  STR  ipp016
+  ota57  STR  ipp016
+  ota60  STR  ipp016
+  ota61  STR  ipp016
+
+  ota62  STR  ipp020
+  ota63  STR  ipp020
+  ota64  STR  ipp020
+  ota65  STR  ipp020
+  ota66  STR  ipp020
+  ota67  STR  ipp020
+
+  ota71  STR  ipp021
+  ota72  STR  ipp021
+  ota73  STR  ipp021
+  ota74  STR  ipp021
+  ota75  STR  ipp021
+  ota76  STR  ipp021
+END
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/configure.ac
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/configure.ac	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/configure.ac	(revision 22157)
@@ -0,0 +1,14 @@
+AC_PREREQ(2.59)
+
+AC_INIT([ippTasks], [1.1.0], [ipp-support@ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([pantasks.pro])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_MAINTAINER_MODE
+
+AC_PROG_INSTALL
+
+AC_CONFIG_FILES([
+  Makefile
+])
+AC_OUTPUT
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/detrend.correct.pro
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/detrend.correct.pro	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/detrend.correct.pro	(revision 22157)
@@ -0,0 +1,294 @@
+## detrend.correct.pro : globals and support macros : -*- sh -*-
+## this file contains the tasks for running the detrend correction stage
+## these tasks use the books detPendingCorrectedImfile and detPendingCorrectedExp
+
+# test for required global variables
+check.globals
+
+book init detPendingCorrectedImfile
+book init detPendingCorrectedExp
+
+macro detcorr.reset
+  book init detPendingCorrectedImfile
+  book init detPendingCorrectedExp
+end
+
+macro detcorr.status
+  echo detPendingCorrectedImfile
+  book listbook detPendingCorrectedImfile
+  echo detPendingCorrectedExp
+  book listbook detPendingCorrectedExp
+end
+
+macro detcorr.on
+  task detrend.correct.load
+    active true
+  end
+  task detrend.correct.run
+    active true
+  end
+  task detrend.correctexp.load
+    active true
+  end
+  task detrend.correctexp.run
+    active true
+  end
+end
+
+macro detcorr.off
+  task detrend.correct.load
+    active false
+  end
+  task detrend.correct.run
+    active false
+  end
+  task detrend.correctexp.load
+    active false
+  end
+  task detrend.correctexp.run
+    active false
+  end
+end
+
+
+# these variables will cycle through the known database names
+$detPendingCorrectedImfile_DB = 0
+$detPendingCorrectedExp_DB = 0
+
+# select images ready for copy 
+# new entries are added to detPendingCorrectedImfile
+# compare the new list with the ones already selected
+task	       detrend.correct.load
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 30
+  npending     1
+
+  stdout NULL
+  stderr $LOGDIR/detcorr.load.log
+
+  task.exec
+    if ($DB:n == 0)
+      option DEFAULT
+      command dettool -tocorrectedimfile -limit 20
+    else
+      # save the DB name for the exit tasks
+      option $DB:$detPendingCorrectedImfile_DB
+      command dettool -tocorrectedimfile -limit 20 -dbname $DB:$detPendingCorrectedImfile_DB
+      $detPendingCorrectedImfile_DB ++
+      if ($detPendingCorrectedImfile_DB >= $DB:n) set detPendingCorrectedImfile_DB = 0
+    end
+  end
+
+  # success
+  task.exit    0
+    # convert 'stdout' to book format
+    ipptool2book stdout detPendingCorrectedImfile -key det_id:exp_id:class_id -uniq -setword dbname $options:0 -setword pantaskState INIT
+    if ($VERBOSE > 2)
+      book listbook detPendingCorrectedImfile
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup detPendingCorrectedImfile
+  end
+
+  # error
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand
+  end
+end
+
+# run detrend_correct_imfile.pl on pending images
+task	       detrend.correct.run
+  periods      -poll $RUNPOLL
+  periods      -exec $RUNEXEC
+  periods      -timeout 30
+
+  task.exec
+    book npages detPendingCorrectedImfile -var N
+    if ($N == 0) break
+    if ($NETWORK == 0) break
+    
+    # look for new images in detPendingCorrectedImfile
+    book getpage detPendingCorrectedImfile 0 -var pageName -key pantaskState INIT
+    if ("$pageName" == "NULL") break
+
+    book setword detPendingCorrectedImfile $pageName pantaskState RUN
+    book getword detPendingCorrectedImfile $pageName det_id    -var DET_ID   
+    book getword detPendingCorrectedImfile $pageName exp_id    -var EXP_ID  
+    book getword detPendingCorrectedImfile $pageName class_id  -var CLASS_ID 
+    book getword detPendingCorrectedImfile $pageName exp_tag   -var EXP_TAG  XXX where does this come from
+    book getword detPendingCorrectedImfile $pageName det_type  -var DET_TYPE  XXX where does this come from
+    book getword detPendingCorrectedImfile $pageName camera    -var CAMERA  XXX where does this come from
+    book getword detPendingCorrectedImfile $pageName uri       -var URI      
+    book getword detPendingCorrectedImfile $pageName corr_id   -var CORR_ID
+    book getword detPendingCorrectedImfile $pageName corr_type -var CORR_TYPE
+    book getword detPendingCorrectedImfile $pageName workdir   -var WORKDIR_TEMPLATE
+    book getword detPendingCorrectedImfile $pageName dbname    -var DBNAME
+
+    # specify choice of local or remote host based on camera and chip (class_id)
+    set.host.for.camera $CAMERA $CLASS_ID
+
+    # set the WORKDIR variable
+    set.workdir.by.camera $CAMERA $CLASS_ID $WORKDIR_TEMPLATE $default_host WORKDIR
+
+    ## generate outroot specific to this exposure (& chip)
+    sprintf outroot "%s/%s.%s.%s/%s/%s.detcorr.%s" $WORKDIR $CAMERA $DET_TYPE $DET_ID $EXP_TAG $EXP_TAG $DET_ID
+
+    stdout $LOGDIR/detcorr.run.log
+    stderr $LOGDIR/detcorr.run.log
+
+    $run = detrend_correct_imfile.pl --det_id $DET_ID --exp_id $EXP_ID --class_id $CLASS_ID --exp_tag $EXP_TAG --input_uri $URI --corr_id $CORR_ID --corr_type $CORR_TYPE --camera $CAMERA --outroot $outroot --redirect-output
+    if ("$WORKDIR" != "NULL")
+      $run = $run --workdir $WORKDIR
+    end
+    if ("$REDUCTION" != "NULL")
+      $run = $run --reduction $REDUCTION
+    end
+    add_standard_args run
+
+    # save the pageName for future reference below
+    options $pageName
+
+    # create example job options as a demonstration
+    if ($VERBOSE > 1)
+      echo command $run
+    end
+    command $run
+  end
+
+  # default exit status
+  task.exit    default
+    process_exit detPendingCorrectedImfile $options:0 $JOB_STATUS
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+    book setword detPendingCorrectedImfile $options:0 pantaskState TIMEOUT
+ end
+end
+
+# select images ready for copy 
+# new entries are added to detPendingCorrectedExp
+# compare the new list with the ones already selected
+task	       detrend.correctexp.load
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 30
+  npending     1
+
+  stdout NULL
+  stderr $LOGDIR/detcorr.exp.load.log
+
+  task.exec
+    if ($DB:n == 0)
+      option DEFAULT
+      command dettool -tocorrectedexp -limit 20
+    else
+      # save the DB name for the exit tasks
+      option $DB:$detPendingCorrectedExp_DB
+      command dettool -tocorrectedexp -limit 20 -dbname $DB:$detPendingCorrectedExp_DB
+      $detPendingCorrectedExp_DB ++
+      if ($detPendingCorrectedExp_DB >= $DB:n) set detPendingCorrectedExp_DB = 0
+    end
+  end
+
+  # success
+  task.exit    0
+    # convert 'stdout' to book format
+    ipptool2book stdout detPendingCorrectedExp -key det_id:exp_id -uniq -setword dbname $options:0 -setword pantaskState INIT
+    if ($VERBOSE > 2)
+      book listbook detPendingCorrectedExp
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup detPendingCorrectedExp
+  end
+
+  # error
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
+# copy new images, sending job to desired host
+task	       detrend.correctexp.run
+  periods      -poll $RUNPOLL
+  periods      -exec $RUNEXEC
+  periods      -timeout 30
+
+  task.exec
+    book npages detPendingCorrectedExp -var N
+    if ($N == 0) break
+    if ($NETWORK == 0) break
+    
+    # look for new exposures in detPendingCorrectedExp
+    book getpage detPendingCorrectedExp 0 -var pageName -key pantaskState INIT
+    if ("$pageName" == "NULL") break
+
+    book setword detPendingCorrectedExp $pageName pantaskState RUN
+    book getword detPendingCorrectedExp $pageName det_id    -var DET_ID
+    book getword detPendingCorrectedExp $pageName exp_id    -var EXP_ID
+    book getword detPendingCorrectedExp $pageName det_type  -var DET_TYPE
+    book getword detPendingCorrectedExp $pageName camera    -var CAMERA  
+    book getword detPendingCorrectedExp $pageName exp_tag   -var EXP_TAG
+    book getword detPendingCorrectedExp $pageName corr_id   -var CORR_ID
+    book getword detPendingCorrectedExp $pageName corr_type -var CORR_TYPE
+    book getword detPendingCorrectedExp $pageName workdir   -var WORKDIR_TEMPLATE
+    book getword detPendingCorrectedExp $pageName dbname    -var DBNAME
+
+    # specify choice of local or remote host based on camera and chip (class_id)
+    set.host.for.camera $camera FPA
+
+    # set workdir (interpolate host; see camera.pro for examples)
+    set.workdir.by.camera $CAMERA FPA $WORKDIR_TEMPLATE $default_host WORKDIR
+
+    ## generate outroot specific to this exposure (& chip)
+    sprintf outroot "%s/%s.%s.%s/%s/%s.detcorr.%s" $WORKDIR $CAMERA $DET_TYPE $DET_ID $EXP_TAG $EXP_TAG $DET_ID
+
+    stdout $LOGDIR/detcorr.exp.run.log
+    stderr $LOGDIR/detcorr.exp.run.log
+
+    $run = detrend_correct_exp.pl --det_id $DET_ID --exp_id $EXP_ID --exp_tag $EXP_TAG --det_type $DET_TYPE --camera $CAMERA --corr_id $CORR_ID --corr_type $CORR_TYPE --camera $CAMERA --redirect-output
+
+    if ("$WORKDIR" != "NULL")
+      $run = $run --workdir $WORKDIR
+    end
+    add_standard_args run
+
+    # save the pageName for future reference below
+    options $pageName
+
+    # create example job options as a demonstration
+    if ($VERBOSE > 1)
+      echo command $run
+    end
+    command $run
+  end
+
+  # success
+  task.exit    default
+    process_exit detPendingCorrectedExp $options:0 $JOB_STATUS
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+    book setword detPendingCorrectedExp $options:0 pantaskState TIMEOUT
+  end
+end
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/detrend.mkruns.pro
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/detrend.mkruns.pro	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/detrend.mkruns.pro	(revision 22157)
@@ -0,0 +1,99 @@
+## detrend.mkruns.pro : globals and support macros : -*- sh -*-
+
+## XXX this script needs to be updated to use books, not queues
+## XXX this script needs to be reconsidered...
+
+## This script defines tasks which regularly initiate the detrend
+## runs.  Each detrend type is given its own task, which are run once
+## (or twice) per day to build the relevant detrend data
+
+## when one of these tasks is run, it creates a new detrend run ID in
+## the database, and selects the exposures for that run on the basis
+## of the specified filters
+
+# create a new detrend bias-creation run
+task mkdetruns.megacam
+  # command      dettool -select -time NOW -24h -type bias -camera megacam
+  command      mkdetruns.megacam
+  host         local
+
+  periods      -poll 1
+  periods      -exec 60
+  periods      -timeout 30
+  trange        18:00 19:00 -nmax 1
+
+  # success: a new detrend run was created
+  task.exit    0
+    echo "new MEGACAM detrend runs"
+    queueprint stdout
+  end
+
+  # default exit status
+  task.exit default
+    echo "unable to define a new detrend run"
+  end
+
+  # operation timed out?  is the database down?
+  task.exit timeout
+    # send someone email?
+    echo "unable to define a new detrend run"
+  end
+end
+
+# create a new detrend bias-creation run
+task mkdetruns.isp
+  # command      dettool -select -time NOW -24h -type bias -camera megacam
+  command      mkdetruns.isp
+  host         local
+
+  periods      -poll 1
+  periods      -exec 60
+  periods      -timeout 30
+  trange        18:00 19:00 -nmax 1
+
+  # success: a new detrend run was created
+  task.exit    0
+    echo "new ISP detrend runs"
+    queueprint stdout
+  end
+
+  # default exit status
+  task.exit default
+    echo "unable to define a new detrend run"
+  end
+
+  # operation timed out?  is the database down?
+  task.exit timeout
+    # send someone email?
+    echo "unable to define a new detrend run"
+  end
+end
+
+# create a new detrend bias-creation run
+task mkdetruns.gpc
+  # command      dettool -select -time NOW -24h -type bias -camera megacam
+  command      mkdetruns.gpc
+  host         local
+
+  periods      -poll 1
+  periods      -exec 60
+  periods      -timeout 30
+  trange        18:00 19:00 -nmax 1
+
+  # success: a new detrend run was created
+  task.exit    0
+    echo "new GPC detrend runs"
+    queueprint stdout
+  end
+
+  # default exit status
+  task.exit default
+    echo "unable to define a new detrend run"
+  end
+
+  # operation timed out?  is the database down?
+  task.exit timeout
+    # send someone email?
+    echo "unable to define a new detrend run"
+  end
+end
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/detrend.norm.pro
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/detrend.norm.pro	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/detrend.norm.pro	(revision 22157)
@@ -0,0 +1,402 @@
+## detrend.norm.pro : globals and support macros : -*- sh -*-
+## this file contains the tasks for running the detrend normalization stages
+## these tasks use the books detPendingNormStatImfile detPendingNormImfile detPendingNormExp
+
+# test for required global variables
+check.globals
+
+book init detPendingNormStatImfile
+book init detPendingNormImfile
+book init detPendingNormExp
+
+macro detnorm.reset
+  book init detPendingNormStatImfile
+  book init detPendingNormImfile
+  book init detPendingNormExp
+end
+
+macro detnorm.status
+  book listbook detPendingNormStatImfile
+  book listbook detPendingNormImfile
+  book listbook detPendingNormExp
+end
+
+macro detnorm.on
+  task detrend.norm.load
+    active true
+  end
+  task detrend.norm.run
+    active true
+  end
+  task detrend.normexp.load
+    active true
+  end
+  task detrend.normexp.run
+    active true
+  end
+  task detrend.normstat.load
+    active true
+  end
+  task detrend.normstat.run
+    active true
+  end
+end
+
+macro detnorm.off
+  task detrend.norm.load
+    active false
+  end
+  task detrend.norm.run
+    active false
+  end
+  task detrend.normexp.load
+    active false
+  end
+  task detrend.normexp.run
+    active false
+  end
+  task detrend.normstat.load
+    active false
+  end
+  task detrend.normstat.run
+    active false
+  end
+end
+
+# these variables will cycle through the known database names
+$detPendingNormStatImfile_DB = 0
+$detPendingNormImfile_DB = 0
+$detPendingNormExp_DB = 0
+
+# select images ready for copy 
+# new entries are added to detPendingNormStatImfile
+# compare the new list with the ones already selected
+task	       detrend.normstat.load
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 30
+  npending     1
+
+  stdout NULL
+  stderr $LOGDIR/detrend.normstat.load.log
+
+  task.exec
+    if ($DB:n == 0)
+      option DEFAULT
+      command dettool -tonormalizedstat -limit 20
+    else
+      # save the DB name for the exit tasks
+      option $DB:$detPendingNormStatImfile_DB
+      command dettool -tonormalizedstat -limit 20 -dbname $DB:$detPendingNormStatImfile_DB
+      $detPendingNormStatImfile_DB ++
+      if ($detPendingNormStatImfile_DB >= $DB:n) set detPendingNormStatImfile_DB = 0
+    end
+  end
+
+  # success
+  task.exit    0
+    # convert 'stdout' to book format
+    ipptool2book stdout detPendingNormStatImfile -key det_id:iteration -uniq -setword dbname $options:0 -setword pantaskState INIT
+    if ($VERBOSE > 2)
+      book listbook detPendingNormStatImfile
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup detPendingNormStatImfile
+  end
+
+  # default exit status
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
+# copy new images, sending job to desired host
+task	       detrend.normstat.run
+  periods      -poll $RUNPOLL
+  periods      -exec $RUNEXEC
+  periods      -timeout 30
+
+  task.exec
+    book npages detPendingNormStatImfile -var N
+    if ($N == 0) break
+    if ($NETWORK == 0) break
+    
+    # look for new images in detPendingNormStatImfile
+    book getpage detPendingNormStatImfile 0 -var pageName -key pantaskState INIT
+    if ("$pageName" == "NULL") break
+
+    book setword detPendingNormStatImfile $pageName pantaskState RUN
+    book getword detPendingNormStatImfile $pageName det_id    -var DET_ID
+    book getword detPendingNormStatImfile $pageName det_type  -var DET_TYPE
+    book getword detPendingNormStatImfile $pageName iteration -var ITERATION
+    book getword detPendingNormStatImfile $pageName camera    -var CAMERA  
+    book getword detPendingNormStatImfile $pageName workdir   -var WORKDIR_TEMPLATE
+    book getword detPendingNormStatImfile $pageName dbname    -var DBNAME
+
+    # specify choice of local or remote host based on camera and chip (class_id)
+    set.host.for.camera $CAMERA FPA
+
+    # set workdir (interpolate host; see camera.pro for examples)
+    set.workdir.by.camera $CAMERA FPA $WORKDIR_TEMPLATE $default_host WORKDIR
+
+    ## generate outroot specific to this exposure (& chip)
+    sprintf outroot "%s/%s.%s.%s/%s.%s.normstat.%s.%s" $WORKDIR $CAMERA $DET_TYPE $DET_ID $CAMERA $DET_TYPE $DET_ID $ITERATION
+
+    stdout $LOGDIR/detrend.normstat.log
+    stderr $LOGDIR/detrend.normstat.log
+
+    $run = detrend_norm_calc.pl --det_id $DET_ID --iteration $ITERATION --det_type $DET_TYPE --outroot $outroot --redirect-output
+    add_standard_args run
+
+    # save the pageName for future reference below
+    options $pageName
+
+    # create example job options as a demonstration
+    if ($VERBOSE > 1)
+      echo command $run
+    end
+    command $run
+  end
+
+  # default exit status
+  task.exit    default
+    process_exit detPendingNormStatImfile $options:0 $JOB_STATUS
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+    book setword detPendingNormStatImfile $options:0 pantaskState TIMEOUT
+  end
+end
+
+# select images ready for copy 
+# new entries are added to detPendingNormImfile
+# compare the new list with the ones already selected
+task	       detrend.norm.load
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 30
+  npending     1
+
+  stdout NULL
+  stderr $LOGDIR/detrend.norm.load.log
+
+  task.exec
+    if ($DB:n == 0)
+      option DEFAULT
+      command dettool -tonormalize -limit 20
+    else
+      # save the DB name for the exit tasks
+      option $DB:$detPendingNormImfile_DB
+      command dettool -tonormalize -limit 20 -dbname $DB:$detPendingNormImfile_DB
+      $detPendingNormImfile_DB ++
+      if ($detPendingNormImfile_DB >= $DB:n) set detPendingNormImfile_DB = 0
+    end
+  end
+
+  # success
+  task.exit    0
+    # convert 'stdout' to book format
+    ipptool2book stdout detPendingNormImfile -key det_id:iteration:class_id -uniq -setword dbname $options:0 -setword pantaskState INIT
+    if ($VERBOSE > 2)
+      book listbook detPendingNormImfile
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup detPendingNormImfile
+  end
+
+  # default exit status
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
+# copy new images, sending job to desired host
+task	       detrend.norm.run
+  periods      -poll $RUNPOLL
+  periods      -exec $RUNEXEC
+  periods      -timeout 30
+
+  task.exec
+    book npages detPendingNormImfile -var N
+    if ($N == 0) break
+    if ($NETWORK == 0) break
+    
+    # look for new images in detPendingNormImfile
+    book getpage detPendingNormImfile 0 -var pageName -key pantaskState INIT
+    if ("$pageName" == "NULL") break
+
+    book setword detPendingNormImfile $pageName pantaskState RUN
+    book getword detPendingNormImfile $pageName det_type  -var DET_TYPE 
+    book getword detPendingNormImfile $pageName camera    -var CAMERA   
+    book getword detPendingNormImfile $pageName uri       -var URI      
+    book getword detPendingNormImfile $pageName det_id    -var DET_ID   
+    book getword detPendingNormImfile $pageName iteration -var ITERATION     
+    book getword detPendingNormImfile $pageName class_id  -var CLASS_ID 
+    book getword detPendingNormImfile $pageName norm      -var NORM     
+    book getword detPendingNormImfile $pageName workdir   -var WORKDIR_TEMPLATE
+    book getword detPendingNormImfile $pageName dbname    -var DBNAME
+
+    # specify choice of local or remote host based on camera and chip (class_id)
+    set.host.for.camera $CAMERA $CLASS_ID
+
+    # set workdir (interpolate host; see camera.pro for examples)
+    set.workdir.by.camera $CAMERA $CLASS_ID $WORKDIR_TEMPLATE $default_host WORKDIR
+
+    ## generate outroot specific to this stack
+    sprintf outroot "%s/%s.%s.%s/%s.%s.norm.%s.%s" $WORKDIR $CAMERA $DET_TYPE $DET_ID $CAMERA $DET_TYPE $DET_ID $ITERATION
+
+    stdout $LOGDIR/detrend.norm.run.log
+    stderr $LOGDIR/detrend.norm.run.log
+
+    $run = detrend_norm_apply.pl --det_id $DET_ID --iteration $ITERATION --class_id $CLASS_ID --value $NORM --input_uri $URI --camera $CAMERA --det_type $DET_TYPE --outroot $outroot --redirect-output
+    add_standard_args run
+    
+    # save the pageName for future reference below
+    options $pageName
+
+    # create example job options as a demonstration
+    if ($VERBOSE > 1)
+      echo command $run
+    end
+    command $run
+  end
+
+  # default exit status
+  task.exit    default
+    process_exit detPendingNormImfile $options:0 $JOB_STATUS
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+    book setword detPendingNormImfile $options:0 pantaskState TIMEOUT
+  end
+end
+
+# select images ready for copy 
+# new entries are added to detPendingNormExp
+# compare the new list with the ones already selected
+task	       detrend.normexp.load
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 30
+  npending     1
+
+  stdout NULL
+  stderr $LOGDIR/detrend.normexp.load.log
+
+  task.exec
+    if ($DB:n == 0)
+      option DEFAULT
+      command dettool -tonormalizedexp -limit 20
+    else
+      # save the DB name for the exit tasks
+      option $DB:$detPendingNormExp_DB
+      command dettool -tonormalizedexp -limit 20 -dbname $DB:$detPendingNormExp_DB
+      $detPendingNormExp_DB ++
+      if ($detPendingNormExp_DB >= $DB:n) set detPendingNormExp_DB = 0
+    end
+  end
+
+  # success
+  task.exit    0
+    # convert 'stdout' to book format
+    ipptool2book stdout detPendingNormExp -key det_id:iteration -uniq -setword dbname $options:0 -setword pantaskState INIT
+    if ($VERBOSE > 2)
+      book listbook detPendingNormExp
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup detPendingNormExp
+  end
+
+  # default exit status
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
+# copy new images, sending job to desired host
+task	       detrend.normexp.run
+  periods      -poll $RUNPOLL
+  periods      -exec $RUNEXEC
+  periods      -timeout 30
+
+  task.exec
+    book npages detPendingNormExp -var N
+    if ($N == 0) break
+    if ($NETWORK == 0) break
+    
+    # look for new images in detPendingNormExp
+    book getpage detPendingNormExp 0 -var pageName -key pantaskState INIT
+    if ("$pageName" == "NULL") break
+
+    book setword detPendingNormExp $pageName pantaskState RUN
+    book getword detPendingNormExp $pageName det_id    -var DET_ID  
+    book getword detPendingNormExp $pageName iteration -var ITERATION    
+    book getword detPendingNormExp $pageName det_type  -var DET_TYPE
+    book getword detPendingNormExp $pageName camera    -var CAMERA  
+    book getword detPendingNormExp $pageName workdir   -var WORKDIR_TEMPLATE
+    book getword detPendingNormExp $pageName dbname    -var DBNAME
+
+    # specify choice of local or remote host based on camera and chip (class_id)
+    set.host.for.camera $CAMERA FPA
+
+    # set workdir (interpolate host; see camera.pro for examples)
+    set.workdir.by.camera $CAMERA FPA $WORKDIR_TEMPLATE $default_host WORKDIR
+
+    ## generate outroot specific to this exposure (& chip)
+    sprintf outroot "%s/%s.%s.%s/%s.%s.normexp.%s.%s" $WORKDIR $CAMERA $DET_TYPE $DET_ID $CAMERA $DET_TYPE $DET_ID $ITERATION
+
+    stdout $LOGDIR/detrend.norm.run.log
+    stderr $LOGDIR/detrend.norm.run.log
+
+    $run = detrend_norm_exp.pl --det_id $DET_ID --iteration $ITERATION --camera $CAMERA --det_type $DET_TYPE  --outroot $outroot
+    add_standard_args run
+
+    # save the pageName for future reference below
+    options $pageName
+
+    # create example job options as a demonstration
+    if ($VERBOSE > 1)
+      echo command $run
+    end
+    command $run
+  end
+
+  # default exit status
+  task.exit    default
+    process_exit detPendingNormExp $options:0 $JOB_STATUS
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+    book setword detPendingNormExp $options:0 pantaskState TIMEOUT
+  end
+end
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/detrend.process.pro
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/detrend.process.pro	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/detrend.process.pro	(revision 22157)
@@ -0,0 +1,288 @@
+## detrend.process.pro : globals and support macros : -*- sh -*-
+## this file contains the tasks for running the detrend processing stage
+## these tasks use the books detPendingProcessedImfile and detPendingProcessedExp
+
+# test for required global variables
+check.globals
+
+book init detPendingProcessedImfile
+book init detPendingProcessedExp
+
+macro detproc.reset
+  book init detPendingProcessedImfile
+  book init detPendingProcessedExp
+end
+
+macro detproc.status
+  echo detPendingProcessedImfile
+  book listbook detPendingProcessedImfile
+  echo detPendingProcessedExp
+  book listbook detPendingProcessedExp
+end
+
+macro detproc.on
+  task detrend.process.load
+    active true
+  end
+  task detrend.process.run
+    active true
+  end
+  task detrend.processexp.load
+    active true
+  end
+  task detrend.processexp.run
+    active true
+  end
+end
+
+macro detproc.off
+  task detrend.process.load
+    active false
+  end
+  task detrend.process.run
+    active false
+  end
+  task detrend.processexp.load
+    active false
+  end
+  task detrend.processexp.run
+    active false
+  end
+end
+
+
+# these variables will cycle through the known database names
+$detPendingProcessedImfile_DB = 0
+$detPendingProcessedExp_DB = 0
+
+# select images ready for copy 
+# new entries are added to detPendingProcessedImfile
+# compare the new list with the ones already selected
+task	       detrend.process.load
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 30
+  npending     1
+
+  stdout NULL
+  stderr $LOGDIR/detproc.imfile.load.log
+
+  task.exec
+    if ($DB:n == 0)
+      option DEFAULT
+      command dettool -toprocessedimfile -limit 20
+    else
+      # save the DB name for the exit tasks
+      option $DB:$detPendingProcessedImfile_DB
+      command dettool -toprocessedimfile -limit 20 -dbname $DB:$detPendingProcessedImfile_DB
+      $detPendingProcessedImfile_DB ++
+      if ($detPendingProcessedImfile_DB >= $DB:n) set detPendingProcessedImfile_DB = 0
+    end
+  end
+
+  # success
+  task.exit    0
+    # convert 'stdout' to book format
+    ipptool2book stdout detPendingProcessedImfile -key det_id:exp_id:class_id -uniq -setword dbname $options:0 -setword pantaskState INIT
+    if ($VERBOSE > 2)
+      book listbook detPendingProcessedImfile
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup detPendingProcessedImfile
+  end
+
+  # error
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand
+  end
+end
+
+# run detrend_process_imfile.ps on pending images
+task	       detrend.process.run
+  periods      -poll $RUNPOLL
+  periods      -exec $RUNEXEC
+  periods      -timeout 30
+
+  task.exec
+    book npages detPendingProcessedImfile -var N
+    if ($N == 0) break
+    if ($NETWORK == 0) break
+    
+    # look for new images in detPendingProcessedImfile
+    book getpage detPendingProcessedImfile 0 -var pageName -key pantaskState INIT
+    if ("$pageName" == "NULL") break
+
+    book setword detPendingProcessedImfile $pageName pantaskState RUN
+    book getword detPendingProcessedImfile $pageName det_id   -var DET_ID   
+    book getword detPendingProcessedImfile $pageName det_type -var DET_TYPE 
+    book getword detPendingProcessedImfile $pageName exp_id   -var EXP_ID  
+    book getword detPendingProcessedImfile $pageName class_id -var CLASS_ID 
+    book getword detPendingProcessedImfile $pageName exp_tag  -var EXP_TAG  
+    book getword detPendingProcessedImfile $pageName uri      -var URI      
+    book getword detPendingProcessedImfile $pageName camera   -var CAMERA   
+    book getword detPendingProcessedImfile $pageName workdir  -var WORKDIR_TEMPLATE
+    book getword detPendingProcessedImfile $pageName dbname   -var DBNAME
+    book getword detPendingProcessedImfile $pageName reduction -var REDUCTION
+
+    # specify choice of local or remote host based on camera and chip (class_id)
+    set.host.for.camera $CAMERA $CLASS_ID
+
+    # see chip.pro for examples
+    set.workdir.by.camera $CAMERA $CLASS_ID $WORKDIR_TEMPLATE $default_host WORKDIR
+
+    ## generate outroot specific to this exposure (& chip)
+    sprintf outroot "%s/%s.%s.%s/%s/%s.detproc.%s" $WORKDIR $CAMERA $DET_TYPE $DET_ID $EXP_TAG $EXP_TAG $DET_ID
+
+    stdout $LOGDIR/detproc.imfile.run.log
+    stderr $LOGDIR/detproc.imfile.run.log
+
+    $run = detrend_process_imfile.pl --det_id $DET_ID --exp_id $EXP_ID --det_type $DET_TYPE --class_id $CLASS_ID --exp_tag $EXP_TAG --input_uri $URI --camera $CAMERA --outroot $outroot --redirect-output
+    if ("$REDUCTION" != "NULL")
+      $run = $run --reduction $REDUCTION
+    end
+    add_standard_args run
+
+    # save the pageName for future reference below
+    options $pageName
+
+    # create example job options as a demonstration
+    if ($VERBOSE > 1)
+      echo command $run
+    end
+    command $run
+  end
+
+  # default exit status
+  task.exit    default
+    process_exit detPendingProcessedImfile $options:0 $JOB_STATUS
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+    book setword detPendingProcessedImfile $options:0 pantaskState TIMEOUT
+ end
+end
+
+# select images ready for copy 
+# new entries are added to detPendingProcessedExp
+# compare the new list with the ones already selected
+task	       detrend.processexp.load
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 30
+  npending     1
+
+  stdout NULL
+  stderr $LOGDIR/detproc.exp.load.log
+
+  task.exec
+    if ($DB:n == 0)
+      option DEFAULT
+      command dettool -toprocessedexp -limit 20
+    else
+      # save the DB name for the exit tasks
+      option $DB:$detPendingProcessedExp_DB
+      command dettool -toprocessedexp -limit 20 -dbname $DB:$detPendingProcessedExp_DB
+      $detPendingProcessedExp_DB ++
+      if ($detPendingProcessedExp_DB >= $DB:n) set detPendingProcessedExp_DB = 0
+    end
+  end
+
+  # success
+  task.exit    0
+    # convert 'stdout' to book format
+    ipptool2book stdout detPendingProcessedExp -key det_id:exp_id -uniq -setword dbname $options:0 -setword pantaskState INIT
+    if ($VERBOSE > 2)
+      book listbook detPendingProcessedExp
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup detPendingProcessedExp
+  end
+
+  # error
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
+# copy new images, sending job to desired host
+task	       detrend.processexp.run
+  periods      -poll $RUNPOLL
+  periods      -exec $RUNEXEC
+  periods      -timeout 30
+
+  task.exec
+    book npages detPendingProcessedExp -var N
+    if ($N == 0) break
+    if ($NETWORK == 0) break
+    
+    # look for new exposures in detPendingProcessedExp
+    book getpage detPendingProcessedExp 0 -var pageName -key pantaskState INIT
+    if ("$pageName" == "NULL") break
+
+    book setword detPendingProcessedExp $pageName pantaskState RUN
+    book getword detPendingProcessedExp $pageName det_id    -var DET_ID
+    book getword detPendingProcessedExp $pageName exp_id    -var EXP_ID
+    book getword detPendingProcessedExp $pageName det_type  -var DET_TYPE
+    book getword detPendingProcessedExp $pageName camera    -var CAMERA  
+    book getword detPendingProcessedExp $pageName exp_tag   -var EXP_TAG
+    book getword detPendingProcessedExp $pageName workdir   -var WORKDIR_TEMPLATE
+    book getword detPendingProcessedExp $pageName dbname    -var DBNAME
+    book getword detPendingProcessedExp $pageName reduction -var REDUCTION
+
+    # specify choice of local or remote host based on camera and chip (class_id)
+    set.host.for.camera $CAMERA FPA
+
+    # set workdir (interpolate host; see camera.pro for examples)
+    set.workdir.by.camera $CAMERA FPA $WORKDIR_TEMPLATE $default_host WORKDIR
+
+    ## generate outroot specific to this exposure (& chip)
+    sprintf outroot "%s/%s.%s.%s/%s/%s.detproc.%s" $WORKDIR $CAMERA $DET_TYPE $DET_ID $EXP_TAG $EXP_TAG $DET_ID
+
+    stdout $LOGDIR/detproc.exp.run.log
+    stderr $LOGDIR/detproc.exp.run.log
+
+    $run = detrend_process_exp.pl --det_id $DET_ID --exp_id $EXP_ID --exp_tag $EXP_TAG --det_type $DET_TYPE --camera $CAMERA --outroot $outroot --redirect-output
+    if ("$REDUCTION" != "NULL")
+      $run = $run --reduction $REDUCTION
+    end
+    add_standard_args run
+
+    # save the pageName for future reference below
+    options $pageName
+
+    # create example job options as a demonstration
+    if ($VERBOSE > 1)
+      echo command $run
+    end
+    command $run
+  end
+
+  # success
+  task.exit    default
+    process_exit detPendingProcessedExp $options:0 $JOB_STATUS
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+    book setword detPendingProcessedExp $options:0 pantaskState TIMEOUT
+  end
+end
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/detrend.reject.pro
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/detrend.reject.pro	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/detrend.reject.pro	(revision 22157)
@@ -0,0 +1,148 @@
+## detrend.reject.pro : globals and support macros : -*- sh -*-
+## this file contains the tasks for running the detrend processing stage
+## these tasks use the book detRejectExp
+
+# test for required global variables
+check.globals
+
+book init detRejectExp
+
+macro detreject.reset
+  book init detRejectExp
+end
+
+macro detreject.status
+  book listbook detRejectExp
+end
+
+macro detreject.on
+  task detrend.reject.load
+    active true
+  end
+  task detrend.reject.run
+    active true
+  end
+end
+
+macro detreject.off
+  task detrend.reject.load
+    active false
+  end
+  task detrend.reject.run
+    active false
+  end
+end
+
+# this variable will cycle through the known database names
+$detRejectExp_DB = 0
+
+# select images ready for copy 
+# new entries are added to detRejectExp
+# compare the new list with the ones already selected
+task	       detrend.reject.load
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 30
+  npending     1
+
+  stdout NULL
+  stderr $LOGDIR/detrend.reject.log
+
+  task.exec
+    if ($DB:n == 0)
+      option DEFAULT
+      command dettool -todetrunsummary -limit 20
+    else
+      # save the DB name for the exit tasks
+      option $DB:$detRejectExp_DB
+      command dettool -todetrunsummary -limit 20 -dbname $DB:$detRejectExp_DB
+      $detRejectExp_DB ++
+      if ($detRejectExp_DB >= $DB:n) set detRejectExp_DB = 0
+    end
+  end
+
+  # success
+  task.exit    0
+    # convert 'stdout' to book format
+    ipptool2book stdout detRejectExp -key det_id:iteration -uniq -setword dbname $options:0 -setword pantaskState INIT
+    if ($VERBOSE > 2)
+      book listbook detRejectExp
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup detRejectExp
+  end
+
+  # locked list
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand failure
+  end
+end
+
+# copy new images, sending job to desired host
+task	       detrend.reject.run
+  periods      -poll $RUNPOLL
+  periods      -exec $RUNEXEC
+  periods      -timeout 30
+
+  task.exec
+    book npages detRejectExp -var N
+    if ($N == 0) break
+    if ($NETWORK == 0) break
+    
+    # look for new images in detRejectExp
+    book getpage detRejectExp 0 -var pageName -key pantaskState INIT
+    if ("$pageName" == "NULL") break
+
+    book setword detRejectExp $pageName pantaskState RUN
+    book getword detRejectExp $pageName det_id    -var DET_ID   
+    book getword detRejectExp $pageName iteration -var ITERATION     
+    book getword detRejectExp $pageName det_type  -var DET_TYPE 
+    book getword detRejectExp $pageName mode      -var MODE     
+    book getword detRejectExp $pageName camera    -var CAMERA   
+    book getword detRejectExp $pageName workdir   -var WORKDIR_TEMPLATE
+    book getword detRejectExp $pageName dbname    -var DBNAME
+
+    # specify choice of local or remote host based on camera and chip (class_id)
+    set.host.for.camera $CAMERA FPA
+
+    # set workdir (interpolate host; see camera.pro for examples)
+    set.workdir.by.camera $CAMERA FPA $WORKDIR_TEMPLATE $default_host WORKDIR
+
+    ## generate outroot specific to this exposure (& chip)
+    sprintf outroot "%s/%s.%s.%s/%s.%s.%s.%s.detreject" $WORKDIR $CAMERA $DET_TYPE $DET_ID $CAMERA $DET_TYPE $DET_ID $ITERATION
+
+    stdout $LOGDIR/detrend.reject.log
+    stderr $LOGDIR/detrend.reject.log
+
+    $run = detrend_reject_exp.pl --det_id $DET_ID --iteration $ITERATION --det_type $DET_TYPE --camera $CAMERA --outroot $outroot --redirect-output
+    add_standard_args run
+
+    # save the pageName for future reference below
+    options $pageName
+
+    # create example job options as a demonstration
+    if ($VERBOSE > 1)
+      echo command $run
+    end
+    command $run
+ end
+
+  # default exit status
+  task.exit    default
+    process_exit detRejectExp $options:0 $JOB_STATUS
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand failure
+    book setword detRejectExp $options:0 pantaskState TIMEOUT
+  end
+end
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/detrend.resid.pro
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/detrend.resid.pro	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/detrend.resid.pro	(revision 22157)
@@ -0,0 +1,287 @@
+## detrend.resid.pro : globals and support macros : -*- sh -*-
+## this file contains the tasks for running the detrend processing stage
+## these tasks use the books detPendingResidImfile and detPendingResidExp
+
+# test for required global variables
+check.globals
+
+book init detPendingResidImfile
+book init detPendingResidExp
+
+macro detresid.reset
+  book init detPendingResidImfile
+  book init detPendingResidExp
+end
+
+macro detresid.status
+  book listbook detPendingResidImfile
+  book listbook detPendingResidExp
+end
+
+macro detresid.on
+  task detrend.resid.load
+    active true
+  end
+  task detrend.resid.run
+    active true
+  end
+  task detrend.residexp.load
+    active true
+  end
+  task detrend.residexp.run
+    active true
+  end
+end
+
+macro detresid.off
+  task detrend.resid.load
+    active false
+  end
+  task detrend.resid.run
+    active false
+  end
+  task detrend.residexp.load
+    active false
+  end
+  task detrend.residexp.run
+    active false
+  end
+end
+
+# these variables will cycle through the known database names
+$detPendingResidImfile_DB = 0
+$detPendingResidExp_DB = 0
+
+# select images ready for copy 
+# new entries are added to detPendingResidImfile
+# compare the new list with the ones already selected
+task	       detrend.resid.load
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 30
+  npending     1
+
+  stdout NULL
+  stderr $LOGDIR/detresid.load.log
+
+  task.exec
+    if ($DB:n == 0)
+      option DEFAULT
+      command dettool -toresidimfile -limit 20
+    else
+      # save the DB name for the exit tasks
+      option $DB:$detPendingResidImfile_DB
+      command dettool -toresidimfile -limit 20 -dbname $DB:$detPendingResidImfile_DB
+      $detPendingResidImfile_DB ++
+      if ($detPendingResidImfile_DB >= $DB:n) set detPendingResidImfile_DB = 0
+    end
+  end
+
+  # success
+  task.exit    0
+    # convert 'stdout' to book format
+    ipptool2book stdout detPendingResidImfile -key det_id:iteration:exp_id:class_id -uniq -setword dbname $options:0 -setword pantaskState INIT
+    if ($VERBOSE > 2)
+      book listbook detPendingResidImfile
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup detPendingResidImfile
+  end
+
+  # default exit status
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
+# copy new images, sending job to desired host
+task	       detrend.resid.run
+  periods      -poll $RUNPOLL
+  periods      -exec $RUNEXEC
+  periods      -timeout 30
+
+  task.exec
+    book npages detPendingResidImfile -var N
+    if ($N == 0) break
+    if ($NETWORK == 0) break
+    
+    # look for new images in detPendingResidImfile
+    book getpage detPendingResidImfile 0 -var pageName -key pantaskState INIT
+    if ("$pageName" == "NULL") break
+
+    book setword detPendingResidImfile $pageName pantaskState RUN
+    book getword detPendingResidImfile $pageName det_id    -var DET_ID   
+    book getword detPendingResidImfile $pageName exp_id    -var EXP_ID  
+    book getword detPendingResidImfile $pageName iteration -var ITERATION     
+    book getword detPendingResidImfile $pageName det_type  -var DET_TYPE 
+    book getword detPendingResidImfile $pageName mode      -var MODE     
+    book getword detPendingResidImfile $pageName exp_tag   -var EXP_TAG  
+    book getword detPendingResidImfile $pageName class_id  -var CLASS_ID 
+    book getword detPendingResidImfile $pageName uri       -var URI      
+    book getword detPendingResidImfile $pageName det_uri   -var DET_URI  
+    book getword detPendingResidImfile $pageName camera    -var CAMERA   
+    book getword detPendingResidImfile $pageName workdir   -var WORKDIR_TEMPLATE
+    book getword detPendingResidImfile $pageName dbname    -var DBNAME
+    book getword detPendingResidImfile $pageName reduction -var REDUCTION
+
+    # specify choice of local or remote host based on camera and chip (class_id)
+    set.host.for.camera $CAMERA $CLASS_ID
+
+    # set workdir (interpolate host; see camera.pro for examples)
+    set.workdir.by.camera $CAMERA $CLASS_ID $WORKDIR_TEMPLATE $default_host WORKDIR
+
+    ## generate outroot specific to this exposure (& chip)
+    sprintf outroot "%s/%s.%s.%s/%s/%s.detresid.%s.%s" $WORKDIR $CAMERA $DET_TYPE $DET_ID $EXP_TAG $EXP_TAG $DET_ID $ITERATION
+
+    stdout $LOGDIR/detresid.run.log
+    stderr $LOGDIR/detresid.run.log
+
+    $run = detrend_resid_imfile.pl --det_id $DET_ID --iteration $ITERATION --exp_id $EXP_ID --exp_tag $EXP_TAG --class_id $CLASS_ID --det_type $DET_TYPE --detrend $DET_URI --input_uri $URI --camera $CAMERA --mode $MODE --outroot $outroot --redirect-output
+    if ("$REDUCTION" != "NULL")
+      $run = $run --reduction $REDUCTION
+    end
+    add_standard_args run
+
+    # save the pageName for future reference below
+    options $pageName
+
+    # create command
+    if ($VERBOSE > 1)
+      echo command $run
+    end
+    command $run
+  end
+
+  # default exit status
+  task.exit    default
+    process_exit detPendingResidImfile $options:0 $JOB_STATUS
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+    book setword detPendingResidImfile $options:0 pantaskState TIMEOUT
+  end
+end
+
+# select images ready for copy 
+# new entries are added to detPendingResidExp
+# compare the new list with the ones already selected
+task	       detrend.residexp.load
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 30
+  npending     1
+
+  stdout NULL
+  stderr $LOGDIR/detresid.exp.load.log
+
+  task.exec
+    if ($DB:n == 0)
+      option DEFAULT
+      command dettool -toresidexp -limit 20
+    else
+      # save the DB name for the exit tasks
+      option $DB:$detPendingResidExp_DB
+      command dettool -toresidexp -limit 20 -dbname $DB:$detPendingResidExp_DB
+      $detPendingResidExp_DB ++
+      if ($detPendingResidExp_DB >= $DB:n) set detPendingResidExp_DB = 0
+    end
+  end
+
+  # success
+  task.exit    0
+    # convert 'stdout' to book format
+    ipptool2book stdout detPendingResidExp -key det_id:iteration:exp_id -uniq -setword dbname $options:0 -setword pantaskState INIT
+    if ($VERBOSE > 2)
+      book listbook detPendingResidExp
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup detPendingResidExp
+  end
+
+  # default exit status
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
+# copy new images, sending job to desired host
+task	       detrend.residexp.run
+  periods      -poll $RUNPOLL
+  periods      -exec $RUNEXEC
+  periods      -timeout 30
+
+  task.exec
+    book npages detPendingResidExp -var N
+    if ($N == 0) break
+    if ($NETWORK == 0) break
+    
+    # look for new images in detPendingResidExp
+    book getpage detPendingResidExp 0 -var pageName -key pantaskState INIT
+    if ("$pageName" == "NULL") break
+
+    book setword detPendingResidExp $pageName pantaskState RUN
+    book getword detPendingResidExp $pageName det_id    -var DET_ID  
+    book getword detPendingResidExp $pageName exp_id    -var EXP_ID 
+    book getword detPendingResidExp $pageName iteration -var ITERATION    
+    book getword detPendingResidExp $pageName det_type  -var DET_TYPE
+    book getword detPendingResidExp $pageName mode      -var MODE    
+    book getword detPendingResidExp $pageName exp_tag   -var EXP_TAG 
+    book getword detPendingResidExp $pageName include   -var INCLUDE 
+    book getword detPendingResidExp $pageName camera    -var CAMERA  
+    book getword detPendingResidExp $pageName workdir   -var WORKDIR_TEMPLATE
+    book getword detPendingResidExp $pageName dbname    -var DBNAME
+
+    # specify choice of local or remote host based on camera and chip (class_id)
+    set.host.for.camera $CAMERA FPA
+
+    # set workdir (interpolate host; see camera.pro for examples)
+    set.workdir.by.camera $CAMERA FPA $WORKDIR_TEMPLATE $default_host WORKDIR
+
+    ## generate outroot specific to this exposure (& chip)
+    sprintf outroot "%s/%s.%s.%s/%s/%s.detresid.%s.%s" $WORKDIR $CAMERA $DET_TYPE $DET_ID $EXP_TAG $EXP_TAG $DET_ID $ITERATION
+
+    stdout $LOGDIR/detresid.exp.run.log
+    stderr $LOGDIR/detresid.exp.run.log
+
+    $run = detrend_resid_exp.pl --det_id $DET_ID --iteration $ITERATION --exp_id $EXP_ID --exp_tag $EXP_TAG --det_type $DET_TYPE --camera $CAMERA --outroot $outroot --redirect-output
+    add_standard_args run
+
+    # save the pageName for future reference below
+    options $pageName
+
+    # create example job options as a demonstration
+    if ($VERBOSE > 1)
+      echo command $run
+    end
+    command $run
+  end
+
+  # default exit status
+  task.exit    default
+    process_exit detPendingResidExp $options:0 $JOB_STATUS
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+    book setword detPendingResidExp $options:0 pantaskState TIMEOUT
+  end
+end
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/detrend.stack.pro
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/detrend.stack.pro	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/detrend.stack.pro	(revision 22157)
@@ -0,0 +1,153 @@
+## detrend.stack.pro : globals and support macros : -*- sh -*-
+## this file contains the tasks for running the detrend stacking stage
+## these tasks use the book detPendingStackedImfile
+
+# test for required global variables
+check.globals
+
+book init detPendingStackedImfile
+
+macro detstack.reset
+  book init detPendingStackedImfile
+end
+
+macro detstack.status
+  echo detPendingStackedImfile
+  book listbook detPendingStackedImfile
+end
+
+macro detstack.on
+  task detrend.stack.load
+    active true
+  end
+  task detrend.stack.run
+    active true
+  end
+end
+
+macro detstack.off
+  task detrend.stack.load
+    active false
+  end
+  task detrend.stack.run
+    active false
+  end
+end
+
+# this variable will cycle through the known database names
+$detPendingStackedImfile_DB = 0
+
+# select images ready for detrend_stack.pl
+# new entries are added to detPendingStackedImfile
+# compare the new list with the ones already selected
+task	       detrend.stack.load
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 30
+  npending     1
+
+  stdout NULL
+  stderr $LOGDIR/detrend.stack.load.log
+
+  task.exec
+    if ($DB:n == 0)
+      option DEFAULT
+      command dettool -tostacked -limit 20
+    else
+      # save the DB name for the exit tasks
+      option $DB:$detPendingStackedImfile_DB
+      command dettool -tostacked -limit 20 -dbname $DB:$detPendingStackedImfile_DB
+      $detPendingStackedImfile_DB ++
+      if ($detPendingStackedImfile_DB >= $DB:n) set detPendingStackedImfile_DB = 0
+    end
+  end
+
+  # success
+  task.exit    0
+    # convert 'stdout' to book format
+    ipptool2book stdout detPendingStackedImfile -key det_id:iteration:class_id -uniq -setword dbname $options:0 -setword pantaskState INIT
+    if ($VERBOSE > 2)
+      book listbook detPendingStackedImfile
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup detPendingStackedImfile
+  end
+
+  # locked list
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
+# copy new images, sending job to desired host
+task	       detrend.stack.run
+  periods      -poll $RUNPOLL
+  periods      -exec $RUNEXEC
+  periods      -timeout 30
+
+  task.exec
+    book npages detPendingStackedImfile -var N
+    if ($N == 0) break
+    if ($NETWORK == 0) break
+    
+    # look for new images in detPendingStackedImfile
+    book getpage detPendingStackedImfile 0 -var pageName -key pantaskState INIT
+    if ("$pageName" == "NULL") break
+
+    book setword detPendingStackedImfile $pageName pantaskState RUN
+    book getword detPendingStackedImfile $pageName det_id    -var DET_ID
+    book getword detPendingStackedImfile $pageName iteration -var ITERATION
+    book getword detPendingStackedImfile $pageName det_type  -var DET_TYPE
+    book getword detPendingStackedImfile $pageName class_id  -var CLASS_ID
+    book getword detPendingStackedImfile $pageName camera    -var CAMERA  
+    book getword detPendingStackedImfile $pageName workdir   -var WORKDIR_TEMPLATE
+    book getword detPendingStackedImfile $pageName dbname    -var DBNAME
+    book getword detPendingStackedImfile $pageName reduction -var REDUCTION
+
+    # specify choice of local or remote host based on camera and chip (class_id)
+    set.host.for.camera $CAMERA $CLASS_ID
+
+    # set workdir (interpolate host; see camera.pro for examples)
+    set.workdir.by.camera $CAMERA FPA $WORKDIR_TEMPLATE $default_host WORKDIR
+
+    ## generate outroot specific to this exposure
+    sprintf outroot "%s/%s.%s.%s/%s.%s.%s.%s" $WORKDIR $CAMERA $DET_TYPE $DET_ID $CAMERA $DET_TYPE $DET_ID $ITERATION
+
+    stdout $LOGDIR/detrend.stack.run.log
+    stderr $LOGDIR/detrend.stack.run.log
+
+    $run = detrend_stack.pl --det_id $DET_ID --iteration $ITERATION --class_id $CLASS_ID --det_type $DET_TYPE --camera $CAMERA --outroot $outroot --redirect-output --verbose
+    if ("$REDUCTION" != "NULL")
+      $run = $run --reduction $REDUCTION
+    end
+    add_standard_args run
+
+    # save the pageName for future reference below
+    options $pageName
+
+    # create the command line
+    if ($VERBOSE > 1)
+      echo command $run
+    end
+    command $run
+  end
+
+  # default exit status
+  task.exit    default
+    process_exit detPendingStackedImfile $options:0 $JOB_STATUS
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+    book setword detPendingStackedImfile $options:0 pantaskState TIMEOUT
+  end
+end
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/diff.pro
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/diff.pro	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/diff.pro	(revision 22157)
@@ -0,0 +1,168 @@
+## diff.pro : image difference analysis : -*- sh -*-
+
+## This file contains panTasks definitions for performing the image differencing.
+## After a difference (with associated diff_id) is defined, the difference is performed
+## (tasks in diffSkyfile).
+
+# test for required global variables
+check.globals
+
+$LOGSUBDIR = $LOGDIR/diff
+exec mkdir -p $LOGSUBDIR
+
+### Initialise the books containing the tasks to do
+book init diffSkyfile
+
+### Database lists
+$diffSkycell_DB = 0
+
+### Check status of diffing tasks
+macro diff.status
+  book listbook diffSkyfile
+end
+
+### Reset diffing tasks
+macro diff.reset
+  book init diffSkyfile
+end
+
+### Turn diffing tasks on
+macro diff.on
+  task diff.skycell.load
+    active true
+  end
+  task diff.skycell.run
+    active true
+  end
+end
+
+### Turn diffing tasks off
+macro diff.off
+  task diff.skycell.load
+    active false
+  end
+  task diff.skycell.run
+    active false
+  end
+end
+
+
+### Load tasks for doing the differences
+### Tasks are loaded into diffSkyfile.
+task	       diff.skycell.load
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 30
+  npending     1
+
+  stdout NULL
+  stderr $LOGSUBDIR/diff.skycell.log
+
+  task.exec
+    if ($DB:n == 0)
+      option DEFAULT
+      command difftool -todiffskyfile -limit 20
+    else
+      # save the DB name for the exit tasks
+      option $DB:$diffSkycell_DB
+      command difftool -todiffskyfile -limit 20 -dbname $DB:$diffSkycell_DB
+      $diffSkycell_DB ++
+      if ($diffSkycell_DB >= $DB:n) set diffSkycell_DB = 0
+    end
+  end
+
+  # success
+  task.exit    0
+    # convert 'stdout' to book format
+    ipptool2book stdout diffSkyfile -key diff_id -uniq -setword dbname $options:0 -setword pantaskState INIT
+    if ($VERBOSE > 2)
+      book listbook diffSkyfile
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup diffSkyfile
+  end
+
+  # locked list
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
+### Run tasks for calculating the diff overlaps
+### Tasks are taken from diffSkyfile.
+task	       diff.skycell.run
+  periods      -poll $RUNPOLL
+  periods      -exec $RUNEXEC
+  periods      -timeout 60
+
+  task.exec
+    book npages diffSkyfile -var N
+    if ($N == 0) break
+    if ($NETWORK == 0) break
+    
+    # look for new images in diffSkyfile (pantaskState == INIT)
+    book getpage diffSkyfile 0 -var pageName -key pantaskState INIT
+    if ("$pageName" == "NULL") break
+
+    book setword diffSkyfile $pageName pantaskState RUN
+    book getword diffSkyfile $pageName diff_id -var DIFF_ID
+    book getword diffSkyfile $pageName tess_id -var TESS_DIR
+    book getword diffSkyfile $pageName skycell_id -var SKYCELL_ID
+    book getword diffSkyfile $pageName camera -var CAMERA
+    book getword diffSkyfile $pageName workdir -var WORKDIR_TEMPLATE
+    book getword diffSkyfile $pageName dbname -var DBNAME
+
+    ### XXX Instead of setting these by camera, need to set by skycell_id
+#    set.host.for.camera $CAMERA $DIFF_ID
+#    set.workdir.by.camera $CAMERA $DIFF_ID $WORKDIR_TEMPLATE $default_host WORKDIR
+    host anyhost
+    $WORKDIR = $WORKDIR_TEMPLATE
+
+    basename $TESS_DIR -var TESS_ID
+    sprintf outroot "%s/%s/%s/%s.%s.dif.%s" $WORKDIR $TESS_ID $SKYCELL_ID $TESS_ID $SKYCELL_ID $DIFF_ID
+
+    ## generate output log based on filerule (convert the URI to a PATH)
+    $logfile = `ipp_filename.pl --filerule LOG.EXP --camera $CAMERA --class_id $DIFF_ID --basename $outroot`
+    if ("$logfile" == "") 
+      echo "WARNING: logfile not defined in diff.skycell.run"
+      break
+    end
+
+    stdout $logfile
+    stderr $logfile
+    dirname $logfile -var outpath
+    mkdir $outpath
+
+    $run = diff_skycell.pl --diff_id $DIFF_ID --outroot $outroot
+    add_standard_args run
+
+    # save the pageName for future reference below
+    options $pageName
+
+    # create the command line
+    if ($VERBOSE > 1)
+      echo command $run
+    end
+    command $run
+  end
+
+  # default exit status
+  task.exit    default
+    process_exit diffSkyfile $options:0 $JOB_STATUS
+  end
+
+  # operation timed out?
+  task.exit    timeout
+    showcommand timeout
+    book setword diffSkyfile $options:0 pantaskState TIMEOUT
+  end
+end
+
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/fake.pro
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/fake.pro	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/fake.pro	(revision 22157)
@@ -0,0 +1,154 @@
+## fake.pro : globals and support macros : -*- sh -*-
+## this file contains the tasks for running the phase 0 stage
+## these tasks use the book fakePendingImfile
+
+# test for required global variables
+check.globals
+
+book init fakePendingImfile
+
+macro fake.status
+  book listbook fakePendingImfile
+end
+
+macro fake.reset
+  book init fakePendingImfile
+end
+
+macro fake.on
+  task fake.imfile.load
+    active true
+  end
+  task fake.imfile.run
+    active true
+  end
+end
+
+macro fake.off
+  task fake.imfile.load
+    active false
+  end
+  task fake.imfile.run
+    active false
+  end
+end
+
+# this variable will cycle through the known database names
+$fake_DB = 0
+
+# select images ready for fake analysis
+# new entries are added to fakePendingImfile
+# skip already-present entries
+task	       fake.imfile.load
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 30
+  npending     1
+
+  stdout NULL
+  stderr $LOGDIR/fake.imfile.log
+
+  task.exec
+    if ($DB:n == 0)
+      option DEFAULT
+      command faketool -pendingimfile -limit 20
+    else
+      # save the DB name for the exit tasks
+      option $DB:$fake_DB
+      command faketool -pendingimfile -limit 20 -dbname $DB:$fake_DB
+      $fake_DB ++
+      if ($fake_DB >= $DB:n) set fake_DB = 0
+    end
+  end
+
+  # success
+  task.exit    0
+    # convert 'stdout' to book format
+    ipptool2book stdout fakePendingImfile -key fake_id:class_id -uniq -setword dbname $options:0 -setword pantaskState INIT
+    if ($VERBOSE > 2)
+      book listbook fakePendingImfile
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup fakePendingImfile
+  end
+
+  # locked list
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
+# run the fake_imfile.pl script on pending images
+task	       fake.imfile.run
+  periods      -poll $RUNPOLL
+  periods      -exec $RUNEXEC
+  periods      -timeout 60
+
+  task.exec
+    book npages fakePendingImfile -var N
+    if ($N == 0) break
+    if ($NETWORK == 0) break
+    
+    # look for new images in fakePendingImfile (pantaskState == INIT)
+    book getpage fakePendingImfile 0 -var pageName -key pantaskState INIT
+    if ("$pageName" == "NULL") break
+
+    book setword fakePendingImfile $pageName pantaskState RUN
+    book getword fakePendingImfile $pageName camera -var CAMERA
+    book getword fakePendingImfile $pageName exp_id -var EXP_ID
+    book getword fakePendingImfile $pageName exp_tag -var EXP_TAG
+    book getword fakePendingImfile $pageName fake_id -var FAKE_ID
+    book getword fakePendingImfile $pageName workdir -var WORKDIR_TEMPLATE
+    book getword fakePendingImfile $pageName class_id -var CLASS_ID
+    book getword fakePendingImfile $pageName chip_path_base -var CHIPROOT
+    book getword fakePendingImfile $pageName cam_path_base -var CAMROOT
+    book getword fakePendingImfile $pageName dbname -var DBNAME
+    book getword fakePendingImfile $pageName reduction -var REDUCTION
+
+    # specify choice of local or remote host based on camera and fake (class_id)
+    set.host.for.camera $CAMERA $CLASS_ID
+
+    # set the WORKDIR variable
+    set.workdir.by.camera $CAMERA $CLASS_ID $WORKDIR_TEMPLATE $default_host WORKDIR
+
+    ## generate outroot specific to this exposure (& chip)
+    sprintf outroot "%s/%s/%s.fk.%s" $WORKDIR $EXP_TAG $EXP_TAG $FAKE_ID
+
+    stderr $LOGDIR/fake.imfile.log
+    stderr $LOGDIR/fake.imfile.log
+
+    $run = fake_imfile.pl --exp_id $EXP_ID --fake_id $FAKE_ID --class_id $CLASS_ID --chiproot=$CHIPROOT --camroot=$CAMROOT --camera $CAMERA --outroot $outroot
+    if ("$REDUCTION" != "NULL")
+      $run = $run --reduction $REDUCTION
+    end
+    add_standard_args run
+
+    # save the pageName for future reference below
+    options $pageName
+
+    # create the command line
+    if ($VERBOSE > 1)
+      echo command $run
+    end
+    command $run
+  end
+
+  # default exit status
+  task.exit    default
+    process_exit fakePendingImfile $options:0 $JOB_STATUS
+  end
+
+  # operation timed out?
+  task.exit    timeout
+    showcommand timeout
+    book setword fakePendingImfile $options:0 pantaskState TIMEOUT
+  end
+end
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/flatcorr.pro
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/flatcorr.pro	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/flatcorr.pro	(revision 22157)
@@ -0,0 +1,160 @@
+## flatcorr.pro : tasks to run flat-field correction : -*- sh -*-
+## this file contains the tasks for running the flatcorr stage
+## these tasks use the book 'flatcorrBook'
+
+# test for required global variables
+check.globals
+
+$LOGSUBDIR = $LOGDIR/flatcorr
+exec mkdir -p $LOGSUBDIR
+
+book init flatcoorBook
+
+macro flatcorr.reset
+  book init flatcorrBook
+end
+
+macro flatcorr.status
+  book listbook flatcorrBook
+end
+
+macro flatcorr.on
+  task flatcorr.init
+    active true
+  end
+end
+
+macro flatcorr.off
+  task flatcorr.init
+    active false
+  end
+end
+
+# these variables will cycle through the known ippdb database names
+$calInit_DB = 0
+
+# we have three steps here:
+# 1) get the list of dvo databases:  caltool -dbs
+# 2) define a new flatcorr run for each of the dvo dbs
+#    caltool 
+
+# create new flatcorr entries for the currently known DVO databases
+# run this multiple times once an hour - on pass for each db
+task	       flatcorr.load
+  host         local
+
+  # check the list of pending flatcorr runs
+  periods      -poll 10
+  periods      -exec 900
+  periods      -timeout 60
+  npending 1
+
+  # define the command (does not depend on previous queries)
+  if ($DB:n == 0)
+    command flatcorr -pending
+  else
+    # save the DB name for the exit tasks
+    # note that this DB name refers to the ippdb, not the dvodb
+    option $DB:$calInit_DB
+    command flatcorr -pending -dbname $DB:$calInit_DB
+    $calInit_DB ++
+    if ($calInit_DB >= $DB:n) set calInit_DB = 0
+  end
+
+  # silently drop stdout
+  stdout NULL
+  stderr $LOGSUBDIR/flatcorr.log
+
+  # success
+  task.exit $EXIT_SUCCESS
+    # convert 'stdout' to book format
+    # XXX have ippTools report the dbname?
+    ipptool2book stdout flatcorrBook -key dbname:corr_id -uniq -setword dbname $options:0 -setword pantaskState INIT
+    if ($VERBOSE > 2)
+      book listbook flatcorrBook
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup flatcorrBook
+  end
+
+  # locked list
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
+# create new flatcorr entries for the currently known DVO databases
+# run this multiple times once an hour - one pass for each db
+# can we include information that the db has been updated without a recent cal analysis?
+task	       flatcorr.run
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 60
+  # trange     Hourly@00:00 Hourly@00:10
+  # every hour on the hour
+  npending     1
+
+  # silently drop stdout
+  stdout NULL
+  stderr $LOGSUBDIR/flatcorr.log
+
+  task.exec
+    book npages flatcorrBook -var N
+    if ($N == 0) break
+    if ($NETWORK == 0) break
+    
+    # look for new images in flatcorrBook
+    # the sequencing in this task set is by the pantasksState (see notes.txt)
+    book getpage flatcorrBook 0 -var pageName -key pantaskState INIT
+    if ("$pageName" == "NULL") break
+
+    book setword flatcorrBook $pageName pantaskState     RUN
+    book setword flatcorrBook $pageName region           $REGION
+
+    # XXX probably need to set the output / log based on WORKDIR...
+    book getword flatcorrBook $pageName cal_id      -var ID
+    book getword flatcorrBook $pageName dvodb       -var DVODB
+    book getword flatcorrBook $pageName region      -var REGION
+    book getword flatcorrBook $pageName filter      -var FILTER
+    book getword flatcorrBook $pageName dbname      -var DBNAME
+
+    # specify choice of remote host
+    # set a specific DVO host here 
+    if ($PARALLEL)
+      host anyhost
+    else
+      host local
+    end
+
+    # save the pageName for future reference below
+    options $pageName
+
+    $run = flatcorr_proc.pl --corr_id $ID --dvodb $DVODB --region $REGION --filter $FILTER
+    add_standard_args run
+
+    # create the command line
+    if ($VERBOSE > 1)
+      echo command $run
+    end
+    command $run
+  end
+
+  # default exit status
+  task.exit default
+    process_exit flatcorrBook $options:0 $JOB_STATUS
+  end
+
+  # operation timed out?
+  task.exit    timeout
+    showcommand timeout
+    book setword flatcorrBook $options:0 pantaskState TIMEOUT
+  end
+end
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/isp.det
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/isp.det	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/isp.det	(revision 22157)
@@ -0,0 +1,91 @@
+
+detRunDef MULTI
+
+detRunDef METADATA
+  detRunName   STR BIAS
+  prereq       STR NONE
+  options      STR "-definebyquery -inst ISP-Apogee -workdir path://ISP/detrend/bias -det_type bias -select_exp_type bias -select_dateobs_begin 2007-01-13T00:00:00"
+  dbname       STR isp
+  pantaskState STR DONE
+  detRunID     STR 1
+END
+
+detRunDef METADATA
+  detRunName   STR DARK-warm
+  prereq       STR BIAS
+  options      STR "-definebyquery -inst ISP-Apogee -workdir path://ISP/detrend/dark -det_type dark -select_exp_type dark -select_exp_time_min 0.5 -select_ccd_temp_min -27.5 -select_ccd_temp_max -24.0 -ccd_temp_min -27.5 -ccd_temp_max -24.0"
+  dbname       STR isp
+  pantaskState STR NULL
+END
+
+detRunDef METADATA
+  detRunName   STR DARK-cool
+  prereq       STR BIAS
+  options      STR "-definebyquery -inst ISP-Apogee -workdir path://ISP/detrend/dark -det_type dark -select_exp_type dark -select_exp_time_min 0.5 -select_ccd_temp_max -27.5 -ccd_temp_max -27.5"
+  dbname       STR isp
+  pantaskState STR NULL
+END
+
+detRunDef METADATA
+  detRunName   STR SHUTTER
+  prereq       STR "DARK-warm DARK-cool"
+  options      STR "-definebyquery -inst ISP-Apogee -workdir path://ISP/detrend/shutter -det_type shutter -select_exp_type skyflat -select_exp_time_min 0.05 -select_filter g -select_dateobs_begin 2007-01-14T04:00:00 -select_dateobs_end 2007-01-14T05:00:00 -select_sat_pixel_frac_max 0.1"
+  dbname       STR isp
+  pantaskState STR NULL
+END
+
+detRunDef METADATA
+  detRunName   STR FLAT-g
+  prereq       STR SHUTTER
+  options      STR "-definebyquery -inst ISP-Apogee -workdir path://ISP/detrend/flat/g -det_type flat -select_exp_type skyflat -select_exp_time_min 0.05 -select_sat_pixel_frac_max 0.1 -select_filter g -filter g -select_dateobs_begin 2007-01-14T00:00:00 -select_dateobs_end 2007-01-15T00:00:00"
+  dbname       STR isp
+  pantaskState STR NULL
+END
+
+detRunDef METADATA
+  detRunName   STR FLAT-r
+  prereq       STR SHUTTER
+  options      STR "-definebyquery -inst ISP-Apogee -workdir path://ISP/detrend/flat/r -det_type flat -select_exp_type skyflat -select_exp_time_min 0.05 -select_sat_pixel_frac_max 0.1 -select_filter r -filter r -select_dateobs_begin 2007-01-14T00:00:00 -select_dateobs_end 2007-01-15T00:00:00"
+  dbname       STR isp
+  pantaskState STR NULL
+END
+
+detRunDef METADATA
+  detRunName   STR FLAT-i
+  prereq       STR SHUTTER
+  options      STR "-definebyquery -inst ISP-Apogee -workdir path://ISP/detrend/flat/i -det_type flat -select_exp_type skyflat -select_exp_time_min 0.05 -select_sat_pixel_frac_max 0.1 -select_filter i -filter i -select_dateobs_begin 2007-01-14T00:00:00 -select_dateobs_end 2007-01-15T00:00:00"
+  dbname       STR isp
+  pantaskState STR NULL
+END
+
+detRunDef METADATA
+  detRunName   STR FLAT-z
+  prereq       STR SHUTTER
+  options      STR "-definebyquery -inst ISP-Apogee -workdir path://ISP/detrend/flat/z -det_type flat -select_exp_type skyflat -select_exp_time_min 0.05 -select_sat_pixel_frac_max 0.1 -select_filter z -filter z -select_dateobs_begin 2007-01-14T00:00:00 -select_dateobs_end 2007-01-15T00:00:00"
+  dbname       STR isp
+  pantaskState STR NULL
+END
+
+detRunDef METADATA
+  detRunName   STR FLAT-y
+  prereq       STR SHUTTER
+  options      STR "-definebyquery -inst ISP-Apogee -workdir path://ISP/detrend/flat/y -det_type flat -select_exp_type skyflat -select_exp_time_min 0.05 -select_sat_pixel_frac_max 0.1 -select_filter y -filter y -select_dateobs_begin 2007-01-14T00:00:00 -select_dateobs_end 2007-01-15T00:00:00"
+  dbname       STR isp
+  pantaskState STR NULL
+END
+
+detRunDef METADATA
+  detRunName   STR FRINGE-z
+  prereq       STR FLAT-z
+  options      STR "-definebyquery -inst ISP-Apogee -workdir path://ISP/detrend/fringe/z -det_type fringe -select_exp_type object -select_exp_time_min 0.05 -select_filter z -filter z -select_dateobs_begin 2007-01-14T09:19:00 -select_dateobs_end 2007-01-18"
+  dbname       STR isp
+  pantaskState STR NULL
+END
+
+detRunDef METADATA
+  detRunName   STR FRINGE-y
+  prereq       STR FLAT-y
+  options      STR "-definebyquery -inst ISP-Apogee -workdir path://ISP/detrend/fringe/y -det_type fringe -select_exp_type object -select_exp_time_min 0.05 -select_filter y -filter y -select_dateobs_begin 2007-01-13 -select_dateobs_end 2007-01-18"
+  dbname       STR isp
+  pantaskState STR NULL
+END
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/magic.pro
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/magic.pro	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/magic.pro	(revision 22157)
@@ -0,0 +1,236 @@
+## magic.pro : support for the streak removal : -*- sh -*-
+
+# test for required global variables
+check.globals
+
+$LOGSUBDIR = $LOGDIR/magic
+exec mkdir -p $LOGSUBDIR
+
+### Initialise the books containing the tasks to do
+book init magicToProcess
+book init magicToMask
+book init magicToSkyfileMask
+
+### Database lists
+$magic_DB = 0
+
+### Check status of warping tasks
+#macro warp.status
+#  book listbook warpInputExp
+#  book listbook warpPendingSkyCell
+#end
+
+### Reset warping tasks
+#macro warp.reset
+#  book init warpInputExp
+#  book init warpPendingSkyCell
+#end
+
+### Turn warping tasks on
+# macro warp.on
+#   task warp.exp.load
+#     active true
+#   end
+#   task warp.exp.run
+#     active true
+#   end
+#   task warp.skycell.load
+#     active true
+#   end
+#   task warp.skycell.run
+#     active true
+#   end
+# end
+# 
+# ### Turn warping tasks off
+# macro warp.off
+#   task warp.exp.load
+#     active false
+#   end
+#   task warp.exp.run
+#     active false
+#   end
+#   task warp.skycell.load
+#     active false
+#   end
+#   task warp.skycell.run
+#     active false
+#   end
+# end
+
+# populate magicToProcess
+task	       magic.toprocess
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 30
+  npending     1
+
+  stdout NULL
+  stderr $LOGSUBDIR/magic.toprocess.log
+
+  task.exec
+    if ($DB:n == 0)
+      option DEFAULT
+      run = command magictool -toprocess -limit 20
+      if ($VERBOSE > 1)
+        echo command $run
+      end
+      command $run
+    else
+      # save the DB name for the exit tasks
+      option $DB:$magic_DB
+      command magictool -toprocess -limit 20 -dbname $DB:$magic_DB
+      $magic_DB ++
+      if ($magic_DB >= $DB:n) set magic_DB = 0
+    end
+  end
+
+  # success
+  task.exit    0
+    # convert 'stdout' to book format
+    ipptool2book stdout magicToProcess -key magic_id:node -uniq -setword dbname $options:0 -setword pantaskState INIT
+    if ($VERBOSE > 2)
+      book listbook magicToProcess
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup magicToProcess
+  end
+
+  # locked list
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
+# task	 magic.process
+#   periods      -poll $loadpoll
+#   periods      -exec $loadexec
+#   periods      -timeout 30
+# 
+#   task.exec
+#   end
+# 
+#   # default exit status
+#   task.exit    default
+#     process_exit magicToProcess $options:0 $JOB_STATUS
+#   end
+# 
+#   # operation timed out?
+#   task.exit    timeout
+#     showcommand timeout
+#     book setword magicToProcess $options:0 pantaskState TIMEOUT
+#   end
+# end
+
+# populate magicToMask
+task	       magic.tomask
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 30
+  npending     1
+
+  stdout NULL
+  stderr $LOGSUBDIR/magic.tomask.log
+
+  task.exec
+    if ($DB:n == 0)
+      option DEFAULT
+      run = command magictool -tomask -limit 20
+      if ($VERBOSE > 1)
+        echo command $run
+      end
+      command $run
+    else
+      # save the DB name for the exit tasks
+      option $DB:$magic_DB
+      command magictool -tomask -limit 20 -dbname $DB:$magic_DB
+      $magic_DB ++
+      if ($magic_DB >= $DB:n) set magic_DB = 0
+    end
+  end
+
+  # success
+  task.exit    0
+    # convert 'stdout' to book format
+    ipptool2book stdout magicToMask -key magic_id -uniq -setword dbname $options:0 -setword pantaskState INIT
+    if ($VERBOSE > 2)
+      book listbook magicToProcess
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup magicToMask
+  end
+
+  # locked list
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
+
+# populate magicToMask
+task	       magic.toskyfilemask
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 30
+  npending     1
+
+  stdout NULL
+  stderr $LOGSUBDIR/magic.toskyfilemask.log
+
+  task.exec
+    if ($DB:n == 0)
+      option DEFAULT
+      run = command magictool -toskyfilemask -limit 20
+      if ($VERBOSE > 1)
+        echo command $run
+      end
+      command $run
+    else
+      # save the DB name for the exit tasks
+      option $DB:$magic_DB
+      command magictool -toskyfilemask-limit 20 -dbname $DB:$magic_DB
+      $magic_DB ++
+      if ($magic_DB >= $DB:n) set magic_DB = 0
+    end
+  end
+
+  # success
+  task.exit    0
+    # convert 'stdout' to book format
+    ipptool2book stdout magicToSkyfileMask -key magic_id -uniq -setword dbname $options:0 -setword pantaskState INIT
+    if ($VERBOSE > 2)
+      book listbook magicToProcess
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup magicToSkyfileMask
+  end
+
+  # locked list
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/mkdetrend.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/mkdetrend.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/mkdetrend.config	(revision 22157)
@@ -0,0 +1,43 @@
+
+detRunDef MULTI
+
+# add in the min number of input images and a time range
+detRunDef METADATA
+  detRunName STR BIAS
+  prereq     STR NONE
+  options    STR "-definebyquery -workdir path://EAMWORK -inst SIMTEST -det_type BIAS -select_exp_type BIAS"
+  dbname     STR eamtest
+  pantaskState STR NULL
+END
+
+detRunDef METADATA
+  detRunName STR DARK
+  prereq     STR BIAS
+  options    STR "-definebyquery -workdir path://EAMWORK -inst SIMTEST -det_type DARK -select_exp_type DARK"
+  dbname     STR eamtest
+  pantaskState STR NULL
+END
+
+detRunDef METADATA
+  detRunName STR SHUTTER
+  prereq     STR DARK
+  options    STR "-definebyquery -workdir path://EAMWORK -inst SIMTEST -det_type SHUTTER -filter r -select_exp_type FLAT -select_filter r"
+  dbname     STR eamtest
+  pantaskState STR NULL
+END
+
+detRunDef METADATA
+  detRunName STR FLAT-r
+  prereq     STR SHUTTER
+  options    STR "-definebyquery -workdir path://EAMWORK -inst SIMTEST -det_type FLAT -filter r -select_exp_type FLAT -select_filter r"
+  dbname     STR eamtest
+  pantaskState STR NULL
+END
+
+detRunDef METADATA
+  detRunName STR FLAT-i
+  prereq     STR SHUTTER
+  options    STR "-definebyquery -workdir path://EAMWORK -inst SIMTEST -det_type FLAT -filter i -select_exp_type FLAT -select_filter i"
+  dbname     STR eamtest
+  pantaskState STR NULL
+END
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/mkdetrend.pro
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/mkdetrend.pro	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/mkdetrend.pro	(revision 22157)
@@ -0,0 +1,217 @@
+## mkdetrend.pro : autorun the detrend analysis : -*- sh -*-
+
+# detRun  METADATA  
+#    det_id           STR       3               
+#    iteration        S32       0              
+#    det_type         STR       SHUTTER         
+#    mode             STR       master          
+#    state            STR       run             
+#    filelevel        STR       FPA             
+# END
+
+macro load.detRunDefs
+  if ($0 != 2)
+    echo "USAGE: load.detRunDefs (filename)"
+    break
+  end
+  queueload detruns -x "cat $MODULES:0/$1"
+  ipptool2book detruns detRunDef -key detRunName -uniq -setword pantaskState INIT
+  queuedelete detruns
+end
+
+task detrun.launch
+  host         local
+
+  periods      -poll 1
+  periods      -exec 5
+  periods      -timeout 30
+  active       true
+  npending     1
+
+  # each outstanding detrend run blocks the next one.  only launch the next 
+  # if the current one is done
+  task.exec
+    local i j N found missed detRunName prereq prereqList prereqState cmdOptions dbname
+
+    # how many detRunDefs are waiting to be started?
+    book npages detRunDef -var N -key pantaskState INIT
+    # echo "detrun.launch N: $N"
+    if ($N == 0) break
+
+    $found = 0
+
+    # search the detRunDef book for an entry which is unstarted (state NULL) 
+    # and for which the dependencies are done 
+    for i 0 $N
+      book getpage detRunDef $i -var detRunName -key pantaskState INIT
+      # echo "detrun.launch detRunName: $detRunName"
+      if ("$detRunName" == "NULL") break 
+ 
+      book getword detRunDef $detRunName prereq -var prereqList
+      if (("$prereqList" == "NULL") || ("$prereqList" == "NONE"))
+        $found = 1
+        last
+      end
+
+      list prereq -split $prereqList
+      $missed = 0
+      for j 0 $prereq:n
+        # prereq must exist and be in state of DONE
+        book getword detRunDef $prereq:$j pantaskState -var prereqState
+
+        # echo "detrun.launch prereqState: $prereqState"
+        # echo "detrun.launch detRunName: $detRunName"
+        if ("$prereqState" != "DONE") 
+          $missed = 1
+          last
+        end
+      end
+      if ($missed == 0)
+        $found = 1
+        last
+      end
+
+      if ($found == 0) 
+        if ($VERBOSE >= 2)
+          echo "$detRunName not ready"
+        end
+      else
+        if ($VERBOSE >= 2)
+          echo "$detRunName ready"
+        end
+      end
+    end
+
+    if ($found == 0) break
+
+    # echo "detrun.launch detRunName: $detRunName"
+
+    book getword detRunDef $detRunName options -var cmdOptions
+    book getword detRunDef $detRunName dbname -var dbname
+    book setword detRunDef $detRunName pantaskState INIT
+
+    if ($VERBOSE >= 1)
+      echo "starting detrend $detRunName :"
+      echo "command dettool $cmdOptions -dbname $dbname"
+    end
+
+    options $dbname $detRunName
+    command dettool $cmdOptions -dbname $dbname
+  end
+
+  task.exit $EXIT_SUCCESS
+    local detRunID
+
+    $dbname = $options:0
+    $detRunName = $options:1
+
+    # convert 'stdout' to book format
+    ipptool2book stdout detRun -key det_id -uniq
+    if ($VERBOSE > 2)
+      book listbook detRun
+    end
+
+    # we should have launched only a single detRun
+    book getpage detRun 0 -var detRunID
+    if ("$detRunID" == "NULL") 
+      echo "ERROR:detRun not defined"
+      break
+    end
+
+    # we need to track the detID generated
+    book setword detRunDef $detRunName detRunID $detRunID
+    book setword detRunDef $detRunName pantaskState RUN
+
+    # drop the detRun book after we've grabbed the state
+    book delete detRun
+  end
+end
+
+$detRunNcheck = 0
+
+# loop over the outstanding detRuns and update state
+task detrun.check
+  host         local
+
+  periods      -poll 1
+  periods      -exec 5
+  periods      -timeout 30
+  active       true
+  npending     1
+
+  # each outstanding detrend run blocks the next one.  only launch the next 
+  # if the current one is done
+  task.exec
+    local i N found detRunName detRunID dbname
+
+    book npages detRunDef -var N -key pantaskState RUN
+    if ($N == 0) break
+    if ($detRunNcheck >= $N) 
+      $detRunNcheck = 0
+    end
+
+    # try the next detRun entry which is in state RUN
+    book getpage detRunDef $detRunNcheck -var detRunName -key pantaskState RUN
+    $detRunNcheck ++
+
+    if ("$detRunName" == "NULL")
+      if ($VERBOSE >= 3)
+        echo "no more active detruns (Ncheck = $detRunNcheck)"
+      end
+      break
+    end 
+
+    # info needed to check on current state
+    book getword detRunDef $detRunName detRunID -var detRunID
+    book getword detRunDef $detRunName dbname -var dbname
+
+    if ($VERBOSE >= 2) 
+      echo " checking on $detRunName"
+      echo "command dettool -runs -det_id $detRunID -dbname $dbname"
+    end
+
+    # we need a dettool command which takes a specific det_id and returns
+    # the current state
+    options $dbname $detRunName
+    command dettool -runs -det_id $detRunID -dbname $dbname
+  end
+
+  task.exit $EXIT_SUCCESS
+    local detRunID state
+
+    $dbname = $options:0
+    $detRunName = $options:1
+
+    # convert 'stdout' to book format
+    ipptool2book stdout detRun -key det_id -uniq
+    if ($VERBOSE > 2)
+      book listbook detRun
+    end
+
+    # we should have launched only a single detRun
+    book getpage detRun 0 -var detRunID
+    if ("$detRunID" == "NULL") 
+      echo "ERROR:detRun not defined"
+      break
+    end
+
+    # what is the current state of the detRun?
+    book getword detRun $detRunID state -var state
+
+    # use any other state?
+    if ("$state" == "stop")
+      # update the state for this detRunName, drop detRun page
+      book setword detRunDef $detRunName pantaskState DONE
+      if ($VERBOSE > 1)
+        echo "$detRunName is done"
+      end
+    else
+      if ($VERBOSE > 1)
+        echo "$detRunName is still running"
+      end
+    end
+
+    # drop this book when completed
+    book delete detRun
+  end
+end
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/notes.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/notes.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/notes.txt	(revision 22157)
@@ -0,0 +1,314 @@
+
+  proposal to clean scripts/tasks up somewhat wrt names:
+
+  * scripts do NOT use workdir to change the workdir in the db
+  * thus, what is supplied is actually the outputRoot
+  * require the output root for the scripts
+  * have the calling tasks construct the outputRoot
+  ** replace -workdir with -outroot 
+
+  some filename rules :
+
+  * the db entry WORKDIR is the most abstract version:
+    path://GPC-@HOST@/20080130
+    file://data/@HOST@.0/gpc1/20080130
+    neb:///@HOST@/gpc1/20080130 
+    neb://gpc1/20080130 
+    XXX have nebulous distinguish between neb:///host/path and
+    neb://path?
+
+  * the db WORKDIR entry is carried from step to step unless
+    over-ridden with a -queue command
+
+  * psLib-based binaries (ppImage, etc) may be passed URIs, but not
+    files with the @HOST@ abstraction
+
+  * IPP perl scripts may be passed URIs, but not files with the @HOST@
+    abstraction
+
+  * nebulous : convert neb:///@HOST@/path/name
+
+  *** examples:
+
+  * this tells ppImage to use the ipp004 version for both input and output:
+  ppImage -file neb:///ipp004-v0/gpc1/20080130/o4414g0001o/o4414g0001o.XY01.fits neb:///ipp004-v0/gpc1/20080130/o4414g0001o/o4414g0001o.XY01
+  
+  * we already know the hostname at this point; include it in the call
+  chip_imfile.pl --uri neb:///ipp004-v0/gpc1/20080130/o4414g0001o/o4414g0001o.XY01.fits --workdir neb:///ipp004-v0/gpc1/20080130/
+
+
+
+
+
+2008.01.21
+
+  I'm working on the summit.copy.pro interaction with nebulous (and
+  those of the other ippTasks) and the assignment of jobs and data
+  locations to hosts on the basis of the chip/host relationship.  I
+  have defined a table which relates the chip (class_id) to the
+  desired host name.  There are a few places, and a few slightly
+  different ways, in which this gets used:
+
+  * choice of host for command execution:
+
+    every job which operates on a specific chip must be told the chip
+    host so it can run the job on that machine.  the information is
+    supplied in pantasks to the job with the 'host' command.  I've
+    made a pantasks macro 'set.host.by.camera' which takes the camera
+    and class_id, looks up the match in the chip/host table, and calls
+    the host command with the desired host.  If there is no match
+    (camera not listed in chip/host table or chip not listed in
+    table), the host is set to 'anyhost', which randomly chooses one.
+    I have used this command for all tasks which generate a pcontrol
+    command.  For the tasks which are performed at the camera level,
+    these are set to anyhost.  For the tasks which operate on stacks,
+    we can use the same structure to associate the skycell with a
+    host.  
+
+  * specific output filenames
+
+    In both ippTasks and ippScripts, there are references to files
+    which must correctly located on the host.
+
+  * workdir choice of output directory.   
+
+    the 
+
+  * dsget output target:
+
+    dsget is called by pantasks in the task 'dsget'.  currently, dsget
+    accepts a --filename argument which specifies the output filename
+    to use.  in the nebulous context, this is of the form
+    neb://top/next/stuff, without any host-specific information.  To
+    target the file instance to a specific host, we pass dsget the
+    --volume (name) argument.  The volumes refer to the specific
+    devices (potentially more than one per host), and the names can
+    then be chosen to include the host.  
+  
+2007.10.11
+
+  The calibration suite uses a database table of known/registered DVO
+  databases to be calibrated.  The suite operates on a segment of the
+  sky in each run, nominally a section ~1h west of the current ST.
+
+  caltool -pendingdbs returns a list of entries (mdc format), one for
+  each DVO database in the table.  
+
+  caltool -databases [-active] : returns the list of dvo databases
+  caltool -addcalrun : inserts the stats for a single calibration run
+  caltool -setstate (active/inactive) : modify the state of a dvo
+    database
+  caltool -adddatabase : insert a new database
+
+  XXX a potential source of collisions.  the books are marked by
+  unique keys in the db tables, but a single pantasks run may be
+  addressing multiple databases.  IDs are not unique across
+  databases.  we can solve this by including in the key the dbname
+
+  the calibration suite runs the following tasks, in sequences on each
+  of the databases:
+
+  * resort, relphot, uniphot, relastro.objects, relastro.images
+
+  ** detrend correction: any detrend images which need to be corrected
+     get inserted in the detCorrectedExp table.  
+
+  dettool commands needed:
+  * add a detrend exposure (det_id, exp_id) to the detCorrectedExp table
+  * add a detrend imfile (det_id, exp_id, class_id) to the
+  detCorrectedImfile table
+  * select pending detCorrectedExp
+  * select pending detCorrectedImfiles
+
+
+2007.01.08
+
+  I have updated pantasks to used named tags in named groups to make
+  life much easier when changing the schema of the db tables.  The
+  function ipptool2book convernts the output from an ippTool query in
+  the form of a set of metadata structures (saved in stdout) into a
+  book format.
+
+  dettool -tonormalizedstat     DetrendNormStat
+  dettool -tonormalize          DetrendNorm
+  dettool -tonormalizedexp      DetrendNormExp
+  dettool -toprocessedimfile    DetrendProcessImfiles
+  dettool -toprocessedexp       DetrendProcessExposures
+  dettool -residdetrun          DetrendRejectExp
+  dettool -toresidimfile        DetrendResidImfiles
+  dettool -toresidexp           DetrendResidExposures
+  dettool -tostacked            DetrendStackClass
+  p0tool -pendingimfile         Phase0Imfiles
+  p0tool -pendingexp            Phase0Exposures
+  p2tool -pendingimfile         Phase2Imfiles
+  p3tool -pendingexp            Phase3Exposures
+
+2006.08.28
+
+- ipptools need to use expID:camera:telescope as unique key (ie, not expID alone)
+- p0tool -pendingimfile -simple needs to report the expKey (ie, not expID)
+- 
+
+
+This directory contains the PanTasks scripts which define the steps
+performed by the IPP.  These scripts use the ippTools to decide what
+images need to be processed, and to update the Metadata Database with
+the current state of the analysis.  The analysis is performed using
+IPP programs which are distributed to the cluster computers as needed.
+
+1. The Detrend Creation Scripts
+
+The detrend creation process is among the more complex elements of the
+IPP.  There are N major steps which must be performed:
+
+1.1. Define New Detrend Runs
+
+The script 'detrend.mkruns.pro' defines tasks which initiate new
+Detrend Runs.  There is a separate task for each camera automatically
+processed by the system (mkdetruns.CAMERA).  These tasks are executed
+on a regular, but infrequent basis.  The tasks call external helper
+programs which actually run the command dettools -define.  The helper
+programs are thus responsible for identifying all of the types of
+detrend images to be run for a given camera, as well as the correct
+set of filters to apply.  These tasks are run locally and only affect
+the database.  PanTasks does not track the detrend runs which are
+created.  
+
+- we may need to define these within the PanTasks scripts
+- how frequently are these tasks run (1/day?)
+- if no raw images are selected by the query, no new detrun should be
+  created.
+- we may need different schedules for the dark (bias/dark) and the
+  light (flat/fringe) data.  And yet another schedule for sky data
+  (skyflat/skyfringe).
+
+1.2. Process new raw imfiles
+
+The script 'detrend.process.pro' defines tasks which processes the new
+raw imfiles for any detrend run.  This script defines two tasks,
+'dettool.raw.load' and 'dettool.raw.process'.  The first script
+occasionally polls the Metadata Database to select raw imfiles which
+have been identified as part of a detrend run.  The identified raw
+imfiles are added to an internal queue, DetrendImfilesToProcess.  Any
+raw imfile already in the list is ignored.  The imfiles are added to
+the queue with a state of NEW.  The second task examines the queue for
+imfiles with state NEW and defines a ppImage analysis job for that
+imfile.  The job is sent to pcontrol for processing on the distributed
+cluster.  At this point, the corresponding queue entry is set to the
+state RUN. Upon success, the queue entry is set to DONE and the
+database is notified of the successful operation.  The DONE entries
+are occasionally pruned from the queue; this must be done in the same
+task as the initial query and the update of the list based on the
+database results.  
+
+- we need to define the columns in the output of dettool -raw
+- we need a method to associate an imfile with a node in the cluster
+- we need to perform some appropriate action when the queries fail.
+- we need a method to generate the output URIs
+- we need a method to identify the correct recipe (function of type?)
+
+1.3. Stack new processed imfiles
+
+The script 'detrend.stack.pro' defines tasks which stack the newly
+processed imfiles for any detrend run iteration.  This script defines
+two tasks, 'dettool.stack.load and 'dettool.stack.process.  The first
+script occasionally polls the Metadata Database to select class IDs
+for detrend runs for which the input imfiles have all been processed.
+The identified detRun/classID combination is added to an internal
+queue, DetrendClassIDtoStack.  The entries are added to the queue with
+a state of NEW.  The second task examines the queue for entries with a
+state of NEW.  It defines a ppMerge analysis job for that
+detID/classID.  The helper script selects the corresponding input
+imfiles and passes them to ppMerge.  The job is sent to pcontrol for
+processing on the distributed cluster.  At this point, the
+corresponding queue entry is set to the state RUN. Upon success, the
+queue entry is set to DONE and the database is notified of the
+successful operation.  The DONE entries are occasionally pruned from
+the queue; this must be done in the same task as the initial query and
+the update of the list based on the database results.
+
+- we need to define the columns in the output of dettool -chip -processed
+- we need a method to associate a classID with a node in the cluster
+- we need to perform some appropriate action when the queries fail.
+- we need a method to generate the output URIs (not a function of the imfiles...)
+- we need a method to identify the correct recipe (function of type?)
+
+
+1.4. Normalize stacked masters
+
+The script 'detrend.norm.pro' defines tasks which normalize the newly
+processed stacks for any detrend run iteration.  This script defines
+two tasks, 'dettool.norm.load and 'dettool.norm.process.  The first
+script occasionally polls the Metadata Database to select detrun IDs
+for which the classes have all been stacked.  The identified
+detRunID/iteration combination is added to an internal queue,
+DetrendRunToNormalize.  The entries are added to the queue with a
+state of NEW.  The second task examines the queue for entries with a
+state of NEW.  It defines a ppNorm analysis job for that
+detID/iteration.  The job is sent to pcontrol for processing on the
+distributed cluster.  At this point, the corresponding queue entry is
+set to the state RUN. Upon success, the queue entry is set to DONE and
+the database is notified of the successful operation.  The DONE
+entries are occasionally pruned from the queue; this must be done in
+the same task as the initial query and the update of the list based on
+the database results.
+
+- we need to define the columns in the output of dettool
+- we need to perform some appropriate action when the queries fail.
+- we need a method to generate the output URIs (not a function of the imfiles...)
+- we need a method to identify the correct recipe (function of type?)
+- we need to define the ppNorm operation inputs and outputs better
+
+
+1.5. Generate Resid Imfiles
+
+The script 'detrend.resid.pro' defines tasks which apply the newly
+generated detrend image to the corresponding input images to generate
+the residual images.  This script defines two tasks,
+'dettool.resid.load and 'dettool.resid.process.  The first script
+occasionally polls the Metadata Database to select imfiles for which
+the master detrend images has been normalized.  The identified imfiles
+are added to an internal queue, DetrendResidImfiles.  The entries are
+added to the queue with a state of NEW.  The second task examines the
+queue for entries with a state of NEW.  It defines a ppImage analysis
+job for that imfile.  The job is sent to pcontrol for processing on
+the distributed cluster.  At this point, the corresponding queue entry
+is set to the state RUN. Upon success, the queue entry is set to DONE
+and the database is notified of the successful operation.  The DONE
+entries are occasionally pruned from the queue; this must be done in
+the same task as the initial query and the update of the list based on
+the database results.
+
+- we need to define the columns in the output of dettool
+- can we select the imfiles here without apriori knowledge that the
+  master has been normalized?
+- we need to perform some appropriate action when the queries fail.
+- we need a method to generate the output URIs (not a function of the imfiles...)
+- we need a method to identify the correct recipe (function of type?)
+- we need a method to pass the matching detrend image to ppImage in
+  the correct context (ie, a bias needs to be supplied to the -bias element...)
+
+1.6. Assess Detrend Results
+
+The script 'detrend.assess.pro' defines tasks which assess the
+residual statistics for completed detrend runs.  This script defines
+two tasks, 'dettool.assess.load and 'dettool.assess.process.  The
+first script occasionally polls the Metadata Database to select
+detrunIDs/iterations for which the resid imfiles have all been
+generated.  The identified detruns are added to an internal queue,
+DetrendRunToAssess.  The entries are added to the queue with a state
+of NEW.  The second task examines the queue for entries with a state
+of NEW.  It defines an analysis job for that collection.  The job is
+sent to pcontrol for processing on the distributed cluster.  At this
+point, the corresponding queue entry is set to the state RUN. Upon
+success, the queue entry is set to DONE and the database is notified
+of the successful operation.  The DONE entries are occasionally pruned
+from the queue; this must be done in the same task as the initial
+query and the update of the list based on the database results.
+
+- the assess tool needs to trigger a new run, if needed.
+- what is the command to run on the resid statistics?
+- we need to define the columns in the output of dettool
+- can we select the detrend runs here?
+- we need to perform some appropriate action when the queries fail.
+- we need a method to generate the output URIs (not a function of the imfiles...)
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/pantasks.pro
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/pantasks.pro	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/pantasks.pro	(revision 22157)
@@ -0,0 +1,435 @@
+## pantasks.pro : globals and support macros : -*- sh -*-
+
+# globals used to control system behavior : these should be in uppercase
+$NEBULOUS = 0
+$NETWORK = 1
+$PARALLEL = 1
+$VERBOSE = 1
+$LOGDIR = `pwd`
+$LOGDIR = $LOGDIR/pantasks_logs
+mkdir $LOGDIR
+$KEEP_FAILURES = 0
+
+$LOADPOLL = 0.25
+$LOADEXEC = 5
+$RUNPOLL = 0.5
+$RUNEXEC = 1
+
+$EXIT_SUCCESS     = 0
+$EXIT_UNKNOWN_ERR = 1
+$EXIT_SYS_ERR     = 2
+$EXIT_CONFIG_ERR  = 3
+$EXIT_PROG_ERR    = 4
+$EXIT_DATA_ERR    = 5
+
+# DB lists the database names in use; the default ipprc database
+# is only used if DB:n is 0
+$DB:n = 0
+
+# very basic values: set these with init.copy.mhpcc
+$default_host     = anyhost
+$workdir_template = `pwd`
+
+# user function to add databases by name (avoids duplicates)
+macro add.database
+  if ($0 != 2)
+    echo "USAGE: add.database (db)"
+    break
+  end
+  if ($?DB:n == 0)
+    list DB -add $1
+    return
+  end
+
+  $found = 0
+  for i 0 $DB:n
+    if ($DB:$i == $1) 
+      $found = 1
+      echo "$DB:$i set"
+      last
+    end
+  end
+  
+  if ($found == 0)
+    list DB -add $1
+  end
+end
+
+macro del.database
+  if ($0 != 2)
+    echo "USAGE: del.database (db)"
+    break
+  end
+  if ($?DB:n == 0)
+    return
+  end
+
+  list DB -del $1
+end
+
+macro init.isp
+  list DB -add isp
+end
+
+macro init.essence
+  list DB -add essence_v2
+end
+
+macro init.simtest.local
+  list DB -add simtest
+  $PARALLEL = 0
+  controller exit true
+end
+
+macro init.simtest.default.db
+  $host = `hostname`
+  if ($PARALLEL) 
+    controller exit true
+    controller host add $host
+  end
+end
+
+macro init.simtest
+  list DB -add simtest
+
+  $host = `hostname`
+  if ($PARALLEL) 
+    controller exit true
+    controller host add $host
+  end
+end
+
+# need the ability to activate or deactivate specific tasks...
+
+macro detrend.on
+  detproc.on
+  detstack.on
+  detnorm.on
+  detresid.on
+  detreject.on
+end
+
+macro detrend.off
+  detproc.off
+  detstack.off
+  detnorm.off
+  detresid.off
+  detreject.off
+end
+
+macro detrend.reset
+  detproc.reset
+  detstack.reset
+  detnorm.reset
+  detresid.reset
+  detreject.reset
+end
+
+macro all.on
+  register.on
+  detrend.on
+  chip.on
+  camera.on
+  fake.on
+  warp.on
+  diff.on
+  stack.on
+end
+
+macro all.off
+  register.off
+  detrend.off
+  chip.off
+  camera.off
+  fake.off
+  warp.off
+  diff.off
+  stack.off
+end
+
+macro module.tasks
+  module register.pro
+  module detrend.process.pro
+  module detrend.stack.pro
+  module detrend.norm.pro
+  module detrend.resid.pro
+  module detrend.reject.pro
+  module chip.pro
+  module camera.pro
+  module fake.pro
+  module warp.pro
+  module diff.pro
+  module stack.pro
+end
+
+macro showcommand
+  local n 
+
+  if ($VERBOSE)
+    $command = $taskarg:0
+    for n 1 $taskarg:n
+      $command = $command $taskarg:$n
+    end
+    if ($0 == 2)
+      echo ""
+      echo "$1 for: $command"
+      echo "job exit status: $JOB_STATUS"
+      echo "job host: $JOB_HOSTNAME"
+      echo "job dtime: $JOB_DTIME"
+    else
+      echo "command: $command"
+    end    
+  end
+end
+
+macro add_standard_args
+  if ($0 != 2)
+    echo "Must pass in the command of interest, and this function will supplement"
+    stop
+  end
+
+  local command
+  $command = $$1
+
+  if ($?DBNAME && "$DBNAME" != "DEFAULT")
+    $command = $command --dbname $DBNAME
+  end
+  if ($?NOOP != 0) 
+    if ($NOOP != 0)
+      $command = $command --no-op
+    end
+  end
+  if ($?NOUPDATE != 0) 
+    if ($NOUPDATE != 0)
+      $command = $command --no-update
+    end
+  end
+  $$1 = $command --verbose
+end
+
+
+macro process_exit
+  if ($0 != 4)
+    echo "USAGE: process_exit (bookName) (pageName) (exitCode)"
+    break
+  end
+
+  $bookName = $1
+  $pageName = $2
+  $exitCode = $3
+
+  if ($VERBOSE > 4)
+    echo "*** exit status ***"
+    echo "JOB_STATUS: $JOB_STATUS"
+    echo "JOB_HOSTNAME: $JOB_HOSTNAME"
+    echo "JOB_DTIME: $JOB_DTIME"
+    echo "*** stdout ***"
+    queueprint stdout
+    echo "*** stderr ***"
+    queueprint stderr
+  end
+
+  # success
+  if ($exitCode == $EXIT_SUCCESS) 
+    # the handler scripts update DB the tables; here we just update the page
+    book setword $bookName $pageName pantaskState DONE
+    return
+  end
+
+  # failure related to the data files
+  # jobs which result in DATAERR must have db pantaskState updated 
+  if ($exitCode == $EXIT_DATA_ERR)
+    showcommand failure
+    book setword $bookName $pageName pantaskState DATA_ERR
+    return
+  end
+
+  if ($VERBOSE)
+    echo "*** stdout ***"
+    queueprint stdout
+    echo "*** stderr ***"
+    queueprint stderr
+  end
+
+  # failure related to the data files
+  if ($exitCode == $EXIT_SYS_ERR)
+    # stop
+    showcommand "system failure"
+    book setword $bookName $pageName pantaskState SYS_ERR
+    return
+  end
+
+  # failure related to the data files
+  if ($exitCode == $EXIT_CONFIG_ERR)
+    # stop
+    showcommand "config error"
+    book setword $bookName $pageName pantaskState CONFIG_ERR
+    return
+  end
+
+  # failure related to the data files
+  if ($exitCode == $EXIT_PROG_ERR)
+    # stop
+    showcommand "programming error"
+    book setword $bookName $pageName pantaskState PROG_ERR
+    return
+  end
+
+  # any other exit status
+  showcommand failure
+  book setword $bookName $pageName pantaskState UNKNOWN_ERR
+end
+
+## XXX for the moment, remove all errors
+macro process_cleanup
+  if ($0 != 2)
+    echo "USAGE: process_cleanup (bookname)"
+    break
+  end
+
+  book delpage $1 -key pantaskState DONE
+
+  if (not($KEEP_FAILURES))
+    book delpage $1 -key pantaskState SYS_ERR
+    book delpage $1 -key pantaskState DATA_ERR
+    book delpage $1 -key pantaskState PROG_ERR
+    book delpage $1 -key pantaskState CONFIG_ERR
+    book delpage $1 -key pantaskState UNKNOWN_ERR
+  end
+end
+
+macro set.verbosity
+  if ($0 != 2)
+    echo "USAGE: set.verbosity (level)"
+    break
+  end
+  $VERBOSE = $1
+end
+
+macro show.variables
+  ??
+end
+
+macro set.workdir.by.camera
+  if ($0 != 6)
+    echo "USAGE: set.workdir.by.camera (camera) (class_id) (template) (default) (varname)"
+    break
+  end
+
+  local host default template camera classID varname
+  $camera = $1
+  $classID = $2
+  $template = $3
+  $default = $4
+  $varname = $5
+
+  if ("$template" == "NULL")
+    $varname = $workdir_template
+    echo "WARNING: WORKDIR template not set.  Defaulting to $workdir_template"
+    break
+  end
+
+  # missing camera and/or chiphosts table results in host = NULL
+  book getword chiphosts $camera $classID -var host
+
+  if ("$host" == "NULL")
+    strsub $template @HOST@ $default -var $varname
+  else
+    strsub $template @HOST@ $host -var $varname
+  end
+end
+
+macro set.volume.by.camera
+  if ($0 != 3)
+    echo "USAGE: set.volume.by.camera (camera) (class_id) (template) (default) (varname)"
+    echo " uses global variable NEBULOUS to determine if nebulous is active"
+    break
+  end
+
+  local host default template camera classID varname
+  $camera = $1
+  $classID = $2
+  $template = $3
+  $default = $4
+  $varname = $5
+
+  # missing camera and/or chiphosts table results in host = NULL
+  book getword chiphosts $camera $classID -var host
+
+  if (not($NEBULOUS))
+    $$varname = "NULL"
+    return
+  end
+
+  if ("$host" == "NULL") 
+    $$varname = $default
+    return
+  end
+
+  strsub $template @HOST@ $host -var $varname
+end
+
+macro set.host.for.camera
+  if ($0 != 3)
+    echo "USAGE: set.host.for.camera (camera) (class_id)"
+    break
+  end
+
+  if (not($PARALLEL))
+    host local
+    return
+  end
+
+  # missing camera and/or chiphosts table results in host = NULL
+  book getword chiphosts $1 $2 -var host
+
+  if ("$host" == "NULL")
+    host anyhost
+  else
+    host $host
+  end
+end
+
+macro get.host.for.camera
+  if ($0 != 3)
+    echo "USAGE: get.host.for.camera (camera) (class_id)"
+    break
+  end
+
+  if (not($PARALLEL))
+    echo local
+    return
+  end
+
+  # missing camera and/or chiphosts table results in host = NULL
+  book getword chiphosts $1 $2 -var host
+
+  if ("$host" == "NULL")
+    echo anyhost
+  else
+    echo $host
+  end
+end
+
+macro check.globals
+  if ($?NETWORK == 0)
+    echo "NETWORK not defined: load pantasks.pro first"
+    break
+  end
+  if ($?PARALLEL == 0)
+    echo "PARALLEL not defined: load pantasks.pro first"
+    break
+  end
+  if ($?VERBOSE == 0)
+    echo "VERBOSE not defined: load pantasks.pro first"
+    break
+  end
+  if ($?LOGDIR == 0)
+    echo "LOGDIR not defined: load pantasks.pro first"
+    break
+  end
+  if ($?KEEP_FAILURES == 0)
+    echo "KEEP_FAILURES not defined: load pantasks.pro first"
+    break
+  end
+end
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/pstamp.pro
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/pstamp.pro	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/pstamp.pro	(revision 22157)
@@ -0,0 +1,298 @@
+
+#$VERBOSE = 3
+
+# these variables wil cycle through the known database names
+
+$pstampDS_DB = 0
+$pstampReq_DB = 0
+$pstampJob_DB = 0
+$pstampFin_DB = 0
+
+macro pstamp.reset
+    book init pstampRequest
+    book init pstampJob
+end
+
+pstamp.reset
+
+
+macro pstamp.on
+    task pstamp.request.find
+        active true
+    end
+    task pstamp.request.load
+        active true
+    end
+    task pstamp.request.run
+        active true
+    end
+    task pstamp.request.finish
+        active true
+    end
+
+    task pstamp.job.load
+        active true
+    end
+    task pstamp.job.run
+        active true
+    end
+end
+
+macro pstamp.off
+    task pstamp.request.find
+        active false
+    end
+    task pstamp.request.load
+        active false
+    end
+    task pstamp.request.run
+        active false
+    end
+    task pstamp.request.finish
+        active false
+    end
+
+    task pstamp.job.load
+        active false
+    end
+    task pstamp.job.run
+        active false
+    end
+end
+
+task pstamp.request.find
+    host        local
+
+    periods     -poll $LOADPOLL
+    periods     -exec 10
+    periods     -timeout 30
+    npending    1
+
+    task.exec
+        if ($DB:n == 0)
+            option DEFAULT
+            command pstamp_queue_requests.pl --limit 5
+        else 
+            option $DB:$pstampDS_DB
+            command pstamp_queue_requests.pl --limit 5 --dbname $DB:$pstampDS_DB --verbose
+            $pstampDS_DB ++
+            if ($pstampDS_DB >= $DB:n) set pstampDS_DB = 0
+        end
+    end
+
+    task.exit $EXIT_SUCCESS
+        # echo nothing to do
+    end
+
+    task.exit   default
+        showcommand failure
+    end
+
+    task.exit   timeout
+        showcommand timeout
+    end
+end
+
+task pstamp.request.load
+    host        local
+
+    periods     -poll $LOADPOLL
+    periods     -exec $LOADEXEC
+    periods     -timeout 300
+    npending    1
+
+    task.exec
+        if ($DB:n == 0)
+            option DEFAULT
+            command pstamptool  -pendingreq
+        else 
+            option $DB:$pstampReq_DB
+            command pstamptool  -pendingreq -dbname $DB:$pstampReq_DB
+            $pstampReq_DB ++
+            if ($pstampReq_DB >= $DB:n) set pstampReq_DB = 0
+        end
+    end
+
+    task.exit $EXIT_SUCCESS
+        ipptool2book stdout pstampRequest -key req_id -uniq -setword dbname $options:0 -setword pantaskState INIT
+        if ($VERBOSE > 2)
+            echo starting request
+            book listbook pstampRequest
+        end
+
+        process_cleanup pstampRequest
+    end
+
+    task.exit   default
+        showcommand failure
+    end
+
+    task.exit   timeout
+        showcommand timeout
+    end
+end
+
+task pstamp.request.run
+    periods     -poll $RUNPOLL
+    periods     -exec $RUNEXEC
+    periods     -timeout 30
+    npending    1
+
+    task.exec
+        book npages pstampRequest -var N
+        if ($N == 0) break
+        
+        book getpage pstampRequest 0 -var pageName -key pantaskState INIT
+        if ("$pageName" == "NULL") break
+
+        book setword pstampRequest $pageName pantaskState RUN
+        book getword pstampRequest $pageName req_id -var REQ_ID
+        book getword pstampRequest $pageName dbname -var DBNAME
+
+        $run = pstamp_parser_run.pl --req_id $REQ_ID
+
+        add_standard_args run
+        options $pageName
+
+        if ($VERBOSE > 1) 
+            echo command $run
+        end
+        command $run
+    end
+
+
+    task.exit default
+#        echo pstamp.request.run task.exit status: $JOB_STATUS
+        process_exit pstampRequest $options:0 $JOB_STATUS
+    end
+
+    task.exit timeout
+        showcommand timeout
+        book setword pstampRequest $options:0 pantaskState TIMEOUT
+    end
+end
+
+task pstamp.request.finish
+    #periods     -poll $RUNPOLL
+    #periods     -exec $RUNEXEC
+    periods     -poll $RUNPOLL
+    periods     -exec 5
+    periods     -timeout 30
+    npending    1
+
+    task.exec
+        if ($DB:n == 0)
+            option DEFAULT
+            command pstampfinish
+        else 
+            option $DB:$pstampFin_DB
+            command pstampfinish -dbname $DB:$pstampFin_DB
+            $pstampFin_DB ++
+            if ($pstampFin_DB >= $DB:n) set pstampFin_DB = 0
+        end
+    end
+
+    task.exit $EXIT_SUCCESS
+        # nothing to do
+    end
+    task.exit default
+        showcommand failure
+    end
+
+    task.exit timeout
+        showcommand timeout
+    end
+end
+
+task pstamp.job.load
+    host        local
+
+    periods     -poll $LOADPOLL
+    periods     -exec $LOADEXEC
+    periods     -timeout 30
+    npending    1
+
+    task.exec
+        if ($DB:n == 0)
+            option DEFAULT
+            command pstamptool -pendingjob -limit 10
+        else
+            option $DB:$pstampJob_DB
+            command pstamptool -pendingjob -limit 10 -dbname $DB:$pstampJob_DB
+            $pstampJob_DB ++
+            if ($pstampJob_DB >= $DB:n) set pstampJob_DB = 0
+        end
+    end
+
+    task.exit $EXIT_SUCCESS
+        # echo ipptool2book stdout pstampJob -key job_id -setword dbname $options:0 -setword pantaskState INIT
+        ipptool2book stdout pstampJob -key job_id -uniq -setword dbname $options:0 -setword pantaskState INIT
+
+        book npages pstampJob -var N
+        if ($VERBOSE > 2)
+            book listbook pstampJob
+        end
+
+        # delete existing entries in the appropriate pantaskStates
+        process_cleanup pstampJob
+    end
+
+    task.exit   default
+        showcommand failure
+    end
+
+    task.exit   timeout
+        showcommand timeout
+    end
+end
+
+task pstamp.job.run
+    periods     -poll $RUNPOLL
+    periods     -exec $RUNEXEC
+    periods     -timeout 1200
+    npending    1
+
+    task.exec
+        book npages pstampJob -var N
+        if ($N == 0) break
+        
+        book getpage pstampJob 0 -var pageName -key pantaskState INIT
+        if ("$pageName" == "NULL") break
+
+        #echo pageName: $pageName
+
+        book setword pstampJob $pageName pantaskState RUN
+        book getword pstampJob $pageName job_id -var JOB_ID
+        book getword pstampJob $pageName dbname -var DBNAME
+
+        if ($VERBOSE > 1) 
+            book listpage pstampJob $pageName
+        end
+
+
+        $run = pstamp_job_run.pl --job_id $JOB_ID
+        add_standard_args run
+
+        options $pageName
+
+        if ($VERBOSE > 1) 
+            echo command $run
+        end
+        command $run
+    end
+
+
+    task.exit default
+        if ($VERBOSE > 1)
+            echo pstamp.job.run task.exit $JOB_ID status: $JOB_STATUS
+        end
+        process_exit pstampJob $options:0 $JOB_STATUS
+    end
+
+    task.exit timeout
+        echo pstamp.job.run task.timeout $JOB_ID status: $JOB_STATUS
+        process_exit pstampJob $options:0 $JOB_STATUS
+        showcommand timeout
+        book setword pstampJob $options:0 pantaskState TIMEOUT
+    end
+end
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/register.pro
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/register.pro	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/register.pro	(revision 22157)
@@ -0,0 +1,304 @@
+## register.pro : tasks for image registration (insert into database) : -*- sh -*-
+## this file contains the tasks for running the registration stage
+## these tasks use the books regPendingImfile and regPendingExp
+
+# test for required global variables
+check.globals
+
+book init regPendingImfile
+book init regPendingExp
+
+macro register.reset
+  book init regPendingImfile
+  book init regPendingExp
+end
+
+macro register.status
+  book listbook regPendingImfile
+  book listbook regPendingExp
+end
+
+macro register.on
+  task register.imfile.load
+    active true
+  end
+  task register.imfile.run
+    active true
+  end
+  task register.exp.load
+    active true
+  end
+  task register.exp.run
+    active true
+  end
+end
+
+macro register.off
+  task register.imfile.load
+    active false
+  end
+  task register.imfile.run
+    active false
+  end
+  task register.exp.load
+    active false
+  end
+  task register.exp.run
+    active false
+  end
+end
+
+# these variables will cycle through the known database names
+$regPendingImfile_DB = 0
+$regPendingExp_DB = 0
+
+# select images ready for register analysis
+# new entries are added to regPendingImfile
+# compare the new list with the ones already selected
+task	       register.imfile.load
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 30
+  npending     1
+
+  # silently drop stdout
+  stdout NULL
+  stderr $LOGDIR/register.imfile.load.log
+
+  # select entries from the current DB; cycle to the next DB, if it exists
+  # iff the DB list is not set, use the value defined in .ipprc
+  task.exec
+    if ($DB:n == 0)
+      option DEFAULT
+      command regtool -pendingimfile -limit 20
+    else
+      # save the DB name for the exit tasks
+      option $DB:$regPendingImfile_DB
+      command regtool -pendingimfile -limit 20 -dbname $DB:$regPendingImfile_DB
+      $regPendingImfile_DB ++
+      if ($regPendingImfile_DB >= $DB:n) set regPendingImfile_DB = 0
+    end
+  end
+
+  # success
+  task.exit $EXIT_SUCCESS
+    # convert 'stdout' to book format
+    ipptool2book stdout regPendingImfile -key exp_id:tmp_class_id -uniq -setword dbname $options:0 -setword pantaskState INIT
+    book shuffle regPendingImfile
+    if ($VERBOSE > 2)
+      book listbook regPendingImfile
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup regPendingImfile
+  end
+
+  # locked list
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
+# run the register_imfile.pl script on pending images
+task	       register.imfile.run
+  periods      -poll $RUNPOLL
+  periods      -exec $RUNEXEC
+  periods      -timeout 30
+
+  task.exec
+    if ($NETWORK == 0) break
+    book npages regPendingImfile -var N
+    if ($N == 0)
+        periods -exec $RUNEXEC
+        break
+    end
+    periods -exec 0.05 
+    
+    # look for new images in regPendingImfile
+    book getpage regPendingImfile 0 -var pageName -key pantaskState INIT
+    if ("$pageName" == "NULL") break
+
+    book setword regPendingImfile $pageName pantaskState RUN
+
+    book getword regPendingImfile $pageName exp_id       -var EXP_ID
+    book getword regPendingImfile $pageName tmp_exp_name -var TMP_EXP_NAME
+    book getword regPendingImfile $pageName tmp_camera   -var TMP_CAMERA
+    book getword regPendingImfile $pageName tmp_class_id -var TMP_CLASS_ID
+    book getword regPendingImfile $pageName uri          -var URI
+    book getword regPendingImfile $pageName workdir      -var WORKDIR_TEMPLATE
+    book getword regPendingImfile $pageName dbname       -var DBNAME
+
+    # EXP_TAG is used to generate the unique, but human-readable, filenames
+    sprintf EXP_TAG "%s.%s" $TMP_EXP_NAME $EXP_ID
+
+    # specify choice of remote host
+    set.host.for.camera $TMP_CAMERA $TMP_CLASS_ID 
+
+    # set the WORKDIR variable
+    set.workdir.by.camera $TMP_CAMERA $TMP_CLASS_ID $WORKDIR_TEMPLATE $default_host WORKDIR
+
+    # notes on how this works:
+    # -- raw workdir examples:
+    # file://data/@HOST@.0/gpc1/20080130
+    # neb:///@HOST@-vol0/gpc1/20080130 (need to supply volname?, or are we re-defining this each time?)
+    # -- out workdir examples:
+    # file://data/ipp004.0/gpc1/20080130
+    # neb:///ipp004-vol0/gpc1/20080130
+
+    ## generate outroot specific to this exposure (& chip)
+    sprintf logfile "%s/%s/%s.reg.%s.log" $WORKDIR $EXP_TAG $EXP_TAG $TMP_CLASS_ID
+
+    stdout $LOGDIR/register.imfile.run.log
+    stderr $LOGDIR/register.imfile.run.log
+
+    # XXX register_imfile.pl differs from the standard script : it does not have an 'outroot' argument, and it does not take '--redirect'
+    $run = register_imfile.pl --exp_id $EXP_ID --tmp_class_id $TMP_CLASS_ID --tmp_exp_name $TMP_EXP_NAME --uri $URI --logfile $logfile
+    add_standard_args run
+
+    # save the pageName for future reference below
+    options $pageName
+
+    # create the command line
+    if ($VERBOSE > 1)
+      echo command $run
+    end
+    command $run
+  end
+
+  # default exit status
+  task.exit default
+    process_exit regPendingImfile $options:0 $JOB_STATUS
+  end
+
+  # operation timed out?
+  task.exit    timeout
+    showcommand timeout
+    book setword regPendingImfile $options:0 pantaskState TIMEOUT
+  end
+end
+
+# select exposures ready for register_exp.pl
+task	       register.exp.load
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 30
+  npending     1
+
+  stdout NULL
+  stderr $LOGDIR/register.exp.load.log
+
+  task.exec
+    if ($DB:n == 0)
+      option DEFAULT
+      command regtool -pendingexp -limit 20
+    else
+      # save the DB name for the exit tasks
+      option $DB:$regPendingExp_DB
+      command regtool -pendingexp -limit 20 -dbname $DB:$regPendingExp_DB
+      $regPendingExp_DB ++
+      if ($regPendingExp_DB >= $DB:n) set regPendingExp_DB = 0
+    end
+  end
+
+  # success
+  task.exit $EXIT_SUCCESS
+    # convert 'stdout' to book format
+    ipptool2book stdout regPendingExp -key exp_id -uniq -setword dbname $options:0 -setword pantaskState INIT
+    if ($VERBOSE > 2)
+      book listbook regPendingExp
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup regPendingExp
+  end
+
+  # default exit status
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
+# run the register_exp.pl script on pending exposures
+task	       register.exp.run
+  periods      -poll $RUNPOLL
+  periods      -exec $RUNEXEC
+  periods      -timeout 30
+
+  task.exec
+    book npages regPendingExp -var N
+    if ($N == 0) break
+    if ($NETWORK == 0) break
+    
+    # look for new images in regPendingExp (pantaskState == INIT)
+    book getpage regPendingExp 0 -var pageName -key pantaskState INIT
+    if ("$pageName" == "NULL") break
+
+    book setword regPendingExp $pageName pantaskState RUN
+    book getword regPendingExp $pageName exp_id       -var EXP_ID
+    book getword regPendingExp $pageName tmp_exp_name -var TMP_EXP_NAME
+    book getword regPendingExp $pageName tmp_camera   -var TMP_CAMERA
+    book getword regPendingExp $pageName workdir      -var WORKDIR_TEMPLATE
+    book getword regPendingExp $pageName dbname       -var DBNAME
+
+    # EXP_TAG is used to generate the unique, but human-readable, filenames
+    sprintf EXP_TAG "%s.%s" $TMP_EXP_NAME $EXP_ID
+
+    # specify choice of remote host
+    set.host.for.camera $TMP_CAMERA $TMP_CLASS_ID 
+
+    # set the WORKDIR variable
+    set.workdir.by.camera $TMP_CAMERA $TMP_CLASS_ID $WORKDIR_TEMPLATE $default_host WORKDIR
+
+    # notes on how this works:
+    # -- raw workdir examples:
+    # file://data/@HOST@.0/gpc1/20080130
+    # neb:///@HOST@-vol0/gpc1/20080130 (need to supply volname?, or are we re-defining this each time?)
+    # -- out workdir examples:
+    # file://data/ipp004.0/gpc1/20080130
+    # neb:///ipp004-vol0/gpc1/20080130
+    ## generate output log based on filerule
+    sprintf logfile "%s/%s/%s.reg.log" $WORKDIR $EXP_TAG $EXP_TAG
+
+    stdout $LOGDIR/register.exp.run.log
+    stderr $LOGDIR/register.exp.run.log
+
+    $run = register_exp.pl --exp_id $EXP_ID --exp_tag $EXP_TAG --logfile $logfile
+    add_standard_args run
+
+    # save the pageName for future reference below
+    options $pageName
+
+    # create the command line
+    if ($VERBOSE > 1)
+      echo command $run
+    end
+    command $run
+  end
+
+  # success
+  task.exit default
+    process_exit regPendingExp $options:0 $JOB_STATUS
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+    book setword regPendingExp $options:0 pantaskState TIMEOUT
+  end
+end
+
+## XXX add a global path to output files  
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/replicate.pro
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/replicate.pro	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/replicate.pro	(revision 22157)
@@ -0,0 +1,168 @@
+## replicate.pro : tasks for data replication : -*- sh -*-
+## this file contains the tasks for maintaining duplicates as needed in Nebulous
+## these tasks use the books replicatePending
+
+# test for required global variables
+check.globals
+
+$NEB_DB    = nebulous
+$NEB_HOST  = alala
+$NEB_USER  = jhipp
+$NEB_PASS  = jhipp
+
+$LOGSUBDIR = $LOGDIR/replicate
+exec mkdir -p $LOGSUBDIR
+
+book init replicatePending
+
+macro replicate.reset
+  book init replicatePending
+end
+
+macro replicate.status
+  book listbook replicatePending
+end
+
+macro replicate.on
+  task replicate.load
+    active true
+  end
+  task replicate.run
+    active true
+  end
+end
+
+macro replicate.off
+  task replicate.load
+    active false
+  end
+  task replicate.run
+    active false
+  end
+end
+
+macro set.host.for.replicate
+  if ($0 != 2)
+    echo "USAGE: set.host.for.replicate (hostname)"
+    break
+  end
+
+  if (not($PARALLEL))
+    host local
+    return
+  end
+
+# parse volume name
+
+  if ("$1" == "NULL")
+    host anyhost
+  else
+    host $1
+  end
+end
+
+# the replicate process interacts with only the single Nebulous server
+
+# select Nebulous objects which desire additional copies
+task	       replicate.load
+  host         local
+
+  # modify these after the tasks are tested
+  periods      -poll 10
+  periods      -exec 1
+  periods      -timeout 300
+  npending     1
+
+  # silently drop stdout
+  stdout NULL
+  stderr $LOGSUBDIR/replicate.log
+
+  task.exec
+      # command does not need to be dynamic, but having it so allows us to adjust the periods
+      # so that we dont have to wait 10 minutes for things to start up
+      command neb-admin --host $NEB_HOST --db $NEB_DB --user $NEB_USER --pass $NEB_PASS --pendingreplicate --limit 2000
+      periods      -exec 600
+
+  end
+
+  # success
+  task.exit $EXIT_SUCCESS
+    # convert 'stdout' to book format
+    ipptool2book stdout replicatePending -key key -uniq -setword pantaskState INIT
+
+    if ($VERBOSE > 2)
+      book listbook replicatePending
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup replicatePending
+  end
+
+  # locked list
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
+# create the desired replicas
+task	       replicate.run
+  periods      -poll 5
+  periods      -exec 30
+  periods      -timeout 30
+
+  task.exec
+    book npages replicatePending -var N
+    if ($NETWORK == 0) break
+    if ($N == 0)
+        periods -exec 30
+        break
+    end
+    periods -exec 0.1
+    
+    # look for new objects in replicatePending
+    book getpage replicatePending 0 -var pageName -key pantaskState INIT
+    if ("$pageName" == "NULL") break
+
+    book setword replicatePending $pageName pantaskState RUN
+
+    # XXX what values do I need to get back?
+    book getword replicatePending $pageName key         -var KEY
+    book getword replicatePending $pageName need_copies -var NEED_COPIES
+    book getword replicatePending $pageName volume_name -var VOLUME_NAME
+    book getword replicatePending $pageName volume_host -var VOLUME_HOST
+
+    set.host.for.replicate $VOLUME_HOST
+
+    stdout NULL
+    stderr $LOGSUBDIR/replicate.log
+
+    # these operations do not require a database to be specified
+    $run = neb-replicate --copies $NEED_COPIES $KEY
+
+    # save the pageName for future reference below
+    options $pageName
+
+    # create the command line
+    if ($VERBOSE > 1)
+      echo command $run
+    end
+    command $run
+  end
+
+  # default exit status
+  task.exit default
+    process_exit replicatePending $options:0 $JOB_STATUS
+  end
+
+  # operation timed out?
+  task.exit    timeout
+    showcommand timeout
+    book setword replicatePending $options:0 pantaskState TIMEOUT
+  end
+end
+
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/simtest.auto
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/simtest.auto	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/simtest.auto	(revision 22157)
@@ -0,0 +1,98 @@
+
+automate MULTI
+
+# we run in the sequence BLOCK -> CHECK -> LAUNCH -> BLOCK -> CHECK
+# success on CHECK -> LAUNCH
+# failure on BLOCK -> CHECK
+
+# XXX these steps all refer to "@DBNAME@"; they need to be more generic, even for the basic simtest analysis
+
+automate METADATA
+  name       STR BIAS
+  check      STR "regtool -processedexp -exp_type BIAS -inst SIMTEST -dbname @DBNAME@"
+  ncheck     S32 20
+  launch     STR "dettool -definebyquery -workdir file://@CWD@/detwork -inst SIMTEST -det_type BIAS -select_exp_type BIAS -dbname @DBNAME@"
+  block      STR "dettool -runs -active -det_type BIAS -dbname @DBNAME@"
+END
+
+automate METADATA
+  name       STR DARK
+  check      STR "detselect -search -inst SIMTEST -det_type BIAS -dbname @DBNAME@"
+  launch     STR "dettool -definebyquery -workdir file://@CWD@/detwork -inst SIMTEST -det_type DARK -select_exp_type DARK -dbname @DBNAME@"
+  block      STR "dettool -runs -active -det_type DARK -dbname @DBNAME@"
+END
+ 
+automate METADATA
+  name       STR SHUTTER
+  check      STR "detselect -search -inst SIMTEST -det_type DARK -dbname @DBNAME@"
+  launch     STR "dettool -definebyquery -workdir file://@CWD@/detwork -inst SIMTEST -det_type SHUTTER -filter r -select_exp_type FLAT -select_filter r -dbname @DBNAME@"
+  block      STR "dettool -runs -active -det_type SHUTTER -dbname @DBNAME@"
+END
+
+automate METADATA
+  name       STR FLAT-r
+  check      STR "detselect -search -inst SIMTEST -det_type SHUTTER -dbname @DBNAME@"
+  launch     STR "dettool -definebyquery -workdir file://@CWD@/detwork -inst SIMTEST -det_type FLAT -filter r -select_exp_type FLAT -select_filter r -dbname @DBNAME@"
+  block      STR "dettool -runs -active -det_type FLAT -filter r -dbname @DBNAME@"
+END
+
+automate METADATA
+  name       STR FLAT-i
+  check      STR "detselect -search -inst SIMTEST -det_type SHUTTER -dbname @DBNAME@"
+  launch     STR "dettool -definebyquery -workdir file://@CWD@/detwork -inst SIMTEST -det_type FLAT -filter i -select_exp_type FLAT -select_filter i -dbname @DBNAME@"
+  block      STR "dettool -runs -active -det_type FLAT -filter i -dbname @DBNAME@"
+END
+
+automate METADATA
+  name       STR OBJECT-i
+  check      STR "detselect -dbname @DBNAME@ -search    -inst SIMTEST -filter i -det_type FLAT"
+  launch     STR "chiptool  -dbname @DBNAME@ -updaterun -inst SIMTEST -filter i -label wait -set_label proc"
+  block      STR NONE
+END
+
+automate METADATA
+  name       STR OBJECT-r
+  check      STR "detselect -dbname @DBNAME@ -search    -inst SIMTEST -filter r -det_type FLAT"
+  launch     STR "chiptool  -dbname @DBNAME@ -updaterun -inst SIMTEST -filter r -label wait -set_label proc"
+  block      STR NONE
+END
+
+### automate METADATA
+###   name       STR OBJECT-r
+###   check      STR "detselect -search -inst SIMTEST -det_type FLAT -dbname @DBNAME@ -filter r"
+###   launch     STR "chiptool -unblock -label simtest-r -dbname @DBNAME@"
+###   block      STR "chiptool -unmasked -label simtest-r -dbname @DBNAME@"
+### END
+
+### automate METADATA
+###   name       STR OBJECT-i
+###   check      STR "detselect -search -inst SIMTEST -det_type FLAT -dbname @DBNAME@ -filter i"
+###   launch     STR "chiptool -unblock -label simtest-i -dbname @DBNAME@"
+###   block      STR "chiptool -unmasked -label simtest-i -dbname @DBNAME@"
+### END
+
+### there is a weakness in the label / block business: the labels are
+### generic, and the blocks are against those fairly generic words.
+### that makes it difficult to block and unblock science exposures
+### based on different detrend types that are available.  For example,
+### to block the r-band against the absence of the desired r-band
+### flat, we would need to specify a label specific to the r-band
+### images and remove that block when ready.
+
+### a better approach might be to modify the labels rather than the
+### blocks.  we can define, eg, chiptool -set_label -definebyquery to
+### turn assign the label names based on various properties of the
+### interesting images.
+
+### here is what the automate element might look like for such a circumstance:
+
+### automate METADATA
+###   name       STR OBJECT-i
+###   block      STR "chiptool -dbname @DBNAME@ -unmasked -label wait -filter i -time_begin 2008/1/1 -time_end 2008/1/2"
+###   check      STR "detselect -dbname @DBNAME@ -search -inst SIMTEST -det_type FLAT -dbname @DBNAME@ -filter i -time_begin 2008/1/1 -time_end 2008/1/2" 
+###   launch     STR "chiptool -dbname @DBNAME@ -set_label proc -label wait -dbname @DBNAME@ -time_begin 2008/1/1 -time_end 2008/1/2"
+### END
+
+### XXX still not quite there....
+### the 'checks' or maybe the 'launch' needs to include a '-n_exp_min' ?
+### 
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/simtest.basic.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/simtest.basic.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/simtest.basic.config	(revision 22157)
@@ -0,0 +1,104 @@
+
+# define the simulated data to be generated
+SEQUENCE MULTI
+
+SEQUENCE METADATA
+  OBSTYPE    STR BIAS
+  CAMERA     STR SIMTEST
+  NIMAGES    S32  20
+END
+
+SEQUENCE METADATA
+  OBSTYPE    STR DARK
+  CAMERA     STR SIMTEST
+  @EXPTIMES  F32  30.0, 300.0
+  @NIMAGES   S32  10,   10
+END
+
+SEQUENCE METADATA
+  OBSTYPE    STR FLAT
+  CAMERA     STR SIMTEST
+
+  FILTERS    STR r,r,i
+  @EXPTIMES  F32 0.1,20.0,20.0
+
+  NSETUP     S32 5
+END
+
+SEQUENCE METADATA
+  OBSTYPE    STR FLAT
+  CAMERA     STR SIMTEST
+
+  FILTERS    STR r,r,r,r,r
+  @EXPTIMES  F32 0.5,1.0,2.0,5.0,10.0
+
+  NSETUP     S32 1
+END
+
+SEQUENCE METADATA
+  OBSTYPE    STR OBJECT
+  CAMERA     STR SIMTEST
+
+  CENTER.RA  F32 150.119167 
+  CENTER.DEC F32   2.205833
+
+  OFFSET.RA  F32 3600.0 # linear offset in arcsec (do not include cos(DEC) correction)
+  OFFSET.DEC F32 3600.0 # linear offset in arcsec (do not include cos(DEC) correction)
+  OFFSET.NR  S32 1
+  OFFSET.ND  S32 1
+
+  DITHER.RA  F32 60.0   # linear offset in arcsec (do not include cos(DEC) correction)
+  DITHER.DEC F32 60.0   # linear offset in arcsec (do not include cos(DEC) correction)
+  DITHER.NR  S32 3
+  DITHER.ND  S32 2
+
+  DVODB      STR /data/alala.0/ipp/ippRefs/catdir.synth.grizy 
+  FILTERS    STR r,i
+  @EXPTIMES  F32 10.00,10.00
+  @SKYMAGS   F32 20.86,20.15
+
+  # XXX use a more realistic IQ distribution...
+  IQ_MIN     F32 0.55
+  IQ_MAX     F32 1.25
+  
+  # rotation sequence
+  POS_MIN    F32 0.0
+  POS_MAX    F32 0.0
+  POS_DELTA  F32 1.0
+
+END
+
+
+SEQUENCE METADATA
+  OBSTYPE    STR OBJECT
+  CAMERA     STR SIMTEST
+
+  CENTER.RA  F32 150.119167 
+  CENTER.DEC F32   2.205833
+
+  OFFSET.RA  F32 3600.0 # linear offset in arcsec (do not include cos(DEC) correction)
+  OFFSET.DEC F32 3600.0 # linear offset in arcsec (do not include cos(DEC) correction)
+  OFFSET.NR  S32 1
+  OFFSET.ND  S32 1
+
+  DITHER.RA  F32 30.0   # linear offset in arcsec (do not include cos(DEC) correction)
+  DITHER.DEC F32 30.0   # linear offset in arcsec (do not include cos(DEC) correction)
+  DITHER.NR  S32 1
+  DITHER.ND  S32 1
+
+  DVODB      STR /data/alala.0/ipp/ippRefs/catdir.synth.grizy 
+  FILTERS    STR r,r,i,i
+  @EXPTIMES  F32 5.0, 240.0, 5.0, 240.0
+  @SKYMAGS   F32 20.86,20.86,20.15,20.15
+
+  # XXX use a more realistic IQ distribution...
+  IQ_MIN     F32 0.55
+  IQ_MAX     F32 1.25
+  
+  # rotation sequence
+  POS_MIN    F32 0.0
+  POS_MAX    F32 0.0
+  POS_DELTA  F32 1.0
+
+END
+
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/simtest.det
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/simtest.det	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/simtest.det	(revision 22157)
@@ -0,0 +1,42 @@
+
+detRunDef MULTI
+
+detRunDef METADATA
+  detRunName STR BIAS
+  prereq     STR NONE
+  options    STR "-definebyquery -workdir path://EAMWORK -inst SIMTEST -det_type BIAS -select_exp_type BIAS"
+  dbname     STR eamtest
+  pantaskState STR NULL
+END
+
+detRunDef METADATA
+  detRunName STR DARK
+  prereq     STR BIAS
+  options    STR "-definebyquery -workdir path://EAMWORK -inst SIMTEST -det_type DARK -select_exp_type DARK"
+  dbname     STR eamtest
+  pantaskState STR NULL
+END
+
+detRunDef METADATA
+  detRunName STR SHUTTER
+  prereq     STR DARK
+  options    STR "-definebyquery -workdir path://EAMWORK -inst SIMTEST -det_type SHUTTER -filter r -select_exp_type FLAT -select_filter r"
+  dbname     STR eamtest
+  pantaskState STR NULL
+END
+
+detRunDef METADATA
+  detRunName STR FLAT-r
+  prereq     STR SHUTTER
+  options    STR "-definebyquery -workdir path://EAMWORK -inst SIMTEST -det_type FLAT -filter r -select_exp_type FLAT -select_filter r"
+  dbname     STR eamtest
+  pantaskState STR NULL
+END
+
+detRunDef METADATA
+  detRunName STR FLAT-i
+  prereq     STR SHUTTER
+  options    STR "-definebyquery -workdir path://EAMWORK -inst SIMTEST -det_type FLAT -filter i -select_exp_type FLAT -select_filter i"
+  dbname     STR eamtest
+  pantaskState STR NULL
+END
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/simtest.megacam.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/simtest.megacam.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/simtest.megacam.config	(revision 22157)
@@ -0,0 +1,67 @@
+
+# define the simulated data to be generated
+SEQUENCE MULTI
+
+SEQUENCE METADATA
+  OBSTYPE    STR BIAS
+  CAMERA     STR MEGACAM
+
+  BIAS.LEVEL F32 100.0
+  BIAS.RANGE F32  10.0
+
+  NIMAGE     S32 15
+END
+
+SEQUENCE METADATA
+  OBSTYPE    STR DARK
+  CAMERA     STR MEGACAM
+
+  DARK.MIN   F32 100.0
+  DARK.MAX   F32 200.0
+
+  NIMAGE     S32 15
+END
+
+SEQUENCE METADATA
+  OBSTYPE    STR FLAT
+  CAMERA     STR MEGACAM
+
+  FILTERS    STR g,r,i
+  @EXPTIMES  F32 60.0,30.0,30.0
+
+  NSETUP     S32 15
+END
+
+SEQUENCE_SKIP METADATA
+  OBSTYPE    STR OBJECT
+  CAMERA     STR MEGACAM
+
+  CENTER.RA  F32 10.0 
+  CENTER.DEC F32 60.0
+
+  OFFSET.RA  F32 3600.0 # linear offset in arcsec (do not include cos(DEC) correction)
+  OFFSET.DEC F32 3600.0 # linear offset in arcsec (do not include cos(DEC) correction)
+  OFFSET.NR  S32 5
+  OFFSET.ND  S32 5
+
+  DITHER.RA  F32 30.0   # linear offset in arcsec (do not include cos(DEC) correction)
+  DITHER.DEC F32 30.0   # linear offset in arcsec (do not include cos(DEC) correction)
+  DITHER.NR  S32 2
+  DITHER.ND  S32 2
+
+  DVODB      STR /data/alala.0/ipp/ippRefs/catdir.synth.grizy 
+  FILTERS    STR g,r,i
+  @EXPTIMES  F32 60.0,30.0,30.0
+  @SKYMAGS   F32 21.0,20.5,20.0
+
+  # XXX use a more realistic IQ distribution...
+  IQ_MIN     F32 0.5
+  IQ_MAX     F32 0.8
+  
+  # rotation sequence
+  POS_MIN    F32 0.0
+  POS_MAX    F32 0.0
+  POS_DELTA  F32 1.0
+
+END
+
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/simtest.pro
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/simtest.pro	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/simtest.pro	(revision 22157)
@@ -0,0 +1,50 @@
+## simtest.pro : automatic full-suite simulated IPP test : -*- sh -*-
+
+macro simtest
+  if ($0 != 5)
+    echo "USAGE: simtest (testroot) (dbname) (hostname) (init)"
+    echo " if (init) == 0, just run the analysis tasks"
+    echo " if (init) == 1, (re)create the database and (re)inject the images"
+    echo " if (init) == 2, restart completely from scratch"
+    break
+  end
+
+  $testroot = $1
+  $dbname = $2
+  $hostname = $3
+ 
+  if ($4 > 0)
+    # XXX this will fail and exit the script if the db does not exist or is old...
+    exec pxadmin -delete -dbname $dbname
+
+    # XXX this gives warnings if the db exists...
+    exec pxadmin -create -dbname $dbname
+
+    if ($4 == 2)
+      # the labels "wait" and "proc" are special names used in automate.pro
+      exec ppSimSequence $MODULES:0/$testroot.config $testroot.mkimages $testroot.inject -path raw -workdir work -dbname $dbname -label wait -dvodb DVO -tess_id TESS
+      exec source $testroot.mkimages
+
+      file TESS/Images.dat found
+      if ($found == 0)
+        exec skycells 8 -scale 0.2 -D CATDIR TESS
+      end
+    end
+
+    exec chiptool -dbname $dbname -block -label wait
+    exec source $testroot.inject
+  end
+
+  module pantasks.pro
+  module automate.pro
+
+  module.tasks
+
+  controller host add $hostname
+  add.database $dbname
+
+  load.automate simtest.auto $dbname
+  run
+end
+
+# XXX add a test function to pxadmin to check for db existence
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/site.manoa.pro
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/site.manoa.pro	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/site.manoa.pro	(revision 22157)
@@ -0,0 +1,52 @@
+## site.mhpcc.pro : example configuration script for the MHPCC IPP cluster : -*- sh -*-
+
+## a site configuration needs to define the following pieces of information:
+## 1) the machines used for processing
+## 2) the default_host and workdir_template variables
+## 3) the chiphost table
+
+macro init.cluster.po
+  $PARALLEL = 1
+  controller exit true
+
+  # po05 -- using for pantasks 
+  # po23 -- broken perl
+
+  controller host add po02
+  controller host add po03
+  controller host add po04
+# controller host add po05
+  controller host add po06
+  controller host add po07
+# controller host add po08
+# controller host add po09
+# controller host add po10
+# controller host add po11
+# controller host add po12
+# controller host add po13
+# controller host add po14
+# controller host add po15
+# controller host add po16
+# controller host add po17
+# controller host add po18
+# controller host add po19
+# controller host add po20
+# controller host add po21
+# controller host add po22
+# controller host add po23
+# controller host add po24
+end
+
+macro init.cluster.sn
+  $PARALLEL = 1
+  controller exit true
+  controller host add sn2
+  controller host add sn3
+  controller host add sn4
+  controller host add sn5
+end
+
+macro init.site
+  queueload tmp -x "cat $MODULES:0/chiphosts.manoa.config"
+  ipptool2book tmp chiphosts -key camera
+end
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/site.mhpcc.pro
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/site.mhpcc.pro	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/site.mhpcc.pro	(revision 22157)
@@ -0,0 +1,68 @@
+## site.mhpcc.pro : example configuration script for the MHPCC IPP cluster : -*- sh -*-
+
+## a site configuration needs to define the following pieces of information:
+## 1) the machines used for processing
+## 2) the default_host and workdir_template variables
+## 3) the chiphost table
+
+macro init.cluster.mhpcc
+  $PARALLEL = 1
+  controller exit true
+  controller host add ipp005
+  controller host add ipp006
+  controller host add ipp007
+  controller host add ipp008
+  controller host add ipp009
+  controller host add ipp010
+  controller host add ipp011
+  controller host add ipp016
+  controller host add ipp020
+  controller host add ipp021
+end
+
+## override the basic inits set in pantasks.pro
+macro init.copy.mhpcc
+ if ($0 != 2)
+   echo "USAGE: init.copy.mhpcc (nebulous)"
+   echo "nebulous may be 'on' or 'off'"
+   break
+ end
+
+ if (("$1" != "on") && ("$1" != "off")) 
+   echo "USAGE: init.copy.mhpcc (nebulous)"
+   echo "nebulous may be 'on' or 'off'"
+   break
+ end
+ 
+ # XXX this is only used by summit.copy.pro.  move this as a check into summit.copy.pro?
+ $COMPRESS = 1
+
+ # the templates are used if we have a class_id/host relationship; 
+ # if none is found, the default values are used
+ # XXX not sure how to handle the .N value if we need to use more than one
+
+ if ("$1" == "on")
+  $NEBULOUS = 1
+  $default_host     = ipp004
+  $workdir_template = neb://@HOST@.0
+ else
+  $NEBULOUS = 0
+  $default_host     = ipp004
+  $workdir_template = /data/@HOST@.0
+ end
+end
+
+macro init.site
+  init.cluster.mhpcc
+  init.copy.mhpcc on
+
+  queueload tmp -x "cat $MODULES:0/chiphosts.mhpcc.config"
+  ipptool2book tmp chiphosts -key camera
+end
+
+macro init.site.nohosts
+  init.copy.mhpcc on
+
+  queueload tmp -x "cat $MODULES:0/chiphosts.mhpcc.config"
+  ipptool2book tmp chiphosts -key camera
+end
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/stack.pro
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/stack.pro	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/stack.pro	(revision 22157)
@@ -0,0 +1,169 @@
+## stack.pro : tasks for image stacking : -*- sh -*-
+
+## This file contains panTasks definitions for performing the image stacking.
+## After a stack (with associated stack_id) is defined, the stack is performed
+## (tasks in stackSumSkyfile).
+
+# test for required global variables
+check.globals
+
+$LOGSUBDIR = $LOGDIR/stack
+exec mkdir -p $LOGSUBDIR
+
+### Initialise the books containing the tasks to do
+book init stackSumSkyfile
+
+### Database lists
+$stackSkycell_DB = 0
+
+### Check status of stacking tasks
+macro stack.status
+  book listbook stackSumSkyfile
+end
+
+### Reset stacking tasks
+macro stack.reset
+  book init stackSumSkyfile
+end
+
+### Turn stacking tasks on
+macro stack.on
+  task stack.skycell.load
+    active true
+  end
+  task stack.skycell.run
+    active true
+  end
+end
+
+### Turn stacking tasks off
+macro stack.off
+  task stack.skycell.load
+    active false
+  end
+  task stack.skycell.run
+    active false
+  end
+end
+
+
+### Load tasks for doing the stack
+### Tasks are loaded into stackSumSkyfile.
+task	       stack.skycell.load
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 30
+  npending     1
+
+  stdout NULL
+  stderr $LOGSUBDIR/stack.skycell.log
+
+  task.exec
+    if ($DB:n == 0)
+      option DEFAULT
+      command stacktool -tosum -limit 20
+    else
+      # save the DB name for the exit tasks
+      option $DB:$stackSkycell_DB
+      command stacktool -tosum -limit 20 -dbname $DB:$stackSkycell_DB
+      $stackSkycell_DB ++
+      if ($stackSkycell_DB >= $DB:n) set stackSkycell_DB = 0
+    end
+  end
+
+  # success
+  task.exit    0
+    # convert 'stdout' to book format
+    ipptool2book stdout stackSumSkyfile -key stack_id -uniq -setword dbname $options:0 -setword pantaskState INIT
+    if ($VERBOSE > 2)
+      book listbook stackSumSkyfile
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup stackSumSkyfile
+  end
+
+  # locked list
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
+### Run tasks for calculating the stack overlaps
+### Tasks are taken from stackSumSkyfile.
+task	       stack.skycell.run
+  periods      -poll $RUNPOLL
+  periods      -exec $RUNEXEC
+  periods      -timeout 10800
+
+  task.exec
+    book npages stackSumSkyfile -var N
+    if ($N == 0) break
+    if ($NETWORK == 0) break
+    
+    # look for new images in stackSumSkyfile (pantaskState == INIT)
+    book getpage stackSumSkyfile 0 -var pageName -key pantaskState INIT
+    if ("$pageName" == "NULL") break
+
+    book setword stackSumSkyfile $pageName pantaskState RUN
+    book getword stackSumSkyfile $pageName stack_id -var STACK_ID
+    book getword stackSumSkyfile $pageName tess_id -var TESS_DIR
+    book getword stackSumSkyfile $pageName skycell_id -var SKYCELL_ID
+    book getword stackSumSkyfile $pageName camera -var CAMERA
+    book getword stackSumSkyfile $pageName workdir -var WORKDIR_TEMPLATE
+    book getword stackSumSkyfile $pageName dbname -var DBNAME
+
+    ### XXX Instead of setting these by camera, need to set by skycell_id
+#    set.host.for.camera $CAMERA $STACK_ID
+#    set.workdir.by.camera $CAMERA $STACK_ID $WORKDIR_TEMPLATE $default_host WORKDIR
+    host anyhost
+    $WORKDIR = $WORKDIR_TEMPLATE
+
+    basename $TESS_DIR -var TESS_ID
+    sprintf outroot "%s/%s/%s/%s.%s.stk.%s" $WORKDIR $TESS_ID $SKYCELL_ID $TESS_ID $SKYCELL_ID $STACK_ID
+
+    ## generate output log based on filerule (convert the URI to a PATH) (use STACK_ID?)
+#    $logfile = `ipp_filename.pl --filerule LOG.EXP --camera $CAMERA --class_id $STACK_ID --basename $outroot`
+    $logfile = `ipp_filename.pl --filerule LOG.EXP --camera $CAMERA --class_id NONE --basename $outroot`
+    if ("$logfile" == "") 
+      echo "WARNING: logfile not defined in stack.pro:135"
+      break
+    end
+
+    stdout $logfile
+    stderr $logfile
+    dirname $logfile -var outpath
+    mkdir $outpath
+
+    $run = stack_skycell.pl --stack_id $STACK_ID --outroot $outroot
+    add_standard_args run
+
+    # save the pageName for future reference below
+    options $pageName
+
+    # create the command line
+    if ($VERBOSE > 1)
+      echo command $run
+    end
+    command $run
+  end
+
+  # default exit status
+  task.exit    default
+    process_exit stackSumSkyfile $options:0 $JOB_STATUS
+  end
+
+  # operation timed out?
+  task.exit    timeout
+    showcommand timeout
+    book setword stackSumSkyfile $options:0 pantaskState TIMEOUT
+  end
+end
+
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/summit.copy.pro
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/summit.copy.pro	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/summit.copy.pro	(revision 22157)
@@ -0,0 +1,434 @@
+## summit.copy.pro : tasks for the summit to IPP download : -*- sh -*-
+## PanTasks scripts for Summit Copy
+
+## XXX note that this currently works with a single database as defined in .ipprc
+## XXX tie the database for output to the database from which the datastore was determined
+
+# pztool -adddatastore -inst isp -telescope ps1 -uri http://otis1.ifa.hawaii.edu/ds/skyprobe/index.txt
+# pztool -adddatastore -inst gpc1 -telescope ps1 -uri http://conductor/ds/gpc1/index.txt
+# pztool -adddatastore -inst allskycam -telescope ps1 -uri  http://otis1.ifa.hawaii.edu/ds/allskycam/index.txt
+
+# NOTE: workdir / volume mangling and nebulous.  these tasks copy the
+# imfiles from the summit, placing the output files in a directory
+# which is based on the chip/host relationship.  If nebulous is being
+# used, the volume name is set based on the host; if nebulous is not
+# being used, the workdir is set to include the host name.  The copy
+# operation is targetted to the same host by pcontrol.  The value of
+# workdir is set to be a template into which the appropriate value of
+# @HOST@ may be substituted in later scripts.
+
+# test for required global variables
+check.globals
+
+# list of DataStores to pull data from
+book init pzDataStore
+# list of summit exps that need to be queried
+book init pzPendingExp
+# list of summit imfiles that need to be downloaded
+book init pzPendingImfile
+
+macro copy.on
+  task pztool.datastore
+    active true
+  end
+  task pzgetexp
+    active true
+  end
+  task pztool.pendingexp
+    active true
+  end
+  task pzgetimfile 
+    active true
+  end
+  task pztool.pendingimfile
+    active true
+  end
+  task summit_copy
+    active true
+  end
+end
+
+macro copy.off
+  task pztool.datastore
+    active false
+  end
+  task pzgetexp
+    active false
+  end
+  task pztool.pendingexp
+    active false
+  end
+  task pzgetimfile 
+    active false
+  end
+  task pztool.pendingimfile
+    active false
+  end
+  task summit_copy
+    active false
+  end
+end
+
+# these variables will cycle through the known database names
+$pztoolDatastore_DB = 0
+$pztoolPendingExp_DB = 0
+$pztoolPendingImfile_DB = 0
+
+# build a book of datastores to poll for data
+task pztool.datastore
+    host         local
+
+    # timeout shorter than exec so jobs do not build up
+    periods      -exec      10
+    periods      -poll       1
+    periods      -timeout   20
+    npending     1
+    # trange       16:00 23:59
+    # trange       00:00 04:00
+
+    task.exec
+      if ($DB:n == 0)
+        option DEFAULT
+        command pztool -datastore
+      else
+        # save the DB name for the exit tasks
+        option $DB:$pztoolDatastore_DB
+        command pztool -datastore -dbname $DB:$pztoolDatastore_DB
+        $pztoolDatastore_DB ++
+        if ($pztoolDatastore_DB >= $DB:n) set pztoolDatastore_DB = 0
+      end
+    end
+
+    # success
+    task.exit 0
+        # flush pzDataStore book
+        book init pzDataStore
+        # convert 'stdout' to book format
+        ipptool2book stdout pzDataStore -key camera:telescope -uniq -setword dbname $options:0
+    end
+
+    task.exit default
+        showcommand failure
+    end
+    task.exit timeout
+        showcommand timeout
+    end
+end
+
+$datastore_index = 0
+
+# run pzgetexp periodically to populate pzPendingExp in the database (no I/O)
+# this task is querying the data store for a list of exposures ("filesets")
+# and inserting these into a db table on the local cluster (pzPendingExp)
+task pzgetexp
+  periods      -exec     10
+  periods      -poll     1
+  periods      -timeout  20
+  # trage       16:00 23:59
+  # trage       00:00 04:00
+  npending      1
+  host         local
+
+  task.exec
+        # find an exp that needs imfiles fetched
+        book getpage pzDataStore $datastore_index -var pageName
+        if ("$pageName" == "NULL") break
+
+        # increment our pzDataStore index and loop back to 0 and the end of the
+        # book
+        $datastore_index ++
+        book npages pzDataStore -var npages
+        if ($datastore_index == $npages )
+            $datastore_index = 0
+        end
+
+        book getword pzDataStore $pageName camera    -var CAMERA
+        book getword pzDataStore $pageName telescope -var TELESCOPE
+        book getword pzDataStore $pageName uri       -var URI
+        book getword pzDataStore $pageName dbname    -var DBNAME
+
+        # store the current page
+        options $pageName
+
+        $run = pzgetexp -uri $URI -inst $CAMERA -telescope $TELESCOPE -dbname $DBNAME
+
+        # create the command line
+        if ($VERBOSE > 1)
+          echo command $run
+        end
+
+        command $run
+  end
+
+  task.exit     0
+  end
+
+  task.exit     default
+    showcommand failure
+  end
+  task.exit     timeout
+    showcommand timeout
+  end
+end
+
+# build a book of exps/filesetids that need to be queried
+task pztool.pendingexp
+    host         local
+
+    periods      -exec     10
+    periods      -poll     1
+    periods      -timeout  20
+    # trange       16:00 23:59
+    # trange       00:00 04:00
+    npending     1
+
+    task.exec
+      if ($DB:n == 0)
+        option DEFAULT
+        command pztool -pendingexp -limit 5
+      else
+        # save the DB name for the exit tasks
+        option $DB:$pztoolPendingExp_DB
+        command pztool -pendingexp -limit 5 -dbname $DB:$pztoolPendingExp_DB
+        $pztoolPendingExp_DB ++
+        if ($pztoolPendingExp_DB >= $DB:n) set pztoolPendingExp_DB = 0
+      end
+    end
+
+    # success
+    task.exit 0
+        # convert 'stdout' to book format
+        ipptool2book stdout pzPendingExp -key exp_name:camera:telescope -uniq -setword dbname $options:0 -setword pantaskState INIT
+
+        # delete existing entries in the appropriate pantaskStates
+        process_cleanup pzPendingExp
+    end
+
+    task.exit default
+        showcommand failure
+        if ($VERBOSE)
+          echo "*** stdout ***"
+          queueprint stdout
+          echo "*** stderr ***"
+          queueprint stderr
+        end
+    end
+    task.exit timeout
+        showcommand timeout
+    end
+end
+
+# run pzgetimfiles on pending exps.  analogous to pzgetexp, this task
+# is downloading a list of imfiles reported by the datastore for a
+# given exposure ("fileset"), and placing the result in the local
+# database table of imfiles
+task pzgetimfile 
+    periods      -exec     0.05
+    periods      -poll     0.025
+    periods      -timeout  700
+    # trage       16:00 23:59
+    # trage       00:00 04:00
+    host 	local
+    npending 	10
+
+    task.exec
+        if ($NETWORK == 0) break
+
+        # if we are waiting on data, make the interval long
+        book npages pzPendingExp -var N
+        if ($N == 0)
+            periods -exec 20
+            break
+        end
+        periods -exec 0.05
+
+        # find an exp that needs imfiles fetched
+        book getpage pzPendingExp 0 -var pageName -key pantaskState INIT
+        if ("$pageName" == "NULL") break
+
+        # set that exp to run
+        book setword pzPendingExp $pageName pantaskState RUN
+
+        book getword pzPendingExp $pageName exp_name  -var EXP_NAME
+        book getword pzPendingExp $pageName camera    -var CAMERA
+        book getword pzPendingExp $pageName telescope -var TELESCOPE
+        book getword pzPendingExp $pageName dateobs   -var DATEOBS
+        book getword pzPendingExp $pageName exp_type  -var EXP_TYPE
+        book getword pzPendingExp $pageName uri       -var URI
+        book getword pzPendingExp $pageName imfiles   -var IMFILES
+        book getword pzPendingExp $pageName dbname    -var DBNAME
+
+        # store the current page
+        options $pageName
+
+        $batman = $EXP_NAME
+        # Sidik says we should use a longer timeout
+        $run = pzgetimfiles -uri $URI -filesetid $batman -inst $CAMERA -telescope $TELESCOPE -dbname $DBNAME -timeout 650
+
+        # create the command line
+        if ($VERBOSE > 1)
+          echo command $run
+        end
+        command $run
+    end
+
+    # success
+    task.exit 0
+        process_exit pzPendingExp $options:0 $JOB_STATUS
+    end
+
+    task.exit default
+        showcommand failure
+        process_exit pzPendingExp $options:0 $JOB_STATUS
+    end
+
+    task.exit timeout
+        showcommand timeout
+        book setword pzPendingExp $options:0 pantaskState TIMEOUT
+    end
+end
+
+
+# build a book of imfiles/files that need to be downloaded
+task pztool.pendingimfile
+    host         local
+
+    periods      -exec     30
+    periods      -poll      1
+    periods      -timeout  120
+    # trage       16:00 23:59
+    # trage       00:00 04:00
+    npending     1
+
+    # select entries from the current DB; cycle to the next DB, if it exists
+    # iff the DB list is not set, use the value defined in .ipprc
+    task.exec
+      if ($DB:n == 0)
+        option DEFAULT
+        command pztool -pendingimfile -limit 40
+      else
+        # save the DB name for the exit tasks
+        option $DB:$pztoolPendingImfile_DB
+        command pztool -pendingimfile -limit 60 -dbname $DB:$pztoolPendingImfile_DB
+        $pztoolPendingImfile_DB ++
+        if ($pztoolPendingImfile_DB >= $DB:n) set pztoolPendingImfile_DB = 0
+      end
+    end
+  
+    # success
+    task.exit    0
+        # convert 'stdout' to book format
+        ipptool2book stdout pzPendingImfile -key exp_name:camera:telescope:class:class_id -uniq -setword dbname $options:0 -setword pantaskState INIT
+	book shuffle pzPendingImfile 
+
+        # delete existing entries in the appropriate pantaskStates
+        process_cleanup pzPendingImfile
+    end
+
+    task.exit     default
+        showcommand failure
+    end
+    task.exit     timeout
+        showcommand timeout
+    end
+end
+
+# retreive an imfile with dsget and then call pztool -copydone
+task summit_copy
+    periods      -exec     5
+    periods      -poll     0.05
+    periods      -timeout  650
+    # trage       16:00 23:59
+    # trage       00:00 04:00
+
+    task.exec
+        if ($NETWORK == 0) break
+
+        # if we are waiting on data, make the interval long
+        book npages pzPendingImfile -var N
+        if ($N == 0)
+            periods -exec 20
+            break
+        end
+        periods -exec 0.05
+
+        # find an exp that needs imfiles fetched
+        book getpage pzPendingImfile 0 -var pageName -key pantaskState INIT
+        if ("$pageName" == "NULL") break
+
+        # set that exp to run
+        book setword pzPendingImfile $pageName pantaskState RUN
+
+        book getword pzPendingImfile $pageName uri     	 -var URI
+        book getword pzPendingImfile $pageName bytes   	 -var BYTES
+        book getword pzPendingImfile $pageName md5sum  	 -var MD5SUM
+        book getword pzPendingImfile $pageName dateobs 	 -var DATEOBS
+        book getword pzPendingImfile $pageName exp_name  -var EXP_NAME
+        book getword pzPendingImfile $pageName camera    -var CAMERA
+        book getword pzPendingImfile $pageName telescope -var TELESCOPE
+        book getword pzPendingImfile $pageName class     -var CLASS
+        book getword pzPendingImfile $pageName class_id  -var CLASS_ID
+        book getword pzPendingImfile $pageName dbname    -var DBNAME
+
+        set.host.for.camera $CAMERA $CLASS_ID
+
+        # 2007-08-30T05:09:59Z
+        substr $DATEOBS 0 4 YEAR
+        substr $DATEOBS 5 2 MONTH
+        substr $DATEOBS 8 2 DAY
+
+        # we need to set the workdir based on 1) nebulous or not? 2) chip/host relationship
+        # this function uses workdir_template, default_host, volume_template, volume_default,
+        # it sets workdir and volume
+       	set.workdir.by.camera $CAMERA $CLASS_ID $workdir_template $default_host workdir_base
+
+        # figure out filename
+	# XXX may need to use sprintf here
+        $FILENAME = $workdir_base/$CAMERA/$YEAR\$MONTH\$DAY/$EXP_NAME/$EXP_NAME.$CLASS_ID.fits
+        $workdir = $workdir_template/$CAMERA/$YEAR\$MONTH\$DAY
+
+	# workdir examples:
+	# file://data/@HOST@.0/gpc1/20080130
+	# neb://@HOST@.0/gpc1/20080130
+
+	# filename examples:
+	# file://data/ipp005.0/gpc1/20080130/o4437g0025d/o4437g0025d.XY05.fits
+	# neb://@HOST@.0/gpc1/20080130/o4437g0025d/o4437g0025d.XY05.fits
+
+        book setword pzPendingImfile $pageName filename $FILENAME
+
+        $run = summit_copy.pl --uri $URI --filename $FILENAME --exp_name $EXP_NAME --inst $CAMERA --telescope $TELESCOPE --class $CLASS --class_id $CLASS_ID --bytes $BYTES --md5 $MD5SUM --end_stage reg --workdir $workdir --dbname $DBNAME --timeout 120 --verbose
+	if ($COMPRESS) 
+            $run = $run --compress
+        else
+            $run = $run --bytes $BYTES 
+        end
+        if (("$MD5SUM" != "NULL") && ("$MD5SUM" != "0") && (not($COMPRESS)))
+            $run = $run --md5 $MD5SUM
+        end
+	if ($NEBULOUS) 
+            $run = $run --nebulous
+        end
+        # add_standard_args run
+
+        # store the pageName for future reference below
+        options $pageName
+
+        # create the command line
+        if ($VERBOSE > 1)
+          echo command $run
+        end
+        command $run
+    end
+
+    # default exit status
+    task.exit default
+        process_exit pzPendingImfile $options:0 $JOB_STATUS
+    end
+
+    # operation timed out?
+    task.exit timeout
+        showcommand timeout
+        book setword pzPendingImfile $options:0 pantaskState TIMEOUT
+    end 
+end
Index: /branches/eam_branches/eam_branch_20080706/ippTasks/warp.pro
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTasks/warp.pro	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTasks/warp.pro	(revision 22157)
@@ -0,0 +1,284 @@
+## warp.pro : image warping tasks : -*- sh -*-
+## This file contains panTasks definitions for performing the image warping.
+
+### This is done in two (main) steps.  After a warp (with associated warp_id) is defined,
+### overlaps between the exposure being warped and skycells are calculated (tasks in warpInputExp).
+### Then for each skycell, the warp is made (tasks in warpPendingSkycell).
+
+### Setups
+check.globals
+
+### Initialise the books containing the tasks to do
+book init warpInputExp
+book init warpPendingSkyCell
+
+### Database lists
+$warpExp_DB = 0
+$warpSkycell_DB = 0
+
+### Check status of warping tasks
+macro warp.status
+  book listbook warpInputExp
+  book listbook warpPendingSkyCell
+end
+
+### Reset warping tasks
+macro warp.reset
+  book init warpInputExp
+  book init warpPendingSkyCell
+end
+
+### Turn warping tasks on
+macro warp.on
+  task warp.exp.load
+    active true
+  end
+  task warp.exp.run
+    active true
+  end
+  task warp.skycell.load
+    active true
+  end
+  task warp.skycell.run
+    active true
+  end
+end
+
+### Turn warping tasks off
+macro warp.off
+  task warp.exp.load
+    active false
+  end
+  task warp.exp.run
+    active false
+  end
+  task warp.skycell.load
+    active false
+  end
+  task warp.skycell.run
+    active false
+  end
+end
+
+### Load tasks for calculating the warp overlaps
+### Tasks are loaded into warpInputExp.
+task	       warp.exp.load
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 30
+  npending     1
+
+  stdout NULL
+  stderr $LOGDIR/warp.exp.log
+
+  task.exec
+    if ($DB:n == 0)
+      option DEFAULT
+      command warptool -tooverlap -limit 20
+    else
+      # save the DB name for the exit tasks
+      option $DB:$warpExp_DB
+      command warptool -tooverlap -limit 20 -dbname $DB:$warpExp_DB
+      $warpExp_DB ++
+      if ($warpExp_DB >= $DB:n) set warpExp_DB = 0
+    end
+  end
+
+  # success
+  task.exit    0
+    # convert 'stdout' to book format
+    ipptool2book stdout warpInputExp -key warp_id:fake_id -uniq -setword dbname $options:0 -setword pantaskState INIT
+    if ($VERBOSE > 2)
+      book listbook warpInputExp
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup warpInputExp
+  end
+
+  # locked list
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
+### Run tasks for calculating the warp overlaps
+### Tasks are taken from warpInputExp.
+task	       warp.exp.run
+  periods      -poll $RUNPOLL
+  periods      -exec $RUNEXEC
+  periods      -timeout 60
+
+  task.exec
+    book npages warpInputExp -var N
+    if ($N == 0) break
+    if ($NETWORK == 0) break
+    
+    # look for new images in warpInputExp (pantaskState == INIT)
+    book getpage warpInputExp 0 -var pageName -key pantaskState INIT
+    if ("$pageName" == "NULL") break
+
+    book setword warpInputExp $pageName pantaskState RUN
+    book getword warpInputExp $pageName warp_id -var WARP_ID
+    book getword warpInputExp $pageName camera -var CAMERA
+    book getword warpInputExp $pageName workdir -var WORKDIR_TEMPLATE
+    book getword warpInputExp $pageName dbname -var DBNAME
+    # XXX change tess_id to tess_dir when schema is changed
+    book getword warpInputExp $pageName tess_id -var TESS_DIR
+    book getword warpInputExp $pageName exp_id -var EXP_ID
+
+    ### XXX Instead of setting these by camera, need to set by skycell_id
+    set.host.for.camera $CAMERA $WARP_ID
+    set.workdir.by.camera $CAMERA $WARP_ID $WORKDIR_TEMPLATE $default_host WORKDIR
+
+    ## generate outroot specific to this exposure
+    sprintf logfile "%s/%s/%s.wrp.%s.log" $WORKDIR $EXP_ID $EXP_ID $WARP_ID
+
+    stdout $LOGDIR/warp.exp.log
+    stderr $LOGDIR/warp.exp.log
+
+    # XXX warp_overlap.pl differs from the standard script : it does not have an 'outroot' argument, and it does not take '--redirect'
+    $run = warp_overlap.pl --warp_id $WARP_ID --camera $CAMERA --tess_dir $TESS_DIR --logfile $logfile
+    add_standard_args run
+
+    # save the pageName for future reference below
+    options $pageName
+
+    # create the command line
+    if ($VERBOSE > 1)
+      echo command $run
+    end
+    command $run
+  end
+
+  # default exit status
+  task.exit    default
+    process_exit warpInputExp $options:0 $JOB_STATUS
+  end
+
+  # operation timed out?
+  task.exit    timeout
+    showcommand timeout
+    book setword warpInputExp $options:0 pantaskState TIMEOUT
+  end
+end
+
+
+### Load tasks for doing the warps
+### Tasks are loaded into warpPendingSkyCell.
+task	       warp.skycell.load
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 30
+  npending     1
+
+  stdout NULL
+  stderr $LOGDIR/warp.skycell.log
+
+  task.exec
+    if ($DB:n == 0)
+      option DEFAULT
+      command warptool -towarped -limit 20
+    else
+      # save the DB name for the exit tasks
+      option $DB:$warpSkycell_DB
+      command warptool -towarped -limit 20 -dbname $DB:$warpSkycell_DB
+      $warpSkycell_DB ++
+      if ($warpSkycell_DB >= $DB:n) set warpSkycell_DB = 0
+    end
+  end
+
+  # success
+  task.exit    0
+    # convert 'stdout' to book format
+    # XXX change tess_id to tess_dir when db is updated
+    ipptool2book stdout warpPendingSkyCell -key warp_id:skycell_id:tess_id -uniq -setword dbname $options:0 -setword pantaskState INIT
+    if ($VERBOSE > 2)
+      book listbook warpPendingSkyCell
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup warpPendingSkyCell
+  end
+
+  # locked list
+  task.exit    default
+    showcommand failure
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
+### Run tasks for calculating the warp overlaps
+### Tasks are taken from warpPendingSkyCell.
+task	       warp.skycell.run
+  periods      -poll $RUNPOLL
+  periods      -exec $RUNEXEC
+  periods      -timeout 60
+
+  task.exec
+    book npages warpPendingSkyCell -var N
+    if ($N == 0) break
+    if ($NETWORK == 0) break
+    
+    # look for new images in warpPendingSkyCell (pantaskState == INIT)
+    book getpage warpPendingSkyCell 0 -var pageName -key pantaskState INIT
+    if ("$pageName" == "NULL") break
+
+    book setword warpPendingSkyCell $pageName pantaskState RUN
+    book getword warpPendingSkyCell $pageName warp_id -var WARP_ID
+    book getword warpPendingSkyCell $pageName skycell_id -var SKYCELL_ID
+    # XXX change tess_id to tess_dir when schema is changed
+    book getword warpPendingSkyCell $pageName tess_id -var TESS_DIR
+    book getword warpPendingSkyCell $pageName camera -var CAMERA
+    book getword warpPendingSkyCell $pageName workdir -var WORKDIR_TEMPLATE
+    book getword warpPendingSkyCell $pageName dbname -var DBNAME
+
+    ### XXX Instead of setting these by camera, need to set by skycell_id
+    set.host.for.camera $CAMERA $WARP_ID
+    set.workdir.by.camera $CAMERA $WARP_ID $WORKDIR_TEMPLATE $default_host WORKDIR
+
+    # XXX we are supplied TESS_DIR, strip off path to generate TESS_ID
+    basename $TESS_DIR -var TESS_ID
+    sprintf outroot "%s/%s/%s/%s.%s.wrp.%s" $WORKDIR $TESS_ID $SKYCELL_ID $TESS_ID $SKYCELL_ID $WARP_ID
+
+    stdout $LOGDIR/warp.skycell.log
+    stderr $LOGDIR/warp.skycell.log
+
+    $run = warp_skycell.pl --warp_id $WARP_ID --skycell_id $SKYCELL_ID --tess_dir $TESS_DIR --camera $CAMERA --outroot $outroot --redirect-output
+    add_standard_args run
+
+    # save the pageName for future reference below
+    options $pageName
+
+    # create the command line
+    if ($VERBOSE > 1)
+      echo command $run
+    end
+    command $run
+  end
+
+  # default exit status
+  task.exit    default
+    process_exit warpPendingSkyCell $options:0 $JOB_STATUS
+  end
+
+  # operation timed out?
+  task.exit    timeout
+    showcommand timeout
+    book setword warpPendingSkyCell $options:0 pantaskState TIMEOUT
+  end
+end
+
Index: /branches/eam_branches/eam_branch_20080706/ippTools/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/.cvsignore	(revision 22157)
@@ -0,0 +1,16 @@
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+compile
+config.guess
+config.log
+config.status
+config.sub
+configure
+depcomp
+install-sh
+libtool
+ltmain.sh
+missing
+pxtools.pc
Index: /branches/eam_branches/eam_branch_20080706/ippTools/COPYING
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/COPYING	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/COPYING	(revision 22157)
@@ -0,0 +1,345 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
Index: /branches/eam_branches/eam_branch_20080706/ippTools/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/Makefile.am	(revision 22157)
@@ -0,0 +1,12 @@
+SUBDIRS = src share
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA= pxtools.pc
+
+EXTRA_DIST = \
+	pxtools.pc.in \
+	autogen.sh
+
+CLEANFILES = *~ core core.*
+
+ACLOCAL_AMFLAGS = -I m4
Index: /branches/eam_branches/eam_branch_20080706/ippTools/TODO
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/TODO	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/TODO	(revision 22157)
@@ -0,0 +1,14 @@
+- change dettol -pretend -define... to return a metadata structure that shows what the created detRun would look like
+- rename dettool -residdetrun -> -todetrunsummary
+- add foreign key constraints to the database
+- add the ability to remove dettool detResidExps
+- change p4tool to accept input from/with magic masks
+- change p5tool to accept input from warp * stack
+- difftool/stacktool error handling
+- move all large SQL statements out into separate files
+- add the option of not automatically flowing through from reg -> chip -> cam
+  tools
+- add workdir inheritance rooted in rawExp, eg.
+    select chipProcessedExp.workdir, rawExp.workdir, IFNULL(chipProcessedExp.workdir, rawExp.workdir) from chipProcessedExp join rawExp using(exp_tag);
+- dettool/detRun support for filelevel
+- combind dettool -definebytag & -definebyquery
Index: /branches/eam_branches/eam_branch_20080706/ippTools/autogen.sh
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/autogen.sh	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/autogen.sh	(revision 22157)
@@ -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=pxtools
+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 -I m4 || echo "$ACLOCAL failed"
+$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /branches/eam_branches/eam_branch_20080706/ippTools/configure.ac
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/configure.ac	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/configure.ac	(revision 22157)
@@ -0,0 +1,64 @@
+AC_PREREQ(2.61)
+
+AC_INIT([ipptools], [1.1.36], [ipp-support@ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([autogen.sh])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_CONFIG_HEADER([src/config.h])
+AM_MAINTAINER_MODE
+
+IPP_STDCFLAGS
+
+AC_LANG(C)
+AC_GNU_SOURCE
+AC_PROG_CC_C99
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+
+PKG_CHECK_MODULES([PSLIB], [pslib >= 1.1.0])
+PKG_CHECK_MODULES([PSMODULES], [psmodules >= 1.1.0])
+PKG_CHECK_MODULES([IPPDB], [ippdb >= 1.1.38]) 
+
+PXTOOLS_CFLAGS="${PSLIB_CFLAGS=} ${PSMODULES_CFLAGS=} ${IPPDB_CFLAGS=}"
+PXTOOLS_LIBS="${PSLIB_LIBS=} ${PSMODULES_LIBS=} ${IPPDB_LIBS=}"
+AC_SUBST(PXTOOLS_CFLAGS,[$PXTOOLS_CFLAGS])
+AC_SUBST(PXTOOLS_LIBS,[$PXTOOLS_LIBS])
+
+dnl check for PSDB support
+TMP_CFLAGS=${CFLAGS}
+CFLAGS="${CFLAGS=} ${PSLIB_CFLAGS}"
+
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
+    [[#if !defined HAVE_PSDB
+      error: PSDB support is not avaiable
+      #endif
+    ]])],
+  [],
+  [AC_MSG_ERROR([psLib was built without PSDB support, bailing out.])]
+)
+
+CFLAGS=${TMP_CFLAGS}
+
+dnl --  check for psParseErrorCodes --------------------------------------
+
+AC_PATH_PROG([ERRORCODES], [psParseErrorCodes], [missing])
+if test "$ERRORCODES" = "missing" ; then
+  AC_MSG_ERROR([psParseErrorCodes is required])
+fi
+
+dnl check for perl
+AC_PATH_PROG([PERL], [perl], [missing])
+if test "$PERL" = "missing" ; then
+  AC_MSG_ERROR([perl is required])
+fi
+
+IPP_STDOPTS
+CFLAGS="${CFLAGS=} -Wall -Werror"
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+  share/Makefile
+  pxtools.pc
+])
+AC_OUTPUT
Index: /branches/eam_branches/eam_branch_20080706/ippTools/doc/dettool.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/doc/dettool.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/doc/dettool.txt	(revision 22157)
@@ -0,0 +1,181 @@
+# returns a list of unassociated rawDetrendExp (rawDetrendExps that don't
+# correspond to a exp_id in detInputExp)
+dettool -pending -exp_type -inst ..
+
+# define a new detRun semi-automatically by selecting the component exps with
+# the specified search criteria
+dettool -definebyquery -det_type ...
+
+# create a detRun with the specified input exp_ids
+dettool -definebyexp -det_type -exp_id 1 -exp_id 2 ...
+
+--
+# returns a list of rawImfiles that are associated with an detInputExp that do
+# not appear in detProcessedImfiles
+dettool -raw
+
+# add an imfile to detProcessedImfiles
+dettool -addprocessed
+
+--
+# the list of class_ids that are ready to be stacked
+# returns a list of det_id/iter/class_id/det_typs  where class_id is unique per
+# det_id (not a list of all exp_ids/class_ids) that do not have an entry in
+# detStackedImfile
+# results are masked by entries in detResidExpAnalysis
+# may need to check detResidExp's even on iter 0 - ???
+dettool -tostack
+
+# list all of imfiles from different exps with the same class_id that have
+# been processed
+dettool -processed -wholestack -det_id ...
+
+# adds a stacked class_id and and optionally marks it for normalization
+detool -addstacked [-pleasenormalize] -det_id -iter -class_id ...
+
+--
+# returns a list of det_ids/iters where the entire set of class_ids has been
+# processed
+dettool -tonormalize
+
+# returns the complete list of processed imfiles for the specified det_id
+dettool -processed -unmask -det_id -iter
+
+# add per stacked imfile normalization statistics
+# O: this could accept a MDC doc as input from stdin
+dettool -addnormstat -det_id -iter -class_idi -norm F32
+
+--
+# returns a list of det_ids/iter/class_id/uri from detNormStats.  Stops
+# returning entries after the class_is is inserted into detNormalizedImfile
+dettool -normstat -det_id 
+
+# inserts an entry into detNormalizedImfile
+dettool -addnormalized -det_id -ter -class_id -uri
+
+--
+# returns a list of processed imfiles that have also been normalized with
+# processed imfiles being masked out per detResidImfileAnalysis
+# also returns stackedimfiles that have -pleasenormalize set to false
+dettool -toresid -det_id -iter
+
+# inserts a per det_id/iter/exp_id/class_id residual
+dettool -addresidimfile -det_id -exp_id -class_id -iter -stat F32 -stat_stdev F32 -uri -b1 -b2
+
+# returns a list of det_id/iter/exp_ids but only for exp_ids that have resids
+# for all of their imfiles
+dettool -toresidexp
+
+# returns a list of residual imfile data
+dettool -residimfile -det_id -iter -exp_id
+
+# inserts into detResidExpAnalysis 
+# sets the accept accept bool unless -reject is specified
+dettool -addresidexp -det_id -iter -exp_id -jpeg1 -jpeg2 -stat F32 -stat_stdev
+F32 [-reject]
+
+--
+# lists det_id/iter for detruns that have completed all residexps for their
+# current iteration 
+dettool -residdetrun
+
+# lists all residexps for the given det_id/iter
+dettool -residexp -det_id -iter
+
+# updates the given residexp to be accept/reject
+dettool -updateresid -det_id -iter [-reject]
+
+# changes the state of the detrun.  -rerun cause a new iteration to be started
+# with the accepted with just the exp_ids
+dettool -updatedetrun -det_id -iter -stat F32 -stat_stdev [-rerun|-ok|-stop]
+
+# manual starts a new detrun iteration starting from the specified -iter
+dettool -rerun -det_id -iter -accept expid -accept expid -reject expid -reject expid
+dettool -rerun -det_id -iter
+
+# detInputExp needs to track...
+det_id iter exp_id use bool accept bool
+
+
+
+dettool
+
+    -define | creates a a new detrend Run
+        input:
+            exp IDs?
+            type of det run?
+        creates a new detRun ID automatically
+    
+        output: the new det_id
+
+    -raw    | lists raw imfiles needing to be processed
+        input: 
+            det ID
+            exp ID
+            class ID
+            iteration number?
+        output: list of unprocessed imfiles
+    
+    -addprocessed? | marks a raw imfile as processed
+        input:
+            det ID
+            exp ID
+            class ID
+            iteration number?
+            uri
+            stat
+            recipe
+        output: outout on error only
+    -processed | lists processed imfiles
+        input:
+            det ID
+            exp ID
+            class ID
+            iteration number?
+        output: list of processed imfiles
+    -addstacked | adds a stacked imfiles
+        input:
+            det ID
+            class ID
+            iteration number?
+            URI
+            recipe
+            stats
+        output: output on error only
+    -stacked     | lists stacked imfiles
+        input:
+            det ID
+            class ID
+            iteration number?
+        output: list of stacked imfiles
+    -stacked     | lists stacked imfiles
+        input:
+            det ID
+            class ID
+            iteration number?
+        output: list of stacked imfiles 
+    -stackedframe     | lists stacked imfiles for COMPLETE frames
+        input:
+            det ID  // required for simplicity
+            iteration number?
+        output: list of stacked imfiles
+    -addmaster | adds a master imfiles
+        input:
+            det ID
+            class ID
+            iteration number?
+            URI
+            recipe
+            stats
+        output: output on error only
+    -master     | lists master imfiles
+        input:
+            det ID  // required for simplicity
+            class ID
+            iteration number?
+        output: a list of master imfiles
+    -masterframe     | lists master imfiles for COMPLETE frames
+        input:
+            det ID  // required for simplicity
+            iteration number?
+        output: a list of master imfiles
Index: /branches/eam_branches/eam_branch_20080706/ippTools/doc/dsfilesetls
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/doc/dsfilesetls	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/doc/dsfilesetls	(revision 22157)
@@ -0,0 +1,6 @@
+#!/usr/bin/env perl
+
+print "# uri fileid bytes md5sum type \n";
+print "http://example.org/1 foo1.0 123 asadfasdfasdfasfdasdf CELL\n";
+print "http://example.org/2 foo1.1 123 asadfasdfasdfasdfasdf CELL\n";
+print "http://example.org/3 foo1.2 123 asadfasdfasdfasdfasdf CELL\n";
Index: /branches/eam_branches/eam_branch_20080706/ippTools/doc/dsproductls
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/doc/dsproductls	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/doc/dsproductls	(revision 22157)
@@ -0,0 +1,8 @@
+#!/usr/bin/env perl
+
+print "# uri fileset datetime type\n";
+print "http://example.org/1 foo1 2006-04-17T00:00:12 OBJECT\n";
+print "http://example.org/2 foo2 2006-04-17T00:00:12 OBJECT\n";
+print "http://example.org/3 foo3 2006-04-17T00:00:12 OBJECT\n";
+
+
Index: /branches/eam_branches/eam_branch_20080706/ippTools/doc/install.pod
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/doc/install.pod	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/doc/install.pod	(revision 22157)
@@ -0,0 +1,251 @@
+=pod
+
+=head1 SYNOPSIS
+
+C<pXtools> is a collection of utilties for tracking image processing
+tasks.  Very little decision making logic is built into these programs.  The
+intent is for higher level programs and scripts to make the real I<decisions>
+and then use C<pXtools> to encode those choices in a persistent manner.
+
+=head1 INSTALL
+
+=head2 Prerequisites
+
+C<pXtools> depends on C<psLib> (built with C<psDB> support), C<psModules>, and
+C<ippdb> and requires a C<C99> capable compiler.
+
+XXX document required library versions
+
+=head2 Build Procedure
+
+C<pXtools> is built with the so-called suite of autotools.   The build
+procedure is pretty typical for software built with these tools.  E.g.
+    
+    ./configure
+    make
+    make install
+
+If you checked out the sources directly from CVS you need to run the
+C<autogen.sh> script.  C<autogen.sh> will run C<configure> for you so that
+changes the build procedure to just:
+
+    sh autogen.sh
+    make
+    make install
+
+=head2 jhbuild Build Procedure
+
+If you are using one of the C<ippdev> modulesets jhbuild can build and install C<pXtools> for you.  E.g.
+
+    jhbuild build pxtools
+    
+=head2 Database Setup
+
+All of the C<pXtools>' persistent storage is ultimately done through C<psLib>'s
+C<psDB> facilties.  In turn, C<psDB> uses C<MySQL> to impliment storage (and
+relational querying).  C<MySQL> requires that each namespace (aka database) is
+configured with basic access controls.  These include usernames, passwords,
+access filtering, and privilege restrictions.  Below is a simple (but somewhat
+dangerous) example of configuring a database named C<ipp>.
+
+    mysql -u root mysql -p
+    (enter your MySQL root password)
+
+    DROP DATABASE IF EXISTS ipp;
+    CREATE DATABASE ipp;
+    GRANT ALL PRIVILEGES ON *.* TO 'ippuser'@'localhost' IDENTIFIED BY 'ipppass';
+    FLUSH PRIVILEGES;
+    quit
+
+After setting up the database you should verify that you can connect to with the username and password you configured
+
+    mysql -u ipp ipp -pipp
+
+Next, C<pXtools> needs to be told where to to find MySQL, the name of the
+database you created, the username, and the password you created.  C<pXtools>
+uses the typical C<psModules> configuration system.  The simplest method is to
+add the follow lines to your $HOME/.ipprc file.
+
+    ### Database configuration
+    DBSERVER        STR     localhost   # Database host name (for psDBInit)
+    DBNAME          STR     ipp
+    DBUSER          STR     ipp         # Database user name (for psDBInit)
+    DBPASSWORD      STR     ipp         # Database password (for psDBInit)
+
+The C<pxTools> utilities require the a schema is pre-loaded into the database.  The C<pxadmin> tool can do this for you.  E.g.
+
+    pxadmin -create
+
+You should always verify that the scheme was actually loaded.  E.g.
+
+    mysql -u ipp ipp -pipp
+    Welcome to the MySQL monitor.  Commands end with ; or \g.
+    Your MySQL connection id is 6 to server version: 4.1.20-log
+
+    Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
+
+    mysql> show tables;
+    +------------------------+
+    | Tables_in_ipp          |
+    +------------------------+
+    | detInputExp            |
+    | detMasterFrame         |
+    | detMasterImfile        |
+    | detNormalizedImfile    |
+    | detProcessedImfile     |
+    | detResidExpAnalysis    |
+    | detResidImfileAnalysis |
+    | detRun                 |
+    | detStackedImfile       |
+    | newExp                 |
+    | newImfile              |
+    | p1PendingExp           |
+    | p2DoneExp              |
+    | p2DoneImfile           |
+    | p2PendingExp           |
+    | p2PendingImfile        |
+    | p3PendingExp           |
+    | pzPendingExp           |
+    | pzPendingImfile        |
+    | rawDetrendExp          |
+    | rawImfile              |
+    | rawScienceExp          |
+    | summitExp              |
+    +------------------------+
+    23 rows in set (0.00 sec)
+
+=head1 USE
+
+The workflow is as follows:
+
+XXX
+
+=head2 Phase 0 tools
+
+The primary C<pXtools> utility for handling Phase 0 is C<p0search>.  This tool
+has two modes C<-pending> and C<-update>. The pending mode lists all
+C<newImfile>s that are part of an exposure that I<has not> yet been classified
+as either science or detrend data.
+
+For example:
+
+    p0search -pending
+
+Would output something like:
+
+    newImfile MULTI #
+
+    newImfile  METADATA
+       exp_id STR  10
+       class STR  OTA
+       class_id STR  0
+       uri STR  file://0
+    END
+
+    newImfile  METADATA
+       exp_id STR  10
+       class STR  OTA
+       class_id STR  1
+       uri STR  file://1
+    END
+
+    newImfile  METADATA
+       exp_id STR  10
+       class STR  OTA
+       class_id STR  2
+       uri STR  file://2
+    END
+
+    newImfile  METADATA
+       exp_id STR  10
+       class STR  OTA
+       class_id STR  3
+       uri STR  file://3
+    END
+
+To declare C<exp_id 10> as science data, you would invoke C<p0search> like this:
+    
+    p0search -update -exp_id 10
+
+Note that by default C<-update> flags the exposure as science data.  To declare it to be detrend data the command would be:
+
+    p0search -update -exp_id 10 -detrend
+
+Also keep in mind that this is a one time declaration.  Once it has been
+declared as either science or detrend data this decision can not be undone
+(without manually editing the database)
+
+=head2 Phase 1 tools
+
+These tools are not yet in use.
+
+=head2 Phase 2 tools
+
+C<p2search> handles all of state for Phase 2.  It has four basic modes:
+
+=over 4
+
+=item * C<-quick>
+
+=item * C<-define>
+
+=item * C<-pending>
+
+    p0search -pending
+
+=item * C<-done>
+
+=head2 Administrative tools
+
+=over 4
+
+=item * C<pxadmin>
+
+This tool can be used to load/unload the database schema.  It has three modes of operation:
+
+=over 4
+
+=item * C<-create>
+
+Loads the database schema.
+
+=item * C<-delete>
+
+Deletes the database schema.
+
+=item * C<-recreate>
+
+Deletes the database schema then reloads it.
+
+=back
+
+=item * C<pxinject>
+
+I<Injects> data directly into the database.  It has one mode parameter for each database table.  Note: support for only a few tables has been impliemented.
+
+=back
+
+=head2 Bypassing 'Summit Copy'
+
+The C<pXtools> suite assumes that exposures and image data are initially
+registered with the C<pz*> set of I<Summit Copy step> tools.  Sometimes this is
+inconvenient as you want to work with local data and bypass this set.  This is
+what the C<pxinject> utility is for.  C<pxinject> directly inserts data into
+the database, side stepping any ordering rules or sanity checking.
+
+The end result of the C<pz*> utilities is that new exposures are registered in the C<newExp> table and raw image data is in the C<newImfile> table.  You can insert this data yourself, preparing the system to run I<phase 0>.
+
+Here is an example of inserting an exposure that has 4 component image files.
+
+    #!//bin/sh
+
+    inject="./pxinject"
+
+    $inject -newExp -exp_id 10 -inst gpc -telescope ps1 -exp_type object -imfiles 4
+
+    `$inject -newImfile -exp_id 10 -class OTA -class_id 0 -uri file://0`
+    `$inject -newImfile -exp_id 10 -class OTA -class_id 1 -uri file://1`
+    `$inject -newImfile -exp_id 10 -class OTA -class_id 2 -uri file://2`
+    `$inject -newImfile -exp_id 10 -class OTA -class_id 3 -uri file://3`
+
+=cut
Index: /branches/eam_branches/eam_branch_20080706/ippTools/doc/p0tools.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/doc/p0tools.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/doc/p0tools.txt	(revision 22157)
@@ -0,0 +1,40 @@
+
+Phase 0 pipeline tools:
+
+p0search -pending :
+  * examine the new.imfiles,new.exposures tables and select exposures ready for analysis
+  * output is: (expID) (camera)
+
+p0search -update (expID) (camera):
+  * select a the corresponding images from the new.imfiles/new.exposures table
+  * extract the specified header information
+  * search the summit metadata db tables
+  * write an entry to the raw.imfiles and raw.exposure tables
+  * set the state on the new.imfiles,new.exposure tables
+  * based on the camera config information;
+    * add an entry to the p1.pending table (mosaic) 
+    - or
+    * add an entry to the p2.pending table (single)
+
+p0search -stats (expID) (camera):
+  * select a specified image in the new.imfiles/new.exposures table
+  * extract the specified header information
+  * search the summit metadata db tables
+  * report the image stats
+  [-update without output to MDDB]
+
+p0search -mkraw (expID) (camera):
+  * select a specified image in the new.imfiles/new.exposures table
+  * extract the specified header information
+  * search the summit metadata db tables
+  * write an entry to the raw.imfiles and raw.exposure tables
+  * set the state on the new.imfiles,new.exposure tables
+
+p0search -cleanup:
+  * remove completed entries from the new.imfiles,new.exposure tables
+
+** note : the division of -pending and -update allows separate processes
+   to be examining the image headers and measuring some stats.  these
+   jobs can be run via pcontrol to reduce the load on the PanTasks
+   machines
+
Index: /branches/eam_branches/eam_branch_20080706/ippTools/doc/p1tools.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/doc/p1tools.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/doc/p1tools.txt	(revision 22157)
@@ -0,0 +1,16 @@
+
+Phase 1 pipeline tools:
+
+p1search -define [constraints]:
+  * examine the raw.exposures tables and select exposures matching the given criteria
+  * add entries which are allowed (mosaic) to the p1.pending table
+
+p1search -pending :
+  * examine the p1.pending table and select exposures waiting for p1
+  * output: lines consisting of:
+    (expID) (p1version) (camera)
+
+p1search -done :
+  * select completed entries in the p1.pending table
+  * move to the p1.done table
+  * add new entry to the p2.pending tables
Index: /branches/eam_branches/eam_branch_20080706/ippTools/doc/p2search.sh
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/doc/p2search.sh	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/doc/p2search.sh	(revision 22157)
@@ -0,0 +1,8 @@
+#!/bin/csh -f
+
+echo "# expID class classID url"
+echo "654320o fpa megacam 654320o.fits"
+echo "654321o fpa megacam 654321o.fits"
+echo "654322o fpa megacam 654322o.fits"
+echo "654323o fpa megacam 654323o.fits"
+echo "654324o fpa megacam 654324o.fits"
Index: /branches/eam_branches/eam_branch_20080706/ippTools/doc/p2tools.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/doc/p2tools.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/doc/p2tools.txt	(revision 22157)
@@ -0,0 +1,41 @@
+
+Phase 2 pipeline tools:
+
+p2search -quick 
+  * search for images which match in raw.exp,raw.imfiles
+  * output in format which can be used by ppImage pantasks script
+
+p2search -define [options]
+  * input: searches mddb:raw_exposures,raw_images
+        - compares against p2Pending[Exp|Imfile]
+        - compares against p2Done[Exp|Imfile]
+        - XXX should be implemented as a special wrapper function that
+          implements this comparison as a SQL query
+  * output: updates mddb:P2_exposures_pending,P2_images_pending
+  * alternative output: identical to p2pending
+ 
+p2search -pending 
+  * input: searches mddb:P2_exposures_pending,P2_images_pending
+  * output: Nlines consisting of:
+    (URL) (expID) (class)
+  * options: ?
+
+p2search -update
+  * examine the imfiles and identify any completed exposures
+
+p2search -done
+  * get exp_id/class/class_id from the CLI
+  * add the completed imfile to the p2DoneImfile tables
+  * remove corresponding entries from the p2PendingImfile table
+  * check to see if any p2PendingExps have no associated p2PendingImfiles
+        if so move the p2PendingExp(s) to p2DoneExp
+//  * send new entry to the pending p3 table
+
+ppImage file://path/filename file://path/outroot -recipe (recipe) 
+ppImage neb://nebname neb://outroot -recipe (recipe)
+
+restriction options:
+  -time (start) (stop)
+  -camera (camera) 
+  -region (ra,dec) (ra,dec)
+
Index: /branches/eam_branches/eam_branch_20080706/ippTools/doc/p3tools.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/doc/p3tools.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/doc/p3tools.txt	(revision 22157)
@@ -0,0 +1,22 @@
+
+Phase 3 pipeline tools:
+
+p3search -define :
+  * examine the raw.exposures tables and select exposures matching the given criteria
+  * add entries which are allowed (mosaic) to the p3.pending table
+
+p3search -quick :
+  * examine the raw.exposures tables and select exposures matching the given criteria
+  * return list of entries for p3 processing
+  * output: lines consisting of:
+    (expID) (p3version) (camera)
+
+p3search -pending :
+  * examine the p3.pending exposures table and select exposures waiting for p3
+  * return list of entries for p3 processing
+  * output: lines consisting of:
+    (expID) (p3version) (camera)
+
+p3search -done :
+  * select completed entries in the p3.pending table
+  * move to the p3.done table
Index: /branches/eam_branches/eam_branch_20080706/ippTools/doc/summit_copy.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/doc/summit_copy.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/doc/summit_copy.txt	(revision 22157)
@@ -0,0 +1,49 @@
+The summit copy phase (phase Z or pz) consists of three basic steps.
+Discovering what exposures and available, discovering what image files are
+associated with those exposures, and the downloading of image files.  All
+"external" communication with the DataStore is done with three utilities;
+pzgetexp, pzgetimfiles & dsget.  While local state information is accessed and
+updated via the 'pztool' utility.
+
+The basic work follow is:
+
+pzgetexp retrieves a list of all of the exposures (filesets) available from a
+DataStore "product".  If any previously unknown file exposures are found they
+are added to the pzPendingExp tables and the summitExp table.  New entries to
+the summitExp table have a imfile value of NULL as this information can't yet
+be known.  If any exposures are already in the summitExp table the most recent
+exp_id is used to constrain the result set requested from the DataStore.
+
+    ./pzgetexp -uri http://otis/ds/skyprobe/ -inst skyprobe -telescope ps1
+
+The results of pzgetexp and accessible with pztool -pendingexp`.  Which will
+return a list of new exposures from the pzPendingExp table.
+
+    ./pztool -pendingexp
+
+The output of which is used to invoke pzgetimfiles.
+
+pzgetimfiles retrieves a list of all of the imfiles (files) in an exposure
+(fileset) from the DataStore.  It then updates the imfiles count in the
+summitExp table and adds each imfile to the pzPendingImfile table.
+
+./pzgetimfiles -uri http://otis/ds/skyprobe/sep8_twi_05/ -filesetid sep8_twi_05 -inst skyprobe -telescope ps1
+
+pztool -pendingimfile returns the list of imfiles that need to be downloaded.
+
+    ./pztool -pendingimfile
+
+The results of which are used to invoke dsget and pztool -copydone after dsget
+completes it's download.
+
+dsget from the 'DataStore' package takes the follow parameters.
+
+    dsget --uri <uri> --filename <filename> [--bytes <nbytes>] [--md5 <hex>]
+
+Where "filename" may be either just a filename relative to the CWD or a fully
+qualified path.  After dsget has copied the file pztool -copydone needs to be
+invoked
+
+    ./pztool -copydone -exp_id sep8_twi_03 -inst skyprobe -telescope ps1 -class chip -class_id sep8_twi_03.fits -uri file///data/alala/sep8_twi_03.fits
+
+And then we're Pau.
Index: /branches/eam_branches/eam_branch_20080706/ippTools/m4/ac_prog_perl_modules.m4
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/m4/ac_prog_perl_modules.m4	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/m4/ac_prog_perl_modules.m4	(revision 22157)
@@ -0,0 +1,53 @@
+dnl @synopsis AC_PROG_PERL_MODULES([MODULES], [ACTION-IF-TRUE], [ACTION-IF-FALSE])
+dnl
+dnl Checks to see if the the given perl modules are available. If true
+dnl the shell commands in ACTION-IF-TRUE are executed. If not the shell
+dnl commands in ACTION-IF-FALSE are run. Note if $PERL is not set (for
+dnl example by calling AC_CHECK_PROG, or AC_PATH_PROG),
+dnl AC_CHECK_PROG(PERL, perl, perl) will be run.
+dnl
+dnl Example:
+dnl
+dnl   AC_CHECK_PERL_MODULES(Text::Wrap Net::LDAP, ,
+dnl                         AC_MSG_WARN(Need some Perl modules)
+dnl
+dnl @category InstalledPackages
+dnl @author Dean Povey <povey@wedgetail.com>
+dnl @version 2002-09-25
+dnl @license AllPermissive
+
+AC_DEFUN([AC_PROG_PERL_MODULES],[
+    ac_perl_modules="$1"
+    # Make sure we have perl
+    if test -z "$PERL"; then
+        AC_CHECK_PROG(PERL,perl,perl)
+    fi
+
+    if test "x$PERL" != x; then
+        ac_perl_modules_failed=0
+        for ac_perl_module in $ac_perl_modules; do
+            AC_MSG_CHECKING(for perl module $ac_perl_module)
+
+            # Would be nice to log result here, but can't rely on autoconf
+            # internals
+            $PERL "-M$ac_perl_module" -e exit > /dev/null 2>&1
+            if test $? -ne 0; then
+                AC_MSG_RESULT(no);
+                ac_perl_modules_failed=1
+            else
+                AC_MSG_RESULT(ok);
+            fi
+        done
+
+        # Run optional shell commands
+        if test "$ac_perl_modules_failed" = 0; then
+            :
+            $2
+        else
+            :
+            $3
+        fi
+    else
+        AC_MSG_WARN(could not find perl)
+    fi
+])
Index: /branches/eam_branches/eam_branch_20080706/ippTools/notes.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/notes.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/notes.txt	(revision 22157)
@@ -0,0 +1,34 @@
+
+2008.07.11 EAM 
+
+  * break dettool.c into files for each detrend stage
+  * require -iteration in more places? (esp -add...)
+  * allow %s, %d arguments in the *.sql files or not?
+  * adddetrunsummary has the ability to set values for multiple detRunSummary entries.  why?
+
+2008.07.09 EAM
+
+  In order to implement the 'cleanup' and 'update' strategies, we have
+  changed the state names and defined addtional states as follows:
+
+  * new          : entry is unprocessed (was 'run')
+  * full         : entry is processed and has all output data products (was 'done')
+  * goto_cleaned : entry should be processed by the 'cleanup' system
+  * cleaned   	 : entry has been processed by the 'cleanup' system --
+    		   some output data products are now missing.
+		   
+  * goto_full    : entry should be re-processed by the 'update' system
+  * full         : entry has been re-processed by the 'update' system
+  
+  * goto_purged  : entry should be processed by the 'cleanup' ssytem
+    		   to purge all output data
+  * purged       : entry has been purged
+
+
+
+2008.05.16 EAM
+
+caltool
+  * add the active field, -active flag to -dbs
+  * add the -region 
+  * change 'catdir' to 'dvo_id'
Index: /branches/eam_branches/eam_branch_20080706/ippTools/pxtools.pc.in
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/pxtools.pc.in	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/pxtools.pc.in	(revision 22157)
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@/@PACKAGE_NAME@
+
+Name: @PACKAGE_NAME@
+Description: Pan-STARRS IPP Tools Library
+Version: @VERSION@
+Requires: pslib psmodules ippdb
+Libs: -L${libdir} -lpxtools
+Cflags: -I${includedir} @PXTOOLS_CFLAGS@
Index: /branches/eam_branches/eam_branch_20080706/ippTools/scripts/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/scripts/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/scripts/.cvsignore	(revision 22157)
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
Index: /branches/eam_branches/eam_branch_20080706/ippTools/scripts/camtest.sh
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/scripts/camtest.sh	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/scripts/camtest.sh	(revision 22157)
@@ -0,0 +1,11 @@
+#!//bin/sh
+
+set -o verbose
+
+chiptest.sh || exit 1
+
+camtool -pendingexp || exit 1
+camtool -pendingimfile || exit 1
+
+camtool -addprocessedexp -cam_id 1 -uri file:///cam -bg 1 -bg_stdev 2 -bg_mean_stdev 3 -sigma_ra 1 -sigma_dec 2 -zp_mean 10 -zp_stdev 2 -n_stars 2 -n_extended 0 -n_astrom 42 -n_cr 10000000 -path_base file:///foo || exit 1
+camtool -addprocessedexp -cam_id 2 -uri file:///cam -bg 1 -bg_stdev 2 -bg_mean_stdev 3 -sigma_ra 1 -sigma_dec 2 -zp_mean 10 -zp_stdev 2 -n_stars 2 -n_extended 0 -n_astrom 42 -n_cr 10000000 -path_base file:///foo || exit 1
Index: /branches/eam_branches/eam_branch_20080706/ippTools/scripts/chiptest.sh
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/scripts/chiptest.sh	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/scripts/chiptest.sh	(revision 22157)
@@ -0,0 +1,17 @@
+#!//bin/sh
+
+set -o verbose
+
+regtest.sh || exit 1
+
+chiptool -pendingimfile || exit 1
+
+for ID in `seq 0 3`; do
+    chiptool -addprocessedimfile -chip_id 1 -exp_id 1 -class_id $ID -uri file://chipp-t10.$ID -bg 1 -bg_stdev 2 -bg_mean_stdev 3 -path_base file:///foo || exit 1
+done;
+
+for ID in `seq 0 3`; do
+    chiptool -addprocessedimfile -chip_id 2 -exp_id 2 -class_id $ID -uri file://chipp-t11.$ID -bg 1 -bg_stdev 2 -bg_mean_stdev 3 -path_base file:///foo || exit 1
+done;
+
+chiptool -pendingimfile || exit 1
Index: /branches/eam_branches/eam_branch_20080706/ippTools/scripts/dettest.sh
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/scripts/dettest.sh	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/scripts/dettest.sh	(revision 22157)
@@ -0,0 +1,88 @@
+#!//bin/sh
+
+set -o verbose
+
+det_id=1
+
+./regtest.sh -end_stage reg || exit 1
+
+det_id=`dettool -definebyquery -det_type bias -inst gpc -filelevel fpa -select_exp_type bias -airmass_min 1 -airmass_max 10 -exp_time_min 10 -exp_time_max 30.0 -workdir file::///some/path -simple | cut -f1 -d" "` || exit 1
+
+dettool -raw || exit 1
+
+dettool -toprocessedimfile || exit 1
+
+for ID in `seq 0 3` ; do
+    dettool -addprocessedimfile -det_id 1 -exp_id 1 -class_id $ID -uri file://proc-$ID -recip myrecip -bg 2 -bg_stdev 3 -bg_mean_stdev 4 || exit 1
+done;
+
+for ID in `seq 0 3` ; do
+    dettool -addprocessedimfile -det_id 1 -exp_id 2 -class_id $ID -uri file://proc-$ID -recip myrecip -bg 2 -bg_stdev 3 -bg_mean_stdev 4 || exit 1
+done;
+
+
+dettool -tostacked || exit 1
+
+for ID in `seq 0 3` ; do
+    dettool -addstacked -det_id $det_id -uri file://stacked-$ID -class_id $ID -recip myrecipe -bg 1 -bg_stdev 2 -bg_mean_stdev 3 || exit 1
+done;
+
+dettool -tonormalizedstat || exit 1
+
+for ID in `seq 0 3` ; do
+    dettool -addnormalizedstat -det_id $det_id -class_id $ID -norm 0.12345 || exit 1
+done;
+
+dettool -tonormalize || exit 1
+for ID in `seq 0 3` ; do
+    dettool -addnormalizedimfile -det_id $det_id -class_id $ID -uri file://normalized-$ID -bg 1 -bg_stdev 2 -bg_mean_stdev 3 -path_base banana1 || exit 1
+done;
+
+dettool -tonormalizedexp || exit 1
+dettool -addnormalizedexp -det_id $det_id -recip myrecipe -bg 1 -bg_stdev 2 -bg_mean_stdev 3 -path_base file://normalizedexp || exit 1
+
+dettool -toresidimfile || exit 1
+
+for ID in `seq 0 3` ; do
+    dettool -addresidimfile -det_id $det_id -exp_id 1 -class_id $ID -recip myrecip -bg 1 -bg_stdev 1 -bg_mean_stdev 1 -uri file://resid-$ID || exit 1
+done;
+
+for ID in `seq 0 3` ; do
+    dettool -addresidimfile -det_id $det_id -exp_id 2 -class_id $ID -recip myrecip -bg 1 -bg_stdev 1 -bg_mean_stdev 1 -uri file://resid-$ID || exit 1
+done;
+
+dettool -toresidexp || exit 1
+dettool -addresidexp -det_id $det_id -exp_id 1 -recip myrecipe -bg 1 -bg_stdev 2 -bg_mean_stdev 3 -path_base jpeg1 || exit 1
+dettool -addresidexp -det_id $det_id -exp_id 2 -recip myrecipe -bg 1 -bg_stdev 2 -bg_mean_stdev 3 -path_base jpeg1 -reject || exit 1
+
+dettool -todetrunsummary || exit 1
+dettool -residexp || exit 1
+dettool -updateresidexp -det_id $det_id -iteration 0 -recip yourrecipe || exit 1
+dettool -updateresidexp -det_id $det_id -iteration 0 -exp_id 2 -reject || exit 1
+dettool -updateresidexp -det_id $det_id -iteration 0 -exp_id 2 || exit 1
+
+dettool -adddetrunsummary -det_id $det_id -iteration 0 -bg 1 -bg_stdev 2 -bg_mean_stdev 3 -accept || exit 1
+
+dettool -todetrunsummary || exit 1
+
+dettool -residexp || exit 1
+
+dettool -updatedetrun -det_id $det_id -state stop || exit 1
+
+dettool -register_detrend -det_type bias -mode master -filelevel fpa -workdir file:/// -label foo || exit 1
+dettool -register_detrend_imfile -det_id 2 -class_id 1 -uri file:///foo || exit 1
+dettool -register_detrend_imfile -det_id 2 -class_id 2 -uri file:///foo || exit 1
+dettool -register_detrend_imfile -det_id 2 -class_id 3 -uri file:///foo || exit 1
+dettool -register_detrend_imfile -det_id 2 -class_id 4 -uri file:///foo || exit 1
+dettool -updatedetrun -det_id 2 -state stop || exit 1
+
+# correct test
+dettool -makecorrection -det_id 1 || exit 1
+dettool -tocorrectexp || exit 1
+dettool -tocorrectimfile -det_id 3 || exit 1
+dettool -addcorrectimfile -det_id 3 -class_id 0 -uri file:///correct/0 || exit 1
+dettool -addcorrectimfile -det_id 3 -class_id 1 -uri file:///correct/1 || exit 1
+dettool -addcorrectimfile -det_id 3 -class_id 2 -uri file:///correct/2 || exit 1
+dettool -addcorrectimfile -det_id 3 -class_id 3 -uri file:///correct/3 || exit 1
+# detRun 3 should be automatically set to stop by this point 
+dettool -tocorrectexp || exit 1
Index: /branches/eam_branches/eam_branch_20080706/ippTools/scripts/difftest.sh
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/scripts/difftest.sh	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/scripts/difftest.sh	(revision 22157)
@@ -0,0 +1,29 @@
+#!//bin/sh
+
+set -o verbose
+
+warptest.sh || exit 1
+
+
+difftool -definerun -workdir file:///tmp/diff -skycell_id foo1 -tess_id bar || exit 1
+difftool -addinputskyfile -diff_id 1 -warp_id 1 -kind warped -template || exit 1
+difftool -addinputskyfile -diff_id 1 -warp_id 1 -kind warped || exit 1
+#difftool -updaterun -state run -diff_id 1 || exit 1
+difftool -todiffskyfile || exit 1
+difftool -inputskyfile || exit 1
+difftool -adddiffskyfile -diff_id 1 -uri file:///tmp/diff/skyfile -path_base file://lalaland -bg 1 -bg_stdev 2 || exit 1
+difftool -diffskyfile -diff_id 1 || exit 1
+difftool -updaterun -state stop -diff_id 1 || exit 1
+
+#
+# diff_id 2
+#
+difftool -definerun -workdir file:///tmp/diff -skycell_id foo1 -tess_id bar || exit 1
+difftool -addinputskyfile -diff_id 2 -warp_id 1 -kind warped -template || exit 1
+difftool -addinputskyfile -diff_id 2 -warp_id 1 -kind warped || exit 1
+#difftool -updaterun -state run -diff_id 1 || exit 1
+difftool -todiffskyfile || exit 1
+difftool -inputskyfile || exit 1
+difftool -adddiffskyfile -diff_id 2 -uri file:///tmp/diff/skyfile -path_base file://lalaland -bg 1 -bg_stdev 2 || exit 1
+difftool -diffskyfile -diff_id 2 || exit 1
+difftool -updaterun -state stop -diff_id 2 || exit 1
Index: /branches/eam_branches/eam_branch_20080706/ippTools/scripts/faketest.sh
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/scripts/faketest.sh	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/scripts/faketest.sh	(revision 22157)
@@ -0,0 +1,19 @@
+#!//bin/sh
+
+set -o verbose
+
+camtest.sh || exit 1
+
+#faketool -pendingexp || exit 1
+faketool -pendingimfile || exit 1
+
+for ID in `seq 0 3`; do
+    faketool -addprocessedimfile -fake_id 1 -exp_id 1 -class_id $ID -uri file://chipp-t10.$ID -path_base file:///foo || exit 1
+done;
+
+for ID in `seq 0 3`; do
+    faketool -addprocessedimfile -fake_id 2 -exp_id 2 -class_id $ID -uri file://chipp-t11.$ID -path_base file:///foo || exit 1
+done;
+
+faketool -pendingimfile || exit 1
+faketool -processedimfile || exit 1
Index: /branches/eam_branches/eam_branch_20080706/ippTools/scripts/magic_dep.md
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/scripts/magic_dep.md	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/scripts/magic_dep.md	(revision 22157)
@@ -0,0 +1,6 @@
+# node      dep
+a   STR     NULL
+b   STR     NULL
+root   MULTI
+root   STR     a
+root   STR     b
Index: /branches/eam_branches/eam_branch_20080706/ippTools/scripts/magictest.sh
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/scripts/magictest.sh	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/scripts/magictest.sh	(revision 22157)
@@ -0,0 +1,28 @@
+#!//bin/sh
+
+set -o verbose
+
+difftest.sh || exit 1
+
+
+magictool -definerun -workdir file:///foo/bar/ || exit 1
+magictool -addinputskyfile -magic_id 1 -diff_id 1 -node a || exit 1
+magictool -addinputskyfile -magic_id 1 -diff_id 2 -node b || exit 1
+magictool -inputtree -magic_id 1 -dep_file magic_dep.md 
+magictool -updaterun -state run -magic_id 1 || exit 1
+
+magictool -addresult -magic_id 1 -node a -uri file:///foo/a
+magictool -addresult -magic_id 1 -node b -uri file:///foo/b
+magictool -addresult -magic_id 1 -node root -uri file:///foo/root
+magictool -tomask
+magictool -addmask -magic_id 1 -uri file:///foo/mask
+magictool -toskyfilemask
+magictool -addskyfilemask -magic_id 1 -diff_id 1 -uri file:///foo/mask_1
+magictool -addskyfilemask -magic_id 1 -diff_id 2 -uri file:///foo/mask_2
+magictool -updaterun -state stop -magic_id 1 || exit 1
+
+#difftool -todiffskyfile || exit 1
+#difftool -inputskyfile || exit 1
+#difftool -adddiffskyfile -diff_id 1 -uri file:///tmp/diff/skyfile -path_base file://lalaland -bg 1 -bg_stdev 2 || exit 1
+#difftool -diffskyfile -diff_id 1 || exit 1
+#difftool -updaterun -state stop -diff_id 1 || exit 1
Index: /branches/eam_branches/eam_branch_20080706/ippTools/scripts/mapfile.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/scripts/mapfile.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/scripts/mapfile.txt	(revision 22157)
@@ -0,0 +1,19 @@
+warpSkyCellMap MULTI
+
+warpSkyCellMap METADATA
+    warp_id         S64     1
+    cam_id          S64     1
+    skycell_id      STR     foo1
+    tess_id         STR     bar
+    class_id        STR     quix1
+    fault           S16     0
+END
+
+warpSkyCellMap METADATA
+    warp_id         S64     1
+    cam_id          S64     1
+    skycell_id      STR     foo2
+    tess_id         STR     bar
+    class_id        STR     quix2
+    fault           S16     0
+END
Index: /branches/eam_branches/eam_branch_20080706/ippTools/scripts/regtest.sh
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/scripts/regtest.sh	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/scripts/regtest.sh	(revision 22157)
@@ -0,0 +1,49 @@
+#!//bin/sh
+
+set -o verbose
+
+inject="pxinject"
+regtool="regtool"
+
+echo -e "YES\njhipp\n\n" | pxadmin -recreate || exit 1
+
+exp_id1=`$inject -newExp -tmp_exp_name t10 -tmp_inst gpc -tmp_telescope ps1 -workdir file::///some/path -reduction batman -simple` || exit 1
+
+echo $exp_id1
+
+for ID in `seq 0 3`; do
+    $inject -newImfile -exp_id $exp_id1 -tmp_class_id tmp.$ID -uri file://$ID || exit 1
+done;
+
+$inject -updatenewExp -exp_id $exp_id1 -state run
+
+exp_id2=`$inject -newExp -tmp_exp_name t11 -tmp_inst gpc -tmp_telescope ps1 -workdir file::///some/path -reduction robin -simple` || exit 1
+
+for ID in `seq 0 3`; do
+    $inject -newImfile -exp_id $exp_id2 -tmp_class_id tmp.$ID -uri file://$ID || exit 1
+done;
+
+$inject -updatenewExp -exp_id $exp_id2 -state run
+
+
+$regtool -pendingimfile || exit 1
+
+for ID in `seq 0 3`; do
+    $regtool -addprocessedimfile -exp_id $exp_id1 -exp_name t10 -inst gpc -telescope ps1 -tmp_class_id tmp.$ID -class_id $ID -filter r -airmass 10 -ra 1 -decl 2 -exp_time 0 -bg 1 -bg_stdev 1 -bg_mean_stdev 10 -alt 10 -az 10 -ccd_temp 10 -posang 10 -object dog -dateobs 2006-10-20T10:10:10Z -uri file://$ID || exit 1
+done;
+
+$regtool -pendingexp|| exit 1
+
+$regtool -addprocessedexp -exp_id $exp_id1 -exp_name t10 -inst gpc -telescope ps1 -exp_tag batman.t10 -filelevel OTA -filter r -airmass 10 -ra 1 -decl 2 -exp_type object -exp_time 0 -bg 10 -bg_stdev 1 -bg_mean_stdev 10 -alt 10 -az 10 -ccd_temp 45 -posang 10 -object dog -dateobs "2006-10-20T10:10:10Z" -label foobar -tess_id moldyshoe $* || exit 1
+# use BIAS or OBJECT? $regtool -addprocessedexp -exp_id $exp_id1 -exp_name t10 -inst gpc -telescope ps1 -exp_tag batman.t10 -filelevel OTA -filter r -airmass 10 -ra 1 -decl 2 -exp_type bias -exp_time 0 -bg 10 -bg_stdev 1 -bg_mean_stdev 10 -alt 10 -az 10 -ccd_temp 45 -posang 10 -object dog -dateobs "2006-10-20T10:10:10Z" -label foobar -tess_id moldyshoe $* || exit 1
+
+$regtool -pendingimfile || exit 1
+
+for ID in `seq 0 3`; do
+    $regtool -addprocessedimfile -exp_id $exp_id2 -exp_name t11 -inst gpc -telescope ps1  -tmp_class_id tmp.$ID -class_id $ID -filter r -airmass 10 -ra 1 -decl 2 -exp_time 0 -bg 1 -bg_stdev 1 -bg_mean_stdev 10 -alt 10 -az 10 -ccd_temp 10 -posang 10 -object dog -dateobs 2006-10-20T10:10:10Z -uri file://$ID  || exit 1
+done;
+
+$regtool -pendingexp|| exit 1
+
+$regtool -addprocessedexp -exp_id $exp_id2 -exp_name t11 -inst gpc -telescope ps1 -exp_tag batman.t11 -filelevel OTA -filter r -airmass 11 -ra 1 -decl 2 -exp_type object -exp_time 0 -bg 11 -bg_stdev 1 -bg_mean_stdev 11 -alt 11 -az 11 -ccd_temp 45 -posang 11 -object dog -dateobs 2006-10-20T10:10:10Z -label foobar -tess_id moldyshoe $* || exit 1
+# BIAS or OBJECT? $regtool -addprocessedexp -exp_id $exp_id2 -exp_name t11 -inst gpc -telescope ps1 -exp_tag batman.t11 -filelevel OTA -filter r -airmass 11 -ra 1 -decl 2 -exp_type bias -exp_time 0 -bg 11 -bg_stdev 1 -bg_mean_stdev 11 -alt 11 -az 11 -ccd_temp 45 -posang 11 -object dog -dateobs 2006-10-20T10:10:10Z -label foobar -tess_id moldyshoe $* || exit 1
Index: /branches/eam_branches/eam_branch_20080706/ippTools/scripts/warptest.sh
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/scripts/warptest.sh	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/scripts/warptest.sh	(revision 22157)
@@ -0,0 +1,30 @@
+#!//bin/sh
+
+set -o verbose
+
+faketest.sh || exit 1
+
+#warp tool -definerun -fake_id 1 -mode warp -workdir file:///foo 
+
+#warptool -addinputexp -warp_id 1 -cam_id 1 || exit 1
+#warptool -addinputexp -warp_id 1 -cam_id 2 || exit 1
+#warptool -updaterun -warp_id 1 -state run || exit 1
+
+warptool -exp -warp_id 1 || exit 1
+
+warptool -imfile -warp_id 1 || exit 1
+
+warptool -tooverlap -warp_id 1 || exit 1
+
+warptool -addoverlap -mapfile mapfile.txt || exit 1
+
+warptool -scmap || exit 1
+
+warptool -towarped || exit 1
+
+warptool -addwarped -warp_id 1 -skycell_id foo1 -tess_id bar -uri file:///tmp/foo -path_base file://wonderland -bg 1 -bg_stdev 2 || exit 1
+warptool -addwarped -warp_id 1 -skycell_id foo2 -tess_id bar -uri file:///tmp/foo -path_base file://wonderland -bg 1 -bg_stdev 2 || exit 1
+
+warptool -warped -warp_id 1 || exit 1
+
+#warptool -updaterun -warp_id 1 -state stop || exit 1
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/.cvsignore	(revision 22157)
@@ -0,0 +1,2 @@
+Makefile.in
+Makefile
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/Makefile.am	(revision 22157)
@@ -0,0 +1,121 @@
+dist_pkgdata_DATA = \
+	camtool_find_chip_id.sql \
+	camtool_find_pendingexp.sql \
+	camtool_find_pendingimfile.sql \
+	camtool_find_processedexp.sql \
+	camtool_queue_chip_id.sql \
+	camtool_reset_faulted_runs.sql \
+	camtool_revertprocessedexp.sql \
+	chiptool_completely_processed_exp.sql \
+	chiptool_donecleanup.sql \
+	chiptool_find_rawexp.sql \
+	chiptool_pendingcleanupimfile.sql \
+	chiptool_pendingcleanuprun.sql \
+	chiptool_pendingimfile.sql \
+	chiptool_processedimfile.sql \
+	chiptool_revertprocessedimfile.sql \
+	chiptool_run.sql \
+	chiptool_unmasked.sql \
+	detselect_search.sql \
+	detselect_select.sql \
+        dettool_addprocessedexp.sql \
+        dettool_childlessrun.sql \
+        dettool_definebydetrun.sql \
+        dettool_detrunsummary.sql \
+	dettool_find_completed_runs.sql \
+        dettool_input.sql \
+        dettool_normalizedexp.sql \
+        dettool_normalizedimfile.sql \
+	dettool_normalizedstat.sql \
+        dettool_pending.sql \
+        dettool_processedimfile.sql \
+	dettool_raw.sql \
+        dettool_residexp.sql \
+        dettool_residimfile.sql \
+	dettool_revertdetrunsummary.sql \
+	dettool_revertnormalizedexp.sql \
+	dettool_revertnormalizedimfile.sql \
+	dettool_revertnormalizedstat.sql \
+	dettool_revertprocessedexp.sql \
+	dettool_revertprocessedimfile.sql \
+	dettool_revertresidexp.sql \
+	dettool_revertresidimfile.sql\
+	dettool_revertstacked.sql \
+	dettool_runs.sql \
+        dettool_stacked.sql \
+	dettool_start_new_iteration.sql \
+	dettool_stop_completed_correct_runs.sql \
+	dettool_tocorrectexp.sql \
+	dettool_tocorrectimfile.sql \
+	dettool_todetrunsummary.sql \
+        dettool_tonormalize.sql \
+	dettool_tonormalizedexp.sql \
+	dettool_tonormalizedstat.sql \
+	dettool_toprocessedexp.sql \
+	dettool_toprocessedimfile.sql \
+	dettool_toresidexp.sql \
+	dettool_toresidimfile.sql \
+	dettool_tostacked.sql \
+	difftool_inputskyfile.sql \
+	difftool_queue.sql \
+	difftool_revertdiffskyfile.sql \
+	difftool_skyfile.sql \
+	difftool_todiffskyfile.sql \
+	faketool_completely_processed_exp.sql \
+	faketool_find_camrun.sql \
+	faketool_find_pendingexp.sql \
+	faketool_pendingimfile.sql \
+	faketool_processedimfile.sql \
+	faketool_queue_cam_id.sql \
+	faketool_revertprocessedimfile.sql \
+	faketool_unmasked.sql \
+	flatcorr_completely_processed_chiprun.sql \
+	flatcorr_create_tmp_chiprundone.sql \
+	flatcorr_find_completed_floatcorruns.sql \
+	flatcorr_find_processedimfiles.sql \
+        flatcorr_pending.sql \
+        magictool_create_tmp_warpcomplete.sql \
+	magictool_find_complete_diffed_exposures.sql \
+	magictool_find_complete_warpruns.sql \
+        magictool_find_unmagiced.sql \
+	magictool_inputskyfile.sql \
+	magictool_totree.sql \
+	magictool_tomask.sql \
+	magictool_toprocess_inputs.sql \
+	magictool_toprocess_tree.sql \
+	magictool_toskyfilemask.sql \
+	magictool_inputs.sql \
+	magictool_addmask.sql \
+	magictool_mask.sql \
+        pstamptool_addjob_otherjob.sql \
+        pstamptool_addjob_stampjob.sql \
+        pstamptool_datastore.sql \
+        pstamptool_pendingjob.sql \
+        pstamptool_pendingreq.sql \
+	pxadmin_create_tables.sql \
+	pxadmin_drop_tables.sql \
+	pztool_find_completed_exp.sql \
+	pztool_pendingimfile.sql \
+	pztool_revert_downloadimfile_faults.sql \
+	pztool_revert_fileset_faults.sql \
+	pztool_revertcopied.sql \
+	regtool_create_dup_table.sql \
+	regtool_pendingexp.sql \
+	regtool_pendingimfile.sql \
+	regtool_populate_dup_table.sql \
+	regtool_processedexp.sql \
+	regtool_processedimfile.sql \
+	regtool_revertprocessedexp.sql \
+	regtool_revertprocessedimfile.sql \
+	stacktool_find_complete_warps.sql \
+	stacktool_inputskyfile.sql \
+	stacktool_revertsumskyfile.sql \
+	stacktool_sumskyfile.sql \
+	stacktool_tosum.sql \
+	warptool_exp.sql \
+	warptool_imfile.sql \
+	warptool_revertwarped.sql \
+	warptool_scmap.sql \
+	warptool_tooverlap.sql \
+	warptool_towarped.sql \
+	warptool_warped.sql
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/camtool_find_chip_id.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/camtool_find_chip_id.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/camtool_find_chip_id.sql	(revision 22157)
@@ -0,0 +1,31 @@
+SELECT
+    *
+FROM
+    (SELECT DISTINCT
+        chipRun.*,
+        rawExp.camera,
+        rawExp.telescope,
+        rawExp.dateobs,
+        rawExp.exp_tag,
+        rawExp.exp_type,
+        rawExp.filelevel,
+        rawExp.filter,
+        rawExp.airmass,
+        rawExp.ra,
+        rawExp.decl,
+        rawExp.exp_time,
+        rawExp.sat_pixel_frac,
+        rawExp.bg,
+        rawExp.bg_stdev,
+        rawExp.bg_mean_stdev,
+        rawExp.alt,
+        rawExp.az,
+        rawExp.ccd_temp,
+        rawExp.posang,
+        rawExp.object,
+        rawExp.solang
+    FROM chipRun
+    JOIN rawExp
+        using(exp_id)
+    WHERE
+        chipRun.state = 'full') as Foo
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/camtool_find_pendingexp.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/camtool_find_pendingexp.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/camtool_find_pendingexp.sql	(revision 22157)
@@ -0,0 +1,30 @@
+-- this query is used by both camtool -pendingexp & camtool -addprocessedexp it
+-- does a little more work then is necessary for -addprocessed but it seems
+-- "cleaner" to use the same query for both cases 
+SELECT * FROM
+    (SELECT
+        camRun.*,
+        rawExp.exp_tag,
+        rawExp.exp_id,
+        rawExp.exp_name,
+        rawExp.camera,
+        rawExp.telescope,
+        rawExp.filelevel
+    FROM camRun
+    JOIN chipRun
+        USING(chip_id)
+--  JOIN chipProcessedImfile
+--      USING(chip_id)
+    JOIN rawExp
+        USING(exp_id)
+--      ON chipProcessedImfile.exp_id = rawExp.exp_id
+    LEFT JOIN camProcessedExp
+        USING(cam_id)
+    LEFT JOIN camMask
+        ON camRun.label = camMask.label
+    WHERE
+        chipRun.state = 'full'
+        AND camRun.state = 'run'
+        AND camMask.label IS NULL
+        AND camProcessedExp.cam_id IS NULL
+    ) as Foo
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/camtool_find_pendingimfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/camtool_find_pendingimfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/camtool_find_pendingimfile.sql	(revision 22157)
@@ -0,0 +1,41 @@
+SELECT DISTINCT * FROM 
+     -- the subselect is so where criteria can be specified without knowing
+     -- which table the field came from
+    (SELECT
+        camRun.*,
+        chipProcessedImfile.exp_id,
+        chipProcessedImfile.class_id,
+        chipProcessedImfile.uri,
+        chipProcessedImfile.bg,
+        chipProcessedImfile.bg_stdev,
+        chipProcessedImfile.bg_mean_stdev,
+        chipProcessedImfile.fringe_0,
+        chipProcessedImfile.fringe_1,
+        chipProcessedImfile.fringe_2,
+        chipProcessedImfile.ap_resid,
+        chipProcessedImfile.ap_resid_stdev,
+        chipProcessedImfile.sigma_ra,
+        chipProcessedImfile.sigma_dec,
+        chipProcessedImfile.n_stars,
+        chipProcessedImfile.n_extended,
+        chipProcessedImfile.n_cr,
+        chipProcessedImfile.n_astrom,
+        chipProcessedImfile.path_base,
+        rawExp.exp_name,
+        rawExp.camera,
+        rawExp.telescope,
+        rawExp.filelevel
+    FROM camRun
+    JOIN chipRun
+        USING(chip_id)
+    JOIN chipProcessedImfile
+        USING(chip_id)
+    JOIN rawExp
+        ON chipProcessedImfile.exp_id = rawExp.exp_id
+    LEFT JOIN camProcessedExp
+        USING(cam_id)
+    LEFT JOIN camMask
+        ON camRun.label = camMask.label
+    WHERE
+        camProcessedExp.cam_id IS NULL
+        AND camMask.label IS NULL) as foo
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/camtool_find_processedexp.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/camtool_find_processedexp.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/camtool_find_processedexp.sql	(revision 22157)
@@ -0,0 +1,18 @@
+SELECT DISTINCT
+    camProcessedExp.*,
+    rawExp.exp_tag,
+    rawExp.exp_name,
+    rawExp.camera,
+    rawExp.telescope,
+    rawExp.filelevel
+FROM camRun
+JOIN camProcessedExp
+    USING(cam_id)
+JOIN chipRun
+    USING(chip_id)
+JOIN chipProcessedImfile
+    USING(chip_id)
+JOIN rawExp
+    ON chipProcessedImfile.exp_id = rawExp.exp_id
+WHERE -- bogus condition so there is a pre-existing where to append to
+    camProcessedExp.cam_id IS NOT NULL
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/camtool_queue_chip_id.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/camtool_queue_chip_id.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/camtool_queue_chip_id.sql	(revision 22157)
@@ -0,0 +1,19 @@
+-- camtool only operates on exposures so we can safely queue more then one at a
+-- time without worrying about losing the track of the generated cam_id
+INSERT INTO camRun
+    SElECT
+        0,              -- cam_id
+        chip_id,        -- chip_id
+        '%s',           -- state
+        '%s',           -- workdir
+        '%s',           -- workdir_state
+        '%s',           -- label
+        '%s',           -- reduction
+        '%s',           -- expgroup
+        '%s',           -- dvodb 
+        '%s',           -- tess_id
+        '%s'            -- end_stage
+    FROM chipRun
+    WHERE
+        chipRun.state = 'full'
+        AND chipRun.chip_id = %lld
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/camtool_reset_faulted_runs.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/camtool_reset_faulted_runs.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/camtool_reset_faulted_runs.sql	(revision 22157)
@@ -0,0 +1,7 @@
+UPDATE camRun, camProcessedExp, chipRun, rawExp
+SET camRun.state = 'run'
+WHERE
+    camRun.cam_id = camProcessedExp.cam_id
+    AND camRun.chip_id = chipRun.chip_id
+    AND chipRun.exp_id = rawExp.exp_id
+    AND camProcessedExp.fault != 0
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/camtool_revertprocessedexp.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/camtool_revertprocessedexp.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/camtool_revertprocessedexp.sql	(revision 22157)
@@ -0,0 +1,7 @@
+DELETE FROM camProcessedExp
+USING camProcessedExp, camRun, chipRun, rawExp
+WHERE
+    camRun.cam_id = camProcessedExp.cam_id
+    AND camRun.chip_id = chipRun.chip_id
+    AND chipRun.exp_id = rawExp.exp_id
+    AND camProcessedExp.fault != 0
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_completely_processed_exp.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_completely_processed_exp.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_completely_processed_exp.sql	(revision 22157)
@@ -0,0 +1,34 @@
+-- the output of this query must match the format of chipRun row
+SELECT DISTINCT
+    chip_id,
+    exp_id,
+    state,
+    workdir,
+    workdir_state,
+    label,
+    reduction,
+    expgroup,
+    dvodb,
+    tess_id,
+    end_stage
+FROM
+    (SELECT 
+        chipRun.*,
+        rawImfile.class_id as rawimfile_class_id,
+        chipProcessedImfile.class_id
+    FROM chipRun
+    JOIN rawImfile
+        USING(exp_id)
+    LEFT JOIN chipProcessedImfile
+        ON chipRun.chip_id = chipProcessedImfile.chip_id
+        AND rawImfile.exp_id = chipProcessedImfile.exp_id
+        AND rawImfile.class_id = chipProcessedImfile.class_id
+    WHERE
+        chipRun.state = 'new'
+    GROUP BY
+        chipRun.chip_id,
+        chipRun.exp_id
+    HAVING
+        COUNT(rawImfile.class_id) = COUNT(chipProcessedImfile.class_id)
+        AND SUM(chipProcessedImfile.fault) = 0
+    ) as Foo
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_donecleanup.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_donecleanup.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_donecleanup.sql	(revision 22157)
@@ -0,0 +1,5 @@
+SELECT
+    chipRun.*
+FROM chipRun
+WHERE
+    chipRun.state = 'cleaned'
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_find_rawexp.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_find_rawexp.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_find_rawexp.sql	(revision 22157)
@@ -0,0 +1,7 @@
+-- this query is used to find potental rawExps to be queued for chiptool
+-- processeing
+SELECT
+    exp_id
+FROM rawExp
+WHERE
+    rawExp.fault = 0
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_find_unprocessed_imfile.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_find_unprocessed_imfile.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_find_unprocessed_imfile.pl	(revision 22157)
@@ -0,0 +1,8 @@
+SELECT DISTINCT
+   chipInputImfile.*
+FROM chipInputImfile
+LEFT JOIN chipProcessedImfile
+    USING(chip_id, class_id)
+WHERE
+    chipProcessedImfile.chip_id IS NULL
+    AND chipProcessedImfile.class_id IS NULL
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_pendingcleanupimfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_pendingcleanupimfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_pendingcleanupimfile.sql	(revision 22157)
@@ -0,0 +1,15 @@
+SELECT
+    chipProcessedImfile.*,
+    chipRun.state,
+    chipRun.workdir,
+    chipRun.label,
+    chipRun.reduction,
+    chipRun.expgroup,
+    chipRun.dvodb,
+    chipRun.tess_id,
+    chipRun.end_stage
+FROM chipRun
+JOIN chipProcessedImfile
+    USING(chip_id)
+WHERE
+    chipRun.state = 'goto_cleaned'
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_pendingcleanuprun.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_pendingcleanuprun.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_pendingcleanuprun.sql	(revision 22157)
@@ -0,0 +1,9 @@
+SELECT
+    chipRun.chip_id,
+    rawExp.camera,
+    chipRun.state
+FROM chipRun
+JOIN rawExp 
+USING (exp_id)
+WHERE
+    chipRun.state = 'goto_cleaned'
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_pendingimfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_pendingimfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_pendingimfile.sql	(revision 22157)
@@ -0,0 +1,26 @@
+SELECT
+    chipRun.*,
+    rawImfile.class_id,
+    rawImfile.uri,
+    rawExp.exp_tag,
+    rawExp.exp_name,
+    rawExp.camera,
+    rawExp.telescope,
+    rawExp.filelevel
+FROM chipRun
+JOIN rawExp
+    USING(exp_id)
+JOIN rawImfile
+    USING(exp_id)
+LEFT JOIN chipProcessedImfile
+    ON chipRun.chip_id = chipProcessedImfile.chip_id
+    AND rawImfile.exp_id = chipProcessedImfile.exp_id
+    AND rawImfile.class_id = chipProcessedImfile.class_id
+LEFT JOIN chipMask
+    ON chipRun.label = chipMask.label
+WHERE
+    chipRun.state = 'new'
+    AND chipProcessedImfile.chip_id IS NULL
+    AND chipProcessedImfile.exp_id IS NULL
+    AND chipProcessedImfile.class_id IS NULL
+    AND chipMask.label IS NULL
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_processedimfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_processedimfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_processedimfile.sql	(revision 22157)
@@ -0,0 +1,21 @@
+SELECT DISTINCT
+    chipProcessedImfile.class_id,
+    chipProcessedImfile.uri,
+    chipProcessedImfile.bg,
+    chipProcessedImfile.bg_stdev,
+    chipProcessedImfile.bg_mean_stdev,
+    chipProcessedImfile.path_base,
+    rawExp.exp_tag,
+    rawExp.exp_name,
+    rawExp.camera,
+    rawExp.telescope,
+    rawExp.filelevel
+FROM chipRun
+JOIN chipProcessedImfile
+    USING(chip_id)
+JOIN rawExp
+    ON chipProcessedImfile.exp_id = rawExp.exp_id
+WHERE
+-- bogus test; just here so there there is a 'WHERE' stmt to append conditionals too
+    chipProcessedImfile.exp_id is NOT NULL
+
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_revertprocessedimfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_revertprocessedimfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_revertprocessedimfile.sql	(revision 22157)
@@ -0,0 +1,5 @@
+DELETE FROM chipProcessedImfile
+USING chipProcessedImfile, rawExp
+WHERE
+    rawExp.exp_id = chipProcessedImfile.exp_id
+    AND chipProcessedImfile.fault != 0
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_run.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_run.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_run.sql	(revision 22157)
@@ -0,0 +1,3 @@
+SELECT
+    chipRun.*
+FROM chipRun
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_unmasked.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_unmasked.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/chiptool_unmasked.sql	(revision 22157)
@@ -0,0 +1,43 @@
+-- SELECT
+--  *
+-- FROM
+--  (SELECT 
+--      chipMask.label as label, 
+--      count(chipRun.chip_id) as n_chipruns
+--   FROM 
+--      chipMask 
+--   LEFT JOIN 
+--      chipRun 
+--   USING 
+--      (label) 
+--   WHERE chipRun.chip_id IS NOT NULL 
+--   GROUP BY label) as chipMask
+
+-- SELECT
+--  *
+-- FROM
+--  SELECT 
+--      chipRun.*, 
+--      rawExp.*,
+--      chipRun.chip_id as n_chipruns
+--   FROM 
+--      chipRun 
+--   JOIN rawExp ON chipRun.exp_id = rawExp.exp_id
+--   LEFT JOIN chipMask 
+--   USING (label) 
+--   WHERE chipMask.label IS NULL 
+--   AND filter = 'r'
+--   AND chipRun.exp_id = 61
+--  GROUP BY chipRun.label) as chipUnmask
+
+SELECT
+ *
+FROM
+ (SELECT 
+     chipRun.label, 
+     count(chipRun.chip_id) as n_chipruns
+  FROM chipRun 
+  LEFT JOIN chipMask 
+  USING (label) 
+  WHERE chipMask.label IS NULL 
+  GROUP BY chipRun.label) as chipUnmask
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/detselect_search.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/detselect_search.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/detselect_search.sql	(revision 22157)
@@ -0,0 +1,29 @@
+-- this query needs to use the same fields in both of the tables in
+-- the union statement, but we need to report the
+-- detRunSummary.iteration in the first case, and this is missing in
+-- the second case.
+
+SELECT DISTINCT
+    det_id,
+    good_iteration as iteration,
+    filelevel
+FROM
+    (SELECT DISTINCT
+	detRun.*,
+	detRunSummary.iteration as good_iteration
+    FROM detRun
+    JOIN detRunSummary
+        USING(det_id)
+    WHERE
+       detRun.mode  = 'master'
+       AND detRunSummary.accept = 1
+    UNION
+    SELECT DISTINCT
+	detRun.*,
+	detRun.iteration as good_iteration
+    FROM detRun
+    WHERE
+       (detRun.mode  = 'register' OR detRun.mode = 'correction')
+    ) as Foo
+WHERE
+    (state = 'stop' OR state = 'register')
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/detselect_select.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/detselect_select.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/detselect_select.sql	(revision 22157)
@@ -0,0 +1,27 @@
+SELECT DISTINCT
+    *
+FROM
+    (SELECT DISTINCT
+	detRun.state,
+        detNormalizedImfile.*
+    FROM detNormalizedImfile
+    JOIN detRun
+        USING(det_id)
+    JOIN detRunSummary
+        ON detNormalizedImfile.det_id = detRunSummary.det_id
+        AND detNormalizedImfile.iteration = detRunSummary.iteration
+    WHERE
+        detRun.mode  = 'master'
+        AND detRunSummary.accept = 1
+    UNION
+    SELECT DISTINCT
+	detRun.state,
+        detRegisteredImfile.*
+    FROM detRegisteredImfile
+    JOIN detRun
+        USING(det_id)
+    WHERE
+        (detRun.mode  = 'register' OR detRun.mode = 'correction')
+    ) as Foo
+WHERE
+    (state = 'stop' OR state = 'register')
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_addprocessedexp.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_addprocessedexp.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_addprocessedexp.sql	(revision 22157)
@@ -0,0 +1,34 @@
+SELECT DISTINCT
+   detProcessedImfile.det_id,
+   detRun.iteration,
+   detRun.det_type,
+   detProcessedImfile.exp_id
+FROM detRun
+JOIN detInputExp
+   ON detRun.det_id = detInputExp.det_id
+   AND detRun.iteration = detInputExp.iteration
+JOIN rawExp
+   ON detInputExp.exp_id = rawExp.exp_id
+JOIN detProcessedImfile
+   ON detInputExp.det_id = detProcessedImfile.det_id
+   AND detInputExp.exp_id = detProcessedImfile.exp_id
+LEFT JOIN detProcessedExp
+   ON detInputExp.det_id = detProcessedExp.det_id
+   AND detProcessedImfile.exp_id= detProcessedExp.exp_id
+LEFT JOIN rawImfile
+   ON detInputExp.exp_id = rawImfile.exp_id
+   AND detProcessedImfile.class_id = rawImfile.class_id
+WHERE
+  detRun.state = 'run'
+  AND detRun.mode = 'master'
+  AND detProcessedExp.det_id IS NULL
+  AND detProcessedExp.exp_id IS NULL
+  AND detInputExp.include = 1
+  AND detRun.det_id = %s
+  AND detProcessedImfile.exp_id = '%s'
+GROUP BY
+   detProcessedImfile.class_id,
+   rawImfile.class_id,
+   detRun.det_id
+HAVING
+   COUNT(detProcessedImfile.class_id) = COUNT(rawImfile.class_id)
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_childlessrun.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_childlessrun.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_childlessrun.sql	(revision 22157)
@@ -0,0 +1,11 @@
+-- dettool -childlessrun
+
+SELECT DISTINCT
+   detRun.*
+ FROM detRun
+ LEFT JOIN detRun as foo
+   ON foo.parent = detRun.det_id
+ WHERE
+   detRun.state = 'stop'
+   AND detRun.mode = 'master'
+   AND foo.det_id IS NULL
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_definebydetrun.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_definebydetrun.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_definebydetrun.sql	(revision 22157)
@@ -0,0 +1,12 @@
+-- dettool -definebydetrun
+
+INSERT INTO detInputExp
+   SELECT
+       %d,
+       0,
+       detResidExp.exp_id,
+       detResidExp.accept
+   FROM detResidExp
+   JOIN rawExp
+       USING(exp_id)
+   WHERE det_id = %d
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_detrunsummary.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_detrunsummary.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_detrunsummary.sql	(revision 22157)
@@ -0,0 +1,9 @@
+SELECT DISTINCT
+   detRunSummary.*,
+   detRun.det_type,
+   detRun.mode
+ FROM detRun
+ JOIN detRunSummary
+   USING(det_id, iteration)
+ WHERE
+   detRun.state = 'run'
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_find_completed_runs.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_find_completed_runs.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_find_completed_runs.sql	(revision 22157)
@@ -0,0 +1,26 @@
+SELECT DISTINCT
+   det_id,
+   iteration
+FROM
+    (SELECT DISTINCT
+        detRun.det_id,
+        detRun.iteration,
+        detInputExp.exp_id
+    FROM detRun
+        LEFT JOIN detInputExp
+        ON detRun.det_id = detInputExp.det_id
+        AND detRun.iteration = detInputExp.iteration
+    LEFT JOIN rawExp
+        ON detInputExp.exp_id = rawExp.exp_id
+    LEFT JOIN detResidExp
+        ON detRun.det_id = detResidExp.det_id
+        AND detRun.iteration = detResidExp.iteration
+        AND detInputExp.exp_id = detResidExp.exp_id
+   WHERE
+        detRun.state = 'run'
+   GROUP BY
+        detRun.det_id,
+        detRun.iteration
+    HAVING
+        COUNT(detResidExp.exp_id) = COUNT(detInputExp.exp_id)
+    ) AS residdetrun
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_input.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_input.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_input.sql	(revision 22157)
@@ -0,0 +1,4 @@
+SELECT DISTINCT *
+ FROM detInputExp
+ JOIN rawExp
+ USING(exp_id)
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_normalizedexp.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_normalizedexp.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_normalizedexp.sql	(revision 22157)
@@ -0,0 +1,8 @@
+SELECT
+   detNormalizedExp.*
+ FROM detNormalizedExp
+ JOIN detRun
+   USING(det_id, iteration)
+ WHERE
+   detRun.state = 'run'
+   AND detRun.mode = 'master'
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_normalizedimfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_normalizedimfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_normalizedimfile.sql	(revision 22157)
@@ -0,0 +1,8 @@
+SELECT
+ detNormalizedImfile.*
+ FROM detNormalizedImfile
+ JOIN detRun
+   USING(det_id, iteration)
+ WHERE
+   detRun.state = 'run'
+   AND detRun.mode = 'master'
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_normalizedstat.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_normalizedstat.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_normalizedstat.sql	(revision 22157)
@@ -0,0 +1,12 @@
+SELECT
+    *
+FROM
+    (SELECT
+        detNormalizedStatImfile.*
+    FROM detNormalizedStatImfile
+    JOIN detRun
+    USING(det_id, iteration)
+    WHERE
+        detRun.state = 'run'
+        AND detRun.mode = 'master'
+    ) as Foo
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_pending.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_pending.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_pending.sql	(revision 22157)
@@ -0,0 +1,10 @@
+-- dettool -pending : not sure this is still used (EAM : 2008.07.10)
+
+SELECT
+   rawExp.*
+ FROM rawExp
+ LEFT JOIN detInputExp
+   ON rawExp.exp_id = detInputExp.exp_id
+ WHERE
+    detInputExp.exp_id IS NULL
+    AND rawExp.object != 'object'
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_processedimfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_processedimfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_processedimfile.sql	(revision 22157)
@@ -0,0 +1,13 @@
+SELECT DISTINCT
+  detRun.det_type,
+  rawExp.exp_time,
+  detProcessedImfile.*
+FROM detProcessedImfile
+JOIN detRun
+  USING(det_id)
+JOIN detInputExp
+  ON detRun.det_id = detInputExp.det_id
+  AND detRun.iteration = detInputExp.iteration
+  AND detProcessedImfile.exp_id = detInputExp.exp_id
+JOIN rawExp
+  ON rawExp.exp_id = detProcessedImfile.exp_id
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_raw.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_raw.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_raw.sql	(revision 22157)
@@ -0,0 +1,12 @@
+SELECT
+    detInputExp.det_id,
+    detRun.det_type,
+    rawImfile.*
+FROM detRun
+JOIN detInputExp
+    USING(det_id) 
+JOIN rawImfile
+    ON detInputExp.exp_id = rawImfile.exp_id
+WHERE
+    detRun.state = 'run'
+    AND detRun.mode = 'master'
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_residexp.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_residexp.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_residexp.sql	(revision 22157)
@@ -0,0 +1,11 @@
+SELECT
+   detRun.mode,
+   detResidExp.*,
+   detInputExp.include
+ FROM detRun
+ JOIN detInputExp
+   USING(det_id, iteration)
+ JOIN detResidExp
+   USING(det_id, iteration, exp_id)
+ WHERE
+   detRun.state = 'run'
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_residimfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_residimfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_residimfile.sql	(revision 22157)
@@ -0,0 +1,11 @@
+SELECT
+   detRun.det_type,
+   detRun.mode,
+   detResidImfile.*,
+   rawExp.exp_time
+ FROM detResidImfile
+ JOIN detRun
+   USING(det_id, iteration)
+ JOIN rawExp
+   USING(exp_id)
+ WHERE
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertdetrunsummary.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertdetrunsummary.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertdetrunsummary.sql	(revision 22157)
@@ -0,0 +1,3 @@
+DELETE FROM detRunSummary
+WHERE
+    fault != 0
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertnormalizedexp.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertnormalizedexp.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertnormalizedexp.sql	(revision 22157)
@@ -0,0 +1,3 @@
+DELETE FROM detNormalizedExp
+WHERE
+    fault != 0
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertnormalizedimfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertnormalizedimfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertnormalizedimfile.sql	(revision 22157)
@@ -0,0 +1,3 @@
+DELETE FROM detNormalizedImfile
+WHERE
+    fault != 0
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertnormalizedstat.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertnormalizedstat.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertnormalizedstat.sql	(revision 22157)
@@ -0,0 +1,3 @@
+DELETE FROM detNormalizedStatImfile
+WHERE
+    fault != 0
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertprocessedexp.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertprocessedexp.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertprocessedexp.sql	(revision 22157)
@@ -0,0 +1,3 @@
+DELETE FROM detProcessedExp
+WHERE
+    fault != 0
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertprocessedimfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertprocessedimfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertprocessedimfile.sql	(revision 22157)
@@ -0,0 +1,3 @@
+DELETE FROM detProcessedImfile
+WHERE
+    fault != 0
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertresidexp.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertresidexp.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertresidexp.sql	(revision 22157)
@@ -0,0 +1,3 @@
+DELETE FROM detResidExp
+WHERE
+    fault != 0
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertresidimfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertresidimfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertresidimfile.sql	(revision 22157)
@@ -0,0 +1,3 @@
+DELETE FROM detResidImfile
+WHERE
+    fault != 0
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertstacked.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertstacked.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_revertstacked.sql	(revision 22157)
@@ -0,0 +1,3 @@
+DELETE FROM detStackedImfile
+WHERE
+    fault != 0
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_runs.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_runs.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_runs.sql	(revision 22157)
@@ -0,0 +1,2 @@
+
+SELECT * from detRun
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_stacked.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_stacked.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_stacked.sql	(revision 22157)
@@ -0,0 +1,12 @@
+-- select detStackedImfile.*
+-- by:
+-- where det_id, iteration, class_id is not in detNormalizedImfile
+
+SELECT
+   detStackedImfile.*
+ FROM detStackedImfile
+ JOIN detRun
+   USING(det_id, iteration)
+ WHERE
+   detRun.state = 'run'
+   AND detRun.mode = 'master'
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_start_new_iteration.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_start_new_iteration.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_start_new_iteration.sql	(revision 22157)
@@ -0,0 +1,33 @@
+-- select detRun.iteration
+-- select detInputExp.exp_id
+-- select detResidExp.accept
+-- by:
+-- find the current iteration bassed on det_id
+-- find all exp_ids in the current det_id/iteration from detInputExp
+-- find all exp_ids in the current det_id/iteration from detResidExp
+-- compare the counts of exp_ids
+
+SELECT * FROM
+    (SELECT DISTINCT
+        detRun.det_id AS det_id,
+        detRun.iteration,
+        detInputExp.exp_id,
+        detResidExp.accept
+    FROM detRun
+    JOIN detInputExp
+        ON detRun.det_id = detInputExp.det_id
+        AND detRun.iteration = detInputExp.iteration
+    JOIN detResidExp
+        ON detRun.det_id = detResidExp.det_id
+        AND detRun.iteration = detResidExp.iteration
+        AND detInputExp.exp_id = detResidExp.exp_id
+    WHERE
+        detRun.state = 'run'
+        AND detRun.mode = 'master'
+    GROUP BY
+        detRun.det_id,
+        detRun.iteration,
+        detInputExp.exp_id
+    HAVING
+        COUNT(detResidExp.exp_id) = COUNT(detInputExp.exp_id)
+    ) as Foo
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_stop_completed_correct_runs.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_stop_completed_correct_runs.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_stop_completed_correct_runs.sql	(revision 22157)
@@ -0,0 +1,28 @@
+UPDATE detRun
+SET state = 'stop'
+WHERE det_id = (SELECT det_id FROM
+    (SELECT
+        det1.det_id,
+        imfile.class_id,
+        detRegisteredImfile.class_id as reg_class_id
+    FROM detRun AS det1
+    INNER JOIN detRun as det2
+        ON det1.parent = det2.det_id 
+    JOIN detNormalizedImfile as imfile
+        ON det2.det_id = imfile.det_id
+        AND det2.iteration = imfile.iteration
+    LEFT JOIN detRegisteredImfile
+        ON det1.det_id = detRegisteredImfile.det_id
+        AND det1.iteration = detRegisteredImfile.iteration
+        AND imfile.class_id = detRegisteredImfile.class_id
+    WHERE 
+        det1.state = 'run'
+        AND det1.mode = 'correction'
+        AND det2.state = 'stop'
+    GROUP BY
+        det1.det_id
+    HAVING
+        COUNT(imfile.class_id) = COUNT(reg_class_id)
+        AND SUM(imfile.fault) = 0
+    ) as Foo
+)
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_tocorrectexp.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_tocorrectexp.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_tocorrectexp.sql	(revision 22157)
@@ -0,0 +1,4 @@
+SELECT * FROM detRun
+WHERE
+    detRun.mode = 'correction'
+    AND detRun.state = 'run'
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_tocorrectimfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_tocorrectimfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_tocorrectimfile.sql	(revision 22157)
@@ -0,0 +1,31 @@
+SELECT
+    det1.*,
+    imfile.class_id,
+    imfile.uri,
+    imfile.bg,
+    imfile.bg_stdev,
+    imfile.bg_mean_stdev,
+    imfile.user_1,
+    imfile.user_2,
+    imfile.user_3,
+    imfile.user_4,
+    imfile.user_5,
+    imfile.path_base,
+    imfile.fault
+FROM detRun AS det1
+INNER JOIN detRun as det2
+    ON det1.parent = det2.det_id 
+JOIN detNormalizedImfile as imfile
+    ON det2.det_id = imfile.det_id
+    AND det2.iteration = imfile.iteration
+LEFT JOIN detRegisteredImfile
+    ON det1.det_id = detRegisteredImfile.det_id
+    AND det1.iteration = detRegisteredImfile.iteration
+    AND imfile.class_id = detRegisteredImfile.class_id
+WHERE 
+    det1.state = 'run'
+    AND det1.mode = 'correction'
+    AND det2.state = 'stop'
+    AND detRegisteredImfile.det_id IS NULL
+    AND detRegisteredImfile.iteration IS NULL
+    AND detRegisteredImfile.class_id IS NULL
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_todetrunsummary.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_todetrunsummary.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_todetrunsummary.sql	(revision 22157)
@@ -0,0 +1,47 @@
+-- select detRun.det_id
+-- select detRun.iteration
+-- select detRun.det_type
+-- by:
+-- find the current iteration bassed on det_id
+-- find all exp_ids in the current det_id/iteration from detInputExp
+-- find all exp_ids in the current det_id/iteration from detResidExp
+-- compare the counts of exp_ids
+
+SELECT DISTINCT
+    det_id,
+    iteration,
+    det_type,
+    mode,
+    workdir,
+    camera
+FROM
+    (SELECT DISTINCT
+        detRun.det_id,
+        detRun.iteration,
+        detRun.det_type,
+        detRun.mode,
+        detRun.workdir,
+        detInputExp.exp_id,
+        rawExp.camera
+    FROM detRun
+    JOIN detInputExp
+        USING(det_id, iteration)
+    JOIN rawExp
+        ON detInputExp.exp_id = rawExp.exp_id
+    LEFT JOIN detResidExp
+        ON detRun.det_id = detResidExp.det_id
+        AND detRun.iteration = detResidExp.iteration
+        AND detInputExp.exp_id = detResidExp.exp_id
+    LEFT JOIN detRunSummary
+        ON detRun.det_id = detRunSummary.det_id
+        AND detRun.iteration = detRunSummary.iteration
+    WHERE
+        detRun.state = 'run'
+        AND detRunSummary.det_id IS NULL
+        AND detRunSummary.iteration IS NULL
+    GROUP BY
+        detRun.det_id,
+        detRun.iteration
+    HAVING
+        COUNT(detResidExp.exp_id) = COUNT(detInputExp.exp_id)
+    ) AS Foo
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_tonormalize.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_tonormalize.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_tonormalize.sql	(revision 22157)
@@ -0,0 +1,27 @@
+SELECT DISTINCT
+   detRun.det_type,
+   detRun.workdir,
+   rawExp.camera,
+   detStackedImfile.uri,
+   detNormalizedStatImfile.*
+ FROM detRun
+ JOIN detStackedImfile
+   USING(det_id, iteration)
+ JOIN detInputExp
+   USING(det_id, iteration)
+ JOIN rawExp
+   ON detInputExp.exp_id = rawExp.exp_id
+ JOIN detNormalizedStatImfile
+   ON detStackedImfile.det_id = detNormalizedStatImfile.det_id
+   AND detStackedImfile.iteration = detNormalizedStatImfile.iteration
+   AND detStackedImfile.class_id = detNormalizedStatImfile.class_id
+ LEFT JOIN detNormalizedImfile
+   ON detNormalizedStatImfile.det_id = detNormalizedImfile.det_id
+   AND detNormalizedStatImfile.iteration = detNormalizedImfile.iteration
+   AND detNormalizedStatImfile.class_id = detNormalizedImfile.class_id
+ WHERE
+   detNormalizedImfile.det_id IS NULL
+   AND detNormalizedImfile.iteration IS NULL
+   AND detNormalizedImfile.class_id IS NULL
+   AND detNormalizedStatImfile.fault = 0
+   AND detRun.state = 'run'
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_tonormalizedexp.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_tonormalizedexp.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_tonormalizedexp.sql	(revision 22157)
@@ -0,0 +1,48 @@
+-- this is used by both 'tonormalizedexp' and 'addnormalizedexp'
+
+SELECT
+    *
+FROM (SELECT 
+      *
+    FROM (
+        SELECT DISTINCT
+            detRun.det_id,
+            detRun.iteration,
+            detRun.det_type,
+            detRun.workdir,
+            rawExp.camera,
+            rawExp.telescope,
+            rawExp.exp_type,
+            detNormalizedImfile.class_id as norm_class_id,
+            detNormalizedImfile.fault,
+            detStackedImfile.class_id as stack_class_id
+        FROM detRun
+        JOIN detInputExp
+            ON detRun.det_id = detInputExp.det_id
+            AND detRun.iteration = detInputExp.iteration
+        JOIN rawExp
+            ON detInputExp.exp_id = rawExp.exp_id
+        JOIN detStackedImfile
+            ON detStackedImfile.det_id = detRun.det_id
+            AND detStackedImfile.iteration = detRun.iteration
+        LEFT JOIN detNormalizedImfile
+            ON detStackedImfile.det_id = detNormalizedImfile.det_id
+            AND detStackedImfile.iteration = detNormalizedImfile.iteration
+            AND detStackedImfile.class_id = detNormalizedImfile.class_id
+        LEFT JOIN detNormalizedExp
+            ON detInputExp.det_id = detNormalizedExp.det_id
+            AND detInputExp.iteration = detNormalizedExp.iteration
+        WHERE
+            detRun.state = 'run' AND
+            detRun.mode = 'master' AND
+            detNormalizedExp.det_id IS NULL AND
+            detNormalizedExp.iteration IS NULL AND
+            detInputExp.include = 1
+    ) as Foo
+    GROUP BY
+      det_id,
+      iteration
+    HAVING
+      COUNT(norm_class_id) = COUNT(stack_class_id)
+      AND SUM(fault) = 0
+) as Bar
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_tonormalizedstat.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_tonormalizedstat.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_tonormalizedstat.sql	(revision 22157)
@@ -0,0 +1,58 @@
+-- select detRun.det_id (det_id)
+-- select detRun.iteration
+-- by:
+-- find the current iteration bassed on det_id
+-- find all exp_ids in the current det_id/iteration from detInputExp
+-- sort to detInputExp.imfiles to find the largest value per det_id/iter
+-- compare imfiles to the number of detStackedImfiles by class_id
+-- and:
+-- ???
+-- det_id is not in detStackedImfile;
+-- iteration is not in detStackedImfile;
+
+SELECT
+    det_id,
+    det_type,
+    iteration,
+    camera,
+    workdir,
+    class_id
+FROM
+    (SELECT DISTINCT
+        detRun.det_id,
+        detRun.det_type,
+        detRun.iteration,
+        detRun.workdir,
+        rawExp.camera,
+        detStackedImfile.class_id,
+        rawImfile.class_id as rawimfile_class_id
+    FROM detRun
+    JOIN detInputExp
+        ON detRun.det_id = detInputExp.det_id
+        AND detRun.iteration = detInputExp.iteration
+    JOIN rawExp
+        ON detInputExp.exp_id = rawExp.exp_id
+    JOIN rawImfile
+        ON rawExp.exp_id = rawImfile.exp_id
+    LEFT JOIN detStackedImfile
+        ON detInputExp.det_id = detStackedImfile.det_id
+        AND detInputExp.iteration = detStackedImfile.iteration
+        AND rawImfile.class_id = detStackedImfile.class_id
+    LEFT JOIN detNormalizedStatImfile
+        ON detStackedImfile.det_id = detNormalizedStatImfile.det_id
+        AND detStackedImfile.iteration = detNormalizedStatImfile.iteration
+        AND detStackedImfile.class_id = detNormalizedStatImfile.class_id
+    WHERE
+        detRun.state = 'run'
+        AND detRun.mode = 'master'
+        AND detNormalizedStatImfile.det_id IS NULL
+        AND detNormalizedStatImfile.iteration IS NULL
+        AND detNormalizedStatImfile.class_id IS NULL
+    GROUP BY
+--        rawExp.exp_id,
+        detRun.iteration,
+        detRun.det_id
+    HAVING
+        COUNT(rawImfile.class_id) = COUNT(detStackedImfile.class_id)
+        AND SUM(detStackedImfile.fault) = 0
+    ) as tonormalizedstat
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_toprocessedexp.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_toprocessedexp.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_toprocessedexp.sql	(revision 22157)
@@ -0,0 +1,61 @@
+-- select detProcessedImfile.det_id
+-- select detRun.iteration
+-- select detRun.det_type
+-- select detProcessedImfile.exp_id
+-- by:
+-- find the current iteration bassed on det_id
+-- find all exp_ids in the current det_id/iteration from detInputExp
+-- find all rawImfiles in the current exp_ids
+-- compare to detProcessedImfiles by det_id/exp_id
+-- found how many imfile there are in each class_id
+-- and:
+-- det_id is not in detProcessedExp;
+-- iteration is not in detProcessedExp;
+
+SELECT DISTINCT
+    det_id,
+    iteration,
+    det_type,
+    exp_id,
+    camera,
+    workdir,
+    exp_tag
+FROM
+    (SELECT DISTINCT
+        detRun.det_id,
+        detRun.iteration,
+        detRun.det_type,
+        detRun.workdir,
+        detProcessedImfile.exp_id,
+        detProcessedImfile.class_id,
+        rawImfile.class_id as rawimfile_class_id,
+        rawExp.camera,
+        rawExp.exp_tag
+    FROM detRun
+    JOIN detInputExp
+        USING(det_id, iteration)
+    JOIN rawExp
+        ON detInputExp.exp_id = rawExp.exp_id
+    JOIN rawImfile
+        ON rawExp.exp_id = rawImfile.exp_id
+    LEFT JOIN detProcessedImfile
+        ON detRun.det_id = detProcessedImfile.det_id
+        AND detInputExp.exp_id = detProcessedImfile.exp_id
+        AND rawImfile.class_id = detProcessedImfile.class_id
+    LEFT JOIN detProcessedExp
+        ON detProcessedImfile.det_id = detProcessedExp.det_id
+        AND detProcessedImfile.exp_id = detProcessedExp.exp_id
+    WHERE
+        detRun.state = 'run'
+        AND detRun.mode = 'master'
+        AND detProcessedExp.det_id IS NULL
+        AND detProcessedExp.exp_id IS NULL
+        AND detInputExp.include = 1
+    GROUP BY
+        rawExp.exp_id,
+        detRun.det_id
+    HAVING
+        COUNT(detProcessedImfile.class_id) = COUNT(rawImfile.class_id)
+        AND SUM(detProcessedImfile.fault) = 0
+    ) AS detProcessedExp
+
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_toprocessedimfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_toprocessedimfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_toprocessedimfile.sql	(revision 22157)
@@ -0,0 +1,24 @@
+SELECT
+    detRun.det_id,
+    detRun.det_type,
+    detRun.workdir,
+    detRun.reduction,
+    rawImfile.*,
+    rawExp.exp_tag
+FROM detRun
+JOIN detInputExp
+    USING(det_id, iteration)
+JOIN rawExp
+    ON detInputExp.exp_id = rawExp.exp_id
+JOIN rawImfile
+    ON detInputExp.exp_id = rawImfile.exp_id
+LEFT JOIN detProcessedImfile
+    ON detInputExp.det_id = detProcessedImfile.det_id
+    AND rawImfile.exp_id = detProcessedImfile.exp_id
+    AND rawImfile.class_id = detProcessedImfile.class_id
+WHERE
+    detRun.state = 'run'
+    AND detRun.mode = 'master'
+    AND detProcessedImfile.det_id IS NULL
+    AND detProcessedImfile.exp_id IS NULL
+    AND detProcessedImfile.class_id IS NULL
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_toresidexp.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_toresidexp.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_toresidexp.sql	(revision 22157)
@@ -0,0 +1,70 @@
+-- this is used by both 'toresidexp' and 'addresidexp'
+
+-- which returns a list of exposures for which all component class ids have had
+-- residuals created.  This list includes the detrend id, iteration, exposure
+-- id, detrend type, and whether the exposure was included in the stack for
+-- this iteration.
+
+-- select detRun.det_id
+-- select detRun.iteration
+-- select detRun.det_type
+-- select detInputExp.exp_id
+-- select detInputExp.include
+-- by:
+-- find the current iteration bassed on det_id
+-- find all exp_ids in the current det_id/iteration from detInputExp
+-- compare to detInputExp.imfiles to derResidImfile by class_id
+-- and:
+-- detResidImfile.{det_id, iteration, exp_id} is not in detResidExp
+
+SELECT DISTINCT
+    det_id,
+    iteration,
+    det_type,
+    mode,
+    exp_id,
+    include,
+    camera,
+    workdir,
+    exp_tag
+FROM
+    (SELECT DISTINCT
+        detRun.det_id AS det_id,
+        detRun.iteration,
+        detRun.det_type,
+        detRun.mode,
+        detRun.workdir,
+        detInputExp.exp_id,
+        detInputExp.include,
+        rawExp.camera,
+        rawExp.exp_tag,
+        detResidImfile.class_id
+    FROM detRun
+    JOIN detInputExp
+        USING(det_id, iteration)
+    JOIN rawExp
+        ON detInputExp.exp_id = rawExp.exp_id
+    JOIN rawImfile
+        on rawExp.exp_id = rawImfile.exp_id
+    LEFT JOIN detResidImfile
+        ON detRun.det_id = detResidImfile.det_id
+        AND detRun.iteration = detResidImfile.iteration
+        AND detInputExp.exp_id = detResidImfile.exp_id
+        AND rawImfile.class_id = detResidImfile.class_id
+    LEFT JOIN detResidExp
+        ON detResidImfile.det_id = detResidExp.det_id
+        AND detResidImfile.iteration = detResidExp.iteration
+        AND detResidImfile.exp_id = detResidExp.exp_id
+    WHERE
+        detRun.state = 'run'
+        AND detResidExp.det_id IS NULL
+        AND detResidExp.iteration IS NULL
+        AND detResidExp.exp_id IS NULL
+    GROUP BY
+        detInputExp.exp_id,
+        detRun.iteration,
+        detRun.det_id
+    HAVING
+        COUNT(rawImfile.class_id) = COUNT(detResidImfile.class_id)
+        AND SUM(detResidImfile.fault = 0)
+    ) AS toresidexp
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_toresidimfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_toresidimfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_toresidimfile.sql	(revision 22157)
@@ -0,0 +1,82 @@
+-- which returns a list of processed imfiles (with corresponding detrend id,
+-- iteration, class id, uri, type) for which the appropriate stacked imfile has
+-- been normalised, and which have not been masked out by detResidImfileAnalysis
+
+SELECT DISTINCT
+    detRun.det_id,
+    detRun.iteration,
+    detRun.det_type,
+    detRun.mode,
+    detRun.workdir,
+    detRun.reduction,
+    detProcessedImfile.exp_id,
+    detProcessedImfile.class_id,
+    detProcessedImfile.uri,
+    detNormalizedImfile.uri AS det_uri,
+    rawExp.camera,
+    rawExp.exp_tag
+FROM detRun
+JOIN detInputExp
+    USING(det_id, iteration)
+JOIN rawExp
+    ON detInputExp.exp_id = rawExp.exp_id
+JOIN detProcessedImfile
+    ON detRun.det_id = detProcessedImfile.det_id
+    AND detInputExp.exp_id = detProcessedImfile.exp_id
+JOIN detNormalizedImfile
+    ON detRun.det_id = detNormalizedImfile.det_id
+    AND detRun.iteration = detNormalizedImfile.iteration
+    AND detProcessedImfile.class_id = detNormalizedImfile.class_id
+JOIN detNormalizedExp
+    ON detRun.det_id = detNormalizedExp.det_id
+    AND detRun.iteration = detNormalizedExp.iteration
+LEFT JOIN detResidImfile
+    ON detRun.det_id = detResidImfile.det_id
+    AND detRun.iteration = detResidImfile.iteration
+    AND detProcessedImfile.exp_id = detResidImfile.exp_id
+    AND detProcessedImfile.class_id = detResidImfile.class_id
+WHERE
+    detRun.state = 'run'
+    AND detRun.mode = 'master'
+    AND detNormalizedImfile.fault = 0
+    AND detNormalizedExp.fault = 0
+    AND detResidImfile.det_id IS NULL
+    AND detResidImfile.iteration IS NULL
+    AND detResidImfile.exp_id IS NULL
+    AND detResidImfile.class_id IS NULL
+UNION
+SELECT
+    detRun.det_id,
+    detRun.iteration,
+    detRun.det_type,
+    detRun.mode,
+    detRun.workdir,
+    detRun.reduction,
+    rawImfile.exp_id,
+    rawImfile.class_id,
+    rawImfile.uri,
+    'NULL' AS det_uri,
+    rawExp.camera,
+    rawExp.exp_tag
+FROM detRun
+JOIN detInputExp
+    USING(det_id, iteration)
+JOIN rawExp
+    ON detInputExp.exp_id = rawExp.exp_id
+JOIN rawImfile
+    ON detInputExp.exp_id = rawImfile.exp_id
+LEFT JOIN detResidImfile
+    ON detRun.det_id = detResidImfile.det_id
+    AND detRun.iteration = detResidImfile.iteration
+    AND rawImfile.exp_id = detResidImfile.exp_id
+    AND rawImfile.class_id = detResidImfile.class_id
+WHERE
+    detRun.state = 'run'
+    AND detRun.mode = 'verify'
+    -- AND detNormalizedImfile.fault = 0
+    -- AND detNormalizedExp.fault = 0
+    AND detResidImfile.det_id IS NULL
+    AND detResidImfile.iteration IS NULL
+    AND detResidImfile.exp_id IS NULL
+    AND detResidImfile.class_id IS NULL
+ORDER BY exp_id
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_tostacked.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_tostacked.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/dettool_tostacked.sql	(revision 22157)
@@ -0,0 +1,52 @@
+-- select detRun.iteration
+-- select detProcessedImfile.det_id
+-- select detProcessedImfile.class_id
+-- by:
+-- find the current iteration bassed on det_id
+-- find all exp_ids in the current det_id/iteration from detInputExp
+-- find all rawImfiles in the current exp_ids
+-- compare to detProcessedImfiles by det_id/exp_id
+-- found how many imfile there are in each class_id
+-- and:
+-- det_id is not in detStackedImfile;
+-- iteration is not in detStackedImfile;
+-- class_id is not in detStackedImfile;
+
+SELECT
+    detProcessedImfile.det_id,
+    detRun.iteration,
+    detRun.det_type,
+    detRun.workdir,
+    detRun.reduction,
+    detProcessedImfile.class_id,
+    rawExp.camera,
+    rawExp.exp_tag
+FROM detRun
+JOIN detInputExp
+    ON detRun.det_id = detInputExp.det_id
+    AND detRun.iteration = detInputExp.iteration
+JOIN rawExp
+    ON detInputExp.exp_id = rawExp.exp_id
+JOIN rawImfile
+    ON detInputExp.exp_id = rawImfile.exp_id
+LEFT JOIN detProcessedImfile
+    ON detInputExp.det_id = detProcessedImfile.det_id
+    AND detInputExp.exp_id = detProcessedImfile.exp_id
+    AND rawImfile.class_id = detProcessedImfile.class_id
+LEFT JOIN detStackedImfile
+    ON detInputExp.det_id = detStackedImfile.det_id
+    AND detInputExp.iteration = detStackedImfile.iteration
+    AND rawImfile.class_id = detStackedImfile.class_id
+WHERE
+    detRun.state = 'run'
+    AND detRun.mode = 'master'
+    AND detStackedImfile.det_id IS NULL
+    AND detStackedImfile.iteration IS NULL
+    AND detStackedImfile.class_id IS NULL
+    AND detInputExp.include = 1
+GROUP BY
+    rawImfile.class_id,
+    detRun.det_id
+HAVING
+    COUNT(detProcessedImfile.class_id) = COUNT(rawImfile.class_id)
+    AND SUM(detProcessedImfile.fault) = 0
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/difftool_inputskyfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/difftool_inputskyfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/difftool_inputskyfile.sql	(revision 22157)
@@ -0,0 +1,73 @@
+SELECT * FROM
+    (SELECT 
+        diffRun.diff_id,
+        diffRun.skycell_id,
+        diffRun.tess_id,
+        0 as stack_id,
+        warpSkyfile.warp_id,
+        warpSkyfile.uri,
+        warpSkyfile.path_base,
+        diffInputSkyfile.template,
+        rawExp.camera
+    FROM diffRun
+    JOIN diffInputSkyfile
+        USING(diff_id)
+    JOIN warpSkyfile
+        ON  diffInputSkyfile.warp_id    = warpSkyfile.warp_id
+        AND diffInputSkyfile.skycell_id = warpSkyfile.skycell_id
+        AND diffInputSkyfile.tess_id    = warpSkyfile.tess_id
+    JOIN warpRun
+        ON diffInputSkyfile.warp_id = warpRun.warp_id
+    JOIN fakeRun
+        USING(fake_id)
+    JOIN camRun
+        USING(cam_id)
+    JOIN chipRun
+        USING(chip_id)
+    JOIN chipProcessedImfile
+        USING(chip_id)
+    JOIN rawExp
+        ON chipRun.exp_id = rawExp.exp_id
+    WHERE
+        diffRun.state = 'run'
+        AND warpRun.state = 'stop'
+        AND fakeRun.state = 'stop'
+        AND camRun.state = 'stop'
+        AND chipRun.state = 'full'
+    UNION
+    SELECT 
+        diffRun.diff_id,
+        diffRun.skycell_id,
+        diffRun.tess_id,
+        stackSumSkyfile.stack_id,
+        0 as warp_id,
+        stackSumSkyfile.uri,
+        stackSumSkyfile.path_base,
+        diffInputSkyfile.template,
+        rawExp.camera
+    FROM diffRun
+    JOIN diffInputSkyfile
+        USING(diff_id)
+    JOIN stackSumSkyfile
+        ON  diffInputSkyfile.stack_id = stackSumSkyfile.stack_id
+    JOIN stackInputSkyfile
+        ON diffInputSkyfile.stack_id = stackInputSkyfile.stack_id
+    JOIN warpRun
+        ON stackInputSkyfile.warp_id = warpRun.warp_id
+    JOIN fakeRun
+        USING(fake_id)
+    JOIN camRun
+        USING(cam_id)
+    JOIN chipRun
+        USING(chip_id)
+    JOIN chipProcessedImfile
+        USING(chip_id)
+    JOIN rawExp
+        ON chipRun.exp_id = rawExp.exp_id
+    WHERE
+        diffRun.state = 'run'
+        AND warpRun.state = 'stop'
+        AND fakeRun.state = 'stop'
+        AND camRun.state = 'stop'
+        AND chipRun.state = 'full'
+    ) as Foo
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/difftool_queue.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/difftool_queue.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/difftool_queue.sql	(revision 22157)
@@ -0,0 +1,22 @@
+SELECT
+        stackRun.skycell_id,
+        stackRun.tess_id,
+        stackRun.stack_id,
+	diffInputSkyfile.template,
+        warpSkyfile.warp_id
+FROM stackSumSkyfile
+JOIN stackRun
+        USING(stack_id)
+JOIN stackInputSkyfile
+        USING(stack_id)
+JOIN warpSkyfile
+        USING(warp_id,tess_id,skycell_id)
+LEFT JOIN diffInputSkyfile
+        ON diffInputSkyfile.stack_id = stackRun.stack_id
+        OR diffInputSkyfile.warp_id = warpSkyfile.warp_id
+WHERE
+        stackSumSkyfile.fault = 0
+        AND warpSkyfile.fault = 0
+        AND stackRun.state = 'stop'
+        AND diffInputSkyfile.stack_id IS NULL
+        AND diffInputSkyfile.warp_id IS NULL
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/difftool_revertdiffskyfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/difftool_revertdiffskyfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/difftool_revertdiffskyfile.sql	(revision 22157)
@@ -0,0 +1,3 @@
+DELETE FROM diffSkyfile
+WHERE
+    fault != 0
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/difftool_skyfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/difftool_skyfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/difftool_skyfile.sql	(revision 22157)
@@ -0,0 +1,10 @@
+SELECT
+    diffRun.skycell_id,
+    diffRun.tess_id,
+    diffSkyfile.*
+FROM diffRun
+JOIN diffSkyfile
+    USING(diff_id)
+WHERE
+    diffRun.state = 'run'
+
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/difftool_todiffskyfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/difftool_todiffskyfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/difftool_todiffskyfile.sql	(revision 22157)
@@ -0,0 +1,32 @@
+SELECT DISTINCT
+    diffRun.diff_id,
+    diffRun.workdir,
+    diffRun.skycell_id,
+    diffRun.tess_id,
+    rawExp.camera
+FROM diffRun
+JOIN diffInputSkyfile
+    USING(diff_id)
+JOIN warpSkyfile
+    ON  diffInputSkyfile.warp_id    = warpSkyfile.warp_id
+    AND diffInputSkyfile.skycell_id = warpSkyfile.skycell_id
+    AND diffInputSkyfile.tess_id    = warpSkyfile.tess_id
+JOIN warpRun
+    ON warpRun.warp_id = warpSkyfile.warp_id
+JOIN fakeRun
+    USING(fake_id)
+JOIN camRun
+    USING(cam_id)
+JOIN chipRun
+    USING(chip_id)
+JOIN rawExp
+    USING(exp_id)
+LEFT JOIN diffSkyfile
+    ON diffInputSkyfile.diff_id = diffSkyfile.diff_id
+WHERE
+  diffRun.state = 'run'
+  AND warpRun.state = 'stop'
+  AND fakeRun.state = 'stop'
+  AND camRun.state = 'stop'
+  AND chipRun.state = 'full'
+  AND diffSkyfile.diff_id IS NULL
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/faketool_completely_processed_exp.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/faketool_completely_processed_exp.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/faketool_completely_processed_exp.sql	(revision 22157)
@@ -0,0 +1,40 @@
+-- the output of this query must match the format of fakeRun row
+SELECT DISTINCT
+    fake_id,
+    cam_id,
+    state,
+    workdir,
+    label,
+    reduction,
+    expgroup,
+    dvodb,
+    tess_id,
+    end_stage,
+    epoch
+FROM
+    (SELECT 
+        fakeRun.*,
+        rawImfile.class_id as rawimfile_class_id,
+        fakeProcessedImfile.class_id
+    FROM fakeRun
+    JOIN camRun
+        USING(cam_id)
+    JOIN chipRun
+        USING(chip_id)
+    JOIN rawExp
+        USING(exp_id)
+    JOIN rawImfile
+        USING(exp_id)
+    LEFT JOIN fakeProcessedImfile
+        ON fakeRun.fake_id = fakeProcessedImfile.fake_id
+        AND rawImfile.exp_id = fakeProcessedImfile.exp_id
+        AND rawImfile.class_id = fakeProcessedImfile.class_id
+    WHERE
+        fakeRun.state = 'run'
+    GROUP BY
+        fakeRun.fake_id,
+        rawExp.exp_id
+    HAVING
+        COUNT(rawImfile.class_id) = COUNT(fakeProcessedImfile.class_id)
+        AND SUM(fakeProcessedImfile.fault) = 0
+    ) as Foo
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/faketool_find_camrun.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/faketool_find_camrun.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/faketool_find_camrun.sql	(revision 22157)
@@ -0,0 +1,36 @@
+SELECT
+    *
+FROM
+    (SELECT DISTINCT
+        camRun.cam_id,
+        chipRun.*,
+        rawExp.camera,
+        rawExp.telescope,
+        rawExp.dateobs,
+        rawExp.exp_tag,
+        rawExp.exp_type,
+        rawExp.filelevel,
+        rawExp.filter,
+        rawExp.airmass,
+        rawExp.ra,
+        rawExp.decl,
+        rawExp.exp_time,
+        rawExp.sat_pixel_frac,
+        rawExp.bg,
+        rawExp.bg_stdev,
+        rawExp.bg_mean_stdev,
+        rawExp.alt,
+        rawExp.az,
+        rawExp.ccd_temp,
+        rawExp.posang,
+        rawExp.object,
+        rawExp.solang
+    FROM camRun
+    JOIN chipRun
+        using(chip_id)
+    JOIN rawExp
+        using(exp_id)
+    WHERE
+        camRun.state = 'stop'
+        AND chipRun.state = 'full'
+) as Foo
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/faketool_find_pendingexp.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/faketool_find_pendingexp.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/faketool_find_pendingexp.sql	(revision 22157)
@@ -0,0 +1,34 @@
+-- this query is used by both camtool -pendingexp & camtool -addprocessedexp it
+-- does a little more work then is necessary for -addprocessed but it seems
+-- "cleaner" to use the same query for both cases 
+SELECT * FROM
+    (SELECT
+        fakeRun.*,
+        rawExp.exp_tag,
+        rawExp.exp_id,
+        rawExp.exp_name,
+        rawExp.camera,
+        rawExp.telescope,
+        rawExp.filelevel
+    FROM fakeRun
+    JOIN camRun
+        USING(cam_id)
+    JOIN chipRun
+        USING(chip_id)
+    JOIN chipProcessedImfile
+        USING(chip_id)
+    JOIN rawExp
+        ON chipRun.exp_id = rawExp.exp_id
+    LEFT JOIN fakeProcessedImfile
+        USING(fake_id)
+    LEFT JOIN fakeMask
+        ON fakeRun.label = fakeMask.label
+    WHERE
+        fakeRun.state = 'run'
+        AND camRun.state = 'stop'
+        AND chipRun.state = 'full'
+        AND fakeMask.label IS NULL
+        AND fakeProcessedImfile.fake_id IS NULL
+    GROUP BY
+        fakeRun.fake_id
+    ) as Foo
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/faketool_pendingimfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/faketool_pendingimfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/faketool_pendingimfile.sql	(revision 22157)
@@ -0,0 +1,49 @@
+SELECT DISTINCT * FROM 
+     -- the subselect is so where criteria can be specified without knowing
+     -- which table the field came from
+    (SELECT
+        fakeRun.*,
+        chipProcessedImfile.exp_id,
+        chipProcessedImfile.class_id,
+        chipProcessedImfile.uri,
+        chipProcessedImfile.bg,
+        chipProcessedImfile.bg_stdev,
+        chipProcessedImfile.bg_mean_stdev,
+        chipProcessedImfile.fringe_0,
+        chipProcessedImfile.fringe_1,
+        chipProcessedImfile.fringe_2,
+        chipProcessedImfile.ap_resid,
+        chipProcessedImfile.ap_resid_stdev,
+        chipProcessedImfile.sigma_ra,
+        chipProcessedImfile.sigma_dec,
+        chipProcessedImfile.n_stars,
+        chipProcessedImfile.n_extended,
+        chipProcessedImfile.n_cr,
+        chipProcessedImfile.n_astrom,
+        chipProcessedImfile.path_base as chip_path_base,
+        camProcessedExp.path_base as cam_path_base,
+        rawExp.exp_name,
+        rawExp.exp_tag,
+        rawExp.camera,
+        rawExp.telescope,
+        rawExp.filelevel
+    FROM fakeRun
+    JOIN camRun
+        USING(cam_id)
+    JOIN camProcessedExp
+        USING(cam_id)
+    JOIN chipRun
+        USING(chip_id)
+    JOIN chipProcessedImfile
+        USING(chip_id)
+    JOIN rawExp
+        ON chipProcessedImfile.exp_id = rawExp.exp_id
+    LEFT JOIN fakeProcessedImfile
+        ON fakeRun.fake_id = fakeProcessedImfile.fake_id
+    LEFT JOIN fakeMask
+        ON fakeRun.label = fakeMask.label
+    WHERE
+        fakeProcessedImfile.fake_id IS NULL
+        AND fakeProcessedImfile.class_id IS NULL
+        AND fakeMask.label IS NULL
+    ) as foo
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/faketool_processedimfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/faketool_processedimfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/faketool_processedimfile.sql	(revision 22157)
@@ -0,0 +1,17 @@
+SELECT DISTINCT
+    fakeProcessedImfile.*,
+    rawExp.exp_tag,
+    rawExp.exp_name,
+    rawExp.camera,
+    rawExp.telescope,
+    rawExp.filelevel
+FROM fakeRun
+JOIN fakeProcessedImfile
+    USING(fake_id)
+JOIN rawExp
+    ON fakeProcessedImfile.exp_id = rawExp.exp_id
+WHERE
+-- bogus test; just here so there there is a 'WHERE' stmt to append
+-- conditionals too
+    fakeProcessedImfile.exp_id is NOT NULL
+
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/faketool_queue_cam_id.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/faketool_queue_cam_id.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/faketool_queue_cam_id.sql	(revision 22157)
@@ -0,0 +1,17 @@
+INSERT INTO fakeRun
+    SElECT
+        0,              -- fake_id
+        cam_id,         -- cam_id
+        '%s',           -- state
+        '%s',           -- workdir
+        '%s',           -- label
+        '%s',           -- reduction
+        '%s',           -- expgroup
+        '%s',           -- dvodb 
+        '%s',           -- tess_id
+        '%s',           -- end_stage
+        NULL            -- epoch
+    FROM camRun
+    WHERE
+        camRun.state = 'stop'
+        AND camRun.cam_id = %lld
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/faketool_revertprocessedimfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/faketool_revertprocessedimfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/faketool_revertprocessedimfile.sql	(revision 22157)
@@ -0,0 +1,8 @@
+DELETE FROM fakeProcessedImfile
+USING fakeProcessedImfile, fakeRun, camRun, chipRun, rawExp
+WHERE
+    fakeProcessedImfile.fake_id = fakeRun.fake_id
+    AND fakeRun.cam_id = camRun.cam_id
+    AND camRun.chip_id = chipRun.chip_id
+    AND chipRun.exp_id = rawExp.exp_id
+    AND fakeProcessedImfile.fault != 0
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/faketool_unmasked.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/faketool_unmasked.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/faketool_unmasked.sql	(revision 22157)
@@ -0,0 +1,11 @@
+SELECT
+ *
+FROM
+ (SELECT 
+     fakeRun.label, 
+     count(fakeRun.fake_id) as n_fakeruns
+  FROM fakeRun 
+  LEFT JOIN fakeMask 
+  USING (label) 
+  WHERE fakeMask.label IS NULL 
+  GROUP BY fakeRun.label) as fakeUnmask
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/flatcorr_completely_processed_chiprun.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/flatcorr_completely_processed_chiprun.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/flatcorr_completely_processed_chiprun.sql	(revision 22157)
@@ -0,0 +1,36 @@
+INSERT INTO chipRunDone
+SELECT
+    chip_id,
+    exp_id
+FROM (SELECT DISTINCT
+        chip_id,
+        exp_id,
+        state,
+        workdir,
+        workdir_state,
+        label,
+        reduction,
+        expgroup,
+        dvodb
+    FROM
+        (SELECT 
+            chipRun.*,
+            rawImfile.class_id as rawimfile_class_id,
+            chipProcessedImfile.class_id
+        FROM chipRun
+        JOIN rawImfile
+            USING(exp_id)
+        LEFT JOIN chipProcessedImfile
+            ON chipRun.chip_id = chipProcessedImfile.chip_id
+            AND rawImfile.exp_id = chipProcessedImfile.exp_id
+            AND rawImfile.class_id = chipProcessedImfile.class_id
+        WHERE
+            chipRun.state = 'full'
+        GROUP BY
+            chipRun.chip_id,
+            chipRun.exp_id
+        HAVING
+            COUNT(rawImfile.class_id) = COUNT(chipProcessedImfile.class_id)
+            AND SUM(chipProcessedImfile.fault) = 0
+        ) as Foo
+    ) as Bar
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/flatcorr_create_tmp_chiprundone.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/flatcorr_create_tmp_chiprundone.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/flatcorr_create_tmp_chiprundone.sql	(revision 22157)
@@ -0,0 +1,3 @@
+CREATE TEMPORARY TABLE chipRunDone
+(chip_id BIGINT, exp_id VARCHAR(64), PRIMARY KEY(chip_id)) ENGINE=MEMORY
+
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/flatcorr_find_completed_floatcorruns.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/flatcorr_find_completed_floatcorruns.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/flatcorr_find_completed_floatcorruns.sql	(revision 22157)
@@ -0,0 +1,13 @@
+SELECT DISTINCT
+    flatcorrRun.*
+FROM flatcorrRun
+JOIN flatcorrExp
+    USING(corr_id)
+JOIN chipRunDone
+    USING(chip_id)
+WHERE
+    flatcorrRun.state != "stop"
+GROUP BY
+    corr_id, chip_id
+HAVING
+    COUNT(flatcorrExp.chip_id) = COUNT(chipRunDone.chip_id)
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/flatcorr_find_processedimfiles.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/flatcorr_find_processedimfiles.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/flatcorr_find_processedimfiles.sql	(revision 22157)
@@ -0,0 +1,14 @@
+SELECT
+    corr_id,
+    chipProcessedImfile.*
+FROM flatcorrRun
+JOIN flatcorrExp
+    USING(corr_id)
+JOIN chipRun
+    USING(chip_id)
+JOIN chipProcessedImfile
+    USING(chip_id)
+WHERE
+    flatcorrRun.state = 'run'
+    AND chipRun.state = 'new'
+
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/flatcorr_pending.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/flatcorr_pending.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/flatcorr_pending.sql	(revision 22157)
@@ -0,0 +1,24 @@
+SELECT 
+    *
+FROM
+flatcorrRun
+JOIN flatcorrExp
+    USING(corr_id)
+JOIN chipRun
+    USING(chip_id)
+JOIN rawImfile
+    USING(exp_id)
+LEFT JOIN chipProcessedImfile
+    ON chipRun.chip_id = chipProcessedImfile.chip_id
+    AND rawImfile.exp_id = chipProcessedImfile.exp_id
+    AND rawImfile.class_id = chipProcessedImfile.class_id
+WHERE
+    flatcorrRun.state = 'run'
+    AND chipRun.state = 'full'
+GROUP BY
+    chipRun.chip_id,
+    chipRun.exp_id
+HAVING
+    COUNT(rawImfile.class_id) = COUNT(chipProcessedImfile.class_id)
+    AND SUM(chipProcessedImfile.fault) = 0
+
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_addmask.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_addmask.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_addmask.sql	(revision 22157)
@@ -0,0 +1,6 @@
+UPDATE
+    magicRun
+SET
+    state = 'stop'
+WHERE
+    state != 'stop'
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_create_tmp_warpcomplete.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_create_tmp_warpcomplete.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_create_tmp_warpcomplete.sql	(revision 22157)
@@ -0,0 +1,3 @@
+CREATE TEMPORARY TABLE warpComplete
+ (warp_id BIGINT, skycell_id VARCHAR(64), tess_id VARCHAR(64),
+ PRIMARY KEY(warp_id, skycell_id, tess_id)) ENGINE=MEMORY;
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_find_complete_diffed_exposures.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_find_complete_diffed_exposures.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_find_complete_diffed_exposures.sql	(revision 22157)
@@ -0,0 +1,29 @@
+SELECT
+    warp_id,
+    diff_id,
+    skycell_id,
+    tess_id
+FROM diffSkyfile
+JOIN 
+    (SELECT
+        warp_id,
+        diff_id,
+        skycell_id,
+        tess_id,
+        diffSkyfile.fault
+    FROM warpComplete 
+    LEFT JOIN diffInputSkyfile
+        USING(warp_id, skycell_id, tess_id)
+    LEFT JOIN diffSkyfile
+        USING(diff_id)
+    WHERE 
+        (diffInputSkyfile.template = 1 || diffInputSkyfile.template IS NULL)
+    GROUP BY
+        warpComplete.warp_id
+    HAVING
+        SUM(diffSkyfile.fault) = 0
+        AND COUNT(warpComplete.warp_id) = COUNT(diffInputSkyfile.warp_id)
+        AND COUNT(warpComplete.skycell_id) = COUNT(diffInputSkyfile.skycell_id)
+        AND COUNT(warpComplete.tess_id) = COUNT(diffInputSkyfile.tess_id)
+    ) as Foo
+    USING(diff_id)
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_find_complete_warpruns.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_find_complete_warpruns.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_find_complete_warpruns.sql	(revision 22157)
@@ -0,0 +1,37 @@
+-- find stopped warp runsthat have had all of their skycell's warped without
+-- any faults
+INSERT INTO warpComplete
+SELECT
+    warp_id,
+    skycell_id,
+    tess_id
+FROM warpSkyCellMap
+JOIN (SELECT warp_id, ignored
+    FROM (SELECT 
+            warpSkyCellMap.warp_id,
+            warpSkyCellMap.skycell_id,
+            warpSkyCellMap.tess_id,
+            warpSkyfile.warp_id as w2,
+            warpSkyfile.skycell_id as sc2,
+            warpSkyfile.tess_id as t2,
+            warpSkyfile.ignored,
+            warpSkyfile.fault
+        FROM warpRun
+        JOIN warpSkyCellMap
+            USING(warp_id, tess_id)
+        LEFT JOIN warpSkyfile
+            USING(warp_id, skycell_id, tess_id)
+        WHERE
+            warpRun.state = 'stop'
+        GROUP BY
+            warpRun.warp_id
+        HAVING
+            SUM(warpSkyfile.fault) = 0
+            AND COUNT(warpSkyCellMap.warp_id) = COUNT(warpSkyfile.warp_id)
+            AND COUNT(warpSkyCellMap.skycell_id) = COUNT(warpSkyfile.skycell_id)
+            AND COUNT(warpSkyCellMap.tess_id) = COUNT(warpSkyfile.tess_id)
+        ) as Foo
+    WHERE
+        ignored = 0
+    )  as Bar
+    USING (warp_id)
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_find_unmagiced.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_find_unmagiced.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_find_unmagiced.sql	(revision 22157)
@@ -0,0 +1,47 @@
+-- find warp_id that have had all of their skycell's differenced but have not
+-- yet been magic'd
+SELECT
+    warp_id
+FROM
+    (SELECT
+        warpRun.warp_id,
+        warpSkyfile.fault as f1,
+        diffSkyfile.fault as f2,
+        warpSkyCellMap.skycell_id as wscm_s,
+        warpSkyfile.skycell_id as wsf_s,
+        diffInputSkyfile.skycell_id as s2
+    FROM warpRun
+    -- find all of the skycells that came out of a single exposure
+    JOIN warpSkyCellMap
+        USING(warp_id, tess_id)
+    LEFT JOIN warpSkyfile
+        USING(warp_id, skycell_id, tess_id)
+    -- find out which skycells have been diff'd
+    LEFT JOIN diffInputSkyfile
+        USING(warp_id, skycell_id, tess_id)
+    LEFT JOIN diffRun
+        USING(diff_id)
+    LEFT JOIN diffSkyfile
+        USING(diff_id)
+    -- find out which diff'd skycells have been added to a magic run
+    LEFT JOIN magicInputSkyfile
+        USING(diff_id)
+    WHERE
+        warpRun.state = 'stop'
+        AND (diffRun.state = 'stop' OR diffRun.state IS NULL)
+    --    AND (warpSkyfile.fault = 0 || warpSkyfile.fault IS NULL)
+        AND (diffInputSkyfile.template = 1 || diffInputSkyfile.template IS NULL)
+        AND (diffInputSkyfile.kind = 'warped' || diffInputSkyfile.kind IS NULL)
+    --    AND (diffSkyfile.fault = 0 || diffSkyfile.fault IS NULL)
+        AND magicInputSkyfile.diff_id IS NULL
+        AND warpSkyfile.warp_id IS NOT NULL
+        AND diffInputSkyfile.warp_id IS NOT NULL
+    GROUP BY
+        warpRun.warp_id
+    --    warpRun.warp_id, warpSkyCellMap.skycell_id, warpSkyCellMap.tess_id
+    HAVING
+        COUNT(warpSkyCellMap.skycell_id) = COUNT(warpSkyfile.skycell_id)
+        AND COUNT(warpSkyCellMap.skycell_id) = COUNT(diffInputSkyfile.skycell_id)
+        AND SUM(warpSkyfile.fault) = 0
+        AND SUM(diffSkyfile.fault) = 0 
+) as Foo
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_inputs.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_inputs.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_inputs.sql	(revision 22157)
@@ -0,0 +1,29 @@
+SELECT *
+FROM (
+-- Single skycells
+SELECT
+    magicInputSkyfile.node,
+    magicRun.state,
+    diffSkyfile.uri,
+    diffSkyfile.fault
+FROM magicInputSkyfile
+JOIN diffSkyfile
+    using(diff_id)
+JOIN magicRun
+    USING(magic_id)
+UNION
+-- Merged skycells
+SELECT
+    magicTree.node,
+    magicRun.state,
+    magicNodeResult.uri,
+    magicNodeResult.fault
+FROM magicTree
+JOIN magicRun
+    USING(magic_id)
+JOIN magicNodeResult
+    ON magicTree.magic_id = magicNodeResult.magic_id
+    AND magicTree.dep = magicNodeResult.node
+) as Foo
+WHERE
+    fault = 0
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_inputskyfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_inputskyfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_inputskyfile.sql	(revision 22157)
@@ -0,0 +1,7 @@
+SELECT
+    magicInputSkyfile.*
+FROM magicInputSkyfile
+JOIN magicRun
+    USING(magic_id)
+WHERE
+    magicRun.state = 'run'
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_mask.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_mask.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_mask.sql	(revision 22157)
@@ -0,0 +1,8 @@
+SELECT
+    magicMask.*
+FROM magicMask
+JOIN magicRun
+    USING(magic_id)
+WHERE
+    magicRun.state = 'stop'
+    AND magicMask.fault = 0
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_tomask.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_tomask.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_tomask.sql	(revision 22157)
@@ -0,0 +1,15 @@
+SELECT
+    magic_id,
+    uri
+FROM magicRun
+JOIN magicTree
+    USING(magic_id)
+LEFT JOIN magicNodeResult
+    USING(magic_id, node)
+WHERE
+    magicRun.state = 'run'
+    AND magicNodeResult.node = 'root'
+GROUP BY
+    magic_id
+HAVING
+    COUNT(magicTree.node) = COUNT(magicNodeResult.node)
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_toprocess_inputs.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_toprocess_inputs.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_toprocess_inputs.sql	(revision 22157)
@@ -0,0 +1,19 @@
+SELECT
+    magicTree.*,
+    -- convert magic_id into a boolean value (1 or 0)
+    -- note that the type stays a 64 bit int
+    magicNodeResult.magic_id IS TRUE as done
+FROM magicTree
+JOIN magicRun
+    USING(magic_id)
+JOIN magicInputSkyfile
+    USING(magic_id, node)
+JOIN diffSkyfile
+    using(diff_id)
+LEFT JOIN magicNodeResult
+    ON magicTree.magic_id = magicNodeResult.magic_id
+    AND magicTree.node = magicNodeResult.node
+WHERE
+    magicRun.state = 'run'
+    AND magicNodeResult.magic_id IS NULL
+    AND magicNodeResult.node IS NULL
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_toprocess_tree.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_toprocess_tree.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_toprocess_tree.sql	(revision 22157)
@@ -0,0 +1,13 @@
+SELECT
+    magicTree.*,
+    -- convert magic_id into a boolean value (1 or 0)
+    -- note that the type stays a 64 bit int
+    magicNodeResult.magic_id IS TRUE as done
+FROM magicTree
+JOIN magicRun
+    USING(magic_id)
+LEFT JOIN magicNodeResult
+    ON magicTree.magic_id = magicNodeResult.magic_id
+    AND magicTree.node = magicNodeResult.node
+WHERE
+    magicRun.state = 'run'
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_toskyfilemask.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_toskyfilemask.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_toskyfilemask.sql	(revision 22157)
@@ -0,0 +1,10 @@
+SELECT
+    magicMask.*
+FROM magicRun
+JOIN magicMask
+    USING(magic_id)
+LEFT JOIN magicSkyfileMask
+    USING(magic_id)
+WHERE
+    magicRun.state = 'run'
+    AND magicSkyfileMask.magic_id IS NULL
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_totree.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_totree.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/magictool_totree.sql	(revision 22157)
@@ -0,0 +1,9 @@
+SELECT
+    magic_id
+FROM magicRun
+LEFT JOIN magicTree
+    USING(magic_id)
+WHERE
+    magicRun.state = 'run'
+    AND magicTree.node IS NULL
+    AND magicRun.fault = 0
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/pstamptool_addjob_otherjob.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/pstamptool_addjob_otherjob.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/pstamptool_addjob_otherjob.sql	(revision 22157)
@@ -0,0 +1,3 @@
+INSERT INTO pstampJob
+ (req_id, state, jobType, uri, outputBase, result)
+ VALUES( %s, '%s', '%s', '%s', '%s', %s);
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/pstamptool_addjob_stampjob.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/pstamptool_addjob_stampjob.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/pstamptool_addjob_stampjob.sql	(revision 22157)
@@ -0,0 +1,3 @@
+INSERT INTO pstampJob
+ (req_id, state, jobType, uri, outputBase, result, args)
+ VALUES( %s, '%s', '%s', '%s', '%s', %s, '%s');
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/pstamptool_datastore.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/pstamptool_datastore.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/pstamptool_datastore.sql	(revision 22157)
@@ -0,0 +1,1 @@
+SELECT * FROM pstampDataStore
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/pstamptool_pendingjob.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/pstamptool_pendingjob.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/pstamptool_pendingjob.sql	(revision 22157)
@@ -0,0 +1,4 @@
+SELECT
+ *
+ FROM pstampJob
+ WHERE state = 'run'
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/pstamptool_pendingreq.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/pstamptool_pendingreq.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/pstamptool_pendingreq.sql	(revision 22157)
@@ -0,0 +1,4 @@
+SELECT
+ *
+ FROM pstampRequest
+ WHERE state = 'new'
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/pxadmin_create_tables.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/pxadmin_create_tables.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/pxadmin_create_tables.sql	(revision 22157)
@@ -0,0 +1,786 @@
+CREATE TABLE pzDataStore (
+    camera VARCHAR(64),
+    telescope VARCHAR(64),
+    uri VARCHAR(255),
+    epoch TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY(camera, telescope)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE summitExp (
+    exp_name VARCHAR(64),
+    camera VARCHAR(64),
+    telescope VARCHAR(64),
+    dateobs DATETIME,
+    exp_type VARCHAR(64),
+    uri VARCHAR(255),
+    imfiles INT,
+    fault SMALLINT NOT NULL,
+    epoch TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY(exp_name, camera, telescope),
+    KEY(fault))
+ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE summitImfile (
+    exp_name VARCHAR(64),
+    camera VARCHAR(64),
+    telescope VARCHAR(64),
+    file_id VARCHAR(64),
+    bytes INT,
+    md5sum VARCHAR(32),
+    class VARCHAR(64),
+    class_id VARCHAR(64),
+    uri VARCHAR(255),
+    epoch TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY(exp_name, camera, telescope, class, class_id),
+    KEY(file_id),
+    FOREIGN KEY (exp_name, camera, telescope)
+        REFERENCES  summitExp(exp_name, camera, telescope))
+ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE pzDownloadExp (
+    exp_name VARCHAR(64),
+    camera VARCHAR(64),
+    telescope VARCHAR(64),
+    state VARCHAR(64),
+    epoch TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY(exp_name, camera, telescope),
+    KEY(state),
+    FOREIGN KEY (exp_name, camera, telescope)
+        REFERENCES  summitExp(exp_name, camera, telescope))
+ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE pzDownloadImfile (
+    exp_name VARCHAR(64),
+    camera VARCHAR(64),
+    telescope VARCHAR(64),
+    class VARCHAR(64),
+    class_id VARCHAR(64),
+    uri VARCHAR(255),
+    fault SMALLINT NOT NULL,
+    epoch TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY(exp_name, camera, telescope, class, class_id),
+    KEY(fault),
+    FOREIGN KEY (exp_name, camera, telescope)
+        REFERENCES  pzDownloadExp(exp_name, camera, telescope),
+    FOREIGN KEY (exp_name, camera, telescope, class, class_id)
+        REFERENCES  summitImfile(exp_name, camera, telescope, class, class_id)) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE newExp (
+    exp_id BIGINT AUTO_INCREMENT,
+    tmp_exp_name VARCHAR(64),
+    tmp_camera VARCHAR(64),
+    tmp_telescope VARCHAR(64),
+    state VARCHAR(64),
+    workdir VARCHAR(255),
+    workdir_state VARCHAR(64),
+    reduction VARCHAR(64),
+    dvodb VARCHAR(255),
+    tess_id VARCHAR(64),
+    end_stage VARCHAR(64),
+    label VARCHAR(64),
+    epoch TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY(exp_id),
+    KEY(exp_id),
+    KEY(tmp_exp_name),
+    KEY(tmp_camera),
+    KEY(tmp_telescope),
+    KEY(state),
+    KEY(workdir_state),
+    KEY(end_stage),
+    KEY(label)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE newImfile (
+    exp_id BIGINT,
+    tmp_class_id VARCHAR(64),
+    uri VARCHAR(255),
+    epoch TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY(exp_id, tmp_class_id),
+    FOREIGN KEY (exp_id)
+        REFERENCES  newExp(exp_id))
+ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE rawExp (
+    exp_id BIGINT,
+    exp_name VARCHAR(64),
+    camera VARCHAR(64),
+    telescope VARCHAR(64),
+    dateobs DATETIME,
+    exp_tag VARCHAR(255),
+    exp_type VARCHAR(64),
+    filelevel VARCHAR(64),
+    workdir VARCHAR(255),
+    reduction VARCHAR(64),
+    dvodb VARCHAR(255),
+    tess_id VARCHAR(64),
+    end_stage VARCHAR(64),
+    filter VARCHAR(64),
+    comment VARCHAR(80),
+    airmass FLOAT,
+    ra DOUBLE,
+    decl DOUBLE,
+    exp_time FLOAT,
+    sat_pixel_frac FLOAT,
+    bg DOUBLE,
+    bg_stdev DOUBLE,
+    bg_mean_stdev DOUBLE,
+    alt DOUBLE,
+    az DOUBLE,
+    ccd_temp FLOAT,
+    posang DOUBLE,
+    m1_x FLOAT,
+    m1_y FLOAT,
+    m1_z FLOAT,
+    m1_tip FLOAT,
+    m1_tilt FLOAT,
+    m2_x FLOAT,
+    m2_y FLOAT,
+    m2_z FLOAT,
+    m2_tip FLOAT,
+    m2_tilt FLOAT,
+    env_temperature FLOAT,
+    env_humidity FLOAT,
+    env_wind_speed FLOAT,
+    env_wind_dir FLOAT,
+    teltemp_m1 FLOAT,
+    teltemp_m1cell FLOAT,
+    teltemp_m2 FLOAT,
+    teltemp_spider FLOAT,
+    teltemp_truss FLOAT,
+    teltemp_extra FLOAT,
+    pon_time FLOAT,
+    user_1 DOUBLE,
+    user_2 DOUBLE,
+    user_3 DOUBLE,
+    user_4 DOUBLE,
+    user_5 DOUBLE,
+    object VARCHAR(64),
+    solang FLOAT,
+    hostname VARCHAR(64),
+    fault SMALLINT NOT NULL,
+    epoch TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY(exp_id),
+    KEY(exp_name),
+    KEY(end_stage),
+    KEY(fault),
+    FOREIGN KEY (exp_id)
+        REFERENCES  newExp(exp_id))
+ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE rawImfile (
+    exp_id BIGINT,
+    exp_name VARCHAR(64),
+    camera VARCHAR(64),
+    telescope VARCHAR(64),
+    dateobs DATETIME,
+    tmp_class_id VARCHAR(64),
+    class_id VARCHAR(64),
+    uri VARCHAR(255),
+    exp_type VARCHAR(64),
+    filelevel VARCHAR(64),
+    filter VARCHAR(64),
+    comment VARCHAR(80),
+    airmass FLOAT,
+    ra DOUBLE,
+    decl DOUBLE,
+    exp_time FLOAT,
+    sat_pixel_frac FLOAT,
+    bg DOUBLE,
+    bg_stdev DOUBLE,
+    bg_mean_stdev DOUBLE,
+    alt DOUBLE,
+    az DOUBLE,
+    ccd_temp FLOAT,
+    posang DOUBLE,
+    m1_x FLOAT,
+    m1_y FLOAT,
+    m1_z FLOAT,
+    m1_tip FLOAT,
+    m1_tilt FLOAT,
+    m2_x FLOAT,
+    m2_y FLOAT,
+    m2_z FLOAT,
+    m2_tip FLOAT,
+    m2_tilt FLOAT,
+    env_temperature FLOAT,
+    env_humidity FLOAT,
+    env_wind_speed FLOAT,
+    env_wind_dir FLOAT,
+    teltemp_m1 FLOAT,
+    teltemp_m1cell FLOAT,
+    teltemp_m2 FLOAT,
+    teltemp_spider FLOAT,
+    teltemp_truss FLOAT,
+    teltemp_extra FLOAT,
+    pon_time FLOAT,
+    user_1 DOUBLE,
+    user_2 DOUBLE,
+    user_3 DOUBLE,
+    user_4 DOUBLE,
+    user_5 DOUBLE,
+    object VARCHAR(64),
+    hostname VARCHAR(64),
+    fault SMALLINT NOT NULL,
+    epoch TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY(exp_id, class_id),
+    KEY(tmp_class_id),
+    KEY(fault),
+    UNIQUE KEY(exp_id, tmp_class_id),
+    FOREIGN KEY (exp_id, tmp_class_id)
+        REFERENCES newImfile(exp_id, tmp_class_id))
+ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE guidePendingExp (guide_id BIGINT AUTO_INCREMENT, exp_id BIGINT, recipe VARCHAR(64), PRIMARY KEY(guide_id), KEY(guide_id), KEY(exp_id)) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE chipRun (
+    chip_id BIGINT AUTO_INCREMENT,
+    exp_id BIGINT, 
+    state VARCHAR(64), 
+    workdir VARCHAR(255), 
+    workdir_state VARCHAR(64), 
+    label VARCHAR(64), 
+    reduction VARCHAR(64),
+     expgroup VARCHAR(64), 
+    dvodb VARCHAR(255), 
+    tess_id VARCHAR(64), 
+    end_stage VARCHAR(64), 
+    PRIMARY KEY(chip_id), 
+    KEY(chip_id), KEY(exp_id), 
+    KEY(state), 
+    KEY(workdir_state),
+    KEY(label), 
+    KEY(expgroup), 
+    KEY(end_stage), 
+    INDEX(chip_id, exp_id), 
+    FOREIGN KEY (exp_id)  
+        REFERENCES rawExp(exp_id))
+ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE chipProcessedImfile (
+    chip_id BIGINT, 
+    exp_id BIGINT, 
+    class_id VARCHAR(64), 
+    uri VARCHAR(255), 
+    bg FLOAT, 
+    bg_stdev FLOAT, 
+    bg_mean_stdev FLOAT, 
+    bias FLOAT, 
+    bias_stdev FLOAT, 
+    fringe_0 FLOAT, 
+    fringe_1 FLOAT, 
+    fringe_2 FLOAT, 
+    sigma_ra FLOAT, 
+    sigma_dec FLOAT, 
+    ap_resid FLOAT, 
+    ap_resid_stdev FLOAT, 
+    zp_mean FLOAT, 
+    zp_stdev FLOAT, 
+    fwhm_major FLOAT, 
+    fwhm_minor FLOAT, 
+    dtime_detrend FLOAT, 
+    dtime_photom FLOAT, 
+    dtime_astrom FLOAT, 
+    hostname VARCHAR(64), 
+    n_stars INT, 
+    n_extended INT, 
+    n_cr INT, 
+    n_astrom INT, 
+    path_base VARCHAR(255), 
+    fault SMALLINT NOT NULL, 
+    PRIMARY KEY(chip_id, exp_id, class_id), 
+    KEY(fault), 
+    FOREIGN KEY (chip_id, exp_id)  
+        REFERENCES  chipRun(chip_id, exp_id),
+    FOREIGN KEY (exp_id, class_id)
+        REFERENCES  rawImfile(exp_id, class_id))
+ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE chipMask (
+    label VARCHAR(64), 
+    PRIMARY KEY(label)) 
+ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE camRun (
+    cam_id BIGINT AUTO_INCREMENT, 
+    chip_id BIGINT, 
+    state VARCHAR(64), 
+    workdir VARCHAR(255), 
+    workdir_state VARCHAR(64), 
+    label VARCHAR(64), 
+    reduction VARCHAR(64), 
+    expgroup VARCHAR(64), 
+    dvodb VARCHAR(255), 
+    tess_id VARCHAR(64), 
+    end_stage VARCHAR(64), 
+    PRIMARY KEY(cam_id), 
+    KEY(cam_id), 
+    KEY(chip_id), 
+    KEY(state), 
+    KEY(workdir_state), 
+    KEY(label), 
+    KEY(expgroup), 
+    KEY(end_stage), 
+    INDEX(cam_id, chip_id), 
+    FOREIGN KEY (chip_id)  
+        REFERENCES chipRun(chip_id)) 
+ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE camProcessedExp (
+    cam_id BIGINT,
+    uri VARCHAR(255), 
+    bg FLOAT, 
+    bg_stdev FLOAT, 
+    bg_mean_stdev FLOAT, 
+    bias FLOAT, 
+    bias_stdev FLOAT, 
+    fringe_0 FLOAT, 
+    fringe_1 FLOAT, 
+    fringe_2 FLOAT, 
+    sigma_ra FLOAT, 
+    sigma_dec FLOAT, 
+    ap_resid FLOAT, 
+    ap_resid_stdev FLOAT, 
+    zp_mean FLOAT, 
+    zp_stdev FLOAT, 
+    fwhm_major FLOAT, 
+    fwhm_minor FLOAT, 
+    dtime_detrend FLOAT, 
+    dtime_photom FLOAT, 
+    dtime_astrom FLOAT, 
+    hostname VARCHAR(64),
+    n_stars INT, 
+    n_extended INT, 
+    n_cr INT, 
+    n_astrom INT, 
+    path_base VARCHAR(255), 
+    fault SMALLINT NOT NULL, 
+    PRIMARY KEY(cam_id), 
+    KEY(fault), 
+    FOREIGN KEY (cam_id)  
+        REFERENCES camRun(cam_id)) 
+ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE camMask (
+    label VARCHAR(64),
+    PRIMARY KEY(label))
+ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE fakeRun (
+    fake_id BIGINT AUTO_INCREMENT, 
+    cam_id BIGINT, 
+    state VARCHAR(64), 
+    workdir VARCHAR(255), 
+    label VARCHAR(64), 
+    reduction VARCHAR(64), 
+    expgroup VARCHAR(64), 
+    dvodb VARCHAR(255), 
+    tess_id VARCHAR(64), 
+    end_stage VARCHAR(64), 
+    epoch TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY(fake_id), 
+    KEY(cam_id), 
+    KEY(state), 
+    KEY(label), 
+    KEY(expgroup), 
+    KEY(end_stage), 
+    INDEX(fake_id, cam_id), 
+    FOREIGN KEY (cam_id)  
+        REFERENCES camRun(cam_id)) 
+ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE fakeProcessedImfile (
+    fake_id BIGINT AUTO_INCREMENT, 
+    exp_id BIGINT, 
+    class_id VARCHAR(64), 
+    uri VARCHAR(255), 
+    dtime_fake FLOAT, 
+    hostname VARCHAR(64),
+    path_base VARCHAR(255), 
+    fault SMALLINT NOT NULL, 
+    epoch TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY(fake_id, exp_id, class_id), 
+    KEY(fault),
+    FOREIGN KEY (fake_id)  
+        REFERENCES fakeRun(fake_id),
+    FOREIGN KEY (exp_id, class_id)  
+        REFERENCES rawImfile(exp_id, class_id)) 
+ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE fakeMask (
+    label VARCHAR(64), 
+    PRIMARY KEY(label)) 
+ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE detRun (
+    det_id BIGINT AUTO_INCREMENT,
+    iteration INT,
+    det_type VARCHAR(64),
+    mode VARCHAR(64),
+    state VARCHAR(64),
+    filelevel VARCHAR(64),
+    workdir VARCHAR(255), 
+    camera VARCHAR(64), 
+    telescope VARCHAR(64), 
+    exp_type VARCHAR(64), 
+    reduction VARCHAR(64), 
+    filter VARCHAR(64), 
+    airmass_min FLOAT, 
+    airmass_max FLOAT, 
+    exp_time_min FLOAT, 
+    exp_time_max FLOAT, 
+    ccd_temp_min FLOAT, 
+    ccd_temp_max FLOAT, 
+    posang_min DOUBLE, 
+    posang_max DOUBLE, 
+    registered DATETIME, 
+    time_begin DATETIME, 
+    time_end DATETIME, 
+    use_begin DATETIME, 
+    use_end DATETIME, 
+    solang_min FLOAT, 
+    solang_max FLOAT, 
+    label VARCHAR(64), 
+    parent INT, 
+    PRIMARY KEY(det_id), 
+    KEY(det_id), 
+    KEY(iteration), 
+    KEY(det_type), 
+    KEY(mode), 
+    KEY(state), 
+    KEY(label), 
+    KEY(parent), 
+    INDEX(det_id, iteration)) 
+ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE detInputExp (
+det_id BIGINT, 
+iteration INT, 
+exp_id BIGINT, 
+include TINYINT, 
+PRIMARY KEY(det_id, iteration, exp_id), 
+INDEX(det_id, exp_id), 
+INDEX(det_id, iteration), 
+FOREIGN KEY (det_id)  
+REFERENCES  detRun(det_id), 
+FOREIGN KEY (exp_id)  
+REFERENCES  rawExp(exp_id)) 
+ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE detProcessedImfile (
+    det_id BIGINT,
+    exp_id BIGINT, 
+    class_id VARCHAR(64), 
+    uri VARCHAR(255), 
+    recipe VARCHAR(64), 
+    bg DOUBLE, 
+    bg_stdev DOUBLE, 
+    bg_mean_stdev DOUBLE, 
+    fringe_0 DOUBLE, 
+    fringe_1 DOUBLE, 
+    fringe_2 DOUBLE, 
+    user_1 DOUBLE, 
+    user_2 DOUBLE, 
+    user_3 DOUBLE, 
+    user_4 DOUBLE, 
+    user_5 DOUBLE, 
+    path_base VARCHAR(255), 
+    fault SMALLINT NOT NULL, 
+    PRIMARY KEY(det_id, exp_id, class_id), 
+    KEY(fault), 
+    INDEX(det_id, class_id), 
+    INDEX(det_id, exp_id), 
+    FOREIGN KEY (det_id, exp_id)  
+        REFERENCES  detInputExp(det_id, exp_id), 
+    FOREIGN KEY (exp_id, class_id)  
+        REFERENCES  rawImfile(exp_id, class_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE detProcessedExp (
+    det_id BIGINT, 
+    exp_id BIGINT, 
+    recipe VARCHAR(64), 
+    bg DOUBLE, 
+    bg_stdev DOUBLE, 
+    bg_mean_stdev DOUBLE, 
+    fringe_0 DOUBLE, 
+    fringe_1 DOUBLE, 
+    fringe_2 DOUBLE, 
+    user_1 DOUBLE, 
+    user_2 DOUBLE, 
+    user_3 DOUBLE, 
+    user_4 DOUBLE, 
+    user_5 DOUBLE, 
+    path_base VARCHAR(255), 
+    fault SMALLINT NOT NULL, 
+    PRIMARY KEY(det_id, exp_id), 
+    KEY(fault), 
+    FOREIGN KEY (det_id, exp_id)  
+        REFERENCES  detInputExp(det_id, exp_id), 
+    FOREIGN KEY (det_id, exp_id)  
+        REFERENCES  detProcessedImfile(det_id, exp_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE detStackedImfile (
+    det_id BIGINT,
+    iteration INT,
+    class_id VARCHAR(64),
+    uri VARCHAR(255),
+    recipe VARCHAR(64),
+    bg DOUBLE,
+    bg_stdev DOUBLE, 
+    bg_mean_stdev DOUBLE, 
+    user_1 DOUBLE, 
+    user_2 DOUBLE, 
+    user_3 DOUBLE, 
+    user_4 DOUBLE, 
+    user_5 DOUBLE, 
+    fault SMALLINT NOT NULL, 
+    PRIMARY KEY(det_id, iteration, class_id), 
+    KEY(fault), 
+    FOREIGN KEY (det_id, iteration)  
+        REFERENCES  detInputExp(det_id, iteration), 
+    FOREIGN KEY (det_id, class_id)  
+        REFERENCES  detProcessedImfile(det_id, class_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE detNormalizedStatImfile (
+    det_id BIGINT, 
+    iteration INT, 
+    class_id VARCHAR(64), 
+    norm FLOAT, 
+    fault SMALLINT NOT NULL, 
+    PRIMARY KEY(det_id, iteration, class_id), 
+    KEY(fault), 
+    FOREIGN KEY (det_id, iteration)  
+    REFERENCES  detInputExp(det_id, iteration), 
+    FOREIGN KEY (det_id, iteration, class_id)  
+    REFERENCES  detStackedImfile(det_id, iteration, class_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE detNormalizedImfile (
+    det_id BIGINT, 
+    iteration INT, 
+    class_id VARCHAR(64), 
+    uri VARCHAR(255), 
+    bg DOUBLE, 
+    bg_stdev DOUBLE, 
+    bg_mean_stdev DOUBLE, 
+    user_1 DOUBLE, 
+    user_2 DOUBLE, 
+    user_3 DOUBLE, 
+    user_4 DOUBLE, 
+    user_5 DOUBLE, 
+    path_base VARCHAR(255), 
+    fault SMALLINT NOT NULL, 
+    PRIMARY KEY(det_id, iteration, class_id), 
+    KEY(fault), 
+    INDEX(det_id, iteration), 
+    FOREIGN KEY (det_id)  
+    REFERENCES  detInputExp(det_id), 
+    FOREIGN KEY (det_id, iteration, class_id)  
+    REFERENCES  detNormalizedStatImfile(det_id, iteration, class_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE detNormalizedExp (
+    det_id BIGINT, 
+    iteration INT, 
+    recipe VARCHAR(64), 
+    bg DOUBLE, 
+    bg_stdev DOUBLE, 
+    bg_mean_stdev DOUBLE, 
+    user_1 DOUBLE, 
+    user_2 DOUBLE, 
+    user_3 DOUBLE, 
+    user_4 DOUBLE, 
+    user_5 DOUBLE, 
+    path_base VARCHAR(255), 
+    fault SMALLINT NOT NULL, 
+    PRIMARY KEY(det_id, iteration), 
+    KEY(fault), 
+    FOREIGN KEY (det_id, iteration)  
+    REFERENCES  detInputExp(det_id, iteration), 
+    FOREIGN KEY (det_id, iteration)  
+    REFERENCES  detNormalizedImfile(det_id, iteration)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE detResidImfile (
+    det_id BIGINT, 
+    iteration INT, 
+    exp_id BIGINT, 
+    class_id VARCHAR(64), 
+    uri VARCHAR(255), 
+    recipe VARCHAR(64), 
+    bg DOUBLE, 
+    bg_stdev DOUBLE, 
+    bg_mean_stdev DOUBLE, 
+    bg_skewness DOUBLE, 
+    bg_kurtosis DOUBLE, 
+    bin_stdev DOUBLE, 
+    fringe_0 DOUBLE, 
+    fringe_1 DOUBLE, 
+    fringe_2 DOUBLE, 
+    fringe_resid_0 DOUBLE, 
+    fringe_resid_1 DOUBLE, 
+    fringe_resid_2 DOUBLE, 
+    user_1 DOUBLE,
+    user_2 DOUBLE, 
+    user_3 DOUBLE, 
+    user_4 DOUBLE, 
+    user_5 DOUBLE, 
+    path_base VARCHAR(255), 
+    fault SMALLINT NOT NULL, 
+    PRIMARY KEY(det_id, iteration, exp_id, class_id), 
+    KEY(fault), 
+    INDEX(det_id, iteration, exp_id), 
+    FOREIGN KEY (det_id, iteration, exp_id)  
+    REFERENCES  detInputExp(det_id, iteration, exp_id), 
+    FOREIGN KEY (det_id, exp_id, class_id)  
+    REFERENCES  detProcessedImfile(det_id, exp_id, class_id), 
+    FOREIGN KEY (det_id, iteration)  
+    REFERENCES  detNormalizedExp(det_id, iteration)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE detResidExp (det_id BIGINT, iteration INT, exp_id BIGINT, recipe VARCHAR(64), bg DOUBLE, bg_stdev DOUBLE, bg_mean_stdev DOUBLE, bg_skewness DOUBLE, bg_kurtosis DOUBLE, bin_stdev DOUBLE, fringe_0 DOUBLE, fringe_1 DOUBLE, fringe_2 DOUBLE, fringe_resid_0 DOUBLE, fringe_resid_1 DOUBLE, fringe_resid_2 DOUBLE, user_1 DOUBLE, user_2 DOUBLE, user_3 DOUBLE, user_4 DOUBLE, user_5 DOUBLE, path_base VARCHAR(255), accept TINYINT, fault SMALLINT NOT NULL, PRIMARY KEY(det_id, iteration, exp_id), KEY(fault), INDEX(det_id, iteration), FOREIGN KEY (det_id, iteration, exp_id)  REFERENCES  detInputExp(det_id, iteration, exp_id), FOREIGN KEY (det_id, iteration, exp_id)  REFERENCES  detResidImfile(det_id, iteration, exp_id)) ENGINE=innodb DEFAULT CHARSET=latin1;
+CREATE TABLE detRunSummary (det_id BIGINT, iteration INT, bg DOUBLE, bg_stdev DOUBLE, bg_mean_stdev DOUBLE, accept TINYINT, fault SMALLINT NOT NULL, PRIMARY KEY(det_id, iteration), KEY(fault), FOREIGN KEY (det_id, iteration)  REFERENCES  detInputExp(det_id, iteration), FOREIGN KEY (det_id, iteration)  REFERENCES  detResidExp(det_id, iteration)) ENGINE=innodb DEFAULT CHARSET=latin1;
+CREATE TABLE detRegisteredImfile (det_id BIGINT, iteration INT, class_id VARCHAR(64), uri VARCHAR(255), bg DOUBLE, bg_stdev DOUBLE, bg_mean_stdev DOUBLE, user_1 DOUBLE, user_2 DOUBLE, user_3 DOUBLE, user_4 DOUBLE, user_5 DOUBLE, path_base VARCHAR(255), fault SMALLINT NOT NULL, PRIMARY KEY(det_id, iteration, class_id), KEY(fault), FOREIGN KEY (det_id, iteration)  REFERENCES  detRun(det_id, iteration)) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE warpRun (
+    warp_id BIGINT AUTO_INCREMENT, 
+    fake_id BIGINT, 
+    mode VARCHAR(64), 
+    state VARCHAR(64), 
+    workdir VARCHAR(255), 
+    workdir_state VARCHAR(64), 
+    label VARCHAR(64), 
+    dvodb VARCHAR(255), 
+    tess_id VARCHAR(64), 
+    end_stage VARCHAR(64), 
+    registered DATETIME, 
+    magiced TINYINT, 
+    PRIMARY KEY(warp_id), 
+    KEY(warp_id), 
+    KEY(fake_id), 
+    KEY(mode), 
+    KEY(state), 
+    KEY(workdir_state), 
+    KEY(label), 
+    KEY(end_stage), 
+    KEY(magiced), 
+    INDEX(warp_id, fake_id), 
+    FOREIGN KEY (fake_id)  
+        REFERENCES fakeRun(fake_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE warpSkyCellMap (
+    warp_id BIGINT,
+    skycell_id VARCHAR(64),
+    tess_id VARCHAR(64),
+    class_id VARCHAR(64),
+    fault SMALLINT,
+    PRIMARY KEY(warp_id, skycell_id, tess_id, class_id),
+    KEY(fault),
+    FOREIGN KEY (warp_id)
+        REFERENCES warpRun(warp_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE warpSkyfile (
+    warp_id BIGINT, 
+    skycell_id VARCHAR(64), 
+    tess_id VARCHAR(64), 
+    uri VARCHAR(255), 
+    path_base VARCHAR(255), 
+    bg DOUBLE, 
+    bg_stdev DOUBLE, 
+    dtime_warp FLOAT, 
+    hostname VARCHAR(64), 
+    good_frac FLOAT, 
+    xmin INT, 
+    xmax INT, 
+    ymin INT, 
+    ymax INT, 
+    ignored TINYINT, 
+    fault SMALLINT, 
+    PRIMARY KEY(warp_id, skycell_id, tess_id), 
+    KEY(good_frac), 
+    KEY(ignored), KEY(fault), 
+    FOREIGN KEY (warp_id, skycell_id, tess_id)  
+        REFERENCES warpSkyCellMap(warp_id, skycell_id, tess_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE warpMask (
+    label VARCHAR(64),
+    PRIMARY KEY(label)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE stackRun (stack_id BIGINT AUTO_INCREMENT, state VARCHAR(64), workdir VARCHAR(255), dvodb VARCHAR(255), registered DATETIME, skycell_id VARCHAR(64), tess_id VARCHAR(64), PRIMARY KEY(stack_id), KEY(stack_id), KEY(state), KEY(skycell_id), KEY(tess_id)) ENGINE=innodb DEFAULT CHARSET=latin1;
+CREATE TABLE stackInputSkyfile (stack_id BIGINT, warp_id BIGINT, PRIMARY KEY(stack_id, warp_id), FOREIGN KEY (stack_id)  REFERENCES  stackRun(stack_id), FOREIGN KEY (warp_id)  REFERENCES  warpSkyfile(warp_id)) ENGINE=innodb DEFAULT CHARSET=latin1;
+CREATE TABLE stackSumSkyfile (stack_id BIGINT, uri VARCHAR(255), path_base VARCHAR(255), bg DOUBLE, bg_stdev DOUBLE, dtime_stack FLOAT, hostname VARCHAR(64), good_frac FLOAT, fault SMALLINT, PRIMARY KEY(stack_id), KEY(dtime_stack), KEY(good_frac), KEY(fault), FOREIGN KEY (stack_id)  REFERENCES  stackRun(stack_id)) ENGINE=innodb DEFAULT CHARSET=latin1;
+CREATE TABLE diffRun (diff_id BIGINT AUTO_INCREMENT, state VARCHAR(64), workdir VARCHAR(255), dvodb VARCHAR(255), registered DATETIME, skycell_id VARCHAR(64), tess_id VARCHAR(64), PRIMARY KEY(diff_id), KEY(diff_id), KEY(state), KEY(skycell_id), KEY(tess_id)) ENGINE=innodb DEFAULT CHARSET=latin1;
+CREATE TABLE diffInputSkyfile (diff_id BIGINT, template TINYINT, stack_id BIGINT, warp_id BIGINT, skycell_id VARCHAR(64), tess_id VARCHAR(64), kind VARCHAR(64), PRIMARY KEY(diff_id, template), KEY(stack_id), KEY(warp_id), KEY(skycell_id), KEY(tess_id), KEY(kind), FOREIGN KEY (diff_id)  REFERENCES  diffRun(diff_id), FOREIGN KEY (stack_id)  REFERENCES  stackSumSkyfile(stack_id), FOREIGN KEY (warp_id, skycell_id, tess_id)  REFERENCES  warpSkyfile(warp_id, skycell_id, tess_id)) ENGINE=innodb DEFAULT CHARSET=latin1;
+CREATE TABLE diffSkyfile (diff_id BIGINT, uri VARCHAR(255), path_base VARCHAR(255), bg DOUBLE, bg_stdev DOUBLE, stamps_num INT, stamps_rms FLOAT, sources INT, dtime_diff FLOAT, hostname VARCHAR(64), good_frac FLOAT, fault SMALLINT, PRIMARY KEY(diff_id), KEY(good_frac), KEY(fault), FOREIGN KEY (diff_id)  REFERENCES  diffRun(diff_id)) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE magicRun (
+	magic_id BIGINT AUTO_INCREMENT,
+	state VARCHAR(64),
+	workdir VARCHAR(255),
+	workdir_state VARCHAR(255),
+	label VARCHAR(64),
+	dvodb VARCHAR(255),
+	registered DATETIME,
+	fault SMALLINT,
+	PRIMARY KEY(magic_id),
+	KEY(magic_id),
+	KEY(state),
+	KEY(workdir_state),
+	KEY(label),
+	KEY(fault)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE magicInputSkyfile (
+	magic_id BIGINT,
+	diff_id BIGINT,
+	node VARCHAR(64),
+	PRIMARY KEY(magic_id, diff_id),
+	FOREIGN KEY (magic_id)  REFERENCES  magicRun(magic_id),
+	FOREIGN KEY (diff_id)  REFERENCES  diffRun(diff_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE magicTree (
+	magic_id BIGINT,
+	node VARCHAR(64),
+	dep VARCHAR(64),
+	KEY(magic_id),
+	KEY(node),
+	KEY(dep),
+	INDEX(magic_id, node),
+	FOREIGN KEY (magic_id)  REFERENCES  magicRun(magic_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE magicNodeResult (
+	magic_id BIGINT,
+	node VARCHAR(64),
+	uri VARCHAR(255),
+	fault SMALLINT,
+	PRIMARY KEY(magic_id, node),
+	FOREIGN KEY (magic_id)  REFERENCES  magicRun(magic_id),
+	FOREIGN KEY (magic_id, node)  REFERENCES  magicTree(magic_id, node),
+	KEY(fault)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE magicMask (
+	magic_id BIGINT,
+	uri VARCHAR(255),
+	streaks INT,
+	fault SMALLINT,
+	PRIMARY KEY(magic_id),
+	FOREIGN KEY (magic_id)  REFERENCES  magicRun(magic_id),
+	KEY(fault)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE calDB (cal_id BIGINT AUTO_INCREMENT, dvodb VARCHAR(64), state VARCHAR(64), PRIMARY KEY(cal_id), KEY(cal_id)) ENGINE=innodb DEFAULT CHARSET=latin1;
+CREATE TABLE calRun (cal_id BIGINT AUTO_INCREMENT, region VARCHAR(64), last_step VARCHAR(64), state VARCHAR(64), PRIMARY KEY(cal_id), KEY(cal_id), KEY(last_step), FOREIGN KEY (cal_id)  REFERENCES  calDB(cal_id)) ENGINE=innodb DEFAULT CHARSET=latin1;
+CREATE TABLE flatcorrRun (corr_id BIGINT AUTO_INCREMENT, dvodb VARCHAR(64), filter VARCHAR(64), state VARCHAR(64), workdir VARCHAR(255), label VARCHAR(64), stats VARCHAR(255), region VARCHAR(255), PRIMARY KEY(corr_id), KEY(corr_id)) ENGINE=innodb DEFAULT CHARSET=latin1;
+CREATE TABLE flatcorrExp (corr_id BIGINT, chip_id BIGINT, PRIMARY KEY(corr_id, chip_id), FOREIGN KEY (corr_id)  REFERENCES  flatcorrRun(corr_id), FOREIGN KEY (chip_id)  REFERENCES  chipRun(chip_id)) ENGINE=innodb DEFAULT CHARSET=latin1;
+CREATE TABLE pstampDataStore (ds_id BIGINT AUTO_INCREMENT, state VARCHAR(64), lastFileset VARCHAR(64), outProduct VARCHAR(64) UNIQUE, uri VARCHAR(255), PRIMARY KEY(ds_id), KEY(ds_id)) ENGINE=innodb DEFAULT CHARSET=latin1;
+CREATE TABLE pstampRequest (req_id BIGINT AUTO_INCREMENT, ds_id BIGINT, state VARCHAR(64), outFileset VARCHAR(64) UNIQUE, uri VARCHAR(255), resultsFile SMALLINT, PRIMARY KEY(req_id), KEY(req_id)) ENGINE=innodb DEFAULT CHARSET=latin1;
+CREATE TABLE pstampJob (job_id BIGINT AUTO_INCREMENT, req_id BIGINT, state VARCHAR(64), jobType VARCHAR(16), result INT, uri VARCHAR(255), outputBase VARCHAR(255), args VARCHAR(511), PRIMARY KEY(job_id, req_id), KEY(job_id), FOREIGN KEY (req_id)  REFERENCES  pstampRequest(req_id)) ENGINE=innodb DEFAULT CHARSET=latin1
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/pxadmin_drop_tables.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/pxadmin_drop_tables.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/pxadmin_drop_tables.sql	(revision 22157)
@@ -0,0 +1,56 @@
+SET FOREIGN_KEY_CHECKS=0;
+DROP TABLE IF EXISTS pzDataStore;
+DROP TABLE IF EXISTS summitExp;
+DROP TABLE IF EXISTS summitImfile;
+DROP TABLE IF EXISTS pzDownloadExp;
+DROP TABLE IF EXISTS pzDownloadImfile;
+DROP TABLE IF EXISTS newExp;
+DROP TABLE IF EXISTS newImfile;
+DROP TABLE IF EXISTS rawExp;
+DROP TABLE IF EXISTS rawImfile;
+DROP TABLE IF EXISTS guidePendingExp;
+DROP TABLE IF EXISTS chipRun;
+DROP TABLE IF EXISTS chipProcessedImfile;
+DROP TABLE IF EXISTS chipMask;
+DROP TABLE IF EXISTS camRun;
+DROP TABLE IF EXISTS camProcessedExp;
+DROP TABLE IF EXISTS camMask;
+DROP TABLE IF EXISTS detRun;
+DROP TABLE IF EXISTS detInputExp;
+DROP TABLE IF EXISTS fakeRun;
+DROP TABLE IF EXISTS fakeProcessedImfile;
+DROP TABLE IF EXISTS fakeMask;
+DROP TABLE IF EXISTS detProcessedImfile;
+DROP TABLE IF EXISTS detProcessedExp;
+DROP TABLE IF EXISTS detStackedImfile;
+DROP TABLE IF EXISTS detNormalizedStatImfile;
+DROP TABLE IF EXISTS detNormalizedImfile;
+DROP TABLE IF EXISTS detNormalizedExp;
+DROP TABLE IF EXISTS detResidImfile;
+DROP TABLE IF EXISTS detResidExp;
+DROP TABLE IF EXISTS detRunSummary;
+DROP TABLE IF EXISTS detRegisteredImfile;
+DROP TABLE IF EXISTS warpRun;
+DROP TABLE IF EXISTS warpSkyCellMap;
+DROP TABLE IF EXISTS warpSkyfile;
+DROP TABLE IF EXISTS warpMask;
+DROP TABLE IF EXISTS diffRun;
+DROP TABLE IF EXISTS diffInputSkyfile;
+DROP TABLE IF EXISTS diffSkyfile;
+DROP TABLE IF EXISTS stackRun;
+DROP TABLE IF EXISTS stackInputSkyfile;
+DROP TABLE IF EXISTS stackSumSkyfile;
+DROP TABLE IF EXISTS magicRun;
+DROP TABLE IF EXISTS magicInputSkyfile;
+DROP TABLE IF EXISTS magicTree;
+DROP TABLE IF EXISTS magicNodeResult;
+DROP TABLE IF EXISTS magicMask;
+DROP TABLE IF EXISTS magicSkyfileMask;
+DROP TABLE IF EXISTS calDB;
+DROP TABLE IF EXISTS calRun;
+DROP TABLE IF EXISTS flatcorrRun;
+DROP TABLE IF EXISTS flatcorrExp;
+DROP TABLE IF EXISTS pstampDataStore;
+DROP TABLE IF EXISTS pstampRequest;
+DROP TABLE IF EXISTS pstampJob;
+SET FOREIGN_KEY_CHECKS=1
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/pztool_find_completed_exp.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/pztool_find_completed_exp.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/pztool_find_completed_exp.sql	(revision 22157)
@@ -0,0 +1,35 @@
+SELECT DISTINCT
+    exp_name, -- return should match pzDownloadExp
+    camera,
+    telescope,
+    state,
+    NULL as epoch    -- epoch
+FROM (
+    SELECT
+        pzDownloadImfile.*,
+        pzDownloadExp.state,
+        summitExp.imfiles
+    FROM pzDownloadExp
+    JOIN summitExp
+        USING(exp_name, camera, telescope)
+    LEFT JOIN pzDownloadImfile
+        USING(exp_name, camera, telescope)
+    LEFT JOIN newExp
+        ON pzDownloadExp.exp_name = newExp.tmp_exp_name
+        AND pzDownloadExp.camera = newExp.tmp_camera
+        AND pzDownloadExp.telescope = newExp.tmp_telescope
+    WHERE
+        pzDownloadExp.state = 'run'
+        AND newExp.tmp_exp_name IS NULL
+        AND newExp.tmp_camera IS NULL
+        AND newExp.tmp_telescope IS NULL
+    GROUP BY
+        pzDownloadExp.exp_name,
+        pzDownloadExp.camera,
+        pzDownloadExp.telescope
+    -- it doesn't matter which field in pzDownloadImfile we count as we've
+    -- already Download a group by
+    HAVING
+        COUNT(pzDownloadImfile.exp_name) = summitExp.imfiles
+        AND SUM(pzDownloadImfile.fault) = 0
+    ) as Foo
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/pztool_pendingimfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/pztool_pendingimfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/pztool_pendingimfile.sql	(revision 22157)
@@ -0,0 +1,20 @@
+SELECT *
+FROM (
+    SELECT
+        summitImfile.*,
+        summitExp.dateobs
+    FROM summitImfile
+    JOIN pzDownloadExp
+        USING(exp_name, camera, telescope)
+    LEFT JOIN pzDownloadImfile
+        USING(exp_name, camera, telescope, class, class_id)
+    JOIN summitExp
+        USING(exp_name, camera, telescope)
+    WHERE 
+        pzDownloadExp.state = 'run'
+        AND pzDownloadImfile.exp_name IS NULL
+        AND pzDownloadImfile.camera IS NULL
+        AND pzDownloadImfile.telescope IS NULL
+        AND pzDownloadImfile.class IS NULL
+        AND pzDownloadImfile.class_id IS NULL
+) as Foo
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/pztool_revert_downloadimfile_faults.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/pztool_revert_downloadimfile_faults.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/pztool_revert_downloadimfile_faults.sql	(revision 22157)
@@ -0,0 +1,17 @@
+DELETE FROM pzDownloadImfile
+WHERE
+    fault != 0
+    -- fault was logged in the last 3 days
+    AND ABS(TIMESTAMPDIFF(SECOND, epoch, NOW())) < 86400 * 3
+    AND (
+        -- HTTP 503: try again
+        fault = 203
+        -- HTTP 500: timeout
+        OR fault = 200
+        -- HTTP 404: unknown datastore internal problem
+        OR fault = 104
+        -- ipptool errors
+        OR fault < 100
+        -- perl untrapped die()
+        OR fault = 255
+    )
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/pztool_revert_fileset_faults.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/pztool_revert_fileset_faults.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/pztool_revert_fileset_faults.sql	(revision 22157)
@@ -0,0 +1,17 @@
+UPDATE summitExp SET fault = 0
+WHERE
+    fault != 0
+    -- fault is older than 10 minutes
+    AND ABS(TIMESTAMPDIFF(SECOND, epoch, NOW())) > 60 * 10 
+    -- fault was logged in the last 3 days
+    AND ABS(TIMESTAMPDIFF(SECOND, epoch, NOW())) < 86400 * 3
+    AND (
+        -- HTTP 500: timeout
+        fault = 200
+        -- HTTP 404: unknown datastore internal problem
+        OR fault = 104
+        -- ipptool errors
+        OR fault < 100
+        -- perl untrapped die()
+        OR fault = 255
+    )
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/pztool_revertcopied.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/pztool_revertcopied.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/pztool_revertcopied.sql	(revision 22157)
@@ -0,0 +1,3 @@
+DELETE FROM pzDownloadImfile
+WHERE
+    fault != 0
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/regtool_create_dup_table.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/regtool_create_dup_table.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/regtool_create_dup_table.sql	(revision 22157)
@@ -0,0 +1,2 @@
+CREATE TEMPORARY TABLE duplicate (exp_id BIGINT, PRIMARY KEY(exp_id))
+ENGINE=MEMORY
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/regtool_pendingexp.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/regtool_pendingexp.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/regtool_pendingexp.sql	(revision 22157)
@@ -0,0 +1,39 @@
+-- output should match the format of newExp
+SELECT DISTINCT
+    exp_id,
+    tmp_exp_name,
+    tmp_camera,
+    tmp_telescope,
+    state,
+    workdir,
+    workdir_state,
+    reduction,
+    dvodb,
+    tess_id,
+    end_stage,
+    label,
+    epoch
+FROM
+    (SELECT
+       newExp.*,
+       newImfile.tmp_class_id,
+       rawImfile.tmp_class_id as raw_tmp_class_id
+    FROM newExp
+    JOIN newImfile
+       USING(exp_id)
+    LEFT JOIN rawExp
+       USING(exp_id)
+    LEFT JOIN rawImfile
+        ON newImfile.exp_id = rawImfile.exp_id
+        AND newImfile.tmp_class_id = rawImfile.tmp_class_id
+    WHERE
+        newExp.state = 'run'
+        AND rawExp.exp_id IS NULL
+-- where hook %s
+    GROUP BY
+        newExp.exp_id
+    HAVING
+        COUNT(newImfile.tmp_class_id) = COUNT(rawImfile.tmp_class_id)
+        AND SUM(rawImfile.fault) = 0
+-- limit hook %s
+    ) as Foo
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/regtool_pendingimfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/regtool_pendingimfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/regtool_pendingimfile.sql	(revision 22157)
@@ -0,0 +1,17 @@
+SELECT DISTINCT
+    newExp.exp_id,
+    newExp.tmp_exp_name,
+    newExp.tmp_camera,
+    newExp.tmp_telescope,
+    newExp.workdir,
+    newImfile.tmp_class_id,
+    newImfile.uri
+FROM newImfile
+JOIN newExp
+    USING(exp_id)
+LEFT JOIN rawImfile
+    ON newExp.exp_id = rawImfile.exp_id
+    AND newImfile.tmp_class_id = rawImfile.tmp_class_id
+WHERE
+    newExp.state = 'run'
+    AND rawImfile.tmp_class_id IS NULL
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/regtool_populate_dup_table.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/regtool_populate_dup_table.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/regtool_populate_dup_table.sql	(revision 22157)
@@ -0,0 +1,13 @@
+INSERT INTO duplicate
+SELECT exp_id FROM
+    (
+        SELECT
+        MAX(exp_id) AS exp_id,
+--            tmp_exp_name,
+--            tmp_camera,
+--            tmp_telescope,
+        count(exp_id) AS count
+        FROM newExp
+        GROUP BY tmp_exp_name, tmp_camera, tmp_telescope
+        HAVING count > 1
+    ) as Foo
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/regtool_processedimfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/regtool_processedimfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/regtool_processedimfile.sql	(revision 22157)
@@ -0,0 +1,10 @@
+SELECT
+    rawImfile.*,
+    newExp.tmp_exp_name,
+    newExp.tmp_camera,
+    newExp.tmp_telescope
+FROM rawImfile
+JOIN newExp
+    USING(exp_id)
+-- bogus conditional so there is a where clause to append to
+WHERE rawImfile.exp_id is NOT NULL
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/regtool_revertprocessedexp.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/regtool_revertprocessedexp.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/regtool_revertprocessedexp.sql	(revision 22157)
@@ -0,0 +1,3 @@
+DELETE FROM rawExp
+WHERE
+    fault != 0
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/regtool_revertprocessedimfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/regtool_revertprocessedimfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/regtool_revertprocessedimfile.sql	(revision 22157)
@@ -0,0 +1,3 @@
+DELETE FROM rawImfile
+WHERE
+    fault != 0
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/stacktool_find_complete_warps.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/stacktool_find_complete_warps.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/stacktool_find_complete_warps.sql	(revision 22157)
@@ -0,0 +1,24 @@
+    SELECT
+        warp_id,
+        warpSkyfile.skycell_id,
+        warpSkyfile.tess_id
+    FROM warpRun
+    JOIN warpSkyfile 
+        USING(warp_id)
+    JOIN fakeRun
+        USING(fake_id)
+    JOIN camRun
+        USING(cam_id)
+    JOIN chipRun
+        USING(chip_id)
+    JOIN rawExp
+        USING(exp_id)
+    WHERE
+        warpRun.state = 'stop'
+	AND warpSkyfile.ignored = 0
+-- It seems like we should be grouping the results here but in fact we don't
+-- want to do that as each warp_id may contain multiple skycells.  So a warp_id
+-- may end up in more than one stackRun (for different skycells)
+--    GROUP BY
+--        warp_id
+
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/stacktool_inputskyfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/stacktool_inputskyfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/stacktool_inputskyfile.sql	(revision 22157)
@@ -0,0 +1,24 @@
+SELECT DISTINCT
+    warpSkyfile.*,
+    rawExp.camera
+FROM stackRun
+JOIN stackInputSkyfile
+    USING(stack_id)
+JOIN warpSkyfile
+    ON  stackInputSkyfile.warp_id = warpSkyfile.warp_id
+    AND stackRun.skycell_id       = warpSkyfile.skycell_id
+    AND stackRun.tess_id          = warpSkyfile.tess_id
+JOIN warpRun
+    ON stackInputSkyfile.warp_id = warpRun.warp_id
+JOIN fakeRun
+    USING(fake_id)
+JOIN camRun
+    USING(cam_id)
+JOIN chipRun
+    USING(chip_id)
+JOIN chipProcessedImfile
+    USING(chip_id)
+JOIN rawExp
+    ON chipProcessedImfile.exp_id = rawExp.exp_id
+WHERE
+    stackRun.state = 'run'
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/stacktool_revertsumskyfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/stacktool_revertsumskyfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/stacktool_revertsumskyfile.sql	(revision 22157)
@@ -0,0 +1,3 @@
+DELETE FROM stackSumSkyfile
+WHERE
+    fault != 0
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/stacktool_sumskyfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/stacktool_sumskyfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/stacktool_sumskyfile.sql	(revision 22157)
@@ -0,0 +1,7 @@
+SELECT
+    stackSumSkyfile.*
+FROM stackRun
+JOIN stackSumSkyfile
+    USING(stack_id)
+WHERE
+    stackRun.state = 'run'
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/stacktool_tosum.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/stacktool_tosum.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/stacktool_tosum.sql	(revision 22157)
@@ -0,0 +1,28 @@
+SELECT DISTINCT
+    stackRun.stack_id,
+    stackRun.tess_id,
+    stackRun.skycell_id,
+    stackRun.workdir,
+    rawExp.camera
+FROM stackRun
+JOIN stackInputSkyfile
+    USING(stack_id)
+JOIN warpSkyfile
+    ON  stackInputSkyfile.warp_id = warpSkyfile.warp_id
+    AND stackRun.skycell_id       = warpSkyfile.skycell_id
+    AND stackRun.tess_id          = warpSkyfile.tess_id
+JOIN warpRun
+    ON warpRun.warp_id = warpSkyfile.warp_id
+JOIN fakeRun
+    USING(fake_id)
+JOIN camRun
+    USING(cam_id)
+JOIN chipRun
+    USING(chip_id)
+JOIN rawExp
+    USING(exp_id)
+LEFT JOIN stackSumSkyfile
+    USING(stack_id)
+WHERE
+    stackRun.state = 'run'
+    AND stackSumSkyfile.stack_id IS NULL
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/warptool_exp.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/warptool_exp.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/warptool_exp.sql	(revision 22157)
@@ -0,0 +1,8 @@
+SELECT
+    fakeRun.*
+FROM warpRun
+JOIN fakeRun
+    USING(fake_id)
+WHERE
+    warpRun.state = 'run'
+    AND fakeRun.state = 'stop'
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/warptool_imfile.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/warptool_imfile.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/warptool_imfile.sql	(revision 22157)
@@ -0,0 +1,27 @@
+SELECT DISTINCT
+    rawImfile.*,
+    warpRun.fake_id,
+    camRun.cam_id as cam_id,
+    chipProcessedImfile.uri as chip_uri,
+    chipProcessedImfile.path_base as chip_path_base,
+    camProcessedExp.path_base as cam_path_base
+FROM warpRun
+JOIN fakeRun
+    USING(fake_id)
+JOIN camRun
+    USING(cam_id)
+JOIN camProcessedExp
+    USING(cam_id)
+JOIN chipRun
+    ON camRun.chip_id = chipRun.chip_id
+JOIN chipProcessedImfile
+    ON chipRun.chip_id = chipProcessedImfile.chip_id
+JOIN rawImfile -- is there any reason not to refer back to rawimfiles?
+    ON chipProcessedImfile.exp_id = rawImfile.exp_id
+    AND chipProcessedImfile.class_id = rawImfile.class_id 
+WHERE
+    warpRun.state = 'run'
+    AND fakeRun.state = 'stop'
+    AND camRun.state = 'stop'
+    AND chipRun.state = 'full'
+
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/warptool_revertwarped.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/warptool_revertwarped.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/warptool_revertwarped.sql	(revision 22157)
@@ -0,0 +1,9 @@
+DELETE FROM warpSkyfile
+USING warpSkyfile, warpRun, fakeRun, camRun, chipRun, rawExp
+WHERE
+    warpSkyfile.warp_id = warpRun.warp_id
+    AND warpRun.fake_id = fakeRun.fake_id
+    AND fakeRun.cam_id = camRun.cam_id
+    AND camRun.chip_id = chipRun.chip_id
+    AND chipRun.exp_id = rawExp.exp_id
+    AND warpSkyfile.fault != 0
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/warptool_scmap.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/warptool_scmap.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/warptool_scmap.sql	(revision 22157)
@@ -0,0 +1,24 @@
+SELECT
+    warpSkyCellMap.*,
+    chipProcessedImfile.uri,
+    chipProcessedImfile.path_base as chip_path_base,
+    camProcessedExp.path_base as cam_path_base
+FROM warpRun
+JOIN warpSkyCellMap
+    USING(warp_id)
+JOIN fakeRun
+    USING(fake_id)
+JOIN camRun
+    USING(cam_id)
+JOIN camProcessedExp
+    USING(cam_id)
+JOIN chipRun
+    USING(chip_id)
+JOIN chipProcessedImfile
+    ON chipRun.chip_id = chipProcessedImfile.chip_id
+    AND warpSkyCellMap.class_id = chipProcessedImfile.class_id
+WHERE
+--    warpRun.state = 'run'
+    fakeRun.state = 'stop'
+    AND camRun.state = 'stop'
+    AND chipRun.state = 'full'
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/warptool_tooverlap.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/warptool_tooverlap.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/warptool_tooverlap.sql	(revision 22157)
@@ -0,0 +1,29 @@
+SELECT
+    warpRun.warp_id,
+    warpRun.fake_id,
+    warpRun.workdir,
+    warpRun.tess_id,
+    warpRun.label,
+    rawExp.camera,
+    exp_id,
+    warpRun.magiced
+FROM warpRun
+JOIN fakeRun
+    USING(fake_id)
+JOIN camRun
+    USING(cam_id)
+JOIN chipRun
+    USING(chip_id)
+JOIN rawExp
+    USING(exp_id)
+LEFT JOIN warpSkyCellMap
+    USING(warp_id)
+LEFT JOIN warpMask
+    ON warpRun.label = warpMask.label
+WHERE
+    warpRun.state = 'run'
+    AND fakeRun.state = 'stop'
+    AND camRun.state = 'stop'
+    AND chipRun.state = 'full'
+    AND warpSkyCellMap.warp_id IS NULL
+    AND warpMask.label IS NULL
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/warptool_towarped.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/warptool_towarped.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/warptool_towarped.sql	(revision 22157)
@@ -0,0 +1,37 @@
+SELECT DISTINCT
+    warpSkyCellMap.warp_id,
+    warpSkyCellMap.skycell_id,
+    warpSkyCellMap.tess_id,
+    warpRun.fake_id,
+    camRun.cam_id,
+    rawExp.camera,
+    warpRun.workdir
+FROM warpRun
+JOIN warpSkyCellMap
+    USING(warp_id)
+JOIN fakeRun
+    USING(fake_id)
+JOIN camRun
+    USING(cam_id)
+JOIN chipRun
+    USING(chip_id)
+JOIN chipProcessedImfile
+    USING(chip_id)
+JOIN rawExp
+    ON chipRun.exp_id = rawExp.exp_id
+LEFT JOIN warpSkyfile
+    ON warpRun.warp_id = warpSkyfile.warp_id
+    AND warpSkyCellMap.skycell_id = warpSkyfile.skycell_id
+    AND warpSkyCellMap.tess_id = warpSkyfile.tess_id
+LEFT JOIN warpMask
+    ON warpRun.label = warpMask.label
+WHERE
+    warpRun.state = 'run'
+    AND fakeRun.state = 'stop'
+    AND camRun.state = 'stop'
+    AND chipRun.state = 'full'
+    AND warpSkyfile.warp_id IS NULL
+    AND warpSkyfile.skycell_id IS NULL
+    AND warpSkyfile.tess_id IS NULL
+    AND warpSkyCellMap.fault = 0
+    AND warpMask.label IS NULL
Index: /branches/eam_branches/eam_branch_20080706/ippTools/share/warptool_warped.sql
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/share/warptool_warped.sql	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/share/warptool_warped.sql	(revision 22157)
@@ -0,0 +1,7 @@
+SELECT
+    warpSkyfile.*
+FROM warpRun
+JOIN warpSkyfile
+    USING(warp_id)
+WHERE
+    warpRun.state = 'run'
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/.cvsignore	(revision 22157)
@@ -0,0 +1,32 @@
+.deps
+.gdb_history
+.libs
+Makefile
+Makefile.in
+config.h
+config.h.in
+stamp-h1
+*.la
+*.lo
+pxtoolsErrorCodes.c
+pxtoolsErrorCodes.h
+pxadmin
+pxinject
+pztool
+pzgetexp
+pzgetimfiles
+regtool
+guidetool
+chiptool
+camtool
+warptool
+difftool
+stacktool
+faketool
+dettool
+detselect
+pxdata.c
+magictool
+caltool
+flatcorr
+pstamptool
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/Makefile.am	(revision 22157)
@@ -0,0 +1,218 @@
+bin_PROGRAMS = \
+	caltool \
+	camtool \
+	chiptool \
+	detselect \
+	dettool \
+	difftool \
+	flatcorr \
+	magictool \
+	pstamptool \
+	pxadmin \
+	pxinject \
+	faketool \
+	pzgetexp \
+	pzgetimfiles \
+	pztool \
+	regtool \
+	stacktool \
+	warptool
+
+
+bin_SCRIPTS = \
+	fakemagic
+
+pkginclude_HEADERS = \
+	pxadmin.h \
+	pxcam.h \
+	pxchip.h \
+	pxconfig.h \
+	pxdata.h \
+	pxfake.h \
+	pxregister.h \
+	pxtag.h \
+	pxtools.h \
+	pxtoolsErrorCodes.h \
+	pxtree.h \
+	pxwarp.h
+
+noinst_HEADERS = \
+	caltool.h \
+	camtool.h \
+	chiptool.h \
+	detselect.h \
+	dettool.h \
+	difftool.h \
+	flatcorr.h \
+	faketool.h \
+	guidetool.h \
+	magictool.h \
+	pstamptool.h \
+	pxinject.h \
+	pzgetexp.h \
+	pzgetimfiles.h \
+	pztool.h \
+	regtool.h \
+	stacktool.h \
+	warptool.h
+
+lib_LTLIBRARIES = libpxtools.la
+libpxtools_la_CFLAGS	= $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+libpxtools_la_LIBS      = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS)
+libpxtools_la_LDFLAGS   = -release $(PACKAGE_VERSION)
+libpxtools_la_SOURCES   = \
+	pxcam.c \
+	pxchip.c \
+	pxconfig.c \
+	pxdata.c \
+	pxerrors.c \
+	pxfake.c \
+	pxfault.c \
+	pxregister.c \
+	pxtag.c \
+	pxtools.c \
+	pxtoolsErrorCodes.c \
+	pxtree.c \
+	pxwarp.c
+
+# for pxtools.h
+AM_CPPFLAGS = -I$(top_srcdir)/src$
+
+pztool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS) 
+pztool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+pztool_SOURCES = \
+    pztool.c \
+    pztoolConfig.c
+
+pstamptool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS) 
+pstamptool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+pstamptool_SOURCES = \
+    pstamptool.c \
+    pstamptoolConfig.c
+
+caltool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+caltool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+caltool_SOURCES = \
+    caltool.c \
+    caltoolConfig.c
+
+flatcorr_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+flatcorr_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+flatcorr_SOURCES = \
+    flatcorr.c \
+    flatcorrConfig.c
+
+regtool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+regtool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+regtool_SOURCES = \
+    regtool.c \
+    regtoolConfig.c
+
+guidetool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS) 
+guidetool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+guidetool_SOURCES = \
+    guidetool.c \
+    guidetoolConfig.c
+
+chiptool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+chiptool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+chiptool_SOURCES = \
+    chiptool.c \
+    chiptoolConfig.c
+
+camtool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+camtool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+camtool_SOURCES = \
+    camtool.c \
+    camtoolConfig.c
+
+faketool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+faketool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+faketool_SOURCES = \
+    faketool.c \
+    faketoolConfig.c
+
+magictool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+magictool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+magictool_SOURCES = \
+    magictool.c \
+    magictoolConfig.c
+
+warptool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+warptool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+warptool_SOURCES = \
+    warptool.c \
+    warptoolConfig.c
+
+difftool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+difftool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+difftool_SOURCES = \
+    difftool.c \
+    difftoolConfig.c
+
+stacktool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+stacktool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+stacktool_SOURCES = \
+    stacktool.c \
+    stacktoolConfig.c
+
+pxadmin_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+pxadmin_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+pxadmin_SOURCES = \
+    pxadmin.c \
+    pxadminConfig.c
+
+pzgetexp_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+pzgetexp_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+pzgetexp_SOURCES = \
+    pzgetexp.c \
+    pzgetexpConfig.c
+
+pzgetimfiles_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+pzgetimfiles_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+pzgetimfiles_SOURCES = \
+    pzgetimfiles.c \
+    pzgetimfilesConfig.c
+
+dettool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+dettool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+dettool_SOURCES = \
+    dettool.c \
+    dettoolConfig.c \
+    dettool_correction.c
+
+detselect_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+detselect_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+detselect_SOURCES = \
+    detselect.c \
+    detselectConfig.c
+
+pxinject_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+pxinject_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+pxinject_SOURCES = \
+    pxinject.c \
+    pxinjectConfig.c
+
+clean-local:
+	-rm -f TAGS
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
+
+# Error codes.
+BUILT_SOURCES = pxtoolsErrorCodes.h pxtoolsErrorCodes.c
+CLEANFILES = pxtoolsErrorCodes.h pxtoolsErrorCodes.c
+EXTRA_DIST = pxtoolsErrorCodes.dat pxtoolsErrorCodes.c.in pxtoolsErrorCodes.h.in fakemagic
+
+pxtoolsErrorCodes.h : pxtoolsErrorCodes.dat pxtoolsErrorCodes.h.in
+	$(ERRORCODES) --data=pxtoolsErrorCodes.dat --outdir=. pxtoolsErrorCodes.h
+
+pxtoolsErrorCodes.c : pxtoolsErrorCodes.dat pxtoolsErrorCodes.c.in pxtoolsErrorCodes.h
+	$(ERRORCODES) --data=pxtoolsErrorCodes.dat --outdir=. pxtoolsErrorCodes.c
+
+BUILT_SOURCES += pxdata.c
+EXTRA_DIST += pxdata.c.template
+
+pxdata.c: $(srcdir)/pxdata.c.template $(srcdir)/Makefile
+	$(PERL) -pe 's|\@PKGDATADIR\@|$(pkgdatadir)|' $< > $@
+
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/caltool.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/caltool.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/caltool.c	(revision 22157)
@@ -0,0 +1,261 @@
+/*
+ * caltool.c
+ *
+ * Copyright (C) 2006-2007  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVB_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+
+#include <ippdb.h>
+
+#include "pxtools.h"
+#include "caltool.h"
+
+static bool adddbMode(pxConfig *config);
+static bool dbsMode(pxConfig *config);
+static bool addrunMode(pxConfig *config);
+static bool runsMode(pxConfig *config);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = caltoolConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(CALTOOL_MODE_ADDDB,        adddbMode);
+        MODECASE(CALTOOL_MODE_DBS,          dbsMode);
+        MODECASE(CALTOOL_MODE_ADDRUN,       addrunMode);
+        MODECASE(CALTOOL_MODE_RUNS,         runsMode);
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+static bool adddbMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required 
+    PXOPT_LOOKUP_STR(dvodb, config->args, "-set_dvodb", true, false);
+
+    if (!calDBInsert(config->dbh,
+            0,       // cal_id
+            dvodb,
+            "active"    // state
+        )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+
+    }
+
+    return false;
+}
+
+
+static bool dbsMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = psStringCopy("SELECT * FROM calDB");
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("caltool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "calDB", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool addrunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required
+    PXOPT_LOOKUP_STR(cal_id, config->args, "-cal_id", true, false);
+    PXOPT_LOOKUP_STR(region, config->args, "-region", true, false);
+    PXOPT_LOOKUP_STR(last_step, config->args, "-last_step", true, false);
+    PXOPT_LOOKUP_STR(state, config->args, "-state", true, false);
+
+    if (!calRunInsert(config->dbh,
+            (psS64)atoll(cal_id),
+            region,
+            last_step,
+            state
+        )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+
+    }
+
+    return false;
+}
+
+
+static bool runsMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where,  "-cal_id",     "cal_id", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_BOOL(faulted, config->args, "-simple", false);
+
+    psString query = psStringCopy("SELECT * FROM calRun");
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "WHERE fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "WHERE fault = 0");
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, " %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " AND %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("caltool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "calRun", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/caltool.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/caltool.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/caltool.h	(revision 22157)
@@ -0,0 +1,35 @@
+/*
+ * caltool.h
+ *
+ * Copyright (C) 2006-2007  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef CALTOOL_H
+#define CALTOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    CALTOOL_MODE_NONE           = PXTOOL_MODE_NONE,
+    CALTOOL_MODE_DBS,
+    CALTOOL_MODE_ADDDB,
+    CALTOOL_MODE_ADDRUN,
+    CALTOOL_MODE_RUNS
+} caltoolMode;
+
+pxConfig *caltoolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // CALTOOL_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/caltoolConfig.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/caltoolConfig.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/caltoolConfig.c	(revision 22157)
@@ -0,0 +1,104 @@
+/*
+ * caltoolConfig.c
+ *
+ * Copyright (C) 2006-2007  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "caltool.h"
+
+pxConfig *caltoolConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (!config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    psTime *time = psTimeGetNow(PS_TIME_TAI);
+    psString now = psTimeToISO(time);
+    psFree(time);
+
+    // -adddb
+    psMetadata *adddbArgs = psMetadataAlloc();
+    psMetadataAddStr(adddbArgs, PS_LIST_TAIL, "-dvodb",  0,            "define DVO db (required)", NULL);
+
+    // -dbs
+    psMetadata *dbsArgs = psMetadataAlloc();
+    psMetadataAddU64(dbsArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(dbsArgs, PS_LIST_TAIL, "-simple", 0,            "use the simple output format", false);
+
+    // -addrun
+    psMetadata *addrunArgs = psMetadataAlloc();
+    psMetadataAddStr(addrunArgs, PS_LIST_TAIL, "-cal_id",  0,            "define calibration DB ID (required)", NULL);
+    psMetadataAddStr(addrunArgs, PS_LIST_TAIL, "-region",  0,            "define region (required)", NULL);
+    psMetadataAddStr(addrunArgs, PS_LIST_TAIL, "-last_step",  0,            "define last step (required)", NULL);
+    psMetadataAddStr(addrunArgs, PS_LIST_TAIL, "-state",  0,            "define state (required)", NULL);
+
+    // -runs
+    psMetadata *runsArgs = psMetadataAlloc();
+    psMetadataAddStr(runsArgs, PS_LIST_TAIL, "-cal_id",  0,            "search for calibration ID", NULL);
+    psMetadataAddU64(runsArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(runsArgs, PS_LIST_TAIL, "-faulted",  0,            "only return imfiles with a fault status set", false);
+    psMetadataAddBool(runsArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    psFree(now);
+
+    psMetadata *argSets = psMetadataAlloc();
+    psMetadata *modes = psMetadataAlloc();
+
+    PXOPT_ADD_MODE("-adddb",   "add a DVO calibration DB",        CALTOOL_MODE_ADDDB,     adddbArgs);
+    PXOPT_ADD_MODE("-dbs",     "list DVO calibration DBs",        CALTOOL_MODE_DBS,       dbsArgs);
+    PXOPT_ADD_MODE("-addrun",  "add the results of a calibration run",        CALTOOL_MODE_ADDRUN,    addrunArgs);
+    PXOPT_ADD_MODE("-runs",    "list the results of calibration runs",        CALTOOL_MODE_RUNS,      runsArgs);
+
+    if (!pxGetOptions(stderr, argc, argv, config, modes, argSets)) {
+        psError(PS_ERR_UNKNOWN, true, "option parsing failed");
+        psFree(argSets);
+        psFree(modes);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+    psFree(modes);
+
+    // define Database handle, if used
+    // do this last so we don't setup a connection before CLI options are
+    // validated
+    config->dbh = psMemIncrRefCounter(pmConfigDB(config->modules));
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    return config;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/camtool.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/camtool.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/camtool.c	(revision 22157)
@@ -0,0 +1,915 @@
+/*
+ * camtool.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+
+#include "pxtools.h"
+#include "pxcam.h"
+#include "camtool.h"
+
+static bool definebyqueryMode(pxConfig *config);
+static bool updaterunMode(pxConfig *config);
+static bool pendingexpMode(pxConfig *config);
+static bool pendingimfileMode(pxConfig *config);
+static bool addprocessedexpMode(pxConfig *config);
+static bool processedexpMode(pxConfig *config);
+static bool revertprocessedexpMode(pxConfig *config);
+static bool updateprocessedexpMode(pxConfig *config);
+static bool blockMode(pxConfig *config);
+static bool maskedMode(pxConfig *config);
+static bool unblockMode(pxConfig *config);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = camtoolConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(CAMTOOL_MODE_DEFINEBYQUERY,        definebyqueryMode);
+        MODECASE(CAMTOOL_MODE_UPDATERUN,            updaterunMode);
+        MODECASE(CAMTOOL_MODE_PENDINGEXP,           pendingexpMode);
+        MODECASE(CAMTOOL_MODE_PENDINGIMFILE,        pendingimfileMode);
+        MODECASE(CAMTOOL_MODE_ADDPROCESSEDEXP,      addprocessedexpMode);
+        MODECASE(CAMTOOL_MODE_PROCESSEDEXP,         processedexpMode);
+        MODECASE(CAMTOOL_MODE_REVERTPROCESSEDEXP,   revertprocessedexpMode);
+        MODECASE(CAMTOOL_MODE_UPDATEPROCESSEDEXP,   updateprocessedexpMode);
+        MODECASE(CAMTOOL_MODE_BLOCK,                blockMode);
+        MODECASE(CAMTOOL_MODE_MASKED,               maskedMode);
+        MODECASE(CAMTOOL_MODE_UNBLOCK,              unblockMode);
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+
+static bool definebyqueryMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-chip_id", "chip_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-exp_id", "exp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_name", "exp_name", "==");
+    PXOPT_COPY_STR(config->args, where, "-inst", "camera", "==");
+    PXOPT_COPY_STR(config->args, where, "-telescope", "telescope", "==");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_begin", "dateobs", ">=");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_end", "dateobs", "<=");
+    PXOPT_COPY_STR(config->args, where, "-exp_tag", "exp_tag", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_type", "exp_type", "==");
+    PXOPT_COPY_STR(config->args, where, "-filelevel", "filelevel", "==");
+    PXOPT_COPY_STR(config->args, where, "-reduction", "reduction", "==");
+    PXOPT_COPY_STR(config->args, where, "-filter", "filter", "==");
+    PXOPT_COPY_F64(config->args, where, "-airmass_min", "airmass", ">=");
+    PXOPT_COPY_F64(config->args, where, "-airmass_max", "airmass", "<");
+    PXOPT_COPY_F64(config->args, where, "-ra_min", "ra", ">=");
+    PXOPT_COPY_F64(config->args, where, "-ra_max", "ra", "<");
+    PXOPT_COPY_F64(config->args, where, "-decl_min", "decl", ">=");
+    PXOPT_COPY_F64(config->args, where, "-decl_max", "decl", "<");
+    PXOPT_COPY_F32(config->args, where, "-exp_time_min", "exp_time", ">=");
+    PXOPT_COPY_F32(config->args, where, "-exp_time_max", "exp_time", "<");
+    PXOPT_COPY_F32(config->args, where, "-sat_pixel_frac_min", "sat_pixel_frac", ">=");
+    PXOPT_COPY_F32(config->args, where, "-sat_pixel_frac_max", "sat_pixel_frac", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_min", "bt", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_max", "bt", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_stdev_min", "bg_stdev", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_stdev_max", "bg_stdev", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_mean_stdev_min", "bg_mean_stdev", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_mean_stdev_max", "bg_mean_stdev", "<");
+    PXOPT_COPY_F64(config->args, where, "-alt_min", "alt", ">=");
+    PXOPT_COPY_F64(config->args, where, "-alt_max", "alt", "<");
+    PXOPT_COPY_F64(config->args, where, "-az_min", "az", ">=");
+    PXOPT_COPY_F64(config->args, where, "-az_max", "az", "<");
+    PXOPT_COPY_F32(config->args, where, "-ccd_temp_min", "ccd_temp", ">=");
+    PXOPT_COPY_F32(config->args, where, "-ccd_temp_max", "ccd_temp", "<");
+    PXOPT_COPY_F64(config->args, where, "-posang_min", "posang", ">=");
+    PXOPT_COPY_F64(config->args, where, "-posang_max", "posang", "<");
+    PXOPT_COPY_STR(config->args, where, "-object", "object", "==");
+    PXOPT_COPY_F32(config->args, where, "-solang_min", "solang", ">=");
+    PXOPT_COPY_F32(config->args, where, "-solang_max", "solang", "<");
+
+    if (!psListLength(where->list)
+        && !psMetadataLookupBool(NULL, config->args, "-all")) {
+        psFree(where);
+        psError(PXTOOLS_ERR_DATA, false, "search parameters are required");
+        return false;
+    }
+
+    PXOPT_LOOKUP_STR(workdir, config->args, "-set_workdir", false, false);
+    PXOPT_LOOKUP_STR(label, config->args, "-set_label", false, false);
+    PXOPT_LOOKUP_STR(reduction, config->args, "-set_reduction", false, false);
+    PXOPT_LOOKUP_STR(expgroup, config->args, "-set_expgroup", false, false);
+    PXOPT_LOOKUP_STR(dvodb, config->args, "-set_dvodb", false, false);
+    PXOPT_LOOKUP_STR(tess_id, config->args, "-set_tess_id", false, false);
+    PXOPT_LOOKUP_STR(end_stage, config->args, "-set_end_stage", false, false);
+
+    // find the exp_id of all the exposures that we want to queue up.
+    psString query = pxDataGet("camtool_find_chip_id.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        psFree(where);
+        return false;
+    }
+
+    // use psDBGenerateWhereSQL because the SQL yields an intermediate table
+    if (where && psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereSQL(where, NULL);
+        psStringAppend(&query, "%s", whereClause);
+        psFree(whereClause);
+    }
+
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("camtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // start a transaction so we don't end up with an exp without any associted
+    // imfiles
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(output);
+        return false;
+    }
+
+    // would could do this "all in the database" if we didn't want the option
+    // of changing the label/reduction/expgroup/dvodb/etc.  So we're pulling the
+    // data out so we have the option of changing these values or leaving the
+    // old values in place (i.e., passing the values through).
+
+    // loop over our list of chipRun rows
+    for (long i = 0; i < psArrayLength(output); i++) {
+        psMetadata *md = output->data[i];
+
+        chipRunRow *row = chipRunObjectFromMetadata(md);
+        if (!row) {
+            psError(PS_ERR_UNKNOWN, false, "failed to convert metadata into chipRun");
+            psFree(output);
+            return false;
+        }
+
+        // queue the exp
+        if (!pxcamQueueByChipID(config,
+                    row->chip_id,
+                    workdir     ? workdir   : row->workdir,
+                    label       ? label     : row->label,
+                    reduction   ? reduction : row->reduction,
+                    expgroup    ? expgroup  : row->expgroup,
+                    dvodb       ? dvodb     : row->dvodb,
+                    tess_id     ? tess_id   : row->tess_id,
+                    end_stage   ? end_stage : row->end_stage
+        )) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false,
+                    "failed to trying to queue chip_id: %" PRId64, row->chip_id);
+            psFree(row);
+            psFree(output);
+            return false;
+        }
+        psFree(row);
+    }
+    psFree(output);
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool updaterunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-cam_id", "cam_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-chip_id", "chip_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-exp_id", "exp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_name", "exp_name", "==");
+    PXOPT_COPY_STR(config->args, where, "-inst", "camera", "==");
+    PXOPT_COPY_STR(config->args, where, "-telescope", "telescope", "==");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_begin", "dateobs", ">=");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_end", "dateobs", "<=");
+    PXOPT_COPY_STR(config->args, where, "-exp_tag", "exp_tag", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_type", "exp_type", "==");
+    PXOPT_COPY_STR(config->args, where, "-filelevel", "filelevel", "==");
+    PXOPT_COPY_STR(config->args, where, "-reduction", "reduction", "==");
+    PXOPT_COPY_STR(config->args, where, "-filter", "filter", "==");
+    PXOPT_COPY_F64(config->args, where, "-airmass_min", "airmass", ">=");
+    PXOPT_COPY_F64(config->args, where, "-airmass_max", "airmass", "<");
+    PXOPT_COPY_F64(config->args, where, "-ra_min", "ra", ">=");
+    PXOPT_COPY_F64(config->args, where, "-ra_max", "ra", "<");
+    PXOPT_COPY_F64(config->args, where, "-decl_min", "decl", ">=");
+    PXOPT_COPY_F64(config->args, where, "-decl_max", "decl", "<");
+    PXOPT_COPY_F32(config->args, where, "-exp_time_min", "exp_time", ">=");
+    PXOPT_COPY_F32(config->args, where, "-exp_time_max", "exp_time", "<");
+    PXOPT_COPY_F32(config->args, where, "-sat_pixel_frac_min", "sat_pixel_frac", ">=");
+    PXOPT_COPY_F32(config->args, where, "-sat_pixel_frac_max", "sat_pixel_frac", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_min", "bt", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_max", "bt", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_stdev_min", "bg_stdev", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_stdev_max", "bg_stdev", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_mean_stdev_min", "bg_mean_stdev", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_mean_stdev_max", "bg_mean_stdev", "<");
+    PXOPT_COPY_F64(config->args, where, "-alt_min", "alt", ">=");
+    PXOPT_COPY_F64(config->args, where, "-alt_max", "alt", "<");
+    PXOPT_COPY_F64(config->args, where, "-az_min", "az", ">=");
+    PXOPT_COPY_F64(config->args, where, "-az_max", "az", "<");
+    PXOPT_COPY_F32(config->args, where, "-ccd_temp_min", "ccd_temp", ">=");
+    PXOPT_COPY_F32(config->args, where, "-ccd_temp_max", "ccd_temp", "<");
+    PXOPT_COPY_F64(config->args, where, "-posang_min", "posang", ">=");
+    PXOPT_COPY_F64(config->args, where, "-posang_max", "posang", "<");
+    PXOPT_COPY_STR(config->args, where, "-object", "object", "==");
+    PXOPT_COPY_F32(config->args, where, "-solang_min", "solang", ">=");
+    PXOPT_COPY_F32(config->args, where, "-solang_max", "solang", "<");
+
+    if (!psListLength(where->list)
+        && !psMetadataLookupBool(NULL, config->args, "-all")) {
+        psFree(where);
+        psError(PXTOOLS_ERR_DATA, false, "search parameters are required");
+        return false;
+    }
+
+    PXOPT_LOOKUP_STR(state, config->args, "-state", false, false);
+    PXOPT_LOOKUP_STR(label, config->args, "-label", false, false);
+
+    if ((!state) && (!label)) {
+        psError(PXTOOLS_ERR_DATA, false, "parameters are required");
+        psFree(where);
+        return false;
+    }
+
+    if (state) {
+        // set chipRun.state to state
+        if (!pxcamRunSetStateByQuery(config, where, state)) {
+            psFree(where);
+            return false;
+        }
+    }
+
+    if (label) {
+        // set chipRun.label to label
+        if (!pxcamRunSetLabelByQuery(config, where, label)) {
+            psFree(where);
+            return false;
+        }
+    }
+
+    psFree(where);
+
+    return true;
+}
+
+
+static bool pendingexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-cam_id", "cam_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-chip_id", "chip_id", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("camtool_find_pendingexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // use psDBGenerateWhereSQL because the SQL yields an intermediate table
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereSQL(where, NULL);
+        psStringAppend(&query, "%s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("camtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negate simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "camPendingExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool pendingimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-cam_id",   "cam_id",   "==");
+    PXOPT_COPY_S64(config->args, where, "-chip_id",  "chip_id",  "==");
+    PXOPT_COPY_STR(config->args, where, "-class",    "class",    "==");
+    PXOPT_COPY_STR(config->args, where, "-class_id", "class_id", "==");
+
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("camtool_find_pendingimfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // use psDBGenerateWhereSQL because the SQL yields an intermediate table
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereSQL(where, NULL);
+        psStringAppend(&query, "%s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("camtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negate simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "chipProcessedImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool addprocessedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required
+    PXOPT_LOOKUP_S64(cam_id, config->args, "-cam_id", true, false);
+    PXOPT_LOOKUP_STR(uri, config->args, "-uri", true, false);
+
+    // optional
+    PXOPT_LOOKUP_F32(bg, config->args, "-bg", false, false);
+    PXOPT_LOOKUP_F32(bg_stdev, config->args, "-bg_stdev", false, false);
+    PXOPT_LOOKUP_F32(bg_mean_stdev, config->args, "-bg_mean_stdev", false, false);
+    PXOPT_LOOKUP_F32(bias, config->args,           "-bias", false, false);
+    PXOPT_LOOKUP_F32(bias_stdev, config->args,     "-bias_stdev", false, false);
+    PXOPT_LOOKUP_F32(fringe_0, config->args,       "-fringe_0", false, false);
+    PXOPT_LOOKUP_F32(fringe_1, config->args,       "-fringe_1", false, false);
+    PXOPT_LOOKUP_F32(fringe_2, config->args,       "-fringe_2", false, false);
+    PXOPT_LOOKUP_F32(sigma_ra, config->args, "-sigma_ra", false, false);
+    PXOPT_LOOKUP_F32(sigma_dec, config->args, "-sigma_dec", false, false);
+    PXOPT_LOOKUP_F32(ap_resid, config->args,       "-ap_resid", false, false);
+    PXOPT_LOOKUP_F32(ap_resid_stdev, config->args, "-ap_resid_stdev", false, false);
+    PXOPT_LOOKUP_F32(zp_mean, config->args, "-zp_mean", false, false);
+    PXOPT_LOOKUP_F32(zp_stdev, config->args, "-zp_stdev", false, false);
+    PXOPT_LOOKUP_F32(fwhm_major, config->args, "-fwhm_major", false, false);
+    PXOPT_LOOKUP_F32(fwhm_minor, config->args, "-fwhm_minor", false, false);
+    PXOPT_LOOKUP_F32(dtime_detrend, config->args, "-dtime_detrend", false, false);
+    PXOPT_LOOKUP_F32(dtime_photom, config->args, "-dtime_photom", false, false);
+    PXOPT_LOOKUP_F32(dtime_astrom, config->args, "-dtime_astrom", false, false);
+    PXOPT_LOOKUP_STR(hostname, config->args,       "-hostname", false, false);
+    PXOPT_LOOKUP_S32(n_stars, config->args,        "-n_stars", false, false);
+    PXOPT_LOOKUP_S32(n_extended, config->args,     "-n_extended", false, false);
+    PXOPT_LOOKUP_S32(n_cr, config->args,           "-n_cr", false, false);
+    PXOPT_LOOKUP_S32(n_astrom, config->args,       "-n_astrom", false, false);
+    PXOPT_LOOKUP_STR(path_base, config->args, "-path_base", false, false);
+    PXOPT_LOOKUP_S16(code, config->args, "-code", false, false);
+
+    // generate restrictions
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-cam_id",   "cam_id",   "==");
+
+    psString query = pxDataGet("camtool_find_pendingexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // use psDBGenerateWhereSQL because the SQL yields an intermediate table
+    if (psListLength(where->list)) {
+        psString whereClaus = psDBGenerateWhereSQL(where, NULL);
+	psStringAppend(&query, "%s", whereClaus);
+	psFree(whereClaus);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("camtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(output);
+        return false;
+    }
+
+    camRunRow *pendingRow = camRunObjectFromMetadata(output->data[0]);
+    psFree(output);
+    camProcessedExpRow *row = camProcessedExpRowAlloc(
+            pendingRow->cam_id,
+            uri,
+            bg,
+            bg_stdev,
+            bg_mean_stdev,
+            bias,
+            bias_stdev,
+            fringe_0,
+            fringe_1,
+            fringe_2,
+            sigma_ra,
+            sigma_dec,
+            ap_resid,
+            ap_resid_stdev,
+            zp_mean,
+            zp_stdev,
+            fwhm_major,
+            fwhm_minor,
+            dtime_detrend,
+            dtime_photom,
+            dtime_astrom,
+            hostname,
+            n_stars,
+            n_extended,
+            n_cr,
+            n_astrom,
+            path_base,
+            code
+        );
+
+    if (!camProcessedExpInsertObject(config->dbh, row)) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(row);
+        psFree(pendingRow);
+        return false;
+    }
+
+    // since there is only one exp per 'run' set camRun.state = 'stop'
+    if (!pxcamRunSetState(config, row->cam_id, "stop")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to change camRun.state for cam_id: %" PRId64, row->cam_id);
+        psFree(row);
+        psFree(pendingRow);
+        return false;
+    }
+
+    // NULL for end_stage means go as far as possible
+    // EAM : skip here if code != 0
+    // Also, we can run fake even if tess_id is not defined
+    if (code || (pendingRow->end_stage && psStrcasestr(pendingRow->end_stage, "cam"))) {
+        psFree(row);
+        psFree(pendingRow);
+        if (!psDBCommit(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+        return true;
+    }
+    psFree(row);
+    // else continue on...
+
+    if (!pxfakeQueueByCamID(config,
+            pendingRow->cam_id,
+            pendingRow->workdir,
+            pendingRow->label,
+            pendingRow->reduction,
+            pendingRow->expgroup,
+            pendingRow->dvodb,
+            pendingRow->tess_id,
+            pendingRow->end_stage
+    )) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "failed to queue new fakeRun");
+        psFree(pendingRow);
+        return false;
+    }
+    psFree(pendingRow);
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool processedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_BOOL(faulted, config->args, "-faulted", false);
+
+    // generate restrictions
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-cam_id",   "cam_id",   "==");
+    PXOPT_COPY_S64(config->args, where, "-chip_id",  "chip_id",   "==");
+
+    psString query = pxDataGet("camtool_find_processedexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // use psDBGenerateWhereConditionalSQL with AND ... because the SQL ends in a WHERE
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "AND camProcessedExp.fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "AND camProcessedExp.fault = 0");
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("camtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negate simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "camProcessedExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool revertprocessedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-cam_id", "cam_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-chip_id", "chip_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-exp_id", "exp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_name", "exp_name", "==");
+    PXOPT_COPY_STR(config->args, where, "-inst", "camera", "==");
+    PXOPT_COPY_STR(config->args, where, "-telescope", "telescope", "==");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_begin", "dateobs", ">=");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_end", "dateobs", "<=");
+    PXOPT_COPY_STR(config->args, where, "-exp_tag", "exp_tag", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_type", "exp_type", "==");
+    PXOPT_COPY_STR(config->args, where, "-filelevel", "filelevel", "==");
+    PXOPT_COPY_STR(config->args, where, "-reduction", "reduction", "==");
+    PXOPT_COPY_STR(config->args, where, "-filter", "filter", "==");
+    PXOPT_COPY_F32(config->args, where, "-airmass_min", "airmass", ">=");
+    PXOPT_COPY_F32(config->args, where, "-airmass_max", "airmass", "<");
+    PXOPT_COPY_F64(config->args, where, "-ra_min", "ra", ">=");
+    PXOPT_COPY_F64(config->args, where, "-ra_max", "ra", "<");
+    PXOPT_COPY_F64(config->args, where, "-decl_min", "decl", ">=");
+    PXOPT_COPY_F64(config->args, where, "-decl_max", "decl", "<");
+    PXOPT_COPY_F32(config->args, where, "-exp_time_min", "exp_time", ">=");
+    PXOPT_COPY_F32(config->args, where, "-exp_time_max", "exp_time", "<");
+    PXOPT_COPY_F32(config->args, where, "-sat_pixel_frac_min", "sat_pixel_frac", ">=");
+    PXOPT_COPY_F32(config->args, where, "-sat_pixel_frac_max", "sat_pixel_frac", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_min", "bt", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_max", "bt", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_stdev_min", "bg_stdev", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_stdev_max", "bg_stdev", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_mean_stdev_min", "bg_mean_stdev", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_mean_stdev_max", "bg_mean_stdev", "<");
+    PXOPT_COPY_F64(config->args, where, "-alt_min", "alt", ">=");
+    PXOPT_COPY_F64(config->args, where, "-alt_max", "alt", "<");
+    PXOPT_COPY_F64(config->args, where, "-az_min", "az", ">=");
+    PXOPT_COPY_F64(config->args, where, "-az_max", "az", "<");
+    PXOPT_COPY_F32(config->args, where, "-ccd_temp_min", "ccd_temp", ">=");
+    PXOPT_COPY_F32(config->args, where, "-ccd_temp_max", "ccd_temp", "<");
+    PXOPT_COPY_F64(config->args, where, "-posang_min", "posang", ">=");
+    PXOPT_COPY_F64(config->args, where, "-posang_max", "posang", "<");
+    PXOPT_COPY_STR(config->args, where, "-object", "object", "==");
+    PXOPT_COPY_F32(config->args, where, "-solang_min", "solang", ">=");
+    PXOPT_COPY_F32(config->args, where, "-solang_max", "solang", "<");
+
+    if (!psListLength(where->list) && !psMetadataLookupBool(NULL, config->args, "-all")) {
+        psFree(where);
+        psError(PXTOOLS_ERR_DATA, false, "search parameters are required");
+        return false;
+    }
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(where);
+        return false;
+    }
+
+    {
+	psString query = pxDataGet("camtool_reset_faulted_runs.sql");
+	if (!query) {
+	    psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+	    psFree(where);
+	    return false;
+	}
+
+	// use psDBGenerateWhereConditionalSQL with AND ... because the SQL ends in a WHERE
+	if (where && psListLength(where->list)) {
+	    psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+	    psStringAppend(&query, " AND %s", whereClause);
+	    psFree(whereClause);
+	}
+
+	if (!p_psDBRunQuery(config->dbh, query)) {
+	    // rollback
+	    if (!psDBRollback(config->dbh)) {
+		psError(PS_ERR_UNKNOWN, false, "database error");
+	    }
+	    psError(PS_ERR_UNKNOWN, false, "database error");
+	    psFree(query);
+	    psFree(where);
+	    return false;
+	}
+	psFree(query);
+    }
+
+    {
+	psString query = pxDataGet("camtool_revertprocessedexp.sql");
+	if (!query) {
+	    // rollback
+	    if (!psDBRollback(config->dbh)) {
+		psError(PS_ERR_UNKNOWN, false, "database error");
+	    }
+	    psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+	    psFree(where);
+	    return false;
+	}
+
+	// use psDBGenerateWhereConditionalSQL with AND ... because the SQL ends in a WHERE
+	if (where && psListLength(where->list)) {
+	    psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+	    psStringAppend(&query, " AND %s", whereClause);
+	    psFree(whereClause);
+	}
+
+	if (!p_psDBRunQuery(config->dbh, query)) {
+	    // rollback
+	    if (!psDBRollback(config->dbh)) {
+		psError(PS_ERR_UNKNOWN, false, "database error");
+	    }
+	    psError(PS_ERR_UNKNOWN, false, "database error");
+	    psFree(query);
+	    psFree(where);
+	    return false;
+	}
+	psFree(query);
+    }
+    psFree(where);
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool updateprocessedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_S16(code, config->args, "-code", true, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-cam_id",   "cam_id",   "==");
+    PXOPT_COPY_S64(config->args, where, "-chip_id",  "chip_id",  "==");
+    PXOPT_COPY_STR(config->args, where, "-class",    "class",    "==");
+    PXOPT_COPY_STR(config->args, where, "-class_id", "class_id", "==");
+
+    if (!pxSetFaultCode(config->dbh, "camProcessedExp", where, code)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to set set fault flag");
+	psFree (where);
+        return false;
+    }
+    psFree (where);
+
+    return true;
+}
+
+
+static bool blockMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_STR(label, config->args, "-label", true, false);
+
+    if (!camMaskInsert(config->dbh, label)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool maskedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = psStringCopy("SELECT * FROM camMask");
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("camtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "camMask", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool unblockMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_STR(label, config->args, "-label", true, false);
+
+    char *query = "DELETE FROM camMask WHERE label = '%s'";
+
+    if (!p_psDBRunQuery(config->dbh, query, label)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/camtool.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/camtool.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/camtool.h	(revision 22157)
@@ -0,0 +1,42 @@
+/*
+ * camtool.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef CAMTOOL_H
+#define CAMTOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    CAMTOOL_MODE_NONE      = 0x0,
+    CAMTOOL_MODE_DEFINEBYQUERY,
+    CAMTOOL_MODE_UPDATERUN,
+    CAMTOOL_MODE_PENDINGEXP,
+    CAMTOOL_MODE_PENDINGIMFILE,
+    CAMTOOL_MODE_ADDPROCESSEDEXP,
+    CAMTOOL_MODE_PROCESSEDEXP,
+    CAMTOOL_MODE_REVERTPROCESSEDEXP,
+    CAMTOOL_MODE_UPDATEPROCESSEDEXP,
+    CAMTOOL_MODE_BLOCK,
+    CAMTOOL_MODE_MASKED,
+    CAMTOOL_MODE_UNBLOCK
+} camtoolMode;
+
+pxConfig *camtoolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // CAMTOOL_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/camtoolConfig.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/camtoolConfig.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/camtoolConfig.c	(revision 22157)
@@ -0,0 +1,311 @@
+/*
+ * camtoolConfig.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <math.h>
+#include <stdint.h>
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "camtool.h"
+
+pxConfig *camtoolConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (!config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    // -definebyquery
+    // XXX need to allow multiple chip_ids
+    // XXX need to allow multiple exp_ids
+    psMetadata *definebydefinebyqueryArgs = psMetadataAlloc();
+    psMetadataAddS64(definebydefinebyqueryArgs, PS_LIST_TAIL, "-chip_id",            0, "search by chip_id", 0);
+    psMetadataAddS64(definebydefinebyqueryArgs, PS_LIST_TAIL, "-exp_id",  	     0, "search by exp_id", 0);
+    psMetadataAddStr(definebydefinebyqueryArgs, PS_LIST_TAIL, "-exp_name",  	     0, "search by exp_name", NULL);
+    psMetadataAddStr(definebydefinebyqueryArgs, PS_LIST_TAIL, "-inst",  	     0, "search for camera", NULL);
+    psMetadataAddStr(definebydefinebyqueryArgs, PS_LIST_TAIL, "-telescope",  	     0, "search for telescope", NULL);
+    psMetadataAddTime(definebydefinebyqueryArgs, PS_LIST_TAIL, "-dateobs_begin",     0, "search for exposures by time (>=)", NULL);
+    psMetadataAddTime(definebydefinebyqueryArgs, PS_LIST_TAIL, "-dateobs_end",       0, "search for exposures by time (<)", NULL);
+    psMetadataAddStr(definebydefinebyqueryArgs, PS_LIST_TAIL, "-exp_tag",            0, "search by exp_tag", NULL);
+    psMetadataAddStr(definebydefinebyqueryArgs, PS_LIST_TAIL, "-exp_type",  	     0, "search by exp_type", NULL);
+    psMetadataAddStr(definebydefinebyqueryArgs, PS_LIST_TAIL, "-filelevel",  	     0, "search by filelevel", NULL);
+    psMetadataAddStr(definebydefinebyqueryArgs, PS_LIST_TAIL, "-reduction",  	     0, "search by reduction class", NULL);
+    psMetadataAddStr(definebydefinebyqueryArgs, PS_LIST_TAIL, "-filter",  	     0, "search for filter", NULL);
+    psMetadataAddF64(definebydefinebyqueryArgs, PS_LIST_TAIL, "-airmass_min",  	     0, "define min airmass", NAN);
+    psMetadataAddF64(definebydefinebyqueryArgs, PS_LIST_TAIL, "-airmass_max",  	     0, "define max airmass", NAN);
+    psMetadataAddF64(definebydefinebyqueryArgs, PS_LIST_TAIL, "-ra_min",  	     0, "define min", NAN);
+    psMetadataAddF64(definebydefinebyqueryArgs, PS_LIST_TAIL, "-ra_max",  	     0, "define max", NAN);
+    psMetadataAddF64(definebydefinebyqueryArgs, PS_LIST_TAIL, "-decl_min",  	     0, "define min", NAN);
+    psMetadataAddF64(definebydefinebyqueryArgs, PS_LIST_TAIL, "-decl_max",  	     0, "define max", NAN);
+    psMetadataAddF32(definebydefinebyqueryArgs, PS_LIST_TAIL, "-exp_time_min",       0, "define min", NAN);
+    psMetadataAddF32(definebydefinebyqueryArgs, PS_LIST_TAIL, "-exp_time_max",       0, "define max", NAN);
+    psMetadataAddF32(definebydefinebyqueryArgs, PS_LIST_TAIL, "-sat_pixel_frac_min", 0, "define max fraction of saturated pixels", NAN);
+    psMetadataAddF32(definebydefinebyqueryArgs, PS_LIST_TAIL, "-sat_pixel_frac_max", 0, "define min fraction of saturated pixels", NAN);
+    psMetadataAddF64(definebydefinebyqueryArgs, PS_LIST_TAIL, "-bg_min",  	     0, "define max", NAN);
+    psMetadataAddF64(definebydefinebyqueryArgs, PS_LIST_TAIL, "-bg_max",  	     0, "define max", NAN);
+    psMetadataAddF64(definebydefinebyqueryArgs, PS_LIST_TAIL, "-bg_stdev_min",       0, "define max", NAN);
+    psMetadataAddF64(definebydefinebyqueryArgs, PS_LIST_TAIL, "-bg_stdev_max",       0, "define max", NAN);
+    psMetadataAddF64(definebydefinebyqueryArgs, PS_LIST_TAIL, "-bg_mean_stdev_min",  0, "define max", NAN);
+    psMetadataAddF64(definebydefinebyqueryArgs, PS_LIST_TAIL, "-bg_mean_stdev_max",  0, "define max", NAN);
+    psMetadataAddF64(definebydefinebyqueryArgs, PS_LIST_TAIL, "-alt_min",            0, "define min", NAN);
+    psMetadataAddF64(definebydefinebyqueryArgs, PS_LIST_TAIL, "-alt_max",  	     0, "define max", NAN);
+    psMetadataAddF64(definebydefinebyqueryArgs, PS_LIST_TAIL, "-az_min",  	     0, "define min", NAN);
+    psMetadataAddF64(definebydefinebyqueryArgs, PS_LIST_TAIL, "-az_max",  	     0, "define max", NAN);
+    psMetadataAddF32(definebydefinebyqueryArgs, PS_LIST_TAIL, "-ccd_temp_min",       0, "define min ccd tempature", NAN);
+    psMetadataAddF32(definebydefinebyqueryArgs, PS_LIST_TAIL, "-ccd_temp_max",       0, "define max ccd tempature", NAN);
+    psMetadataAddF64(definebydefinebyqueryArgs, PS_LIST_TAIL, "-posang_min",  	     0, "define min rotator position angle", NAN);
+    psMetadataAddF64(definebydefinebyqueryArgs, PS_LIST_TAIL, "-posang_max",  	     0, "define max rotator position angle", NAN);
+    psMetadataAddStr(definebydefinebyqueryArgs, PS_LIST_TAIL, "-object",  	     0, "search by exposure object", NULL);
+    psMetadataAddF32(definebydefinebyqueryArgs, PS_LIST_TAIL, "-solang_min",  	     0, "define min solar angle", NAN);
+    psMetadataAddF32(definebydefinebyqueryArgs, PS_LIST_TAIL, "-solang_max",  	     0, "define max solar angle", NAN);
+
+    psMetadataAddStr(definebydefinebyqueryArgs, PS_LIST_TAIL, "-set_workdir",  	     0, "define workdir", NULL);
+    psMetadataAddStr(definebydefinebyqueryArgs, PS_LIST_TAIL, "-set_label",  	     0, "define label", NULL);
+    psMetadataAddStr(definebydefinebyqueryArgs, PS_LIST_TAIL, "-set_reduction",      0, "define reduction class", NULL);
+    psMetadataAddStr(definebydefinebyqueryArgs, PS_LIST_TAIL, "-set_expgroup",       0, "define exposure group", NULL);
+    psMetadataAddStr(definebydefinebyqueryArgs, PS_LIST_TAIL, "-set_dvodb",  	     0, "define DVO db", NULL);
+    psMetadataAddStr(definebydefinebyqueryArgs, PS_LIST_TAIL, "-set_tess_id",  	     0, "define tess ID", NULL);
+    psMetadataAddStr(definebydefinebyqueryArgs, PS_LIST_TAIL, "-set_end_stage",      0, "define end stage", NULL);
+    psMetadataAddBool(definebydefinebyqueryArgs, PS_LIST_TAIL, "-all",  	     0, "allow everything to be queued without search terms", false);
+
+    // -updaterun
+    // XXX need to allow multiple cam_ids
+    // XXX need to allow multiple chip_ids
+    // XXX need to allow multiple exp_ids
+    psMetadata *updaterunArgs = psMetadataAlloc();
+    psMetadataAddS64(updaterunArgs, PS_LIST_TAIL, "-cam_id",             0, "search by cam_id", 0);
+    psMetadataAddS64(updaterunArgs, PS_LIST_TAIL, "-chip_id",  		 0, "search by chip_id", 0);
+    psMetadataAddS64(updaterunArgs, PS_LIST_TAIL, "-exp_id",  		 0, "search by exp_id", 0);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-exp_name",  	 0, "search by exp_name", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-inst",  		 0, "search for camera", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-telescope",  	 0, "search for telescope", NULL);
+    psMetadataAddTime(updaterunArgs, PS_LIST_TAIL, "-dateobs_begin", 	 0, "search for exposures by time (>=)", NULL);
+    psMetadataAddTime(updaterunArgs, PS_LIST_TAIL, "-dateobs_end", 	 0, "search for exposures by time (<)", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-exp_tag",  		 0, "search by exp_tag", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-exp_type",  	 0, "search by exp_type", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-filelevel",  	 0, "search by filelevel", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-reduction",  	 0, "search by reduction class", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-filter",  		 0, "search for filter", NULL);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-airmass_min",  	 0, "define min airmass", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-airmass_max",  	 0, "define max airmass", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-ra_min",  		 0, "define min", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-ra_max",  		 0, "define max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-decl_min",  	 0, "define min", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-decl_max",  	 0, "define max", NAN);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-exp_time_min",  	 0, "define min", NAN);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-exp_time_max",  	 0, "define max", NAN);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-sat_pixel_frac_min", 0, "define max fraction of saturated pixels", NAN);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-sat_pixel_frac_max", 0, "define min fraction of saturated pixels", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-bg_min",  		 0, "define max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-bg_max",  		 0, "define max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-bg_stdev_min",  	 0, "define max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-bg_stdev_max",  	 0, "define max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-bg_mean_stdev_min",  0, "define max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-bg_mean_stdev_max",  0, "define max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-alt_min",  		 0, "define min", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-alt_max",  		 0, "define max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-az_min",  		 0, "define min", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-az_max",  		 0, "define max", NAN);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-ccd_temp_min",  	 0, "define min ccd tempature", NAN);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-ccd_temp_max",  	 0, "define max ccd tempature", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-posang_min",  	 0, "define min rotator position angle", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-posang_max",  	 0, "define max rotator position angle", NAN);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-object",  		 0, "search by exposure object", NULL);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-solang_min",  	 0, "define min solar angle", NAN);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-solang_max",  	 0, "define max solar angle", NAN);
+
+    psMetadataAddBool(updaterunArgs, PS_LIST_TAIL, "-all",               0, "allow everything to be queued without search terms", false);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-state",              0, "set state", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-label",              0, "set label", NULL);
+
+    // -pendingexp
+    psMetadata *pendingexpArgs = psMetadataAlloc();
+    psMetadataAddS64(pendingexpArgs, PS_LIST_TAIL, "-cam_id",            0, "search by camtool ID", 0);
+    psMetadataAddS64(pendingexpArgs, PS_LIST_TAIL, "-chip_id",           0, "search by chiptool ID", 0);
+    psMetadataAddU64(pendingexpArgs, PS_LIST_TAIL, "-limit",             0, "limit result set to N items", 0);
+    psMetadataAddBool(pendingexpArgs, PS_LIST_TAIL, "-simple",           0, "use the simple output format", false);
+
+    // -pendingimfile
+    psMetadata *pendingimfileArgs = psMetadataAlloc();
+    psMetadataAddS64(pendingimfileArgs, PS_LIST_TAIL, "-cam_id", 0,            "search by camtool ID", 0);
+    psMetadataAddS64(pendingimfileArgs, PS_LIST_TAIL, "-chip_id", 0,            "search by chiptool ID", 0);
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-class", 0,            "search by class", NULL);
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-class_id", 0,            "search by class ID", NULL);
+    psMetadataAddBool(pendingimfileArgs, PS_LIST_TAIL, "-simple", 0,            "use the simple output format", false);
+
+    // -addprocessedexp
+    psMetadata *addprocessedexpArgs = psMetadataAlloc();
+    psMetadataAddS64(addprocessedexpArgs, PS_LIST_TAIL, "-cam_id", 0,            "define camtool ID (required)", 0);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-uri", 0,            "define URI (required)", NULL);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-bg", 0,            "define exposure background", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-bg_stdev", 0,            "define exposure background stdev", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-bg_mean_stdev", 0,            "define exposure background mean stdev", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-bias",  0,            "define bias", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-bias_stdev",  0,            "define bias stdev", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-fringe_0",  0,            "define fringe term 0", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-fringe_1",  0,            "define fringe term 1", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-fringe_2",  0,            "define fringe term 2", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-sigma_ra", 0,            "define exposure E ra", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-sigma_dec", 0,            "define exposure E dec", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-ap_resid",  0,            "define aperture residual", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-ap_resid_stdev",  0,            "define aperture residual stdev", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-zp_mean", 0,            "define zero point mean", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-zp_stdev", 0,            "define zero point stdev", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-fwhm_major", 0,            "define FWHM (major axis; arcsec)", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-fwhm_minor", 0,            "define FWHM (minor axis; arcsec)", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-dtime_detrend", 0,            "define elapsed detrend processing time (seconds)", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-dtime_photom", 0,            "define elapsed photometry processing time (seconds)", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-dtime_astrom", 0,            "define elapsed astrometry processing time (seconds)", NAN);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-hostname", 0,            "define hostname", NULL);
+    psMetadataAddS32(addprocessedexpArgs, PS_LIST_TAIL, "-n_stars", 0,            "define number of stars", 0);
+    psMetadataAddS32(addprocessedexpArgs, PS_LIST_TAIL, "-n_extended", 0,            "define number of extended objects", 0);
+    psMetadataAddS32(addprocessedexpArgs, PS_LIST_TAIL, "-n_cr", 0,            "define number of cosmic rays", 0);
+    psMetadataAddS32(addprocessedexpArgs, PS_LIST_TAIL, "-n_astrom", 0,            "define number of astrometry reference objects", 0);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-path_base", 0,            "define base output location", NULL);
+    psMetadataAddS16(addprocessedexpArgs, PS_LIST_TAIL, "-code",  0,            "set fault code", 0);
+    psMetadataAddBool(addprocessedexpArgs, PS_LIST_TAIL, "-faulted",  0,            "only return imfiles with a fault status set", false);
+
+    // -processedexp
+    psMetadata *processedexpArgs = psMetadataAlloc();
+    psMetadataAddS64(processedexpArgs, PS_LIST_TAIL, "-cam_id", 0,            "search by camtool ID", 0);
+    psMetadataAddS64(processedexpArgs, PS_LIST_TAIL, "-chip_id", 0,            "search by chiptool ID", 0);
+    psMetadataAddU64(processedexpArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(processedexpArgs, PS_LIST_TAIL, "-simple", 0,            "use the simple output format", false);
+    psMetadataAddBool(processedexpArgs, PS_LIST_TAIL, "-faulted",  0,            "only return imfiles with a fault status set", false);
+
+    // -revertprocessedexp
+    // XXX need to allow multiple cam_ids
+    // XXX need to allow multiple chip_ids
+    // XXX need to allow multiple exp_ids
+    psMetadata *revertprocessedexpArgs = psMetadataAlloc();
+    psMetadataAddS64(revertprocessedexpArgs, PS_LIST_TAIL, "-cam_id",  0,            "search by cam_id", 0);
+    psMetadataAddS64(revertprocessedexpArgs, PS_LIST_TAIL, "-chip_id",  0,            "search by chip_id", 0);
+    psMetadataAddS64(revertprocessedexpArgs, PS_LIST_TAIL, "-exp_id",  0,            "search by exp_id", 0);
+    psMetadataAddStr(revertprocessedexpArgs, PS_LIST_TAIL, "-exp_name",  0,            "search by exp_name", NULL);
+    psMetadataAddStr(revertprocessedexpArgs, PS_LIST_TAIL, "-inst",  0,            "search for camera", NULL);
+    psMetadataAddStr(revertprocessedexpArgs, PS_LIST_TAIL, "-telescope",  0,            "search for telescope", NULL);
+    psMetadataAddTime(revertprocessedexpArgs, PS_LIST_TAIL, "-dateobs_begin", 0,            "search for exposures by time (>=)", NULL);
+    psMetadataAddTime(revertprocessedexpArgs, PS_LIST_TAIL, "-dateobs_end", 0,            "search for exposures by time (<)", NULL);
+    psMetadataAddStr(revertprocessedexpArgs, PS_LIST_TAIL, "-exp_tag",  0,            "search by exp_tag", NULL);
+    psMetadataAddStr(revertprocessedexpArgs, PS_LIST_TAIL, "-exp_type",  0,            "search by exp_type", NULL);
+    psMetadataAddStr(revertprocessedexpArgs, PS_LIST_TAIL, "-filelevel",  0,            "search by filelevel", NULL);
+    psMetadataAddStr(revertprocessedexpArgs, PS_LIST_TAIL, "-reduction",  0,            "search by reduction class", NULL);
+    psMetadataAddStr(revertprocessedexpArgs, PS_LIST_TAIL, "-filter",  0,            "search for filter", NULL);
+    psMetadataAddF32(revertprocessedexpArgs, PS_LIST_TAIL, "-airmass_min",  0,            "define min airmass", NAN);
+    psMetadataAddF32(revertprocessedexpArgs, PS_LIST_TAIL, "-airmass_max",  0,            "define max airmass", NAN);
+    psMetadataAddF64(revertprocessedexpArgs, PS_LIST_TAIL, "-ra_min",  0,            "define min", NAN);
+    psMetadataAddF64(revertprocessedexpArgs, PS_LIST_TAIL, "-ra_max",  0,            "define max", NAN);
+    psMetadataAddF64(revertprocessedexpArgs, PS_LIST_TAIL, "-decl_min",  0,            "define min", NAN);
+    psMetadataAddF64(revertprocessedexpArgs, PS_LIST_TAIL, "-decl_max",  0,            "define max", NAN);
+    psMetadataAddF32(revertprocessedexpArgs, PS_LIST_TAIL, "-exp_time_min",  0,            "define min", NAN);
+    psMetadataAddF32(revertprocessedexpArgs, PS_LIST_TAIL, "-exp_time_max",  0,            "define max", NAN);
+    psMetadataAddF32(revertprocessedexpArgs, PS_LIST_TAIL, "-sat_pixel_frac_min",  0,            "define max fraction of saturated pixels", NAN);
+    psMetadataAddF32(revertprocessedexpArgs, PS_LIST_TAIL, "-sat_pixel_frac_max",  0,            "define min fraction of saturated pixels", NAN);
+    psMetadataAddF64(revertprocessedexpArgs, PS_LIST_TAIL, "-bg_min",  0,            "define max", NAN);
+    psMetadataAddF64(revertprocessedexpArgs, PS_LIST_TAIL, "-bg_max",  0,            "define max", NAN);
+    psMetadataAddF64(revertprocessedexpArgs, PS_LIST_TAIL, "-bg_stdev_min",  0,            "define max", NAN);
+    psMetadataAddF64(revertprocessedexpArgs, PS_LIST_TAIL, "-bg_stdev_max",  0,            "define max", NAN);
+    psMetadataAddF64(revertprocessedexpArgs, PS_LIST_TAIL, "-bg_mean_stdev_min",  0,            "define max", NAN);
+    psMetadataAddF64(revertprocessedexpArgs, PS_LIST_TAIL, "-bg_mean_stdev_max",  0,            "define max", NAN);
+    psMetadataAddF64(revertprocessedexpArgs, PS_LIST_TAIL, "-alt_min",  0,            "define min", NAN);
+    psMetadataAddF64(revertprocessedexpArgs, PS_LIST_TAIL, "-alt_max",  0,            "define max", NAN);
+    psMetadataAddF64(revertprocessedexpArgs, PS_LIST_TAIL, "-az_min",  0,            "define min", NAN);
+    psMetadataAddF64(revertprocessedexpArgs, PS_LIST_TAIL, "-az_max",  0,            "define max", NAN);
+    psMetadataAddF32(revertprocessedexpArgs, PS_LIST_TAIL, "-ccd_temp_min",  0,            "define min ccd tempature", NAN);
+    psMetadataAddF32(revertprocessedexpArgs, PS_LIST_TAIL, "-ccd_temp_max",  0,            "define max ccd tempature", NAN);
+    psMetadataAddF64(revertprocessedexpArgs, PS_LIST_TAIL, "-posang_min",  0,            "define min rotator position angle", NAN);
+    psMetadataAddF64(revertprocessedexpArgs, PS_LIST_TAIL, "-posang_max",  0,            "define max rotator position angle", NAN);
+    psMetadataAddStr(revertprocessedexpArgs, PS_LIST_TAIL, "-object",  0,            "search by exposure object", NULL);
+    psMetadataAddF32(revertprocessedexpArgs, PS_LIST_TAIL, "-solang_min",  0,            "define min solar angle", NAN);
+    psMetadataAddF32(revertprocessedexpArgs, PS_LIST_TAIL, "-solang_max",  0,            "define max solar angle", NAN);
+
+    psMetadataAddBool(revertprocessedexpArgs, PS_LIST_TAIL, "-all",  0,            "allow everything to be queued without search terms", false);
+    psMetadataAddS16(revertprocessedexpArgs, PS_LIST_TAIL, "-code",  0,            "search by fault code", 0);
+
+
+    // -updateprocessedexp
+    psMetadata *updateprocessedexpArgs = psMetadataAlloc();
+    psMetadataAddS64(updateprocessedexpArgs, PS_LIST_TAIL, "-cam_id", 0,            "search by camtool ID", 0);
+    psMetadataAddS64(updateprocessedexpArgs, PS_LIST_TAIL, "-chip_id",  0,            "search by chiptool ID", 0);
+    psMetadataAddStr(updateprocessedexpArgs, PS_LIST_TAIL, "-class",  0,            "search by class", NULL);
+    psMetadataAddStr(updateprocessedexpArgs, PS_LIST_TAIL, "-class_id",  0,            "search by class ID", NULL);
+    psMetadataAddS16(updateprocessedexpArgs, PS_LIST_TAIL, "-code",  0,            "set fault code (required)", INT16_MAX);
+
+    // -block
+    psMetadata *blockArgs = psMetadataAlloc();
+    psMetadataAddStr(blockArgs, PS_LIST_TAIL, "-label",  0,            "name of a label to mask out (required)", NULL);
+
+    // -masked
+    psMetadata *maskedArgs = psMetadataAlloc();
+    psMetadataAddBool(maskedArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -unblock
+    psMetadata *unblockArgs = psMetadataAlloc();
+    psMetadataAddStr(unblockArgs, PS_LIST_TAIL, "-label",  0,            "name of a label to unmask (required)", NULL);
+
+    psMetadata *argSets = psMetadataAlloc();
+    psMetadata *modes = psMetadataAlloc();
+
+    PXOPT_ADD_MODE("-definebyquery",       "", CAMTOOL_MODE_DEFINEBYQUERY, definebydefinebyqueryArgs);
+    PXOPT_ADD_MODE("-updaterun",           "", CAMTOOL_MODE_UPDATERUN,      updaterunArgs);
+    PXOPT_ADD_MODE("-pendingexp",          "", CAMTOOL_MODE_PENDINGEXP,    pendingexpArgs);
+    PXOPT_ADD_MODE("-pendingimfile",       "", CAMTOOL_MODE_PENDINGIMFILE, pendingimfileArgs);
+    PXOPT_ADD_MODE("-addprocessedexp",     "", CAMTOOL_MODE_ADDPROCESSEDEXP, addprocessedexpArgs);
+    PXOPT_ADD_MODE("-processedexp",        "", CAMTOOL_MODE_PROCESSEDEXP,  processedexpArgs);
+    PXOPT_ADD_MODE("-revertprocessedexp",  "", CAMTOOL_MODE_REVERTPROCESSEDEXP,  revertprocessedexpArgs);
+    PXOPT_ADD_MODE("-updateprocessedexp",  "", CAMTOOL_MODE_UPDATEPROCESSEDEXP,updateprocessedexpArgs);
+    PXOPT_ADD_MODE("-block",               "", CAMTOOL_MODE_BLOCK,         blockArgs);
+    PXOPT_ADD_MODE("-masked",              "", CAMTOOL_MODE_MASKED,        maskedArgs);
+    PXOPT_ADD_MODE("-unblock",             "", CAMTOOL_MODE_UNBLOCK,       unblockArgs);
+
+    if (!pxGetOptions(stderr, argc, argv, config, modes, argSets)) {
+        psError(PS_ERR_UNKNOWN, false, "option parsing failed");
+        psFree(argSets);
+        psFree(modes);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+    psFree(modes);
+
+    // define Database handle, if used
+    config->dbh = psMemIncrRefCounter(pmConfigDB(config->modules));
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    return config;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/chiptool.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/chiptool.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/chiptool.c	(revision 22157)
@@ -0,0 +1,1180 @@
+/*
+ * chiptool.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "pxtools.h"
+#include "pxdata.h"
+
+#include "chiptool.h"
+#include "camtool.h"
+
+static bool definebyqueryMode(pxConfig *config);
+static bool updaterunMode(pxConfig *config);
+static bool pendingimfileMode(pxConfig *config);
+static bool addprocessedimfileMode(pxConfig *config);
+static bool processedimfileMode(pxConfig *config);
+static bool revertprocessedimfileMode(pxConfig *config);
+static bool updateprocessedimfileMode(pxConfig *config);
+static bool blockMode(pxConfig *config);
+static bool maskedMode(pxConfig *config);
+static bool unmaskedMode(pxConfig *config);
+static bool unblockMode(pxConfig *config);
+static bool pendingcleanuprunMode(pxConfig *config);
+static bool pendingcleanupimfileMode(pxConfig *config);
+static bool donecleanupMode(pxConfig *config);
+static bool runMode(pxConfig *config);
+
+static bool chipProcessedCompleteExp(pxConfig *config);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv) {
+    psLibInit(NULL);
+
+    pxConfig *config = chiptoolConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(CHIPTOOL_MODE_DEFINEBYQUERY,           definebyqueryMode);
+        MODECASE(CHIPTOOL_MODE_UPDATERUN,               updaterunMode);
+        MODECASE(CHIPTOOL_MODE_PENDINGIMFILE,           pendingimfileMode);
+        MODECASE(CHIPTOOL_MODE_ADDPROCESSEDIMFILE,      addprocessedimfileMode);
+        MODECASE(CHIPTOOL_MODE_PROCESSEDIMFILE,         processedimfileMode);
+        MODECASE(CHIPTOOL_MODE_REVERTPROCESSEDIMFILE,   revertprocessedimfileMode);
+        MODECASE(CHIPTOOL_MODE_UPDATEPROCESSEDIMFILE,updateprocessedimfileMode);
+        MODECASE(CHIPTOOL_MODE_BLOCK,                   blockMode);
+        MODECASE(CHIPTOOL_MODE_MASKED,                  maskedMode);
+        MODECASE(CHIPTOOL_MODE_UNMASKED,                unmaskedMode);
+        MODECASE(CHIPTOOL_MODE_UNBLOCK,                 unblockMode);
+        MODECASE(CHIPTOOL_MODE_PENDINGCLEANUPRUN,       pendingcleanuprunMode);
+        MODECASE(CHIPTOOL_MODE_PENDINGCLEANUPIMFILE,    pendingcleanupimfileMode);
+        MODECASE(CHIPTOOL_MODE_DONECLEANUP,             donecleanupMode);
+        MODECASE(CHIPTOOL_MODE_RUN,                     runMode);
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+
+static bool definebyqueryMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    PXOPT_LOOKUP_S64(exp_id, config->args, "-exp_id", false, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-exp_id", "exp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_name", "exp_name", "==");
+    PXOPT_COPY_STR(config->args, where, "-inst", "camera", "==");
+    PXOPT_COPY_STR(config->args, where, "-telescope", "telescope", "==");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_begin", "dateobs", ">=");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_end", "dateobs", "<=");
+    PXOPT_COPY_STR(config->args, where, "-exp_tag", "exp_tag", "==");
+    PXOPT_COPY_STR(config->args, where, "-filelevel", "filelevel", "==");
+    PXOPT_COPY_STR(config->args, where, "-reduction", "reduction", "==");
+    PXOPT_COPY_STR(config->args, where, "-filter", "filter", "==");
+    PXOPT_COPY_F64(config->args, where, "-airmass_min", "airmass", ">=");
+    PXOPT_COPY_F64(config->args, where, "-airmass_max", "airmass", "<");
+    PXOPT_COPY_F64(config->args, where, "-ra_min", "ra", ">=");
+    PXOPT_COPY_F64(config->args, where, "-ra_max", "ra", "<");
+    PXOPT_COPY_F64(config->args, where, "-decl_min", "decl", ">=");
+    PXOPT_COPY_F64(config->args, where, "-decl_max", "decl", "<");
+    PXOPT_COPY_F32(config->args, where, "-exp_time_min", "exp_time", ">=");
+    PXOPT_COPY_F32(config->args, where, "-exp_time_max", "exp_time", "<");
+    PXOPT_COPY_F32(config->args, where, "-sat_pixel_frac_min", "sat_pixel_frac", ">=");
+    PXOPT_COPY_F32(config->args, where, "-sat_pixel_frac_max", "sat_pixel_frac", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_min", "bg", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_max", "bg", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_stdev_min", "bg_stdev", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_stdev_max", "bg_stdev", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_mean_stdev_min", "bg_mean_stdev", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_mean_stdev_max", "bg_mean_stdev", "<");
+    PXOPT_COPY_F64(config->args, where, "-alt_min", "alt", ">=");
+    PXOPT_COPY_F64(config->args, where, "-alt_max", "alt", "<");
+    PXOPT_COPY_F64(config->args, where, "-az_min", "az", ">=");
+    PXOPT_COPY_F64(config->args, where, "-az_max", "az", "<");
+    PXOPT_COPY_F32(config->args, where, "-ccd_temp_min", "ccd_temp", ">=");
+    PXOPT_COPY_F32(config->args, where, "-ccd_temp_max", "ccd_temp", "<");
+    PXOPT_COPY_F64(config->args, where, "-posang_min", "posang", ">=");
+    PXOPT_COPY_F64(config->args, where, "-posang_max", "posang", "<");
+    PXOPT_COPY_STR(config->args, where, "-object", "object", "==");
+    PXOPT_COPY_F32(config->args, where, "-solang_min", "solang", ">=");
+    PXOPT_COPY_F32(config->args, where, "-solang_max", "solang", "<");
+    PXOPT_COPY_STR(config->args, where, "-comment", "comment", "LIKE");
+
+    if (!psListLength(where->list)
+        && !psMetadataLookupBool(NULL, config->args, "-all")) {
+        psFree(where);
+        psError(PXTOOLS_ERR_DATA, false, "search parameters are required");
+        return false;
+    }
+
+    PXOPT_LOOKUP_STR(workdir, config->args, "-set_workdir", false, false);
+    PXOPT_LOOKUP_STR(label, config->args, "-set_label", false, false);
+    PXOPT_LOOKUP_STR(reduction, config->args, "-set_reduction", false, false);
+    PXOPT_LOOKUP_STR(expgroup, config->args, "-set_expgroup", false, false);
+    PXOPT_LOOKUP_STR(dvodb, config->args, "-set_dvodb", false, false);
+    PXOPT_LOOKUP_STR(tess_id, config->args, "-set_tess_id", false, false);
+    PXOPT_LOOKUP_STR(end_stage, config->args, "-set_end_stage", false, false);
+
+    // default
+    PXOPT_COPY_STR(config->args, where, "-exp_type", "exp_type", "==");
+    PXOPT_LOOKUP_BOOL(pretend, config->args, "-pretend", false);
+
+    // find the exp_id of all the exposures that we want to queue up.
+    psString query = pxDataGet("chiptool_find_rawexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        psFree(where);
+        return false;
+    }
+
+    if (where && psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "rawExp");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    psFree(where);
+
+    if (pretend) {
+        // then stop before running the query
+        fprintf(stderr, "%s\n", query);
+        psFree(query);
+        return true;
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("chiptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // start a transaction so we don't end up with an exp without any associted
+    // imfiles
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(output);
+        return false;
+    }
+
+    // loop over our list of exp_ids
+    for (long i = 0; i < psArrayLength(output); i++) {
+        psMetadata *md = output->data[i];
+
+        bool status;
+        psS64 exp_id = psMetadataLookupS64(&status, md, "exp_id");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for exp_id");
+            psFree(output);
+            return false;
+        }
+
+        // queue the exp
+        if (!pxchipQueueByExpTag(config, exp_id, workdir, label, reduction, expgroup, dvodb, tess_id, end_stage)) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false,
+                    "failed to trying to queue exp_id: %" PRId64, exp_id);
+            psFree(output);
+            return false;
+        }
+    }
+    psFree(output);
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool updaterunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args,  where, "-chip_id", "chip_id", "==");
+    PXOPT_COPY_S64(config->args,  where, "-exp_id", "exp_id", "==");
+    PXOPT_COPY_STR(config->args,  where, "-exp_name", "exp_name", "==");
+    PXOPT_COPY_STR(config->args,  where, "-inst", "camera", "==");
+    PXOPT_COPY_STR(config->args,  where, "-telescope", "telescope", "==");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_begin", "dateobs", ">=");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_end", "dateobs", "<=");
+    PXOPT_COPY_STR(config->args,  where, "-exp_tag", "exp_tag", "==");
+    PXOPT_COPY_STR(config->args,  where, "-exp_type", "exp_type", "==");
+    PXOPT_COPY_STR(config->args,  where, "-filelevel", "filelevel", "==");
+    PXOPT_COPY_STR(config->args,  where, "-reduction", "reduction", "==");
+    PXOPT_COPY_STR(config->args,  where, "-filter", "filter", "==");
+    PXOPT_COPY_F64(config->args,  where, "-airmass_min", "airmass", ">=");
+    PXOPT_COPY_F64(config->args,  where, "-airmass_max", "airmass", "<");
+    PXOPT_COPY_F64(config->args,  where, "-ra_min", "ra", ">=");
+    PXOPT_COPY_F64(config->args,  where, "-ra_max", "ra", "<");
+    PXOPT_COPY_F64(config->args,  where, "-decl_min", "decl", ">=");
+    PXOPT_COPY_F64(config->args,  where, "-decl_max", "decl", "<");
+    PXOPT_COPY_F32(config->args,  where, "-exp_time_min", "exp_time", ">=");
+    PXOPT_COPY_F32(config->args,  where, "-exp_time_max", "exp_time", "<");
+    PXOPT_COPY_F32(config->args,  where, "-sat_pixel_frac_min", "sat_pixel_frac", ">=");
+    PXOPT_COPY_F32(config->args,  where, "-sat_pixel_frac_max", "sat_pixel_frac", "<");
+    PXOPT_COPY_F64(config->args,  where, "-bg_min", "bg", ">=");
+    PXOPT_COPY_F64(config->args,  where, "-bg_max", "bg", "<");
+    PXOPT_COPY_F64(config->args,  where, "-bg_stdev_min", "bg_stdev", ">=");
+    PXOPT_COPY_F64(config->args,  where, "-bg_stdev_max", "bg_stdev", "<");
+    PXOPT_COPY_F64(config->args,  where, "-bg_mean_stdev_min", "bg_mean_stdev", ">=");
+    PXOPT_COPY_F64(config->args,  where, "-bg_mean_stdev_max", "bg_mean_stdev", "<");
+    PXOPT_COPY_F64(config->args,  where, "-alt_min", "alt", ">=");
+    PXOPT_COPY_F64(config->args,  where, "-alt_max", "alt", "<");
+    PXOPT_COPY_F64(config->args,  where, "-az_min", "az", ">=");
+    PXOPT_COPY_F64(config->args,  where, "-az_max", "az", "<");
+    PXOPT_COPY_F32(config->args,  where, "-ccd_temp_min", "ccd_temp", ">=");
+    PXOPT_COPY_F32(config->args,  where, "-ccd_temp_max", "ccd_temp", "<");
+    PXOPT_COPY_F64(config->args,  where, "-posang_min", "posang", ">=");
+    PXOPT_COPY_F64(config->args,  where, "-posang_max", "posang", "<");
+    PXOPT_COPY_STR(config->args,  where, "-object", "object", "==");
+    PXOPT_COPY_F32(config->args,  where, "-solang_min", "solang", ">=");
+    PXOPT_COPY_F32(config->args,  where, "-solang_max", "solang", "<");
+    PXOPT_COPY_STR(config->args,  where, "-label", "label", "==");
+
+    if (!psListLength(where->list)
+        && !psMetadataLookupBool(NULL, config->args, "-all")) {
+        psFree(where);
+        where = NULL;
+        psError(PXTOOLS_ERR_DATA, false, "search parameters are required");
+        return false;
+    }
+
+    PXOPT_LOOKUP_STR(state, config->args, "-state", false, false);
+    PXOPT_LOOKUP_STR(label, config->args, "-set_label", false, false);
+
+    if ((!state) && (!label)) {
+        psError(PXTOOLS_ERR_DATA, false, "parameters are required");
+        psFree(where);
+        return false;
+    }
+
+    if (state) {
+        // set chipRun.state to state
+        if (!pxchipRunSetStateByQuery(config, where, state)) {
+            psFree(where);
+            return false;
+        }
+    }
+
+    if (label) {
+        // set chipRun.label to label
+        if (!pxchipRunSetLabelByQuery(config, where, label)) {
+            psFree(where);
+            return false;
+        }
+    }
+
+    psFree(where);
+
+    return true;
+}
+
+
+static bool pendingimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-chip_id", "chipRun.chip_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-exp_id", "rawExp.exp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-class_id", "rawImfile.class_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-inst", "rawExp.telescope", "==");
+    PXOPT_COPY_STR(config->args, where, "-filter", "rawExp.filter", "==");
+
+    psString query = pxDataGet("chiptool_pendingimfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (where && psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("chiptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "chipPendingImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool addprocessedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // chip_id, ext_tag, class_id are required
+    PXOPT_LOOKUP_S64(chip_id, config->args, "-chip_id", true, false);
+    PXOPT_LOOKUP_S64(exp_id, config->args, "-exp_id", true, false);
+    PXOPT_LOOKUP_STR(class_id, config->args, "-class_id", true, false);
+
+    // optional
+    PXOPT_LOOKUP_STR(uri, config->args,            "-uri", false, false);
+    PXOPT_LOOKUP_F32(bg, config->args,             "-bg", false, false);
+    PXOPT_LOOKUP_F32(bg_stdev, config->args,       "-bg_stdev", false, false);
+    PXOPT_LOOKUP_F32(bg_mean_stdev, config->args,  "-bg_mean_stdev", false, false);
+    PXOPT_LOOKUP_F32(bias, config->args,           "-bias", false, false);
+    PXOPT_LOOKUP_F32(bias_stdev, config->args,     "-bias_stdev", false, false);
+    PXOPT_LOOKUP_F32(fringe_0, config->args,       "-fringe_0", false, false);
+    PXOPT_LOOKUP_F32(fringe_1, config->args,       "-fringe_1", false, false);
+    PXOPT_LOOKUP_F32(fringe_2, config->args,       "-fringe_2", false, false);
+    PXOPT_LOOKUP_F32(sigma_ra, config->args,       "-sigma_ra", false, false);
+    PXOPT_LOOKUP_F32(sigma_dec, config->args,      "-sigma_dec", false, false);
+    PXOPT_LOOKUP_F32(ap_resid, config->args,       "-ap_resid", false, false);
+    PXOPT_LOOKUP_F32(ap_resid_stdev, config->args, "-ap_resid_stdev", false, false);
+    PXOPT_LOOKUP_F32(zp_mean, config->args,        "-zp_mean", false, false);
+    PXOPT_LOOKUP_F32(zp_stdev, config->args,       "-zp_stdev", false, false);
+    PXOPT_LOOKUP_F32(fwhm_major, config->args,     "-fwhm_major", false, false);
+    PXOPT_LOOKUP_F32(fwhm_minor, config->args,     "-fwhm_minor", false, false);
+    PXOPT_LOOKUP_F32(dtime_detrend, config->args,  "-dtime_detrend", false, false);
+    PXOPT_LOOKUP_F32(dtime_photom,  config->args,  "-dtime_photom",  false, false);
+    PXOPT_LOOKUP_F32(dtime_astrom,  config->args,  "-dtime_astrom",  false, false);
+    PXOPT_LOOKUP_STR(hostname, config->args,       "-hostname", false, false);
+    PXOPT_LOOKUP_F32(n_stars, config->args, "-n_stars", false, false);
+    PXOPT_LOOKUP_F32(n_extended, config->args, "-n_extended", false, false);
+    PXOPT_LOOKUP_F32(n_cr, config->args, "-n_cr", false, false);
+    PXOPT_LOOKUP_F32(n_astrom, config->args, "-n_astrom", false, false);
+    PXOPT_LOOKUP_STR(path_base, config->args, "-path_base", false, false);
+
+    // default values
+    PXOPT_LOOKUP_S16(code, config->args, "-code", false, false);
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!chipProcessedImfileInsert(config->dbh,
+                                   chip_id,
+                                   exp_id,
+                                   class_id,
+                                   uri,
+                                   bg,
+                                   bg_stdev,
+                                   bg_mean_stdev,
+                                   bias,
+                                   bias_stdev,
+                                   fringe_0,
+                                   fringe_1,
+                                   fringe_2,
+                                   sigma_ra,
+                                   sigma_dec,
+                                   ap_resid,
+                                   ap_resid_stdev,
+                                   zp_mean,
+                                   zp_stdev,
+                                   fwhm_major,
+                                   fwhm_minor,
+                                   dtime_detrend,
+                                   dtime_photom,
+                                   dtime_astrom,
+                                   hostname,
+                                   n_stars,
+                                   n_extended,
+                                   n_cr,
+                                   n_astrom,
+                                   path_base,
+                                   code
+            )) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // XXX I've decided to make the transaction cover the Exp migration as
+    // well.  Otherwise, if the last imfile in an exp is moved and the exp
+    // migration fails then the data base is left in a satuation where the exp
+    // migration can't happen.
+
+    if (!chipProcessedCompleteExp(config)) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool processedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_BOOL(faulted, config->args, "-faulted", false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-chip_id", "chipRun.chip_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-exp_id", "rawExp.chip_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-class_id", "rawImfile.class_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-inst", "rawExp.telescope", "==");
+    PXOPT_COPY_STR(config->args, where, "-filter", "rawExp.filter", "==");
+
+    psString query = pxDataGet("chiptool_processedimfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (where && psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "chipProcessedImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "AND chipProcessedImfile.fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "AND chipProcessedImfile.fault = 0");
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("chiptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "chipProcessedImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool revertprocessedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-chip_id", "chip_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-exp_id", "exp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_name", "exp_name", "==");
+    PXOPT_COPY_STR(config->args, where, "-inst", "camera", "==");
+    PXOPT_COPY_STR(config->args, where, "-telescope", "telescope", "==");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_begin", "dateobs", ">=");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_end", "dateobs", "<=");
+    PXOPT_COPY_STR(config->args, where, "-exp_tag", "exp_tag", "==");
+    PXOPT_COPY_STR(config->args, where, "-filelevel", "filelevel", "==");
+    PXOPT_COPY_STR(config->args, where, "-reduction", "reduction", "==");
+    PXOPT_COPY_STR(config->args, where, "-filter", "filter", "==");
+    PXOPT_COPY_F64(config->args, where, "-airmass_min", "airmass", ">=");
+    PXOPT_COPY_F64(config->args, where, "-airmass_max", "airmass", "<");
+    PXOPT_COPY_F64(config->args, where, "-ra_min", "ra", ">=");
+    PXOPT_COPY_F64(config->args, where, "-ra_max", "ra", "<");
+    PXOPT_COPY_F64(config->args, where, "-decl_min", "decl", ">=");
+    PXOPT_COPY_F64(config->args, where, "-decl_max", "decl", "<");
+    PXOPT_COPY_F32(config->args, where, "-exp_time_min", "exp_time", ">=");
+    PXOPT_COPY_F32(config->args, where, "-exp_time_max", "exp_time", "<");
+    PXOPT_COPY_F32(config->args, where, "-sat_pixel_frac_min", "sat_pixel_frac", ">=");
+    PXOPT_COPY_F32(config->args, where, "-sat_pixel_frac_max", "sat_pixel_frac", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_min", "bt", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_max", "bt", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_stdev_min", "bg_stdev", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_stdev_max", "bg_stdev", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_mean_stdev_min", "bg_mean_stdev", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_mean_stdev_max", "bg_mean_stdev", "<");
+    PXOPT_COPY_F64(config->args, where, "-alt_min", "alt", ">=");
+    PXOPT_COPY_F64(config->args, where, "-alt_max", "alt", "<");
+    PXOPT_COPY_F64(config->args, where, "-az_min", "az", ">=");
+    PXOPT_COPY_F64(config->args, where, "-az_max", "az", "<");
+    PXOPT_COPY_F32(config->args, where, "-ccd_temp_min", "ccd_temp", ">=");
+    PXOPT_COPY_F32(config->args, where, "-ccd_temp_max", "ccd_temp", "<");
+    PXOPT_COPY_F64(config->args, where, "-posang_min", "posang", ">=");
+    PXOPT_COPY_F64(config->args, where, "-posang_max", "posang", "<");
+    PXOPT_COPY_STR(config->args, where, "-object", "object", "==");
+    PXOPT_COPY_F32(config->args, where, "-solang_min", "solang", ">=");
+    PXOPT_COPY_F32(config->args, where, "-solang_max", "solang", "<");
+
+    if (!psListLength(where->list)
+        && !psMetadataLookupBool(NULL, config->args, "-all")) {
+        psFree(where);
+        psError(PXTOOLS_ERR_DATA, false, "search parameters are required");
+        return false;
+    }
+
+    psString query = pxDataGet("chiptool_revertprocessedimfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        psFree(where);
+        return false;
+    }
+
+    if (where && psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "chipProcessedImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    return true;
+}
+
+static bool updateprocessedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-chip_id", "chip_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-exp_id",  "exp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-class_id", "class_id", "==");
+
+    PXOPT_LOOKUP_S16(code, config->args, "-code", true, false);
+
+    if (!pxSetFaultCode(config->dbh, "chipProcessedImfile", where, code)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to set set fault flag");
+        return false;
+    }
+    psFree(where);
+
+    return true;
+}
+
+
+static bool blockMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_STR(label, config->args, "-label", true, false);
+
+    if (!chipMaskInsert(config->dbh, label)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool maskedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+
+    psMetadata *where = psMetadataAlloc();
+
+    PXOPT_COPY_STR(config->args, where, "-label", "label", "==");
+
+    if (where->list->n < 1) {
+        psFree(where);
+        where = NULL;
+    }
+
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = psStringCopy("SELECT * FROM chipMask");
+
+    if (where) {
+        psString whereClause = psDBGenerateWhereSQL(where, NULL);
+        psFree(where);
+        psStringAppend(&query, " %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("chiptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "chipMask", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+// return the list of labels which are NOT blocked
+static bool unmaskedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+
+    psMetadata *where = psMetadataAlloc();
+
+    PXOPT_COPY_STR(config->args, where, "-label", "label", "==");
+
+    if (where->list->n < 1) {
+        psFree(where);
+        where = NULL;
+    }
+
+    psString query = pxDataGet("chiptool_unmasked.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+    
+    if (where) {
+        psString whereClause = psDBGenerateWhereSQL(where, "chipUnmask");
+        psFree(where);
+        psStringAppend(&query, " %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("chiptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "chipUnmask", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool unblockMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_STR(label, config->args, "-label", true, false);
+
+    char *query = "DELETE FROM chipMask WHERE label = '%s'";
+
+    if (!p_psDBRunQuery(config->dbh, query, label)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool pendingcleanuprunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-label", "label", "==");
+
+    psString query = pxDataGet("chiptool_pendingcleanuprun.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (where && psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("chiptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "chipPendingCleanupRun", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool pendingcleanupimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    PXOPT_LOOKUP_S64(chip_id, config->args, "-chip_id", false, false);
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psMetadata *where = psMetadataAlloc();
+    if (chip_id) {
+        PXOPT_COPY_S64(config->args, where, "-chip_id", "chip_id", "==");
+    }
+    PXOPT_COPY_STR(config->args, where, "-label", "label", "==");
+
+    psString query = pxDataGet("chiptool_pendingcleanupimfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (where && psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("chiptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "chipPendingCleanupImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool donecleanupMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-label", "label", "==");
+
+    psString query = pxDataGet("chiptool_donecleanup.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (where && psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("chiptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "chipDoneCleanup", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool runMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    PXOPT_LOOKUP_STR(state, config->args, "-state", true, false);
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    // make sure that the state string is valid
+    if (!pxIsValidState(state)) {
+        psError(PXTOOLS_ERR_DATA, false, "%s is not a valid state", state);
+        return false;
+    }
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-label", "label", "==");
+    PXOPT_COPY_STR(config->args, where, "-state", "state", "==");
+
+    psString query = pxDataGet("chiptool_run.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (where && psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereSQL(where, NULL);
+        psStringAppend(&query, " %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("chiptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "chipDoneCleanup", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool chipProcessedCompleteExp(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // look for completed chipPendingExp
+    // migrate them to chipProccessedExp & camPendingExp
+    psString query = pxDataGet("chiptool_completely_processed_exp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("chiptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    for (long i = 0; i < psArrayLength(output); i++) {
+        psMetadata *row = output->data[i];
+
+        chipRunRow *chipRun = chipRunObjectFromMetadata(row);
+        // set chipRun.state to 'stop'
+        if (!pxchipRunSetState(config, chipRun->chip_id, "full")) {
+            psError(PS_ERR_UNKNOWN, false, "failed to change chipRun.state for chip_id: %" PRId64, chipRun->chip_id);
+            psFree(chipRun);
+            psFree(output);
+            return false;
+        }
+
+        // should we stop here or proceed on to the cam stage?
+        // NULL for end_stage means go as far as possible
+        if (chipRun->end_stage && psStrcasestr(chipRun->end_stage, "chip")) {
+            psFree(chipRun);
+            continue;
+        }
+        // else continue on...
+
+        // camQueueChipID() can only be run after chipRun.state has been set to
+        // stop
+        if (!pxcamQueueByChipID(config,
+                    chipRun->chip_id,
+                    chipRun->workdir,
+                    chipRun->label,
+                    chipRun->reduction,
+                    chipRun->expgroup,
+                    chipRun->dvodb,
+                    chipRun->tess_id,
+                    chipRun->end_stage
+        )) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to queue camPendingExp");
+            psFree(chipRun);
+            psFree(output);
+            return false;
+        }
+        psFree(chipRun);
+    }
+
+    psFree(output);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/chiptool.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/chiptool.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/chiptool.h	(revision 22157)
@@ -0,0 +1,47 @@
+/*
+ * chiptool.h
+ *
+ * Copyright (C) 2006-2008  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef CHIPTOOL_H
+#define CHIPTOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    CHIPTOOL_MODE_NONE      = 0x0,
+    CHIPTOOL_MODE_DEFINEBYQUERY,
+    CHIPTOOL_MODE_UPDATERUN,
+    CHIPTOOL_MODE_PENDINGIMFILE,
+    CHIPTOOL_MODE_ADDPROCESSEDIMFILE,
+    CHIPTOOL_MODE_PROCESSEDIMFILE,
+    CHIPTOOL_MODE_REVERTPROCESSEDIMFILE,
+    CHIPTOOL_MODE_UPDATEPROCESSEDIMFILE,
+    CHIPTOOL_MODE_BLOCK,
+    CHIPTOOL_MODE_MASKED,
+    CHIPTOOL_MODE_UNMASKED,
+    CHIPTOOL_MODE_UNBLOCK,
+    CHIPTOOL_MODE_RETRYPROCESSEDIMFILE,
+    CHIPTOOL_MODE_PENDINGCLEANUPRUN,
+    CHIPTOOL_MODE_PENDINGCLEANUPIMFILE,
+    CHIPTOOL_MODE_DONECLEANUP,
+    CHIPTOOL_MODE_RUN
+} chiptoolMode;
+
+pxConfig *chiptoolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // CHIPTOOL_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/chiptoolConfig.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/chiptoolConfig.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/chiptoolConfig.c	(revision 22157)
@@ -0,0 +1,342 @@
+/*
+ * chiptoolConfig.c
+ *
+ * Copyright (C) 2006-2008  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdint.h>
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "chiptool.h"
+
+pxConfig *chiptoolConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (! config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    // -definebyquery
+    psMetadata *definebyqueryArgs = psMetadataAlloc();
+
+    // XXX need to allow multiple exp_ids
+    psMetadataAddS64(definebyqueryArgs, PS_LIST_TAIL, "-exp_id",  0,            "search by exp_id", 0);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-exp_name",  0,            "search by exp_name", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-inst",  0,            "search for camera", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-telescope",  0,            "search for telescope", NULL);
+    psMetadataAddTime(definebyqueryArgs, PS_LIST_TAIL, "-dateobs_begin", 0,            "search for exposures by time (>=)", NULL);
+    psMetadataAddTime(definebyqueryArgs, PS_LIST_TAIL, "-dateobs_end", 0,            "search for exposures by time (<)", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-exp_tag",  0,            "search by exp_tag", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-exp_type",  0,            "search by exp_type", "object");
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-filelevel",  0,            "search by filelevel", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-reduction",  0,            "search by reduction class", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-filter",  0,            "search for filter", NULL);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-airmass_min",  0,            "define min airmass", NAN);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-airmass_max",  0,            "define max airmass", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-ra_min",  0,            "define min", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-ra_max",  0,            "define max", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-decl_min",  0,            "define min", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-decl_max",  0,            "define max", NAN);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-exp_time_min",  0,            "define min", NAN);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-exp_time_max",  0,            "define max", NAN);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-sat_pixel_frac_min",  0,            "define max fraction of saturated pixels", NAN);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-sat_pixel_frac_max",  0,            "define min fraction of saturated pixels", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-bg_min",  0,            "define max", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-bg_max",  0,            "define max", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-bg_stdev_min",  0,            "define max", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-bg_stdev_max",  0,            "define max", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-bg_mean_stdev_min",  0,            "define max", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-bg_mean_stdev_max",  0,            "define max", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-alt_min",  0,            "define min", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-alt_max",  0,            "define max", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-az_min",  0,            "define min", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-az_max",  0,            "define max", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-ccd_temp_min",  0,            "define min ccd tempature", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-ccd_temp_max",  0,            "define max ccd tempature", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-posang_min",  0,            "define min rotator position angle", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-posang_max",  0,            "define max rotator position angle", NAN);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-object",  0,            "search by exposure object", NULL);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-solang_min",  0,            "define min solar angle", NAN);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-solang_max",  0,            "define max solar angle", NAN);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-comment",     0,        "search by comment field (LIKE comparison)", NULL);
+
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-set_workdir",  0,            "define workdir", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-set_label",  0,            "define label", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-set_reduction",  0,            "define reduction class", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-set_expgroup",  0,            "define exposure group", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-set_dvodb",  0,            "define DVO db", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-set_tess_id",  0,            "define tessellation identifier", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-set_end_stage",  0,            "define end stage", NULL);
+    psMetadataAddBool(definebyqueryArgs, PS_LIST_TAIL, "-pretend",  0,            "do not actually modify the database", false);
+    psMetadataAddBool(definebyqueryArgs, PS_LIST_TAIL, "-all",  0,            "allow everything to be queued without search terms", false);
+
+    // -updaterun
+    psMetadata *updaterunArgs = psMetadataAlloc();
+    psMetadataAddS64(updaterunArgs, PS_LIST_TAIL, "-chip_id",              0,            "search by chip ID", 0);
+    psMetadataAddS64(updaterunArgs, PS_LIST_TAIL, "-exp_id",                 0,            "search by exp_id", 0);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-exp_name",             0,            "search by exp_name", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-inst",                 0,            "search for camera", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-telescope",            0,            "search for telescope", NULL);
+    psMetadataAddTime(updaterunArgs, PS_LIST_TAIL, "-dateobs_begin",       0,            "search for exposures by time (>=)", NULL);
+    psMetadataAddTime(updaterunArgs, PS_LIST_TAIL, "-dateobs_end",         0,            "search for exposures by time (<)", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-exp_tag",                 0,            "search by exp_tag", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-exp_type",             0,            "search by exp_type", "object");
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-filelevel",            0,            "search by filelevel", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-reduction",            0,            "search by reduction class", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-filter",                 0,            "search by filter", NULL);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-airmass_min",          0,            "search by min airmass", NAN);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-airmass_max",          0,            "search by max airmass", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-ra_min",                 0,            "search by min", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-ra_max",                 0,            "search by max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-decl_min",             0,            "search by min", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-decl_max",             0,            "search by max", NAN);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-exp_time_min",         0,            "search by min", NAN);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-exp_time_max",         0,            "search by max", NAN);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-sat_pixel_frac_min",  0,            "search by max fraction of saturated pixels", NAN);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-sat_pixel_frac_max",  0,            "search by min fraction of saturated pixels", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-bg_min",            0,            "search by max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-bg_max",            0,            "search by max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-bg_stdev_min",        0,            "search by max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-bg_stdev_max",        0,            "search by max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-bg_mean_stdev_min",   0,            "search by max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-bg_mean_stdev_max",   0,            "search by max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-alt_min",            0,            "search by min", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-alt_max",            0,            "search by max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-az_min",            0,            "search by min", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-az_max",            0,            "search by max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-ccd_temp_min",        0,            "search by min ccd tempature", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-ccd_temp_max",        0,            "search by max ccd tempature", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-posang_min",        0,            "search by min rotator position angle", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-posang_max",        0,            "search by max rotator position angle", NAN);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-object",            0,            "search by exposure object", NULL);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-solang_min",        0,            "search by min solar angle", NAN);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-solang_max",        0,            "search by max solar angle", NAN);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-label",               0,            "search by label", NULL);
+
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-state", 0,            "set state", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-set_label", 0,        "set label", NULL);
+    psMetadataAddBool(updaterunArgs, PS_LIST_TAIL, "-all",  0,            "allow everything to be queued without search terms", false);
+
+
+    // -pendingimfile
+    psMetadata *pendingimfileArgs = psMetadataAlloc();
+    psMetadataAddS64(pendingimfileArgs, PS_LIST_TAIL, "-chip_id",  0,            "search by chip ID", 0);
+    psMetadataAddS64(pendingimfileArgs, PS_LIST_TAIL, "-exp_id",  0,            "search by exposure ID", 0);
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-class_id",  0,            "search by class ID", NULL);
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-inst",  0,            "search by camera of interest", NULL);
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-filter",  0,            "search by filter of interest", NULL);
+    psMetadataAddU64(pendingimfileArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(pendingimfileArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -addprocessedimfile
+    psMetadata *addprocessedimfileArgs = psMetadataAlloc();
+    psMetadataAddS64(addprocessedimfileArgs, PS_LIST_TAIL, "-chip_id",  0,            "define chip ID (required)", 0);
+    psMetadataAddS64(addprocessedimfileArgs, PS_LIST_TAIL, "-exp_id",  0,            "define exposure ID (required)", 0);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-class_id",  0,            "define class ID (required)", NULL);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-uri",  0,            "define URL", NULL);
+    psMetadataAddF32(addprocessedimfileArgs, PS_LIST_TAIL, "-bg",  0,            "define exposure background", NAN);
+    psMetadataAddF32(addprocessedimfileArgs, PS_LIST_TAIL, "-bg_stdev",  0,            "define exposure background stdev", NAN);
+    psMetadataAddF32(addprocessedimfileArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,            "define exposure background mean stdev", NAN);
+    psMetadataAddF32(addprocessedimfileArgs, PS_LIST_TAIL, "-bias",  0,            "define bias", NAN);
+    psMetadataAddF32(addprocessedimfileArgs, PS_LIST_TAIL, "-bias_stdev",  0,            "define bias stdev", NAN);
+    psMetadataAddF32(addprocessedimfileArgs, PS_LIST_TAIL, "-fringe_0",  0,            "define fringe term 0", NAN);
+    psMetadataAddF32(addprocessedimfileArgs, PS_LIST_TAIL, "-fringe_1",  0,            "define fringe term 1", NAN);
+    psMetadataAddF32(addprocessedimfileArgs, PS_LIST_TAIL, "-fringe_2",  0,            "define fringe term 2", NAN);
+    psMetadataAddF32(addprocessedimfileArgs, PS_LIST_TAIL, "-sigma_ra",  0,            "define error in RA", NAN);
+    psMetadataAddF32(addprocessedimfileArgs, PS_LIST_TAIL, "-sigma_dec",  0,            "define error in DEC", NAN);
+    psMetadataAddF32(addprocessedimfileArgs, PS_LIST_TAIL, "-ap_resid",  0,            "define aperture residual", NAN);
+    psMetadataAddF32(addprocessedimfileArgs, PS_LIST_TAIL, "-ap_resid_stdev",  0,            "define aperture residual stdev", NAN);
+    psMetadataAddF32(addprocessedimfileArgs, PS_LIST_TAIL, "-zp_mean",  0,            "define zero point", NAN);
+    psMetadataAddF32(addprocessedimfileArgs, PS_LIST_TAIL, "-zp_stdev",  0,            "define zero point stdev", NAN);
+    psMetadataAddF32(addprocessedimfileArgs, PS_LIST_TAIL, "-fwhm_major",  0,            "define fwhm (major axis; arcsec)", NAN);
+    psMetadataAddF32(addprocessedimfileArgs, PS_LIST_TAIL, "-fwhm_minor",  0,            "define fwhm (minor axis; arcsec)", NAN);
+    psMetadataAddF32(addprocessedimfileArgs, PS_LIST_TAIL, "-dtime_detrend",  0,            "define elapsed time for detrend (seconds)", NAN);
+    psMetadataAddF32(addprocessedimfileArgs, PS_LIST_TAIL, "-dtime_photom",  0,            "define elapsed time for photometry (seconds)", NAN);
+    psMetadataAddF32(addprocessedimfileArgs, PS_LIST_TAIL, "-dtime_astrom",  0,            "define elapsed time for astrometry (seconds)", NAN);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-hostname",  0,            "define hostname", NULL);
+    psMetadataAddS32(addprocessedimfileArgs, PS_LIST_TAIL, "-n_stars",  0,            "define number of stars in image", 0);
+    psMetadataAddS32(addprocessedimfileArgs, PS_LIST_TAIL, "-n_extended",  0,            "define number of extended objects in image", 0);
+    psMetadataAddS32(addprocessedimfileArgs, PS_LIST_TAIL, "-n_cr",  0,            "define number of cosmic rays in image", 0);
+    psMetadataAddS32(addprocessedimfileArgs, PS_LIST_TAIL, "-n_astrom",  0,            "define number of astrometry stars used", 0);
+
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-path_base",  0,            "define base output location", NULL);
+    psMetadataAddS16(addprocessedimfileArgs, PS_LIST_TAIL, "-code",  0,            "set fault code", 0);
+
+    // -processedimfile
+    psMetadata *processedimfileArgs = psMetadataAlloc();
+    psMetadataAddS64(processedimfileArgs, PS_LIST_TAIL, "-chip_id",  0,            "define chip ID", 0);
+    psMetadataAddS64(processedimfileArgs, PS_LIST_TAIL, "-exp_id",  0,            "define exposure ID", 0);
+    psMetadataAddStr(processedimfileArgs, PS_LIST_TAIL, "-class_id",  0,            "define class ID", NULL);
+    psMetadataAddStr(processedimfileArgs, PS_LIST_TAIL, "-inst",  0,            "define camera of interest", NULL);
+    psMetadataAddStr(processedimfileArgs, PS_LIST_TAIL, "-filter",  0,            "define filter of interest", NULL);
+    psMetadataAddU64(processedimfileArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(processedimfileArgs, PS_LIST_TAIL, "-faulted",  0,            "only return imfiles with a fault status set", false);
+    psMetadataAddBool(processedimfileArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -updateprocessedimfile
+    psMetadata *updateprocessedimfileArgs = psMetadataAlloc();
+    psMetadataAddS64(updateprocessedimfileArgs, PS_LIST_TAIL, "-chip_id",  0,            "search by chip ID", 0);
+    psMetadataAddS64(updateprocessedimfileArgs, PS_LIST_TAIL, "-exp_id",  0,            "search by exposure ID", 0);
+    psMetadataAddStr(updateprocessedimfileArgs, PS_LIST_TAIL, "-class_id",  0,            "search by class ID", NULL);
+    psMetadataAddS16(updateprocessedimfileArgs, PS_LIST_TAIL, "-code",  0,            "set fault code (required)", 0);
+
+    // -revertprocessedimfile
+    psMetadata *revertprocessedimfileArgs = psMetadataAlloc();
+    psMetadataAddS64(revertprocessedimfileArgs, PS_LIST_TAIL, "-chip_id", 0,            "search by chip ID", 0);
+    psMetadataAddS64(revertprocessedimfileArgs, PS_LIST_TAIL, "-exp_id",  0,            "search by exp_id", 0);
+    psMetadataAddStr(revertprocessedimfileArgs, PS_LIST_TAIL, "-exp_name",  0,            "search by exp_name", NULL);
+    psMetadataAddStr(revertprocessedimfileArgs, PS_LIST_TAIL, "-inst",  0,            "search for camera", NULL);
+    psMetadataAddStr(revertprocessedimfileArgs, PS_LIST_TAIL, "-telescope",  0,            "search for telescope", NULL);
+    psMetadataAddTime(revertprocessedimfileArgs, PS_LIST_TAIL, "-dateobs_begin", 0,            "search for exposures by time (>=)", NULL);
+    psMetadataAddTime(revertprocessedimfileArgs, PS_LIST_TAIL, "-dateobs_end", 0,            "search for exposures by time (<)", NULL);
+    psMetadataAddStr(revertprocessedimfileArgs, PS_LIST_TAIL, "-exp_tag",  0,            "search by exp_tag", NULL);
+    psMetadataAddStr(revertprocessedimfileArgs, PS_LIST_TAIL, "-exp_type",  0,            "search by exp_type", "object");
+    psMetadataAddStr(revertprocessedimfileArgs, PS_LIST_TAIL, "-filelevel",  0,            "search by filelevel", NULL);
+    psMetadataAddStr(revertprocessedimfileArgs, PS_LIST_TAIL, "-reduction",  0,            "search by reduction class", NULL);
+    psMetadataAddStr(revertprocessedimfileArgs, PS_LIST_TAIL, "-filter",  0,            "search for filter", NULL);
+    psMetadataAddF32(revertprocessedimfileArgs, PS_LIST_TAIL, "-airmass_min",  0,            "define min airmass", NAN);
+    psMetadataAddF32(revertprocessedimfileArgs, PS_LIST_TAIL, "-airmass_max",  0,            "define max airmass", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-ra_min",  0,            "define min", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-ra_max",  0,            "define max", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-decl_min",  0,            "define min", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-decl_max",  0,            "define max", NAN);
+    psMetadataAddF32(revertprocessedimfileArgs, PS_LIST_TAIL, "-exp_time_min",  0,            "define min", NAN);
+    psMetadataAddF32(revertprocessedimfileArgs, PS_LIST_TAIL, "-exp_time_max",  0,            "define max", NAN);
+    psMetadataAddF32(revertprocessedimfileArgs, PS_LIST_TAIL, "-sat_pixel_frac_min",  0,            "define max fraction of saturated pixels", NAN);
+    psMetadataAddF32(revertprocessedimfileArgs, PS_LIST_TAIL, "-sat_pixel_frac_max",  0,            "define min fraction of saturated pixels", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-bg_min",  0,            "define max", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-bg_max",  0,            "define max", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-bg_stdev_min",  0,            "define max", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-bg_stdev_max",  0,            "define max", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-bg_mean_stdev_min",  0,            "define max", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-bg_mean_stdev_max",  0,            "define max", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-alt_min",  0,            "define min", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-alt_max",  0,            "define max", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-az_min",  0,            "define min", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-az_max",  0,            "define max", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-ccd_temp_min",0,            "define min ccd tempature", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-ccd_temp_max",0,            "define max ccd tempature", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-posang_min",  0,            "define min rotator position angle", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-posang_max",  0,            "define max rotator position angle", NAN);
+    psMetadataAddStr(revertprocessedimfileArgs, PS_LIST_TAIL, "-object",  0,            "search by exposure object", NULL);
+    psMetadataAddF32(revertprocessedimfileArgs, PS_LIST_TAIL, "-solang_min",  0,            "define min solar angle", NAN);
+    psMetadataAddF32(revertprocessedimfileArgs, PS_LIST_TAIL, "-solang_max",  0,            "define max solar angle", NAN);
+
+    psMetadataAddBool(revertprocessedimfileArgs, PS_LIST_TAIL, "-all",  0,            "allow everything to be queued without search terms", false);
+    psMetadataAddS16(revertprocessedimfileArgs, PS_LIST_TAIL, "-code",  0,            "search by fault code", 0);
+
+    // -block
+    psMetadata *blockArgs = psMetadataAlloc();
+    psMetadataAddStr(blockArgs, PS_LIST_TAIL, "-label",  0,            "name of a label to mask out (required)", NULL);
+
+    // -masked
+    psMetadata *maskedArgs = psMetadataAlloc();
+    psMetadataAddStr(maskedArgs, PS_LIST_TAIL, "-label",  0,            "list blocks for specified label", NULL);
+    psMetadataAddBool(maskedArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+    psMetadataAddU64(maskedArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+
+    // -unmasked
+    psMetadata *unmaskedArgs = psMetadataAlloc();
+    psMetadataAddStr(unmaskedArgs, PS_LIST_TAIL, "-label",  0,            "restrict to specified label", NULL);
+    psMetadataAddBool(unmaskedArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+    psMetadataAddU64(unmaskedArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+
+    // -unblock
+    psMetadata *unblockArgs = psMetadataAlloc();
+    psMetadataAddStr(unblockArgs, PS_LIST_TAIL, "-label",  0,            "name of a label to unmask (required)", NULL);
+
+    // -pendingcleanuprun
+    psMetadata *pendingcleanuprunArgs = psMetadataAlloc();
+    psMetadataAddStr(pendingcleanuprunArgs, PS_LIST_TAIL, "-label",  0,            "list blocks for specified label", NULL);
+    psMetadataAddBool(pendingcleanuprunArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+    psMetadataAddU64(pendingcleanuprunArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+
+    // -pendingcleanupifmile
+    psMetadata *pendingcleanupimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(pendingcleanupimfileArgs, PS_LIST_TAIL, "-label",  0,            "list blocks for specified label", NULL);
+    psMetadataAddS64(pendingcleanupimfileArgs, PS_LIST_TAIL, "-chip_id", 0,
+        "search by chip ID", 0);
+    psMetadataAddStr(pendingcleanupimfileArgs, PS_LIST_TAIL, "-exp_id",                 0,            "search by exp_id", NULL);
+    psMetadataAddBool(pendingcleanupimfileArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+    psMetadataAddU64(pendingcleanupimfileArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+
+    // -doneclenaup
+    psMetadata *donecleanupArgs = psMetadataAlloc();
+    psMetadataAddStr(donecleanupArgs, PS_LIST_TAIL, "-label",  0,            "list blocks for specified label", NULL);
+    psMetadataAddBool(donecleanupArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+    psMetadataAddU64(donecleanupArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+
+    // -run
+    psMetadata *runArgs = psMetadataAlloc();
+    psMetadataAddStr(runArgs, PS_LIST_TAIL, "-label",  0,       "search by label", NULL);
+    psMetadataAddBool(runArgs, PS_LIST_TAIL, "-simple",  0,     "use the simple output format", false);
+    psMetadataAddU64(runArgs, PS_LIST_TAIL, "-limit",  0,       "limit result set to N items", 0);
+    psMetadataAddStr(runArgs, PS_LIST_TAIL, "-state", 0,        "search by state (required)", NULL);
+
+    psMetadata *argSets = psMetadataAlloc();
+    psMetadata *modes = psMetadataAlloc();
+
+    PXOPT_ADD_MODE("-definebyquery",        "create runs from raw exposures",       CHIPTOOL_MODE_DEFINEBYQUERY,        definebyqueryArgs);
+    PXOPT_ADD_MODE("-updaterun",            "change chip run properties",           CHIPTOOL_MODE_UPDATERUN,            updaterunArgs);
+    PXOPT_ADD_MODE("-pendingimfile",        "show pending imfiles",        	    CHIPTOOL_MODE_PENDINGIMFILE,        pendingimfileArgs);
+    PXOPT_ADD_MODE("-addprocessedimfile",   "add a processed imfile",      	    CHIPTOOL_MODE_ADDPROCESSEDIMFILE,   addprocessedimfileArgs);
+    PXOPT_ADD_MODE("-processedimfile",      "show processed imfiles",      	    CHIPTOOL_MODE_PROCESSEDIMFILE,      processedimfileArgs);
+    PXOPT_ADD_MODE("-updateprocessedimfile","change procesed imfile properties",    CHIPTOOL_MODE_UPDATEPROCESSEDIMFILE,updateprocessedimfileArgs);
+    PXOPT_ADD_MODE("-revertprocessedimfile","undo a processed imfile",     	    CHIPTOOL_MODE_REVERTPROCESSEDIMFILE,revertprocessedimfileArgs);
+    PXOPT_ADD_MODE("-block",                "set a label block",           	    CHIPTOOL_MODE_BLOCK,                blockArgs);
+    PXOPT_ADD_MODE("-masked",               "show blocked labels",         	    CHIPTOOL_MODE_MASKED,               maskedArgs);
+    PXOPT_ADD_MODE("-unmasked",             "",                            	    CHIPTOOL_MODE_UNMASKED,             unmaskedArgs);
+    PXOPT_ADD_MODE("-unblock",              "remove a label block",        	    CHIPTOOL_MODE_UNBLOCK,              unblockArgs);
+    PXOPT_ADD_MODE("-pendingcleanuprun",    "show runs that need to be cleaned up", CHIPTOOL_MODE_PENDINGCLEANUPRUN,    pendingcleanuprunArgs);
+    PXOPT_ADD_MODE("-pendingcleanupimfile", "show runs that need to be cleaned up", CHIPTOOL_MODE_PENDINGCLEANUPIMFILE, pendingcleanupimfileArgs);
+    PXOPT_ADD_MODE("-donecleanup",          "show runs that have been cleaned",     CHIPTOOL_MODE_DONECLEANUP,          donecleanupArgs);
+    PXOPT_ADD_MODE("-run",                  "show runs",                            CHIPTOOL_MODE_RUN,                  runArgs);
+
+    if (!pxGetOptions(stderr, argc, argv, config, modes, argSets)) {
+        psError(PS_ERR_UNKNOWN, true, "option parsing failed");
+        psFree(argSets);
+        psFree(modes);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+    psFree(modes);
+
+    // define Database handle, if used
+    config->dbh = psMemIncrRefCounter(pmConfigDB(config->modules));
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    return config;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/detselect.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/detselect.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/detselect.c	(revision 22157)
@@ -0,0 +1,220 @@
+/*
+ * detselect.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#include "pxtools.h"
+#include "detselect.h"
+
+static bool searchMode(pxConfig *config);
+static bool selectMode(pxConfig *config);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = detselectConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(DETSELECT_MODE_SEARCH,        searchMode);
+        MODECASE(DETSELECT_MODE_SELECT,        selectMode);
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+static bool searchMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_BOOL(unlimit, config->args, "-unlimit", false);
+
+    PXOPT_LOOKUP_TIME(time, config->args, "-time", false, false);
+
+    psMetadata *where = psMetadataAlloc();
+
+    PXOPT_COPY_STR(config->args, where, "-inst",      "camera", "==");
+    PXOPT_COPY_STR(config->args, where, "-telescope", "telescope", "==");
+    PXOPT_COPY_STR(config->args, where, "-det_type",  "det_type", "==");
+    PXOPT_COPY_STR(config->args, where, "-type",      "det_type", "==");
+    PXOPT_COPY_STR(config->args, where, "-filter",    "filter", "==");
+
+    // airmass_min  < airmass  < airmass_max
+    PXOPT_COPY_F32(config->args, where, "-airmass", "airmass_min", "<=");
+    PXOPT_COPY_F32(config->args, where, "-airmass", "airmass_max", ">=");
+
+    // exp_time_min < exp_time < exp_time_max
+    PXOPT_COPY_F32(config->args, where, "-exp_time", "exp_time_min", "<=");
+    PXOPT_COPY_F32(config->args, where, "-exp_time", "exp_time_max", ">=");
+
+    // ccd_temp_min < ccd_temp < ccd_temp_max
+    PXOPT_COPY_F32(config->args, where, "-ccd_temp", "ccd_temp_min", "<=");
+    PXOPT_COPY_F32(config->args, where, "-ccd_temp", "ccd_temp_max", ">=");
+
+    PXOPT_COPY_F64(config->args, where, "-posang", "posang_min", "<=");
+    PXOPT_COPY_F64(config->args, where, "-posang", "posang_max", ">=");
+
+    // time_begin    < time     < time_end
+    // the == NULL tests invokes some psDB magic to make an OR
+    // conditional query
+    psMetadataAddTime(where, PS_LIST_TAIL, "time_begin", 0, "<=", time);
+    psMetadataAddTime(where, PS_LIST_TAIL, "time_begin", PS_META_DUPLICATE_OK, "==", NULL);
+    psMetadataAddTime(where, PS_LIST_TAIL, "time_end", 0, ">=", time);
+    psMetadataAddTime(where, PS_LIST_TAIL, "time_end", PS_META_DUPLICATE_OK, "==", NULL);
+
+    psString query = pxDataGet("detselect_search.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // use psDBGenerateWhereConditionalSQL with AND ... because the SQL ends in a WHERE
+    if (where && psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+	psStringAppend(&query, " AND %s", whereClause);
+	psFree(whereClause);
+    }
+    psFree(where);
+
+    // we choose the single detrend image which matches all criteria and has
+    // the latest insertion date
+
+    // unless explicitly specified by the user, list all possible matches
+    if (!unlimit) {
+        psStringAppend(&query, " ORDER BY registered DESC LIMIT 1");
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        // XXX check psError here
+        psError(PS_ERR_UNKNOWN, false, "no detrend exposures found");
+        psFree(output);
+        return true;
+    }
+
+    // negate simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool selectMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_id",    "det_id", "==");
+    PXOPT_COPY_S32(config->args, where, "-iteration", "iteration", "==");
+    PXOPT_COPY_STR(config->args, where, "-class_id",  "class_id", "==");
+
+    psString query = pxDataGet("detselect_select.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // use psDBGenerateWhereConditionalSQL with AND ... because the SQL ends in a WHERE
+    if (where && psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        // XXX check psError here
+        psError(PS_ERR_UNKNOWN, false, "no detNormalizedImfile rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detNormalizedImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/detselect.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/detselect.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/detselect.h	(revision 22157)
@@ -0,0 +1,33 @@
+/*
+ * detselect.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef DETSELECT_H
+#define DETSELECT_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    DETSELECT_MODE_NONE      = 0x0,
+    DETSELECT_MODE_SEARCH,
+    DETSELECT_MODE_SELECT,
+} detselectMode;
+
+pxConfig *detselectConfig(pxConfig *config, int argc, char **argv);
+
+#endif // DETSELECT_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/detselectConfig.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/detselectConfig.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/detselectConfig.c	(revision 22157)
@@ -0,0 +1,94 @@
+/*
+ * detselectConfig.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <psmodules.h>
+#include <math.h>
+
+#include "pxtools.h"
+#include "detselect.h"
+
+pxConfig *detselectConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (!config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    // -search
+    psMetadata *searchArgs = psMetadataAlloc();
+    psMetadataAddStr(searchArgs, PS_LIST_TAIL, "-inst", 0,                     "search by camera", NULL);
+    psMetadataAddStr(searchArgs, PS_LIST_TAIL, "-telescope", 0,                     "search by telescope", NULL);
+    psMetadataAddStr(searchArgs, PS_LIST_TAIL, "-det_type", 0,                     "search by detrend type", NULL);
+    psMetadataAddStr(searchArgs, PS_LIST_TAIL, "-type", 0,                     "search by detrend type (alias for -det_type)", NULL);
+    psMetadataAddStr(searchArgs, PS_LIST_TAIL, "-filter", 0,                     "search by filter", NULL);
+    psMetadataAddF32(searchArgs, PS_LIST_TAIL, "-airmass", 0,                     "define airmass", NAN);
+    psMetadataAddF32(searchArgs, PS_LIST_TAIL, "-exp_time", 0,                     "search by exposure time", NAN);
+    psMetadataAddF32(searchArgs, PS_LIST_TAIL, "-ccd_temp", 0,                     "search by ccd tempature", NAN);
+    psMetadataAddF64(searchArgs, PS_LIST_TAIL, "-posang", 0,                     "search by rotator position angle", NAN);
+    psMetadataAddTime(searchArgs, PS_LIST_TAIL, "-time", 0,                      "define time for desired detrend data", NULL);
+    psMetadataAddBool(searchArgs, PS_LIST_TAIL, "-simple",  0,                      "use the simple output format", false);
+    psMetadataAddBool(searchArgs, PS_LIST_TAIL, "-unlimit",  0,                      "list all possible detruns, not just the best match", false);
+ 
+    // -select
+    psMetadata *selectArgs = psMetadataAlloc();
+    psMetadataAddStr(selectArgs, PS_LIST_TAIL, "-det_id", 0,                     "search by detrend ID", NULL);
+    psMetadataAddS32(selectArgs, PS_LIST_TAIL, "-iteration", 0,                     "search by iteration number", 0);
+    psMetadataAddStr(selectArgs, PS_LIST_TAIL, "-class_id", 0,                     "search by class ID", NULL);
+    psMetadataAddBool(selectArgs, PS_LIST_TAIL, "-simple",  0,                      "use the simple output format", false);
+    
+    psMetadata *argSets = psMetadataAlloc();
+    psMetadata *modes = psMetadataAlloc();
+
+    PXOPT_ADD_MODE("-search",  "search for an appropriate detrend", DETSELECT_MODE_SEARCH, searchArgs);
+    PXOPT_ADD_MODE("-select",  "retreive detrend information", DETSELECT_MODE_SELECT, selectArgs);
+
+    if (!pxGetOptions(stderr, argc, argv, config, modes, argSets)) {
+        psError(PS_ERR_UNKNOWN, true, "option parsing failed");
+        psFree(argSets);
+        psFree(modes);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+    psFree(modes);
+
+    // define Database handle, if used
+    config->dbh = psMemIncrRefCounter(pmConfigDB(config->modules));
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    return config;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/dettool.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/dettool.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/dettool.c	(revision 22157)
@@ -0,0 +1,4215 @@
+/*
+ * dettool.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "dettool.h"
+
+static bool pendingMode(pxConfig *config);
+static bool definebytagMode(pxConfig *config);
+static bool definebyqueryMode(pxConfig *config);
+static bool definebydetrunMode(pxConfig *config);
+static bool runsMode(pxConfig *config);
+static bool childlessrunMode(pxConfig *config);
+static bool inputMode(pxConfig *config);
+static bool rawMode(pxConfig *config);
+// processedimfile
+static bool toprocessedimfileMode(pxConfig *config);
+static bool addprocessedimfileMode(pxConfig *config);
+static bool processedimfileMode(pxConfig *config);
+static bool revertprocessedimfileMode(pxConfig *config);
+// processedexp
+static bool toprocessedexpMode(pxConfig *config);
+static bool addprocessedexpMode(pxConfig *config);
+static bool processedexpMode(pxConfig *config);
+static bool revertprocessedexpMode(pxConfig *config);
+// stackedimfile
+static bool tostackedMode(pxConfig *config);
+static bool addstackedMode(pxConfig *config);
+static bool stackedMode(pxConfig *config);
+static bool revertstackedMode(pxConfig *config);
+// normalizedstat
+static bool tonormalizedstatMode(pxConfig *config);
+static bool addnormalizedstatMode(pxConfig *config);
+static bool normalizedstatMode(pxConfig *config);
+static bool revertnormalizedstatMode(pxConfig *config);
+// normalizedimfile
+static bool tonormalizeMode(pxConfig *config);
+static bool addnormalizedimfileMode(pxConfig *config);
+static bool normalizedimfileMode(pxConfig *config);
+static bool revertnormalizedimfileMode(pxConfig *config);
+// normalizedexp
+static bool tonormalizedexpMode(pxConfig *config);
+static bool addnormalizedexpMode(pxConfig *config);
+static bool normalizedexpMode(pxConfig *config);
+static bool revertnormalizedexpMode(pxConfig *config);
+// residimfile
+static bool toresidimfileMode(pxConfig *config);
+static bool addresidimfileMode(pxConfig *config);
+static bool residimfileMode(pxConfig *config);
+static bool revertresidimfileMode(pxConfig *config);
+// residexp
+static bool toresidexpMode(pxConfig *config);
+static bool addresidexpMode(pxConfig *config);
+static bool residexpMode(pxConfig *config);
+static bool revertresidexpMode(pxConfig *config);
+static bool updateresidexpMode(pxConfig *config);
+// detrunsummary
+static bool todetrunsummaryMode(pxConfig *config);
+static bool adddetrunsummaryMode(pxConfig *config);
+static bool detrunsummaryMode(pxConfig *config);
+static bool revertdetrunsummaryMode(pxConfig *config);
+static bool updatedetrunsummaryMode(pxConfig *config);
+// run
+static bool updatedetrunMode(pxConfig *config);
+static bool rerunMode(pxConfig *config);
+// register
+static bool register_detrendMode(pxConfig *config);
+
+static detRunSummaryRow *mdToDetRunSummary(pxConfig *config, psMetadata *row);
+//static psArray *validDetInputClassIds(pxConfig *config, const char *det_id);
+//static psArray *searchInputImfiles(pxConfig *config, const char *det_id);
+static detInputExpRow *rawDetrenTodetInputExpRow(rawExpRow *rawExp, psS64 det_id, psS32 iteration);
+static psArray *searchRawImfiles(pxConfig *config);
+static bool startNewIteration(pxConfig *config, psS64 det_id);
+static psS32 incrementIteration(pxConfig *config, psS64 det_id);
+static bool setDetRunState(pxConfig *config, psS64 det_id, const char *state);
+static bool isValidMode(pxConfig *config, const char *mode);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = dettoolConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(DETTOOL_MODE_PENDING,          pendingMode);
+        MODECASE(DETTOOL_MODE_DEFINEBYTAG,      definebytagMode);
+        MODECASE(DETTOOL_MODE_DEFINEBYQUERY,    definebyqueryMode);
+        MODECASE(DETTOOL_MODE_DEFINEBYDETRUN,   definebydetrunMode);
+        MODECASE(DETTOOL_MODE_RUNS,             runsMode);
+        MODECASE(DETTOOL_MODE_CHILDLESSRUN,     childlessrunMode);
+        MODECASE(DETTOOL_MODE_INPUT,            inputMode);
+        MODECASE(DETTOOL_MODE_RAW,              rawMode);
+        // correction
+        MODECASE(DETTOOL_MODE_MAKECORRECTION,    makecorrectionMode);
+        MODECASE(DETTOOL_MODE_TOCORRECTEXP,      tocorrectexpMode);
+        MODECASE(DETTOOL_MODE_TOCORRECTIMFILE,   tocorrectimfileMode);
+        MODECASE(DETTOOL_MODE_ADDCORRECTIMFILE,  addcorrectimfileMode);
+        // imfile
+        MODECASE(DETTOOL_MODE_TOPROCESSEDIMFILE,toprocessedimfileMode);
+        MODECASE(DETTOOL_MODE_ADDPROCESSEDIMFILE,addprocessedimfileMode);
+        MODECASE(DETTOOL_MODE_PROCESSEDIMFILE,  processedimfileMode);
+        MODECASE(DETTOOL_MODE_REVERTPROCESSEDIMFILE, revertprocessedimfileMode);
+        // exp
+        MODECASE(DETTOOL_MODE_TOPROCESSEDEXP,   toprocessedexpMode);
+        MODECASE(DETTOOL_MODE_ADDPROCESSEDEXP,  addprocessedexpMode);
+        MODECASE(DETTOOL_MODE_PROCESSEDEXP,     processedexpMode);
+        MODECASE(DETTOOL_MODE_REVERTPROCESSEDEXP, revertprocessedexpMode);
+        // stacked
+        MODECASE(DETTOOL_MODE_TOSTACKED,        tostackedMode);
+        MODECASE(DETTOOL_MODE_ADDSTACKED,       addstackedMode);
+        MODECASE(DETTOOL_MODE_STACKED,          stackedMode);
+        MODECASE(DETTOOL_MODE_REVERTSTACKED,    revertstackedMode);
+        // normalizedstat
+        MODECASE(DETTOOL_MODE_TONORMALIZEDSTAT, tonormalizedstatMode);
+        MODECASE(DETTOOL_MODE_ADDNORMALIZEDSTAT,addnormalizedstatMode);
+        MODECASE(DETTOOL_MODE_NORMALIZEDSTAT,   normalizedstatMode);
+        MODECASE(DETTOOL_MODE_REVERTNORMALIZEDSTAT, revertnormalizedstatMode);
+        // normalizedimfile
+        MODECASE(DETTOOL_MODE_TONORMALIZE,      tonormalizeMode);
+        MODECASE(DETTOOL_MODE_ADDNORMALIZEDIMFILE,addnormalizedimfileMode);
+        MODECASE(DETTOOL_MODE_NORMALIZEDIMFILE, normalizedimfileMode);
+        MODECASE(DETTOOL_MODE_REVERTNORMALIZEDIMFILE, revertnormalizedimfileMode);
+        // normalizedexp
+        MODECASE(DETTOOL_MODE_TONORMALIZEDEXP,  tonormalizedexpMode);
+        MODECASE(DETTOOL_MODE_ADDNORMALIZEDEXP, addnormalizedexpMode);
+        MODECASE(DETTOOL_MODE_NORMALIZEDEXP,    normalizedexpMode);
+        MODECASE(DETTOOL_MODE_REVERTNORMALIZEDEXP, revertnormalizedexpMode);
+        // residimfile
+        MODECASE(DETTOOL_MODE_TORESIDIMFILE,    toresidimfileMode);
+        MODECASE(DETTOOL_MODE_ADDRESIDIMFILE,   addresidimfileMode);
+        MODECASE(DETTOOL_MODE_RESIDIMFILE,      residimfileMode);
+        MODECASE(DETTOOL_MODE_REVERTRESIDIMFILE,revertresidimfileMode);
+        // residexp
+        MODECASE(DETTOOL_MODE_TORESIDEXP,       toresidexpMode);
+        MODECASE(DETTOOL_MODE_ADDRESIDEXP,      addresidexpMode);
+        MODECASE(DETTOOL_MODE_RESIDEXP,         residexpMode);
+        MODECASE(DETTOOL_MODE_REVERTRESIDEXP,   revertresidexpMode);
+        MODECASE(DETTOOL_MODE_UPDATERESIDEXP,   updateresidexpMode);
+        // detrunsummary
+        MODECASE(DETTOOL_MODE_TODETRUNSUMMARY,  todetrunsummaryMode);
+        MODECASE(DETTOOL_MODE_ADDDETRUNSUMMARY, adddetrunsummaryMode);
+        MODECASE(DETTOOL_MODE_DETRUNSUMMARY,    detrunsummaryMode);
+        MODECASE(DETTOOL_MODE_REVERTDETRUNSUMMARY, revertdetrunsummaryMode);
+        MODECASE(DETTOOL_MODE_UPDATEDETRUNSUMMARY, updatedetrunsummaryMode);
+        MODECASE(DETTOOL_MODE_UPDATEDETRUN,     updatedetrunMode);
+        MODECASE(DETTOOL_MODE_RERUN,            rerunMode);
+        // register
+        MODECASE(DETTOOL_MODE_REGISTER_DETREND, register_detrendMode);
+        MODECASE(DETTOOL_MODE_REGISTER_DETREND_IMFILE, register_detrend_imfileMode);
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+static bool pendingMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("dettool_pending.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+    
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-exp_id", "exp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_type", "exp_type", "==");
+    PXOPT_COPY_STR(config->args, where, "-inst", "camera", "==");
+    PXOPT_COPY_STR(config->args, where, "-telescope", "telescope", "==");
+    PXOPT_COPY_STR(config->args, where, "-filter", "filter", "==");
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "rawExp");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "rawExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool definebytagMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // what type of detRun is this?
+    // required
+    PXOPT_LOOKUP_STR(det_type, config->args, "-det_type", true, false);
+    PXOPT_LOOKUP_STR(workdir, config->args, "-workdir", true, false);
+
+    // optional
+    PXOPT_LOOKUP_STR(filelevel, config->args, "-filelevel", false, false);
+    PXOPT_LOOKUP_STR(mode, config->args, "-mode", false, false);
+    // check mode
+    if (mode && !isValidMode(config, mode)) {
+        psError(PS_ERR_UNKNOWN, false, "invalud mode");
+        return false;
+    }
+    PXOPT_LOOKUP_STR(camera, config->args, "-inst", false, false);
+    PXOPT_LOOKUP_STR(telescope, config->args, "-telescope", false, false);
+    PXOPT_LOOKUP_STR(exp_type, config->args, "-exp_type", false, false);
+    PXOPT_LOOKUP_STR(filter, config->args, "-filter", false, false);
+
+    PXOPT_LOOKUP_F32(airmass_min, config->args, "-airmass_min", false, false);
+    PXOPT_LOOKUP_F32(airmass_max, config->args, "-airmass_max", false, false);
+    PXOPT_LOOKUP_F32(exp_time_min, config->args, "-exp_time_min", false, false);
+    PXOPT_LOOKUP_F32(exp_time_max, config->args, "-exp_time_max", false, false);
+    PXOPT_LOOKUP_F32(ccd_temp_min, config->args, "-ccd_temp_min", false, false);
+    PXOPT_LOOKUP_F32(ccd_temp_max, config->args, "-ccd_temp_max", false, false);
+    PXOPT_LOOKUP_F64(posang_min, config->args, "-posang_min", false, false);
+    PXOPT_LOOKUP_F64(posang_max, config->args, "-posang_max", false, false);
+    PXOPT_LOOKUP_F64(solang_min, config->args, "-solang_min", false, false);
+    PXOPT_LOOKUP_F64(solang_max, config->args, "-solang_max", false, false);
+    PXOPT_LOOKUP_TIME(time_begin, config->args, "-time_begin", false, false);
+    PXOPT_LOOKUP_TIME(time_end, config->args, "-time_end", false, false);
+    PXOPT_LOOKUP_TIME(use_begin, config->args, "-use_begin", false, false);
+    PXOPT_LOOKUP_TIME(use_end, config->args, "-use_end", false, false);
+    PXOPT_LOOKUP_STR(reduction, config->args, "-reduction", false, false);
+    PXOPT_LOOKUP_STR(label, config->args, "-label", false, false);
+
+    // default
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_TIME(registered, config->args, "-registered", false, false);
+
+    // we have to support multipe exp_ids
+    psMetadataItem *item = psMetadataLookup(config->args, "-exp_id");
+    if (!item) {
+        // this shouldn't actually happen when using psArgs
+        psError(PS_ERR_UNKNOWN, true, "-exp_id is required");
+        return false;
+    }
+    psMetadata *where = psMetadataAlloc();
+
+    // make sure that -exp_id was parsed correctly
+    // XXX this can be removed someday
+    // XXX Special case for a multi : move into a macro?
+    if (item->type == PS_DATA_METADATA_MULTI) {
+        psListIterator *iter = psListIteratorAlloc(item->data.list, 0, false);
+        psMetadataItem *mItem = NULL;
+        while ((mItem = psListGetAndIncrement(iter))) {
+            psString exp_id = mItem->data.V;
+            // if exp_id is NULL then it means that -exp_id has not been
+            // specified
+            if (!exp_id) {
+                psError(PS_ERR_UNKNOWN, true,
+                        "at least one -exp_id is required");
+                psFree(where);
+                return false;
+            }
+
+            if (!psMetadataAddStr(where, PS_LIST_TAIL, "exp_id",
+                 PS_META_DUPLICATE_OK, "==", exp_id)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+                psFree(iter);
+                psFree(where);
+                return false;
+            }
+        }
+        psFree(iter);
+    } else {
+        psAbort(                "-exp_id was not parsed correctly (this should not happen");
+    }
+
+    if (psListLength(where->list) < 1) {
+        psFree(where);
+        where = NULL;
+    }
+
+    // check that the specified exp_ids actually exist
+    psArray *detrendExps = rawExpSelectRowObjects(config->dbh, where, 0);
+    psFree(where);
+    if (!detrendExps) {
+        psError(PS_ERR_UNKNOWN, false, "no rawExp rows found");
+        return false;
+    }
+
+    // we should have one rawExp row per exp_id specified
+    if (psListLength(item->data.list) != psArrayLength(detrendExps)) {
+        psAbort(    "an -exp_id matched more then one rawExp (this should not happen");
+
+    }
+
+    // check to see if -filelevel was set on the command line
+    if (!filelevel) {
+        filelevel = psStringCopy(((rawExpRow *)detrendExps->data[0])->filelevel);
+    }
+
+    // start a transaction so we don't end up with childlessed det_ids
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(detrendExps);
+        return false;
+    }
+
+    // the first iteration is always 0
+    // XXX the camera name is set from the first inputExp
+    // XXX det_id
+    detRunInsert(config->dbh,
+                 0,
+                 0,
+                 det_type,
+                 mode,
+                 "run",
+                 filelevel,
+                 workdir,
+                 camera,
+                 telescope,
+                 exp_type,
+                 reduction,
+                 filter,
+                 airmass_min,
+                 airmass_max,
+                 exp_time_min,
+                 exp_time_max,
+                 ccd_temp_min,
+                 ccd_temp_max,
+                 posang_min,
+                 posang_max,
+                 registered,
+                 time_begin,
+                 time_end,
+                 use_begin,
+                 use_end,
+                 solang_min,
+                 solang_max,
+                 label,
+                 0       // parent
+        );
+    psS64 det_id = psDBLastInsertID(config->dbh);
+
+    // create new detInputExp row(s) from the rawExp row(s)
+    psArray *inputExps = psArrayAllocEmpty(psArrayLength(detrendExps));
+    for (long i = 0; i < psArrayLength(detrendExps); i++) {
+        detInputExpRow *inputExp = rawDetrenTodetInputExpRow(
+            detrendExps->data[i],
+            det_id,
+            0 // the first iteration is explicitly 0
+        );
+        psArrayAdd(inputExps, 0, inputExp);
+        psFree(inputExp);
+    }
+
+    psFree(detrendExps);
+
+    // insert detInputExp objects into the database
+    if (!detInputExpInsertObjects(config->dbh, inputExps)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psFree(inputExps);
+        return false;
+    }
+    psFree(inputExps);
+
+    // point of no return for det_id creation
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // print the new det_id
+    psArray *detRuns = NULL;
+    {
+        psMetadata *where = psMetadataAlloc();
+        psMetadataAddS64(where, PS_LIST_TAIL, "det_id", 0, "==", det_id);
+        detRuns = psDBSelectRows(config->dbh, "detRun", where, 0);
+        psFree(where);
+    }
+    if (!detRuns) {
+        psError(PS_ERR_UNKNOWN, false, "can't find the detRun we just created");
+        return false;
+    }
+    // sanity check results
+    if (psArrayLength(detRuns) != 1) {
+        psAbort("found more then one detRun matching det_id %" PRId64 " (this should not happen)", det_id);
+        return false;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, detRuns, "detRun", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(detRuns);
+        return false;
+    }
+    psFree(detRuns);
+
+    return true;
+}
+
+#if 0
+// This function is used to convert the det_id from an int, as it is used
+// internally, to be a string for external use.  The rationale being that we may
+// want to change how det_id is generated in the future and don't want to
+// external programs to become depending on this value being an int.
+static bool convertDetIdToStr(psArray *mds)
+{
+    PS_ASSERT_PTR_NON_NULL(mds, false);
+
+    for (long i = 0; i < psArrayLength(mds); i++) {
+        psMetadata *md = mds->data[i];
+        bool status = false;
+        psS32 det_id = psMetadataLookupS32(&status, md, "det_id");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for det_id");
+            return false;
+        }
+        psMetadataRemoveKey(md, "det_id");
+        psString det_idStr = psDBIntToString((psU64)det_id);
+        psMetadataAddStr(mds->data[i], PS_LIST_HEAD, "det_id", 0, NULL, det_idStr);
+        psFree(det_idStr);
+    }
+
+    return true;
+}
+#endif
+
+static bool definebyqueryMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required
+    PXOPT_LOOKUP_STR(det_type, config->args, "-det_type", true, false);
+    PXOPT_LOOKUP_STR(workdir, config->args, "-workdir", true, false);
+    PXOPT_LOOKUP_STR(camera, config->args, "-inst", true, false);
+
+    // optional
+    PXOPT_LOOKUP_STR(mode, config->args, "-mode", false, false);
+    // check mode
+    if (mode && !isValidMode(config, mode)) {
+        psError(PS_ERR_UNKNOWN, false, "invalid mode");
+        return false;
+    }
+    PXOPT_LOOKUP_STR(filelevel, config->args, "-filelevel", false, false);
+    PXOPT_LOOKUP_STR(telescope, config->args, "-telescope", false, false);
+    PXOPT_LOOKUP_STR(filter, config->args, "-filter", false, false);
+    PXOPT_LOOKUP_F32(airmass_min, config->args, "-airmass_min", false, false);
+    PXOPT_LOOKUP_F32(airmass_max, config->args, "-airmass_max", false, false);
+    PXOPT_LOOKUP_F32(exp_time_min, config->args, "-exp_time_min", false, false);
+    PXOPT_LOOKUP_F32(exp_time_max, config->args, "-exp_time_max", false, false);
+    PXOPT_LOOKUP_F32(ccd_temp_min, config->args, "-ccd_temp_min", false, false);
+    PXOPT_LOOKUP_F32(ccd_temp_max, config->args, "-ccd_temp_max", false, false);
+    PXOPT_LOOKUP_F64(posang_min, config->args, "-posang_min", false, false);
+    PXOPT_LOOKUP_F64(posang_max, config->args, "-posang_max", false, false);
+    PXOPT_LOOKUP_F64(solang_min, config->args, "-solang_min", false, false);
+    PXOPT_LOOKUP_F64(solang_max, config->args, "-solang_max", false, false);
+
+    PXOPT_LOOKUP_BOOL(pretend, config->args, "-pretend", false);
+    PXOPT_LOOKUP_STR(reduction, config->args, "-reduction", false, false);
+    PXOPT_LOOKUP_STR(label, config->args, "-label", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    PXOPT_LOOKUP_BOOL(randomSubset, config->args, "-random_subset", false);
+    PXOPT_LOOKUP_S32(randomLimit, config->args, "-random_limit", false, false);
+
+    PXOPT_LOOKUP_TIME(registered, config->args, "-registered", false, false);
+    PXOPT_LOOKUP_TIME(time_begin, config->args, "-time_begin", false, false);
+    PXOPT_LOOKUP_TIME(time_end, config->args, "-time_end", false, false);
+    PXOPT_LOOKUP_TIME(use_begin, config->args, "-use_begin", false, false);
+    PXOPT_LOOKUP_TIME(use_end, config->args, "-use_end", false, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-select_exp_type", "exp_type", "==");
+    PXOPT_COPY_STR(config->args, where, "-select_inst", "camera", "==");
+    PXOPT_COPY_STR(config->args, where, "-select_telescope", "telescope", "==");
+    PXOPT_COPY_STR(config->args, where, "-select_filter", "filter", "==");
+    PXOPT_COPY_STR(config->args, where, "-select_uri", "uri", "==");
+    PXOPT_COPY_TIME(config->args, where, "-select_dateobs_begin", "dateobs", ">=");
+    PXOPT_COPY_TIME(config->args, where, "-select_dateobs_end", "dateobs", "<=");
+    PXOPT_COPY_F32(config->args, where, "-select_airmass_min", "airmass", ">=");
+    PXOPT_COPY_F32(config->args, where, "-select_airmass_max", "airmass", "<=");
+    PXOPT_COPY_F32(config->args, where, "-select_sat_pixel_frac_max", "sat_pixel_frac", "<=");
+    PXOPT_COPY_F32(config->args, where, "-select_exp_time_min", "exp_time", ">=");
+    PXOPT_COPY_F32(config->args, where, "-select_exp_time_max", "exp_time", "<=");
+    PXOPT_COPY_F64(config->args, where, "-select_ccd_temp_min", "ccd_temp", ">=");
+    PXOPT_COPY_F64(config->args, where, "-select_ccd_temp_max", "ccd_temp", "<=");
+    PXOPT_COPY_F64(config->args, where, "-select_pon_time_min", "pon_time", ">=");
+    PXOPT_COPY_F64(config->args, where, "-select_pon_time_max", "pon_time", "<=");
+    PXOPT_COPY_F64(config->args, where, "-select_posang_min", "posang", ">=");
+    PXOPT_COPY_F64(config->args, where, "-select_posang_max", "posang", "<=");
+    PXOPT_COPY_F64(config->args, where, "-select_solang_min", "solang", ">=");
+    PXOPT_COPY_F64(config->args, where, "-select_solang_max", "solang", "<=");
+
+    if (!psListLength(where->list)) {
+        psFree(where);
+        where = NULL;
+    }
+
+    // there is some namespace overlap between the names of the fields we'd
+    // like to search by to setup a detrun and the names of the fields we'd
+    // like to assign values to so I've separated them but prepending set- to
+    // the assigned values
+
+    // search for rawExps with the specified options
+    psArray *detrendExps = rawExpSelectRowObjects(config->dbh, where, 0);
+    psFree(where);
+    // make sure that we found at least one rawExp
+    if (!detrendExps) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(detrendExps)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(detrendExps);
+        return true;
+    }
+
+    if (randomSubset && (randomLimit < detrendExps->n)) {
+      // generate a random-valued vector, return an index sorted by the random values
+      psVector *randomVector = psVectorAlloc(detrendExps->n, PS_TYPE_F32); // random values
+      psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, 0);
+      for (int i = 0; i < randomVector->n; i++) {
+        randomVector->data.F32[i] = psRandomUniform(rng);
+      }
+      psVector *indexVector = psVectorSortIndex(NULL, randomVector);
+
+      // accept for first n of the sequence
+      psArray *subset = psArrayAlloc (randomLimit);
+      for (int i = 0; i < randomLimit; i++ ){
+        int j = indexVector->data.S32[i];
+        subset->data[i] = psMemIncrRefCounter (detrendExps->data[j]);
+      }
+      psFree (detrendExps);
+      detrendExps = subset;
+    }
+
+    // check to see if -filelevel was set on the command line
+    if (!filelevel) {
+        filelevel = psStringCopy(((rawExpRow *)detrendExps->data[0])->filelevel);
+    }
+
+    if (pretend) {
+        // negative simple so the default is true
+        if (!rawExpPrintObjects(stdout, detrendExps, !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(detrendExps);
+            return false;
+        }
+        psFree(detrendExps);
+        return true;
+    }
+
+    // start a transaction so we don't end up with childlessed det_ids
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(detrendExps);
+        return false;
+    }
+
+    // the first iteration is always 0
+    // XXX det_id
+    detRunInsert(config->dbh,
+                 0,      // det_id
+                 0,      // iteration
+                 det_type,
+                 mode,
+                 "run",  // state
+                 filelevel,
+                 workdir,
+                 camera,
+                 telescope,
+                 "NA",
+                 reduction,
+                 filter,
+                 airmass_min,
+                 airmass_max,
+                 exp_time_min,
+                 exp_time_max,
+                 ccd_temp_min,
+                 ccd_temp_max,
+                 posang_min,
+                 posang_max,
+                 registered,
+                 time_begin,
+                 time_end,
+                 use_begin,
+                 use_end,
+                 solang_min,
+                 solang_max,
+                 label,
+                 0       // parent
+        );
+    psS64 det_id = psDBLastInsertID(config->dbh);
+
+    // create new detInputExp row(s) from the rawExp row(s)
+    psArray *inputExps = psArrayAllocEmpty(psArrayLength(detrendExps));
+    for (long i = 0; i < psArrayLength(detrendExps); i++) {
+        detInputExpRow *inputExp = rawDetrenTodetInputExpRow(
+            detrendExps->data[i],
+            det_id,
+            0 // the first iteration is explicitly 0
+        );
+        psArrayAdd(inputExps, 0, inputExp);
+        psFree(inputExp);
+    }
+
+    psFree(detrendExps);
+
+    // insert detInputExp objects into the database
+    if (!detInputExpInsertObjects(config->dbh, inputExps)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psFree(inputExps);
+        return false;
+    }
+    psFree(inputExps);
+
+    // point of no return for det_id creation
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+
+    // print the new det_id
+    psArray *detRuns = NULL;
+    {
+        psMetadata *where = psMetadataAlloc();
+        psMetadataAddS64(where, PS_LIST_TAIL, "det_id", 0, "==", det_id);
+        detRuns = psDBSelectRows(config->dbh, "detRun", where, 0);
+        psFree(where);
+    }
+    if (!detRuns) {
+        psError(PS_ERR_UNKNOWN, false, "can't find the detRun we just created");
+        return false;
+    }
+    // sanity check results
+    if (psArrayLength(detRuns) != 1) {
+        psAbort("found more then one detRun matching det_id %" PRId64 " (this should not happen)", det_id);
+        return false;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, detRuns, "detRun", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(detRuns);
+        return false;
+    }
+    psFree(detRuns);
+
+    return true;
+}
+
+static bool definebydetrunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    
+    PXOPT_LOOKUP_STR(det_id, config->args, "-det_id", true, false); // required
+    PXOPT_LOOKUP_STR(det_type, config->args, "-set_det_type", false, false); // optional
+    PXOPT_LOOKUP_STR(mode, config->args, "-set_mode", false, false); // optional
+    PXOPT_LOOKUP_STR(exp_type, config->args, "-set_exp_type", false, false);
+    PXOPT_LOOKUP_STR(filelevel, config->args, "-set_filelevel", false, false);
+    PXOPT_LOOKUP_STR(workdir, config->args, "-set_workdir", false, false);
+    PXOPT_LOOKUP_STR(filter, config->args, "-set_filter", false, false);
+    PXOPT_LOOKUP_F32(airmass_min, config->args, "-set_airmass_min", false, false);
+    PXOPT_LOOKUP_F32(airmass_max, config->args, "-set_airmass_max", false, false);
+    PXOPT_LOOKUP_F32(exp_time_min, config->args, "-set_exp_time_min", false, false);
+    PXOPT_LOOKUP_F32(exp_time_max, config->args, "-set_exp_time_max", false, false);
+    PXOPT_LOOKUP_F64(ccd_temp_min, config->args, "-set_ccd_temp_min", false, false);
+    PXOPT_LOOKUP_F64(ccd_temp_max, config->args, "-set_ccd_temp_max", false, false);
+    PXOPT_LOOKUP_F64(posang_min, config->args, "-set_posang_min", false, false);
+    PXOPT_LOOKUP_F64(posang_max, config->args, "-set_posang_max", false, false);
+    PXOPT_LOOKUP_F64(solang_min, config->args, "-set_solang_min", false, false);
+    PXOPT_LOOKUP_F64(solang_max, config->args, "-set_solang_max", false, false);
+    PXOPT_LOOKUP_TIME(registered, config->args, "-set_registered", false, false);
+    PXOPT_LOOKUP_TIME(time_begin, config->args, "-set_time_begin", false, false);
+    PXOPT_LOOKUP_TIME(time_end, config->args, "-set_end", false, false);
+    PXOPT_LOOKUP_TIME(use_begin, config->args, "-use_begin", false, false);
+    PXOPT_LOOKUP_TIME(use_end, config->args, "-use_end", false, false);
+    PXOPT_LOOKUP_STR(reduction, config->args, "-set_reduction", false, false);
+    PXOPT_LOOKUP_STR(label, config->args, "-set_label", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false); // optional
+
+    // check mode
+    if (mode && !isValidMode(config, mode)) {
+        psError(PS_ERR_UNKNOWN, false, "invalud mode");
+        return false;
+    }
+
+    // lookup the detRun that we will be basing this one on
+    psArray *detRuns = NULL;
+    {
+        psMetadata *where = psMetadataAlloc();
+        psMetadataAddS64(where, PS_LIST_TAIL, "det_id", 0, "==", (psS64)atoll(det_id));
+        detRuns = detRunSelectRowObjects(config->dbh, where, 0);
+        psFree(where);
+    }
+    if (!detRuns) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    // sanity check the result... we should have only found one det_id
+    if (psArrayLength(detRuns) != 1) {
+        psAbort("found more then one detRun matching det_id %" PRId64 " (this should not happen)", (psS64)atoll(det_id));
+        return false;                   // unreachable
+    }
+
+    // pull the detRun object out the result array
+    detRunRow *detRun = psMemIncrRefCounter(detRuns->data[0]);
+
+    // discard the resultarray
+    psFree(detRuns);
+
+    // set the det_id to 0/NULL so the database can assign it
+    detRun->det_id = 0;
+
+    // reset the iteration to 0
+    detRun->iteration = 0;
+
+    // reset the state to "run"
+    psFree(detRun->state);
+    detRun->state = psStringCopy("run");
+
+    // walk through the optional values and update the detRun as required
+    if (det_type) {
+        psFree(detRun->det_type);
+        detRun->det_type = psStringCopy(det_type);
+    }
+
+    if (mode) {
+        psFree(detRun->mode);
+        detRun->mode = psStringCopy(mode);
+    }
+
+    if (exp_type) {
+        psFree(detRun->exp_type);
+        detRun->exp_type = psStringCopy(exp_type);
+    }
+
+    if (filelevel) {
+        psFree(detRun->filelevel);
+        detRun->filelevel = psStringCopy(filelevel);
+    }
+
+    if (workdir) {
+        psFree(detRun->workdir);
+        detRun->workdir = psStringCopy(workdir);
+    }
+
+    if (filter) {
+        psFree(detRun->filter);
+        detRun->filter = psStringCopy(filter);
+    }
+
+    if (!isnan(airmass_min)) {
+        detRun->airmass_min = airmass_min;
+    }
+
+    if (!isnan(airmass_max)) {
+        detRun->airmass_max = airmass_max;
+    }
+
+    if (!isnan(exp_time_min)) {
+        detRun->exp_time_min = exp_time_min;
+    }
+
+    if (!isnan(exp_time_max)) {
+        detRun->exp_time_max = exp_time_max;
+    }
+
+    if (!isnan(ccd_temp_min)) {
+        detRun->ccd_temp_min = ccd_temp_min;
+    }
+
+    if (!isnan(ccd_temp_max)) {
+        detRun->ccd_temp_max = ccd_temp_max;
+    }
+
+    if (!isnan(posang_min)) {
+        detRun->posang_min = posang_min;
+    }
+
+    if (!isnan(posang_max)) {
+        detRun->posang_max = posang_max;
+    }
+
+    if (!isnan(solang_min)) {
+        detRun->solang_min = solang_min;
+    }
+
+    if (!isnan(solang_max)) {
+        detRun->solang_max = solang_max;
+    }
+
+    if (label) {
+        psFree(detRun->label);
+        detRun->label = label;
+    }
+
+    if (registered) {
+        psFree(detRun->registered);
+        detRun->registered = psMemIncrRefCounter(registered);
+    }
+
+    if (time_begin) {
+        psFree(detRun->time_begin);
+        detRun->time_begin = psMemIncrRefCounter(time_begin);
+    }
+
+    if (time_end) {
+        psFree(detRun->time_end);
+        detRun->time_end = psMemIncrRefCounter(time_end);
+    }
+
+    if (use_begin) {
+        psFree(detRun->use_begin);
+        detRun->use_begin = psMemIncrRefCounter(use_begin);
+    }
+
+    if (use_end) {
+        psFree(detRun->use_end);
+        detRun->use_end = psMemIncrRefCounter(use_end);
+    }
+
+    if (reduction) {
+        psFree(detRun->reduction);
+        detRun->reduction = psStringCopy(reduction);
+    }
+
+    // create a metadata to restrict detInputExp's be in in the specified range
+
+    psMetadata *time_filter = psMetadataAlloc();
+
+    // XXX: possible mem leak: PXOPT_COPY* will free time_filter but NOTHING
+    // that has previously been allocated
+    PXOPT_COPY_TIME(config->args, time_filter, "-set_input_begin", "dateobs", ">=");
+    PXOPT_COPY_TIME(config->args, time_filter, "-set_input_end", "dateobs", "<");
+
+    // start a transaction so we don't end up with childlessed det_ids
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(time_filter);
+        psFree(detRun);
+        return false;
+    }
+
+    if (!detRunInsertObject(config->dbh, detRun)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psFree(time_filter);
+        psFree(detRun);
+        return false;
+    }
+    psFree(detRun);
+
+    // get the det_id
+    psS64 newDet_id = psDBLastInsertID(config->dbh);
+
+    psString query = pxDataGet("dettool_definebydetrun.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (time_filter->list->n) {
+        psString whereClause = psDBGenerateWhereConditionSQL(time_filter, "rawExp");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(time_filter);
+
+    if (!p_psDBRunQuery(config->dbh, query, (psS64)newDet_id, (psS64)atoll(det_id))) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    // point of no return for det_id creation
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // print the new det_id
+    psArray *newDetRuns = NULL;
+    {
+        psMetadata *where = psMetadataAlloc();
+        psMetadataAddS64(where, PS_LIST_TAIL, "det_id", 0, "==", newDet_id);
+        newDetRuns = psDBSelectRows(config->dbh, "detRun", where, 0);
+        psFree(where);
+    }
+    if (!newDetRuns) {
+        psError(PS_ERR_UNKNOWN, false, "can't find the detRun we just created");
+        return false;
+    }
+    // sanity check the result... we should have only found one det_id
+    if (psArrayLength(newDetRuns) != 1) {
+        psAbort("found more then one detRun matching det_id %" PRId64 " (this should not happen)", newDet_id);
+        return false;                   // unreachable
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, newDetRuns, "detRun", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(newDetRuns);
+        return false;
+    }
+    psFree(newDetRuns);
+
+    return true;
+}
+
+static bool runsMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_type", "det_type", "==");
+    PXOPT_COPY_STR(config->args, where, "-det_id",   "det_id", "==");
+
+    PXOPT_COPY_STR(config->args, where, "-inst", "camera", "==");
+    PXOPT_COPY_STR(config->args, where, "-telescope", "telescope", "==");
+    PXOPT_COPY_STR(config->args, where, "-filter", "filter", "==");
+
+    // airmass_min  < airmass  < airmass_max
+    PXOPT_COPY_F32(config->args, where, "-airmass", "airmass_min", "<=");
+    PXOPT_COPY_F32(config->args, where, "-airmass", "airmass_max", ">=");
+
+    // exp_time_min < exp_time < exp_time_max
+    PXOPT_COPY_F32(config->args, where, "-exp_time", "exp_time_min", "<=");
+    PXOPT_COPY_F32(config->args, where, "-exp_time", "exp_time_max", ">=");
+
+    // ccd_temp_min < ccd_temp < ccd_temp_max
+    PXOPT_COPY_F32(config->args, where, "-ccd_temp", "ccd_temp_min", "<=");
+    PXOPT_COPY_F32(config->args, where, "-ccd_temp", "ccd_temp_max", ">=");
+
+    PXOPT_COPY_F64(config->args, where, "-posang", "posang_min", "<=");
+    PXOPT_COPY_F64(config->args, where, "-posang", "posang_max", ">=");
+
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_BOOL(active, config->args, "-active", false);
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+
+    psString query = pxDataGet("dettool_runs.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list) && active) {
+        psString whereClause = psDBGenerateWhereSQL(where, "detRun");
+        psStringAppend(&query, " %s AND (detRun.state = 'run' OR detRun.state = 'stop' OR detRun.state = 'register')", whereClause);
+        psFree(whereClause);
+    }
+    if (psListLength(where->list) && !active) {
+        psString whereClause = psDBGenerateWhereSQL(where, "detRun");
+        psStringAppend(&query, " %s", whereClause);
+        psFree(whereClause);
+    }
+    if (!psListLength(where->list) && active) {
+        psStringAppend(&query, " WHERE (detRun.state = 'run' OR detRun.state = 'stop' OR detRun.state = 'register')");
+    }
+    psFree (where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *runs = p_psDBFetchResult(config->dbh);
+    if (!runs) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!psArrayLength(runs)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(runs);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, runs, "detRun", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(runs);
+        return false;
+    }
+
+    psFree(runs);
+
+    return true;
+}
+
+static bool childlessrunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_type", "det_type", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    // find the exp_id of all the exposures that we want to queue up.
+    psString query = pxDataGet("dettool_childlessrun.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        psFree(where);
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "detRun");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree (where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detRun", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static detInputExpRow *rawDetrenTodetInputExpRow(rawExpRow *rawExp, psS64 det_id, psS32 iteration)
+{
+    PS_ASSERT_PTR_NON_NULL(rawExp, NULL);
+
+    return detInputExpRowAlloc(
+        det_id,
+        iteration,
+        rawExp->exp_id,
+        true            // use
+    );
+}
+
+static bool inputMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_id",    "det_id", "==");
+    PXOPT_COPY_S32(config->args, where, "-iteration", "iteration", "==");
+    PXOPT_COPY_S64(config->args, where, "-exp_id",    "exp_id", "==");
+
+    psString query = pxDataGet("dettool_input.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        psFree(where);
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereSQL(where, "detInputExp");
+        psStringAppend(&query, " %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree (where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detInputExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool rawMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psMetadata *where = psMetadataAlloc();
+
+    psString query = pxDataGet("dettool_raw.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "rawImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree (where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "rawImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool toprocessedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_id",   "det_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_id",   "exp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-class_id", "class_id", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("dettool_toprocessedimfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "rawImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree (where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detPendingProcessedImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool addprocessedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // det_id, exp_id, class_id, uri, recipe, -bg, -bg_stdev are required
+    PXOPT_LOOKUP_STR(det_id, config->args, "-det_id", true, false);
+    PXOPT_LOOKUP_STR(exp_id, config->args, "-exp_id", true, false);
+    PXOPT_LOOKUP_STR(class_id, config->args, "-class_id", true, false);
+
+    // default values
+    PXOPT_LOOKUP_S16(code, config->args, "-code", false, false);
+
+    // Required if code == 0
+    PXOPT_LOOKUP_STR(uri, config->args, "-uri", (code == 0), false);
+    PXOPT_LOOKUP_STR(recipe, config->args, "-recip", (code == 0), false);
+
+    // optional
+    PXOPT_LOOKUP_F64(bg, config->args, "-bg", false, false);
+    PXOPT_LOOKUP_F64(bg_stdev, config->args, "-bg_stdev", false, false);
+    PXOPT_LOOKUP_F64(bg_mean_stdev, config->args, "-bg_mean_stdev", false, false);
+    PXOPT_LOOKUP_F64(fringe_0, config->args, "-fringe_0", false, false);
+    PXOPT_LOOKUP_F64(fringe_1, config->args, "-fringe_1", false, false);
+    PXOPT_LOOKUP_F64(fringe_2, config->args, "-fringe_2", false, false);
+    PXOPT_LOOKUP_F64(user_1, config->args, "-user_1", false, false);
+    PXOPT_LOOKUP_F64(user_2, config->args, "-user_2", false, false);
+    PXOPT_LOOKUP_F64(user_3, config->args, "-user_3", false, false);
+    PXOPT_LOOKUP_F64(user_4, config->args, "-user_4", false, false);
+    PXOPT_LOOKUP_F64(user_5, config->args, "-user_5", false, false);
+    PXOPT_LOOKUP_STR(path_base, config->args, "-path_base", false, false);
+
+    // find the matching rawImfile by exp_id/class_id
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-exp_id",   "exp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-class_id", "class_id", "==");
+
+    psArray *rawImfiles = rawImfileSelectRowObjects(config->dbh, where, 0);
+    psFree(where);
+
+    if (!rawImfiles) {
+        psError(PS_ERR_UNKNOWN, false, "no rawImfile rows found ");
+        return false;
+    }
+
+    // create a new detProcessedImfile object
+    detProcessedImfileRow *detRow = detProcessedImfileRowAlloc(
+        (psS64)atoll(det_id),
+        (psS64)atoll(exp_id),
+        class_id,
+        uri,
+        recipe,
+        bg,
+        bg_stdev,
+        bg_mean_stdev,
+        fringe_0,
+        fringe_1,
+        fringe_2,
+        user_1,
+        user_2,
+        user_3,
+        user_4,
+        user_5,
+        path_base,
+        code
+    );
+    psFree(rawImfiles);
+
+    // insert the new row into the detProcessedImfile table
+    if (!detProcessedImfileInsertObject(config->dbh, detRow)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(detRow);
+        return false;
+    }
+
+    psFree(detRow);
+
+    return true;
+}
+
+static bool processedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    char *value = NULL;
+
+    PXOPT_LOOKUP_BOOL(included, config->args, "-included", false);
+    PXOPT_LOOKUP_BOOL(faulted, config->args, "-faulted", false);
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_id", "det_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_id", "exp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-class_id", "class_id", "==");
+
+    psString query = pxDataGet("dettool_processedimfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        psFree(where);
+        return false;
+    }
+
+    // add the two required restrictions: detRun.state and detRun.mode
+    // NOTE the above query requires one of the following two WHERE statements
+    {
+        bool status;
+        if ((value = psMetadataLookupStr(&status, config->args, "-select_state"))) {
+            psStringAppend(&query, " WHERE detRun.state = '%s'", value);
+        } else {
+            psStringAppend(&query, " WHERE detRun.state = 'run'");
+        }
+        if ((value = psMetadataLookupStr(&status, config->args, "-select_mode"))) {
+            psStringAppend(&query, " AND detRun.mode = '%s'", value);
+        } else {
+            psStringAppend(&query, " AND detRun.mode = 'master'");
+        }
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "detProcessedImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree (where);
+
+    // restrict search to included imfiles
+    if (included) {
+	psStringAppend(&query, " AND detInputExp.include = 1");
+    }
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "AND detProcessedImfile.fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "AND detProcessedImfile.fault = 0");
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "rawImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool revertprocessedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_id", "det_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_id", "exp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-class_id", "class_id", "==");
+    PXOPT_COPY_S16(config->args, where, "-code", "fault", "==");
+
+    psString query = pxDataGet("dettool_revertprocessedimfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "detProcessedImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    if (psDBAffectedRows(config->dbh) < 1) {
+        psError(PS_ERR_UNKNOWN, false, "should have affected atleast 1 row");
+        return false;
+    }
+
+    return true;
+}
+
+static bool toprocessedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("dettool_toprocessedexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detPendingProcessedExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool addprocessedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // det_id, exp_id, recip, -bg, -bg_stdev
+    // are required
+    PXOPT_LOOKUP_STR(det_id, config->args, "-det_id", true, false);
+    PXOPT_LOOKUP_STR(exp_id, config->args, "-exp_id", true, false);
+
+    // default values
+    PXOPT_LOOKUP_S16(code, config->args, "-code", false, false);
+
+    // Required if code == 0
+    PXOPT_LOOKUP_STR(recipe, config->args, "-recip", (code == 0), false);
+
+    // optional
+    PXOPT_LOOKUP_F64(bg, config->args, "-bg", false, false);
+    PXOPT_LOOKUP_F64(bg_stdev, config->args, "-bg_stdev", false, false);
+    PXOPT_LOOKUP_F64(bg_mean_stdev, config->args, "-bg_mean_stdev", false, false);
+    PXOPT_LOOKUP_F64(fringe_0, config->args, "-fringe_0", false, false);
+    PXOPT_LOOKUP_F64(fringe_1, config->args, "-fringe_1", false, false);
+    PXOPT_LOOKUP_F64(fringe_2, config->args, "-fringe_2", false, false);
+    PXOPT_LOOKUP_F64(user_1, config->args, "-user_1", false, false);
+    PXOPT_LOOKUP_F64(user_2, config->args, "-user_2", false, false);
+    PXOPT_LOOKUP_F64(user_3, config->args, "-user_3", false, false);
+    PXOPT_LOOKUP_F64(user_4, config->args, "-user_4", false, false);
+    PXOPT_LOOKUP_F64(user_5, config->args, "-user_5", false, false);
+    PXOPT_LOOKUP_STR(path_base, config->args, "-path_base", true, false);
+
+    psString query = pxDataGet("dettool_addprocessedexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query, det_id, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+    psFree(output);
+
+    // create a new detProcessedImfile object
+    detProcessedExpRow *detRow = detProcessedExpRowAlloc(
+        (psS64)atoll(det_id),
+        (psS64)atoll(exp_id),
+        recipe,
+        bg,
+        bg_stdev,
+        bg_mean_stdev,
+        fringe_0,
+        fringe_1,
+        fringe_2,
+        user_1,
+        user_2,
+        user_3,
+        user_4,
+        user_5,
+        path_base,
+        code
+    );
+
+    // insert the new row into the detProcessedImfile table
+    if (!detProcessedExpInsertObject(config->dbh, detRow)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(detRow);
+        return false;
+    }
+
+    psFree(detRow);
+
+    return true;
+}
+
+static bool processedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_id", "det_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_id", "exp_id", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(faulted, config->args, "-faulted", false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = psStringCopy("SELECT * FROM detProcessedExp");
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereSQL(where, NULL);
+        psStringAppend(&query, " %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "AND detProcessedExp.fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "AND detProcessedExp.fault = 0");
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detProcessedExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool revertprocessedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_id", "det_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_id", "exp_id", "==");
+    PXOPT_COPY_S16(config->args, where, "-code",   "fault", "==");
+
+    psString query = pxDataGet("dettool_revertprocessedexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "detProcessedExp");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    if (psDBAffectedRows(config->dbh) < 1) {
+        psError(PS_ERR_UNKNOWN, false, "should have affected atleast 1 row");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool tostackedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("dettool_tostacked.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detPendingStackedImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+// this is NOT a command-line mode : it is used by 'addstackedMode'
+static psArray *searchRawImfiles(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_id",    "det_id", "==");
+    PXOPT_COPY_S32(config->args, where, "-iteration", "iteration", "==");
+
+    // select exp_ids from detInputExp matching det_id & iteration
+    // where query should be pre-generated
+    psArray *detInputExp = detInputExpSelectRowObjects(config->dbh, where, 0);
+    psFree (where);
+
+    if (!detInputExp) {
+        psError(PS_ERR_UNKNOWN, false, "no rawExp rows found");
+        return NULL;
+    }
+
+    // generate where query with just the exp_ids
+    psMetadata *where_exp_ids = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(detInputExp); i++) {
+        detInputExpRow *row = detInputExp->data[i];
+        if (!psMetadataAddS64(where_exp_ids, PS_LIST_TAIL, "exp_id",
+                PS_META_DUPLICATE_OK, "==", row->exp_id)
+        ) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+            psFree(detInputExp);
+            psFree(where_exp_ids);
+            return NULL;
+        }
+    }
+    psFree(detInputExp);
+
+    // select rawImfiles with matching exp_ids
+    psArray *rawImfiles =
+        rawImfileSelectRowObjects(config->dbh, where_exp_ids, 0);
+    psFree(where_exp_ids);
+    if (!rawImfiles) {
+        psError(PS_ERR_UNKNOWN, false, "no rawImfile rows found");
+        return NULL;
+    }
+
+    return rawImfiles;
+}
+
+static bool addstackedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // det_id, iteration, class_id, uri, & recipe are required
+    PXOPT_LOOKUP_STR(det_id, config->args, "-det_id", true, false);
+    PXOPT_LOOKUP_S32(iteration, config->args, "-iteration", true, false);
+    PXOPT_LOOKUP_STR(class_id, config->args, "-class_id", true, false);
+
+    PXOPT_LOOKUP_S16(code, config->args, "-code", false, false);
+
+    // Required if code == 0
+    PXOPT_LOOKUP_STR(uri, config->args, "-uri", (code == 0), false);
+    PXOPT_LOOKUP_STR(recipe, config->args, "-recip", (code == 0), false);
+
+    // optional
+    PXOPT_LOOKUP_F64(bg, config->args, "-bg", false, false);
+    PXOPT_LOOKUP_F64(bg_stdev, config->args, "-bg_stdev", false, false);
+    PXOPT_LOOKUP_F64(bg_mean_stdev, config->args, "-bg_mean_stdev", false, false);
+    PXOPT_LOOKUP_F64(user_1, config->args, "-user_1", false, false);
+    PXOPT_LOOKUP_F64(user_2, config->args, "-user_2", false, false);
+    PXOPT_LOOKUP_F64(user_3, config->args, "-user_3", false, false);
+    PXOPT_LOOKUP_F64(user_4, config->args, "-user_4", false, false);
+    PXOPT_LOOKUP_F64(user_5, config->args, "-user_5", false, false);
+
+    // correlate the class_id against the input exposure(s)
+    // searchRawImfiles defines a where clause to search by det_id and iteration
+    psArray *rawImfiles = searchRawImfiles(config);
+
+    if (!rawImfiles) {
+        psError(PS_ERR_UNKNOWN, false, "failed to get rawImfiles from db");
+        return false;
+    }
+
+    bool valid_class_id = false;
+    if (rawImfiles) {
+        for (long i = 0; i < psArrayLength(rawImfiles); i++) {
+            if (!rawImfiles->data[i]) {
+                fprintf (stderr, "*");
+                continue;
+            }
+            if (strcmp(class_id, ((rawImfileRow *)rawImfiles->data[i])->class_id) == 0) {
+                valid_class_id = true;
+                break;
+            }
+        }
+        psFree(rawImfiles);
+    }
+
+    if (!valid_class_id) {
+        psError(PS_ERR_UNKNOWN, true,
+            "class_id can not be correlated with the input exposures");
+        return false;
+    }
+
+    // create a new detStackedImfile object
+    detStackedImfileRow *stackedImfile = detStackedImfileRowAlloc(
+            (psS64)atoll(det_id),
+            iteration,
+            class_id,
+            uri,
+            recipe,
+            bg,
+            bg_stdev,
+            bg_mean_stdev,
+            user_1,
+            user_2,
+            user_3,
+            user_4,
+            user_5,
+            code
+        );
+
+    // insert the new row into the detProcessedImfile table
+    if (!detStackedImfileInsertObject(config->dbh, stackedImfile)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(stackedImfile);
+        return false;
+    }
+
+    psFree(stackedImfile);
+
+    return true;
+}
+
+static bool stackedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_id",    "det_id", "==");
+    PXOPT_COPY_S32(config->args, where, "-iteration", "iteration", "==");
+    PXOPT_COPY_STR(config->args, where, "-class_id",  "class_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-recip",     "recipe", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(faulted, config->args, "-faulted", false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("dettool_stacked.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        psFree(where);
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "detStackedImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "AND detStackedImfile.fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "AND detStackedImfile.fault = 0");
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "rawImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool revertstackedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_id",    "det_id", "==");
+    PXOPT_COPY_S32(config->args, where, "-iteration", "iteration", "==");
+    PXOPT_COPY_STR(config->args, where, "-class_id",  "class_id", "==");
+    PXOPT_COPY_S16(config->args, where, "-code",      "fault", "==");
+
+    psString query = pxDataGet("dettool_revertstacked.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "detStackedImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    if (psDBAffectedRows(config->dbh) < 1) {
+        psError(PS_ERR_UNKNOWN, false, "should have affected atleast 1 row");
+        return false;
+    }
+
+    return true;
+}
+
+static bool tonormalizedstatMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("dettool_tonormalizedstat.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detPendingNormStatImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool addnormalizedstatMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    // required
+    PXOPT_LOOKUP_STR(det_id, config->args, "-det_id", true, false);
+    PXOPT_LOOKUP_STR(class_id, config->args, "-class_id", true, false);
+
+    // optional
+    PXOPT_LOOKUP_F32(norm, config->args, "-norm", false, false);
+
+    // default
+    PXOPT_LOOKUP_S32(iteration, config->args, "-iteration", false, false);
+    PXOPT_LOOKUP_S16(code, config->args, "-code", false, false);
+
+    if (!detNormalizedStatImfileInsert(config->dbh,
+        (psS64)atoll(det_id),
+        iteration,
+        class_id,
+        norm,
+        code
+    )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool normalizedstatMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_id",    "det_id", "==");
+    PXOPT_COPY_S32(config->args, where, "-iteration", "iteration", "==");
+    PXOPT_COPY_STR(config->args, where, "-class_id",  "class_id", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(faulted, config->args, "-faulted", false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("dettool_normalizedstat.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "WHERE fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "WHERE fault = 0");
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detNormalizedStatImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool revertnormalizedstatMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_id",    "det_id", "==");
+    PXOPT_COPY_S32(config->args, where, "-iteration", "iteration", "==");
+    PXOPT_COPY_STR(config->args, where, "-class_id",  "class_id", "==");
+    PXOPT_COPY_S16(config->args, where, "-code",      "fault", "==");
+
+    psString query = pxDataGet("dettool_revertnormalizedstat.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "detNormalizedStatImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    if (psDBAffectedRows(config->dbh) < 1) {
+        psError(PS_ERR_UNKNOWN, false, "should have affected atleast 1 row");
+        return false;
+    }
+
+    return true;
+}
+
+static bool tonormalizeMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("dettool_tonormalize.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detPendingNormImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool addnormalizedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required
+    PXOPT_LOOKUP_STR(det_id, config->args, "-det_id", true, false);
+    PXOPT_LOOKUP_S32(iteration, config->args, "-iteration", false, false);
+    PXOPT_LOOKUP_STR(class_id, config->args, "-class_id", true, false);
+
+    PXOPT_LOOKUP_S16(code, config->args, "-code", false, false);
+
+    // Required if code == 0
+    PXOPT_LOOKUP_STR(uri, config->args, "-uri", (code == 0), false);
+
+    // optional
+    PXOPT_LOOKUP_F64(bg, config->args, "-bg", false, false);
+    PXOPT_LOOKUP_F64(bg_stdev, config->args, "-bg_stdev", false, false);
+    PXOPT_LOOKUP_F64(bg_mean_stdev, config->args, "-bg_mean_stdev", false, false);
+    PXOPT_LOOKUP_F64(user_1, config->args, "-user_1", false, false);
+    PXOPT_LOOKUP_F64(user_2, config->args, "-user_2", false, false);
+    PXOPT_LOOKUP_F64(user_3, config->args, "-user_3", false, false);
+    PXOPT_LOOKUP_F64(user_4, config->args, "-user_4", false, false);
+    PXOPT_LOOKUP_F64(user_5, config->args, "-user_5", false, false);
+    PXOPT_LOOKUP_STR(path_base, config->args, "-path_base", false, false);
+
+    if (!detNormalizedImfileInsert(config->dbh,
+        (psS64)atoll(det_id),
+        iteration,
+        class_id,
+        uri,
+        bg,
+        bg_stdev,
+        bg_mean_stdev,
+        user_1,
+        user_2,
+        user_3,
+        user_4,
+        user_5,
+        path_base,
+        code
+    )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool normalizedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_id",    "det_id", "==");
+    PXOPT_COPY_S32(config->args, where, "-iteration", "iteration", "==");
+    PXOPT_COPY_STR(config->args, where, "-class_id",  "class_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-recip",     "recipe", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_BOOL(faulted, config->args, "-faulted", false);
+
+    psString query = pxDataGet("dettool_normalizedimfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "detNormalizedImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "AND detNormalizedImfile.fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "AND detNormalizedImfile.fault = 0");
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detNormalizedImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool revertnormalizedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_id",    "det_id", "==");
+    PXOPT_COPY_S32(config->args, where, "-iteration", "iteration", "==");
+    PXOPT_COPY_STR(config->args, where, "-class_id",  "class_id", "==");
+    PXOPT_COPY_S16(config->args, where, "-code",      "fault", "==");
+
+    psString query = pxDataGet("dettool_revertnormalizedimfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "detNormalizedImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    if (psDBAffectedRows(config->dbh) < 1) {
+        psError(PS_ERR_UNKNOWN, false, "should have affected atleast 1 row");
+        return false;
+    }
+
+    return true;
+}
+
+static bool tonormalizedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("dettool_tonormalizedexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detPendingNormExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool addnormalizedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    
+    PXOPT_LOOKUP_STR(det_id, config->args, "-det_id", true, false); // required
+    PXOPT_LOOKUP_S32(iteration, config->args, "-iteration", false, false);
+    PXOPT_LOOKUP_S16(code, config->args, "-code", false, false);
+    PXOPT_LOOKUP_STR(recipe, config->args, "-recip", (code == 0), false); // Required if code == 0
+    PXOPT_LOOKUP_F64(bg, config->args, "-bg", false, false);
+    PXOPT_LOOKUP_F64(bg_stdev, config->args, "-bg_stdev", false, false);
+    PXOPT_LOOKUP_F64(bg_mean_stdev, config->args, "-bg_mean_stdev", false, false);
+    PXOPT_LOOKUP_F64(user_1, config->args, "-user_1", false, false);
+    PXOPT_LOOKUP_F64(user_2, config->args, "-user_2", false, false);
+    PXOPT_LOOKUP_F64(user_3, config->args, "-user_3", false, false);
+    PXOPT_LOOKUP_F64(user_4, config->args, "-user_4", false, false);
+    PXOPT_LOOKUP_F64(user_5, config->args, "-user_5", false, false);
+    PXOPT_LOOKUP_STR(path_base, config->args, "-path_base", false, false);
+
+    psString query = pxDataGet("dettool_tonormalizedexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // XXX in some other places, we put the arguments in the .sql and supply them to RunQuery
+    psStringAppend(&query, " WHERE det_id = %s AND iteration = %d", det_id, iteration);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+    psFree(output);
+
+    // insert the new row into the detProcessedImfile table
+    if (!detNormalizedExpInsert(config->dbh,
+        (psS64)atoll(det_id),
+        iteration,
+        recipe,
+        bg,
+        bg_stdev,
+        bg_mean_stdev,
+        user_1,
+        user_2,
+        user_3,
+        user_4,
+        user_5,
+        path_base,
+        code
+    )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool normalizedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_id",    "det_id", "==");
+    PXOPT_COPY_S32(config->args, where, "-iteration", "iteration", "==");
+    PXOPT_COPY_STR(config->args, where, "-recip",  "recipe", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_BOOL(faulted, config->args, "-faulted", false);
+
+    psString query = pxDataGet("dettool_normalizedexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereSQL(where, NULL);
+        psStringAppend(&query, " %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "AND detNormalizedExp.fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "AND detNormalizedExp.fault = 0");
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detNormalizedExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+
+static bool revertnormalizedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_id",    "det_id", "==");
+    PXOPT_COPY_S32(config->args, where, "-iteration", "iteration", "==");
+    PXOPT_COPY_S16(config->args, where, "-code",      "fault", "==");
+
+    psString query = pxDataGet("dettool_revertnormalizedexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "detNormalizedExp");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    if (psDBAffectedRows(config->dbh) < 1) {
+        psError(PS_ERR_UNKNOWN, false, "should have affected atleast 1 row");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool toresidimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("dettool_toresidimfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detPendingResidImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool addresidimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    
+    PXOPT_LOOKUP_STR(det_id, config->args, "-det_id", true, false); // required
+    PXOPT_LOOKUP_S32(iteration, config->args, "-iteration", false, false);
+    PXOPT_LOOKUP_STR(exp_id, config->args, "-exp_id", true, false); // required
+    PXOPT_LOOKUP_STR(class_id, config->args, "-class_id", true, false); // required
+    PXOPT_LOOKUP_S16(code, config->args, "-code", false, false);
+    PXOPT_LOOKUP_STR(uri, config->args, "-uri", (code == 0), false); // Required if code == 0
+    PXOPT_LOOKUP_STR(recipe, config->args, "-recip", (code == 0), false); // Required if code == 0
+    PXOPT_LOOKUP_F64(bg, config->args, "-bg", false, false);
+    PXOPT_LOOKUP_F64(bg_stdev, config->args, "-bg_stdev", false, false);
+    PXOPT_LOOKUP_F64(bg_mean_stdev, config->args, "-bg_mean_stdev", false, false);
+    PXOPT_LOOKUP_F64(bg_skewness, config->args, "-bg_skewness", false, false);
+    PXOPT_LOOKUP_F64(bg_kurtosis, config->args, "-bg_kurtosis", false, false);
+    PXOPT_LOOKUP_F64(bin_stdev, config->args, "-bin_stdev", false, false);
+    PXOPT_LOOKUP_F64(fringe_0, config->args, "-fringe_0", false, false);
+    PXOPT_LOOKUP_F64(fringe_1, config->args, "-fringe_1", false, false);
+    PXOPT_LOOKUP_F64(fringe_2, config->args, "-fringe_2", false, false);
+    PXOPT_LOOKUP_F64(fringe_resid_0, config->args, "-fringe_resid_0", false, false);
+    PXOPT_LOOKUP_F64(fringe_resid_1, config->args, "-fringe_resid_1", false, false);
+    PXOPT_LOOKUP_F64(fringe_resid_2, config->args, "-fringe_resid_2", false, false);
+    PXOPT_LOOKUP_F64(user_1, config->args, "-user_1", false, false);
+    PXOPT_LOOKUP_F64(user_2, config->args, "-user_2", false, false);
+    PXOPT_LOOKUP_F64(user_3, config->args, "-user_3", false, false);
+    PXOPT_LOOKUP_F64(user_4, config->args, "-user_4", false, false);
+    PXOPT_LOOKUP_F64(user_5, config->args, "-user_5", false, false);
+    PXOPT_LOOKUP_STR(path_base, config->args, "-path_base", false, false);
+
+    if (!detResidImfileInsert(config->dbh,
+            (psS64)atoll(det_id),
+            iteration,
+            (psS64)atoll(exp_id),
+            class_id,
+            uri,
+            recipe,
+            bg,
+            bg_stdev,
+            bg_mean_stdev,
+            bg_skewness,
+            bg_kurtosis,
+            bin_stdev,
+            fringe_0,
+            fringe_1,
+            fringe_2,
+            fringe_resid_0,
+            fringe_resid_1,
+            fringe_resid_2,
+            user_1,
+            user_2,
+            user_3,
+            user_4,
+            user_5,
+            path_base,
+            code
+    )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool residimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_id",     "det_id", "==");
+    PXOPT_COPY_S32(config->args, where, "-iteration",  "iteration", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_id",     "exp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-class_id",   "class_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-recip",      "recipe", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_BOOL(faulted, config->args, "-faulted", false);
+
+    psString query = pxDataGet("dettool_residimfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // NOTE: the query ends in a WHERE, add the required restriction on detRun.state
+    char *value = NULL;
+    bool status;
+    if ((value = psMetadataLookupStr(&status, config->args, "-select_state"))) {
+        psStringAppend(&query, " detRun.state = '%s'", value);
+    } else {
+        psStringAppend(&query, " detRun.state = 'run'");
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "detResidImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "AND detResidImfile.fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "AND detResidImfile.fault = 0");
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "rawResidImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool revertresidimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_id",    "det_id", "==");
+    PXOPT_COPY_S32(config->args, where, "-iteration", "iteration", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_id",  "exp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-class_id",  "class_id", "==");
+    PXOPT_COPY_S16(config->args, where, "-code",      "fault", "==");
+
+    psString query = pxDataGet("dettool_revertresidimfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "detResidImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    if (psDBAffectedRows(config->dbh) < 1) {
+        psError(PS_ERR_UNKNOWN, false, "should have affected atleast 1 row");
+        return false;
+    }
+
+    return true;
+}
+
+
+/*
+  The SQL returns a list of exposures for which all component class ids have had residuals
+  created.  This list includes the detrend id, iteration, exposure id, detrend type, and
+  whether the exposure was included in the stack for this iteration.
+*/
+
+static bool toresidexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("dettool_toresidexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detPendingResidExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+/*
+  The SQL returns a list of exposures for which all component class ids have had residuals
+  created.  This list includes the detrend id, iteration, exposure id, detrend type, and
+  whether the exposure was included in the stack for this iteration.
+*/
+
+static bool addresidexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // limit search by det_id, iteration, exp_id
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_id", "det_id", "==");
+    PXOPT_COPY_S32(config->args, where, "-iteration", "iteration", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_id", "exp_id", "==");
+
+    PXOPT_LOOKUP_STR(det_id, config->args, "-det_id", true, false); // required
+    PXOPT_LOOKUP_S32(iteration, config->args, "-iteration", false, false);
+    PXOPT_LOOKUP_STR(exp_id, config->args, "-exp_id", true, false); // required
+    PXOPT_LOOKUP_S16(code, config->args, "-code", false, false);
+    PXOPT_LOOKUP_STR(recipe, config->args, "-recip", (code == 0), false); // Required if code == 0
+    PXOPT_LOOKUP_F64(bg, config->args, "-bg", false, false);
+    PXOPT_LOOKUP_F64(bg_stdev, config->args, "-bg_stdev", false, false);
+    PXOPT_LOOKUP_F64(bg_mean_stdev, config->args, "-bg_mean_stdev", false, false);
+    PXOPT_LOOKUP_F64(bg_skewness, config->args, "-bg_skewness", false, false);
+    PXOPT_LOOKUP_F64(bg_kurtosis, config->args, "-bg_kurtosis", false, false);
+    PXOPT_LOOKUP_F64(bin_stdev, config->args, "-bin_stdev", false, false);
+    PXOPT_LOOKUP_F64(fringe_0, config->args, "-fringe_0", false, false);
+    PXOPT_LOOKUP_F64(fringe_1, config->args, "-fringe_1", false, false);
+    PXOPT_LOOKUP_F64(fringe_2, config->args, "-fringe_2", false, false);
+    PXOPT_LOOKUP_F64(fringe_resid_0, config->args, "-fringe_resid_0", false, false);
+    PXOPT_LOOKUP_F64(fringe_resid_1, config->args, "-fringe_resid_1", false, false);
+    PXOPT_LOOKUP_F64(fringe_resid_2, config->args, "-fringe_resid_2", false, false);
+    PXOPT_LOOKUP_F64(user_1, config->args, "-user_1", false, false);
+    PXOPT_LOOKUP_F64(user_2, config->args, "-user_2", false, false);
+    PXOPT_LOOKUP_F64(user_3, config->args, "-user_3", false, false);
+    PXOPT_LOOKUP_F64(user_4, config->args, "-user_4", false, false);
+    PXOPT_LOOKUP_F64(user_5, config->args, "-user_5", false, false);
+    PXOPT_LOOKUP_STR(path_base, config->args, "-path_base", false, false);
+    PXOPT_LOOKUP_BOOL(reject, config->args, "-reject", false);
+
+    psString query = pxDataGet("dettool_toresidexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+	psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+	psStringAppend(&query, " WHERE %s", whereClause);
+	psFree(whereClause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        // XXX check psError here
+        psError(PS_ERR_UNKNOWN, false, "no detResidImfile rows found");
+        psFree(output);
+        return false;
+    }
+    psFree(output);
+
+    if (!detResidExpInsert(config->dbh,
+            (psS64)atoll(det_id),
+            iteration,
+            (psS64)atoll(exp_id),
+            recipe,
+            bg,
+            bg_stdev,
+            bg_mean_stdev,
+            bg_skewness,
+            bg_kurtosis,
+            bin_stdev,
+            fringe_0,
+            fringe_1,
+            fringe_2,
+            fringe_resid_0,
+            fringe_resid_1,
+            fringe_resid_2,
+            user_1,
+            user_2,
+            user_3,
+            user_4,
+            user_5,
+            path_base,
+            !reject,
+            code
+        )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool residexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_id",    "det_id", "==");
+    PXOPT_COPY_S32(config->args, where, "-iteration", "iteration", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_id",    "exp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-recip",     "recipe", "==");
+
+    PXOPT_LOOKUP_U64(limit,    config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple,  config->args, "-simple", false);
+    PXOPT_LOOKUP_BOOL(faulted, config->args, "-faulted", false);
+    PXOPT_LOOKUP_BOOL(reject,  config->args, "-reject", false);
+
+    psString query = pxDataGet("dettool_residexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "detResidExp");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "AND detResidExp.fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "AND detResidExp.fault = 0");
+    }
+
+    // list only accepted rows
+    // XXX the usage of this flag is unclear : -reject means "accepted"?
+    if (reject) {
+        psStringAppend(&query, " %s", "AND detResidExp.accept != 0");
+    } 
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detResidExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool revertresidexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_id",    "det_id", "==");
+    PXOPT_COPY_S32(config->args, where, "-iteration", "iteration", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_id",    "exp_id", "==");
+    PXOPT_COPY_S16(config->args, where, "-code",      "fault", "==");
+
+    psString query = pxDataGet("dettool_revertresidexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "detResidExp");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    if (psDBAffectedRows(config->dbh) < 1) {
+        psError(PS_ERR_UNKNOWN, false, "should have affected atleast 1 row");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool updateresidexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_BOOL(reject, config->args, "-reject", false);
+
+    // build a query to search by det_id, iteration, exp_id
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_id", "det_id", "==");
+    PXOPT_COPY_S32(config->args, where, "-iteration", "iteration", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_id", "exp_id", "==");
+
+    // find the values we're going to set
+    // copy everything but det_id, iteration, & exp_id from the args and
+    // remove the '-' prefix
+    psMetadata *set = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, set, "-recip", "recipe", "==");
+    PXOPT_COPY_F64(config->args, set, "-bg", "bg", "==");
+    PXOPT_COPY_F64(config->args, set, "-bg_stdev", "bg_stdev", "==");
+    PXOPT_COPY_F64(config->args, set, "-bg_mean_stdev", "bg_mean_stdev", "==");
+    PXOPT_COPY_STR(config->args, set, "-path_base", "path_base", "==");
+
+    // this can't be PXOPT_ macro-ized as reject is !'d
+    if (!psMetadataAddBool(set, PS_LIST_TAIL, "accept", 0, "==", !reject)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item accept");
+        psFree(set);
+        psFree(where);
+        return false;
+    }
+
+    long changed = psDBUpdateRows(config->dbh, "detResidExp", where, set);
+    psFree(set);
+    psFree(where);
+
+    if (changed < 0) {
+        psError(PS_ERR_UNKNOWN, false, "no rows were updated");
+        return false;
+    }
+
+    return true;
+}
+
+/* The SQL returns a list of detrend runs (with detrend id, iteration and detrend type) which
+ * have completed all residexps.
+ */
+
+static bool todetrunsummaryMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("dettool_todetrunsummary.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detRejectExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+// this function is used to generate the detrunSummary entries used below
+// XXX why is this a separate function?  roll it back into adddetrunsummary?
+static detRunSummaryRow *mdToDetRunSummary(pxConfig *config, psMetadata *row)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    // from row
+    psS64 det_id = psMetadataLookupS64(&status, row, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for det_id");
+        return false;
+    }
+    psS32 iteration = psMetadataLookupS32(&status, row, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for iteration");
+        return false;
+    }
+
+    // optional
+    PXOPT_LOOKUP_F64(bg, config->args, "-bg", false, false);
+    PXOPT_LOOKUP_F64(bg_stdev, config->args, "-bg_stdev", false, false);
+    PXOPT_LOOKUP_F64(bg_mean_stdev, config->args, "-bg_mean_stdev", false, false);
+
+    // default values
+    PXOPT_LOOKUP_S16(code, config->args, "-code", false, false);
+    PXOPT_LOOKUP_BOOL(accept, config->args, "-accept", false);
+
+    return detRunSummaryRowAlloc(
+            det_id,
+            iteration,
+            bg,
+            bg_stdev,
+            bg_mean_stdev,
+            accept,
+            code
+        );
+}
+
+static bool adddetrunsummaryMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    
+    // build a query to search by det_id, iteration, exp_id
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_id", "det_id", "==");
+    PXOPT_COPY_S32(config->args, where, "-iteration", "iteration", "==");
+
+    PXOPT_LOOKUP_STR(det_id, config->args, "-det_id", true, false); // required
+    PXOPT_LOOKUP_BOOL(again, config->args, "-again", false);
+
+    // The values supplied as arguments on the command (eg, -bg) are parsed 
+    // by mdToDetRunSummary below. 
+    // XXX why is there ever more than one?
+
+    psString query = pxDataGet("dettool_find_completed_runs.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+	psStringAppend(&query, " WHERE %s", whereClause);
+	psFree(whereClause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // start a transaction so it's all rows or nothing
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(output);
+        return false;
+    }
+
+    // XXX why is there ever more than one result here?
+    for (long i = 0; i < psArrayLength(output); i++) {
+        psMetadata *row = output->data[i];
+        // convert metadata into a detResidExp object
+        detRunSummaryRow *runSummary = mdToDetRunSummary(config, row);
+        if (!runSummary) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to convert metadata to detResidExp");
+            psFree(output);
+            return false;
+        }
+        // insert detResidExp object into the database
+        if (!detRunSummaryInsertObject(config->dbh, runSummary)) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(runSummary);
+            psFree(output);
+            return false;
+        }
+        psFree(runSummary);
+    }
+
+    psFree(output);
+
+    // XXX this logic does not deal with the case of -code being set
+    // XXX it should be an error for -again and -code to both be set
+    if (again) {
+        if (!startNewIteration(config, (psS64)atoll(det_id))) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to start new iteration");
+            return false;
+        }
+    } else {
+        // set detRun.state to stop
+        if (!setDetRunState(config, (psS64)atoll(det_id), "stop")) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to set detRun.state");
+            return false;
+        }
+    }
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    return true;
+}
+
+static bool detrunsummaryMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_id",    "det_id", "==");
+    PXOPT_COPY_S32(config->args, where, "-iteration", "iteration", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_BOOL(faulted, config->args, "-faulted", false);
+
+    psString query = pxDataGet("dettool_detrunsummary.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        psFree(where);
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "NULL");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "AND detRunSummary.fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "AND detRunSummary.fault = 0");
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "rawDetrendImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool revertdetrunsummaryMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_id",    "det_id", "==");
+    PXOPT_COPY_S32(config->args, where, "-iteration", "iteration", "==");
+    PXOPT_COPY_STR(config->args, where, "-code",      "fault", "==");
+
+    psString query = pxDataGet("dettool_revertdetrunsummary.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "detRunSummary");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    if (psDBAffectedRows(config->dbh) < 1) {
+        psError(PS_ERR_UNKNOWN, false, "should have affected atleast 1 row");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool updatedetrunsummaryMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    
+    PXOPT_LOOKUP_STR(det_id, config->args, "-det_id", true, false); // required
+    PXOPT_LOOKUP_BOOL(accept, config->args, "-accept", false);
+    PXOPT_LOOKUP_BOOL(reject, config->args, "-reject", false);
+
+    if (accept && reject) {
+        psError(PS_ERR_UNKNOWN, true, "-accept and -reject are exclusive");
+        return false;
+    }
+
+    if (!(accept || reject)) {
+        psError(PS_ERR_UNKNOWN, true, "either -accept or -reject is required");
+        return false;
+    }
+
+    char *query = "UPDATE detRunSummary SET accept = %d WHERE det_id = %"PRId64; 
+    if (!p_psDBRunQuery(config->dbh, query, accept, (psS64)atoll(det_id))) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool updatedetrunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_STR(det_id, config->args, "-det_id", true, false); // required
+    PXOPT_LOOKUP_BOOL(again, config->args, "-again", false);
+    PXOPT_LOOKUP_STR(state, config->args, "-state", false, false);
+
+    // either -rerun or -state must be specified
+    if (!(again || state)) {
+        psError(PS_ERR_UNKNOWN, true, "either -again or -state must be specified");
+        return false;
+    }
+    if (again && state) {
+        psError(PS_ERR_UNKNOWN, true, "-accept and -reject are exclusive");
+        return false;
+    }
+
+    if (state) {
+        // set detRun.state to state
+        return setDetRunState(config, (psS64)atoll(det_id), state);
+    }
+
+    // else
+    // -again
+    if (!startNewIteration(config, (psS64)atoll(det_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to start new iteration");
+        return false;
+    }
+
+    return true;
+}
+
+// used by updatedetrunMode 
+static bool startNewIteration(pxConfig *config, psS64 det_id)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psString query = pxDataGet("dettool_start_new_iteration.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // restrict the update only to the specified det_id
+    psStringAppend(&query, " WHERE det_id = %" PRId64 , det_id);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psError(PS_ERR_UNKNOWN, false, "det_id %" PRId64 " not found", det_id);
+        psFree(output);
+        return false;
+    }
+
+    // start a transaction so we don't end up with an incremented iteration
+    // count but no detInputExps
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(output);
+        return false;
+    }
+
+    // up the detRuns iteration count for for the single det_id we are
+    // operating on
+    // XXX this will have to changed in order to support multiple det_ids with
+    // a single invocation of this functions
+    psS32 newIteration = incrementIteration(config, det_id);
+    if (!newIteration) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psFree(output);
+        return false;
+    }
+
+    for (long i = 0; i < psArrayLength(output); i++) {
+        psMetadata *row = output->data[i];
+        bool status = false;
+        psS64 exp_id = psMetadataLookupS64(&status, row, "exp_id");
+        if (!status) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for exp_id");
+            psFree(output);
+            return false;
+        }
+        bool accept = psMetadataLookupBool(&status, row, "accept");
+        if (!status) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for accept");
+            psFree(output);
+            return false;
+        }
+
+        // detResidExp.include is used to set detInputExp.include
+        if (!detInputExpInsert(
+                    config->dbh,
+                    det_id,
+                    newIteration,
+                    exp_id,
+                    accept
+                )
+        ) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    // point of no return for det_id creation
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool rerunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // det_id is required
+    PXOPT_LOOKUP_STR(det_id, config->args, "-det_id", true, false);
+
+    // we have to support multipe exp_ids
+    psMetadataItem *item = psMetadataLookup(config->args, "-exp_id");
+    if (!item) {
+        // this shouldn't actually happen when using psArgs
+        psError(PS_ERR_UNKNOWN, true, "-exp_id is required");
+        return false;
+    }
+
+    psList *exp_id_list = item->data.list;
+    psMetadata *where = psMetadataAlloc();
+    // make sure that -exp_id was parsed correctly
+    // XXX this can be removed someday
+    if (item->type == PS_DATA_METADATA_MULTI) {
+        psListIterator *iter = psListIteratorAlloc(item->data.list, 0, false);
+        psMetadataItem *mItem = NULL;
+        while ((mItem = psListGetAndIncrement(iter))) {
+            psString exp_id = mItem->data.V;
+            // if exp_id is NULL then it means that -exp_id has not been
+            // specified
+            if (!exp_id) {
+                psError(PS_ERR_UNKNOWN, true,
+                        "at least one -exp_id is required");
+                psFree(where);
+                return false;
+            }
+
+            if (!psMetadataAddStr(where, PS_LIST_TAIL, "exp_id",
+                        PS_META_DUPLICATE_OK, "==", exp_id)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+                psFree(iter);
+                psFree(where);
+                return false;
+            }
+        }
+        psFree(iter);
+    } else {
+        psAbort("-exp_id was not parsed correctly (this should not happen");
+    }
+
+    // check that the specified exp_ids actually exist in the iteration zero
+    // detInputExp set
+
+    // add the det_id & iteration == 0 to the where clause
+    if (!psMetadataAddS64(where, PS_LIST_TAIL, "det_id", 0, "==",
+                (psS64)atoll(det_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(where);
+        return false;
+    }
+    if (!psMetadataAddS32(where, PS_LIST_TAIL, "iteration", 0, "==", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(where);
+        return false;
+    }
+
+    psArray *detrendExps = detInputExpSelectRowObjects(config->dbh, where, 0);
+    psFree(where);
+    if (!detrendExps) {
+        psError(PS_ERR_UNKNOWN, false, "no rawExp rows found");
+        psFree(where);
+        return false;
+    }
+
+    // build a hash for the valid exp_ids
+    psHash *valid_exp_ids = psHashAlloc(psArrayLength(detrendExps));
+    for (long i = 0; i < psArrayLength(detrendExps); i++) {
+        psString exp_idStr = psDBIntToString(((detInputExpRow *)detrendExps->data[i])->exp_id);
+        psHashAdd(valid_exp_ids, exp_idStr, detrendExps->data[i]);
+        psFree(exp_idStr);
+    }
+    psFree(detrendExps);
+
+    // start a transaction so we don't end up with an incremented iteration
+    // count but no detInputExps
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(valid_exp_ids);
+        return false;
+    }
+
+    // up the detRuns iteration count
+    psS32 newIteration = incrementIteration(config, (psS64)atoll(det_id));
+    if (!newIteration) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psFree(valid_exp_ids);
+        return false;
+    }
+
+    // check exp_ids and build up an array of new detInputExp rows at the same
+    // time
+    psListIterator *iter = psListIteratorAlloc(exp_id_list, 0, false);
+    psMetadataItem *mItem = NULL;
+    psArray *newInputExps = psArrayAllocEmpty(psListLength(exp_id_list));
+    while ((mItem = psListGetAndIncrement(iter))) {
+        detInputExpRow *inputExp = psHashLookup(valid_exp_ids,
+                (char *)mItem->data.V);
+        if (!inputExp) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            // invalid exp_id
+            psError(PS_ERR_UNKNOWN, false, "exp_id %s is invalid for det_id %s",
+                    (char *)mItem->data.V, det_id);
+            psFree(iter);
+            psFree(valid_exp_ids);
+            return false;
+        }
+        detInputExpRow *newInputExp = detInputExpRowAlloc(
+            (psS64)atoll(det_id),
+            newIteration,
+            inputExp->exp_id,
+            true   // use
+        );
+        psArrayAdd(newInputExps, 0, newInputExp);
+        psFree(newInputExp);
+    }
+    psFree(iter);
+    psFree(valid_exp_ids);
+
+    for (long i = 0; i < psArrayLength(newInputExps); i++) {
+        if (!detInputExpInsertObject(config->dbh, newInputExps->data[i])) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psFree(newInputExps);
+            return false;
+        }
+    }
+    psFree(newInputExps);
+
+    // point of no return for det_id creation
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool register_detrendMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_STR(det_type, config->args, "-det_type", true, false); // required
+    PXOPT_LOOKUP_STR(filelevel, config->args, "-filelevel", true, false); // required
+    PXOPT_LOOKUP_STR(workdir, config->args, "-workdir", false, false);
+    PXOPT_LOOKUP_STR(camera, config->args, "-inst", false, false);
+    PXOPT_LOOKUP_STR(telescope, config->args, "-telescope", false, false);
+    PXOPT_LOOKUP_STR(exp_type, config->args, "-exp_type", false, false);
+    PXOPT_LOOKUP_STR(filter, config->args, "-filter", false, false);
+    PXOPT_LOOKUP_F32(airmass_min, config->args, "-airmass_min", false, false);
+    PXOPT_LOOKUP_F32(airmass_max, config->args, "-airmass_max", false, false);
+    PXOPT_LOOKUP_F32(exp_time_min, config->args, "-exp_time_min", false, false);
+    PXOPT_LOOKUP_F32(exp_time_max, config->args, "-exp_time_max", false, false);
+    PXOPT_LOOKUP_F32(ccd_temp_min, config->args, "-ccd_temp_min", false, false);
+    PXOPT_LOOKUP_F32(ccd_temp_max, config->args, "-ccd_temp_max", false, false);
+    PXOPT_LOOKUP_F64(posang_min, config->args, "-posang_min", false, false);
+    PXOPT_LOOKUP_F64(posang_max, config->args, "-posang_max", false, false);
+    PXOPT_LOOKUP_F64(solang_min, config->args, "-solang_min", false, false);
+    PXOPT_LOOKUP_F64(solang_max, config->args, "-solang_max", false, false);
+    PXOPT_LOOKUP_TIME(registered, config->args, "-registered", false, false);
+    PXOPT_LOOKUP_TIME(time_begin, config->args, "-time_begin", false, false);
+    PXOPT_LOOKUP_TIME(time_end, config->args, "-time_end", false, false);
+    PXOPT_LOOKUP_TIME(use_begin, config->args, "-use_begin", false, false);
+    PXOPT_LOOKUP_TIME(use_end, config->args, "-use_end", false, false);
+    PXOPT_LOOKUP_STR(parent, config->args, "-parent", false, false);
+    PXOPT_LOOKUP_STR(label, config->args, "-label", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!detRunInsert(config->dbh,
+                      0,            // det_id
+                      0,            // the iteration is fixed at 0
+                      det_type,
+                      "register",   // mode is required to be "register"
+                      "register",   // state
+                      filelevel,
+                      workdir,
+                      camera,
+                      telescope,
+                      exp_type,
+                      NULL,
+                      filter,
+                      airmass_min,
+                      airmass_max,
+                      exp_time_min,
+                      exp_time_max,
+                      ccd_temp_min,
+                      ccd_temp_max,
+                      posang_min,
+                      posang_max,
+                      registered,
+                      time_begin,
+                      time_end,
+                      use_begin,
+                      use_end,
+                      solang_min,
+                      solang_max,
+                      label,      // label
+                      parent ? (psS64)atoll(parent) : 0
+            )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        return false;
+    }
+
+    // print the new detRun
+    psS64 det_id = psDBLastInsertID(config->dbh);
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    psArray *detRuns = NULL;
+    {
+        psMetadata *where = psMetadataAlloc();
+        psMetadataAddS64(where, PS_LIST_TAIL, "det_id", 0, "==", det_id);
+        detRuns = psDBSelectRows(config->dbh, "detRun", where, 0);
+        psFree(where);
+    }
+    if (!detRuns) {
+        psError(PS_ERR_UNKNOWN, false, "can't find the detRun we just created");
+        return false;
+    }
+    // sanity check results
+    if (psArrayLength(detRuns) != 1) {
+        psAbort("found more then one detRun matching det_id %" PRId64 "(this should not happen)", det_id);
+        return false;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, detRuns, "detRun", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(detRuns);
+        return false;
+    }
+    psFree(detRuns);
+
+    return true;
+}
+
+// NOTE : this function is also used by addcorrectimfileMode
+bool register_detrend_imfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    
+    PXOPT_LOOKUP_STR(det_id, config->args, "-det_id", true, false); // required
+    PXOPT_LOOKUP_STR(class_id, config->args, "-class_id", true, false); // required
+    PXOPT_LOOKUP_STR(uri, config->args, "-uri", true, false); // required
+    PXOPT_LOOKUP_F64(bg, config->args, "-bg", false, false);
+    PXOPT_LOOKUP_F64(bg_stdev, config->args, "-bg_stdev", false, false);
+    PXOPT_LOOKUP_F64(bg_mean_stdev, config->args, "-bg_mean_stdev", false, false);
+    PXOPT_LOOKUP_F64(user_1, config->args, "-user_1", false, false);
+    PXOPT_LOOKUP_F64(user_2, config->args, "-user_2", false, false);
+    PXOPT_LOOKUP_F64(user_3, config->args, "-user_3", false, false);
+    PXOPT_LOOKUP_F64(user_4, config->args, "-user_4", false, false);
+    PXOPT_LOOKUP_F64(user_5, config->args, "-user_5", false, false);
+    PXOPT_LOOKUP_STR(path_base, config->args, "-path_base", false, false);
+
+    if (!detRegisteredImfileInsert(config->dbh,
+        (psS64)atoll(det_id),
+        0,  // the iteration is fixed at 0
+        class_id,
+        uri,
+        bg,
+        bg_stdev,
+        bg_mean_stdev,
+        user_1,
+        user_2,
+        user_3,
+        user_4,
+        user_5,
+        path_base,
+        0       // fault code
+    )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static psS32 incrementIteration(pxConfig *config, psS64 det_id)
+{
+    // this function returns zero on error
+    PS_ASSERT_PTR_NON_NULL(config, 0);
+
+    char *query = "UPDATE detRun SET iteration = iteration + 1 WHERE det_id = %" PRId64;
+    if (!p_psDBRunQuery(config->dbh, query, det_id)) {
+        psError(PS_ERR_UNKNOWN, false,
+                "failed to increment iteration for det_id %" PRId64, det_id);
+        return 0;
+    }
+
+    psMetadata *where = psMetadataAlloc();
+    if (!psMetadataAddS64(where, PS_LIST_TAIL, "det_id", 0, "==", det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(where);
+        return 0;
+    }
+
+    psArray *detRuns = detRunSelectRowObjects(config->dbh, where, 0);
+    psFree(where);
+    if (!detRuns) {
+        psError(PS_ERR_UNKNOWN, false, "no detRun rows found");
+        return 0;
+    }
+
+    // sanity check the database
+    if (psArrayLength(detRuns) != 1) {
+        // this should no happen
+        psAbort(                "database query return too many rows (this should not happen");
+    }
+
+    psS32 newIteration = ((detRunRow *)detRuns->data[0])->iteration;
+    psFree(detRuns);
+
+    return newIteration;
+}
+
+static bool setDetRunState(pxConfig *config, psS64 det_id, const char *state)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(state, false);
+
+    // check that state is a valid string value
+    if (!(
+            (strncmp(state, "run", 4) == 0)
+            || (strncmp(state, "stop", 5) == 0)
+            || (strncmp(state, "drop", 5) == 0)
+            || (strncmp(state, "register", 4) == 0)
+        )
+    ) {
+        psError(PS_ERR_UNKNOWN, false,
+                "invalid detRun state: %s", state);
+        return false;
+    }
+
+    char *query = "UPDATE detRun SET state = '%s' WHERE det_id = %" PRId64;
+    if (!p_psDBRunQuery(config->dbh, query, state, det_id)) {
+        psError(PS_ERR_UNKNOWN, false,
+                "failed to change state for det_id %" PRId64, det_id);
+        return false;
+    }
+
+    return true;
+}
+
+static bool isValidMode(pxConfig *config, const char *mode)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(mode, false);
+
+    // check that state is a valid string value
+    if (!(
+            (strncmp(mode, "master", 7) == 0)
+            || (strncmp(mode, "verify", 7) == 0)
+        )
+    ) {
+        psError(PS_ERR_UNKNOWN, false,
+                "invalid detRun mode: %s", mode);
+        return false;
+    }
+
+    return true;
+}
+
+
+#if 0
+// XXX this function was left in commented as this method may be useful in the
+// future
+static psArray *validDetInputClassIds(pxConfig *config, const char *det_id)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    // det_id is input as a string because the fact that it is an integer
+    // is just a database impliementation detail.
+    PS_ASSERT_PTR_NON_NULL(det_id, NULL);
+
+    psArray *rawImfiles = searchInputImfiles(config, det_id);
+    if (!rawImfiles) {
+        return NULL;
+    }
+
+    psArray *valid_class_ids = NULL;
+    {
+        // All this jumping through hoops is so that we end up with unique
+        // values. PP thinks that making multiple passes through this array and
+        // deleting matched elements would end up being more expensive then a
+        // double sort and stagger scheme. JH thinks it would be cheaper to
+        // just do a unqiue sort and delete.  So this is really just a cheap
+        // hack to avoid implimenting a unique sort function but at least it's
+        // stable.
+        psHash *hash = psHashAlloc(psArrayLength(rawImfiles));
+        for (long i = 0; i < psArrayLength(rawImfiles); i++) {
+            psHashAdd(hash,
+                ((rawImfileRow *)rawImfiles->data[i])->class_id,
+                ((rawImfileRow *)rawImfiles->data[i])->class_id
+            );
+        }
+        valid_class_ids = psHashToArray(hash);
+        psFree(hash);
+    }
+    psFree(rawImfiles);
+
+    return valid_class_ids;
+}
+
+static psArray *searchInputImfiles(pxConfig *config, const char *det_id)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    // det_id is input as a string because the fact that it is an integer
+    // is just a database impliementation detail.
+    PS_ASSERT_PTR_NON_NULL(det_id, NULL);
+
+    psArray *inputExps = NULL;
+    {
+        psMetadata *where = psMetadataAlloc();
+        if (!psMetadataAddS32(where, PS_LIST_TAIL, "det_id", 0, "==",
+                (psS32)atoi(det_id))) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+            psFree(where);
+            return NULL;
+        }
+        inputExps = detInputExpSelectRowObjects(config->dbh, where, 0);
+        psFree(where);
+    }
+    if (!inputExps) {
+        psError(PS_ERR_UNKNOWN, false, "no detInputExp rows found");
+        return NULL;
+    }
+
+    // find rawImfiles associated with detInputExps
+    psArray *rawImfiles = NULL;
+    {
+        psMetadata *where = psMetadataAlloc();
+        for (long i = 0; i < psArrayLength(inputExps); i++) {
+            if (!psMetadataAddStr(where, PS_LIST_TAIL, "exp_id",
+                    PS_META_DUPLICATE_OK, "==",
+                    ((detInputExpRow *)inputExps->data[i])->exp_id)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+                psFree(inputExps);
+                psFree(where);
+                return NULL;
+            }
+        }
+        psFree(inputExps);
+        rawImfiles = rawImfileSelectRowObjects(config->dbh, where, 0);
+        // XXX this really should be sorted for uniqueness
+        psFree(where);
+    }
+    if (!rawImfiles) {
+        psError(PS_ERR_UNKNOWN, false, "no rawImfile rows found");
+        return NULL;
+    }
+
+    return rawImfiles;
+}
+#endif
+
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/dettool.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/dettool.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/dettool.h	(revision 22157)
@@ -0,0 +1,97 @@
+/*
+ * dettool.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifndef DETTOOL_H
+#define DETTOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    DETTOOL_MODE_NONE           = PXTOOL_MODE_NONE,
+    DETTOOL_MODE_PENDING,
+    DETTOOL_MODE_DEFINEBYTAG,
+    DETTOOL_MODE_DEFINEBYQUERY,
+    DETTOOL_MODE_DEFINEBYDETRUN,
+    DETTOOL_MODE_MAKECORRECTION,
+    DETTOOL_MODE_TOCORRECTEXP,
+    DETTOOL_MODE_TOCORRECTIMFILE,
+    DETTOOL_MODE_ADDCORRECTIMFILE,
+    DETTOOL_MODE_RUNS,
+    DETTOOL_MODE_CHILDLESSRUN,
+    DETTOOL_MODE_INPUT,
+    DETTOOL_MODE_RAW,
+    DETTOOL_MODE_TOPROCESSEDIMFILE,
+    DETTOOL_MODE_ADDPROCESSEDIMFILE,
+    DETTOOL_MODE_PROCESSEDIMFILE,
+    DETTOOL_MODE_REVERTPROCESSEDIMFILE,
+    DETTOOL_MODE_TOPROCESSEDEXP,
+    DETTOOL_MODE_ADDPROCESSEDEXP,
+    DETTOOL_MODE_PROCESSEDEXP,
+    DETTOOL_MODE_REVERTPROCESSEDEXP,
+    DETTOOL_MODE_TOSTACKED,
+    DETTOOL_MODE_ADDSTACKED,
+    DETTOOL_MODE_STACKED,
+    DETTOOL_MODE_REVERTSTACKED,
+    DETTOOL_MODE_TONORMALIZEDSTAT,
+    DETTOOL_MODE_ADDNORMALIZEDSTAT,
+    DETTOOL_MODE_NORMALIZEDSTAT,
+    DETTOOL_MODE_REVERTNORMALIZEDSTAT,
+    DETTOOL_MODE_TONORMALIZE,
+    DETTOOL_MODE_ADDNORMALIZEDIMFILE,
+    DETTOOL_MODE_NORMALIZEDIMFILE,
+    DETTOOL_MODE_REVERTNORMALIZEDIMFILE,
+    DETTOOL_MODE_TONORMALIZEDEXP,
+    DETTOOL_MODE_ADDNORMALIZEDEXP,
+    DETTOOL_MODE_NORMALIZEDEXP,
+    DETTOOL_MODE_REVERTNORMALIZEDEXP,
+    DETTOOL_MODE_TORESIDIMFILE,
+    DETTOOL_MODE_ADDRESIDIMFILE,
+    DETTOOL_MODE_RESIDIMFILE,
+    DETTOOL_MODE_REVERTRESIDIMFILE,
+    DETTOOL_MODE_TORESIDEXP,
+    DETTOOL_MODE_ADDRESIDEXP,
+    DETTOOL_MODE_RESIDEXP,
+    DETTOOL_MODE_REVERTRESIDEXP,
+    DETTOOL_MODE_TODETRUNSUMMARY,
+    DETTOOL_MODE_ADDDETRUNSUMMARY,
+    DETTOOL_MODE_DETRUNSUMMARY,
+    DETTOOL_MODE_REVERTDETRUNSUMMARY,
+    DETTOOL_MODE_UPDATERESIDEXP,
+    DETTOOL_MODE_UPDATEDETRUNSUMMARY,
+    DETTOOL_MODE_UPDATEDETRUN,
+    DETTOOL_MODE_RERUN,
+    DETTOOL_MODE_REGISTER_DETREND,
+    DETTOOL_MODE_REGISTER_DETREND_IMFILE
+} dettoolMode;
+
+pxConfig *dettoolConfig(pxConfig *config, int argc, char **argv);
+
+// correct
+bool makecorrectionMode(pxConfig *config);
+bool tocorrectexpMode(pxConfig *config);
+bool tocorrectimfileMode(pxConfig *config);
+bool addcorrectimfileMode(pxConfig *config);
+// register
+bool register_detrend_imfileMode(pxConfig *config);
+
+#endif // DETTOOL_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/dettoolConfig.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/dettoolConfig.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/dettoolConfig.c	(revision 22157)
@@ -0,0 +1,748 @@
+/*
+ * dettoolConfig.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "dettool.h"
+
+pxConfig *dettoolConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (!config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    psTime *now = psTimeGetNow(PS_TIME_TAI);
+
+    // -pending
+    // XXX EAM : is this used?  does it make sense?
+    psMetadata *pendingArgs = psMetadataAlloc();
+    psMetadataAddStr(pendingArgs, PS_LIST_TAIL, "-exp_id",  0,            "search by exposure ID", NULL);
+    psMetadataAddStr(pendingArgs, PS_LIST_TAIL, "-exp_type",  0,            "search by exposure type", NULL);
+    psMetadataAddStr(pendingArgs, PS_LIST_TAIL, "-inst",  0,            "search by camera", NULL);
+    psMetadataAddStr(pendingArgs, PS_LIST_TAIL, "-telescope",  0,            "search by telescope", NULL);
+    psMetadataAddStr(pendingArgs, PS_LIST_TAIL, "-filter",  0,            "search by filter", NULL);
+    psMetadataAddStr(pendingArgs, PS_LIST_TAIL, "-uri",  0,            "search by URL", NULL);
+    psMetadataAddBool(pendingArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -definebytag
+    psMetadata *definebytagArgs = psMetadataAlloc();
+    psMetadataAddStr(definebytagArgs, PS_LIST_TAIL, "-exp_id",            PS_META_DUPLICATE_OK,           "include this exposure (multiple OK, required)", NULL);
+    psMetadataAddStr(definebytagArgs, PS_LIST_TAIL, "-det_type",  0,            "define the type of detrend run (required)", NULL);
+    psMetadataAddStr(definebytagArgs, PS_LIST_TAIL, "-mode",  0,            "define the mode of this detrend run", "master");
+    psMetadataAddStr(definebytagArgs, PS_LIST_TAIL, "-filelevel",  0,            "define filelevel", NULL);
+    psMetadataAddStr(definebytagArgs, PS_LIST_TAIL, "-workdir",  0,            "define workdir (required)", NULL);
+    psMetadataAddStr(definebytagArgs, PS_LIST_TAIL, "-inst",  0,            "define camera", NULL);
+    psMetadataAddStr(definebytagArgs, PS_LIST_TAIL, "-telescope",  0,            "define telescope", NULL);
+    psMetadataAddStr(definebytagArgs, PS_LIST_TAIL, "-exp_type",  0,            "define exposure type", NULL);
+    psMetadataAddStr(definebytagArgs, PS_LIST_TAIL, "-filter",  0,            "define filter ", NULL);
+    psMetadataAddF32(definebytagArgs, PS_LIST_TAIL, "-airmass_min",  0,            "define min airmass", NAN);
+    psMetadataAddF32(definebytagArgs, PS_LIST_TAIL, "-airmass_max",  0,            "define max airmass", NAN);
+    psMetadataAddF32(definebytagArgs, PS_LIST_TAIL, "-exp_time_min",  0,            "define min exposure time", NAN);
+    psMetadataAddF32(definebytagArgs, PS_LIST_TAIL, "-exp_time_max",  0,            "define max exposure time", NAN);
+    psMetadataAddF64(definebytagArgs, PS_LIST_TAIL, "-ccd_temp_min",  0,            "define min ccd tempature", NAN);
+    psMetadataAddF64(definebytagArgs, PS_LIST_TAIL, "-ccd_temp_max",  0,            "define max ccd tempature", NAN);
+    psMetadataAddF64(definebytagArgs, PS_LIST_TAIL, "-posang_min",  0,            "define min rotator position angle", NAN);
+    psMetadataAddF64(definebytagArgs, PS_LIST_TAIL, "-posang_max",  0,            "define max rotator position angle", NAN);
+    psMetadataAddF64(definebytagArgs, PS_LIST_TAIL, "-solang_min",  0,            "define min solar angle", NAN);
+    psMetadataAddF64(definebytagArgs, PS_LIST_TAIL, "-solang_max",  0,            "define max solar angle", NAN);
+    psMetadataAddTime(definebytagArgs, PS_LIST_TAIL, "-registered",  0,            "time detrend run was registered", now);
+    psMetadataAddTime(definebytagArgs, PS_LIST_TAIL, "-time_begin",  0,            "detrend applies to exposures taken during this period", NULL);
+    psMetadataAddTime(definebytagArgs, PS_LIST_TAIL, "-time_end",  0,            "detrend applies to exposures taken during this period", NULL);
+    psMetadataAddTime(definebytagArgs, PS_LIST_TAIL, "-use_begin",  0,            "start of detrend run applicable period", NULL);
+    psMetadataAddTime(definebytagArgs, PS_LIST_TAIL, "-use_end",  0,            "end of detrend run applicable period", NULL);
+    psMetadataAddStr(definebytagArgs, PS_LIST_TAIL, "-reduction",  0,            "define reduction class for processing", NULL);
+    psMetadataAddStr(definebytagArgs, PS_LIST_TAIL, "-label",  0,            "define detrun label", NULL);
+    psMetadataAddBool(definebytagArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -definebyquery
+    psMetadata *definebyqueryArgs = psMetadataAlloc();
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-det_type",  0,            "define the type of detrend run (required)", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-mode",  0,            "define the mode of this detrend run", "master");
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-filelevel",  0,            "define filelevel", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-workdir",  0,            "define workdir (required)", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-inst",  0,            "define camera (required)", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-telescope",  0,            "define telescope", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-filter",  0,            "define filter ", NULL);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-airmass_min",  0,            "define min airmass", NAN);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-airmass_max",  0,            "define max airmass", NAN);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-exp_time_min",  0,            "define min exposure time", NAN);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-exp_time_max",  0,            "define max exposure time", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-ccd_temp_min",  0,            "define min ccd tempature", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-ccd_temp_max",  0,            "define max ccd tempature", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-posang_min",  0,            "define min rotator position angle", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-posang_max",  0,            "define max rotator position angle", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-solang_min",  0,            "define min solar angle", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-solang_max",  0,            "define max solar angle", NAN);
+    psMetadataAddTime(definebyqueryArgs, PS_LIST_TAIL, "-registered",  0,            "time detrend run was registered", now);
+    psMetadataAddTime(definebyqueryArgs, PS_LIST_TAIL, "-time_begin",  0,            "detrend applies to exposures taken during this period", NULL);
+    psMetadataAddTime(definebyqueryArgs, PS_LIST_TAIL, "-time_end",  0,            "detrend applies to exposures taken during this period", NULL);
+    psMetadataAddTime(definebyqueryArgs, PS_LIST_TAIL, "-use_begin",  0,            "start of detrend run applicable period", NULL);
+    psMetadataAddTime(definebyqueryArgs, PS_LIST_TAIL, "-use_end",  0,            "end of detrend run applicable period", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-select_exp_type",  0,            "search for exp_type", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-select_inst",  0,            "search for camera", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-select_telescope",  0,            "search for telescope", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-select_filter",  0,            "search for filter", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-select_uri",  0,            "search for uri", NULL);
+    psMetadataAddTime(definebyqueryArgs, PS_LIST_TAIL, "-select_dateobs_begin", 0,            "search for exposures by time (>=)", NULL);
+    psMetadataAddTime(definebyqueryArgs, PS_LIST_TAIL, "-select_dateobs_end", 0,            "search for exposures by time (<)", NULL);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-select_airmass_min",  0,            "define min airmass", NAN);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-select_airmass_max",  0,            "define max airmass", NAN);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-select_sat_pixel_frac_max",  0,            "define max fraction of saturated pixels", NAN);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-select_exp_time_min",  0,            "define min exposure time", NAN);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-select_exp_time_max",  0,            "define max exposure time", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-select_ccd_temp_min",  0,            "define min ccd tempature", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-select_ccd_temp_max",  0,            "define max ccd tempature", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-select_pon_time_min",  0,            "define min power-on time", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-select_pon_time_max",  0,            "define max power-on time", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-select_posang_min",  0,            "define min rotator position angle", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-select_posang_max",  0,            "define max rotator position angle", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-select_solang_min",  0,            "define min solar angle", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-select_solang_max",  0,            "define max solar angle", NAN);
+    psMetadataAddBool(definebyqueryArgs, PS_LIST_TAIL, "-pretend",  0,            "print the exposures that would be included in the detrend run and exit", false);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-reduction",  0,            "define reduction class for processing", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-label",  0,            "define detrun label", NULL);
+    psMetadataAddBool(definebyqueryArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+    psMetadataAddBool(definebyqueryArgs, PS_LIST_TAIL, "-random_subset",  0,            "use a random subset of elements", false);
+    psMetadataAddS32(definebyqueryArgs, PS_LIST_TAIL, "-random_limit",  0,            "use this number of random elements", 20);
+
+    // -definebydetrun
+    psMetadata *definebydetrunArgs = psMetadataAlloc();
+    psMetadataAddStr(definebydetrunArgs, PS_LIST_TAIL, "-det_id",  0,            "det ID to base a new detRun on (required)", NULL);
+    psMetadataAddStr(definebydetrunArgs, PS_LIST_TAIL, "-set_det_type",  0,            "define the type of detrend run", NULL);
+    psMetadataAddStr(definebydetrunArgs, PS_LIST_TAIL, "-set_mode",  0,            "define the mode of this detrend run", "master");
+    psMetadataAddStr(definebydetrunArgs, PS_LIST_TAIL, "-set_exp_type",  0,            "define exposure type", NULL);
+    psMetadataAddStr(definebydetrunArgs, PS_LIST_TAIL, "-set_filelevel",  0,            "define filelevel", NULL);
+    psMetadataAddStr(definebydetrunArgs, PS_LIST_TAIL, "-set_workdir",  0,            "define workdir", NULL);
+    psMetadataAddStr(definebydetrunArgs, PS_LIST_TAIL, "-set_filter",  0,            "define filter ", NULL);
+    psMetadataAddF32(definebydetrunArgs, PS_LIST_TAIL, "-set_airmass_min",  0,            "define airmass", NAN);
+    psMetadataAddF32(definebydetrunArgs, PS_LIST_TAIL, "-set_airmass_max",  0,            "define airmass", NAN);
+    psMetadataAddF32(definebydetrunArgs, PS_LIST_TAIL, "-set_exp_time_min",  0,            "define exposure time", NAN);
+    psMetadataAddF32(definebydetrunArgs, PS_LIST_TAIL, "-set_exp_time_max",  0,            "define exposure time", NAN);
+    psMetadataAddF64(definebydetrunArgs, PS_LIST_TAIL, "-set_ccd_temp_min",  0,            "define ccd tempature", NAN);
+    psMetadataAddF64(definebydetrunArgs, PS_LIST_TAIL, "-set_ccd_temp_max",  0,            "define ccd tempature", NAN);
+    psMetadataAddF64(definebydetrunArgs, PS_LIST_TAIL, "-set_posang_min",  0,            "define rotator position angle", NAN);
+    psMetadataAddF64(definebydetrunArgs, PS_LIST_TAIL, "-set_posang_max",  0,            "define rotator position angle", NAN);
+    psMetadataAddF64(definebydetrunArgs, PS_LIST_TAIL, "-set_solang_min",  0,            "define solar angle", NAN);
+    psMetadataAddF64(definebydetrunArgs, PS_LIST_TAIL, "-set_solang_max",  0,            "define solar angle", NAN);
+    psMetadataAddTime(definebydetrunArgs, PS_LIST_TAIL, "-set_registered",  0,            "time detrend run was registered", now);
+    psMetadataAddTime(definebydetrunArgs, PS_LIST_TAIL, "-set_time_begin",  0,            "start of period to apply detrend too", NULL);
+    psMetadataAddTime(definebydetrunArgs, PS_LIST_TAIL, "-set_time_end",  0,            "end of period to apply detrend too", NULL);
+    psMetadataAddTime(definebydetrunArgs, PS_LIST_TAIL, "-set_use_begin",  0,            "start of detrend run applicable period", NULL);
+    psMetadataAddTime(definebydetrunArgs, PS_LIST_TAIL, "-set_use_end",  0,            "end of detrend run applicable period", NULL);
+    psMetadataAddStr(definebydetrunArgs, PS_LIST_TAIL, "-set_reduction",  0,            "define reduction class for processing", NULL);
+    psMetadataAddStr(definebydetrunArgs, PS_LIST_TAIL, "-set_label",  0,            "define detrun label", NULL);
+    psMetadataAddBool(definebydetrunArgs, PS_LIST_TAIL, "-simple", 0,            "use the simple output format", false);
+    psMetadataAddStr(definebydetrunArgs, PS_LIST_TAIL, "-set_input_begin", 0,     "require input exp to be in this period", NULL);
+    psMetadataAddStr(definebydetrunArgs, PS_LIST_TAIL, "-set_input_end", 0,       "require input exp to be in this period", NULL);
+
+    // *** these are in dettool_correction.c ***
+    // -makecorrection
+    psMetadata *makecorrectionArgs = psMetadataAlloc();
+    psMetadataAddStr(makecorrectionArgs, PS_LIST_TAIL, "-det_id",  0,            "det ID to be corrected (required)", NULL);
+    psMetadataAddBool(makecorrectionArgs, PS_LIST_TAIL, "-simple", 0,            "use the simple output format", false);
+
+    // -tocorrectexp
+    psMetadata *tocorrectexpArgs = psMetadataAlloc();
+    psMetadataAddU64(tocorrectexpArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(tocorrectexpArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -tocorrectimfile
+    psMetadata *tocorrectimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(tocorrectimfileArgs, PS_LIST_TAIL, "-det_id", 0,            "search for detrend ID", NULL);
+    psMetadataAddU64(tocorrectimfileArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(tocorrectimfileArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -addcorrectimfile
+    psMetadata *addcorrectimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(addcorrectimfileArgs, PS_LIST_TAIL, "-det_id",  0,            "define detrend ID (required)", NULL);
+    psMetadataAddStr(addcorrectimfileArgs, PS_LIST_TAIL, "-class_id",  0,            "search for class ID (required)", NULL);
+    psMetadataAddStr(addcorrectimfileArgs, PS_LIST_TAIL, "-uri",  0,            "define resid file URI", NULL);
+    psMetadataAddF64(addcorrectimfileArgs, PS_LIST_TAIL, "-bg",  0,            "define exposure background", NAN);
+    psMetadataAddF64(addcorrectimfileArgs, PS_LIST_TAIL, "-bg_stdev",  0,            "define exposure background stdev", NAN);
+    psMetadataAddF64(addcorrectimfileArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,            "define exposure background mean stdev", NAN);
+    psMetadataAddF64(addcorrectimfileArgs, PS_LIST_TAIL, "-user_1",  0,            "define user statistic (1)", NAN);
+    psMetadataAddF64(addcorrectimfileArgs, PS_LIST_TAIL, "-user_2",  0,            "define user statistic (2)", NAN);
+    psMetadataAddF64(addcorrectimfileArgs, PS_LIST_TAIL, "-user_3",  0,            "define user statistic (3)", NAN);
+    psMetadataAddF64(addcorrectimfileArgs, PS_LIST_TAIL, "-user_4",  0,            "define user statistic (4)", NAN);
+    psMetadataAddF64(addcorrectimfileArgs, PS_LIST_TAIL, "-user_5",  0,            "define user statistic (5)", NAN);
+    psMetadataAddStr(addcorrectimfileArgs, PS_LIST_TAIL, "-path_base",  0,            "define base output location", NULL);
+
+    // -runs
+    psMetadata *runsArgs = psMetadataAlloc();
+    psMetadataAddStr(runsArgs, PS_LIST_TAIL, "-det_type",  0,            "search for type of detrend run", NULL);
+    psMetadataAddStr(runsArgs, PS_LIST_TAIL, "-det_id", 0,            "search for detrend ID", NULL);
+    psMetadataAddStr(runsArgs, PS_LIST_TAIL, "-inst", 0,            "search for camera (instrument)", NULL);
+    psMetadataAddStr(runsArgs, PS_LIST_TAIL, "-telescope", 0,            "search for telescope", NULL);
+    psMetadataAddStr(runsArgs, PS_LIST_TAIL, "-filter", 0,            "search for filter", NULL);
+    psMetadataAddF32(runsArgs, PS_LIST_TAIL, "-airmass", 0,            "match airmass", NAN);
+    psMetadataAddF32(runsArgs, PS_LIST_TAIL, "-exp_time", 0,            "match exp time", NAN);
+    psMetadataAddF32(runsArgs, PS_LIST_TAIL, "-ccd_temp", 0,            "match ccd temp", NAN);
+    psMetadataAddF64(runsArgs, PS_LIST_TAIL, "-posang", 0,            "match posang", NAN);
+    psMetadataAddBool(runsArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+    psMetadataAddBool(runsArgs, PS_LIST_TAIL, "-active",  0,            "only return active detRuns", false);
+    psMetadataAddU64(runsArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+
+    // -childlessrun
+    psMetadata *childlessrunArgs = psMetadataAlloc();
+    psMetadataAddStr(childlessrunArgs, PS_LIST_TAIL, "-det_type",  0,            "search for type of detrend run", NULL);
+    psMetadataAddU64(childlessrunArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(childlessrunArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -input
+    psMetadata *inputArgs = psMetadataAlloc();
+    psMetadataAddStr(inputArgs, PS_LIST_TAIL, "-det_id", 0,            "search for detrend ID", NULL);
+    psMetadataAddS32(inputArgs, PS_LIST_TAIL, "-iteration",  0,            "define iteration number", 0);
+    psMetadataAddS64(inputArgs, PS_LIST_TAIL, "-exp_id",  0,            "search for exp ID", 0);
+    psMetadataAddBool(inputArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -raw
+    psMetadata *rawArgs = psMetadataAlloc();
+    psMetadataAddBool(rawArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -toprocessedimfile
+    psMetadata *toprocessedimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(toprocessedimfileArgs, PS_LIST_TAIL, "-det_id",  0,            "search for detrend ID", NULL);
+    psMetadataAddStr(toprocessedimfileArgs, PS_LIST_TAIL, "-exp_id",  0,            "search for exp ID", NULL);
+    psMetadataAddStr(toprocessedimfileArgs, PS_LIST_TAIL, "-class_id",  0,            "search for class ID", NULL);
+    psMetadataAddU64(toprocessedimfileArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(toprocessedimfileArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -addprocessedimfile
+    psMetadata *addprocessedimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-det_id",  0,            "define detrend ID (required)", NULL);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-exp_id",  0,            "define exp ID (required)", NULL);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-class_id",  0,            "define class ID (required)", NULL);
+    psMetadataAddS16(addprocessedimfileArgs, PS_LIST_TAIL, "-code",  0,            "set fault code", 0);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-uri",  0,            "define URI", NULL);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-recip",  0,            "define recipe", NULL);
+    psMetadataAddF64(addprocessedimfileArgs, PS_LIST_TAIL, "-bg",  0,            "define exposure background", NAN);
+    psMetadataAddF64(addprocessedimfileArgs, PS_LIST_TAIL, "-bg_stdev",  0,            "define exposure background stdev", NAN);
+    psMetadataAddF64(addprocessedimfileArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,            "define exposure background mean stdev", NAN);
+    psMetadataAddF64(addprocessedimfileArgs, PS_LIST_TAIL, "-fringe_0",  0,            "define fringe slope (0th term)", NAN);
+    psMetadataAddF64(addprocessedimfileArgs, PS_LIST_TAIL, "-fringe_1",  0,            "define fringe slope (1st term)", NAN);
+    psMetadataAddF64(addprocessedimfileArgs, PS_LIST_TAIL, "-fringe_2",  0,            "define fringe slope (2nd term)", NAN);
+    psMetadataAddF64(addprocessedimfileArgs, PS_LIST_TAIL, "-user_1",  0,            "define user statistic (1)", NAN);
+    psMetadataAddF64(addprocessedimfileArgs, PS_LIST_TAIL, "-user_2",  0,            "define user statistic (2)", NAN);
+    psMetadataAddF64(addprocessedimfileArgs, PS_LIST_TAIL, "-user_3",  0,            "define user statistic (3)", NAN);
+    psMetadataAddF64(addprocessedimfileArgs, PS_LIST_TAIL, "-user_4",  0,            "define user statistic (4)", NAN);
+    psMetadataAddF64(addprocessedimfileArgs, PS_LIST_TAIL, "-user_5",  0,            "define user statistic (5)", NAN);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-path_base",  0,            "define base output location", NULL);
+
+    // -processedimfile
+    psMetadata *processedimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(processedimfileArgs, PS_LIST_TAIL, "-det_id",  0,            "search for detrend ID", NULL);
+    psMetadataAddStr(processedimfileArgs, PS_LIST_TAIL, "-exp_id",  0,            "search for exp ID", NULL);
+    psMetadataAddStr(processedimfileArgs, PS_LIST_TAIL, "-class_id",  0,            "search for class ID", NULL);
+    psMetadataAddStr(processedimfileArgs, PS_LIST_TAIL, "-select_state",  0,            "search for state", NULL);
+    psMetadataAddStr(processedimfileArgs, PS_LIST_TAIL, "-select_mode",  0,            "search for mode", NULL);
+    psMetadataAddBool(processedimfileArgs, PS_LIST_TAIL, "-included",  0,            "restrict results to exposures 'includeded' in the current iteration", false);
+    psMetadataAddBool(processedimfileArgs, PS_LIST_TAIL, "-faulted",  0,            "only return imfiles with a fault status set", false);
+    psMetadataAddU64(processedimfileArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(processedimfileArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // XXX these don't seem to be used anymore
+    // psMetadataAddBool(processedimfileArgs, PS_LIST_TAIL, "-chip",  0,            "restrict results to completed 'chip' sets", false);
+    // psMetadataAddBool(processedimfileArgs, PS_LIST_TAIL, "-exp",  0,            "restrict results to complete 'exposures'", false);
+
+    // -revertprocessedimfile
+    psMetadata *revertprocessedimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(revertprocessedimfileArgs, PS_LIST_TAIL, "-det_id",  0,            "search for detrend ID (required)", NULL);
+    psMetadataAddStr(revertprocessedimfileArgs, PS_LIST_TAIL, "-exp_id",  0,            "search by exposure ID", NULL);
+    psMetadataAddStr(revertprocessedimfileArgs, PS_LIST_TAIL, "-class_id",  0,            "search by class ID", NULL);
+    psMetadataAddS16(revertprocessedimfileArgs, PS_LIST_TAIL, "-code",  0,            "search by fault code", 0);
+
+    // -toprocessedexp
+    psMetadata *toprocessedexpArgs = psMetadataAlloc();
+    psMetadataAddU64(toprocessedexpArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(toprocessedexpArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -addproccessedexp
+    psMetadata *addprocessedexpArgs = psMetadataAlloc();
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-det_id",  0,            "define detrend ID (required)", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-exp_id",  0,            "define detrend ID (required)", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-recip",  0,            "define recipe", NULL);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-bg",  0,            "define exposure background", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-bg_stdev",  0,            "define exposure background stdev", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,            "define exposure background mean stdev", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-fringe_0",  0,            "define fringe slope (0th term)", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-fringe_1",  0,            "define fringe slope (1st term)", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-fringe_2",  0,            "define fringe slope (2nd term)", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-user_1",  0,            "define user statistic (1)", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-user_2",  0,            "define user statistic (2)", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-user_3",  0,            "define user statistic (3)", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-user_4",  0,            "define user statistic (4)", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-user_5",  0,            "define user statistic (5)", NAN);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-path_base",  0,            "define base output location", NULL);
+    psMetadataAddS16(addprocessedexpArgs, PS_LIST_TAIL, "-code",  0,            "set fault code", 0);
+
+    // -proccessedexp
+    psMetadata *processedexpArgs = psMetadataAlloc();
+    psMetadataAddStr(processedexpArgs, PS_LIST_TAIL, "-det_id",  0,            "search by detrend ID", NULL);
+    psMetadataAddStr(processedexpArgs, PS_LIST_TAIL, "-exp_id",  0,            "search by exp ID", NULL);
+    psMetadataAddU64(processedexpArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(processedexpArgs, PS_LIST_TAIL, "-faulted",  0,            "only return imfiles with a fault status set", false);
+    psMetadataAddBool(processedexpArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -revertprocessedexp
+    psMetadata *revertprocessedexpArgs = psMetadataAlloc();
+    psMetadataAddStr(revertprocessedexpArgs, PS_LIST_TAIL, "-det_id",  0,            "search by detrend ID (required)", NULL);
+    psMetadataAddStr(revertprocessedexpArgs, PS_LIST_TAIL, "-exp_id",  0,            "search by exposure ID", NULL);
+    psMetadataAddS16(revertprocessedexpArgs, PS_LIST_TAIL, "-code",  0,            "search by fault code", 0);
+
+    // -tostacked
+    psMetadata *tostackedArgs = psMetadataAlloc();
+    psMetadataAddU64(tostackedArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(tostackedArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -addstacked
+    psMetadata *addstackedArgs = psMetadataAlloc();
+    psMetadataAddStr(addstackedArgs, PS_LIST_TAIL, "-det_id",  0,            "define detrend ID (required)", NULL);
+    psMetadataAddS32(addstackedArgs, PS_LIST_TAIL, "-iteration",  0,            "define iteration number (required)", 0);
+    psMetadataAddStr(addstackedArgs, PS_LIST_TAIL, "-class_id",  0,            "define class ID (required)", NULL);
+    psMetadataAddS16(addstackedArgs, PS_LIST_TAIL, "-code",  0,            "set fault code", 0);
+    psMetadataAddStr(addstackedArgs, PS_LIST_TAIL, "-uri",  0,            "define URI", NULL);
+    psMetadataAddStr(addstackedArgs, PS_LIST_TAIL, "-recip",  0,            "define recipe", NULL);
+    psMetadataAddF64(addstackedArgs, PS_LIST_TAIL, "-bg",  0,            "define exposure background", NAN);
+    psMetadataAddF64(addstackedArgs, PS_LIST_TAIL, "-bg_stdev",  0,            "define exposure background stdev", NAN);
+    psMetadataAddF64(addstackedArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,            "define exposure background mean stdev", NAN);
+    psMetadataAddF64(addstackedArgs, PS_LIST_TAIL, "-user_1",  0,            "define user statistic (1)", NAN);
+    psMetadataAddF64(addstackedArgs, PS_LIST_TAIL, "-user_2",  0,            "define user statistic (2)", NAN);
+    psMetadataAddF64(addstackedArgs, PS_LIST_TAIL, "-user_3",  0,            "define user statistic (3)", NAN);
+    psMetadataAddF64(addstackedArgs, PS_LIST_TAIL, "-user_4",  0,            "define user statistic (4)", NAN);
+    psMetadataAddF64(addstackedArgs, PS_LIST_TAIL, "-user_5",  0,            "define user statistic (5)", NAN);
+
+    // -stacked
+    psMetadata *stackedArgs = psMetadataAlloc();
+    psMetadataAddStr(stackedArgs, PS_LIST_TAIL, "-det_id",  0,            "search for detrend ID", NULL);
+    psMetadataAddS32(stackedArgs, PS_LIST_TAIL, "-iteration",  0,            "search for iteration number", 0);
+    psMetadataAddStr(stackedArgs, PS_LIST_TAIL, "-class_id",  0,            "search for class ID", NULL);
+    psMetadataAddStr(stackedArgs, PS_LIST_TAIL, "-recip",  0,            "search for recipe", NULL);
+    psMetadataAddU64(stackedArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(stackedArgs, PS_LIST_TAIL, "-faulted",  0,            "only return imfiles with a fault status set", false);
+    psMetadataAddBool(stackedArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -revertstacked
+    psMetadata *revertstackedArgs= psMetadataAlloc();
+    psMetadataAddStr(revertstackedArgs, PS_LIST_TAIL, "-det_id",  0,            "search for detrend ID (required)", NULL);
+    psMetadataAddS32(revertstackedArgs, PS_LIST_TAIL, "-iteration",  0,            "search by iteration number", 0);
+    psMetadataAddStr(revertstackedArgs, PS_LIST_TAIL, "-class_id",  0,            "search by class ID", NULL);
+    psMetadataAddS16(revertstackedArgs, PS_LIST_TAIL, "-code",  0,            "search by fault code", 0);
+
+    // -tonormalizedstat
+    psMetadata *tonormalizedstatArgs = psMetadataAlloc();
+    psMetadataAddU64(tonormalizedstatArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(tonormalizedstatArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -addnormalizedstat
+    psMetadata *addnormstatArgs = psMetadataAlloc();
+    psMetadataAddStr(addnormstatArgs, PS_LIST_TAIL, "-det_id",  0,            "define detrend ID (required)", NULL);
+    psMetadataAddS32(addnormstatArgs, PS_LIST_TAIL, "-iteration",  0,            "define iteration number", 0);
+    psMetadataAddStr(addnormstatArgs, PS_LIST_TAIL, "-class_id",  0,            "define class ID (required)", NULL);
+    psMetadataAddF32(addnormstatArgs, PS_LIST_TAIL, "-norm",  0,            "define normal value (required)", NAN);
+    psMetadataAddS16(addnormstatArgs, PS_LIST_TAIL, "-code",  0,            "set fault code", 0);
+
+    // -normalizedstat
+    psMetadata *normalizedstatArgs = psMetadataAlloc();
+    psMetadataAddStr(normalizedstatArgs, PS_LIST_TAIL, "-det_id",  0,            "search by detrend ID", NULL);
+    psMetadataAddS32(normalizedstatArgs, PS_LIST_TAIL, "-iteration",  0,            "search by iteration number", 0);
+    psMetadataAddStr(normalizedstatArgs, PS_LIST_TAIL, "-class_id",  0,            "search by class ID", NULL);
+    psMetadataAddU64(normalizedstatArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(normalizedstatArgs, PS_LIST_TAIL, "-faulted",  0,            "only return imfiles with a fault status set", false);
+    psMetadataAddBool(normalizedstatArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -revertnormalizedstat
+    psMetadata *revertnormalizedstatArgs= psMetadataAlloc();
+    psMetadataAddStr(revertnormalizedstatArgs, PS_LIST_TAIL, "-det_id",  0,            "search by detrend ID (required)", NULL);
+    psMetadataAddS32(revertnormalizedstatArgs, PS_LIST_TAIL, "-iteration",  0,            "search by iteration number", 0);
+    psMetadataAddStr(revertnormalizedstatArgs, PS_LIST_TAIL, "-class_id",  0,            "search by class ID", NULL);
+    psMetadataAddS16(revertnormalizedstatArgs, PS_LIST_TAIL, "-code",  0,            "search by fault code", 0);
+
+    // -tonormalize
+    psMetadata *tonormalizeArgs = psMetadataAlloc();
+    psMetadataAddU64(tonormalizeArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(tonormalizeArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -addnormalizedimfile
+    psMetadata *addnormalizedimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(addnormalizedimfileArgs, PS_LIST_TAIL, "-det_id", 0,            "define detrend ID (required)", NULL);
+    psMetadataAddS32(addnormalizedimfileArgs, PS_LIST_TAIL, "-iteration", 0,            "define iteration number", 0);
+    psMetadataAddStr(addnormalizedimfileArgs, PS_LIST_TAIL, "-class_id", 0,            "define class ID (required)", NULL);
+    psMetadataAddS16(addnormalizedimfileArgs, PS_LIST_TAIL, "-code",  0,            "set fault code", 0);
+    psMetadataAddStr(addnormalizedimfileArgs, PS_LIST_TAIL, "-uri", 0,            "define URI", NULL);
+    psMetadataAddF64(addnormalizedimfileArgs, PS_LIST_TAIL, "-bg", 0,            "define exposure background", NAN);
+    psMetadataAddF64(addnormalizedimfileArgs, PS_LIST_TAIL, "-bg_stdev", 0,            "define exposure background stdev", NAN);
+    psMetadataAddF64(addnormalizedimfileArgs, PS_LIST_TAIL, "-bg_mean_stdev", 0,            "define exposure background mean stdev", NAN);
+    psMetadataAddF64(addnormalizedimfileArgs, PS_LIST_TAIL, "-user_1",  0,            "define user statistic (1)", NAN);
+    psMetadataAddF64(addnormalizedimfileArgs, PS_LIST_TAIL, "-user_2",  0,            "define user statistic (2)", NAN);
+    psMetadataAddF64(addnormalizedimfileArgs, PS_LIST_TAIL, "-user_3",  0,            "define user statistic (3)", NAN);
+    psMetadataAddF64(addnormalizedimfileArgs, PS_LIST_TAIL, "-user_4",  0,            "define user statistic (4)", NAN);
+    psMetadataAddF64(addnormalizedimfileArgs, PS_LIST_TAIL, "-user_5",  0,            "define user statistic (5)", NAN);
+    psMetadataAddStr(addnormalizedimfileArgs, PS_LIST_TAIL, "-path_base", 0,            "define base output location", NULL);
+
+    // -normalizedimfile
+    psMetadata *normalizedimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(normalizedimfileArgs, PS_LIST_TAIL, "-det_id",  0,            "search for detrend ID", NULL);
+    psMetadataAddS32(normalizedimfileArgs, PS_LIST_TAIL, "-iteration",  0,            "search for iteration number", 0);
+    psMetadataAddStr(normalizedimfileArgs, PS_LIST_TAIL, "-class_id",  0,            "search for class ID", NULL);
+    psMetadataAddStr(normalizedimfileArgs, PS_LIST_TAIL, "-recip",  0,            "search for recipe", NULL);
+    psMetadataAddU64(normalizedimfileArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(normalizedimfileArgs, PS_LIST_TAIL, "-faulted",  0,            "only return imfiles with a fault status set", false);
+    psMetadataAddBool(normalizedimfileArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -revertnormalizedimfile
+    psMetadata *revertnormalizedimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(revertnormalizedimfileArgs, PS_LIST_TAIL, "-det_id", 0,            "search by detrend ID (required)", NULL);
+    psMetadataAddS32(revertnormalizedimfileArgs, PS_LIST_TAIL, "-iteration", 0,            "search by iteration number", 0);
+    psMetadataAddStr(revertnormalizedimfileArgs, PS_LIST_TAIL, "-class_id", 0,            "search by class ID", NULL);
+    psMetadataAddS16(revertnormalizedimfileArgs, PS_LIST_TAIL, "-code",  0,            "search by fault code", 0);
+
+    // -tonormalizedexp
+    psMetadata *tonormalizedexpArgs = psMetadataAlloc();
+    psMetadataAddU64(tonormalizedexpArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(tonormalizedexpArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -addnormalizedexp
+    psMetadata *addnormalizedexpArgs = psMetadataAlloc();
+    psMetadataAddStr(addnormalizedexpArgs, PS_LIST_TAIL, "-det_id",  0,            "define detrend ID (required)", NULL);
+    psMetadataAddS32(addnormalizedexpArgs, PS_LIST_TAIL, "-iteration",  0,            "define iteration number", 0);
+    psMetadataAddS16(addnormalizedexpArgs, PS_LIST_TAIL, "-code",  0,            "set fault code", 0);
+    psMetadataAddStr(addnormalizedexpArgs, PS_LIST_TAIL, "-recip",  0,            "search for recipe", NULL);
+    psMetadataAddF64(addnormalizedexpArgs, PS_LIST_TAIL, "-bg",  0,            "define exposure background", NAN);
+    psMetadataAddF64(addnormalizedexpArgs, PS_LIST_TAIL, "-bg_stdev",  0,            "define exposure background stdev", NAN);
+    psMetadataAddF64(addnormalizedexpArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,            "define exposure background mean stdev", NAN);
+    psMetadataAddF64(addnormalizedexpArgs, PS_LIST_TAIL, "-user_1",  0,            "define user statistic (1)", NAN);
+    psMetadataAddF64(addnormalizedexpArgs, PS_LIST_TAIL, "-user_2",  0,            "define user statistic (2)", NAN);
+    psMetadataAddF64(addnormalizedexpArgs, PS_LIST_TAIL, "-user_3",  0,            "define user statistic (3)", NAN);
+    psMetadataAddF64(addnormalizedexpArgs, PS_LIST_TAIL, "-user_4",  0,            "define user statistic (4)", NAN);
+    psMetadataAddF64(addnormalizedexpArgs, PS_LIST_TAIL, "-user_5",  0,            "define user statistic (5)", NAN);
+    psMetadataAddStr(addnormalizedexpArgs, PS_LIST_TAIL, "-path_base",  0,            "define base output location", NULL);
+
+    // -normalizedexp
+    psMetadata *normalizedexpArgs = psMetadataAlloc();
+    psMetadataAddStr(normalizedexpArgs, PS_LIST_TAIL, "-det_id",  0,            "search by detrend ID", NULL);
+    psMetadataAddS32(normalizedexpArgs, PS_LIST_TAIL, "-iteration",  0,            "search by iteration number", 0);
+    psMetadataAddStr(normalizedexpArgs, PS_LIST_TAIL, "-recip",  0,            "search for recipe", NULL);
+    psMetadataAddU64(normalizedexpArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(normalizedexpArgs, PS_LIST_TAIL, "-faulted",  0,            "only return imfiles with a fault status set", false);
+    psMetadataAddBool(normalizedexpArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -revertnormalizedexp
+    psMetadata *revertnormalizedexpArgs = psMetadataAlloc();
+    psMetadataAddStr(revertnormalizedexpArgs, PS_LIST_TAIL, "-det_id", 0,            "search by detrend ID (required)", NULL);
+    psMetadataAddS32(revertnormalizedexpArgs, PS_LIST_TAIL, "-iteration", 0,            "search by iteration number", 0);
+    psMetadataAddS16(revertnormalizedexpArgs, PS_LIST_TAIL, "-code",  0,            "search by fault code", 0);
+
+    // -toresidimfile
+    psMetadata *toresidimfileArgs = psMetadataAlloc();
+    psMetadataAddU64(toresidimfileArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(toresidimfileArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -addresidimfile
+    psMetadata *addresidimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(addresidimfileArgs, PS_LIST_TAIL, "-det_id",  0,            "define detrend ID (required)", NULL);
+    psMetadataAddS32(addresidimfileArgs, PS_LIST_TAIL, "-iteration",  0,            "define iteration number", 0);
+    psMetadataAddStr(addresidimfileArgs, PS_LIST_TAIL, "-exp_id",  0,            "define detrend ID (required)", NULL);
+    psMetadataAddStr(addresidimfileArgs, PS_LIST_TAIL, "-class_id",  0,            "define class ID (required)", NULL);
+    psMetadataAddS16(addresidimfileArgs, PS_LIST_TAIL, "-code",  0,            "set fault code", 0);
+    psMetadataAddStr(addresidimfileArgs, PS_LIST_TAIL, "-uri",  0,            "define resid file URI", NULL);
+    psMetadataAddStr(addresidimfileArgs, PS_LIST_TAIL, "-recip",  0,            "define recipe", NULL);
+    psMetadataAddF64(addresidimfileArgs, PS_LIST_TAIL, "-bg",  0,            "define exposure background", NAN);
+    psMetadataAddF64(addresidimfileArgs, PS_LIST_TAIL, "-bg_stdev",  0,            "define exposure background stdev", NAN);
+    psMetadataAddF64(addresidimfileArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,            "define exposure background mean stdev", NAN);
+    psMetadataAddF64(addresidimfileArgs, PS_LIST_TAIL, "-bg_skewness",  0,            "define exposure background skewness", NAN);
+    psMetadataAddF64(addresidimfileArgs, PS_LIST_TAIL, "-bg_kurtosis",  0,            "define exposure background kurtosis", NAN);
+    psMetadataAddF64(addresidimfileArgs, PS_LIST_TAIL, "-bin_stdev",  0,            "define exposure background binned stdev", NAN);
+    psMetadataAddF64(addresidimfileArgs, PS_LIST_TAIL, "-fringe_0",  0,            "define fringe slope (0th term)", NAN);
+    psMetadataAddF64(addresidimfileArgs, PS_LIST_TAIL, "-fringe_1",  0,            "define fringe slope (1st term)", NAN);
+    psMetadataAddF64(addresidimfileArgs, PS_LIST_TAIL, "-fringe_2",  0,            "define fringe slope (2nd term)", NAN);
+    psMetadataAddF64(addresidimfileArgs, PS_LIST_TAIL, "-fringe_resid_0",  0,            "define fringe residual (0th term)", NAN);
+    psMetadataAddF64(addresidimfileArgs, PS_LIST_TAIL, "-fringe_resid_1",  0,            "define fringe residual (1st term)", NAN);
+    psMetadataAddF64(addresidimfileArgs, PS_LIST_TAIL, "-fringe_resid_2",  0,            "define fringe residual (2nd term)", NAN);
+    psMetadataAddF64(addresidimfileArgs, PS_LIST_TAIL, "-user_1",  0,            "define user statistic (1)", NAN);
+    psMetadataAddF64(addresidimfileArgs, PS_LIST_TAIL, "-user_2",  0,            "define user statistic (2)", NAN);
+    psMetadataAddF64(addresidimfileArgs, PS_LIST_TAIL, "-user_3",  0,            "define user statistic (3)", NAN);
+    psMetadataAddF64(addresidimfileArgs, PS_LIST_TAIL, "-user_4",  0,            "define user statistic (4)", NAN);
+    psMetadataAddF64(addresidimfileArgs, PS_LIST_TAIL, "-user_5",  0,            "define user statistic (5)", NAN);
+    psMetadataAddStr(addresidimfileArgs, PS_LIST_TAIL, "-path_base",  0,            "define base output location", NULL);
+
+    // -residimfile
+    psMetadata *residimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(residimfileArgs, PS_LIST_TAIL, "-det_id",  0,            "search for detrend ID", NULL);
+    psMetadataAddS32(residimfileArgs, PS_LIST_TAIL, "-iteration",  0,            "search for iteration number", 0);
+    psMetadataAddStr(residimfileArgs, PS_LIST_TAIL, "-exp_id",  0,            "search by detrend ID", NULL);
+    psMetadataAddStr(residimfileArgs, PS_LIST_TAIL, "-class_id",  0,            "search for class ID", NULL);
+    psMetadataAddStr(residimfileArgs, PS_LIST_TAIL, "-recip",  0,            "search for recipe", NULL);
+    psMetadataAddU64(residimfileArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(residimfileArgs, PS_LIST_TAIL, "-faulted",  0,            "only return imfiles with a fault status set", false);
+    psMetadataAddBool(residimfileArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+    psMetadataAddStr(residimfileArgs, PS_LIST_TAIL, "-select_state",  0,            "search for state", NULL);
+
+    // -revertresidimfile
+    psMetadata *revertresidimfileArgs =  psMetadataAlloc();
+    psMetadataAddStr(revertresidimfileArgs, PS_LIST_TAIL, "-det_id", 0,          "search by detrend ID (required)", NULL);
+    psMetadataAddS32(revertresidimfileArgs, PS_LIST_TAIL, "-iteration", 0,       "search by iteration number", 0);
+    psMetadataAddStr(revertresidimfileArgs, PS_LIST_TAIL, "-exp_id",  0,         "search by detrend ID", NULL);
+    psMetadataAddStr(revertresidimfileArgs, PS_LIST_TAIL, "-class_id",  0,       "search for class ID", NULL);
+    psMetadataAddS16(revertresidimfileArgs, PS_LIST_TAIL, "-code",  0,           "search by fault code", 0);
+
+    // -toresidexp
+    psMetadata *toresidexpArgs = psMetadataAlloc();
+    psMetadataAddU64(toresidexpArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(toresidexpArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -addresidexp
+    psMetadata *addresidexpArgs = psMetadataAlloc();
+    psMetadataAddStr(addresidexpArgs, PS_LIST_TAIL, "-det_id",  0,            "define detrend ID (required)", NULL);
+    psMetadataAddS32(addresidexpArgs, PS_LIST_TAIL, "-iteration",  0,            "define iteration number", 0);
+    psMetadataAddStr(addresidexpArgs, PS_LIST_TAIL, "-exp_id",  0,            "define detrend ID (required)", NULL);
+    psMetadataAddS16(addresidexpArgs, PS_LIST_TAIL, "-code",  0,            "set fault code", 0);
+    psMetadataAddStr(addresidexpArgs, PS_LIST_TAIL, "-recip",  0,            "define recipe", NULL);
+    psMetadataAddF64(addresidexpArgs, PS_LIST_TAIL, "-bg",  0,            "define exposure background", NAN);
+    psMetadataAddF64(addresidexpArgs, PS_LIST_TAIL, "-bg_stdev",  0,            "define exposure background stdev", NAN);
+    psMetadataAddF64(addresidexpArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,            "define exposure background mean stdev", NAN);
+    psMetadataAddF64(addresidexpArgs, PS_LIST_TAIL, "-bg_skewness",  0,            "define exposure background skewness", NAN);
+    psMetadataAddF64(addresidexpArgs, PS_LIST_TAIL, "-bg_kurtosis",  0,            "define exposure background kurtosis", NAN);
+    psMetadataAddF64(addresidexpArgs, PS_LIST_TAIL, "-bin_stdev",  0,            "define exposure background binned stdev", NAN);
+    psMetadataAddF64(addresidexpArgs, PS_LIST_TAIL, "-fringe_0",  0,            "define fringe slope (0th term)", NAN);
+    psMetadataAddF64(addresidexpArgs, PS_LIST_TAIL, "-fringe_1",  0,            "define fringe slope (1st term)", NAN);
+    psMetadataAddF64(addresidexpArgs, PS_LIST_TAIL, "-fringe_2",  0,            "define fringe slope (2nd term)", NAN);
+    psMetadataAddF64(addresidexpArgs, PS_LIST_TAIL, "-fringe_resid_0",  0,            "define fringe residual (0th term)", NAN);
+    psMetadataAddF64(addresidexpArgs, PS_LIST_TAIL, "-fringe_resid_1",  0,            "define fringe residual (1st term)", NAN);
+    psMetadataAddF64(addresidexpArgs, PS_LIST_TAIL, "-fringe_resid_2",  0,            "define fringe residual (2nd term)", NAN);
+    psMetadataAddF64(addresidexpArgs, PS_LIST_TAIL, "-user_1",  0,            "define user statistic (1)", NAN);
+    psMetadataAddF64(addresidexpArgs, PS_LIST_TAIL, "-user_2",  0,            "define user statistic (2)", NAN);
+    psMetadataAddF64(addresidexpArgs, PS_LIST_TAIL, "-user_3",  0,            "define user statistic (3)", NAN);
+    psMetadataAddF64(addresidexpArgs, PS_LIST_TAIL, "-user_4",  0,            "define user statistic (4)", NAN);
+    psMetadataAddF64(addresidexpArgs, PS_LIST_TAIL, "-user_5",  0,            "define user statistic (5)", NAN);
+    psMetadataAddStr(addresidexpArgs, PS_LIST_TAIL, "-path_base",  0,            "define base output location", NULL);
+    psMetadataAddBool(addresidexpArgs, PS_LIST_TAIL, "-reject",  0,            "exposure is not to be stacked in the next iteration", false);
+
+    // -residexp
+    psMetadata *residexpArgs = psMetadataAlloc();
+    psMetadataAddStr(residexpArgs, PS_LIST_TAIL, "-det_id",  0,            "search for detrend ID", NULL);
+    psMetadataAddS32(residexpArgs, PS_LIST_TAIL, "-iteration",  0,            "search for iteration number", 0);
+    psMetadataAddStr(residexpArgs, PS_LIST_TAIL, "-exp_id",  0,            "search for exp ID", NULL);
+    psMetadataAddStr(residexpArgs, PS_LIST_TAIL, "-recip",  0,            "search for recipe", NULL);
+    psMetadataAddBool(residexpArgs, PS_LIST_TAIL, "-reject",  0,            "search for acceptable residuals", false);
+    psMetadataAddU64(residexpArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(residexpArgs, PS_LIST_TAIL, "-faulted",  0,            "only return imfiles with a fault status set", false);
+    psMetadataAddBool(residexpArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -revertresidexp
+    psMetadata *revertresidexpArgs = psMetadataAlloc();
+    psMetadataAddStr(revertresidexpArgs, PS_LIST_TAIL, "-det_id", 0,            "search by detrend ID (required)", NULL);
+    psMetadataAddS32(revertresidexpArgs, PS_LIST_TAIL, "-iteration", 0,            "search by iteration number", 0);
+    psMetadataAddStr(revertresidexpArgs, PS_LIST_TAIL, "-exp_id",  0,            "search by detrend ID", NULL);
+    psMetadataAddS16(revertresidexpArgs, PS_LIST_TAIL, "-code",  0,            "search by fault code", 0);
+
+    // -updateresidexp
+    psMetadata *updateresidexpArgs = psMetadataAlloc();
+    psMetadataAddStr(updateresidexpArgs, PS_LIST_TAIL, "-det_id",  0,            "define detrend ID", NULL);
+    psMetadataAddS32(updateresidexpArgs, PS_LIST_TAIL, "-iteration",  0,            "define iteration number", 0);
+    psMetadataAddStr(updateresidexpArgs, PS_LIST_TAIL, "-exp_id",  0,            "define exp ID", NULL);
+    psMetadataAddStr(updateresidexpArgs, PS_LIST_TAIL, "-recip",  0,            "define recipe", NULL);
+    psMetadataAddF64(updateresidexpArgs, PS_LIST_TAIL, "-bg",  0,            "define exposure background", NAN);
+    psMetadataAddF64(updateresidexpArgs, PS_LIST_TAIL, "-bg_stdev",  0,            "define exposure background stdev", NAN);
+    psMetadataAddF64(updateresidexpArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,            "define exposure background mean stdev", NAN);
+    psMetadataAddStr(updateresidexpArgs, PS_LIST_TAIL, "-path_base",  0,            "define base output location", NULL);
+    psMetadataAddBool(updateresidexpArgs, PS_LIST_TAIL, "-reject",  0,            "exposure is not to be stacked in the next iteration", false);
+
+    // -todetrunsummary
+    psMetadata *todetrunsummaryArgs = psMetadataAlloc();
+    psMetadataAddBool(todetrunsummaryArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+    psMetadataAddU64(todetrunsummaryArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+
+    // -adddetrunsummary
+    psMetadata *adddetrunsummaryArgs = psMetadataAlloc();
+    psMetadataAddStr(adddetrunsummaryArgs, PS_LIST_TAIL, "-det_id",  0,            "define detrend ID (required)", NULL);
+    psMetadataAddS32(adddetrunsummaryArgs, PS_LIST_TAIL, "-iteration",  0,            "define iteration number", 0);
+    psMetadataAddF64(adddetrunsummaryArgs, PS_LIST_TAIL, "-bg",  0,            "define exposure background", NAN);
+    psMetadataAddF64(adddetrunsummaryArgs, PS_LIST_TAIL, "-bg_stdev",  0,            "define exposure background stdev", NAN);
+    psMetadataAddF64(adddetrunsummaryArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,            "define exposure background mean stdev", NAN);
+    psMetadataAddS16(adddetrunsummaryArgs, PS_LIST_TAIL, "-code",  0,            "set fault code", 0);
+    psMetadataAddBool(adddetrunsummaryArgs, PS_LIST_TAIL, "-accept",  0,            "declare that this detrun iteration is accepted as a master", false);
+    psMetadataAddBool(adddetrunsummaryArgs, PS_LIST_TAIL, "-again",  0,            "start a new iteration of this detrend run", false);
+
+    // -detrunsummary
+    psMetadata *detrunsummaryArgs = psMetadataAlloc();
+    psMetadataAddStr(detrunsummaryArgs, PS_LIST_TAIL, "-det_id",  0,            "search for detrend ID", NULL);
+    psMetadataAddS32(detrunsummaryArgs, PS_LIST_TAIL, "-iteration",  0,            "search for iteration number", 0);
+    psMetadataAddU64(detrunsummaryArgs, PS_LIST_TAIL, "-limit",  0,                     "limit result set to N items", 0);
+    psMetadataAddBool(detrunsummaryArgs, PS_LIST_TAIL, "-faulted",  0,            "only return imfiles with a fault status set", false);
+    psMetadataAddBool(detrunsummaryArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+    psMetadataAddBool(detrunsummaryArgs, PS_LIST_TAIL, "-reject",  0,            "search for acceptable residuals", false);
+
+    // -revertdetrunsummary
+    psMetadata *revertdetrunsummaryArgs = psMetadataAlloc();
+    psMetadataAddStr(revertdetrunsummaryArgs, PS_LIST_TAIL, "-det_id", 0,            "search by detrend ID (required)", NULL);
+    psMetadataAddS32(revertdetrunsummaryArgs, PS_LIST_TAIL, "-iteration", 0,            "search by iteration number", 0);
+    psMetadataAddS16(revertdetrunsummaryArgs, PS_LIST_TAIL, "-code",  0,            "search by fault code", 0);
+
+    // -updatedetrunsummary
+    psMetadata *updatedetrunsummaryArgs = psMetadataAlloc();
+    psMetadataAddStr(updatedetrunsummaryArgs, PS_LIST_TAIL, "-det_id",  0,            "search for detrend master for detrend ID (required)", NULL);
+    psMetadataAddBool(updatedetrunsummaryArgs, PS_LIST_TAIL, "-accept",  0,            "declare that this detrun iteration is accepted as a master", false);
+    psMetadataAddBool(updatedetrunsummaryArgs, PS_LIST_TAIL, "-reject",  0,            "reject this detrun iteration as a master", false);
+
+    // -updatedetrun
+    psMetadata *updatedetrunArgs = psMetadataAlloc();
+    psMetadataAddStr(updatedetrunArgs, PS_LIST_TAIL, "-det_id",  0,            "search for detrend master for detrend ID (required)", NULL);
+    psMetadataAddBool(updatedetrunArgs, PS_LIST_TAIL, "-again",  0,            "start a new iteration of this detrend run", false);
+    psMetadataAddStr(updatedetrunArgs, PS_LIST_TAIL, "-state",  0,            "set the state of this detrend run", false);
+
+    // -rerun
+    psMetadata *rerunArgs = psMetadataAlloc();
+    psMetadataAddStr(rerunArgs, PS_LIST_TAIL, "-det_id",  0,            "search for detrend master for detrend ID (required)", NULL);
+    psMetadataAddStr(rerunArgs, PS_LIST_TAIL, "-exp_id",  PS_META_DUPLICATE_OK,            "include this exposure (multiple OK, required)", NULL);
+
+    // -register_detrend
+    psMetadata *register_detrendArgs = psMetadataAlloc();
+    psMetadataAddStr(register_detrendArgs, PS_LIST_TAIL, "-det_type",  0,            "define the type of detrend run (required)", NULL);
+    psMetadataAddStr(register_detrendArgs, PS_LIST_TAIL, "-filelevel",  0,            "define filelevel (required)", NULL);
+    psMetadataAddStr(register_detrendArgs, PS_LIST_TAIL, "-workdir",  0,            "define workdir", NULL);
+    psMetadataAddStr(register_detrendArgs, PS_LIST_TAIL, "-inst",  0,            "define camera", NULL);
+    psMetadataAddStr(register_detrendArgs, PS_LIST_TAIL, "-telescope",  0,            "define telescope", NULL);
+    psMetadataAddStr(register_detrendArgs, PS_LIST_TAIL, "-exp_type",  0,            "define exposure type", NULL);
+    psMetadataAddStr(register_detrendArgs, PS_LIST_TAIL, "-filter",  0,            "define filter ", NULL);
+    psMetadataAddF32(register_detrendArgs, PS_LIST_TAIL, "-airmass_min",  0,            "define min airmass", NAN);
+    psMetadataAddF32(register_detrendArgs, PS_LIST_TAIL, "-airmass_max",  0,            "define max airmass", NAN);
+    psMetadataAddF32(register_detrendArgs, PS_LIST_TAIL, "-exp_time_min",  0,            "define min exposure time", NAN);
+    psMetadataAddF32(register_detrendArgs, PS_LIST_TAIL, "-exp_time_max",  0,            "define max exposure time", NAN);
+    psMetadataAddF32(register_detrendArgs, PS_LIST_TAIL, "-ccd_temp_min",  0,            "define min ccd tempature", NAN);
+    psMetadataAddF32(register_detrendArgs, PS_LIST_TAIL, "-ccd_temp_max",  0,            "define max ccd tempature", NAN);
+    psMetadataAddF64(register_detrendArgs, PS_LIST_TAIL, "-posang_min",  0,            "define min rotator position angle", NAN);
+    psMetadataAddF64(register_detrendArgs, PS_LIST_TAIL, "-posang_max",  0,            "define max rotator position angle", NAN);
+    psMetadataAddF64(register_detrendArgs, PS_LIST_TAIL, "-solang_min",  0,            "define min solar angle", NAN);
+    psMetadataAddF64(register_detrendArgs, PS_LIST_TAIL, "-solang_max",  0,            "define max solar angle", NAN);
+    psMetadataAddTime(register_detrendArgs, PS_LIST_TAIL, "-registered",  0,            "time detrend run was registered", now);
+    psMetadataAddTime(register_detrendArgs, PS_LIST_TAIL, "-time_begin",  0,            "detrend applies to exposures taken during this period", NULL);
+    psMetadataAddTime(register_detrendArgs, PS_LIST_TAIL, "-time_end",  0,            "detrend applies to exposures taken during this period", NULL);
+    psMetadataAddTime(register_detrendArgs, PS_LIST_TAIL, "-use_begin",  0,            "start of detrend run applicable period", NULL);
+    psMetadataAddTime(register_detrendArgs, PS_LIST_TAIL, "-use_end",  0,            "end of detrend run applicable period", NULL);
+    psMetadataAddStr(register_detrendArgs, PS_LIST_TAIL, "-parent",  0,            "define parent det_id", NULL);
+    psMetadataAddStr(register_detrendArgs, PS_LIST_TAIL, "-label",  0,            "define detrun label", NULL);
+    psMetadataAddBool(register_detrendArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -register_detrend_imfile
+    psMetadata *register_detrend_imfileArgs = psMetadataAlloc();
+    psMetadataAddStr(register_detrend_imfileArgs, PS_LIST_TAIL, "-det_id",  0,            "define detrend ID (required)", NULL);
+    psMetadataAddStr(register_detrend_imfileArgs, PS_LIST_TAIL, "-class_id",  0,            "search for class ID (required)", NULL);
+    psMetadataAddStr(register_detrend_imfileArgs, PS_LIST_TAIL, "-uri",  0,            "define resid file URI (required)", NULL);
+    psMetadataAddF64(register_detrend_imfileArgs, PS_LIST_TAIL, "-bg",  0,            "define exposure background", NAN);
+    psMetadataAddF64(register_detrend_imfileArgs, PS_LIST_TAIL, "-bg_stdev",  0,            "define exposure background stdev", NAN);
+    psMetadataAddF64(register_detrend_imfileArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,            "define exposure background mean stdev", NAN);
+    psMetadataAddF64(register_detrend_imfileArgs, PS_LIST_TAIL, "-user_1",  0,            "define user statistic (1)", NAN);
+    psMetadataAddF64(register_detrend_imfileArgs, PS_LIST_TAIL, "-user_2",  0,            "define user statistic (2)", NAN);
+    psMetadataAddF64(register_detrend_imfileArgs, PS_LIST_TAIL, "-user_3",  0,            "define user statistic (3)", NAN);
+    psMetadataAddF64(register_detrend_imfileArgs, PS_LIST_TAIL, "-user_4",  0,            "define user statistic (4)", NAN);
+    psMetadataAddF64(register_detrend_imfileArgs, PS_LIST_TAIL, "-user_5",  0,            "define user statistic (5)", NAN);
+    psMetadataAddStr(register_detrend_imfileArgs, PS_LIST_TAIL, "-path_base",  0,            "define base output location", NULL);
+
+    psFree(now);
+
+    psMetadata *argSets = psMetadataAlloc();
+    psMetadata *modes = psMetadataAlloc();
+
+    PXOPT_ADD_MODE("-pending",         "list active detruns", DETTOOL_MODE_PENDING,       pendingArgs);
+    PXOPT_ADD_MODE("-definebytag",     "", DETTOOL_MODE_DEFINEBYTAG,   definebytagArgs);
+    PXOPT_ADD_MODE("-definebyquery",   "", DETTOOL_MODE_DEFINEBYQUERY, definebyqueryArgs);
+    PXOPT_ADD_MODE("-definebydetrun",  "", DETTOOL_MODE_DEFINEBYDETRUN, definebydetrunArgs);
+    PXOPT_ADD_MODE("-makecorrection",  "", DETTOOL_MODE_MAKECORRECTION, makecorrectionArgs);
+    PXOPT_ADD_MODE("-tocorrectexp",    "", DETTOOL_MODE_TOCORRECTEXP, tocorrectexpArgs);
+    PXOPT_ADD_MODE("-tocorrectimfile", "", DETTOOL_MODE_TOCORRECTIMFILE, tocorrectimfileArgs);
+    PXOPT_ADD_MODE("-addcorrectimfile", "", DETTOOL_MODE_ADDCORRECTIMFILE, addcorrectimfileArgs);
+    PXOPT_ADD_MODE("-raw",             "", DETTOOL_MODE_RAW,           rawArgs);
+    PXOPT_ADD_MODE("-runs",            "", DETTOOL_MODE_RUNS,          runsArgs);
+    PXOPT_ADD_MODE("-childlessrun",    "", DETTOOL_MODE_CHILDLESSRUN,  childlessrunArgs);
+    PXOPT_ADD_MODE("-input",           "", DETTOOL_MODE_INPUT,         inputArgs);
+    PXOPT_ADD_MODE("-toprocessedimfile", "", DETTOOL_MODE_TOPROCESSEDIMFILE, toprocessedimfileArgs);
+    PXOPT_ADD_MODE("-addprocessedimfile", "", DETTOOL_MODE_ADDPROCESSEDIMFILE,  addprocessedimfileArgs);
+    PXOPT_ADD_MODE("-processedimfile", "", DETTOOL_MODE_PROCESSEDIMFILE, processedimfileArgs);
+    PXOPT_ADD_MODE("-revertprocessedimfile", "", DETTOOL_MODE_REVERTPROCESSEDIMFILE, revertprocessedimfileArgs);
+    PXOPT_ADD_MODE("-toprocessedexp",  "", DETTOOL_MODE_TOPROCESSEDEXP,  toprocessedexpArgs);
+    PXOPT_ADD_MODE("-addprocessedexp", "", DETTOOL_MODE_ADDPROCESSEDEXP, addprocessedexpArgs);
+    PXOPT_ADD_MODE("-revertprocessedexp", "", DETTOOL_MODE_REVERTPROCESSEDEXP, revertprocessedexpArgs);
+    PXOPT_ADD_MODE("-processedexp",    "", DETTOOL_MODE_PROCESSEDEXP, processedexpArgs);
+    PXOPT_ADD_MODE("-tostacked",       "", DETTOOL_MODE_TOSTACKED,     tostackedArgs);
+    PXOPT_ADD_MODE("-addstacked",      "", DETTOOL_MODE_ADDSTACKED,    addstackedArgs);
+    PXOPT_ADD_MODE("-stacked",         "", DETTOOL_MODE_STACKED,       stackedArgs);
+    PXOPT_ADD_MODE("-revertstacked",   "", DETTOOL_MODE_REVERTSTACKED,  revertstackedArgs);
+    PXOPT_ADD_MODE("-tonormalize",     "", DETTOOL_MODE_TONORMALIZE,   tonormalizeArgs);
+    PXOPT_ADD_MODE("-addnormalizedstat",     "", DETTOOL_MODE_ADDNORMALIZEDSTAT,   addnormstatArgs);
+    PXOPT_ADD_MODE("-normalizedstat",  "", DETTOOL_MODE_NORMALIZEDSTAT,   normalizedstatArgs);
+    PXOPT_ADD_MODE("-revertnormalizedstat",  "", DETTOOL_MODE_REVERTNORMALIZEDSTAT,   revertnormalizedstatArgs);
+
+    PXOPT_ADD_MODE("-tonormalizedstat",      "", DETTOOL_MODE_TONORMALIZEDSTAT,    tonormalizedstatArgs);
+    PXOPT_ADD_MODE("-addnormalizedimfile", "", DETTOOL_MODE_ADDNORMALIZEDIMFILE,addnormalizedimfileArgs);
+    PXOPT_ADD_MODE("-normalizedimfile","", DETTOOL_MODE_NORMALIZEDIMFILE, normalizedimfileArgs);
+    PXOPT_ADD_MODE("-revertnormalizedimfile","", DETTOOL_MODE_REVERTNORMALIZEDIMFILE, revertnormalizedimfileArgs);
+    PXOPT_ADD_MODE("-tonormalizedexp", "", DETTOOL_MODE_TONORMALIZEDEXP, tonormalizedexpArgs);
+    PXOPT_ADD_MODE("-addnormalizedexp", "", DETTOOL_MODE_ADDNORMALIZEDEXP, addnormalizedexpArgs);
+    PXOPT_ADD_MODE("-normalizedexp",   "", DETTOOL_MODE_NORMALIZEDEXP, normalizedexpArgs);
+    PXOPT_ADD_MODE("-revertnormalizedexp","", DETTOOL_MODE_REVERTNORMALIZEDEXP, revertnormalizedexpArgs);
+    PXOPT_ADD_MODE("-toresidimfile",   "", DETTOOL_MODE_TORESIDIMFILE, toresidimfileArgs);
+    PXOPT_ADD_MODE("-addresidimfile",  "", DETTOOL_MODE_ADDRESIDIMFILE, addresidimfileArgs);
+    PXOPT_ADD_MODE("-residimfile",     "", DETTOOL_MODE_RESIDIMFILE, residimfileArgs);
+    PXOPT_ADD_MODE("-revertresidimfile", "", DETTOOL_MODE_REVERTRESIDIMFILE, revertresidimfileArgs);
+    PXOPT_ADD_MODE("-toresidexp",      "", DETTOOL_MODE_TORESIDEXP,    toresidexpArgs);
+    PXOPT_ADD_MODE("-addresidexp",     "", DETTOOL_MODE_ADDRESIDEXP,  addresidexpArgs);
+    PXOPT_ADD_MODE("-residexp",        "", DETTOOL_MODE_RESIDEXP,     residexpArgs);
+    PXOPT_ADD_MODE("-revertresidexp",  "", DETTOOL_MODE_REVERTRESIDEXP, revertresidexpArgs);
+    PXOPT_ADD_MODE("-todetrunsummary",  "", DETTOOL_MODE_TODETRUNSUMMARY,  todetrunsummaryArgs);
+    PXOPT_ADD_MODE("-updateresidexp", "", DETTOOL_MODE_UPDATERESIDEXP,updateresidexpArgs);
+    PXOPT_ADD_MODE("-adddetrunsummary", "", DETTOOL_MODE_ADDDETRUNSUMMARY,adddetrunsummaryArgs);
+    PXOPT_ADD_MODE("-detrunsummary", "", DETTOOL_MODE_DETRUNSUMMARY,detrunsummaryArgs);
+    PXOPT_ADD_MODE("-revertdetrunsummary", "", DETTOOL_MODE_REVERTDETRUNSUMMARY, revertdetrunsummaryArgs);
+    PXOPT_ADD_MODE("-updatedetrunsummary", "", DETTOOL_MODE_UPDATEDETRUNSUMMARY, updatedetrunsummaryArgs);
+    PXOPT_ADD_MODE("-updatedetrun", "", DETTOOL_MODE_UPDATEDETRUN, updatedetrunArgs);
+    PXOPT_ADD_MODE("-rerun",           "", DETTOOL_MODE_RERUN,         rerunArgs);
+    PXOPT_ADD_MODE("-register_detrend", "", DETTOOL_MODE_REGISTER_DETREND, register_detrendArgs);
+    PXOPT_ADD_MODE("-register_detrend_imfile", "", DETTOOL_MODE_REGISTER_DETREND_IMFILE, register_detrend_imfileArgs);
+
+    if (!pxGetOptions(stderr, argc, argv, config, modes, argSets)) {
+        psError(PS_ERR_UNKNOWN, false, "option parsing failed");
+        psFree(argSets);
+        psFree(modes);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+    psFree(modes);
+
+    // define Database handle, if used
+    // do this last so we don't setup a connection before CLI options are
+    // validated
+    config->dbh = psMemIncrRefCounter(pmConfigDB(config->modules));
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    return config;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/dettool_correction.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/dettool_correction.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/dettool_correction.c	(revision 22157)
@@ -0,0 +1,251 @@
+/*
+ * dettool_correction.c
+ *
+ * Copyright (C) 2006-2007  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "dettool.h"
+
+bool makecorrectionMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_STR(det_id, config->args, "-det_id", true, false); // required
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false); // optional
+
+    // build the needed where 
+    psMetadata *where = psMetadataAlloc();
+    psMetadataAddS64(where, PS_LIST_TAIL, "det_id", 0, "==", (psS64)atoll(det_id));
+    psArray *runs = detRunSelectRowObjects(config->dbh, where, 1);
+    psFree (where);
+
+    if (!psArrayLength(runs)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    detRunRow *detRun = runs->data[0];
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    detRunInsert(config->dbh,
+         0,             // det_id
+         0,             // iteration
+         detRun->det_type,
+         "correction",  // mode
+         "run",         // state
+         detRun->filelevel,
+         detRun->workdir,
+         detRun->camera,
+         detRun->telescope,
+         detRun->exp_type,
+         detRun->reduction,
+         detRun->filter,
+         detRun->airmass_min,
+         detRun->airmass_max,
+         detRun->exp_time_min,
+         detRun->exp_time_max,
+         detRun->ccd_temp_min,
+         detRun->ccd_temp_max,
+         detRun->posang_min,
+         detRun->posang_max,
+         detRun->registered,
+         detRun->time_begin,
+         detRun->time_end,
+         detRun->use_begin,
+         detRun->use_end,
+         detRun->solang_min,
+         detRun->solang_max,
+         detRun->label,
+         detRun->det_id // parent
+    );
+
+    psFree(runs);
+
+    // print the new detRun
+    psS64 new_det_id = psDBLastInsertID(config->dbh);
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    psMetadata *where_new = psMetadataAlloc();
+    psMetadataAddS64(where, PS_LIST_TAIL, "det_id", 0, "==", new_det_id);
+    psArray *detRuns = psDBSelectRows(config->dbh, "detRun", where_new, 0);
+    psFree(where_new);
+
+    if (!detRuns) {
+        psError(PS_ERR_UNKNOWN, false, "can't find the detRun we just created");
+        return false;
+    }
+    // sanity check results
+    if (psArrayLength(detRuns) != 1) {
+        psAbort("found more then one detRun matching det_id %" PRId64 "(this should not happen)", new_det_id);
+        return false;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, detRuns, "detRun", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(detRuns);
+        return false;
+    }
+    psFree(detRuns);
+
+    return true;
+}
+
+bool tocorrectexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("dettool_tocorrectexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detRun", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+bool tocorrectimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-det_type", "det_type", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("dettool_tocorrectimfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+	// NOTE the SQL uses an intermediate table 'det1' for this query
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "det1"); 
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree (where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detPendingCorrectImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+// NOTE : the command-line args are parsed by register_detrend_imfileMode
+bool addcorrectimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // insert the row into detRegisterImfile
+    if (!register_detrend_imfileMode(config)) {
+        return false;
+    }
+    
+    // automatically stop completed 'correct' detRuns
+    psString query = pxDataGet("dettool_stop_completed_correct_runs.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/difftool.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/difftool.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/difftool.c	(revision 22157)
@@ -0,0 +1,909 @@
+/*
+ * difftool.c
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVB_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+#include <ippdb.h>
+
+#include "pxtools.h"
+#include "difftool.h"
+
+static bool definerunMode(pxConfig *config);
+static bool updaterunMode(pxConfig *config);
+static bool addinputskyfileMode(pxConfig *config);
+static bool inputskyfileMode(pxConfig *config);
+static bool todiffskyfileMode(pxConfig *config);
+static bool adddiffskyfileMode(pxConfig *config);
+static bool diffskyfileMode(pxConfig *config);
+static bool revertdiffskyfileMode(pxConfig *config);
+static bool definepoprunMode(pxConfig *config);
+static bool definebyqueryMode(pxConfig *config);
+
+static bool setdiffRunState(pxConfig *config, psS64 diff_id, const char *state);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = difftoolConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(DIFFTOOL_MODE_DEFINERUN,         definerunMode);
+        MODECASE(DIFFTOOL_MODE_UPDATERUN,         updaterunMode);
+        MODECASE(DIFFTOOL_MODE_ADDINPUTSKYFILE,   addinputskyfileMode);
+        MODECASE(DIFFTOOL_MODE_INPUTSKYFILE,      inputskyfileMode);
+        MODECASE(DIFFTOOL_MODE_TODIFFSKYFILE,     todiffskyfileMode);
+        MODECASE(DIFFTOOL_MODE_ADDDIFFSKYFILE,    adddiffskyfileMode);
+        MODECASE(DIFFTOOL_MODE_DIFFSKYFILE,       diffskyfileMode);
+        MODECASE(DIFFTOOL_MODE_REVERTDIFFSKYFILE, revertdiffskyfileMode);
+        MODECASE(DIFFTOOL_MODE_DEFINEPOPRUN,      definepoprunMode);
+        MODECASE(DIFFTOOL_MODE_DEFINEBYQUERY,             definebyqueryMode);
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+
+static bool definerunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required options
+    PXOPT_LOOKUP_STR(workdir, config->args, "-workdir", true, false);
+    PXOPT_LOOKUP_STR(skycell_id, config->args, "-skycell_id", true, false);
+    PXOPT_LOOKUP_STR(tess_id, config->args, "-tess_id", true, false);
+
+    // default
+    PXOPT_LOOKUP_TIME(registered, config->args, "-registered", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    diffRunRow *run = diffRunRowAlloc(
+            0,          // ID
+            "reg",      // state
+            workdir,
+            NULL,       // dvodb
+            registered,
+            skycell_id,
+            tess_id
+    );
+    if (!run) {
+        psError(PS_ERR_UNKNOWN, false, "failed to alloc diffRun object");
+        return true;
+    }
+    if (!diffRunInsertObject(config->dbh, run)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(run);
+        return true;
+    }
+
+    // get the assigned diff_id
+    run->diff_id = psDBLastInsertID(config->dbh);
+
+    if (!diffRunPrintObject(stdout, run, !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print object");
+            psFree(run);
+            return false;
+    }
+
+    psFree(run);
+
+    return true;
+}
+
+
+static bool updaterunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required options
+    PXOPT_LOOKUP_S64(diff_id, config->args, "-diff_id", true, false);
+    PXOPT_LOOKUP_STR(state, config->args, "-state", true, false);
+
+    if (state) {
+        // set detRun.state to state
+        return setdiffRunState(config, diff_id, state);
+    }
+
+    return true;
+}
+
+
+static bool addinputskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required options
+    PXOPT_LOOKUP_S64(diff_id, config->args, "-diff_id", true, false);
+
+    // optional
+    PXOPT_LOOKUP_S64(stack_id, config->args, "-stack_id", false, false);
+    PXOPT_LOOKUP_S64(warp_id, config->args, "-warp_id", false, false);
+    PXOPT_LOOKUP_STR(kind, config->args, "-kind", false, false);
+
+    // defaults to false
+    PXOPT_LOOKUP_BOOL(template, config->args, "-template", false);
+
+    // must provide either stack_id or warp_id but not BOTH
+    if (!(stack_id || warp_id)) {
+        psError(PS_ERR_UNKNOWN, true, "either -stack_id or -warp_id must be specified");
+        return false;
+    }
+    if (stack_id && warp_id) {
+        psError(PS_ERR_UNKNOWN, true, "either -stack_id or -warp_id must be specified");
+        return false;
+    }
+
+    // if a warp_id was provided we need to lookup the skycell_id and tess_id
+    // from the warpRun
+    psString skycell_id = NULL;
+    psString tess_id = NULL;
+    if (warp_id) {
+        if (!p_psDBRunQuery(config->dbh, "SELECT * from diffRun WHERE diff_id = %" PRId64, diff_id)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+
+        psArray *output = p_psDBFetchResult(config->dbh);
+        if (!output) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+        if (!psArrayLength(output)) {
+            psError(PS_ERR_UNKNOWN, false, "diff_id %" PRId64 " not found", diff_id);
+            psFree(output);
+            return false;
+        }
+
+        diffRunRow *run = diffRunObjectFromMetadata(output->data[0]);
+        skycell_id = run->skycell_id;
+        tess_id = run->tess_id;
+    }
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!diffInputSkyfileInsert(config->dbh,
+            diff_id,
+            template,
+            stack_id ? stack_id : PS_MAX_S64, // defined or NULL
+            warp_id ? warp_id : PS_MAX_S64, // defined or NULL
+            skycell_id,
+            tess_id,
+            kind
+        )) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!p_psDBRunQuery(config->dbh, "SELECT count(diff_id) FROM diffRun JOIN diffInputSkyfile USING(diff_id) WHERE diff_id = %" PRId64, diff_id)) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "diff_id %" PRId64 " not found", diff_id);
+        psFree(output);
+        return false;
+    }
+
+    bool status;
+    psS32 count = psMetadataLookupS32(&status, output->data[0], "count(diff_id)");
+
+    if (count == 2) {
+        if (!setdiffRunState(config, diff_id, "run")) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+    }
+
+    // point of no return
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool inputskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+
+    PXOPT_COPY_S64(config->args, where,  "-diff_id", "diff_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-warp_id", "warp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-skycell_id", "skycell_id", "==");
+    PXOPT_COPY_STR(config->args, where,  "-tess_id", "tess_id", "==");
+    PXOPT_COPY_STR(config->args, where,  "-kind", "kind", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_BOOL(template, config->args, "-template", false);
+
+    // find all rawImfiles matching the default query
+    psString query = pxDataGet("difftool_inputskyfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (template) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "WHERE diffInputSkyfile.template != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "WHERE diffInputSkyfile.template = 0");
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("difftool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "diffInputSkyfile", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool todiffskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where,  "-diff_id", "diff_id", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("difftool_todiffskyfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "diffSkyfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("difftool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "diffSkyfile", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool adddiffskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    
+    PXOPT_LOOKUP_S64(diff_id, config->args, "-diff_id", true, false); // required
+    PXOPT_LOOKUP_S16(code, config->args, "-code", false, false);
+    PXOPT_LOOKUP_STR(uri, config->args, "-uri", (code == 0), false);
+    PXOPT_LOOKUP_STR(path_base, config->args, "-path_base", (code == 0), false);
+
+    // optional
+    PXOPT_LOOKUP_F64(bg, config->args, "-bg", false, false);
+    PXOPT_LOOKUP_F64(bg_stdev, config->args, "-bg_stdev", false, false);
+    PXOPT_LOOKUP_F32(dtime_diff, config->args, "-dtime_diff", false, false);
+    PXOPT_LOOKUP_S32(stamps_num, config->args, "-stamps_num", false, false);
+    PXOPT_LOOKUP_F32(stamps_rms, config->args, "-stamps_rms", false, false);
+    PXOPT_LOOKUP_S32(sources, config->args, "-sources", false, false);
+    PXOPT_LOOKUP_STR(hostname, config->args, "-hostname", false, false);
+    PXOPT_LOOKUP_F32(good_frac, config->args, "-good_frac", false, false);
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!diffSkyfileInsert(config->dbh,
+                           diff_id,
+                           uri,
+                           path_base,
+                           bg,
+                           bg_stdev,
+                           stamps_num,
+                           stamps_rms,
+                           sources,
+                           dtime_diff,
+                           hostname,
+                           good_frac,
+                           code
+          )) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!setdiffRunState(config, diff_id, "stop")) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // point of no return
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool diffskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where,  "-diff_id", "diff_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-skycell_id", "skycell_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-tess_id", "tess_id", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("difftool_skyfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "diffSkyfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("difftool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "diffSkyfile", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool revertdiffskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where,  "-diff_id", "diff_id", "==");
+    PXOPT_COPY_S16(config->args, where, "-code",     "fault", "==");
+
+    psString query = pxDataGet("difftool_revertdiffskyfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "diffSkyfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    if (psDBAffectedRows(config->dbh) < 1) {
+        psError(PS_ERR_UNKNOWN, false, "should have affected atleast 1 row");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool setdiffRunState(pxConfig *config, psS64 diff_id, const char *state)
+{
+    PS_ASSERT_PTR_NON_NULL(state, false);
+
+    // check that state is a valid string value
+    if (!(
+            (strncmp(state, "run", 4) == 0)
+            || (strncmp(state, "stop", 5) == 0)
+            || (strncmp(state, "reg", 4) == 0)
+        )
+    ) {
+        psError(PS_ERR_UNKNOWN, false,
+                "invalid diffRun state: %s", state);
+        return false;
+    }
+
+    char *query = "UPDATE diffRun SET state = '%s' WHERE diff_id = %"PRId64;
+    if (!p_psDBRunQuery(config->dbh, query, state, diff_id)) {
+        psError(PS_ERR_UNKNOWN, false,
+                "failed to change state for diff_id %"PRId64, diff_id);
+        return false;
+    }
+
+    return true;
+}
+
+// Generate a single populated run
+static bool populatedrun(const char *workdir, // Working directory
+                         const char *skycell_id, // Skycell identifier
+                         const char *tess_id, // Tessellation identifier
+                         psS64 input_warp_id, // Warp identifier for input image, PS_MAX_S64 for none
+                         psS64 input_stack_id, // Stack identifier for input image, PS_MAX_S64 for none
+                         psS64 template_warp_id, // Warp identifier for template image, PS_MAX_S64 for none
+                         psS64 template_stack_id, // Stack identifier for template image, PS_MAX_S64 for none
+                         pxConfig *config // Configuration
+                         )
+{
+    PS_ASSERT_STRING_NON_EMPTY(workdir, false);
+    PS_ASSERT_STRING_NON_EMPTY(skycell_id, false);
+    PS_ASSERT_STRING_NON_EMPTY(tess_id, false);
+    if ((input_warp_id == PS_MAX_S64 && input_stack_id == PS_MAX_S64) ||
+        (input_warp_id != PS_MAX_S64 && input_stack_id != PS_MAX_S64)) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "One, and only one, input must be defined.");
+        return false;
+    }
+    if ((template_warp_id == PS_MAX_S64 && template_stack_id == PS_MAX_S64) ||
+        (template_warp_id != PS_MAX_S64 && template_stack_id != PS_MAX_S64)) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "One, and only one, template must be defined.");
+        return false;
+    }
+
+    // default
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_TIME(registered, config->args, "-registered", false, false);
+
+    diffRunRow *run = diffRunRowAlloc(
+            0,          // ID
+            "reg",      // state
+            workdir,
+            NULL,       // dvodb
+            registered,
+            skycell_id,
+            tess_id
+    );
+
+    if (!run) {
+        psError(PS_ERR_UNKNOWN, false, "failed to alloc diffRun object");
+        return true;
+    }
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!diffRunInsertObject(config->dbh, run)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(run);
+        return true;
+    }
+
+    // get the assigned diff_id
+    run->diff_id = psDBLastInsertID(config->dbh);
+
+    // Template
+    if (!diffInputSkyfileInsert(config->dbh,
+            run->diff_id,
+            true,
+            template_stack_id,
+            template_warp_id,
+            skycell_id,
+            tess_id,
+            NULL    // kind
+        )) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // Input
+    if (!diffInputSkyfileInsert(config->dbh,
+            run->diff_id,
+            false,
+            input_stack_id,
+            input_warp_id,
+            skycell_id,
+            tess_id,
+            NULL    // kind
+        )) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    char *query = "UPDATE diffRun SET state = 'run' WHERE diff_id = '%" PRId64 "'";
+    if (!p_psDBRunQuery(config->dbh, query, run->diff_id)) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false,
+                "failed to set state to run for diff_id %" PRId64, run->diff_id);
+        return false;
+    }
+
+    // point of no return
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!diffRunPrintObject(stdout, run, !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print object");
+            psFree(run);
+            return false;
+    }
+
+    psFree(run);
+
+    return true;
+}
+
+
+static bool definepoprunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_STR(workdir, config->args, "-workdir", true, false); // required options
+    PXOPT_LOOKUP_STR(skycell_id, config->args, "-skycell_id", true, false); // required options
+    PXOPT_LOOKUP_STR(tess_id, config->args, "-tess_id", true, false); // required options
+    PXOPT_LOOKUP_S64(template_warp_id, config->args, "-template_warp_id", false, false);
+    PXOPT_LOOKUP_S64(template_stack_id, config->args, "-template_stack_id", false, false);
+    PXOPT_LOOKUP_S64(input_warp_id, config->args, "-input_warp_id", false, false);
+    PXOPT_LOOKUP_S64(input_stack_id, config->args, "-input_stack_id", false, false);
+
+    if (template_stack_id && template_warp_id) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Only one template can be defined.");
+        return false;
+    }
+    if (!template_stack_id && !template_warp_id) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "No template has been defined (-template_stack_id or -template_warp_id)");
+        return false;
+    }
+
+    if (input_stack_id && input_warp_id) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Only one input can be defined.");
+        return false;
+    }
+    if (!input_stack_id && !input_warp_id) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "No input has been defined (-input_stack_id or -input_warp_id)");
+        return false;
+    }
+
+    return populatedrun(workdir, skycell_id, tess_id,
+                        input_warp_id ? input_warp_id : PS_MAX_S64,
+                        input_stack_id ? input_stack_id : PS_MAX_S64,
+                        template_warp_id ? template_warp_id : PS_MAX_S64,
+                        template_stack_id ? template_stack_id : PS_MAX_S64,
+                        config);
+
+    return true;
+}
+
+
+static bool definebyqueryMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+
+    PXOPT_COPY_S64(config->args, where,  "-diff_id", "diff_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-warp_id", "warp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-skycell_id", "skycell_id", "==");
+    PXOPT_COPY_STR(config->args, where,  "-tess_id", "tess_id", "==");
+    PXOPT_COPY_STR(config->args, where,  "-kind", "kind", "==");
+    PXOPT_LOOKUP_STR(workdir, config->args, "-workdir", true, false); // required options
+    PXOPT_LOOKUP_TIME(registered, config->args, "-registered", false, false);
+    PXOPT_LOOKUP_BOOL(template, config->args, "-template", false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    // find all things to queue
+    psString query = pxDataGet("difftool_queue.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (template) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "WHERE diffInputSkyfile.template != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "WHERE diffInputSkyfile.template = 0");
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("difftool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    long numGood = 0;                   // Number of good rows added
+    for (long i = 0; i < output->n; i++) {
+        psMetadata *row = output->data[i]; // Output row from query
+
+        // Selected parameters
+        bool mdok;                      // Status of MD lookup
+        const char *skycell_id = psMetadataLookupStr(&mdok, row, "skycell_id");
+        if (!mdok || !skycell_id) {
+            psWarning("skycell_id not found --- ignoring row %ld", i);
+            continue;
+        }
+        const char *tess_id = psMetadataLookupStr(&mdok, row, "tess_id");
+        if (!mdok || !tess_id) {
+            psWarning("tess_id not found --- ignoring row %ld", i);
+            continue;
+        }
+        psS64 stack_id = psMetadataLookupS64(&mdok, row, "stack_id");
+        if (!mdok) {
+            psWarning("stack_id not found --- ignoring row %ld", i);
+            continue;
+        }
+        psS64 warp_id = psMetadataLookupS64(&mdok, row, "warp_id");
+        if (!mdok) {
+            psWarning("warp_id not found --- ignoring row %ld", i);
+            continue;
+        }
+
+        if (!populatedrun(workdir, skycell_id, tess_id, warp_id, PS_MAX_S64, PS_MAX_S64, stack_id, config)) {
+            psWarning("Unable to add run for %s,%s,%" PRId64 ",%" PRId64, skycell_id, tess_id,
+                      warp_id, stack_id);
+            psErrorClear();
+            continue;
+        }
+
+        numGood++;
+    }
+
+    psLogMsg("difftool", PS_LOG_INFO, "%ld runs added.", numGood);
+
+    psFree(output);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/difftool.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/difftool.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/difftool.h	(revision 22157)
@@ -0,0 +1,41 @@
+/*
+ * difftool.h
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef DIFFTOOL_H
+#define DIFFTOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    DIFFTOOL_MODE_NONE           = 0x0,
+    DIFFTOOL_MODE_DEFINERUN,
+    DIFFTOOL_MODE_UPDATERUN,
+    DIFFTOOL_MODE_ADDINPUTSKYFILE,
+    DIFFTOOL_MODE_INPUTSKYFILE,
+    DIFFTOOL_MODE_TODIFFSKYFILE,
+    DIFFTOOL_MODE_ADDDIFFSKYFILE,
+    DIFFTOOL_MODE_DIFFSKYFILE,
+    DIFFTOOL_MODE_REVERTDIFFSKYFILE,
+    DIFFTOOL_MODE_DEFINEPOPRUN,
+    DIFFTOOL_MODE_DEFINEBYQUERY,
+} difftoolMode;
+
+pxConfig *difftoolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // DIFFTOOL_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/difftoolConfig.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/difftoolConfig.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/difftoolConfig.c	(revision 22157)
@@ -0,0 +1,175 @@
+/*
+ * difftoolConfig.c
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "difftool.h"
+
+pxConfig *difftoolConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (!config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    psTime *now = psTimeGetNow(PS_TIME_TAI);
+
+    // -definerun
+    psMetadata *definerunArgs = psMetadataAlloc();
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-workdir", 0,            "define workdir (required)", NULL);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-skycell_id",  0,            "define skycell ID (required)", NULL);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-tess_id",  0,            "define tessellation ID (required)", NULL);
+    psMetadataAddTime(definerunArgs, PS_LIST_TAIL, "-registered",  0,            "time detrend run was registered", now);
+    psMetadataAddBool(definerunArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -updaterun
+    psMetadata *updaterunArgs = psMetadataAlloc();
+    psMetadataAddS64(updaterunArgs, PS_LIST_TAIL, "-diff_id", 0,            "define diff ID (required)", 0);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-state", 0,            "set state (required)", NULL);
+
+    // -addinputskyfile
+    psMetadata *addinputskyfileArgs = psMetadataAlloc();
+    psMetadataAddS64(addinputskyfileArgs, PS_LIST_TAIL, "-diff_id", 0,            "define diff ID (required)", 0);
+    psMetadataAddS64(addinputskyfileArgs, PS_LIST_TAIL, "-stack_id", 0,            "define stack ID", 0);
+    psMetadataAddS64(addinputskyfileArgs, PS_LIST_TAIL, "-warp_id", 0,            "define warp ID", 0);
+    psMetadataAddStr(addinputskyfileArgs, PS_LIST_TAIL, "-kind", 0,            "define kind", NULL);
+    psMetadataAddBool(addinputskyfileArgs, PS_LIST_TAIL, "-template",  0,            "this sky cell file is the subtrahend", false);
+
+    // -inputskyfile
+    psMetadata *inputskyfileArgs = psMetadataAlloc();
+    psMetadataAddS64(inputskyfileArgs, PS_LIST_TAIL, "-diff_id", 0,            "search by diff ID", 0);
+    psMetadataAddS64(inputskyfileArgs, PS_LIST_TAIL, "-warp_id", 0,            "search by warp ID", 0);
+    psMetadataAddStr(inputskyfileArgs, PS_LIST_TAIL, "-skycell_id", 0,            "search by skycell ID", NULL);
+    psMetadataAddStr(inputskyfileArgs, PS_LIST_TAIL, "-tess_id", 0,            "search by tess ID", NULL);
+    psMetadataAddStr(inputskyfileArgs, PS_LIST_TAIL, "-kind", 0,            "search by kind", NULL);
+    psMetadataAddBool(inputskyfileArgs, PS_LIST_TAIL, "-template",  0,            "search by subtrahend", false);
+    psMetadataAddU64(inputskyfileArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(inputskyfileArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -todiffskyfile
+    psMetadata *todiffskyfileArgs = psMetadataAlloc();
+    psMetadataAddS64(todiffskyfileArgs, PS_LIST_TAIL, "-diff_id", 0,            "search by diff ID", 0);
+    psMetadataAddU64(todiffskyfileArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(todiffskyfileArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -adddiffskyfile
+    psMetadata *adddiffskyfileArgs = psMetadataAlloc();
+    psMetadataAddS64(adddiffskyfileArgs, PS_LIST_TAIL, "-diff_id", 0,            "define warp ID (required)", 0);
+    psMetadataAddS16(adddiffskyfileArgs, PS_LIST_TAIL, "-code",  0,            "set fault code", 0);
+    psMetadataAddStr(adddiffskyfileArgs, PS_LIST_TAIL, "-uri", 0,            "define URI of file", 0);
+    psMetadataAddStr(adddiffskyfileArgs, PS_LIST_TAIL, "-path_base", 0,            "define base output location", 0);
+    psMetadataAddF64(adddiffskyfileArgs, PS_LIST_TAIL, "-bg",  0,            "define exposure background", NAN);
+    psMetadataAddF64(adddiffskyfileArgs, PS_LIST_TAIL, "-bg_stdev",  0,            "define exposure background mean stdev", NAN);
+    psMetadataAddF32(adddiffskyfileArgs, PS_LIST_TAIL, "-dtime_diff",  0,            "define elapsed processing time", NAN);
+    psMetadataAddS32(adddiffskyfileArgs, PS_LIST_TAIL, "-stamps_num",  0,            "define subtraction stamp number", 0);
+    psMetadataAddF32(adddiffskyfileArgs, PS_LIST_TAIL, "-stamps_rms",  0,            "define subtraction stamp rms", NAN);
+    psMetadataAddS32(adddiffskyfileArgs, PS_LIST_TAIL, "-sources",  0,            "define number of sources", 0);
+    psMetadataAddStr(adddiffskyfileArgs, PS_LIST_TAIL, "-hostname", 0,            "define hostname", 0);
+    psMetadataAddF32(adddiffskyfileArgs, PS_LIST_TAIL, "-good_frac",  0,            "define %% of good pixels", NAN);
+
+    // -diffskyfile
+    psMetadata *diffskyfileArgs = psMetadataAlloc();
+    psMetadataAddS64(diffskyfileArgs, PS_LIST_TAIL, "-diff_id", 0,            "search by warp ID", 0);
+    psMetadataAddStr(diffskyfileArgs , PS_LIST_TAIL, "-skycell_id",  0,            "define skycell ID", NULL);
+    psMetadataAddStr(diffskyfileArgs, PS_LIST_TAIL, "-tess_id",  0,            "define tessellation ID", NULL);
+    psMetadataAddU64(diffskyfileArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(diffskyfileArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -revertdiffskyfile
+    psMetadata *revertdiffskyfileArgs = psMetadataAlloc();
+    psMetadataAddS64(revertdiffskyfileArgs, PS_LIST_TAIL, "-diff_id", 0,            "define warp ID (required)", 0);
+    psMetadataAddS16(revertdiffskyfileArgs, PS_LIST_TAIL, "-code",  0,            "search by fault code", 0);
+
+    // -definepoprun
+    psMetadata *definepoprunArgs = psMetadataAlloc();
+    psMetadataAddStr(definepoprunArgs, PS_LIST_TAIL, "-workdir", 0,            "define workdir (required)", NULL);
+    psMetadataAddTime(definepoprunArgs, PS_LIST_TAIL, "-registered",  0,            "time detrend run was registered", now);
+    psMetadataAddStr(definepoprunArgs, PS_LIST_TAIL, "-skycell_id",  0,            "define skycell ID (required)", NULL);
+    psMetadataAddStr(definepoprunArgs, PS_LIST_TAIL, "-tess_id",  0,            "define tessellation ID (required)", NULL);
+    psMetadataAddBool(definepoprunArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+    psMetadataAddS64(definepoprunArgs, PS_LIST_TAIL, "-template_warp_id", 0,            "define warp ID for template", 0);
+    psMetadataAddS64(definepoprunArgs, PS_LIST_TAIL, "-template_stack_id", 0,            "define stack ID for template", 0);
+    psMetadataAddS64(definepoprunArgs, PS_LIST_TAIL, "-input_warp_id", 0,            "define warp ID for input", 0);
+    psMetadataAddS64(definepoprunArgs, PS_LIST_TAIL, "-input_stack_id", 0,            "define stack ID for input", 0);
+
+    // -definebyquery
+    psMetadata *queueArgs = psMetadataAlloc();
+    psMetadataAddS64(queueArgs, PS_LIST_TAIL, "-diff_id", 0,            "search by diff ID", 0);
+    psMetadataAddS64(queueArgs, PS_LIST_TAIL, "-warp_id", 0,            "search by warp ID", 0);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-skycell_id", 0,            "search by skycell ID", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-tess_id", 0,            "search by tess ID", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-kind", 0,            "search by kind", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-workdir", 0,            "define workdir (required)", NULL);
+    psMetadataAddTime(queueArgs, PS_LIST_TAIL, "-registered",  0,            "time detrend run was registered", now);
+    psMetadataAddBool(queueArgs, PS_LIST_TAIL, "-template",  0,            "search by subtrahend", false);
+    psMetadataAddBool(queueArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    psFree(now);
+
+    psMetadata *argSets = psMetadataAlloc();
+    psMetadata *modes = psMetadataAlloc();
+
+    PXOPT_ADD_MODE("-definerun",        "", DIFFTOOL_MODE_DEFINERUN,         definerunArgs);
+    PXOPT_ADD_MODE("-updaterun",        "", DIFFTOOL_MODE_UPDATERUN,         updaterunArgs);
+    PXOPT_ADD_MODE("-addinputskyfile",  "", DIFFTOOL_MODE_ADDINPUTSKYFILE,   addinputskyfileArgs);
+    PXOPT_ADD_MODE("-inputskyfile",     "", DIFFTOOL_MODE_INPUTSKYFILE,      inputskyfileArgs);
+    PXOPT_ADD_MODE("-todiffskyfile",    "", DIFFTOOL_MODE_TODIFFSKYFILE,     todiffskyfileArgs);
+    PXOPT_ADD_MODE("-adddiffskyfile",   "", DIFFTOOL_MODE_ADDDIFFSKYFILE,    adddiffskyfileArgs);
+    PXOPT_ADD_MODE("-diffskyfile",      "", DIFFTOOL_MODE_DIFFSKYFILE,       diffskyfileArgs);
+    PXOPT_ADD_MODE("-revertdiffskyfile","", DIFFTOOL_MODE_REVERTDIFFSKYFILE, revertdiffskyfileArgs);
+    PXOPT_ADD_MODE("-definepoprun",     "", DIFFTOOL_MODE_DEFINEPOPRUN,      definepoprunArgs);
+    PXOPT_ADD_MODE("-definebyquery",            "", DIFFTOOL_MODE_DEFINEBYQUERY,             queueArgs);
+
+    if (!pxGetOptions(stderr, argc, argv, config, modes, argSets)) {
+        psError(PS_ERR_UNKNOWN, true, "option parsing failed");
+        psFree(argSets);
+        psFree(modes);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+    psFree(modes);
+
+    // define Database handle, if used
+    // do this last so we don't setup a connection before CLI options are
+    // validated
+    config->dbh = psMemIncrRefCounter(pmConfigDB(config->modules));
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    return config;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/fakemagic
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/fakemagic	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/fakemagic	(revision 22157)
@@ -0,0 +1,25 @@
+#!/usr/bin/env perl
+
+# Copyright (C) 2007  Joshua Hoblitt
+#
+# $Id: fakemagic,v 1.1 2007-11-05 23:29:54 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version pass_through );
+use Pod::Usage qw( pod2usage );
+
+my ($output);
+GetOptions(
+    'output|o=s'    => \$output,
+) || pod2usage( 2 );
+
+pod2usage( -msg => "Required options: --output <filepath>", -exitval => 2 )
+        unless defined $output;
+
+open(my $fh, ">$output") or die "can't open file $output: $!";
+close($fh) or die "can't close file $output: $!";
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/faketool.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/faketool.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/faketool.c	(revision 22157)
@@ -0,0 +1,932 @@
+/*
+ * faketool.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "pxtools.h"
+#include "pxdata.h"
+
+#include "faketool.h"
+
+static bool definebyqueryMode(pxConfig *config);
+static bool updaterunMode(pxConfig *config);
+static bool pendingexpMode(pxConfig *config);
+static bool pendingimfileMode(pxConfig *config);
+static bool addprocessedimfileMode(pxConfig *config);
+static bool processedimfileMode(pxConfig *config);
+static bool revertprocessedimfileMode(pxConfig *config);
+static bool updateprocessedimfileMode(pxConfig *config);
+static bool blockMode(pxConfig *config);
+static bool maskedMode(pxConfig *config);
+static bool unmaskedMode(pxConfig *config);
+static bool unblockMode(pxConfig *config);
+
+static bool fakeProcessedCompleteExp(pxConfig *config);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv) {
+    psLibInit(NULL);
+
+    pxConfig *config = faketoolConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(FAKETOOL_MODE_DEFINEBYQUERY,                   definebyqueryMode);
+        MODECASE(FAKETOOL_MODE_UPDATERUN,               updaterunMode);
+        MODECASE(FAKETOOL_MODE_PENDINGEXP,              pendingexpMode);
+        MODECASE(FAKETOOL_MODE_PENDINGIMFILE,           pendingimfileMode);
+        MODECASE(FAKETOOL_MODE_ADDPROCESSEDIMFILE,      addprocessedimfileMode);
+        MODECASE(FAKETOOL_MODE_PROCESSEDIMFILE,         processedimfileMode);
+        MODECASE(FAKETOOL_MODE_REVERTPROCESSEDIMFILE,   revertprocessedimfileMode);
+        MODECASE(FAKETOOL_MODE_UPDATEPROCESSEDIMFILE,updateprocessedimfileMode);
+        MODECASE(FAKETOOL_MODE_BLOCK,                   blockMode);
+        MODECASE(FAKETOOL_MODE_MASKED,                  maskedMode);
+        MODECASE(FAKETOOL_MODE_UNMASKED,                unmaskedMode);
+        MODECASE(FAKETOOL_MODE_UNBLOCK,                 unblockMode);
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+
+static bool definebyqueryMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-exp_id", "exp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_name", "exp_name", "==");
+    PXOPT_COPY_STR(config->args, where, "-inst", "camera", "==");
+    PXOPT_COPY_STR(config->args, where, "-telescope", "telescope", "==");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_begin", "dateobs", ">=");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_end", "dateobs", "<=");
+    PXOPT_COPY_STR(config->args, where, "-exp_tag", "exp_tag", "==");
+    PXOPT_COPY_STR(config->args, where, "-filelevel", "filelevel", "==");
+    PXOPT_COPY_STR(config->args, where, "-reduction", "reduction", "==");
+    PXOPT_COPY_STR(config->args, where, "-filter", "filter", "==");
+    PXOPT_COPY_F64(config->args, where, "-airmass_min", "airmass", ">=");
+    PXOPT_COPY_F64(config->args, where, "-airmass_max", "airmass", "<");
+    PXOPT_COPY_F64(config->args, where, "-ra_min", "ra", ">=");
+    PXOPT_COPY_F64(config->args, where, "-ra_max", "ra", "<");
+    PXOPT_COPY_F64(config->args, where, "-decl_min", "decl", ">=");
+    PXOPT_COPY_F64(config->args, where, "-decl_max", "decl", "<");
+    PXOPT_COPY_F32(config->args, where, "-exp_time_min", "exp_time", ">=");
+    PXOPT_COPY_F32(config->args, where, "-exp_time_max", "exp_time", "<");
+    PXOPT_COPY_F32(config->args, where, "-sat_pixel_frac_min", "sat_pixel_frac", ">=");
+    PXOPT_COPY_F32(config->args, where, "-sat_pixel_frac_max", "sat_pixel_frac", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_min", "bt", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_max", "bt", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_stdev_min", "bg_stdev", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_stdev_max", "bg_stdev", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_mean_stdev_min", "bg_mean_stdev", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_mean_stdev_max", "bg_mean_stdev", "<");
+    PXOPT_COPY_F64(config->args, where, "-alt_min", "alt", ">=");
+    PXOPT_COPY_F64(config->args, where, "-alt_max", "alt", "<");
+    PXOPT_COPY_F64(config->args, where, "-az_min", "az", ">=");
+    PXOPT_COPY_F64(config->args, where, "-az_max", "az", "<");
+    PXOPT_COPY_F32(config->args, where, "-ccd_temp_min", "ccd_temp", ">=");
+    PXOPT_COPY_F32(config->args, where, "-ccd_temp_max", "ccd_temp", "<");
+    PXOPT_COPY_F64(config->args, where, "-posang_min", "posang", ">=");
+    PXOPT_COPY_F64(config->args, where, "-posang_max", "posang", "<");
+    PXOPT_COPY_STR(config->args, where, "-object", "object", "==");
+    PXOPT_COPY_F32(config->args, where, "-solang_min", "solang", ">=");
+    PXOPT_COPY_F32(config->args, where, "-solang_max", "solang", "<");
+
+    if (!psListLength(where->list)
+        && !psMetadataLookupBool(NULL, config->args, "-all")) {
+        psFree(where);
+        psError(PXTOOLS_ERR_DATA, false, "search parameters are required");
+        return false;
+    }
+
+    PXOPT_LOOKUP_STR(workdir, config->args, "-set_workdir", false, false);
+    PXOPT_LOOKUP_STR(label, config->args, "-set_label", false, false);
+    PXOPT_LOOKUP_STR(reduction, config->args, "-set_reduction", false, false);
+    PXOPT_LOOKUP_STR(expgroup, config->args, "-set_expgroup", false, false);
+    PXOPT_LOOKUP_STR(dvodb, config->args, "-set_dvodb", false, false);
+    PXOPT_LOOKUP_STR(tess_id, config->args, "-set_tess_id", false, false);
+    PXOPT_LOOKUP_STR(end_stage, config->args, "-set_end_stage", false, false);
+
+    // default
+    PXOPT_COPY_STR(config->args, where, "-exp_type", "exp_type", "==");
+    PXOPT_LOOKUP_BOOL(pretend, config->args, "-pretend", false);
+
+    // find the exp_id of all the exposures that we want to queue up.
+    psString query = pxDataGet("faketool_find_camrun.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        psFree(where);
+        return false;
+    }
+
+    if (where && psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereSQL(where, NULL);
+        psStringAppend(&query, " %s", whereClause);
+        psFree(whereClause);
+    }
+
+    psFree(where);
+
+    if (pretend) {
+        // then stop before running the query
+        fprintf(stderr, "%s\n", query);
+        psFree(query);
+        return true;
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("faketool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // start a transaction so we don't end up with an exp without any associted
+    // imfiles
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(output);
+        return false;
+    }
+
+    // loop over our list of cam_ids
+    for (long i = 0; i < psArrayLength(output); i++) {
+        psMetadata *md = output->data[i];
+
+        bool status;
+        psS64 cam_id = psMetadataLookupS64(&status, md, "cam_id");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for cam_id");
+            psFree(output);
+            return false;
+        }
+
+        // queue the exp
+        if (!pxfakeQueueByCamID(config, cam_id, workdir, label, reduction, expgroup, dvodb, tess_id, end_stage)) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false,
+                    "failed to trying to queue cam_id: %" PRId64, cam_id);
+            psFree(output);
+            return false;
+        }
+    }
+    psFree(output);
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool updaterunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-fake_id", "fake_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-exp_id", "exp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_name", "exp_name", "==");
+    PXOPT_COPY_STR(config->args, where, "-inst", "camera", "==");
+    PXOPT_COPY_STR(config->args, where, "-telescope", "telescope", "==");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_begin", "dateobs", ">=");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_end", "dateobs", "<=");
+    PXOPT_COPY_STR(config->args, where, "-exp_tag", "exp_tag", "==");
+    PXOPT_COPY_STR(config->args, where, "-filelevel", "filelevel", "==");
+    PXOPT_COPY_STR(config->args, where, "-reduction", "reduction", "==");
+    PXOPT_COPY_STR(config->args, where, "-filter", "filter", "==");
+    PXOPT_COPY_F64(config->args, where, "-airmass_min", "airmass", ">=");
+    PXOPT_COPY_F64(config->args, where, "-airmass_max", "airmass", "<");
+    PXOPT_COPY_F64(config->args, where, "-ra_min", "ra", ">=");
+    PXOPT_COPY_F64(config->args, where, "-ra_max", "ra", "<");
+    PXOPT_COPY_F64(config->args, where, "-decl_min", "decl", ">=");
+    PXOPT_COPY_F64(config->args, where, "-decl_max", "decl", "<");
+    PXOPT_COPY_F32(config->args, where, "-exp_time_min", "exp_time", ">=");
+    PXOPT_COPY_F32(config->args, where, "-exp_time_max", "exp_time", "<");
+    PXOPT_COPY_F32(config->args, where, "-sat_pixel_frac_min", "sat_pixel_frac", ">=");
+    PXOPT_COPY_F32(config->args, where, "-sat_pixel_frac_max", "sat_pixel_frac", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_min", "bt", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_max", "bt", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_stdev_min", "bg_stdev", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_stdev_max", "bg_stdev", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_mean_stdev_min", "bg_mean_stdev", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_mean_stdev_max", "bg_mean_stdev", "<");
+    PXOPT_COPY_F64(config->args, where, "-alt_min", "alt", ">=");
+    PXOPT_COPY_F64(config->args, where, "-alt_max", "alt", "<");
+    PXOPT_COPY_F64(config->args, where, "-az_min", "az", ">=");
+    PXOPT_COPY_F64(config->args, where, "-az_max", "az", "<");
+    PXOPT_COPY_F32(config->args, where, "-ccd_temp_min", "ccd_temp", ">=");
+    PXOPT_COPY_F32(config->args, where, "-ccd_temp_max", "ccd_temp", "<");
+    PXOPT_COPY_F64(config->args, where, "-posang_min", "posang", ">=");
+    PXOPT_COPY_F64(config->args, where, "-posang_max", "posang", "<");
+    PXOPT_COPY_STR(config->args, where, "-object", "object", "==");
+    PXOPT_COPY_F32(config->args, where, "-solang_min", "solang", ">=");
+    PXOPT_COPY_F32(config->args, where, "-solang_max", "solang", "<");
+
+    if (!psListLength(where->list)
+        && !psMetadataLookupBool(NULL, config->args, "-all")) {
+        psFree(where);
+        where = NULL;
+        psError(PXTOOLS_ERR_DATA, false, "search parameters are required");
+        return false;
+    }
+
+    PXOPT_LOOKUP_STR(state, config->args, "-state", false, false);
+    PXOPT_LOOKUP_STR(label, config->args, "-label", false, false);
+
+    if ((!state) && (!label)) {
+        psError(PXTOOLS_ERR_DATA, false, "parameters are required");
+        psFree(where);
+        return false;
+    }
+
+    if (state) {
+        // set fakeRun.state to state
+        if (!pxfakeRunSetStateByQuery(config, where, state)) {
+            psFree(where);
+            return false;
+        }
+    }
+
+    if (label) {
+        // set fakeRun.label to label
+        if (!pxfakeRunSetLabelByQuery(config, where, label)) {
+            psFree(where);
+            return false;
+        }
+    }
+
+    psFree(where);
+
+    return true;
+}
+
+
+static bool pendingexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-fake_id", "fake_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-cam_id", "cam_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-chip_id", "chip_id", "==");
+
+    psString query = pxDataGet("faketool_find_pendingexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        psFree(where);
+        return false;
+    }
+
+    if (where && psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("camtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negate simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "fakePendingExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool pendingimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-fake_id", "fake_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-exp_id", "exp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-class_id", "rawImfile.class_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-inst", "rawExp.telescope", "==");
+    PXOPT_COPY_STR(config->args, where, "-filter", "rawExp.filter", "==");
+
+    psString query = pxDataGet("faketool_pendingimfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (where && psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("faketool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "fakePendingImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool addprocessedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // fake_id, ext_tag, class_id are required
+    PXOPT_LOOKUP_S64(fake_id, config->args,     "-fake_id", true, false);
+    PXOPT_LOOKUP_S64(exp_id, config->args,      "-exp_id", true, false);
+    PXOPT_LOOKUP_STR(class_id, config->args,    "-class_id", true, false);
+
+    // optional
+    PXOPT_LOOKUP_STR(uri, config->args,         "-uri", false, false);
+
+    PXOPT_LOOKUP_F32(dtime_fake, config->args,  "-dtime_fake", false, false);
+    PXOPT_LOOKUP_STR(hostname, config->args,    "-hostname", false, false);
+    PXOPT_LOOKUP_STR(path_base, config->args,   "-path_base", false, false);
+
+    // default values
+    PXOPT_LOOKUP_S16(code, config->args,        "-code", false, false);
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!fakeProcessedImfileInsert(config->dbh,
+                                   fake_id,
+                                   exp_id,
+                                   class_id,
+                                   uri,
+                                   dtime_fake,
+                                   hostname,
+                                   path_base,
+                                   code,
+                                   NULL         // epoch
+            )) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // XXX I've decided to make the transaction cover the Exp migration as
+    // well.  Otherwise, if the last imfile in an exp is moved and the exp
+    // migration fails then the data base is left in a satuation where the exp
+    // migration can't happen.
+
+    if (!fakeProcessedCompleteExp(config)) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool processedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_BOOL(faulted, config->args, "-faulted", false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-fake_id", "fakeRun.fake_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-exp_id", "rawExp.exp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-class_id", "rawImfile.class_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-inst", "rawExp.telescope", "==");
+    PXOPT_COPY_STR(config->args, where, "-filter", "rawExp.filter", "==");
+
+    psString query = pxDataGet("faketool_processedimfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (where && psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "fakeProcessedImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "AND fakeProcessedImfile.fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "AND fakeProcessedImfile.fault = 0");
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("faketool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "fakeProcessedImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool revertprocessedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-fake_id", "fake_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-exp_id", "exp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_name", "exp_name", "==");
+    PXOPT_COPY_STR(config->args, where, "-inst", "camera", "==");
+    PXOPT_COPY_STR(config->args, where, "-telescope", "telescope", "==");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_begin", "dateobs", ">=");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_end", "dateobs", "<=");
+    PXOPT_COPY_STR(config->args, where, "-exp_tag", "exp_tag", "==");
+    PXOPT_COPY_STR(config->args, where, "-filelevel", "filelevel", "==");
+    PXOPT_COPY_STR(config->args, where, "-reduction", "reduction", "==");
+    PXOPT_COPY_STR(config->args, where, "-filter", "filter", "==");
+    PXOPT_COPY_F64(config->args, where, "-airmass_min", "airmass", ">=");
+    PXOPT_COPY_F64(config->args, where, "-airmass_max", "airmass", "<");
+    PXOPT_COPY_F64(config->args, where, "-ra_min", "ra", ">=");
+    PXOPT_COPY_F64(config->args, where, "-ra_max", "ra", "<");
+    PXOPT_COPY_F64(config->args, where, "-decl_min", "decl", ">=");
+    PXOPT_COPY_F64(config->args, where, "-decl_max", "decl", "<");
+    PXOPT_COPY_F32(config->args, where, "-exp_time_min", "exp_time", ">=");
+    PXOPT_COPY_F32(config->args, where, "-exp_time_max", "exp_time", "<");
+    PXOPT_COPY_F32(config->args, where, "-sat_pixel_frac_min", "sat_pixel_frac", ">=");
+    PXOPT_COPY_F32(config->args, where, "-sat_pixel_frac_max", "sat_pixel_frac", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_min", "bt", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_max", "bt", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_stdev_min", "bg_stdev", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_stdev_max", "bg_stdev", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_mean_stdev_min", "bg_mean_stdev", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_mean_stdev_max", "bg_mean_stdev", "<");
+    PXOPT_COPY_F64(config->args, where, "-alt_min", "alt", ">=");
+    PXOPT_COPY_F64(config->args, where, "-alt_max", "alt", "<");
+    PXOPT_COPY_F64(config->args, where, "-az_min", "az", ">=");
+    PXOPT_COPY_F64(config->args, where, "-az_max", "az", "<");
+    PXOPT_COPY_F32(config->args, where, "-ccd_temp_min", "ccd_temp", ">=");
+    PXOPT_COPY_F32(config->args, where, "-ccd_temp_max", "ccd_temp", "<");
+    PXOPT_COPY_F64(config->args, where, "-posang_min", "posang", ">=");
+    PXOPT_COPY_F64(config->args, where, "-posang_max", "posang", "<");
+    PXOPT_COPY_STR(config->args, where, "-object", "object", "==");
+    PXOPT_COPY_F32(config->args, where, "-solang_min", "solang", ">=");
+    PXOPT_COPY_F32(config->args, where, "-solang_max", "solang", "<");
+
+    if (!psListLength(where->list)
+        && !psMetadataLookupBool(NULL, config->args, "-all")) {
+        psFree(where);
+        psError(PXTOOLS_ERR_DATA, false, "search parameters are required");
+        return false;
+    }
+
+    psString query = pxDataGet("faketool_revertprocessedimfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        psFree(where);
+        return false;
+    }
+
+    if (where && psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "fakeProcessedImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    return true;
+}
+
+static bool updateprocessedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_S16(code, config->args, "-code", true, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-fake_id", "fake_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-class_id", "class_id", "==");
+
+    if (!pxSetFaultCode(config->dbh, "fakeProcessedImfile", where, code)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to set set fault flag");
+        psFree(where);
+        return false;
+    }
+    psFree(where);
+
+    return true;
+}
+
+
+static bool blockMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_STR(label, config->args, "-label", true, false);
+
+    if (!fakeMaskInsert(config->dbh, label)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool maskedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+
+    psMetadata *where = psMetadataAlloc();
+
+    PXOPT_COPY_STR(config->args, where, "-label", "label", "==");
+
+    if (where->list->n < 1) {
+        psFree(where);
+        where = NULL;
+    }
+
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = psStringCopy("SELECT * FROM fakeMask");
+
+    if (where) {
+        psString whereClause = psDBGenerateWhereSQL(where, NULL);
+        psFree(where);
+        psStringAppend(&query, " %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("faketool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "fakeMask", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+// return the list of labels which are NOT blocked
+static bool unmaskedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+
+    psMetadata *where = psMetadataAlloc();
+
+    PXOPT_COPY_STR(config->args, where, "-label", "label", "==");
+
+    if (where->list->n < 1) {
+        psFree(where);
+        where = NULL;
+    }
+
+    psString query = pxDataGet("faketool_unmasked.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+    
+    if (where) {
+        psString whereClause = psDBGenerateWhereSQL(where, "fakeUnmask");
+        psFree(where);
+        psStringAppend(&query, " %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("faketool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "fakeUnmask", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool unblockMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_STR(label, config->args, "-label", true, false);
+
+    char *query = "DELETE FROM fakeMask WHERE label = '%s'";
+
+    if (!p_psDBRunQuery(config->dbh, query, label)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool fakeProcessedCompleteExp(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // look for completed fakePendingExp
+    // migrate them to fakeProccessedExp & camPendingExp
+    psString query = pxDataGet("faketool_completely_processed_exp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("faketool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    for (long i = 0; i < psArrayLength(output); i++) {
+        psMetadata *row = output->data[i];
+
+        fakeRunRow *fakeRun = fakeRunObjectFromMetadata(row);
+        // set fakeRun.state to 'stop'
+        if (!pxfakeRunSetState(config, fakeRun->fake_id, "stop")) {
+            psError(PS_ERR_UNKNOWN, false, "failed to change fakeRun.state for fake_id: %" PRId64, fakeRun->fake_id);
+            psFree(fakeRun);
+            psFree(output);
+            return false;
+        }
+
+        // should we stop here or proceed on to the cam stage?
+        // NULL for end_stage means go as far as possible
+        if (fakeRun->end_stage && psStrcasestr(fakeRun->end_stage, "fake")) {
+            psFree(fakeRun);
+            continue;
+        }
+        // else continue on...
+
+        // camQueueFakeID() can only be run after fakeRun.state has been set to
+        // stop
+        if (!pxwarpQueueByFakeID(config,
+                    fakeRun->fake_id,
+                    fakeRun->workdir,
+                    fakeRun->label,
+                    fakeRun->dvodb,
+                    fakeRun->tess_id,
+                    fakeRun->end_stage
+        )) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to queue camPendingExp");
+            psFree(fakeRun);
+            psFree(output);
+            return false;
+        }
+        psFree(fakeRun);
+    }
+
+    psFree(output);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/faketool.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/faketool.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/faketool.h	(revision 22157)
@@ -0,0 +1,46 @@
+/*
+ * faketool.h
+ *
+ * Copyright (C) 2006-2008  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef FAKETOOL_H
+#define FAKETOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    FAKETOOL_MODE_NONE      = 0x0,
+    FAKETOOL_MODE_DEFINEBYQUERY,
+    FAKETOOL_MODE_UPDATERUN,
+    FAKETOOL_MODE_PENDINGIMFILE,
+    FAKETOOL_MODE_PENDINGEXP,
+    FAKETOOL_MODE_ADDPROCESSEDIMFILE,
+    FAKETOOL_MODE_PROCESSEDIMFILE,
+    FAKETOOL_MODE_REVERTPROCESSEDIMFILE,
+    FAKETOOL_MODE_UPDATEPROCESSEDIMFILE,
+    FAKETOOL_MODE_BLOCK,
+    FAKETOOL_MODE_MASKED,
+    FAKETOOL_MODE_UNMASKED,
+    FAKETOOL_MODE_UNBLOCK,
+    FAKETOOL_MODE_RETRYPROCESSEDIMFILE,
+    FAKETOOL_MODE_PENDINGCLEANUP,
+    FAKETOOL_MODE_DONECLEANUP,
+} FAKETOOLMode;
+
+pxConfig *faketoolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // FAKETOOL_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/faketoolConfig.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/faketoolConfig.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/faketoolConfig.c	(revision 22157)
@@ -0,0 +1,467 @@
+/*
+ * faketoolConfig.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdint.h>
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "faketool.h"
+
+pxConfig *faketoolConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (! config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    // -definebyquery
+    psMetadata *queueArgs = psMetadataAlloc();
+    // XXX need to allow multiple exp_ids
+    psMetadataAddS64(queueArgs, PS_LIST_TAIL, "-exp_id",  0,
+            "search by exp_id", 0);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-exp_name",  0,
+            "search by exp_name", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-inst",  0,
+            "search for camera", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-telescope",  0,
+            "search for telescope", NULL);
+    psMetadataAddTime(queueArgs, PS_LIST_TAIL, "-dateobs_begin", 0,
+            "search for exposures by time (>=)", NULL);
+    psMetadataAddTime(queueArgs, PS_LIST_TAIL, "-dateobs_end", 0,
+            "search for exposures by time (<)", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-exp_tag",  0,
+            "search by exp_tag", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-exp_type",  0,
+            "search by exp_type", "object");
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-filelevel",  0,
+            "search by filelevel", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-reduction",  0,
+            "search by reduction class", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-filter",  0,
+            "search for filter", NULL);
+    psMetadataAddF32(queueArgs, PS_LIST_TAIL, "-airmass_min",  0,
+            "define min airmass", NAN);
+    psMetadataAddF32(queueArgs, PS_LIST_TAIL, "-airmass_max",  0,
+            "define max airmass", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-ra_min",  0,
+            "define min", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-ra_max",  0,
+            "define max", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-decl_min",  0,
+            "define min", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-decl_max",  0,
+            "define max", NAN);
+    psMetadataAddF32(queueArgs, PS_LIST_TAIL, "-exp_time_min",  0,
+            "define min", NAN);
+    psMetadataAddF32(queueArgs, PS_LIST_TAIL, "-exp_time_max",  0,
+            "define max", NAN);
+    psMetadataAddF32(queueArgs, PS_LIST_TAIL, "-sat_pixel_frac_min",  0,
+            "define max fraction of saturated pixels", NAN);
+    psMetadataAddF32(queueArgs, PS_LIST_TAIL, "-sat_pixel_frac_max",  0,
+            "define min fraction of saturated pixels", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-bg_min",  0,
+            "define max", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-bg_max",  0,
+            "define max", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-bg_stdev_min",  0,
+            "define max", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-bg_stdev_max",  0,
+            "define max", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-bg_mean_stdev_min",  0,
+            "define max", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-bg_mean_stdev_max",  0,
+            "define max", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-alt_min",  0,
+            "define min", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-alt_max",  0,
+            "define max", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-az_min",  0,
+            "define min", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-az_max",  0,
+            "define max", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-ccd_temp_min",  0,
+            "define min ccd tempature", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-ccd_temp_max",  0,
+            "define max ccd tempature", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-posang_min",  0,
+            "define min rotator position angle", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-posang_max",  0,
+            "define max rotator position angle", NAN);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-object",  0,
+            "search by exposure object", NULL);
+    psMetadataAddF32(queueArgs, PS_LIST_TAIL, "-solang_min",  0,
+            "define min solar angle", NAN);
+    psMetadataAddF32(queueArgs, PS_LIST_TAIL, "-solang_max",  0,
+            "define max solar angle", NAN);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-set_workdir",  0,
+            "define workdir", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-set_label",  0,
+            "define label", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-set_reduction",  0,
+            "define reduction class", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-set_expgroup",  0,
+            "define exposure group", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-set_dvodb",  0,
+            "define DVO db", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-set_tess_id",  0,
+            "define tessellation identifier", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-set_end_stage",  0,
+            "define end stage", NULL);
+    psMetadataAddBool(queueArgs, PS_LIST_TAIL, "-pretend",  0,
+            "do not actually modify the database", false);
+    psMetadataAddBool(queueArgs, PS_LIST_TAIL, "-all",  0,
+            "allow everything to be queued without search terms", false);
+
+
+    // -updaterun
+    psMetadata *updaterunArgs = psMetadataAlloc();
+    psMetadataAddS64(updaterunArgs, PS_LIST_TAIL, "-fake_id", 0,
+            "search by fake ID", 0);
+    psMetadataAddS64(updaterunArgs, PS_LIST_TAIL, "-exp_id",  0,
+            "search by exp_id", 0);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-exp_name",  0,
+            "search by exp_name", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-inst",  0,
+            "search for camera", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-telescope",  0,
+            "search for telescope", NULL);
+    psMetadataAddTime(updaterunArgs, PS_LIST_TAIL, "-dateobs_begin", 0,
+            "search for exposures by time (>=)", NULL);
+    psMetadataAddTime(updaterunArgs, PS_LIST_TAIL, "-dateobs_end", 0,
+            "search for exposures by time (<)", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-exp_tag",  0,
+            "search by exp_tag", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-exp_type",  0,
+            "search by exp_type", "object");
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-filelevel",  0,
+            "search by filelevel", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-reduction",  0,
+            "search by reduction class", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-filter",  0,
+            "search for filter", NULL);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-airmass_min",  0,
+            "define min airmass", NAN);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-airmass_max",  0,
+            "define max airmass", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-ra_min",  0,
+            "define min", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-ra_max",  0,
+            "define max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-decl_min",  0,
+            "define min", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-decl_max",  0,
+            "define max", NAN);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-exp_time_min",  0,
+            "define min", NAN);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-exp_time_max",  0,
+            "define max", NAN);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-sat_pixel_frac_min",  0,
+            "define max fraction of saturated pixels", NAN);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-sat_pixel_frac_max",  0,
+            "define min fraction of saturated pixels", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-bg_min",  0,
+            "define max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-bg_max",  0,
+            "define max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-bg_stdev_min",  0,
+            "define max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-bg_stdev_max",  0,
+            "define max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-bg_mean_stdev_min",  0,
+            "define max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-bg_mean_stdev_max",  0,
+            "define max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-alt_min",  0,
+            "define min", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-alt_max",  0,
+            "define max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-az_min",  0,
+            "define min", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-az_max",  0,
+            "define max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-ccd_temp_min",  0,
+            "define min ccd tempature", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-ccd_temp_max",  0,
+            "define max ccd tempature", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-posang_min",  0,
+            "define min rotator position angle", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-posang_max",  0,
+            "define max rotator position angle", NAN);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-object",  0,
+            "search by exposure object", NULL);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-solang_min",  0,
+            "define min solar angle", NAN);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-solang_max",  0,
+            "define max solar angle", NAN);
+
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-state", 0,
+            "set state", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-label", 0,
+            "set label", NULL);
+    psMetadataAddBool(updaterunArgs, PS_LIST_TAIL, "-all",  0,
+            "allow everything to be queued without search terms", false);
+
+    // -pendingexp
+    psMetadata *pendingexpArgs = psMetadataAlloc();
+    psMetadataAddS64(pendingexpArgs, PS_LIST_TAIL, "-cam_id", 0,
+            "search by camtool ID", 0);
+    psMetadataAddS64(pendingexpArgs, PS_LIST_TAIL, "-chip_id", 0,
+            "search by chiptool ID", 0);
+    psMetadataAddU64(pendingexpArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(pendingexpArgs, PS_LIST_TAIL, "-simple", 0,
+            "use the simple output format", false);
+
+    // -pendingimfile
+    psMetadata *pendingimfileArgs = psMetadataAlloc();
+    psMetadataAddS64(pendingimfileArgs, PS_LIST_TAIL, "-fake_id",  0,
+            "search by fake ID", 0);
+    psMetadataAddS64(pendingimfileArgs, PS_LIST_TAIL, "-exp_id",  0,
+            "search by exposure ID", 0);
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-class_id",  0,
+            "search by class ID", NULL);
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-inst",  0,
+            "search by camera of interest", NULL);
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-filter",  0,
+            "search by filter of interest", NULL);
+    psMetadataAddU64(pendingimfileArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(pendingimfileArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -addprocessedimfile
+    psMetadata *addprocessedimfileArgs = psMetadataAlloc();
+    psMetadataAddS64(addprocessedimfileArgs, PS_LIST_TAIL, "-fake_id",  0,
+            "define fake ID (required)", 0);
+    psMetadataAddS64(addprocessedimfileArgs, PS_LIST_TAIL, "-exp_id",  0,
+            "define exposure ID (required)", 0);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-class_id",  0,
+            "define class ID (required)", NULL);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-uri",  0,
+            "define URL", NULL);
+    psMetadataAddF32(addprocessedimfileArgs, PS_LIST_TAIL, "-dtime_fake",  0,
+            "define elapsed time for detrend (seconds)", NAN);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-hostname",  0,
+            "define hostname", NULL);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-path_base",  0,
+            "define base output location", NULL);
+    psMetadataAddS16(addprocessedimfileArgs, PS_LIST_TAIL, "-code",  0,
+            "set fault code", 0);
+
+    // -processedimfile
+    psMetadata *processedimfileArgs = psMetadataAlloc();
+    psMetadataAddS64(processedimfileArgs, PS_LIST_TAIL, "-fake_id",  0,
+            "define fake ID", 0);
+    psMetadataAddS64(processedimfileArgs, PS_LIST_TAIL, "-exp_id",  0,
+            "define exposure ID", 0);
+    psMetadataAddStr(processedimfileArgs, PS_LIST_TAIL, "-class_id",  0,
+            "define class ID", NULL);
+    psMetadataAddStr(processedimfileArgs, PS_LIST_TAIL, "-inst",  0,
+            "define camera of interest", NULL);
+    psMetadataAddStr(processedimfileArgs, PS_LIST_TAIL, "-filter",  0,
+            "define filter of interest", NULL);
+    psMetadataAddU64(processedimfileArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(processedimfileArgs, PS_LIST_TAIL, "-faulted",  0,
+            "only return imfiles with a fault status set", false);
+    psMetadataAddBool(processedimfileArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -updateprocessedimfile
+    psMetadata *updateprocessedimfileArgs = psMetadataAlloc();
+    psMetadataAddS64(updateprocessedimfileArgs, PS_LIST_TAIL, "-fake_id",  0,
+            "search by fake ID", 0);
+    psMetadataAddS64(updateprocessedimfileArgs, PS_LIST_TAIL, "-exp_id",  0,
+            "search by exposure ID", 0);
+    psMetadataAddStr(updateprocessedimfileArgs, PS_LIST_TAIL, "-class_id",  0,
+            "search by class ID", NULL);
+    psMetadataAddS16(updateprocessedimfileArgs, PS_LIST_TAIL, "-code",  0,
+            "set fault code (required)", 0);
+
+    // -revertprocessedimfile
+    psMetadata *revertprocessedimfileArgs = psMetadataAlloc();
+    psMetadataAddS64(revertprocessedimfileArgs, PS_LIST_TAIL, "-fake_id", 0,
+            "search by fake ID", 0);
+    psMetadataAddS64(revertprocessedimfileArgs, PS_LIST_TAIL, "-exp_id",  0,
+            "search by exp_id", 0);
+    psMetadataAddStr(revertprocessedimfileArgs, PS_LIST_TAIL, "-exp_name",  0,
+            "search by exp_name", NULL);
+    psMetadataAddStr(revertprocessedimfileArgs, PS_LIST_TAIL, "-inst",  0,
+            "search for camera", NULL);
+    psMetadataAddStr(revertprocessedimfileArgs, PS_LIST_TAIL, "-telescope",  0,
+            "search for telescope", NULL);
+    psMetadataAddTime(revertprocessedimfileArgs, PS_LIST_TAIL, "-dateobs_begin", 0,
+            "search for exposures by time (>=)", NULL);
+    psMetadataAddTime(revertprocessedimfileArgs, PS_LIST_TAIL, "-dateobs_end", 0,
+            "search for exposures by time (<)", NULL);
+    psMetadataAddStr(revertprocessedimfileArgs, PS_LIST_TAIL, "-exp_tag",  0,
+            "search by exp_tag", NULL);
+    psMetadataAddStr(revertprocessedimfileArgs, PS_LIST_TAIL, "-exp_type",  0,
+            "search by exp_type", "object");
+    psMetadataAddStr(revertprocessedimfileArgs, PS_LIST_TAIL, "-filelevel",  0,
+            "search by filelevel", NULL);
+    psMetadataAddStr(revertprocessedimfileArgs, PS_LIST_TAIL, "-reduction",  0,
+            "search by reduction class", NULL);
+    psMetadataAddStr(revertprocessedimfileArgs, PS_LIST_TAIL, "-filter",  0,
+            "search for filter", NULL);
+    psMetadataAddF32(revertprocessedimfileArgs, PS_LIST_TAIL, "-airmass_min",  0,
+            "define min airmass", NAN);
+    psMetadataAddF32(revertprocessedimfileArgs, PS_LIST_TAIL, "-airmass_max",  0,
+            "define max airmass", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-ra_min",  0,
+            "define min", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-ra_max",  0,
+            "define max", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-decl_min",  0,
+            "define min", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-decl_max",  0,
+            "define max", NAN);
+    psMetadataAddF32(revertprocessedimfileArgs, PS_LIST_TAIL, "-exp_time_min",  0,
+            "define min", NAN);
+    psMetadataAddF32(revertprocessedimfileArgs, PS_LIST_TAIL, "-exp_time_max",  0,
+            "define max", NAN);
+    psMetadataAddF32(revertprocessedimfileArgs, PS_LIST_TAIL, "-sat_pixel_frac_min",  0,
+            "define max fraction of saturated pixels", NAN);
+    psMetadataAddF32(revertprocessedimfileArgs, PS_LIST_TAIL, "-sat_pixel_frac_max",  0,
+            "define min fraction of saturated pixels", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-bg_min",  0,
+            "define max", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-bg_max",  0,
+            "define max", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-bg_stdev_min",  0,
+            "define max", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-bg_stdev_max",  0,
+            "define max", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-bg_mean_stdev_min",  0,
+            "define max", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-bg_mean_stdev_max",  0,
+            "define max", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-alt_min",  0,
+            "define min", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-alt_max",  0,
+            "define max", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-az_min",  0,
+            "define min", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-az_max",  0,
+            "define max", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-ccd_temp_min",0,
+            "define min ccd tempature", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-ccd_temp_max",0,
+            "define max ccd tempature", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-posang_min",  0,
+            "define min rotator position angle", NAN);
+    psMetadataAddF64(revertprocessedimfileArgs, PS_LIST_TAIL, "-posang_max",  0,
+            "define max rotator position angle", NAN);
+    psMetadataAddStr(revertprocessedimfileArgs, PS_LIST_TAIL, "-object",  0,
+            "search by exposure object", NULL);
+    psMetadataAddF32(revertprocessedimfileArgs, PS_LIST_TAIL, "-solang_min",  0,
+            "define min solar angle", NAN);
+    psMetadataAddF32(revertprocessedimfileArgs, PS_LIST_TAIL, "-solang_max",  0,
+            "define max solar angle", NAN);
+
+    psMetadataAddBool(revertprocessedimfileArgs, PS_LIST_TAIL, "-all",  0,
+            "allow everything to be queued without search terms", false);
+    psMetadataAddS16(revertprocessedimfileArgs, PS_LIST_TAIL, "-code",  0,
+            "search by fault code", 0);
+
+    // -block
+    psMetadata *blockArgs = psMetadataAlloc();
+    psMetadataAddStr(blockArgs, PS_LIST_TAIL, "-label",  0,
+            "name of a label to mask out (required)", NULL);
+
+    // -masked
+    psMetadata *maskedArgs = psMetadataAlloc();
+    psMetadataAddStr(maskedArgs, PS_LIST_TAIL, "-label",  0,
+            "list blocks for specified label", NULL);
+    psMetadataAddBool(maskedArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+    psMetadataAddU64(maskedArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+
+    // -unmasked
+    psMetadata *unmaskedArgs = psMetadataAlloc();
+    psMetadataAddStr(unmaskedArgs, PS_LIST_TAIL, "-label",  0,
+            "restrict to specified label", NULL);
+    psMetadataAddBool(unmaskedArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+    psMetadataAddU64(unmaskedArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+
+    // -unblock
+    psMetadata *unblockArgs = psMetadataAlloc();
+    psMetadataAddStr(unblockArgs, PS_LIST_TAIL, "-label",  0,
+            "name of a label to unmask (required)", NULL);
+
+    psMetadata *argSets = psMetadataAlloc();
+    psMetadata *modes = psMetadataAlloc();
+
+    PXOPT_ADD_MODE("-definebyquery",                 "create runs from raw exposures",
+        FAKETOOL_MODE_DEFINEBYQUERY,                    queueArgs);
+    PXOPT_ADD_MODE("-updaterun",             "change fake run properties",
+        FAKETOOL_MODE_UPDATERUN,                updaterunArgs);
+    PXOPT_ADD_MODE("-pendingexp",           "show pending exposures",
+        FAKETOOL_MODE_PENDINGEXP,               pendingexpArgs);
+    PXOPT_ADD_MODE("-pendingimfile",         "show pending imfiles",
+        FAKETOOL_MODE_PENDINGIMFILE,            pendingimfileArgs);
+    PXOPT_ADD_MODE("-addprocessedimfile",    "add a processed imfile",
+        FAKETOOL_MODE_ADDPROCESSEDIMFILE,       addprocessedimfileArgs);
+    PXOPT_ADD_MODE("-processedimfile",       "show processed imfiles",
+        FAKETOOL_MODE_PROCESSEDIMFILE,          processedimfileArgs);
+    PXOPT_ADD_MODE("-updateprocessedimfile","change procesed imfile properties",        FAKETOOL_MODE_UPDATEPROCESSEDIMFILE,    updateprocessedimfileArgs);
+    PXOPT_ADD_MODE("-revertprocessedimfile", "undo a processed imfile",
+        FAKETOOL_MODE_REVERTPROCESSEDIMFILE,    revertprocessedimfileArgs);
+    PXOPT_ADD_MODE("-block",                 "set a label block", FAKETOOL_MODE_BLOCK,          blockArgs);
+    PXOPT_ADD_MODE("-masked",                "show blocked labels", FAKETOOL_MODE_MASKED,         maskedArgs);
+    PXOPT_ADD_MODE("-unmasked",              "", FAKETOOL_MODE_UNMASKED,       unmaskedArgs);
+    PXOPT_ADD_MODE("-unblock",               "remove a label block", FAKETOOL_MODE_UNBLOCK,        unblockArgs);
+
+    if (!pxGetOptions(stderr, argc, argv, config, modes, argSets)) {
+        psError(PS_ERR_UNKNOWN, true, "option parsing failed");
+        psFree(argSets);
+        psFree(modes);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+    psFree(modes);
+
+    // define Database handle, if used
+    config->dbh = psMemIncrRefCounter(pmConfigDB(config->modules));
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    return config;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/flatcorr.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/flatcorr.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/flatcorr.c	(revision 22157)
@@ -0,0 +1,556 @@
+/*
+ * flatcorr.c
+ *
+ * Copyright (C) 2006-2007  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVB_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+
+#include <ippdb.h>
+
+#include "pxtools.h"
+#include "flatcorr.h"
+
+static bool newrunMode(pxConfig *config);
+static bool addexpMode(pxConfig *config);
+static bool pendingMode(pxConfig *config);
+static bool flatcorrimfileMode(pxConfig *config);
+static bool doneMode(pxConfig *config);
+static bool updaterunMode(pxConfig *config);
+
+static bool setflatcorrRunState(pxConfig *config, psS64 corr_id, const char *state);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = flatcorrConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(FLATCORR_MODE_NEWRUN,      newrunMode);
+        MODECASE(FLATCORR_MODE_ADDEXP,      addexpMode);
+        MODECASE(FLATCORR_MODE_PENDING,     pendingMode);
+        MODECASE(FLATCORR_MODE_FLATCORRIMFILE, flatcorrimfileMode);
+        MODECASE(FLATCORR_MODE_DONE,        doneMode);
+        MODECASE(FLATCORR_MODE_UPDATERUN,   updaterunMode);
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+
+static bool newrunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+
+    PXOPT_COPY_S64(config->args, where, "-exp_id", "exp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_name", "exp_name", "==");
+    PXOPT_COPY_STR(config->args, where, "-inst", "camera", "==");
+    PXOPT_COPY_STR(config->args, where, "-telescope", "telescope", "==");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_begin", "dateobs", ">=");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_end", "dateobs", "<=");
+    PXOPT_COPY_STR(config->args, where, "-exp_tag", "exp_tag", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_type", "exp_type", "==");
+    PXOPT_COPY_STR(config->args, where, "-filelevel", "filelvel", "==");
+    PXOPT_COPY_STR(config->args, where, "-reduction", "reduction", "==");
+    PXOPT_COPY_STR(config->args, where, "-filter", "filter", "==");
+    PXOPT_COPY_F32(config->args, where, "-airmass_min", "airmass", ">=");
+    PXOPT_COPY_F32(config->args, where, "-airmass_max", "airmass", "<");
+    PXOPT_COPY_F64(config->args, where, "-ra_min", "ra", ">=");
+    PXOPT_COPY_F64(config->args, where, "-ra_max", "ra", "<");
+    PXOPT_COPY_F64(config->args, where, "-decl_min", "decl", ">=");
+    PXOPT_COPY_F64(config->args, where, "-decl_max", "decl", "<");
+    PXOPT_COPY_F32(config->args, where, "-exp_time_min", "exp_time", ">=");
+    PXOPT_COPY_F32(config->args, where, "-exp_time_max", "exp_time", "<");
+    PXOPT_COPY_F32(config->args, where, "-sat_pixel_frac_min", "sat_pixel_frac", ">=");
+    PXOPT_COPY_F32(config->args, where, "-sat_pixel_frac_max", "sat_pixel_frac", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_min", "bt", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_max", "bt", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_stdev_min", "bg_stdev", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_stdev_max", "bg_stdev", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_mean_stdev_min", "bg_mean_stdev", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_mean_stdev_max", "bg_mean_stdev", "<");
+    PXOPT_COPY_F64(config->args, where, "-alt_min", "alt", ">=");
+    PXOPT_COPY_F64(config->args, where, "-alt_max", "alt", "<");
+    PXOPT_COPY_F64(config->args, where, "-az_min", "az", ">=");
+    PXOPT_COPY_F64(config->args, where, "-az_max", "az", "<");
+    PXOPT_COPY_F32(config->args, where, "-ccd_temp_min", "ccd_temp", ">=");
+    PXOPT_COPY_F32(config->args, where, "-ccd_temp_max", "ccd_temp", "<");
+    PXOPT_COPY_F64(config->args, where, "-posang_min", "posang", ">=");
+    PXOPT_COPY_F64(config->args, where, "-posang_max", "posang", "<");
+    PXOPT_COPY_STR(config->args, where, "-object", "object", "==");
+    PXOPT_COPY_F32(config->args, where, "-solang_min", "solang", ">=");
+    PXOPT_COPY_F32(config->args, where, "-solang_max", "solang", "<");
+
+    if (where->list->n < 1) {
+        psFree(where);
+        psError(PXTOOLS_ERR_DATA, false, "search parameters are required");
+        return false;
+    }
+
+    PXOPT_LOOKUP_STR(workdir, config->args, "-set_workdir", false, false);
+    PXOPT_LOOKUP_STR(label, config->args, "-set_label", false, false);
+    PXOPT_LOOKUP_STR(reduction, config->args, "-set_reduction", false, false);
+    PXOPT_LOOKUP_STR(expgroup, config->args, "-set_expgroup", false, false);
+    PXOPT_LOOKUP_STR(dvodb, config->args, "-set_dvodb", false, false);
+    PXOPT_LOOKUP_STR(filter, config->args, "-set_filter", false, false);
+    PXOPT_LOOKUP_STR(tess_id, config->args, "-set_tess_id", false, false);
+    PXOPT_LOOKUP_STR(end_stage, config->args, "-set_end_stage", false, false);
+    PXOPT_LOOKUP_STR(region, config->args, "-set_region", false, false);
+
+    PXOPT_LOOKUP_BOOL(pretend, config->args, "-pretend", false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    // find the exp_id of all the exposures that we want to queue up.
+    psString query = pxDataGet("chiptool_find_rawexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "rawExp");
+        psFree(where);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("chiptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (pretend) {
+        // negative simple so the default is true
+    for (int i = 0; i < output->n; i++) {
+        psMetadata *md = output->data[i];
+        psMetadataConfigPrint (stdout, md);
+    }
+//        if (!rawExpPrintObjects(stdout, output, !simple)) {
+//            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+//            psFree(output);
+//            return false;
+//        }
+        psFree(output);
+        return true;
+    }
+
+    // start a transaction so we don't end up with an exp without any associted
+    // imfiles
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(output);
+        return false;
+    }
+
+    // create a new flatcorrRun
+    if (!flatcorrRunInsert(config->dbh,
+            0,      // corr_id
+            dvodb,
+            filter,
+            "reg",  // state
+            workdir,
+            label,
+            NULL,   // stats
+            region
+        )) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // figure out the ID of the flatcorrRun we just created
+    psS64 corr_id = psDBLastInsertID(config->dbh);
+
+    // loop over our list of exp_ids
+    for (long i = 0; i < psArrayLength(output); i++) {
+        psMetadata *md = output->data[i];
+
+        bool status;
+        psS64 exp_id = psMetadataLookupS64(&status, md, "exp_id");
+        if (!status) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for exp_id");
+            psFree(output);
+            return false;
+        }
+
+        // queue the exp
+        psS64 chip_id = pxchipQueueByExpTag(config, exp_id, workdir, label, reduction, expgroup, dvodb, tess_id, end_stage);
+        if (!chip_id) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false,
+                    "failed to trying to queue exp_id: %" PRId64, exp_id);
+            psFree(output);
+            return false;
+        }
+
+        // add a flatcorrExp to the flatcorr Run we just created
+        if (!flatcorrExpInsert(config->dbh,
+                corr_id,
+                chip_id
+            )) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+
+    }
+    psFree(output);
+
+    // set the flatcorrRun to a state of 'run'
+    if (!setflatcorrRunState(config, corr_id, "run")) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "failed to set run state");
+        return false;
+    }
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool addexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required
+    PXOPT_LOOKUP_STR(corr_id, config->args, "-corr_id", true, false);
+    PXOPT_LOOKUP_STR(chip_id, config->args, "-chip_id", true, false);
+
+    if (!flatcorrExpInsert(config->dbh,
+            (psS64)atoll(corr_id),
+            (psS64)atoll(chip_id)
+        )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool pendingMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_BOOL(limit, config->args, "-limit", false);
+
+    // create a temp table to hold completely processed chipRuns
+    {
+	psString query = pxDataGet("flatcorr_create_tmp_chiprundone.sql");
+	if (!query) {
+	    psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+	    return false;
+	}
+
+	if (!p_psDBRunQuery(config->dbh, query)) {
+	    psError(PS_ERR_UNKNOWN, false, "database error");
+	    psFree(query);
+	    return false;
+	}
+
+	psFree(query);
+    }
+   
+    // find all completed chipRuns
+    {
+	psString query = pxDataGet("flatcorr_completely_processed_chiprun.sql");
+	if (!query) {
+	    psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+	    return false;
+	}
+
+	if (!p_psDBRunQuery(config->dbh, query)) {
+	    psError(PS_ERR_UNKNOWN, false, "database error");
+	    psFree(query);
+	    return false;
+	}
+
+	psFree(query);
+    }
+
+    // find flatcorrRun's that have had all of their chipRun's completed by
+    // comparing against the temp table
+    psString query = pxDataGet("flatcorr_find_completed_floatcorruns.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+	psString limitString = psDBGenerateLimitSQL(limit);
+	psStringAppend(&query, " %s", limitString);
+	psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+	  case PS_ERR_DB_CLIENT:
+	    psError(PXTOOLS_ERR_SYS, false, "database error");
+	  case PS_ERR_DB_SERVER:
+	    psError(PXTOOLS_ERR_PROG, false, "database error");
+	  default:
+	    psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("regtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "flatcorrPending", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return false;
+}
+
+
+static bool flatcorrimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-corr_id", "corr_id", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    // find all rawImfiles matching the default query
+    psString query = pxDataGet("flatcorr_find_processedimfiles.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+    
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        } 
+    
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("warptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true; 
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "chipProcessedImfile", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool doneMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_STR(corr_id, config->args, "-corr_id", true, false);
+    PXOPT_LOOKUP_STR(stats, config->args, "-stats", true, false);
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!setflatcorrRunState(config, (psS64)atoll(corr_id), "stop")) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "failed to set run state");
+        return false;
+    }
+
+    char *query = "UPDATE flatcorrRun SET stats = '%s' WHERE corr_id = %" PRId64;
+    if (!p_psDBRunQuery(config->dbh, query, stats, (psS64)atoll(corr_id))) {
+        psError(PS_ERR_UNKNOWN, false,
+                "failed to change state for corr_id %" PRId64, (psS64)atoll(corr_id));
+        return false;
+    }
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return false;
+}
+
+
+static bool setflatcorrRunState(pxConfig *config, psS64 corr_id, const char *state)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(state, false);
+
+    // check that state is a valid string value
+    if (!(
+            (strncmp(state, "run", 4) == 0)
+            || (strncmp(state, "stop", 5) == 0)
+            || (strncmp(state, "reg", 4) == 0)
+        )
+    ) {
+        psError(PS_ERR_UNKNOWN, false, "invalid state: %s", state);
+        return false;
+    }
+
+    char *query = "UPDATE flatcorrRun SET state = '%s' WHERE corr_id = %" PRId64;
+    if (!p_psDBRunQuery(config->dbh, query, state, corr_id)) {
+        psError(PS_ERR_UNKNOWN, false,
+                "failed to change state for corr_id %" PRId64, corr_id);
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool updaterunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_STR(corr_id, config->args, "-corr_id", true, false);
+    PXOPT_LOOKUP_STR(state, config->args, "-state", true, false);
+
+    if (state) {
+        // set detRun.state to state
+        return setflatcorrRunState(config, (psS64)atoll(corr_id), state);
+    }
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/flatcorr.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/flatcorr.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/flatcorr.h	(revision 22157)
@@ -0,0 +1,37 @@
+/*
+ * flatcorr.h
+ *
+ * Copyright (C) 2006-2007  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef FLATCORR_H
+#define FLATCORR_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    FLATCORR_MODE_NONE           = PXTOOL_MODE_NONE,
+    FLATCORR_MODE_NEWRUN,
+    FLATCORR_MODE_ADDEXP,
+    FLATCORR_MODE_PENDING,
+    FLATCORR_MODE_FLATCORRIMFILE,
+    FLATCORR_MODE_DONE,
+    FLATCORR_MODE_UPDATERUN
+} flatcorrMode;
+
+pxConfig *flatcorrConfig(pxConfig *config, int argc, char **argv);
+
+#endif // FLATCORR_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/flatcorrConfig.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/flatcorrConfig.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/flatcorrConfig.c	(revision 22157)
@@ -0,0 +1,162 @@
+/*
+ * flatcorrConfig.c
+ *
+ * Copyright (C) 2006-2007  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "flatcorr.h"
+
+pxConfig *flatcorrConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (!config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    psTime *now = psTimeGetNow(PS_TIME_TAI);
+
+    // -newrun
+    psMetadata *newrunArgs = psMetadataAlloc();
+    // XXX need to allow multiple exp_ids
+    psMetadataAddS64(newrunArgs, PS_LIST_TAIL, "-exp_id",  0,            "search by exp_id", 0);
+    psMetadataAddStr(newrunArgs, PS_LIST_TAIL, "-exp_name",  0,            "search by exp_name", NULL);
+    psMetadataAddStr(newrunArgs, PS_LIST_TAIL, "-inst",  0,            "search for camera", NULL); 
+    psMetadataAddStr(newrunArgs, PS_LIST_TAIL, "-telescope",  0,            "search for telescope", NULL);
+    psMetadataAddTime(newrunArgs, PS_LIST_TAIL, "-dateobs_begin", 0,               "search for exposures by time (>=)", NULL);
+    psMetadataAddTime(newrunArgs, PS_LIST_TAIL, "-dateobs_end", 0,            "search for exposures by time (<)", NULL);
+    psMetadataAddStr(newrunArgs, PS_LIST_TAIL, "-exp_tag",  0,            "search by exp_tag", NULL);
+    psMetadataAddStr(newrunArgs, PS_LIST_TAIL, "-exp_type",  0,            "search by exp_type", "object");
+    psMetadataAddStr(newrunArgs, PS_LIST_TAIL, "-filelevel",  0,            "search by filelevel", NULL);
+    psMetadataAddStr(newrunArgs, PS_LIST_TAIL, "-reduction",  0,            "search by reduction class", NULL);
+    psMetadataAddStr(newrunArgs, PS_LIST_TAIL, "-filter",  0,            "search for filter", NULL); 
+    psMetadataAddF32(newrunArgs, PS_LIST_TAIL, "-airmass_min",  0,            "define min airmass", NAN);
+    psMetadataAddF32(newrunArgs, PS_LIST_TAIL, "-airmass_max",  0,            "define max airmass", NAN); 
+    psMetadataAddF64(newrunArgs, PS_LIST_TAIL, "-ra_min",  0,            "define min", NAN);
+    psMetadataAddF64(newrunArgs, PS_LIST_TAIL, "-ra_max",  0,            "define max", NAN); 
+    psMetadataAddF64(newrunArgs, PS_LIST_TAIL, "-decl_min",  0,            "define min", NAN);
+    psMetadataAddF64(newrunArgs, PS_LIST_TAIL, "-decl_max",  0,            "define max", NAN); 
+    psMetadataAddF32(newrunArgs, PS_LIST_TAIL, "-exp_time_min",  0,            "define min", NAN);
+    psMetadataAddF32(newrunArgs, PS_LIST_TAIL, "-exp_time_max",  0,            "define max", NAN); 
+    psMetadataAddF32(newrunArgs, PS_LIST_TAIL, "-sat_pixel_frac_min",  0,            "define max fraction of saturated pixels", NAN);
+    psMetadataAddF32(newrunArgs, PS_LIST_TAIL, "-sat_pixel_frac_max",  0,            "define min fraction of saturated pixels", NAN);
+    psMetadataAddF64(newrunArgs, PS_LIST_TAIL, "-bg_min",  0,            "define max", NAN);
+    psMetadataAddF64(newrunArgs, PS_LIST_TAIL, "-bg_max",  0,            "define max", NAN);
+    psMetadataAddF64(newrunArgs, PS_LIST_TAIL, "-bg_stdev_min",  0,            "define max", NAN);
+    psMetadataAddF64(newrunArgs, PS_LIST_TAIL, "-bg_stdev_max",  0,            "define max", NAN);
+    psMetadataAddF64(newrunArgs, PS_LIST_TAIL, "-bg_mean_stdev_min",  0,            "define max", NAN);
+    psMetadataAddF64(newrunArgs, PS_LIST_TAIL, "-bg_mean_stdev_max",  0,            "define max", NAN);
+    psMetadataAddF64(newrunArgs, PS_LIST_TAIL, "-alt_min",  0,            "define min", NAN);
+    psMetadataAddF64(newrunArgs, PS_LIST_TAIL, "-alt_max",  0,            "define max", NAN); 
+    psMetadataAddF64(newrunArgs, PS_LIST_TAIL, "-az_min",  0,            "define min", NAN);
+    psMetadataAddF64(newrunArgs, PS_LIST_TAIL, "-az_max",  0,            "define max", NAN);
+    psMetadataAddF32(newrunArgs, PS_LIST_TAIL, "-ccd_temp_min",  0,               "define min ccd tempature", NAN);
+    psMetadataAddF32(newrunArgs, PS_LIST_TAIL, "-ccd_temp_max",  0,            "define max ccd tempature", NAN);
+    psMetadataAddF64(newrunArgs, PS_LIST_TAIL, "-posang_min",  0,            "define min rotator position angle", NAN);
+    psMetadataAddF64(newrunArgs, PS_LIST_TAIL, "-posang_max",  0,            "define max rotator position angle", NAN);
+    psMetadataAddStr(newrunArgs, PS_LIST_TAIL, "-object",  0,            "search by exposure object", NULL);
+    psMetadataAddF32(newrunArgs, PS_LIST_TAIL, "-solang_min",  0,            "define min solar angle", NAN);
+    psMetadataAddF32(newrunArgs, PS_LIST_TAIL, "-solang_max",  0,            "define max solar angle", NAN);
+
+    psMetadataAddStr(newrunArgs, PS_LIST_TAIL, "-set_workdir",  0,            "define workdir", NULL);
+    psMetadataAddStr(newrunArgs, PS_LIST_TAIL, "-set_label",  0,            "define label", NULL);
+    psMetadataAddStr(newrunArgs, PS_LIST_TAIL, "-set_reduction",  0,            "define reduction class", NULL);
+    psMetadataAddStr(newrunArgs, PS_LIST_TAIL, "-set_expgroup",  0,            "define exposure group", NULL);
+    psMetadataAddStr(newrunArgs, PS_LIST_TAIL, "-set_dvodb",  0,            "define DVO db", NULL);
+    psMetadataAddStr(newrunArgs, PS_LIST_TAIL, "-set_filter",  0,            "define filter", NULL);
+    psMetadataAddStr(newrunArgs, PS_LIST_TAIL, "-set_tess_id",  0,            "define tessalation", NULL);
+    psMetadataAddStr(newrunArgs, PS_LIST_TAIL, "-set_end_stage",  0,            "define processing end stage", NULL);
+    psMetadataAddStr(newrunArgs, PS_LIST_TAIL, "-set_region",  0,            "define region", NULL);
+
+    psMetadataAddBool(newrunArgs, PS_LIST_TAIL, "-pretend",  0,            "print the exposures that would be included in the detrend run and exit", false);
+    psMetadataAddBool(newrunArgs, PS_LIST_TAIL, "-simple", 0,            "use the simple output format", false);
+
+    // -addexp
+    psMetadata *addexpArgs = psMetadataAlloc();
+    psMetadataAddStr(addexpArgs, PS_LIST_TAIL, "-corr_id", 0,            "define Flat Correction ID (required)", NULL);
+    psMetadataAddStr(addexpArgs, PS_LIST_TAIL, "-chip_id", 0,            "define Chip ID (required)", NULL);
+
+    // -pending
+    psMetadata *pendingArgs = psMetadataAlloc();
+    psMetadataAddU64(pendingArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(pendingArgs, PS_LIST_TAIL, "-simple", 0,            "use the simple output format", false);
+
+    // -flatcorrimfile
+    psMetadata *flatcorrimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(flatcorrimfileArgs, PS_LIST_TAIL, "-corr_id",  0,            "search by flat correction ID (required)", NULL);
+    psMetadataAddU64(flatcorrimfileArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(flatcorrimfileArgs, PS_LIST_TAIL, "-simple", 0,            "use the simple output format", false);
+
+    // -done
+    psMetadata *doneArgs = psMetadataAlloc();
+    psMetadataAddStr(doneArgs, PS_LIST_TAIL, "-corr_id",  0,            "define correction id (required)", NULL);
+    psMetadataAddStr(doneArgs, PS_LIST_TAIL, "-stats",  0,            "define stats (required)", NULL);
+
+    // -updaterun
+    psMetadata *updaterunArgs = psMetadataAlloc();
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-corr_id",  0,            "define correction id (required)", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-state", 0,            "set state (required)", NULL);
+
+    psFree(now);
+
+    psMetadata *argSets = psMetadataAlloc();
+    psMetadata *modes = psMetadataAlloc();
+
+    PXOPT_ADD_MODE("-newrun",  "start a new flat correction run",        FLATCORR_MODE_NEWRUN,       newrunArgs);
+    PXOPT_ADD_MODE("-addexp",  "add an exp to a flat correction run",        FLATCORR_MODE_ADDEXP,       addexpArgs);
+    PXOPT_ADD_MODE("-pending", "show flat correction runs needing to be processed",        FLATCORR_MODE_PENDING,      pendingArgs);
+    PXOPT_ADD_MODE("-flatcorrimfile", "list all the imfiles in a flat correction run",        FLATCORR_MODE_FLATCORRIMFILE, flatcorrimfileArgs);
+    PXOPT_ADD_MODE("-done",  "change a flat calibration run's state",        FLATCORR_MODE_DONE,         doneArgs);
+    PXOPT_ADD_MODE("-updaterun",  "change a flat calibration run's state",        FLATCORR_MODE_UPDATERUN,   updaterunArgs);
+
+    if (!pxGetOptions(stderr, argc, argv, config, modes, argSets)) {
+        psError(PS_ERR_UNKNOWN, true, "option parsing failed");
+        psFree(argSets);
+        psFree(modes);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+    psFree(modes);
+
+    // define Database handle, if used
+    // do this last so we don't setup a connection before CLI options are
+    // validated
+    config->dbh = psMemIncrRefCounter(pmConfigDB(config->modules));
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    return config;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/guidetool.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/guidetool.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/guidetool.c	(revision 22157)
@@ -0,0 +1,221 @@
+/*
+ * guidetool.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#include "pxtools.h"
+#include "guidetool.h"
+
+static bool pendingMode(pxConfig *config);
+static bool defineMode(pxConfig *config);
+static guidePendingExpRow *rawScienceToguidePendingExp(pxConfig *config, rawScienceExpRow *exp);
+
+int main(int argc, char **argv)
+{
+    pxConfig *config = guidetoolConfig(NULL, argc, argv);
+
+    switch (config->mode) {
+        case PX_MODE_PENDING:
+            if (!pendingMode(config)) {
+                goto FAIL;
+            }
+            break;
+        case PX_MODE_DEFINE:
+            if (!defineMode(config)) {
+                goto FAIL;
+            }
+            break;
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psFree(config);
+    exit(EXIT_FAILURE);
+}
+
+static bool pendingMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-exp_id",        "exp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-inst",          "camera", "==");
+    PXOPT_COPY_STR(config->args, where, "-telescope", 	  "telescope", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_type",  	  "exp_type", "==");
+    PXOPT_COPY_S32(config->args, where, "-imfiles",   	  "imfiles", "==");
+    PXOPT_COPY_STR(config->args, where, "-filter",    	  "filter", "==");
+    PXOPT_COPY_STR(config->args, where, "-recip",         "recipe", "==");
+    PXOPT_COPY_S32(config->args, where, "-guide_version", "guide_version", "==");
+    if (!psListLength(where->list)) {
+	psFree (where);
+	where = NULL;
+    }
+
+    // return all guidePendingExp rows unless there CLI search options
+    psArray *guidePendingExp = guidePendingExpSelectRowObjects(config->dbh, where, 0);
+    psFree (where);
+
+    if (!guidePendingExp) {
+        psError(PS_ERR_UNKNOWN, false, "no guidePendingExp found");
+        return false;
+    }
+    psMetadata *output = psMetadataAlloc();
+
+    for (long i = 0; i < psArrayLength(guidePendingExp); i++) {
+        psMetadata *md = guidePendingExpMetadataFromObject(guidePendingExp->data[i]);
+        psMetadataAddMetadata(output, PS_LIST_TAIL, "guidePendingExp",
+            PS_META_DUPLICATE_OK, NULL, md);
+        psFree(md);
+    }
+
+    psString str = psMetadataConfigFormat(output);
+    psFree(output);
+
+    fprintf(stdout, "%s\n", str);
+    psFree(str);
+
+    return true;
+}
+
+static bool defineMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-exp_id",        "exp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-inst",          "camera", "==");
+    PXOPT_COPY_STR(config->args, where, "-telescope", 	  "telescope", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_type",  	  "exp_type", "==");
+    PXOPT_COPY_S32(config->args, where, "-imfiles",   	  "imfiles", "==");
+    PXOPT_COPY_STR(config->args, where, "-filter",    	  "filter", "==");
+    if (!psListLength(where->list)) {
+	psFree (where);
+	where = NULL;
+    }
+
+    // return all rawScienceExp rows unless there CLI search options
+    psArray *rawScienceExps = rawScienceExpSelectRowObjects(config->dbh, where, 0);
+    psFree (where);
+    
+    if (!rawScienceExps) {
+        psError(PS_ERR_UNKNOWN, false, "no rawScienceExp rows found");
+        return true;
+    }
+
+    // insert the rawScienceExps into guidePendingExp
+    // XXX for the being we're going to skip checking for duplicate entries
+    // XXX this will have to implimented unless duplicate key insertion 
+    // XXX support is implimented in psDB
+    for (long i = 0; i < psArrayLength(rawScienceExps); i++) {
+        guidePendingExpRow * new = rawScienceToguidePendingExp(
+                                            config, rawScienceExps->data[i]);
+        if (!guidePendingExpInsertObject(config->dbh, new)) {
+            psError(PS_ERR_UNKNOWN, false, "dbh access failed");
+            return false; 
+        }
+        psFree(new);
+    }
+
+    return true;
+}
+
+static guidePendingExpRow *rawScienceToguidePendingExp(pxConfig *config, rawScienceExpRow *exp)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(exp, NULL);
+
+    bool status = false;
+    psString filter = psMetadataLookupStr(&status, config->args, "-filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -filter");
+        return false;
+    }
+    if (!filter) {
+        psError(PS_ERR_UNKNOWN, true, "-filter is required");
+        return false;
+    }
+    psF32 airmass = psMetadataLookupF32(&status, config->args, "-airmass");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -airmass");
+        return false;
+    }
+    if (!airmass) {
+        psError(PS_ERR_UNKNOWN, true, "-airmass is required");
+        return false;
+    }
+    psF64 ra = psMetadataLookupF64(&status, config->args, "-ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -ra");
+        return false;
+    }
+    if (!ra) {
+        psError(PS_ERR_UNKNOWN, true, "-airmass is required");
+        return false;
+    }
+    psF64 dec = psMetadataLookupF64(&status, config->args, "-dec");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -dec");
+        return false;
+    }
+    if (!dec) {
+        psError(PS_ERR_UNKNOWN, true, "-dec is required");
+        return false;
+    }
+    psF32 exp_time = psMetadataLookupF32(&status, config->args, "-exp_time");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_time");
+        return false;
+    }
+    if (!exp_time) {
+        psError(PS_ERR_UNKNOWN, true, "-exp_time is required");
+        return false;
+    }
+    psF64 background = psMetadataLookupF64(&status, config->args, "-background");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -background");
+        return false;
+    }
+    if (!exp_time) {
+        psError(PS_ERR_UNKNOWN, true, "-background is required");
+        return false;
+    }
+
+    return guidePendingExpRowAlloc(
+        exp->exp_id,
+        exp->camera,
+        exp->telescope,
+        exp->exp_type,
+        exp->imfiles,
+        filter,
+        airmass,
+        ra,
+        dec,
+        exp_time,
+        background,
+        "my recipe",
+        0xff // XXX calc version number
+    );
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/guidetool.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/guidetool.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/guidetool.h	(revision 22157)
@@ -0,0 +1,27 @@
+/*
+ * guidetool.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef GUIDETOOL_H
+#define GUIDETOOL_H 1
+
+#include "pxtools.h"
+
+pxConfig *guidetoolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // GUIDETOOL_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/guidetoolConfig.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/guidetoolConfig.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/guidetoolConfig.c	(revision 22157)
@@ -0,0 +1,140 @@
+/*
+ * guidetoolConfig.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+
+// this function can not fail -- exits on error
+
+pxConfig *guidetoolConfig(pxConfig *config, int argc, char **argv) {
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv);
+    if (!config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration");
+        exit(EXIT_FAILURE);
+    }
+
+    // CLI options
+    // modes
+    psMetadata *args = psMetadataAlloc();
+    psMetadataAddStr(args , PS_LIST_TAIL, "-pending", 0,
+        "examine pending image table, write ppImage output", NULL);
+    psMetadataAddStr(args , PS_LIST_TAIL, "-define",  0,
+        "update pending image table", NULL);
+
+    // -pending search
+    psMetadata *pendingArgs = psMetadataAlloc();
+    psMetadataAddStr(pendingArgs, PS_LIST_TAIL, "-exp_id",  0,        "define exposure ID", NULL);
+    psMetadataAddStr(pendingArgs, PS_LIST_TAIL, "-inst",  0,        "define camera of interest", NULL);
+    psMetadataAddStr(pendingArgs, PS_LIST_TAIL, "-telescope",  0,        "define telescope of interest", NULL);
+    psMetadataAddStr(pendingArgs, PS_LIST_TAIL, "-exp_type",  0,        "define exposure type", NULL);
+    psMetadataAddS32(pendingArgs, PS_LIST_TAIL, "-imfiles",  0,        "define number of imfiles", 0);
+    psMetadataAddStr(pendingArgs, PS_LIST_TAIL, "-filter",  0,        "define filter of interest", NULL);
+    psMetadataAddStr(pendingArgs, PS_LIST_TAIL, "-recipe",  0,        "define recipe of interest", NULL);
+    psMetadataAddS32(pendingArgs, PS_LIST_TAIL, "-guide_version",  0,        "define guide_version of interest", 0);
+
+    // -define inputs
+    psMetadata *defineArgs = psMetadataAlloc();
+    psMetadataAddStr(defineArgs, PS_LIST_TAIL, "-exp_id",  0,        "define class", NULL);
+    psMetadataAddStr(defineArgs, PS_LIST_TAIL, "-inst",  0,        "define camera of interest", NULL);
+    psMetadataAddStr(defineArgs, PS_LIST_TAIL, "-telescope",  0,        "define camera of interest", NULL);
+    psMetadataAddStr(defineArgs, PS_LIST_TAIL, "-exp_type",  0,        "define class", NULL);
+    psMetadataAddS32(defineArgs, PS_LIST_TAIL, "-imfiles",  0,        "define number of imfiles", 0);
+    psMetadataAddStr(defineArgs, PS_LIST_TAIL, "-filter",  0,        "define filter of interest", NULL);
+    // these two are inputs NOT search parameters
+    psMetadataAddStr(defineArgs, PS_LIST_TAIL, "-recipe",  0,        "define recipe of interest", NULL);
+    psMetadataAddS32(defineArgs, PS_LIST_TAIL, "-guide_version",  0,        "define guide_version of interest", 0);
+
+    // find which mode we're running under
+    int N = 0;
+    config->mode = PX_MODE_NONE;
+    if ((N = psArgumentGet (argc, argv, "-pending"))) {
+        psArgumentRemove (N, &argc, argv);
+        if (config->mode) {
+            psError(PS_ERR_UNKNOWN, true, "only one mode selection is allowed");
+        }
+        config->mode = PX_MODE_PENDING;
+    }
+    if ((N = psArgumentGet (argc, argv, "-define"))) {
+        psArgumentRemove (N, &argc, argv);
+        if (config->mode) {
+            psError(PS_ERR_UNKNOWN, true, "only one mode selection is allowed");
+        }
+        config->mode = PX_MODE_DEFINE;
+    }
+
+    // parse CLI for these options
+    switch (config->mode) {
+        case PX_MODE_PENDING:
+            config->args = psMemIncrRefCounter(pendingArgs);
+            break;
+        case PX_MODE_DEFINE:
+            config->args = psMemIncrRefCounter(defineArgs);
+            break;
+        default:
+            config->args = psMemIncrRefCounter(args);
+    }
+
+    bool argErr = false;
+    if (config->mode == PX_MODE_NONE) {
+        argErr = true;
+        fprintf (stderr, "mode argument is required\n");
+    } else if (! psArgumentParse(config->args, &argc, argv) || argc != 1) {
+        argErr = true;
+        fprintf (stderr, "error parsing arguments\n");
+    }
+
+    if (argErr) {
+        printf("\nPan-STARRS Guide Star Tool\n");
+        printf("Usage: %s <mode> [<options>]\n\n", argv[0]);
+        printf(" <mode> : -pending | -define\n\n");
+        fprintf (stdout, "-pending ");
+        psArgumentHelp(pendingArgs);
+        fprintf (stdout, "-define ");
+        psArgumentHelp(defineArgs);
+        psFree(args);
+        psFree(pendingArgs);
+        psFree(defineArgs);
+        exit(EXIT_FAILURE);
+    }
+
+    psFree(args);
+    psFree(pendingArgs);
+    psFree(defineArgs);
+
+    // define Database handle, if used
+    config->dbh = psMemIncrRefCounter(pmConfigDB(config->modules));
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        exit(EXIT_FAILURE);
+    }
+
+    return config;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/magictool.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/magictool.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/magictool.c	(revision 22157)
@@ -0,0 +1,1076 @@
+/*
+ * magictool.c
+ *
+ * Copyright (C) 2006-2007  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVB_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+#include <ippdb.h>
+
+#include "pxtools.h"
+#include "magictool.h"
+
+static bool definebyqueryMode(pxConfig *config);
+static psS64 definerunMode(pxConfig *config);
+static bool updaterunMode(pxConfig *config);
+static bool addinputskyfileMode(pxConfig *config);
+static bool inputskyfileMode(pxConfig *config);
+static bool totreeMode(pxConfig *config);
+static bool inputtreeMode(pxConfig *config);
+static bool toprocessMode(pxConfig *config);
+static bool addresultMode(pxConfig *config);
+static bool inputsMode(pxConfig *config);
+static bool tomaskMode(pxConfig *config);
+static bool addmaskMode(pxConfig *config);
+static bool maskMode(pxConfig *config);
+
+static bool setmagicRunState(pxConfig *config, psS64 magic_id, const char *state);
+static bool parseAndInsertNodeDeps(pxConfig *config, psS64 magic_id, const char *filename);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = magictoolConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(MAGICTOOL_MODE_DEFINEBYQUERY,  definebyqueryMode);
+        MODECASE(MAGICTOOL_MODE_DEFINERUN,      definerunMode);
+        MODECASE(MAGICTOOL_MODE_UPDATERUN,      updaterunMode);
+        MODECASE(MAGICTOOL_MODE_ADDINPUTSKYFILE,addinputskyfileMode);
+        MODECASE(MAGICTOOL_MODE_INPUTSKYFILE,   inputskyfileMode);
+        MODECASE(MAGICTOOL_MODE_TOTREE,         totreeMode);
+        MODECASE(MAGICTOOL_MODE_INPUTTREE,      inputtreeMode);
+        MODECASE(MAGICTOOL_MODE_TOPROCESS,      toprocessMode);
+        MODECASE(MAGICTOOL_MODE_ADDRESULT,      addresultMode);
+        MODECASE(MAGICTOOL_MODE_INPUTS,         inputsMode);
+        MODECASE(MAGICTOOL_MODE_TOMASK,         tomaskMode);
+        MODECASE(MAGICTOOL_MODE_ADDMASK,        addmaskMode);
+        MODECASE(MAGICTOOL_MODE_MASK,           maskMode);
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+static bool definebyqueryMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // create warped skycells temp table
+    {
+	psString query = pxDataGet("magictool_create_tmp_warpcomplete.sql");
+	if (!query) {
+	    psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+	    return false;
+	}
+
+	if (!p_psDBRunQuery(config->dbh, query)) {
+	    psError(PS_ERR_UNKNOWN, false, "database error");
+	    return false;
+	}
+    }
+
+    // find warped skycells
+    {
+	psString query = pxDataGet("magictool_find_complete_warpruns.sql");
+	if (!query) {
+	    psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+	    return false;
+	}
+
+	if (!p_psDBRunQuery(config->dbh, query)) {
+	    psError(PS_ERR_UNKNOWN, false, "database error");
+	    psFree(query);
+	    return false;
+	}
+	psFree(query);
+    }
+
+    // find the diff_id's of the warped skycells
+    {
+	psString query = pxDataGet("magictool_find_complete_diffed_exposures.sql");
+	if (!query) {
+	    psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+	    return false;
+	}
+
+	if (!p_psDBRunQuery(config->dbh, query)) {
+	    psError(PS_ERR_UNKNOWN, false, "database error");
+	    psFree(query);
+	    return false;
+	}
+	psFree(query);
+    }
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+	  case PS_ERR_DB_CLIENT:
+	    psError(PXTOOLS_ERR_SYS, false, "database error");
+	  case PS_ERR_DB_SERVER:
+	    psError(PXTOOLS_ERR_PROG, false, "database error");
+	  default:
+	    psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("magictool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // pre-allocate enough hash buckets for 1 unique warp_id per row
+    psHash *groups = psHashAlloc(psArrayLength(output));
+    // iterate over result set, and group by warp_id
+    for (long i = 0; i < psArrayLength(output); i++) {
+        // lookup the key we're grouping by
+        bool status = false;
+        psS64 warp_id = psMetadataLookupS64(&status, output->data[i], "warp_id");
+        if (!status) {
+            psAbort("failed to lookup value for warp_id");
+        }
+
+        psString key = psDBIntToString(warp_id);
+        // look to see if there is a bin for this key
+        psArray *groupBin = psHashLookup(groups, key);
+        if (!groupBin) {
+            // if not, create one
+            groupBin = psArrayAlloc(0);
+            psHashAdd(groups, key, groupBin);
+        }
+        psFree(key);
+
+        // add this row to the bin
+        psArrayAdd(groupBin, 0, output->data[i]);
+        psFree(groupBin);
+    }
+    psFree(output);
+
+    // create a magic run
+    psArray *grouped =  psHashToArray(groups);
+    psFree(groups);
+
+    // iterate over array of grouped warp_ids
+    for (long i = 0; i < psArrayLength(grouped); i++) {
+        psArray *group = grouped->data[i];
+
+        // create a new magicRun for this group
+        // XXX This need to have options passed through to it
+
+        magicRunRow *run = magicRunRowAlloc(
+	    0,          // ID
+	    "reg",      // state
+	    NULL,       // workdir
+	    "dirty",    // workdir_state
+	    NULL,       // label
+	    NULL,       // dvodb
+	    NULL,       // registered
+	    0           // fault
+	    );
+        if (!run) {
+            psAbort("failed to alloc magicRun object");
+        }
+        if (!magicRunInsertObject(config->dbh, run)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(run);
+            psFree(grouped);
+            return false;
+        }
+
+        // get the assigned warp_id
+        psS64 magic_id = psDBLastInsertID(config->dbh);
+
+        // insert all rows in this bin into the new magicRun
+        for (long j = 0; j < psArrayLength(group); j++) {
+            psMetadata *row = group->data[j];
+
+            bool status = false;
+            psS64 diff_id = psMetadataLookupS64(&status, row, "diff_id");
+            if (!status) {
+                psAbort("failed to lookup value for diff_id");
+            }
+            psString node = psMetadataLookupStr(&status, row, "skycell_id");
+            if (!status) {
+                psAbort("failed to lookup value for diff_id");
+            }
+
+            if (!magicInputSkyfileInsert(config->dbh,
+					 magic_id,
+					 diff_id,
+					 node)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+                psFree(grouped);
+                return false;
+            }
+        }
+
+        // set magicRun.state to 'run'
+        return setmagicRunState(config, magic_id, "run");
+    }
+    psFree(grouped);
+
+    return true;
+}
+
+static psS64 definerunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required
+    PXOPT_LOOKUP_STR(workdir, config->args, "-workdir", true, false);
+
+    // optional
+    PXOPT_LOOKUP_STR(label, config->args, "-label", false, false);
+    PXOPT_LOOKUP_STR(dvodb, config->args, "-dvodb", false, false);
+    PXOPT_LOOKUP_TIME(registered, config->args, "-registered", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    magicRunRow *run = magicRunRowAlloc(
+            0,          // ID
+            "reg",      // state
+            workdir,
+            "dirty",    // workdir_state
+            label,
+            dvodb,
+            registered,
+            0
+    );
+
+    if (!run) {
+        psError(PS_ERR_UNKNOWN, false, "failed to alloc magicRun object");
+        return false;
+    }
+    if (!magicRunInsertObject(config->dbh, run)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(run);
+        return false;
+    }
+
+    // get the assigned warp_id
+    psS64 magic_id = psDBLastInsertID(config->dbh);
+    run->magic_id = magic_id;
+
+    if (!magicRunPrintObject(stdout, run, !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print object");
+            psFree(run);
+            return false;
+    }
+
+    psFree(run);
+
+    return magic_id;
+}
+
+
+static bool updaterunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required
+    PXOPT_LOOKUP_STR(magic_id, config->args, "-magic_id", true, false);
+    PXOPT_LOOKUP_STR(state, config->args, "-state", true, false);
+
+    if (state) {
+        // set detRun.state to state
+        return setmagicRunState(config, (psS64)atoll(magic_id), state);
+    }
+
+    return true;
+}
+
+
+static bool addinputskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required
+    PXOPT_LOOKUP_STR(magic_id, config->args, "-magic_id", true, false);
+    PXOPT_LOOKUP_STR(diff_id, config->args, "-diff_id", true, false);
+    PXOPT_LOOKUP_STR(node, config->args, "-node", true, false);
+
+    magicInputSkyfileInsert(
+            config->dbh,
+            (psS64)atoll(magic_id),
+            (psS64)atoll(diff_id),
+            node
+    );
+
+    return true;
+}
+
+
+static bool inputskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-magic_id", "magic_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-diff_id", "diff_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-node", "node", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    // find all rawImfiles matching the default query
+    psString query = pxDataGet("magictool_inputskyfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "magicInputSkyfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("magictool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "magicInputSkyfile", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool totreeMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-magic_id", "magic_id", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    // look for "inputs" that need to processed
+    psString query = pxDataGet("magictool_totree.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+XXX : add where test
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("magictool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "totree", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool inputtreeMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required
+    PXOPT_LOOKUP_STR(magic_id, config->args, "-magic_id", true, false);
+    PXOPT_LOOKUP_STR(dep_file, config->args, "-dep_file", true, false);
+
+    if (!parseAndInsertNodeDeps(config, (psS64)atoll(magic_id), dep_file)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to parse file");
+        return false;
+    }
+
+    return true;
+}
+
+
+bool findBaseNodes(void *arg, pxNode *node)
+{
+    bool status = false;
+    psS64 done = psMetadataLookupS64(&status, node->data, "done");
+    if (!status) {
+        psAbort("failed to lookup value for done column");
+    }
+
+    if ((!pxNodeHasChildren(node)) && (!done)) {
+        // if this node has no child and it's not 'done', then push it's data
+        // onto the void *array
+        psArrayAdd((psArray *)arg, 0, node->data);
+        return false;
+    }
+
+    return true;
+}
+
+
+bool findReadyNodes(void *arg, pxNode *node)
+{
+    if (psMetadataLookupBool(NULL, node->data, "done")) {
+        // It's already done
+        return true;
+    }
+
+    if (pxNodeHasChildren(node)) {
+        psListIterator *iter = psListIteratorAlloc(node->children, 0, false);
+        psMetadata *work = psMetadataCopy(NULL, node->data);
+        psMetadataRemoveKey(work, "dep");
+        psMetadataRemoveKey(work, "done");
+#if 0
+        psMetadataRemoveKey(work, "uri");
+#endif
+        pxNode *child = NULL;
+        while ((child = psListGetAndIncrement(iter))) {
+            psMetadata *data = child->data;
+
+            bool status = false;
+            psS32 done = psMetadataLookupS32(&status, data, "done");
+            if (!status) {
+                psAbort("failed to lookup value for done column");
+            }
+
+            if (!done) {
+                // if a child isn't "done", give up on this node and continue
+                // to crawl the tree
+                psFree(iter);
+                psFree(work);
+                return true;
+            }
+#if 0
+            char *uri = psMetadataLookupStr(&status, data, "uri");
+            if (!status) {
+                psAbort("failed to lookup value for uri column");
+            }
+
+            psMetadataAddStr(work, PS_LIST_TAIL, "uri", PS_META_DUPLICATE_OK, NULL, uri);
+#endif
+        }
+        psFree(iter);
+        // if all this nodes children are done, then push it's data onto the
+        // void *array
+        psArrayAdd((psArray *)arg, 0, work);
+        psFree(work);
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool toprocessMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-magic_id", "magic_id", "==");
+
+    psString whereClause = NULL;
+    if (psListLength(where->list)) {
+        whereClause = psDBGenerateWhereSQL(where, NULL);
+    }
+    psFree(where);
+
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    // look for "inputs" that need to processed
+    psString query = pxDataGet("magictool_toprocess_inputs.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (whereClause) {
+        psStringAppend(&query, " %s", whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("magictool", PS_LOG_INFO, "no rows found");
+	// psFree(output);
+        // return true;
+    }
+
+    // look for tree nodes that need to be processed
+    query = pxDataGet("magictool_toprocess_tree.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (whereClause) {
+        psStringAppend(&query, " %s", whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *magicTree = p_psDBFetchResult(config->dbh);
+    if (!magicTree) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(magicTree)) {
+        psTrace("magictool", PS_LOG_INFO, "no rows found");
+        psFree(magicTree);
+        return true;
+    }
+
+    psHash *forest = psHashAlloc(psArrayLength(magicTree));
+
+    // convert the array of metadata into a pxTree structure
+    for (long i = 0; i < psArrayLength(magicTree); i++) {
+        bool status;
+        psString node = psMetadataLookupStr(&status, magicTree->data[i], "node");
+        if (!status) {
+            psAbort("failed to lookup value for node column");
+        }
+
+        psString dep = psMetadataLookupStr(&status, magicTree->data[i], "dep");
+        if (!status) {
+            psAbort("failed to lookup value for dep column");
+        }
+
+        pxTreeBuilder(forest, node, dep, magicTree->data[i]);
+
+    }
+    psFree(magicTree);
+
+    // find the root of the tree
+    pxNode *root = psHashLookup(forest, "root");
+    psFree(forest);
+    // pxTreePrint(stdout, root);
+
+    // crawl through the tree and looking for nodes with children that are all
+    // "done"
+    pxTreeCrawl(root, findReadyNodes, output);
+    psFree(root);
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "magicMe", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+    psFree(whereClause);
+
+    return true;
+}
+
+
+static bool addresultMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required
+    PXOPT_LOOKUP_STR(magic_id, config->args, "-magic_id", true, false);
+    PXOPT_LOOKUP_STR(node, config->args, "-node", true, false);
+
+    // optional
+    PXOPT_LOOKUP_STR(uri, config->args, "-uri", false, false);
+
+    // default values
+    PXOPT_LOOKUP_S16(code, config->args, "-code", false, false);
+
+    if (!magicNodeResultInsert(config->dbh,
+                               (psS64)atoll(magic_id),
+                               node,
+                               uri,
+                               code
+        )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool inputsMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("magictool_inputs.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, NULL);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("magictool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "magicNode", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool tomaskMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    // look for "inputs" that need to processed
+    psString query = pxDataGet("magictool_tomask.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("magictool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "toprocess", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool addmaskMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required
+    PXOPT_LOOKUP_STR(magic_id, config->args, "-magic_id", true, false);
+
+    // optional
+    PXOPT_LOOKUP_STR(uri, config->args, "-uri", false, false);
+    PXOPT_LOOKUP_S32(streaks, config->args, "-streaks", false, false);
+
+    // default values
+    PXOPT_LOOKUP_S16(code, config->args, "-code", false, false);
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!magicMaskInsert(config->dbh,
+                         (psS64)atoll(magic_id),
+                         uri,
+                         streaks,
+                         code
+        )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // Set the magicRun state
+    psString query = pxDataGet("magictool_addmask.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // Add "magic_id = value"
+    psString whereClause = psDBGenerateWhereConditionSQL(config->where, NULL);
+    psStringAppend(&query, " AND %s", whereClause);
+    psFree(whereClause);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool maskMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-magic_id", "magicRun.magic_id", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("magictool_mask.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("magictool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "magicNode", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool setmagicRunState(pxConfig *config, psS64 magic_id, const char *state)
+{
+    PS_ASSERT_PTR_NON_NULL(state, false);
+
+    // check that state is a valid string value
+    if (!(
+            (strncmp(state, "run", 4) == 0)
+            || (strncmp(state, "stop", 5) == 0)
+            || (strncmp(state, "reg", 4) == 0)
+        )
+    ) {
+        psError(PS_ERR_UNKNOWN, false,
+                "invalid magicRun state: %s", state);
+        return false;
+    }
+
+    char *query = "UPDATE magicRun SET state = '%s' WHERE magic_id = %" PRId64;
+    if (!p_psDBRunQuery(config->dbh, query, state, magic_id)) {
+        psError(PS_ERR_UNKNOWN, false,
+                "failed to change state for magic_id %" PRId64, magic_id);
+        return false;
+    }
+
+    return true;
+}
+
+static bool parseAndInsertNodeDeps(pxConfig *config, psS64 magic_id, const char *filename)
+{
+    unsigned int nFail = 0;
+    psMetadata *deps = psMetadataConfigRead(NULL, &nFail, filename, false);
+    if (!deps) {
+        psError(PS_ERR_UNKNOWN, false, "failed to parse file: %s", filename);
+        return false;
+    }
+    if (nFail) {
+        psError(PS_ERR_UNKNOWN, false, "there were %d errors parsing file: %s", nFail, filename);
+        psFree(deps);
+        return false;
+    }
+
+    psMetadataItem *item = NULL;
+    psMetadataIterator *iter = psMetadataIteratorAlloc(deps, 0, NULL);
+    while ((item = psMetadataGetAndIncrement(iter))) {
+        if (item->type != PS_DATA_STRING) {
+            psError(PS_ERR_UNKNOWN, false, "file: %s is in the wrong format", filename);
+            psFree(iter);
+            psFree(deps);
+            return false;
+        }
+
+        char *name = item->name;
+        char *dependsOn = item->data.str;
+
+        if (!magicTreeInsert(
+                config->dbh,
+                magic_id,
+                name,
+                dependsOn
+            )) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(iter);
+            psFree(deps);
+            return false;
+        }
+    }
+    psFree(iter);
+    psFree(deps);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/magictool.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/magictool.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/magictool.h	(revision 22157)
@@ -0,0 +1,44 @@
+/*
+ * magictool.h
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MAGICTOOL_H
+#define MAGICTOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    MAGICTOOL_MODE_NONE           = 0x0,
+    MAGICTOOL_MODE_DEFINEBYQUERY,
+    MAGICTOOL_MODE_DEFINERUN,
+    MAGICTOOL_MODE_UPDATERUN,
+    MAGICTOOL_MODE_ADDINPUTSKYFILE,
+    MAGICTOOL_MODE_INPUTSKYFILE,
+    MAGICTOOL_MODE_TOTREE,
+    MAGICTOOL_MODE_INPUTTREE,
+    MAGICTOOL_MODE_TOPROCESS,
+    MAGICTOOL_MODE_INPUTS,
+    MAGICTOOL_MODE_ADDRESULT,
+    MAGICTOOL_MODE_TOMASK,
+    MAGICTOOL_MODE_ADDMASK,
+    MAGICTOOL_MODE_MASK,
+} MAGICtoolMode;
+
+pxConfig *magictoolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // MAGICTOOL_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/magictoolConfig.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/magictoolConfig.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/magictoolConfig.c	(revision 22157)
@@ -0,0 +1,184 @@
+/*
+ * magictoolConfig.c
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdint.h>
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "magictool.h"
+
+pxConfig *magictoolConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (!config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    psTime *now = psTimeGetNow(PS_TIME_TAI);
+
+    // -definebyquery
+    psMetadata *queueArgs = psMetadataAlloc();
+
+    // -definerun
+    psMetadata *definerunArgs = psMetadataAlloc();
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-workdir", 0,            "define workdir (required)", NULL);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-label", 0,            "define label", NULL);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-dvodb", 0,            "define dvodb", NULL);
+    psMetadataAddTime(definerunArgs, PS_LIST_TAIL, "-registered",  0,            "time detrend run was registered", now);
+    psMetadataAddBool(definerunArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -updaterun
+    psMetadata *updaterunArgs = psMetadataAlloc();
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-magic_id", 0,            "define warptool ID (required)", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-state", 0,            "set state (required)", NULL);
+
+    // -addinputskyfile
+    psMetadata *addinputskyfileArgs = psMetadataAlloc();
+    psMetadataAddStr(addinputskyfileArgs, PS_LIST_TAIL, "-magic_id", 0,            "define magictool ID (required)", NULL);
+    psMetadataAddStr(addinputskyfileArgs, PS_LIST_TAIL, "-diff_id", 0,            "define difftool ID (required)", NULL);
+    psMetadataAddStr(addinputskyfileArgs, PS_LIST_TAIL, "-node",  0,            "define symbolic node name (required)", NULL);
+
+    // -inputskyfile
+    psMetadata *inputskyfileArgs = psMetadataAlloc();
+    psMetadataAddStr(inputskyfileArgs, PS_LIST_TAIL, "-magic_id", 0,            "search by magictool ID", NULL);
+    psMetadataAddStr(inputskyfileArgs, PS_LIST_TAIL, "-diff_id", 0,            "search by difftool ID", NULL);
+    psMetadataAddStr(inputskyfileArgs, PS_LIST_TAIL, "-node",  0,            "search by symbolic node name", NULL);
+    psMetadataAddU64(inputskyfileArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(inputskyfileArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -totree
+    psMetadata *totreeArgs = psMetadataAlloc();
+    psMetadataAddStr(totreeArgs, PS_LIST_TAIL, "-magic_id", 0, "search by magic ID", NULL);
+    psMetadataAddU64(totreeArgs, PS_LIST_TAIL, "-limit", 0, "limit result set to N items", 0);
+    psMetadataAddBool(totreeArgs, PS_LIST_TAIL, "-simple", 0, "use the simple output format", false);
+
+    // -inputtree
+    psMetadata *inputtreeArgs = psMetadataAlloc();
+    psMetadataAddStr(inputtreeArgs, PS_LIST_TAIL, "-magic_id", 0,            "define magictool ID (required)", NULL);
+    psMetadataAddStr(inputtreeArgs, PS_LIST_TAIL, "-dep_file", 0,            "order of operations dep. file (required)", NULL);
+    psMetadataAddS16(inputtreeArgs, PS_LIST_TAIL, "-code",  0, "set fault code", 0);
+
+    // -inputs
+    psMetadata *inputsArgs = psMetadataAlloc();
+    psMetadataAddStr(inputsArgs, PS_LIST_TAIL, "-magic_id", 0,            "search by magictool ID", NULL);
+    psMetadataAddStr(inputsArgs, PS_LIST_TAIL, "-node",  0,            "search by symbolic node name", NULL);
+    psMetadataAddU64(inputsArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(inputsArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -tooprocess
+    psMetadata *toprocessArgs = psMetadataAlloc();
+    psMetadataAddStr(toprocessArgs, PS_LIST_TAIL, "-magic_id", 0,            "search by magic ID", NULL);
+    psMetadataAddU64(toprocessArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(toprocessArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -addresult
+    psMetadata *addresultArgs = psMetadataAlloc();
+    psMetadataAddStr(addresultArgs, PS_LIST_TAIL, "-magic_id", 0,            "define magictool ID (required)", NULL);
+    psMetadataAddStr(addresultArgs, PS_LIST_TAIL, "-node",  0,            "define symbolic node name (required)", NULL);
+    psMetadataAddStr(addresultArgs, PS_LIST_TAIL, "-uri",  0,            "define URI (required)", NULL);
+    psMetadataAddS16(addresultArgs, PS_LIST_TAIL, "-code",  0, "set fault code", 0);
+
+    // -tomask
+    psMetadata *tomaskArgs = psMetadataAlloc();
+    psMetadataAddU64(tomaskArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(tomaskArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -addmask
+    psMetadata *addmaskArgs = psMetadataAlloc();
+    psMetadataAddStr(addmaskArgs, PS_LIST_TAIL, "-magic_id", 0,          "define magictool ID (required)", NULL);
+    psMetadataAddStr(addmaskArgs, PS_LIST_TAIL, "-uri",  0, "define URI", NULL);
+    psMetadataAddS32(addmaskArgs, PS_LIST_TAIL, "-streaks",  0, "define number of streaks", 0);
+    psMetadataAddS16(addmaskArgs, PS_LIST_TAIL, "-code",  0, "set fault code", 0);
+
+# if (0)
+    // -toskyfilemask
+    psMetadata *toskyfilemaskArgs = psMetadataAlloc();
+    psMetadataAddStr(toskyfilemaskArgs, PS_LIST_TAIL, "-magic_id", 0,            "search by magic ID", NULL);
+    psMetadataAddU64(toskyfilemaskArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(toskyfilemaskArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -addskyfilemask
+    psMetadata *addskyfilemaskArgs = psMetadataAlloc();
+    psMetadataAddStr(addskyfilemaskArgs, PS_LIST_TAIL, "-magic_id", 0,            "define magictool ID (required)", NULL);
+    psMetadataAddStr(addskyfilemaskArgs, PS_LIST_TAIL, "-diff_id",  0,            "define difftool ID (required)", NULL);
+    psMetadataAddStr(addskyfilemaskArgs, PS_LIST_TAIL, "-uri",  0,            "define URI (required)", NULL);
+# endif
+
+    // -mask
+    psMetadata *maskArgs = psMetadataAlloc();
+    psMetadataAddStr(maskArgs, PS_LIST_TAIL, "-magic_id", 0, "define magictool ID", NULL);
+    psMetadataAddU64(maskArgs, PS_LIST_TAIL, "-limit", 0, "limit result set to N items", 0);
+    psMetadataAddBool(maskArgs, PS_LIST_TAIL, "-simple", 0, "use the simple output format", false);
+
+    psFree(now);
+
+    psMetadata *argSets = psMetadataAlloc();
+    psMetadata *modes   = psMetadataAlloc();
+
+    PXOPT_ADD_MODE("-definebyquery",   "", MAGICTOOL_MODE_DEFINEBYQUERY,   queueArgs);
+    PXOPT_ADD_MODE("-definerun",       "", MAGICTOOL_MODE_DEFINERUN,       definerunArgs);
+    PXOPT_ADD_MODE("-updaterun",       "", MAGICTOOL_MODE_UPDATERUN,       updaterunArgs);
+    PXOPT_ADD_MODE("-addinputskyfile", "", MAGICTOOL_MODE_ADDINPUTSKYFILE, addinputskyfileArgs);
+    PXOPT_ADD_MODE("-inputskyfile",    "", MAGICTOOL_MODE_INPUTSKYFILE,    inputskyfileArgs);
+    PXOPT_ADD_MODE("-totree",          "", MAGICTOOL_MODE_TOTREE,          totreeArgs);
+    PXOPT_ADD_MODE("-inputtree",       "", MAGICTOOL_MODE_INPUTTREE,       inputtreeArgs);
+    PXOPT_ADD_MODE("-toprocess",       "", MAGICTOOL_MODE_TOPROCESS,       toprocessArgs);
+    PXOPT_ADD_MODE("-inputs",          "", MAGICTOOL_MODE_INPUTS,          inputsArgs);
+    PXOPT_ADD_MODE("-addresult",       "", MAGICTOOL_MODE_ADDRESULT,       addresultArgs);
+    PXOPT_ADD_MODE("-tomask",          "", MAGICTOOL_MODE_TOMASK,          tomaskArgs);
+    PXOPT_ADD_MODE("-addmask",         "", MAGICTOOL_MODE_ADDMASK,         addmaskArgs);
+    PXOPT_ADD_MODE("-mask",            "", MAGICTOOL_MODE_MASK,            maskArgs);
+
+    if (!pxGetOptions(stderr, argc, argv, config, modes, argSets)) {
+        psError(PS_ERR_UNKNOWN, true, "option parsing failed");
+        psFree(argSets);
+        psFree(modes);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+    psFree(modes);
+
+    // define Database handle, if used
+    // do this last so we don't setup a connection before CLI options are
+    // validated
+    config->dbh = psMemIncrRefCounter(pmConfigDB(config->modules));
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    return config;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pstamptool.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pstamptool.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pstamptool.c	(revision 22157)
@@ -0,0 +1,707 @@
+/*
+ * pstamptool.c
+ *
+ * Copyright (C) 2008  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+#include "pxtools.h"
+#include "pxdata.h"
+#include "pstamptool.h"
+
+static bool adddatastoreMode(pxConfig *config);
+static bool datastoreMode(pxConfig *config);
+static bool moddatastoreMode(pxConfig *config);
+static bool addReqMode(pxConfig *config);
+static bool completedReqMode(pxConfig *config);
+static bool listReqMode(pxConfig *config);
+static bool pendingReqMode(pxConfig *config);
+static bool processedReqMode(pxConfig *config);
+static bool addJobMode(pxConfig *config);
+static bool listJobMode(pxConfig *config);
+static bool pendingJobMode(pxConfig *config);
+static bool processedJobMode(pxConfig *config);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+                goto FAIL; \
+            } \
+    break;
+
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = pstamptoolConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(PSTAMPTOOL_MODE_ADDDATASTORE, adddatastoreMode);
+        MODECASE(PSTAMPTOOL_MODE_DATASTORE, datastoreMode);
+        MODECASE(PSTAMPTOOL_MODE_MODDATASTORE, moddatastoreMode);
+        MODECASE(PSTAMPTOOL_MODE_ADDREQ, addReqMode);
+        MODECASE(PSTAMPTOOL_MODE_COMPLETEDREQ, completedReqMode);
+        MODECASE(PSTAMPTOOL_MODE_LISTREQ, listReqMode);
+        MODECASE(PSTAMPTOOL_MODE_PENDINGREQ, pendingReqMode);
+        MODECASE(PSTAMPTOOL_MODE_PROCESSEDREQ, processedReqMode);
+        MODECASE(PSTAMPTOOL_MODE_ADDJOB, addJobMode);
+        MODECASE(PSTAMPTOOL_MODE_LISTJOB, listJobMode);
+        MODECASE(PSTAMPTOOL_MODE_PENDINGJOB, pendingJobMode);
+        MODECASE(PSTAMPTOOL_MODE_PROCESSEDJOB, processedJobMode);
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+static bool adddatastoreMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_STR(uri,         config->args, "-uri",           true, false);
+    PXOPT_LOOKUP_STR(outProduct,  config->args, "-out_product",   true, false);
+    PXOPT_LOOKUP_STR(lastFileset, config->args, "-last_fileset", false, false);
+    PXOPT_LOOKUP_STR(state,       config->args, "-state",         false, false);
+
+    if (!pstampDataStoreInsert(config->dbh,
+            0,
+            state,
+            lastFileset,
+            outProduct,
+            uri
+        )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool datastoreMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-ds_id", "ds_id", "==");
+
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("pstamptool_datastore.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereSQL(where, NULL);
+        psStringAppend(&query, " %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("pstamptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "pstampDataStore", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+static bool moddatastoreMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_STR(ds_id,       config->args, "-ds_id",         true, false);
+    PXOPT_LOOKUP_STR(lastFileset, config->args, "-last_fileset",  false, false);
+    PXOPT_LOOKUP_STR(state,       config->args, "-state",         false, false);
+
+    if (!state && !lastFileset) {
+        psError(PS_ERR_UNKNOWN, true, "at least one of -last_fileset or -state is required");
+        return false;
+    }
+
+    char *query = psStringCopy ("UPDATE pstampDataStore SET");
+    bool needComma = false;
+    
+    if (lastFileset) {
+        psStringAppend(&query, " lastFileset = '%s'", lastFileset);
+        needComma = true;
+    }
+
+    if (state) {
+        psStringAppend(&query, "%s state = '%s'", needComma ? "," : " ", state);
+        needComma = true; // be ready in case we add another field
+    }
+                
+    psStringAppend(&query, " WHERE ds_id = '%s'", ds_id);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+
+    psU64 affected = psDBAffectedRows(config->dbh);
+    if (affected != 1) {
+        psError(PS_ERR_UNKNOWN, false, "should have affected one row but %" 
+                                        PRIu64 " rows were modified", affected);
+        return false;
+    }
+
+    return true;
+}
+
+#define  MAGIC_STR  "$REQ_ID"
+
+// Replace $REQ_ID with the value of the request id if present in the outFileset
+// XXX EAM : rewrite this using psStringSubstitute:
+// word = psStringAppend (NULL, "%" PRId64, req_id;
+// converted = psStringSubstitute (outFileset, word, MAGIC_STR); 
+
+static bool
+updateFilesetID(pxConfig *config, psS64 req_id, psString outFileset)
+{
+    char *magic;
+
+    magic = strcasestr(outFileset, MAGIC_STR);
+    if (magic == NULL) {
+        return true;
+    }
+
+    // find part of the string after the magic
+    char *after = magic + strlen(MAGIC_STR);
+
+    if (*after == 0) {
+        after = NULL;
+    }
+
+    psString converted = NULL;
+    if (magic > outFileset) {
+        int len = magic - outFileset;
+        converted = psStringAlloc(len + 1);
+        strncpy(converted, outFileset, len);
+        converted[len] = 0;
+    }
+
+    psStringAppend(&converted, "%" PRId64, req_id);
+    if (after) {
+        psStringAppend(&converted, after);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, "UPDATE pstampRequest set outFileset = '%s' WHERE req_id = %" PRId64,
+            converted, req_id)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool addReqMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_STR(uri,         config->args, "-uri",           true, false);
+    PXOPT_LOOKUP_STR(outFileset,  config->args, "-out_fileset",   true, false);
+    PXOPT_LOOKUP_STR(ds_id,       config->args, "-ds_id",         false, false);
+
+    char *query ="INSERT INTO pstampRequest"
+                 " (state, uri, ds_id, outFileset, resultsFile)"
+                 " VALUES( 'new', '%s', %s, '%s', 0)";
+
+    if (!p_psDBRunQuery(config->dbh, query, uri, ds_id, outFileset)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    psU64 affected = psDBAffectedRows(config->dbh);
+    if (affected != 1) {
+        psError(PS_ERR_UNKNOWN, false, 
+            "should have affected one row but %" PRIu64 " rows were modified",
+            affected);
+        return false;
+    }
+
+    psS64 req_id = psDBLastInsertID(config->dbh);
+    if (!updateFilesetID(config, req_id, outFileset)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to update fileset id\n");
+        return false;
+    }
+
+    printf("%" PRId64 "\n", req_id);
+
+    return true;
+}
+
+static bool pendingReqMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-ds_id", "ds_id", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("pstamptool_pendingreq.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "pstampRequest");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("pstamptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "pstampRequest", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool listReqMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_STR(req_id,  config->args, "-req_id", true, false);
+    PXOPT_LOOKUP_U64(limit,   config->args, "-limit",  false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = NULL;
+    psStringAppend(&query, "SELECT"
+		   " *"
+		   " FROM pstampRequest"
+		   " WHERE req_id = %s", req_id);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("pstamptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "pstampRequest", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool completedReqMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = 
+        "SELECT * FROM pstampRequest WHERE state = 'run' AND "
+            "(SELECT count(*) FROM pstampJob WHERE pstampJob.req_id = pstampRequest.req_id "
+            " AND pstampJob.state != 'stop') = 0" ;
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!psArrayLength(output)) {
+        psTrace("pstamptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "pstampRequest", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool processedReqMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_STR(req_id, config->args, "-req_id", true, false);
+    PXOPT_LOOKUP_STR(state,  config->args, "-state",  true, false);
+
+    // XXX: check state for a legal value
+
+    char *query ="UPDATE pstampRequest"
+                 " SET state = '%s'"
+                 " WHERE req_id = '%s'";
+
+    if (!p_psDBRunQuery(config->dbh, query, state, req_id)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+
+    psU64 affected = psDBAffectedRows(config->dbh);
+    if (affected != 1) {
+        psError(PS_ERR_UNKNOWN, false, "should have affected one row but %" 
+                                        PRIu64 " rows were modified", affected);
+        return false;
+    }
+
+    return true;
+}
+
+static bool addJobMode(pxConfig *config)
+{
+    bool    stampJob = false;
+    char   *query = NULL;
+
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_STR(uri,         config->args, "-uri",        true, false);
+    PXOPT_LOOKUP_STR(req_id,      config->args, "-req_id",     true, false);
+    PXOPT_LOOKUP_STR(job_type,    config->args, "-job_type",   false, false);
+    PXOPT_LOOKUP_STR(outputBase,  config->args, "-outputBase", true,  false);
+    PXOPT_LOOKUP_STR(argString,   config->args, "-args",       true,  false);
+    PXOPT_LOOKUP_STR(stateString, config->args, "-state",      false, false);
+    PXOPT_LOOKUP_STR(result,      config->args, "-result",     false, false);
+
+    if (!strcmp(job_type, "get_image") || !strcmp(job_type, "detect_query")) {
+	stampJob = false;
+    } else if (!strcmp(job_type, "stamp")) {
+	stampJob = true;
+    } else {
+	psError(PS_ERR_UNKNOWN, false, "unknown value for -job_type: %s", job_type);
+	return false;
+    }
+    
+    if (stampJob) {
+	query = pxDataGet("pstamptool_addjob_stampjob.sql");
+    } else {
+	query = pxDataGet("pstamptool_addjob_otherjob.sql");
+    }
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query, req_id, stateString, job_type, uri, outputBase, result, argString)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    psU64 affected = psDBAffectedRows(config->dbh);
+    if (affected != 1) {
+        psError(PS_ERR_UNKNOWN, false, 
+            "should have affected one row but %" PRIu64 " rows were modified",
+            affected);
+        return false;
+    }
+
+    psS64 job_id = psDBLastInsertID(config->dbh);
+    printf("%" PRId64 "\n", job_id);
+
+    return true;
+}
+
+static bool listJobMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_STR(req_id, config->args, "-req_id", false, false);
+    PXOPT_LOOKUP_STR(job_id, config->args, "-job_id", false, false);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = NULL;
+
+    if (!req_id && !job_id) {
+        fprintf(stderr, "either req_id or job_id must be specified\n");
+        exit (1);
+    }
+
+    if (req_id) {
+        psStringAppend(&query,
+                "SELECT"
+                " *"
+                " FROM pstampJob"
+                " WHERE req_id = %s", req_id
+            );
+    } else {
+        psStringAppend(&query,
+                "SELECT"
+                " *"
+                " FROM pstampJob"
+                " WHERE job_id = %s", job_id
+            );
+    } 
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("pstamptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "pstampJob", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool pendingJobMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-cam_id", "cam_id", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("pstamptool_pendingjob.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // use psDBGenerateWhereSQL because the SQL yields an intermediate table
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "pstampJob");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("pstamptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "pstampJob", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool processedJobMode(pxConfig *config)
+{
+    bool    status;
+    char *query = NULL;
+
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_STR(job_id,    config->args, "-job_id", true, false);
+    PXOPT_LOOKUP_STR(state,     config->args, "-state",  true, false);
+    PXOPT_LOOKUP_STR(jobResult, config->args, "-result", false, false);
+    // XXX: check state for a legal value
+
+    if (jobResult) {
+	query = psStringCopy ("UPDATE pstampJob"
+			      " SET state = '%s', result = '%s'"
+			      " WHERE job_id = '%s'");
+	status = p_psDBRunQuery(config->dbh, query, state, jobResult, job_id);
+    } else {
+	query = psStringCopy ("UPDATE pstampJob"
+			      " SET state = '%s'"
+			      " WHERE job_id = '%s'");
+	status = p_psDBRunQuery(config->dbh, query, state, job_id);
+    }
+
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+
+    psU64 affected = psDBAffectedRows(config->dbh);
+    if (affected != 1) {
+        psError(PS_ERR_UNKNOWN, false, "should have affected one row but %" 
+                                        PRIu64 " rows were modified", affected);
+        return false;
+    }
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pstamptool.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pstamptool.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pstamptool.h	(revision 22157)
@@ -0,0 +1,44 @@
+/*
+ * pstamptool.h
+ *
+ * Copyright (C) 2008
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PSTAMPTOOL_H
+#define PSTAMPTOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    PSTAMPTOOL_MODE_NONE      = 0x0,
+    PSTAMPTOOL_MODE_ADDDATASTORE,
+    PSTAMPTOOL_MODE_DATASTORE,
+    PSTAMPTOOL_MODE_MODDATASTORE,
+    PSTAMPTOOL_MODE_ADDREQ,
+    PSTAMPTOOL_MODE_LISTREQ,
+    PSTAMPTOOL_MODE_COMPLETEDREQ,
+    PSTAMPTOOL_MODE_PENDINGREQ,
+    PSTAMPTOOL_MODE_PROCESSEDREQ,
+    PSTAMPTOOL_MODE_ADDJOB,
+    PSTAMPTOOL_MODE_LISTJOB,
+    PSTAMPTOOL_MODE_PENDINGJOB,
+    PSTAMPTOOL_MODE_JOBRESULT,
+    PSTAMPTOOL_MODE_PROCESSEDJOB,
+} pstamptoolMode;
+
+pxConfig *pstamptoolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // PSTAMPTOOL_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pstamptoolConfig.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pstamptoolConfig.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pstamptoolConfig.c	(revision 22157)
@@ -0,0 +1,160 @@
+/*
+ * pstamptoolConfig.c
+ *
+ * Copyright (C) 2008
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "pstamptool.h"
+
+pxConfig *pstamptoolConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (! config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration!\n");
+        psFree(config);
+        return NULL;
+    }
+
+    // -adddatastore
+    psMetadata *adddatastoreArgs = psMetadataAlloc();
+    psMetadataAddStr(adddatastoreArgs, PS_LIST_TAIL, "-uri",          0, "define storage uri (required)", NULL);
+    psMetadataAddStr(adddatastoreArgs, PS_LIST_TAIL, "-out_product",  0, "define output product name (required)", NULL);
+    psMetadataAddStr(adddatastoreArgs, PS_LIST_TAIL, "-last_fileset", 0, "define last fileset seen", NULL);
+    psMetadataAddStr(adddatastoreArgs, PS_LIST_TAIL, "-state",        0, "define datastore state", "enabled");
+    
+    // -datastore
+    psMetadata *datastoreArgs = psMetadataAlloc();
+    psMetadataAddStr(datastoreArgs, PS_LIST_TAIL, "-ds_id", 0,            "define ds_id", NULL); 
+    psMetadataAddBool(datastoreArgs, PS_LIST_TAIL, "-simple", 0,            "use the simple output format", false);
+
+    // -moddatastore
+    psMetadata *moddatastoreArgs = psMetadataAlloc();
+    psMetadataAddStr(moddatastoreArgs, PS_LIST_TAIL, "-ds_id", 0,            "define ds_id", "0"); 
+    psMetadataAddStr(moddatastoreArgs, PS_LIST_TAIL, "-last_fileset", 0,            "define storage uri", NULL);
+    psMetadataAddStr(moddatastoreArgs, PS_LIST_TAIL, "-state", 0,            "define storage uri", NULL);
+
+    // -addreq
+    psMetadata *addreqArgs = psMetadataAlloc();
+    psMetadataAddStr(addreqArgs, PS_LIST_TAIL, "-uri", 0,            "define request file uri", NULL); 
+    psMetadataAddStr(addreqArgs, PS_LIST_TAIL, "-ds_id", 0,            "define request ds_id", NULL); 
+    psMetadataAddStr(addreqArgs, PS_LIST_TAIL, "-out_fileset", 0,            "define request output_fileset", NULL); 
+
+    // -pendingreq
+    psMetadata *pendingreqArgs = psMetadataAlloc();
+    psMetadataAddStr(pendingreqArgs, PS_LIST_TAIL, "-req_id", 0,            "define req_id", NULL); 
+    psMetadataAddU64(pendingreqArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(pendingreqArgs, PS_LIST_TAIL, "-simple", 0,            "use the simple output format", false);
+
+    // -listreq
+    psMetadata *listreqArgs = psMetadataAlloc();
+    psMetadataAddStr(listreqArgs, PS_LIST_TAIL, "-req_id", 0,            "define req_id", NULL); 
+    psMetadataAddU64(listreqArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(listreqArgs, PS_LIST_TAIL, "-simple", 0,            "use the simple output format", false);
+
+    // -completedreq
+    psMetadata *completedreqArgs = psMetadataAlloc();
+    psMetadataAddU64(completedreqArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(completedreqArgs, PS_LIST_TAIL, "-simple", 0,            "use the simple output format", false);
+
+    // -processedreq
+    psMetadata *processedreqArgs = psMetadataAlloc();
+    psMetadataAddStr(processedreqArgs, PS_LIST_TAIL, "-req_id", 0,            "req_id for which to change state", NULL); 
+    psMetadataAddStr(processedreqArgs, PS_LIST_TAIL, "-state", 0,            "new state", NULL); 
+
+    // -addjob
+    psMetadata *addjobArgs = psMetadataAlloc();
+    psMetadataAddStr(addjobArgs, PS_LIST_TAIL, "-uri", 0,            "define job file uri", NULL); 
+    psMetadataAddStr(addjobArgs, PS_LIST_TAIL, "-req_id", 0,            "define job req_id", NULL); 
+    psMetadataAddStr(addjobArgs, PS_LIST_TAIL, "-job_type", 0,            "define job job_type", "stamp"); 
+    psMetadataAddStr(addjobArgs, PS_LIST_TAIL, "-outputBase", 0,            "define job outputBase", NULL); 
+    psMetadataAddStr(addjobArgs, PS_LIST_TAIL, "-args", 0,            "define job args", NULL); 
+    psMetadataAddStr(addjobArgs, PS_LIST_TAIL, "-state", 0,            "new state", "run"); 
+    psMetadataAddStr(addjobArgs, PS_LIST_TAIL, "-result", 0,            "new result", "0"); 
+
+    // -listjob
+    psMetadata *listjobArgs = psMetadataAlloc();
+    psMetadataAddStr(listjobArgs, PS_LIST_TAIL, "-req_id", 0,            "define request", NULL); 
+    psMetadataAddStr(listjobArgs, PS_LIST_TAIL, "-job_id", 0,            "define job", NULL); 
+    psMetadataAddU64(listjobArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(listjobArgs, PS_LIST_TAIL, "-simple", 0,            "use the simple output format", false);
+
+    // -pendingjob
+    psMetadata *pendingjobArgs = psMetadataAlloc();
+    psMetadataAddStr(pendingjobArgs, PS_LIST_TAIL, "-job_id", 0,            "define job", NULL); 
+    psMetadataAddU64(pendingjobArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(pendingjobArgs, PS_LIST_TAIL, "-simple", 0,            "use the simple output format", false);
+
+    // -processedjob
+    psMetadata *processedjobArgs = psMetadataAlloc();
+    psMetadataAddStr(processedjobArgs, PS_LIST_TAIL, "-job_id", 0,            "req_id for which to change state", NULL); 
+    psMetadataAddStr(processedjobArgs, PS_LIST_TAIL, "-state", 0,            "new state", NULL); 
+    psMetadataAddStr(processedjobArgs, PS_LIST_TAIL, "-result", 0,            "new result", NULL); 
+
+    psMetadata *argSets = psMetadataAlloc();
+    psMetadata *modes = psMetadataAlloc();
+
+    PXOPT_ADD_MODE("-addreq",          "", PSTAMPTOOL_MODE_ADDREQ,       addreqArgs);
+    PXOPT_ADD_MODE("-pendingreq",      "", PSTAMPTOOL_MODE_PENDINGREQ,   pendingreqArgs);
+    PXOPT_ADD_MODE("-processedreq",    "", PSTAMPTOOL_MODE_PROCESSEDREQ, processedreqArgs);
+    PXOPT_ADD_MODE("-listreq",         "", PSTAMPTOOL_MODE_LISTREQ,      listreqArgs);
+    PXOPT_ADD_MODE("-completedreq",    "", PSTAMPTOOL_MODE_COMPLETEDREQ, completedreqArgs);
+
+    PXOPT_ADD_MODE("-addjob",          "", PSTAMPTOOL_MODE_ADDJOB,       addjobArgs);
+    PXOPT_ADD_MODE("-listjob",         "", PSTAMPTOOL_MODE_LISTJOB,      listjobArgs);
+    PXOPT_ADD_MODE("-pendingjob",      "", PSTAMPTOOL_MODE_PENDINGJOB,   pendingjobArgs);
+    PXOPT_ADD_MODE("-processedjob",    "", PSTAMPTOOL_MODE_PROCESSEDJOB, processedjobArgs);
+
+    PXOPT_ADD_MODE("-adddatastore",    "", PSTAMPTOOL_MODE_ADDDATASTORE, adddatastoreArgs);
+    PXOPT_ADD_MODE("-datastore",       "", PSTAMPTOOL_MODE_DATASTORE,    datastoreArgs);
+    PXOPT_ADD_MODE("-moddatastore",    "", PSTAMPTOOL_MODE_MODDATASTORE, moddatastoreArgs);
+
+    if (!pxGetOptions(stderr, argc, argv, config, modes, argSets)) {
+        psError(PS_ERR_UNKNOWN, true, "option parsing failed");
+        psFree(argSets);
+        psFree(modes);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+    psFree(modes);
+
+    // define Database handle, if used
+    // do this last so we don't setup a connection before CLI options are
+    // validated
+    config->dbh = psMemIncrRefCounter(pmConfigDB(config->modules));
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    return config;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxadmin.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxadmin.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxadmin.c	(revision 22157)
@@ -0,0 +1,207 @@
+/*
+ * pxadmin.c
+ *
+ * Copyright (C) 2006-2008  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "pxtools.h"
+#include "pxadmin.h"
+
+bool createMode(pxConfig *config);
+bool deleteMode(pxConfig *config);
+static bool runMultipleStatments(pxConfig *config, const char *query);
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = pxAdminConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        case PXADMIN_MODE_RECREATE:
+            if (!deleteMode(config)) {
+                goto FAIL;
+            }
+            // fall through
+        case PXADMIN_MODE_CREATE:
+            if (!createMode(config)) {
+                goto FAIL;
+            }
+            break;
+        case PXADMIN_MODE_DELETE:
+            if (!deleteMode(config)) {
+                goto FAIL;
+            }
+            break;
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+
+bool createMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psString query = pxDataGet("pxadmin_create_tables.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // BEGIN
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!runMultipleStatments(config, query)) {
+        if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    // COMMIT
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+bool deleteMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    {
+        bool status;
+        char line[128], answer[128];
+
+        // XXX use the values defined in config->dbh?
+        psString dbName = psMetadataLookupStr(&status, config->modules->complete, "DBNAME");
+
+        fprintf (stdout, "*** delete the chip tables from database %s? ***\n", dbName);
+        fprintf (stdout, "*** to delete the tables, answer YES, and give password ***\n");
+        fprintf (stdout, "*** WARNING: this action is permanent ***\n\n");
+
+        fprintf (stdout, "delete the chip tables (YES/[n]): ");
+        fgets (line, 128, stdin);
+        sscanf (line, "%s", answer);
+        if (strcmp (answer, "YES"))  {
+            psError(PS_ERR_UNKNOWN, true, "tables NOT deleleted");
+            return false;
+        }
+
+        fprintf (stdout, "enter dbh connection password: ");
+        fgets (line, 128, stdin);
+        sscanf (line, "%s", answer);
+
+        psString dbPassword = psMetadataLookupStr(&status, config->modules->complete, "DBPASSWORD");
+        if (strcmp (answer, dbPassword)) {
+            psError(PS_ERR_UNKNOWN, true, "invalid passwd - tables NOT deleleted");
+            return false;
+        }
+    }
+
+    psString query = pxDataGet("pxadmin_drop_tables.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // BEGIN
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!runMultipleStatments(config, query)) {
+        if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    // COMMIT
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool runMultipleStatments(pxConfig *config, const char *query)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(query, false);
+
+    // loop over all statements in string
+    psList *statements = psStringSplit(query, ";", false);
+
+    psString q = NULL;
+    psListIterator *iter = psListIteratorAlloc(statements, PS_LIST_HEAD, false);
+    while ((q = psListGetAndIncrement(iter))) {
+        if (!p_psDBRunQuery(config->dbh, q)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(iter);
+            psFree(statements);
+            return false;
+        }
+    }
+    psFree(iter);
+    psFree(statements);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxadmin.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxadmin.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxadmin.h	(revision 22157)
@@ -0,0 +1,34 @@
+/*
+ * pxadmin.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PXADMIN_H
+#define PXADMIN_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    PXADMIN_MODE_NONE      = 0x0,
+    PXADMIN_MODE_CREATE,
+    PXADMIN_MODE_DELETE,
+    PXADMIN_MODE_RECREATE
+} pxAdminMode;
+
+pxConfig *pxAdminConfig (pxConfig *config, int argc, char **argv);
+
+#endif // PXADMIN_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxadminConfig.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxadminConfig.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxadminConfig.c	(revision 22157)
@@ -0,0 +1,140 @@
+/*
+ * pxadminConfig.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "pxadmin.h"
+
+static void pxAdminShowDB (pxConfig *config, const char *program) {
+
+    bool status;
+
+    fprintf (stderr, "\nPan-STARRS DataBase Admin Tool\n\n");
+    fprintf (stderr, "Usage: %s [mode]\n", program);
+    fprintf (stderr, " [mode] : -create | -delete\n\n");
+
+    // XXX EAM : this should be done with psDB function calls
+    psString dbServer = psMetadataLookupStr(&status, config->modules->complete, "DBSERVER");
+    psString dbUsername = psMetadataLookupStr(&status, config->modules->complete, "DBUSER");
+    psString dbName = psMetadataLookupStr(&status, config->modules->complete, "DBNAME");
+    psS32 dbPort = psMetadataLookupS32(&status, config->modules->complete, "DBPORT");
+
+    fprintf (stderr, "connecting to %s as %s (port %d)\n", dbServer, dbUsername, dbPort);
+    fprintf (stderr, "using database %s\n\n", dbName);
+
+    return;
+}
+
+pxConfig *pxAdminConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (!config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    // Parse other command-line arguments
+    psMetadata *arguments = psMetadataAlloc(); // The arguments, with default values
+
+    int N;
+    config->mode = PXADMIN_MODE_NONE;
+    if ((N = psArgumentGet(argc, argv, "-create"))) {
+        psArgumentRemove(N, &argc, argv);
+        if (config->mode) {
+            psAbort("only one mode selection is allowed");
+        }
+        config->mode = PXADMIN_MODE_CREATE;
+    }
+    if ((N = psArgumentGet(argc, argv, "-delete"))) {
+        psArgumentRemove(N, &argc, argv);
+        if (config->mode) {
+            psAbort("only one mode selection is allowed");
+        }
+        config->mode = PXADMIN_MODE_DELETE;
+    }
+    if ((N = psArgumentGet(argc, argv, "-recreate"))) {
+        psArgumentRemove(N, &argc, argv);
+        if (config->mode) {
+            psAbort("only one mode selection is allowed");
+        }
+        config->mode = PXADMIN_MODE_RECREATE;
+    }
+
+    // paul's argument parsing convention requires: -key value
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-create", 0,
+            "create all IPP tables", "");
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-delete", 0,
+            "delete all IPP tables", "");
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-recreate", 0,
+            "delete and recreate all tables", "");
+
+    if (config->mode == PXADMIN_MODE_NONE) {
+    pxAdminShowDB (config, argv[0]);
+        fprintf (stderr, "admin mode not specified\n");
+
+        psArgumentHelp(arguments);
+        psFree(arguments);
+        psFree(config);
+        return NULL;
+    }
+
+    if ((N = psArgumentGet (argc, argv, "-help"))) {
+    pxAdminShowDB (config, argv[0]);
+        psArgumentHelp(arguments);
+        psFree(arguments);
+        psFree(config);
+        return NULL;
+    }
+
+    if (! psArgumentParse(arguments, &argc, argv) || argc != 1) {
+    pxAdminShowDB (config, argv[0]);
+        psArgumentHelp(arguments);
+        psFree(arguments);
+        psFree(config);
+        return NULL;
+    }
+
+   psFree(arguments);
+
+    // define Database handle, if used
+    config->dbh = psMemIncrRefCounter(pmConfigDB(config->modules));
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    // save argv/argc
+    config->argv = argv;
+    config->argc = argc;
+
+    return config;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxcam.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxcam.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxcam.c	(revision 22157)
@@ -0,0 +1,187 @@
+/*
+ * pxcam.c
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <ippdb.h>
+#include <string.h>
+
+#include "pxtools.h"
+#include "pxcam.h"
+
+bool pxcamRunSetState(pxConfig *config, psS64 cam_id, const char *state)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(state, false);
+
+    // check that state is a valid string value
+    if (!(
+            (strncmp(state, "run", 4) == 0)
+            || (strncmp(state, "stop", 5) == 0)
+            || (strncmp(state, "reg", 4) == 0)
+        )
+    ) {
+        psError(PS_ERR_UNKNOWN, false,
+                "invalid camRun state: %s", state);
+        return false;
+    }
+
+    char *query = "UPDATE camRun SET state = '%s' WHERE cam_id = %" PRId64;
+    if (!p_psDBRunQuery(config->dbh, query, state, cam_id)) {
+        psError(PS_ERR_UNKNOWN, false,
+                "failed to change state for cam_id %" PRId64, cam_id);
+        return false;
+    }
+
+    return true;
+}
+
+
+bool pxcamRunSetStateByQuery(pxConfig *config, psMetadata *where, const char *state)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(state, false);
+
+    // check that state is a valid string value
+    if (!(
+            (strncmp(state, "run", 4) == 0)
+            || (strncmp(state, "stop", 5) == 0)
+            || (strncmp(state, "reg", 4) == 0)
+        )
+    ) {
+        psError(PS_ERR_UNKNOWN, false,
+                "invalid chipRun state: %s", state);
+        return false;
+    }
+
+    psString query = psStringCopy("UPDATE camRun JOIN chipRun USING(chip_id) JOIN rawExp USING(exp_id) SET state = '%s'");
+
+    if (where) {
+        psString whereClause = psDBGenerateWhereSQL(where, NULL);
+        psStringAppend(&query, " %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query, state)) {
+        psFree(query);
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    psFree(query);
+
+    return true;
+}
+
+
+bool pxcamRunSetLabel(pxConfig *config, psS64 cam_id, const char *label)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    // note label == NULL should be explicitly allowed
+
+    char *query = "UPDATE camRun SET camRun.label = '%s' WHERE cam_id = %" PRId64;
+    if (!p_psDBRunQuery(config->dbh, query, label, cam_id)) {
+        psError(PS_ERR_UNKNOWN, false,
+                "failed to change state for cam_id %" PRId64, cam_id);
+        return false;
+    }
+
+    return true;
+}
+
+
+bool pxcamRunSetLabelByQuery(pxConfig *config, psMetadata *where, const char *label)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    // note label == NULL should be explicitly allowed
+
+    psString query = psStringCopy("UPDATE camRun JOIN chipRun USING(chip_id) JOIN rawExp USING(exp_id) SET camRun.label = '%s'");
+
+    if (where) {
+        psString whereClause = psDBGenerateWhereSQL(where, NULL);
+        psStringAppend(&query, " %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query, label)) {
+        psFree(query);
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    psFree(query);
+
+    return true;
+}
+
+
+bool pxcamQueueByChipID(pxConfig *config,
+                    psS64 chip_id,
+                    char *workdir,
+                    char *label,
+                    char *recipe,
+                    char *expgroup,
+                    char *dvodb,
+                    char *tess_id,
+                    char *end_stage)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // load the SQL to enqueue our exp_ids from disk once
+    static psString query = NULL;
+    if (!query) {
+        query = pxDataGet("camtool_queue_chip_id.sql");
+        psMemSetPersistent(query, true);
+        if (!query) {
+            psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+            return false;
+        }
+    }
+
+    // queue the exp
+    // XXX chip_id is being cast here work around psS64 have a different type
+    // different on 32/64
+    if (!p_psDBRunQuery(config->dbh, query,
+                "run", // state
+                workdir  ? workdir  : "NULL",
+                "dirty", //workdir_state
+                label    ? label    : "NULL",
+                recipe   ? recipe   : "NULL",
+                expgroup ? expgroup : "NULL",
+                dvodb    ? dvodb    : "NULL",
+                tess_id  ? tess_id  : "NULL",
+                end_stage ? end_stage : "NULL",
+                (long long)chip_id
+    )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // just to be safe, we should have changed at least one row
+    if (psDBAffectedRows(config->dbh) < 1) {
+        psError(PS_ERR_UNKNOWN, false,
+                "no rows affected - should have changed at least one row");
+        return false;
+    }
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxcam.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxcam.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxcam.h	(revision 22157)
@@ -0,0 +1,42 @@
+/*
+ * pxcam.h
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PXCAM_H
+#define PXCAM_H 1
+
+#include <pslib.h>
+
+#include "pxtools.h"
+
+bool pxcamRunSetState(pxConfig *config, psS64 cam_id, const char *state);
+bool pxcamRunSetStateByQuery(pxConfig *config, psMetadata *where, const char *state);
+bool pxcamRunSetLabel(pxConfig *config, psS64 cam_id, const char *label);
+bool pxcamRunSetLabelByQuery(pxConfig *config, psMetadata *where, const char *label);
+
+bool pxcamQueueByChipID(pxConfig *config,
+                        psS64 chip_id,
+                        char *workdir,
+                        char *label,
+                        char *recipe,
+                        char *expgroup,
+                        char *dvodb,
+                        char *tess_id,
+                        char *end_stage);
+
+#endif // PXCAM_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxchip.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxchip.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxchip.c	(revision 22157)
@@ -0,0 +1,158 @@
+/*
+ * pxchip.c
+ *
+ * Copyright (C) 2007-2008  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <ippdb.h>
+#include <string.h>
+
+#include "pxtools.h"
+#include "pxchip.h"
+
+bool pxchipRunSetState(pxConfig *config, psS64 chip_id, const char *state)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(state, false);
+
+    // check that state is a valid string value
+    if (!pxIsValidState(state)) {
+        psError(PS_ERR_UNKNOWN, false, "invalid chipRun state: %s", state);
+        return false;
+    }
+
+    char *query = "UPDATE chipRun SET state = '%s' WHERE chip_id = %" PRId64;
+    if (!p_psDBRunQuery(config->dbh, query, state, chip_id)) {
+        psError(PS_ERR_UNKNOWN, false,
+                "failed to change state for chip_id %" PRId64, chip_id);
+        return false;
+    }
+
+    return true;
+}
+
+
+bool pxchipRunSetStateByQuery(pxConfig *config, psMetadata *where, const char *state)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(state, false);
+
+    // check that state is a valid string value
+    if (!pxIsValidState(state)) {
+        psError(PS_ERR_UNKNOWN, false, "invalid chipRun state: %s", state);
+        return false;
+    }
+
+
+    psString query = psStringCopy("UPDATE chipRun JOIN rawExp USING(exp_id) SET state = '%s'");
+
+    if (where) {
+        psString whereClause = psDBGenerateWhereSQL(where, NULL);
+        psStringAppend(&query, " %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query, state)) {
+        psFree(query);
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    psFree(query);
+
+    return true;
+}
+
+
+bool pxchipRunSetLabel(pxConfig *config, psS64 chip_id, const char *label)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    // note label == NULL should be explicitly allowed
+ 
+    char *query = "UPDATE chipRun SET label = '%s' WHERE chip_id = %" PRId64;
+    if (!p_psDBRunQuery(config->dbh, query, label, chip_id)) {
+        psError(PS_ERR_UNKNOWN, false,
+                "failed to change state for chip_id %" PRId64, chip_id);
+        return false;
+    }
+
+    return true;
+}
+
+
+bool pxchipRunSetLabelByQuery(pxConfig *config, psMetadata *where, const char *label)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    // note label == NULL should be explicitly allowed
+ 
+    psString query = psStringCopy("UPDATE chipRun JOIN rawExp USING(exp_id) SET label = '%s'");
+
+    if (where) {
+        psString whereClause = psDBGenerateWhereSQL(where, NULL);
+        psStringAppend(&query, " %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query, label)) {
+        psFree(query);
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    psFree(query);
+
+    return true;
+}
+
+
+psS64 pxchipQueueByExpTag(pxConfig *config,
+                         psS64 exp_id,
+                         const char *workdir,
+                         const char *label,
+                         const char *reduction,
+                         const char *expgroup,
+                         const char *dvodb,
+                         const char *tess_id,
+                         const char *end_stage)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // create a chipRun
+    if (!chipRunInsert(config->dbh,
+            0x0, // chip_id
+            exp_id,
+            "new",      // state                
+            workdir,
+            "dirty",    // workdir_state
+            label,
+            reduction,
+            expgroup,
+            dvodb,
+            tess_id,
+            end_stage)
+    ) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return 0;
+    }
+
+    return psDBLastInsertID(config->dbh);
+}
+
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxchip.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxchip.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxchip.h	(revision 22157)
@@ -0,0 +1,43 @@
+/*
+ * pxchip.h
+ *
+ * Copyright (C) 2007-2008  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PXCHIP_H
+#define PXCHIP_H 1
+
+#include <pslib.h>
+
+#include "pxtools.h"
+
+bool pxchipRunSetState(pxConfig *config, psS64 chip_id, const char *state);
+bool pxchipRunSetStateByQuery(pxConfig *config, psMetadata *where, const char *state);
+bool pxchipRunSetLabel(pxConfig *config, psS64 chip_id, const char *label);
+bool pxchipRunSetLabelByQuery(pxConfig *config, psMetadata *where, const char *label);
+
+psS64 pxchipQueueByExpTag(pxConfig *config,
+                         psS64 exp_id,
+                         const char *workdir,
+                         const char *label,
+                         const char *reduction,
+                         const char *expgroup,
+                         const char *dvodb,
+                         const char *tess_id,
+                         const char *end_stage);
+
+
+#endif // PXCHIP_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxconfig.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxconfig.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxconfig.c	(revision 22157)
@@ -0,0 +1,227 @@
+/*
+ * pxconfig.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdio.h>
+
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "pxtools.h"
+
+static void pxConfigFree(pxConfig *ptr);
+
+pxConfig *pxConfigAlloc(void)
+{
+    pxConfig *config;
+
+    config = psAlloc(sizeof(pxConfig));
+    psMemSetDeallocator(config, (psFreeFunc)pxConfigFree);
+
+    config->modeName        = NULL;
+    config->mode            = 0;
+    config->dbh             = NULL;
+    config->modules         = NULL;
+    // XXX config->where           = NULL;
+    config->args            = NULL;
+
+    return config;
+}
+
+static void pxConfigFree(pxConfig *config)
+{
+    psFree(config->modeName);
+    psFree(config->dbh);
+    psFree(config->modules);
+    // XXX psFree(config->where);
+    psFree(config->args);
+}
+
+void pxUsage(FILE *stream, int argc, char **argv, const char *modeName, psMetadata *argSet) 
+{
+    fprintf(stream, "Usage: %s %s [<options>]\n\n", argv[0], modeName);
+    fprintf(stream, "%s:\n", modeName);
+
+    psArgumentHelpSimple(stream, argSet);
+}
+
+bool pxGetOptions(FILE *stream, int argc, char **argv, pxConfig *config, psMetadata *modes, psMetadata *argSets)
+{
+    // figure out what mode we're running in
+    psMetadataIterator *iter = psMetadataIteratorAlloc(modes, PS_LIST_HEAD, NULL);
+    psMetadataItem *item = NULL;
+    while ((item = psMetadataGetAndIncrement(iter))) {
+        int N = 0;
+        if ((N = psArgumentGet(argc, argv, item->name))) {
+            psArgumentRemove(N, &argc, argv);
+            // check for duplicate mode specification
+            if (config->mode) {
+                psError(PS_ERR_UNKNOWN, true, "only one mode selection is allowed");
+                fprintf(stream, "only one mode selection is allowed\n");
+                pxUsage(stream, argc, argv, "<mode>", modes);
+                psFree(iter);
+                return NULL;
+            }
+
+            config->mode = item->data.U32;
+            config->modeName = psStringCopy(item->name);
+
+            bool status = false;
+            psMetadata *argset = psMetadataLookupMetadata(&status, argSets, item->name);
+            // make sure we can find the argSet for the sepcified mode
+            if (!status) {
+                psError(PS_ERR_UNKNOWN, true, "can not find arguments for mode");
+                fprintf(stderr, "can not find arugments for mode");
+                psFree(iter);
+                return NULL;
+            }
+
+            config->args = psMemIncrRefCounter(argset);
+        }
+
+    }
+
+    psFree(iter);
+
+    // make sure we found a mode
+    if (config->mode == PXTOOL_MODE_NONE) {
+        psError(PS_ERR_UNKNOWN, true, "mode argument is required");
+        fprintf(stderr, "mode argument is required\n");
+        pxUsage(stream, argc, argv, "<mode>", modes);
+        return NULL;
+    }
+
+    // actually parse the command line
+    if (!psArgumentParse(config->args, &argc, argv)) {
+        psError(PS_ERR_UNKNOWN, false, "error parsing arguments");
+        fprintf(stderr, "error parsing arguments\n");
+        pxUsage(stream, argc, argv, config->modeName, config->args);
+        return NULL;
+    }
+
+    // look for left overs on the command line
+    if (argc != 1) {
+        psError(PS_ERR_UNKNOWN, false, "extra arguments: ");
+        fprintf(stderr, "extra arguments: ");
+        for (int i = 1; i < argc; i++) {
+            fprintf (stderr, "%s ", argv[i]);
+        }
+        fprintf(stderr, "\n");
+        pxUsage(stream, argc, argv, config->modeName, config->args);
+        return NULL;
+    }
+
+    // make sure that all required parameters have been specified
+    iter = psMetadataIteratorAlloc(config->args, PS_LIST_HEAD, NULL);
+    item = NULL; // Item from iterator
+    while ((item = psMetadataGetAndIncrement(iter))) {
+        if (strstr(item->comment, "require") == NULL) {
+            continue;
+        }
+        if (strstr(item->comment, "(found)") != NULL) {
+            continue;
+        }
+
+        switch (item->type) {
+            case PS_DATA_BOOL:
+                psError(PS_ERR_UNKNOWN, false, "boolean type can not be required");
+                fprintf(stderr, "boolean type can not be required\n");
+                psFree(iter);
+                return NULL;
+                break;
+            case PS_DATA_S8:
+                if (item->data.S8 != INT8_MAX) {
+                    break;
+                }
+                goto ARG_REQUIRED;
+            case PS_DATA_S16:
+                if (item->data.S16 != INT16_MAX) {
+                    break;
+                }
+                goto ARG_REQUIRED;
+            case PS_DATA_S32:
+                if (item->data.S32 != INT32_MAX) {
+                    break;
+                }
+                goto ARG_REQUIRED;
+            case PS_DATA_S64:
+                if (item->data.S64 != INT64_MAX) {
+                    break;
+                }
+                goto ARG_REQUIRED;
+            case PS_DATA_U8:
+                if (item->data.U8 != UINT8_MAX) {
+                    break;
+                }
+                goto ARG_REQUIRED;
+            case PS_DATA_U16:
+                if (item->data.U16 != UINT16_MAX) {
+                    break;
+                }
+                goto ARG_REQUIRED;
+            case PS_DATA_U32:
+                if (item->data.U32 != UINT32_MAX) {
+                    break;
+                }
+                goto ARG_REQUIRED;
+            case PS_DATA_U64:
+                if (item->data.U64 != UINT64_MAX) {
+                    break;
+                }
+                goto ARG_REQUIRED;
+            case PS_DATA_F32:
+            case PS_DATA_F64:
+                if (!isnan(item->data.F64)) {
+                    break;
+                }
+                goto ARG_REQUIRED;
+            case PS_DATA_STRING:
+            case PS_DATA_TIME:
+            case PS_DATA_METADATA:
+                if (item->data.V) {
+                    break;
+                }
+                goto ARG_REQUIRED;
+            default:
+                psError(PS_ERR_UNKNOWN, false, "unknown argument type");
+                fprintf(stream, "unknown argument type\n");
+                psFree(iter);
+                return NULL;
+            ARG_REQUIRED:                
+                psError(PS_ERR_UNKNOWN, false, "argument %s is required", item->name);
+                fprintf(stream, "argument %s is required\n", item->name);
+                pxUsage(stream, argc, argv, config->modeName, config->args);
+                psFree(iter);
+                return NULL;
+        }
+
+    }
+
+    psFree(iter);
+
+    // save argv/argc 
+    config->argv = argv;
+    config->argc = argc;
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxconfig.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxconfig.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxconfig.h	(revision 22157)
@@ -0,0 +1,39 @@
+/*
+ * pxconfig.h
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PXCONFIG_H
+#define PXCONFIG_H 1
+
+#include "pslib.h"
+#include "psmodules.h"
+
+typedef struct {
+    char *modeName;
+    int mode;
+    pmConfig *modules;
+    psDB *dbh;
+    psMetadata *args;
+    // XXX deprecate psMetadata *where;
+    int argc;
+    char **argv;
+} pxConfig;
+
+pxConfig *pxConfigAlloc(void);
+
+#endif // PXCONFIG_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxdata.c.template
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxdata.c.template	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxdata.c.template	(revision 22157)
@@ -0,0 +1,145 @@
+/*
+ * pxdata.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVB_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h> // getenv
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <pslib.h>
+
+#include "pxtools.h"
+#include "pxdata.h"
+
+static psHash *cachedData = NULL;
+
+static int test_f(const char *path);
+
+psString pxDataGet(const char *filename)
+{
+    PS_ASSERT_PTR_NON_NULL(filename, NULL);
+
+    {
+        psString filepath = NULL;
+
+        // look to see if PXDATA is set
+        char *PXDATA = getenv("PXDATA");
+        if (PXDATA) {
+            // if it is, then prepend filename to PXDATA
+            psStringAppend(&filepath, "%s/%s", PXDATA, filename);
+        }
+
+        // see if we have a valid filename
+        if (test_f(filepath)) {
+            // if we do, slurp the file
+            psString data = psSlurpFilename(filepath);
+            if (!data) {
+                psError(PS_ERR_IO, false, "failed to slurp %s", filepath);
+                psFree(filepath);
+                return NULL;
+            }
+            psTrace("pxdata", PS_LOG_INFO, "slupred %s", filepath);
+            psFree(filepath);
+
+            return data;
+        }
+
+        psFree(filepath);
+    }
+
+    {
+        psString filepath = NULL;
+
+        // look under our share path
+        psStringAppend(&filepath, "%s/%s", "@PKGDATADIR@", filename);
+
+        // see if we have a valid filename
+        if (test_f(filepath)) {
+            // if we do, slurp the file
+            psString data = psSlurpFilename(filepath);
+            if (!data) {
+                psError(PS_ERR_IO, false, "failed to slurp %s", filepath);
+                psFree(filepath);
+                return NULL;
+            }
+            psTrace("pxdata", PS_LOG_INFO, "slupred %s", filepath);
+            psFree(filepath);
+
+            return data;
+        }
+
+        psFree(filepath);
+    }
+
+    // couldn't find a matching filename
+    psError(PS_ERR_IO, true, "can't find a file in search path(s) to match : %s", filename);
+
+    return NULL;
+}
+
+static int test_f(const char *path)
+{
+        // copied from coreutils 6.4 test.c licenced under GPL v2 
+        struct stat stat_buf;
+        return (stat (path, &stat_buf) == 0
+                   && S_ISREG (stat_buf.st_mode));
+}
+
+psString pxDataGetCached(const char *filename)
+{
+    bool persistence = p_psMemAllocatePersistent(true);
+
+    // make sure the cache is initalized
+    if (!cachedData) {
+        cachedData = psHashAlloc(10);
+    }
+
+    // look to see if we've cached this file
+    {
+        psString data = psHashLookup(cachedData, filename);
+        if (data) {
+            p_psMemAllocatePersistent(persistence);
+            return psStringCopy(data);
+        } 
+    }
+
+    // we didn't have a cached copy so we need to try to read the file
+    psString data = pxDataGet(filename);
+    if (!data) {
+        p_psMemAllocatePersistent(persistence);
+        psError(PS_ERR_IO, false, "pxDataGet(%s) failed", filename);
+        return NULL;
+    }
+
+    // store a copy of the file
+    if (!psHashAdd(cachedData, filename, data)) {
+        psFree(data);
+        p_psMemAllocatePersistent(persistence);
+        psError(PS_ERR_UNKNOWN, false, "psHashAdd() failed");
+        return NULL;
+    }
+
+    p_psMemAllocatePersistent(persistence);
+
+    return psStringCopy(data);
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxdata.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxdata.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxdata.h	(revision 22157)
@@ -0,0 +1,28 @@
+/*
+ * pxdata.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PXDATA_H
+#define PXDATA_H 1
+
+#include <pslib.h>
+
+psString pxDataGet(const char *filename);
+psString pxDataGetCached(const char *filename);
+
+#endif // PXDATA_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxerrors.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxerrors.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxerrors.c	(revision 22157)
@@ -0,0 +1,42 @@
+/*
+ * pxerrors.c
+ *
+ * Copyright (C) 2006  Eugene Magnier
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "pxtools.h"
+
+psExit pxerrorGetExitStatus () {
+
+    psErrorCode err = psErrorCodeLast ();
+    switch (err) {
+      case PXTOOLS_ERR_SYS:
+    return PS_EXIT_SYS_ERROR;
+      case PXTOOLS_ERR_CONFIG:
+    return PS_EXIT_CONFIG_ERROR;
+      case PXTOOLS_ERR_PROG:
+    return PS_EXIT_PROG_ERROR;
+      case PXTOOLS_ERR_DATA:
+    return PS_EXIT_DATA_ERROR;
+      default:
+    return PS_EXIT_UNKNOWN_ERROR;
+    }    
+    return PS_EXIT_UNKNOWN_ERROR;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxfake.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxfake.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxfake.c	(revision 22157)
@@ -0,0 +1,188 @@
+/*
+ * pxfake.c
+ *
+ * Copyright (C) 2007-2008  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <ippdb.h>
+#include <string.h>
+
+#include "pxtools.h"
+#include "pxcam.h"
+
+bool pxfakeRunSetState(pxConfig *config, psS64 fake_id, const char *state)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(state, false);
+
+    // check that state is a valid string value
+    if (!(
+            (strncmp(state, "run", 4) == 0)
+            || (strncmp(state, "stop", 5) == 0)
+            || (strncmp(state, "reg", 4) == 0)
+        )
+    ) {
+        psError(PS_ERR_UNKNOWN, false,
+                "invalid fakeRun state: %s", state);
+        return false;
+    }
+
+    char *query = "UPDATE fakeRun SET state = '%s' WHERE fake_id = %" PRId64;
+    if (!p_psDBRunQuery(config->dbh, query, state, fake_id)) {
+        psError(PS_ERR_UNKNOWN, false,
+                "failed to change state for fake_id %" PRId64, fake_id);
+        return false;
+    }
+
+    return true;
+}
+
+
+bool pxfakeRunSetStateByQuery(pxConfig *config, psMetadata *where, const char *state)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(state, false);
+
+    // check that state is a valid string value
+    if (!(
+            (strncmp(state, "run", 4) == 0)
+            || (strncmp(state, "stop", 5) == 0)
+            || (strncmp(state, "reg", 4) == 0)
+        )
+    ) {
+        psError(PS_ERR_UNKNOWN, false,
+                "invalid fakeRun state: %s", state);
+        return false;
+    }
+
+    psString query = psStringCopy("UPDATE fakeRun JOIN camRun USING(cam_id) JOIN chipRun USING(chip_id) JOIN rawExp USING(exp_id) SET state = '%s'");
+
+    if (where) {
+        psString whereClause = psDBGenerateWhereSQL(where, NULL);
+        psStringAppend(&query, " %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query, state)) {
+        psFree(query);
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    psFree(query);
+
+    return true;
+}
+
+
+bool pxfakeRunSetLabel(pxConfig *config, psS64 fake_id, const char *label)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    // note label == NULL should be explicitly allowed
+
+    char *query = "UPDATE fakeRun SET fakeRun.label = '%s' WHERE fake_id = %" PRId64;
+    if (!p_psDBRunQuery(config->dbh, query, label, fake_id)) {
+        psError(PS_ERR_UNKNOWN, false,
+                "failed to change state for fake_id %" PRId64, fake_id);
+        return false;
+    }
+
+    return true;
+}
+
+
+bool pxfakeRunSetLabelByQuery(pxConfig *config, psMetadata *where, const char *label)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    // note label == NULL should be explicitly allowed
+
+    psString query = psStringCopy("UPDATE fakeRun JOIN camRun USING(cam_id) JOIN chipRun USING(chip_id) JOIN rawExp USING(exp_id) SET fakeRun.label = '%s'");
+
+    if (where) {
+        psString whereClause = psDBGenerateWhereSQL(where, NULL);
+        psStringAppend(&query, " %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query, label)) {
+        psFree(query);
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    psFree(query);
+
+    return true;
+}
+
+
+psS64 pxfakeQueueByCamID(pxConfig *config,
+                    psS64 cam_id,
+                    char *workdir,
+                    char *label,
+                    char *reduction,
+                    char *expgroup,
+                    char *dvodb,
+                    char *tess_id,
+                    char *end_stage)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psTrace("pxtool", PS_LOG_INFO, "attemping to queue cam_id: %"PRId64, cam_id);
+
+    // load the SQL to enqueue our exp_ids from disk once
+    static psString query = NULL;
+    if (!query) {
+        query = pxDataGet("faketool_queue_cam_id.sql");
+        psMemSetPersistent(query, true);
+        if (!query) {
+            psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+            return false;
+        }
+    }
+
+    // queue the exp
+    // XXX chip_id is being cast here work around psS64 have a different type
+    // different on 32/64
+    if (!p_psDBRunQuery(config->dbh, query,
+                "run", // state
+                workdir  ? workdir  : "NULL",
+                label    ? label    : "NULL",
+                reduction? reduction: "NULL",
+                expgroup ? expgroup : "NULL",
+                dvodb    ? dvodb    : "NULL",
+                tess_id  ? tess_id  : "NULL",
+                end_stage ? end_stage : "NULL",
+                (long long)cam_id
+    )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // just to be safe, we should have changed at least one row
+    if (psDBAffectedRows(config->dbh) != 1) {
+        psError(PS_ERR_UNKNOWN, false,
+                "should have changed just one row");
+        return false;
+    }
+
+    return psDBLastInsertID(config->dbh);
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxfake.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxfake.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxfake.h	(revision 22157)
@@ -0,0 +1,44 @@
+/*
+ * pxfake.h
+ *
+ * Copyright (C) 2007-2008  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PXFAKE_H
+#define PXFAKE_H 1
+
+#include <pslib.h>
+
+#include "pxtools.h"
+
+bool pxfakeRunSetState(pxConfig *config, psS64 fake_id, const char *state);
+bool pxfakeRunSetStateByQuery(pxConfig *config, psMetadata *where, const char *state);
+bool pxfakeRunSetLabel(pxConfig *config, psS64 fake_id, const char *label);
+bool pxfakeRunSetLabelByQuery(pxConfig *config, psMetadata *where, const char *label);
+
+
+psS64 pxfakeQueueByCamID(pxConfig *config,
+                    psS64 cam_id,
+                    char *workdir,
+                    char *label,
+                    char *reduction,
+                    char *expgroup,
+                    char *dvodb,
+                    char *tess_id,
+                    char *end_stage);
+
+
+#endif // PXFAKE_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxfault.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxfault.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxfault.c	(revision 22157)
@@ -0,0 +1,65 @@
+/*
+ * pxfault.c
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pslib.h>
+
+#include "pxtools.h"
+
+bool pxSetFaultCode(psDB *dbh, const char *tableName, psMetadata *where, psS16 code)
+{
+    PS_ASSERT_PTR_NON_NULL(dbh, false);
+    PS_ASSERT_PTR_NON_NULL(tableName, false);
+    PS_ASSERT_PTR_NON_NULL(where, false);
+
+#if 0
+    // map code string to numeric fault code
+    psU32 code = mapCodeStrToInt(codeStr);
+    if (code == (psU32)-1) {
+        psError(PS_ERR_UNKNOWN, false, "error resolving error code");
+        return false;
+    }
+#endif
+
+    // update the database
+    psMetadata *values = psMetadataAlloc();
+    if (!psMetadataAddS16(values, PS_LIST_HEAD, "fault", 0, NULL, code)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add metadata item fault");
+        psFree(values);
+        return false;
+    }
+
+    long rowsAffected = psDBUpdateRows(dbh, tableName, where, values); 
+    psFree(values);
+    if (rowsAffected < 0) {
+        // database error
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (rowsAffected < 1) {
+        // we didn't do anything
+        psError(PS_ERR_UNKNOWN, false, "zero rows were affected - either the search criteria didn't match any rows or the field already had the value being set.");
+        return false;
+    }
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxinject.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxinject.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxinject.c	(revision 22157)
@@ -0,0 +1,163 @@
+/*
+ * pxinject.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <ippdb.h>
+#include <string.h>
+
+#include "pxtools.h"
+#include "pxinject.h"
+
+static bool newExpMode(pxConfig *config);
+static bool newImfileMode(pxConfig *config);
+static bool updatenewExpMode(pxConfig *config);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = pxinjectConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(PXINJECT_MODE_NEWEXP, newExpMode);
+        MODECASE(PXINJECT_MODE_NEWIMFILE, newImfileMode);
+        MODECASE(PXINJECT_MODE_UPDATENEWEXP, updatenewExpMode);
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint (stderr, "failure\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+static bool newExpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_STR(tmp_exp_name, config->args, "-tmp_exp_name", true, false);
+    PXOPT_LOOKUP_STR(tmp_camera, config->args, "-tmp_inst", true, false);
+    PXOPT_LOOKUP_STR(tmp_telescope, config->args, "-tmp_telescope", true, false);
+    PXOPT_LOOKUP_STR(workdir, config->args, "-workdir", true, false);
+    PXOPT_LOOKUP_STR(reduction, config->args, "-reduction", false, false);
+    PXOPT_LOOKUP_STR(dvodb, config->args, "-dvodb", false, false);
+    PXOPT_LOOKUP_STR(tess_id, config->args, "-tess_id", false, false);
+    PXOPT_LOOKUP_STR(end_stage, config->args, "-end_stage", false, false);
+    PXOPT_LOOKUP_STR(label, config->args, "-label", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    if (!newExpInsert(config->dbh,
+                0,    // exp_id
+                tmp_exp_name,
+                tmp_camera,
+                tmp_telescope,
+                "reg",  // state
+                workdir,
+                "dirty",
+                reduction,
+                dvodb,
+                tess_id,
+                end_stage,
+                label,
+                NULL    // epoch
+            )
+        ) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    psS64 exp_id = psDBLastInsertID(config->dbh);
+
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAddS64(md, PS_LIST_TAIL, "exp_id", 0, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+    }
+
+    // negate simple so the default is true
+    if (!ippdbPrintMetadata(stdout, md, !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(md);
+        return false;
+    }
+
+    psFree(md);
+
+    return true;
+}
+
+static bool newImfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_S64(exp_id, config->args, "-exp_id", true, false);
+    PXOPT_LOOKUP_STR(tmp_class_id, config->args, "-tmp_class_id", true, false);
+    PXOPT_LOOKUP_STR(uri, config->args, "-uri", true, false);
+
+    // insert with error flag state set to 0 (no errors)
+    if (!newImfileInsert(config->dbh, exp_id, tmp_class_id, uri, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool updatenewExpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_S64(exp_id, config->args, "-exp_id", true, false);
+    PXOPT_LOOKUP_STR(state, config->args, "-state", true, false);
+
+    if (state) {
+        // set detRun.state to state
+        return pxnewExpSetState(config, exp_id, state);
+    }
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxinject.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxinject.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxinject.h	(revision 22157)
@@ -0,0 +1,34 @@
+/*
+ * pxinject.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PXINJECT_H
+#define PXINJECT_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    PXINJECT_MODE_NONE           = 0x0,
+    PXINJECT_MODE_NEWEXP,
+    PXINJECT_MODE_NEWIMFILE,
+    PXINJECT_MODE_UPDATENEWEXP
+} pxinjectMode;
+
+pxConfig *pxinjectConfig(pxConfig *config, int argc, char **argv);
+
+#endif // PXINJECT_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxinjectConfig.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxinjectConfig.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxinjectConfig.c	(revision 22157)
@@ -0,0 +1,98 @@
+/*
+ * pxinjectConfig.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "pxinject.h"
+
+pxConfig *pxinjectConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (!config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    // -newExp
+    psMetadata *newExpArgs = psMetadataAlloc();
+    psMetadataAddStr(newExpArgs, PS_LIST_TAIL, "-tmp_exp_name",  0,            "define the exp_name (required)", NULL);
+    psMetadataAddStr(newExpArgs, PS_LIST_TAIL, "-tmp_inst",  0,            "define the camera name (required)", NULL);
+    psMetadataAddStr(newExpArgs, PS_LIST_TAIL, "-tmp_telescope",  0,            "define the telescope name (required)", NULL);
+    psMetadataAddStr(newExpArgs, PS_LIST_TAIL, "-workdir",  0,            "define workdir (required)", 0);
+    psMetadataAddStr(newExpArgs, PS_LIST_TAIL, "-reduction",  0,            "define reduction class", NULL);
+    psMetadataAddStr(newExpArgs, PS_LIST_TAIL, "-dvodb",  0,            "define the dvodb for the next processing step", NULL);
+    psMetadataAddStr(newExpArgs, PS_LIST_TAIL, "-tess_id",  0,            "define the tess_id for the next processing step", NULL);
+    psMetadataAddStr(newExpArgs, PS_LIST_TAIL, "-end_stage",  0,            "define the end goal processing step", NULL);
+    psMetadataAddStr(newExpArgs, PS_LIST_TAIL, "-label",  0,            "define a label (carried to chip stage)", NULL);
+    psMetadataAddBool(newExpArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -newImfile
+    psMetadata *newImfileArgs = psMetadataAlloc();
+    psMetadataAddS64(newImfileArgs, PS_LIST_TAIL, "-exp_id",  0,            "define the exp_id (required)", 0);
+    psMetadataAddStr(newImfileArgs, PS_LIST_TAIL, "-tmp_class_id",  0,            "define the class ID (required)", NULL);
+    psMetadataAddStr(newImfileArgs, PS_LIST_TAIL, "-uri",  0,            "define the URI (required)", NULL);
+
+    // -updatenewExp
+    psMetadata *updatenewExpArgs = psMetadataAlloc();
+    psMetadataAddS64(updatenewExpArgs, PS_LIST_TAIL, "-exp_id",  0,            "define the exp_id (required)", 0);
+    psMetadataAddStr(updatenewExpArgs, PS_LIST_TAIL, "-state", 0,            "set state (required)", NULL);
+
+    psMetadata *argSets = psMetadataAlloc();
+    psMetadata *modes = psMetadataAlloc();
+
+    PXOPT_ADD_MODE("-newExp",      "", PXINJECT_MODE_NEWEXP,       newExpArgs);
+    PXOPT_ADD_MODE("-newImfile",   "", PXINJECT_MODE_NEWIMFILE,    newImfileArgs);
+    PXOPT_ADD_MODE("-updatenewExp",   "", PXINJECT_MODE_UPDATENEWEXP,    updatenewExpArgs);
+
+    if (!pxGetOptions(stderr, argc, argv, config, modes, argSets)) {
+        psError(PS_ERR_UNKNOWN, true, "option parsing failed");
+        psFree(argSets);
+        psFree(modes);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+    psFree(modes);
+
+    // define Database handle, if used
+    // do this last so we don't setup a connection before CLI options are
+    // validated
+    config->dbh = psMemIncrRefCounter(pmConfigDB(config->modules));
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    return config;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxregister.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxregister.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxregister.c	(revision 22157)
@@ -0,0 +1,59 @@
+/*
+ * register.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <ippdb.h>
+#include <string.h>
+
+#include "pxtools.h"
+#include "pxregister.h"
+
+bool pxnewExpSetState(pxConfig *config, psS64 exp_id, const char *state)
+{
+    if (!exp_id) {
+        psError(PS_ERR_UNKNOWN, true, "0 is not a valid exp_id");
+        return false;
+    }
+    PS_ASSERT_PTR_NON_NULL(state, false);
+
+    // check that state is a valid string value
+    if (!(
+            (strncmp(state, "run", 4) == 0)
+            || (strncmp(state, "stop", 5) == 0)
+            || (strncmp(state, "reg", 4) == 0)
+        )
+    ) {
+        psError(PS_ERR_UNKNOWN, false,
+                "invalid newExp state: %s", state);
+        return false;
+    }
+
+    char *query = "UPDATE newExp SET state = '%s' WHERE exp_id = %"PRId64;
+    if (!p_psDBRunQuery(config->dbh, query, state, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false,
+                "failed to change state for exp_id %"PRId64, exp_id);
+        return false;
+    }
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxregister.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxregister.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxregister.h	(revision 22157)
@@ -0,0 +1,29 @@
+/*
+ * pxregister.h
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PXREGISTER_H
+#define PXREGISTER_H 1
+
+#include <pslib.h>
+
+#include "pxtools.h"
+
+bool pxnewExpSetState(pxConfig *config, psS64 exp_id, const char *state);
+
+#endif // PXREGISTER_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtag.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtag.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtag.c	(revision 22157)
@@ -0,0 +1,102 @@
+/*
+ * pxtag.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVB_CONFIG_H
+#include <config.h>
+#endif
+
+#include <inttypes.h>
+
+#include "pxtools.h"
+#include "pxtag.h"
+
+psString pxGenExpTag(pxConfig *config, const char *exp_name)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    // start a transaction so we don't increment the expTag counter unless we
+    // can successfully retreive it's value
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return NULL;
+    }
+
+    if (!p_psDBRunQuery(config->dbh,
+        "UPDATE expTagCounter SET counter = LAST_INSERT_ID(counter + 1)")) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return NULL;
+    }
+
+    // psDBLastInsertID() can't be used here as it called mysql_insert_id()
+    // which doesn't work with this trick.  See:
+    // http://dev.mysql.com/doc/refman/4.1/en/information-functions.html
+    if (!p_psDBRunQuery(config->dbh, "SELECT LAST_INSERT_ID() as counter")) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return NULL;
+    }
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return NULL;
+    }
+    // sanity check that we only got one row
+    if (psArrayLength(output) != 1) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "should have gotten 1 row but %lu rows were returned", psArrayLength(output));
+        psFree(output);
+        return NULL;
+    }
+
+    // point of no return 
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(output);
+        return NULL;
+    }
+
+    psMetadata *row = output->data[0];
+    bool status = false;
+    psU64 counter = psMetadataLookupU64(&status, row, "counter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for counter");
+        psFree(output);
+        return NULL;
+    }
+    psString exp_id = NULL;
+    psStringAppend(&exp_id, "%s.%" PRIu64, exp_name, counter);
+    psFree(output);
+
+    return exp_id;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtag.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtag.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtag.h	(revision 22157)
@@ -0,0 +1,29 @@
+/*
+ * pxtag.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PXTAG_H
+#define PXTAG_H 1
+
+#include <pslib.h>
+
+#include "pxconfig.h"
+
+psString pxGenExpTag(pxConfig *config, const char *exp_name);
+
+#endif // PXTAG_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtools.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtools.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtools.c	(revision 22157)
@@ -0,0 +1,43 @@
+/*
+ * pxtool.c
+ *
+ * Copyright (C) 2008  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "pxtools.h"
+
+bool pxIsValidState(const char *state)
+{
+    PS_ASSERT_PTR_NON_NULL(state, false);
+
+    if (!((strncmp(state, "new", 4) == 0)
+    || (strncmp(state, "full", 5) == 0)
+    || (strncmp(state, "goto_cleaned", 8) == 0)
+    || (strncmp(state, "cleaned", 8) == 0)
+    || (strncmp(state, "update", 7) == 0)
+    || (strncmp(state, "purged", 7) == 0)
+    || (strncmp(state, "goto_purged", 12) == 0))) {
+        return false;
+    }
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtools.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtools.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtools.h	(revision 22157)
@@ -0,0 +1,480 @@
+/*
+ * pxtools.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PXTOOLS_H
+#define PXTOOLS_H 1
+
+#include <stdio.h>
+#include <string.h>   // for strcmp and strncmp
+#include <strings.h>  // for strcasecmp
+#include <unistd.h>   // for unlink
+
+// #include <stdlib.h>
+// #include <stdint.h>
+// #include <inttypes.h>
+
+#include <pslib.h>
+#include <psmodules.h>
+#include <ippdb.h>
+
+#include "pxconfig.h"
+#include "pxtoolsErrorCodes.h"
+
+#include "pxcam.h"
+#include "pxchip.h"
+#include "pxdata.h"
+#include "pxfake.h"
+#include "pxwarp.h"
+#include "pxregister.h"
+#include "pxtag.h"
+#include "pxtree.h"
+
+# define MAX_ROWS 10e9
+# define PXTOOL_MODE_NONE 0x0
+
+bool pxIsValidState(const char *state);
+
+bool pxSetFaultCode(psDB *dbh, const char *tableName, psMetadata *where, psS16 code);
+
+psExit pxerrorGetExitStatus ();
+
+void pxUsage(FILE *stream, int argc, char **argv, const char *modeName, psMetadata *argSet);
+bool pxGetOptions(FILE *stream, int argc, char **argv, pxConfig *config, psMetadata *modes, psMetadata *argSets);
+
+#define PXOPT_ADD_MODE(option, comment, modeval, argset) \
+{ \
+    if (!psMetadataAddMetadata(argSets, PS_LIST_TAIL, option, 0, comment, argset)) {;\
+        psError(PS_ERR_UNKNOWN, false, "failed to add argset for %s", option); \
+    } \
+    psFree(argset); \
+\
+    if (!psMetadataAddU32(modes, PS_LIST_TAIL, option, 0, comment, modeval)) {;\
+        psError(PS_ERR_UNKNOWN, false, "failed to add argset for %s", option); \
+    } \
+}
+
+#define PXOPT_LOOKUP_STR(var, md, key, required, ret) \
+psString var; \
+{ \
+    bool status; \
+ \
+    var = psMetadataLookupStr(&status, md, key); \
+    if (!status) { \
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for %s", key); \
+        return ret; \
+    } \
+ \
+    if (required && (!var)) { \
+        psError(PS_ERR_UNKNOWN, true, "%s is required", key); \
+        return ret; \
+    } \
+}
+
+#define PXOPT_LOOKUP_F(var, md, key, type, required, ret) \
+ps##type var; \
+{ \
+    bool status; \
+ \
+    var = psMetadataLookup##type(&status, md, key); \
+    if (!status) { \
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for %s", key); \
+        return ret; \
+    } \
+ \
+    if (required && isnan(var)) { \
+        psError(PS_ERR_UNKNOWN, true, "%s is required", key); \
+        return ret; \
+    } \
+}
+
+#define PXOPT_LOOKUP_F32(var, md, key, required, ret) \
+    PXOPT_LOOKUP_F(var, md, key, F64, required, ret)
+
+#define PXOPT_LOOKUP_F64(var, md, key, required, ret) \
+    PXOPT_LOOKUP_F(var, md, key, F64, required, ret)
+
+#define PXOPT_LOOKUP_PRIMITIVE(var, md, key, type, suffix, max, required, ret) \
+type var; \
+{ \
+    psMetadataItem *item = psMetadataLookup(md, key); \
+    if (!item) { \
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for %s", key); \
+        return ret; \
+    } \
+    psAssert(item->comment, "metadata item comment should be defined"); \
+    if (required && (!psStrcasestr(item->comment, "(found)"))) { \
+        psError(PS_ERR_UNKNOWN, true, "%s is required", key); \
+        return ret; \
+    } \
+ \
+    var = item->data.suffix; \
+ \
+}
+
+#define PXOPT_LOOKUP_S16(var, md, key, required, ret) \
+    PXOPT_LOOKUP_PRIMITIVE(var, md, key, psS16, S16, INT16_MAX, required, ret)
+
+#define PXOPT_LOOKUP_S32(var, md, key, required, ret) \
+    PXOPT_LOOKUP_PRIMITIVE(var, md, key, psS32, S32, INT32_MAX, required, ret)
+
+#define PXOPT_LOOKUP_S64(var, md, key, required, ret) \
+    PXOPT_LOOKUP_PRIMITIVE(var, md, key, psS64, S64, INT64_MAX, required, ret)
+
+#define PXOPT_LOOKUP_U16(var, md, key, required, ret) \
+    PXOPT_LOOKUP_PRIMITIVE(var, md, key, psU16, U16, UINT16_MAX, required, ret)
+
+#define PXOPT_LOOKUP_U32(var, md, key, required, ret) \
+    PXOPT_LOOKUP_PRIMITIVE(var, md, key, psU32, U32, UINT32_MAX, required, ret)
+
+#define PXOPT_LOOKUP_U64(var, md, key, required, ret) \
+    PXOPT_LOOKUP_PRIMITIVE(var, md, key, psU64, U64, UINT64_MAX, required, ret)
+
+#define PXOPT_LOOKUP_TIME(var, md, key, required, ret) \
+psTime *var; \
+{ \
+    bool status; \
+ \
+    var = psMetadataLookupTime(&status, md, key); \
+    if (!status) { \
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for %s", key); \
+        return ret; \
+    } \
+ \
+    if (required && (!var)) { \
+        psError(PS_ERR_UNKNOWN, true, "%s is required", key); \
+        return ret; \
+    } \
+}
+
+#define PXOPT_LOOKUP_BOOL(var, md, key, ret) \
+bool var; \
+{ \
+    bool status; \
+ \
+    var = psMetadataLookupBool(&status, md, key); \
+    if (!status) { \
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for %s", key); \
+        return ret; \
+    } \
+}
+
+// XXX the PXOPT_COPY_* macros free 'to' on error
+
+#define PXOPT_COPY_PRIMITIVE(from, to, type, suffix, oldname, newname, newcomment) \
+{ \
+    psMetadataItem *item = psMetadataLookup(from, oldname); \
+    if (!item) { \
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for %s", oldname); \
+        return false; \
+    } \
+    psAssert(item->comment, "metadata item comment should be defined"); \
+\
+    if (psStrcasestr(item->comment, "(found)")) { \
+        if (!psMetadataAdd##suffix(to, PS_LIST_TAIL, newname, PS_META_DUPLICATE_OK, newcomment, item->data.suffix)) { \
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " newname); \
+            psFree(to); \
+            return false; \
+        } \
+    } \
+}
+
+#define PXOPT_COPY_V(from, to, type, suffix, oldname, newname, comment) \
+{ \
+    bool status = false; \
+    type var = psMetadataLookup##suffix(&status, from, oldname); \
+    if (!status) { \
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for " oldname); \
+        return false; \
+    } \
+    if (var) { \
+        if (!psMetadataAdd##suffix(to, PS_LIST_TAIL, newname, PS_META_DUPLICATE_OK, comment, var)) { \
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " newname); \
+            psFree(to); \
+            return false; \
+        } \
+    }\
+}
+
+#define PXOPT_COPY_F(from, to, type, oldname, newname, comment) \
+{ \
+    bool status = false; \
+    ps##type var = psMetadataLookup##type(&status, from, oldname); \
+    if (!status) { \
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for " oldname); \
+        return false; \
+    } \
+    if (!isnan(var)) { \
+        if (!psMetadataAdd##type(to, PS_LIST_TAIL, newname, PS_META_DUPLICATE_OK, comment, var)) { \
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " newname); \
+            psFree(to); \
+            return false; \
+        } \
+    } \
+}
+
+#define PXOPT_COPY_F32(from, to, oldname, newname, comment) \
+  PXOPT_COPY_F(from, to, F32, oldname, newname, comment)
+
+#define PXOPT_COPY_F64(from, to, oldname, newname, comment) \
+  PXOPT_COPY_F(from, to, F64, oldname, newname, comment)
+
+#define PXOPT_COPY_TIME(from, to, oldname, newname, comment) \
+  PXOPT_COPY_V(from, to, psTime *, Time, oldname, newname, comment)
+
+#define PXOPT_COPY_STR(from, to, oldname, newname, comment) \
+  PXOPT_COPY_V(from, to, psString, Str, oldname, newname, comment)
+
+#define PXOPT_COPY_S16(from, to, oldname, newname, comment) \
+  PXOPT_COPY_PRIMITIVE(from, to, psS16, S16, oldname, newname, comment)
+
+#define PXOPT_COPY_S32(from, to, oldname, newname, comment) \
+  PXOPT_COPY_PRIMITIVE(from, to, psS32, S32, oldname, newname, comment)
+
+#define PXOPT_COPY_S64(from, to, oldname, newname, comment) \
+  PXOPT_COPY_PRIMITIVE(from, to, psS64, S64, oldname, newname, comment)
+
+#define PXOPT_COPY_U16(from, to, oldname, newname, comment) \
+  PXOPT_COPY_PRIMITIVE(from, to, psU16, U16, oldname, newname, comment)
+
+#define PXOPT_COPY_U32(from, to, oldname, newname, comment) \
+  PXOPT_COPY_PRIMITIVE(from, to, psU32, U32, oldname, newname, comment)
+
+#define PXOPT_COPY_U64(from, to, oldname, newname, comment) \
+  PXOPT_COPY_PRIMITIVE(from, to, psU64, U64, oldname, newname, comment)
+
+
+/*** these PXOPT_ADD_WHERE macros are used to construct the default sql elements ***/
+
+#define PXOPT_ADD_WHERE_STR(name) \
+{ \
+    psString str = NULL; \
+    bool status = false; \
+    if ((str = psMetadataLookupStr(&status, config->args, "-" #name))) { \
+        if (!psMetadataAddStr(config->where, PS_LIST_TAIL, #name, 0, "==", str)) {\
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+            psFree(config); \
+            return NULL; \
+        } \
+    } \
+}
+
+#define PXOPT_ADD_WHERE_STR_ALIAS(name,realname) \
+{ \
+    psString str = NULL; \
+    bool status = false; \
+    if ((str = psMetadataLookupStr(&status, config->args, name))) { \
+        if (!psMetadataAddStr(config->where, PS_LIST_TAIL, realname, 0, "==", str)) {\
+            psError(PS_ERR_UNKNOWN, false, "failed to add item %s", name); \
+            psFree(config); \
+            return NULL; \
+        } \
+    } \
+}
+
+#define PXOPT_ADD_WHERE_S16(name) \
+{ \
+    psS16 s16 = 0; \
+    bool status = false; \
+    if ((s16= psMetadataLookupS16(&status, config->args, "-" #name))) { \
+        if (!psMetadataAddS16(config->where, PS_LIST_TAIL, #name, 0, "==", s16)) { \
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+            psFree(config); \
+            return NULL; \
+        } \
+    } \
+}
+
+#define PXOPT_ADD_WHERE_BOOL(name) \
+{ \
+    bool value = false;	 \
+    bool status = false; \
+    if ((value = psMetadataLookupBool(&status, config->args, "-" #name))) { \
+        if (!psMetadataAddBool(config->where, PS_LIST_TAIL, #name, 0, "==", value)) { \
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+            psFree(config); \
+            return NULL; \
+        } \
+    } \
+}
+
+#define PXOPT_ADD_WHERE_S32(name) \
+{ \
+    psS32 s32 = 0; \
+    bool status = false; \
+    if ((s32= psMetadataLookupS32(&status, config->args, "-" #name))) { \
+        if (!psMetadataAddS32(config->where, PS_LIST_TAIL, #name, 0, "==", s32)) { \
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+            psFree(config); \
+            return NULL; \
+        } \
+    } \
+}
+
+#define PXOPT_ADD_WHERE_S64(name) \
+{ \
+    psS64 s64 = 0; \
+    bool status = false; \
+    if ((s64= psMetadataLookupS64(&status, config->args, "-" #name))) { \
+        if (!psMetadataAddS64(config->where, PS_LIST_TAIL, #name, 0, "==", s64)) { \
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+            psFree(config); \
+            return NULL; \
+        } \
+    } \
+}
+
+#define PXOPT_ADD_WHERE_F32(name) \
+{ \
+    psF32 var = 0; \
+    bool status = false; \
+    if ((var = psMetadataLookupF32(&status, config->args, "-" #name))) { \
+        if (!isnan(var)) { \
+            if (!psMetadataAddF32(config->where, PS_LIST_TAIL, #name, 0, "==", var)) { \
+                psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+                psFree(config); \
+                return NULL; \
+            } \
+        } \
+    } \
+}
+
+#define PXOPT_ADD_WHERE_F64(name) \
+{ \
+    psF64 var = 0; \
+    bool status = false; \
+    if ((var = psMetadataLookupF64(&status, config->args, "-" #name))) { \
+        if (!isnan(var))  { \
+            if (!psMetadataAddF64(config->where, PS_LIST_TAIL, #name, 0, "==", var)) { \
+                psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+                psFree(config); \
+                return NULL; \
+            } \
+        } \
+    } \
+}
+
+#define PXOPT_ADD_WHERE_BOOL_ALIAS(flag,name)	\
+{ \
+    bool value = 0; \
+    bool status = false; \
+    if ((value = psMetadataLookupBool(&status, config->args, flag))) {	\
+        if (!psMetadataAddBOOL(config->where, PS_LIST_TAIL, name, 0, "==", value)) { \
+            psError(PS_ERR_UNKNOWN, false, "failed to add item %s", flag); \
+            psFree(config); \
+            return NULL; \
+        } \
+    } \
+}
+
+#define PXOPT_ADD_WHERE_S16_ALIAS(flag,name)	\
+{ \
+    psS16 s16 = 0; \
+    bool status = false; \
+    if ((s16= psMetadataLookupS16(&status, config->args, flag))) {	\
+        if (!psMetadataAddS16(config->where, PS_LIST_TAIL, name, 0, "==", s16)) { \
+            psError(PS_ERR_UNKNOWN, false, "failed to add item %s", flag); \
+            psFree(config); \
+            return NULL; \
+        } \
+    } \
+}
+
+#define PXOPT_ADD_WHERE_S32_ALIAS(flag,name)	\
+{ \
+    psS32 s32 = 0; \
+    bool status = false; \
+    if ((s32= psMetadataLookupS32(&status, config->args, flag))) {	\
+        if (!psMetadataAddS32(config->where, PS_LIST_TAIL, name, 0, "==", s32)) { \
+            psError(PS_ERR_UNKNOWN, false, "failed to add item %s", flag); \
+            psFree(config); \
+            return NULL; \
+        } \
+    } \
+}
+
+#define PXOPT_ADD_WHERE_S64_ALIAS(flag,name)	\
+{ \
+    psS64 s64 = 0; \
+    bool status = false; \
+    if ((s64= psMetadataLookupS64(&status, config->args, flag))) {	\
+        if (!psMetadataAddS64(config->where, PS_LIST_TAIL, name, 0, "==", s64)) { \
+            psError(PS_ERR_UNKNOWN, false, "failed to add item %s", flag); \
+            psFree(config); \
+            return NULL; \
+        } \
+    } \
+}
+
+#define PXOPT_ADD_WHERE_F32_ALIAS(flag,name)	\
+{ \
+    psF32 var = 0; \
+    bool status = false; \
+    if ((var = psMetadataLookupF32(&status, config->args, flag))) {	\
+        if (!isnan(var))  { \
+            if (!psMetadataAddF32(config->where, PS_LIST_TAIL, name, 0, "==", var)) { \
+                psError(PS_ERR_UNKNOWN, false, "failed to add item %s", flag); \
+                psFree(config); \
+                return NULL; \
+            } \
+        } \
+    } \
+}
+
+#define PXOPT_ADD_WHERE_F64_ALIAS(flag,name)	\
+{ \
+    psF64 var = 0; \
+    bool status = false; \
+    if ((var = psMetadataLookupF64(&status, config->args, flag))) {	\
+        if (!isnan(var))  { \
+            if (!psMetadataAddF64(config->where, PS_LIST_TAIL, name, 0, "==", var)) { \
+                psError(PS_ERR_UNKNOWN, false, "failed to add item %s", flag); \
+                psFree(config); \
+                return NULL; \
+            } \
+        } \
+    } \
+}
+
+#define PXOPT_ADD_WHERE_TIME_STR(name) \
+{ \
+    psString str = NULL; \
+    bool status = false; \
+    if ((str = psMetadataLookupStr(&status, config->args, "-" #name))) { \
+        psTime *time = psTimeFromISO(str, PS_TIME_UTC); \
+        if (!time) { \
+            psError(PS_ERR_UNKNOWN, false, "failed to convert " #name " into a psTime object"); \
+            psFree(config); \
+            return NULL; \
+        } \
+        psMetadataItem *item = psMetadataLookup(config->args, "-" #name); \
+        if (item) { \
+            str = item->comment; \
+        } else { \
+            str = NULL; \
+        } \
+        if (!psMetadataAddTime(config->where, PS_LIST_TAIL, #name, 0, str, time)) {\
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+            psFree(config); \
+            return NULL; \
+        } \
+        psFree(time); \
+    } \
+}
+
+#endif // PXTOOLS_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtoolsErrorCodes.c.in
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtoolsErrorCodes.c.in	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtoolsErrorCodes.c.in	(revision 22157)
@@ -0,0 +1,25 @@
+/*
+ * The line
+    { PXTOOLS_ERR_$X{ErrorCode}, "$X{ErrorDescription}"},
+ * (without the Xs)
+ * will be replaced by values from errorCodes.dat
+ */
+#include "pslib.h"
+#include "pxtoolsErrorCodes.h"
+
+void pxtoolsErrorRegister(void)
+{
+    static psErrorDescription errors[] = {
+       { PXTOOLS_ERR_BASE, "First value we use; lower values belong to psLib" },
+       { PXTOOLS_ERR_${ErrorCode}, "${ErrorDescription}"},
+    };
+    static int nerror = PXTOOLS_ERR_NERROR - PXTOOLS_ERR_BASE; // number of values in enum
+
+    for (int i = 0; i < nerror; i++) {
+       psErrorDescription *tmp = psAlloc(sizeof(psErrorDescription));
+       *tmp = errors[i];
+       psErrorRegister(tmp, 1);
+       psFree(tmp);			/* it's on the internal list */
+    }
+    nerror = 0;			                // don't register more than once
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtoolsErrorCodes.dat
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtoolsErrorCodes.dat	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtoolsErrorCodes.dat	(revision 22157)
@@ -0,0 +1,10 @@
+#
+# This file is used to generate pxtoolsErrorCodes.h
+#
+BASE = 1100		First value we use; lower values belong to psLib
+UNKNOWN			Unknown PM error code
+ARGUMENTS		Incorrect arguments
+SYS			System error
+CONFIG			Problem in configure files
+PROG			Programming error
+DATA   			invalid data
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtoolsErrorCodes.h.in
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtoolsErrorCodes.h.in	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtoolsErrorCodes.h.in	(revision 22157)
@@ -0,0 +1,18 @@
+#if !defined(PXTOOLS_ERROR_CODES_H)
+#define PXTOOLS_ERROR_CODES_H
+/*
+ * The line
+ *  PXTOOLS_ERR_$X{ErrorCode},
+ * (without the X)
+ *
+ * will be replaced by values from errorCodes.dat
+ */
+typedef enum {
+    PXTOOLS_ERR_BASE = 512,
+    PXTOOLS_ERR_${ErrorCode},
+    PXTOOLS_ERR_NERROR
+} pxtoolsErrorCode;
+
+void pxtoolsErrorRegister(void);
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtree.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtree.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtree.c	(revision 22157)
@@ -0,0 +1,237 @@
+/*
+ * pxio.c
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVB_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pslib.h>
+
+#include "pxtools.h"
+#include "pxtree.h"
+
+static void pxNodeFree(pxNode *node)
+{
+    psFree(node->name);
+    psFree(node->parent);
+    psFree(node->children);
+    psFree(node->data);
+}
+
+pxNode *pxNodeAlloc(const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(name, NULL);
+
+    pxNode *node = psAlloc(sizeof(pxNode));
+
+    node->name = psStringCopy(name);
+    node->parent = NULL;
+    node->children = psListAlloc(NULL);
+    node->data = NULL;
+
+    psMemSetDeallocator(node, (psFreeFunc) pxNodeFree);
+
+    return node;
+}
+
+pxNode *pxNodeAddParent(pxNode *node, pxNode *parent)
+{
+    // add this node to it's parent's children list
+    psListAdd(parent->children, PS_LIST_TAIL, node);
+    // and set the parent pointer
+    node->parent = psMemIncrRefCounter(parent);
+
+    return node;
+}
+
+pxNode *pxNodeAddChild(pxNode *node, pxNode *child)
+{
+    // add the child node to this nodes list of children
+    psListAdd(node->children, PS_LIST_TAIL, child);
+    // if the child already has a parent, release the ref count
+    if (child->parent) {
+        psFree(child->parent);
+    }
+    // [re]parent the child
+    child->parent = psMemIncrRefCounter(node);
+
+    return node;
+}
+
+pxNode *pxNodeAddData(pxNode *node, psPtr data)
+{
+    node->data = psMemIncrRefCounter(data);
+
+    return node;
+}
+
+void pxNodePrint(FILE *stream, pxNode *node)
+{
+    fprintf(stream, "node name: %s\n", node->name);
+    fprintf(stream, "node parent's name: %s\n", node->parent
+                                              ?  node->parent->name
+                                              : NULL);
+    psListIterator *iter = psListIteratorAlloc(node->children, 0, false);                                          
+    pxNode *child = NULL;
+    while ((child = psListGetAndIncrement(iter))) {
+        fprintf(stream, "node child's name: %s\n", child->name);
+    }
+    psFree(iter);
+}
+
+
+static bool printNodes(void *arg, pxNode *node)
+{
+    pxNodePrint((FILE *)arg, node);
+    fprintf((FILE *)arg, "\n\n");
+
+    return true;
+}
+
+
+void pxTreePrint(FILE *stream, pxNode *root)
+{
+    pxTreeCrawl(root, printNodes, (void *)stream);
+}
+
+// func() returning false means decend no futher along this branch
+
+bool pxTreeCrawl(pxNode *node, pxNodeFunc func, void *arg)
+{
+    // if func() returns false, stop
+    if (!func(arg, node)) {
+        return false;
+    }
+
+    psListIterator *iter = psListIteratorAlloc(node->children, 0, false);                                          
+    pxNode *child = NULL;
+    while ((child = psListGetAndIncrement(iter))) {
+        pxTreeCrawl(child, func, arg);
+    }
+    psFree(iter);
+
+    return true;
+}
+
+bool pxNodeFuncCountDependants(void *arg, pxNode *node)
+{
+    (*(int *)arg)++;
+    return true;
+}
+
+bool pxNodeHasChildren(pxNode *node)
+{
+    int children = psListLength(node->children);
+
+    return children ? true : false;
+}
+
+bool pxNodeHasGrandChildren(pxNode *node)
+{
+    // find out how many nodes there are lower in the tree
+    // subtract the parent node from the count so we have just a tally of
+    // decendants
+    int nodes = -1;
+    pxTreeCrawl(node, pxNodeFuncCountDependants, &nodes);
+    psTrace("pxtree", PS_LOG_INFO, "node %s has %d dependants", node->name, nodes);
+
+    // find out how many children this node has
+    int children = psListLength(node->children);
+    psTrace("pxtree", PS_LOG_INFO, "node %s has %d children", node->name, children);
+
+    if (!children) {
+        // no children
+        return false;
+    }
+
+    if (nodes < children) {
+        psAbort("you can't have fewer children than decendants!");
+    }
+
+    if (nodes == children) {
+        // no grandchildren
+        return false;
+    }
+
+    return true;
+}
+
+pxNode *pxTreeFromMetadata(psMetadata *md)
+{
+    psHash *forest = psHashAlloc(10);
+
+    psMetadataIterator *iter = psMetadataIteratorAlloc(md, 0, NULL);
+    psMetadataItem *item = NULL;
+    while ((item = psMetadataGetAndIncrement(iter))) {
+        if (!(item->type == PS_DATA_STRING)) {
+            continue;
+        }
+
+        // add this node to the forest and hopefully it'll get attached to the
+        // root tree
+        pxTreeBuilder(forest, item->name, item->data.str, NULL);
+    }
+    psFree(iter);
+
+    pxNode *root = psHashLookup(forest, "root");
+    psFree(forest);
+
+    return psMemIncrRefCounter(root);
+}
+
+psHash *pxTreeBuilder(psHash *forest,
+                      const char *nodeName,
+                      const char *childName,
+                      psPtr data)
+{
+    // try to find a node with this name
+    pxNode *node = psHashLookup(forest, nodeName);
+    if (!node) {
+        // create a new node with this name
+        node = pxNodeAlloc(nodeName);
+        if (data) {
+            pxNodeAddData(node, data);
+        }
+        // add it to the hash of nodes
+        psHashAdd(forest, nodeName, node);
+        // node may be used below because it will still have a ref count of
+        // 1 from being on the hash, this is equivalent to the "view" we
+        // get from a hashlookup
+        psFree(node);
+    }
+
+    // does this node declare a child?
+    if (childName) {
+        // try to find a node with this name
+        pxNode *child = psHashLookup(forest, childName);
+        if (!child ) {
+            // create a new node with this name
+            child = pxNodeAlloc(childName);
+            // add it to the hash of nodes
+            psHashAdd(forest, childName, child);
+            // child may be used below because it will still have a ref
+            // count of 1 from being on the hash, this is equivalent to the
+            // "view" we get from a hashlookup
+            psFree(child);
+        }
+        pxNodeAddChild(node, child);
+    }
+
+    return forest;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtree.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtree.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxtree.h	(revision 22157)
@@ -0,0 +1,72 @@
+/*
+ * pxtree.h
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PXTREE_H
+#define PXTREE_H 1
+
+#include <stdio.h>
+#include <stdbool.h>
+
+#include <pslib.h>
+
+
+typedef struct pxNode {
+  char *name;
+  struct pxNode *parent;
+  psList  *children;
+  void *data;
+} pxNode;
+
+typedef bool (*pxNodeFunc)(void *arg, pxNode *node);
+
+pxNode *pxNodeAlloc(
+    const char *name
+) PS_ATTR_MALLOC;
+
+pxNode *pxNodeAddParent(pxNode *node, pxNode *parent);
+pxNode *pxNodeAddChild(pxNode *node, pxNode *child);
+pxNode *pxNodeAddData(pxNode *node, psPtr data);
+
+void pxTreePrint(FILE *stream, pxNode *root);
+
+void pxNodePrint(
+    FILE *stream,
+    pxNode  *node
+);
+
+bool pxTreeCrawl(
+    pxNode  *node,
+    pxNodeFunc func,
+    void    *arg
+);
+
+bool pxNodeFuncCountDependants(void *arg, pxNode *node);
+
+bool pxNodeHasChildren(pxNode *node);
+
+bool pxNodeHasGrandChildren(pxNode *node);
+
+pxNode *pxTreeFromMetadata(psMetadata *md);
+
+psHash *pxTreeBuilder(psHash *forest,
+                      const char *nodeName,
+                      const char *childName,
+                      psPtr data);
+
+#endif // PXTREE_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxwarp.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxwarp.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxwarp.c	(revision 22157)
@@ -0,0 +1,169 @@
+/*
+ * pxwarp.c
+ *
+ * Copyright (C) 2007-2008  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <ippdb.h>
+#include <string.h>
+
+#include "pxtools.h"
+#include "pxwarp.h"
+
+bool pxwarpRunSetState(pxConfig *config, psS64 warp_id, const char *state)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(state, false);
+
+    // check that state is a valid string value
+    if (!(
+            (strncmp(state, "run", 4) == 0)
+            || (strncmp(state, "stop", 5) == 0)
+            || (strncmp(state, "reg", 4) == 0)
+        )
+    ) {
+        psError(PS_ERR_UNKNOWN, false,
+                "invalid warpRun state: %s", state);
+        return false;
+    }
+
+    char *query = "UPDATE warpRun SET state = '%s' WHERE warp_id = %" PRId64;
+    if (!p_psDBRunQuery(config->dbh, query, state, warp_id)) {
+        psError(PS_ERR_UNKNOWN, false,
+                "failed to change state for warp_id %" PRId64, warp_id);
+        return false;
+    }
+
+    return true;
+}
+
+
+bool pxwarpRunSetStateByQuery(pxConfig *config, psMetadata *where, const char *state)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(state, false);
+
+    // check that state is a valid string value
+    if (!(
+            (strncmp(state, "run", 4) == 0)
+            || (strncmp(state, "stop", 5) == 0)
+            || (strncmp(state, "reg", 4) == 0)
+        )
+    ) {
+        psError(PS_ERR_UNKNOWN, false,
+                "invalid chipRun state: %s", state);
+        return false;
+    }
+
+    psString query = psStringCopy("UPDATE warpRun JOIN fakeRun USING(fake_id) JOIN camRun USING(cam_id) JOIN chipRun USING(chip_id) JOIN rawExp USING(exp_id) SET warpRun.state = '%s'");
+
+    if (where) {
+        psString whereClause = psDBGenerateWhereSQL(where, NULL);
+        if (whereClause && strlen(whereClause) > 0) {
+            psStringAppend(&query, " %s", whereClause);
+        }
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query, state)) {
+        psFree(query);
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    psFree(query);
+
+    return true;
+}
+
+
+bool pxwarpRunSetLabel(pxConfig *config, psS64 warp_id, const char *label)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    // note label == NULL should be explicitly allowed
+
+    char *query = "UPDATE warpRun SET warpRun.label = '%s' WHERE warp_id = %" PRId64;
+    if (!p_psDBRunQuery(config->dbh, query, label, warp_id)) {
+        psError(PS_ERR_UNKNOWN, false,
+                "failed to change label for warp_id %" PRId64, warp_id);
+        return false;
+    }
+
+    return true;
+}
+
+
+bool pxwarpRunSetLabelByQuery(pxConfig *config, psMetadata *where, const char *label)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    // note label == NULL should be explicitly allowed
+
+    psString query = psStringCopy("UPDATE warpRun JOIN fakeRun USING(fake_id) JOIN camRun USING(cam_id) JOIN chipRun USING(chip_id) JOIN rawExp USING(exp_id) SET warpRun.label = '%s'");
+
+    if (where) {
+        psString whereClause = psDBGenerateWhereSQL(where, NULL);
+        psStringAppend(&query, " %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query, label)) {
+        psFree(query);
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    psFree(query);
+
+    return true;
+}
+
+
+bool pxwarpQueueByFakeID(pxConfig *config,
+                    psS64 fake_id,
+                    char *workdir,
+                    char *label,
+                    char *dvodb,
+                    char *tess_id,
+                    char *end_stage)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // depend on the f-keys to make sure we have a valid cam_id
+    if (!warpRunInsert(config->dbh,
+        0,          // ID
+        fake_id,
+        "warp",     // mode
+        "run",      // state
+        workdir,
+        "dirty",    // workdir_state
+        label,
+        dvodb,
+        tess_id,
+        end_stage,
+        NULL,       // registered
+        false       // magiced
+    )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return true;
+    }
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pxwarp.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pxwarp.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pxwarp.h	(revision 22157)
@@ -0,0 +1,40 @@
+/*
+ * pxwarp.h
+ *
+ * Copyright (C) 2007-2008  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PXWARP_H
+#define PXWARP_H 1
+
+#include <pslib.h>
+
+#include "pxtools.h"
+
+bool pxwarpRunSetState(pxConfig *config, psS64 warp_id, const char *state);
+bool pxwarpRunSetStateByQuery(pxConfig *config, psMetadata *where, const char *state);
+bool pxwarpRunSetLabel(pxConfig *config, psS64 warp_id, const char *label);
+bool pxwarpRunSetLabelByQuery(pxConfig *config, psMetadata *where, const char *label);
+
+bool pxwarpQueueByFakeID(pxConfig *config,
+                    psS64 fake_id,
+                    char *workdir,
+                    char *label,
+                    char *dvodb,
+                    char *tess_id,
+                    char *end_stage);
+
+#endif // PXWARP_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pzgetexp.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pzgetexp.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pzgetexp.c	(revision 22157)
@@ -0,0 +1,343 @@
+/*
+ * pzgetexp.c
+ *
+ * Copyright (C) 2006-2008  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "pxtools.h"
+#include "pzgetexp.h"
+
+#define PRODUCT_LS_CMD "dsproductls"
+
+static bool go (pxConfig *config);
+static psArray *parseFileSets(pxConfig *config, const char *str);
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = pzgetexpConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    if (!go(config)) {
+        goto FAIL;
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+    
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+static bool go(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    // required
+    PXOPT_LOOKUP_STR(camera, config->args, "-inst", true, false);
+    PXOPT_LOOKUP_STR(telescope, config->args, "-telescope", true, false);
+    PXOPT_LOOKUP_STR(uri, config->args, "-uri", true, false);
+
+    // find last fileset/exp_name (if we have one)
+    bool haveLastFileSet = false;
+    psString lastFileSet = NULL;
+    {
+        char *query = "SELECT * from summitExp WHERE camera = \"%s\" and TELESCOPE = \"%s\" ORDER BY dateobs DESC LIMIT 1";
+        if (!p_psDBRunQuery(config->dbh, query, camera, telescope)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+
+        psArray *output = p_psDBFetchResult(config->dbh);
+        if (!output) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+        if (!psArrayLength(output)) {
+            psError(PS_ERR_UNKNOWN, false, "no summitExp rows found");
+            haveLastFileSet = false;
+            psFree(output);
+        } else {
+            haveLastFileSet = true;
+            bool status = false;
+            lastFileSet = psStringCopy(psMetadataLookupStr(&status, output->data[0], "exp_name"));
+            psFree(output);
+        }
+    }
+
+    // invoke dsproductls
+    // dsproductls --uri <> --last_fileset <>
+    psString cmd = NULL;
+    if (haveLastFileSet) {
+        psStringAppend(&cmd, "%s --uri %s --last_fileset %s",
+            PRODUCT_LS_CMD, uri, lastFileSet);
+        psFree(lastFileSet);
+    } else {
+        psStringAppend(&cmd, "%s --uri %s", PRODUCT_LS_CMD, uri);
+    }
+
+    psTrace("pzgetexp", PS_LOG_INFO, "cmd is: %s\n", cmd);
+
+    FILE *output = popen(cmd, "r");
+    psFree(cmd);
+
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, true, "popen() failed");
+        return false;
+    }
+    psString cmdOutput = psSlurpFile(output);
+    pclose(output);
+
+    psArray *newSummitExps = parseFileSets(config, cmdOutput);
+    psFree(cmdOutput);
+    if (!newSummitExps) {
+        // XXX not nessicarily an error
+        psError(PS_ERR_UNKNOWN, true, "no new fileSet/exp IDs");
+        return false;
+    }
+    if (!psArrayLength(newSummitExps)) {
+        psTrace("pzgetexp", PS_LOG_INFO, "no new fileSet/exp IDs");
+        psFree(newSummitExps);
+        return true;
+    }
+
+    // start a transaction so it's all rows or nothing
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // create a temporry table
+    {
+        char *query = "CREATE TEMPORARY TABLE incoming" 
+            " (exp_name VARCHAR(64), camera VARCHAR(64), telescope VARCHAR(64), dateobs DATETIME, exp_type VARCHAR(64), uri VARCHAR(255), PRIMARY KEY(exp_name, camera, telescope))"
+           " ENGINE=MEMORY";
+        if (!p_psDBRunQuery(config->dbh, query)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(newSummitExps);
+            return false;
+        }
+    }
+
+    {
+        char *query = "INSERT INTO incoming (exp_name, camera, telescope, dateobs, exp_type, uri) VALUES (?, ?, ?, ?, ?, ?)";
+
+        long inserted = p_psDBRunQueryPrepared(config->dbh, newSummitExps, query);
+        if (inserted < 0) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(newSummitExps);
+            return false;
+        }
+        // sanity check that we actually inserted something
+        if (inserted == 0) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error -- we should have inserted at least one row");
+            psFree(newSummitExps);
+            return false;
+        }
+    }
+
+    psFree(newSummitExps);
+
+    // add new exps to summitExp
+    {
+        char *query = 
+            "INSERT INTO summitExp" 
+            "   SElECT"
+            "       incoming.*,"
+            "       NULL,"  // imfiles
+            "       0,"     // fault
+            "       NULL"   // epoch
+            "   FROM incoming"
+            "   LEFT JOIN summitExp"
+            "       USING(exp_name, camera, telescope)"
+            "   WHERE"
+            "       summitExp.exp_name is NULL"
+            "       AND summitExp.camera is NULL"
+            "       AND summitExp.telescope is NULL";
+
+        if (!p_psDBRunQuery(config->dbh, query)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+    }
+    
+    // point of no return
+    if (!psDBCommit(config->dbh)) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static psArray *parseFileSets(pxConfig *config, const char *str)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(str, NULL);
+    
+    // these are constants for all records parsed -- look them up before we do
+    // any work
+    // required
+    PXOPT_LOOKUP_STR(camera, config->args, "-inst", true, false);
+    PXOPT_LOOKUP_STR(telescope, config->args, "-telescope", true, false);
+
+    // split the string into lines
+    psList *doc = psStringSplit(str, "\n", false);
+
+    psListIterator *lineCursor = psListIteratorAlloc(doc, 0, false);
+
+    psArray *summitExps = psArrayAllocEmpty(psListLength(doc));
+    psString line;
+    while ((line = psListGetAndIncrement(lineCursor))) {
+        psTrace("pzgetimfile", PS_LOG_INFO, "parsing line: %s\n", line);
+
+        // split line into tokens
+        psList *tokens = psStringSplit(line, " ", false);
+
+        // check to see if this line is a comment (or if the first token is
+        // NULL)
+        if (!psListGet(tokens, 0) || *((char *)psListGet(tokens, 0)) == '#') {
+            psFree(tokens);
+            continue;
+        }
+
+        // check that we have the right number of tokens
+        // print "# uri fileset datetime type\n";
+        if (psListLength(tokens) != 4) {
+            // error
+            return false;
+        }
+
+        // find the values of interest
+        psListIterator *tokenCursor = psListIteratorAlloc(tokens, 0, false);
+        char *uri       = psListGetAndIncrement(tokenCursor);
+        char *exp_name    = psListGetAndIncrement(tokenCursor); // fileset
+        char *dateobsStr= psListGetAndIncrement(tokenCursor); // datetime
+        char *exp_type  = psListGetAndIncrement(tokenCursor); // type
+
+        // create a new metadata to represent this line and it's values
+        psMetadata *md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_name", 0, NULL, exp_name)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item exp_name");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, camera)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, telescope)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+        psTime *dateobs = psTimeFromISO(dateobsStr, PS_TIME_UTC);
+        if (!psMetadataAddTime(md, PS_LIST_TAIL, "dateobs", 0, NULL, dateobs)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+            psFree(dateobs);
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+        psFree(dateobs);
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, exp_type)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, uri)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+
+        // must be freed after the new metadata is built -- holds the strings
+        psFree(tokenCursor);
+        psFree(tokens);
+
+        // add the new metadata to the result set
+        psArrayAdd(summitExps, 0, md);
+
+        // debugging
+        if (psTraceGetLevel("pzgetexp") == PS_LOG_INFO) {
+            psString doc = psMetadataConfigFormat(md);
+            psTrace("pzgetexp", PS_LOG_INFO, "parsed line as:\n %s\n", doc);
+            psFree(doc);
+        }
+
+        psFree(md);
+
+    }
+
+    psFree(lineCursor);
+    psFree(doc);
+
+    return summitExps;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pzgetexp.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pzgetexp.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pzgetexp.h	(revision 22157)
@@ -0,0 +1,27 @@
+/*
+ * pzgetexp.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PZGETEXP_H
+#define PZGETEXP_H 1
+
+#include "pxtools.h"
+
+pxConfig *pzgetexpConfig(pxConfig *config, int argc, char **argv);
+
+#endif // PZGETEXP_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pzgetexpConfig.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pzgetexpConfig.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pzgetexpConfig.c	(revision 22157)
@@ -0,0 +1,83 @@
+/*
+ * pzgetexpConfig.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "pxtools.h"
+
+pxConfig *pzgetexpConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (! config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration!\n");
+        psFree(config);
+        return NULL;
+    }
+
+    psMetadata *args = psMetadataAlloc();
+    psMetadataAddStr(args , PS_LIST_TAIL, "-uri", 0,
+        "DataStore product URI (required)", "");
+    psMetadataAddStr(args , PS_LIST_TAIL, "-inst", 0,
+        "camera name (required)", "");
+    psMetadataAddStr(args , PS_LIST_TAIL, "-telescope",  0,
+        "telescope name (required)", "");
+
+    bool status = false;
+    if (!psArgumentParse(args, &argc, argv)
+        || argc != 1
+        || strcmp(psMetadataLookupStr(&status, args, "-uri"), "") == 0
+        || strcmp(psMetadataLookupStr(&status, args, "-inst"), "") == 0
+        || strcmp(psMetadataLookupStr(&status, args, "-telescope"), "") == 0
+    ) {
+        fprintf(stderr, "error parsing arguments\n");
+        printf("\nPan-STARRS Phase Z Get Exposures Tool\n");
+        printf("Usage: %s -uri <uri> -inst <camera> -telescope <telescope>\n\n",
+            argv[0]);
+        psArgumentHelp(args);
+        psFree(config);
+        return NULL;
+    }
+
+    config->args = args;
+    // don't free args here as it's silly to increment the ref count then
+    // "free" it
+
+    // define Database handle, if used
+    config->dbh = psMemIncrRefCounter(pmConfigDB(config->modules));
+    if(!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't connect to db\n");
+        psFree(config);
+        return NULL;
+    }
+
+    return config;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pzgetimfiles.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pzgetimfiles.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pzgetimfiles.c	(revision 22157)
@@ -0,0 +1,482 @@
+/*
+ * pzgetimfiles.c
+ *
+ * Copyright (C) 2006-2008  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "pxtag.h"
+#include "pxtools.h"
+#include "pzgetimfiles.h"
+
+#define FILESET_LS_CMD "dsfilesetls"
+
+static bool go (pxConfig *config);
+static psArray *parseFiles(pxConfig *config, const char *str);
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = pzgetimfilesConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    if (!go(config)) {
+        goto FAIL;
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+static bool go(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    // required
+    PXOPT_LOOKUP_STR(uri, config->args, "-uri", true, false);
+    PXOPT_LOOKUP_STR(filesetid, config->args, "-filesetid", true, false);
+    PXOPT_LOOKUP_STR(camera, config->args, "-inst", true, false);
+    PXOPT_LOOKUP_STR(telescope, config->args, "-telescope", true, false);
+
+    // optional
+    PXOPT_LOOKUP_S32(timeout, config->args, "-timeout", false, false);
+
+    // invoke dsfilesetls
+    psString cmd = NULL;
+    psStringAppend(&cmd, "%s --uri %s", FILESET_LS_CMD, uri);
+    if (timeout) {
+        psStringAppend(&cmd, " --timeout %d", timeout);
+    }
+
+    psTrace("pzgetimfiles", PS_LOG_INFO, "cmd is: %s\n", cmd);
+
+    FILE *output = popen(cmd, "r");
+    psFree(cmd);
+
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, true, "popen() failed");
+        return false;
+    }
+
+    psString cmdOutput = psSlurpFile(output);
+    int status = pclose(output);
+
+    if (status != 0) {
+        // mark the summitExp row as faulted
+        if (!p_psDBRunQuery(config->dbh, "UPDATE summitExp SET fault = %d WHERE exp_name = '%s' AND camera = '%s' AND telescope = '%s'", WEXITSTATUS(status), filesetid, camera, telescope)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+
+        psError(PS_ERR_UNKNOWN, true, "%s failed with exit status %d",
+            FILESET_LS_CMD, WEXITSTATUS(status));
+        psFree(cmdOutput);
+        return false;
+    }
+
+    // prase output of dsfilesetls
+    psArray *newImfiles = parseFiles(config, cmdOutput);
+    if (!newImfiles) {
+        // XXX not nessicarily an error
+        psError(PS_ERR_UNKNOWN, true, "no new files/imfiles");
+        psFree(cmdOutput);
+        return false;
+    }
+    psFree(cmdOutput);
+    
+    // save the number of new Imfiles;
+    long imfiles = psArrayLength(newImfiles);
+
+    // start a transaction so it's all rows or nothing
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // if the fileset was empty (no files) then we can bail out early
+    if (imfiles == 0) {
+        psFree(newImfiles);
+
+        char *query = 
+            "UPDATE summitExp"
+            " SET imfiles = %d"
+            " WHERE exp_name = '%s'"
+            " AND camera = '%s'"
+            " AND telescope = '%s'";
+        if (!p_psDBRunQuery(config->dbh, query, imfiles, filesetid, camera, telescope)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+
+        // remove the pzDownloadExp entry for this exp (fileset)
+        {
+            char *query = 
+                "DELETE FROM pzDownloadExp"
+                " WHERE"
+                "   exp_name = '%s'"
+                "   AND camera = '%s'"
+                "   AND telescope = '%s'";
+            if (!p_psDBRunQuery(config->dbh, query, filesetid, camera, telescope)) {
+                // rollback
+                if (!psDBRollback(config->dbh)) {
+                    psError(PS_ERR_UNKNOWN, false, "database error");
+                }
+                psError(PS_ERR_UNKNOWN, false, "database error");
+                return false;
+            }
+
+            // sanity check: we should have removed only one row
+            psU64 affected = psDBAffectedRows(config->dbh);
+            if (psDBAffectedRows(config->dbh) != 1) {
+                // rollback
+                if (!psDBRollback(config->dbh)) {
+                    psError(PS_ERR_UNKNOWN, false, "database error");
+                }
+                psError(PS_ERR_UNKNOWN, false, "should have affected 1 row but %" PRIu64 " rows were modified", affected);
+                return false;
+            }
+        }
+ 
+        // point of no return
+        if (!psDBCommit(config->dbh)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+
+        return true;
+    } 
+
+    // create a temp table
+    {
+        char *query = 
+            "CREATE TEMPORARY TABLE incoming (exp_name VARCHAR(64), camera VARCHAR(64), telescope VARCHAR(64), file_id VARCHAR(64), bytes INT, md5sum VARCHAR(32), class VARCHAR(64), class_id VARCHAR(64), uri VARCHAR(255), PRIMARY KEY(exp_name, camera, telescope, class, class_id)) ENGINE=MEMORY";
+
+        if (!p_psDBRunQuery(config->dbh, query)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(newImfiles);
+            return false;
+        }
+    }
+
+    // load the imfiles (files) into the temp table
+    {
+        char *query = "INSERT INTO incoming (exp_name, camera, telescope, file_id, bytes, md5sum, class, class_id, uri) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
+
+        long inserted = p_psDBRunQueryPrepared(config->dbh, newImfiles, query);
+        if (inserted < 0) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(newImfiles);
+            return false;
+        }
+        // sanity check that we actually inserted something
+        if (inserted == 0) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error -- we should have inserted at least one row");
+            psFree(newImfiles);
+            return false;
+        }
+    }
+
+    psFree(newImfiles);
+
+    // copy imfiles (files) from the temp table into summitImfiles
+    {
+        char *query = 
+            "INSERT IGNORE INTO summitImfile" 
+            "   SELECT"
+            "       incoming.exp_name,"
+            "       incoming.camera,"
+            "       incoming.telescope,"
+            "       incoming.file_id,"
+            "       incoming.bytes,"
+            "       incoming.md5sum,"
+            "       incoming.class,"
+            "       incoming.class_id,"
+            "       incoming.uri,"
+            "       NULL"       // epoch
+            "   FROM incoming";
+        if (!p_psDBRunQuery(config->dbh, query)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+    }
+
+    // update summitExp.imfiles (should have been NULL)
+    {
+        char *query = 
+            "UPDATE summitExp"
+            " SET imfiles = (SELECT COUNT(*) FROM summitImfile"
+            "   WHERE"
+            "       exp_name = '%s'"
+            "       AND camera = '%s'"
+            "       AND telescope = '%s'"
+            ")" 
+            " WHERE"
+            "   exp_name = '%s'"
+            "   AND camera = '%s'"
+            "   AND telescope = '%s'"
+            "   AND imfiles IS NULL";
+        if (!p_psDBRunQuery(config->dbh, query, filesetid, camera, telescope, filesetid, camera, telescope)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+
+        // sanity check: we should have updated only one row
+        psU64 affected = psDBAffectedRows(config->dbh);
+        if (psDBAffectedRows(config->dbh) != 1) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "should have affected 1 row but %" PRIu64 " rows were modified", affected);
+            return false;
+        }
+    }
+
+    // add new exps to pzDownloadExp -- must be done before the new exps are
+    // added to summitExp because of the SQL logic
+    {
+        char *query = 
+            "INSERT IGNORE INTO pzDownloadExp" 
+            "   SELECT"
+            "       incoming.exp_name,"
+            "       incoming.camera,"
+            "       incoming.telescope,"
+            "       \"run\","    // state
+            "       NULL"
+            "   FROM incoming"
+            "   GROUP BY"
+            "       incoming.exp_name,"
+            "       incoming.camera,"
+            "       incoming.telescope";
+
+        if (!p_psDBRunQuery(config->dbh, query)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+    }
+
+    // point of no return
+    if (!psDBCommit(config->dbh)) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static psArray *parseFiles(pxConfig *config, const char *str)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(str, NULL);
+
+    // these are constants for all records parsed -- look them up before we do
+    // any work
+    PXOPT_LOOKUP_STR(exp_name, config->args, "-filesetid", true, false);
+    PXOPT_LOOKUP_STR(camera, config->args, "-inst", true, false);
+    PXOPT_LOOKUP_STR(telescope, config->args, "-telescope", true, false);
+
+    // split the string into lines
+    psList *doc = psStringSplit(str, "\n", false);
+
+    psListIterator *lineCursor = psListIteratorAlloc(doc, 0, false);
+
+    psArray *pzPendingImfiles = psArrayAllocEmpty(psListLength(doc));
+    psString line;
+    while ((line = psListGetAndIncrement(lineCursor))) {
+        psTrace("pzgetimfiles", PS_LOG_INFO, "parsing line: %s\n", line);
+
+        // split line into tokens
+        psList *tokens = psStringSplit(line, " ", false);
+
+        // check to see if this line is a comment (or if the first token is
+        // NULL)
+        if (!psListGet(tokens, 0) || *((char *)psListGet(tokens, 0)) == '#') {
+            psFree(tokens);
+            continue;
+        }
+
+        // check that we have the right number of tokens
+        // print "# uri fileid bytes md5sum type \n";
+        if (!psListLength(tokens) > 5) {
+            psError(PS_ERR_UNKNOWN, true, "invalid line format: %s", line);
+            psFree(tokens);
+            psFree(pzPendingImfiles);                
+            psFree(lineCursor);
+            psFree(doc);
+            return false;
+        }
+
+        // find the values of interest
+        psListIterator *tokenCursor = psListIteratorAlloc(tokens, 0, false);
+        char *uri       = psListGetAndIncrement(tokenCursor);
+        char *file_id   = psListGetAndIncrement(tokenCursor); // fileid
+        char *bytes     = psListGetAndIncrement(tokenCursor); // bytes
+        char *md5sum    = psListGetAndIncrement(tokenCursor); // md5sum
+        char *class     = psListGetAndIncrement(tokenCursor); // type
+        char *class_id  = psListGetAndIncrement(tokenCursor); // chipname
+
+        // create a new metadata to represent this line and it's values
+        psMetadata *md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_name", 0, NULL, exp_name)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item exp_name");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, camera)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, telescope)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "file_id", 0, NULL, file_id)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item bytes");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+        if (!psMetadataAddS64(md, PS_LIST_TAIL, "bytes", 0, NULL, (psS64)atoll(bytes))) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item bytes");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "md5sum", 0, NULL, md5sum)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item md5sum");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class", 0, NULL, class)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, class_id)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, uri)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+
+        // must be freed after the new metadata is built -- holds the strings
+        psFree(tokenCursor);
+        psFree(tokens);
+
+        // debugging
+        if (psTraceGetLevel("pzgetimfiles") >= PS_LOG_INFO) {
+            psString doc = psMetadataConfigFormat(md);
+            psTrace("pzgetimfiles", PS_LOG_INFO, "parsed line as:\n %s\n", doc);
+            psFree(doc);
+        }
+
+        psArrayAdd(pzPendingImfiles, 0, md);
+
+        psFree(md);
+    }
+
+    psFree(lineCursor);
+    psFree(doc);
+
+    if (!psArrayLength(pzPendingImfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "string contained no valid rows");
+        psFree(pzPendingImfiles);
+        return false;
+    }
+
+    return pzPendingImfiles;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pzgetimfiles.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pzgetimfiles.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pzgetimfiles.h	(revision 22157)
@@ -0,0 +1,27 @@
+/*
+ * pzgetimfiles.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PZGETIMFILES_H
+#define PZGETIMFILES 1
+
+#include "pxtools.h"
+
+pxConfig *pzgetimfilesConfig(pxConfig *config, int argc, char **argv);
+
+#endif // PZGETIMFILES_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pzgetimfilesConfig.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pzgetimfilesConfig.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pzgetimfilesConfig.c	(revision 22157)
@@ -0,0 +1,89 @@
+/*
+ * pzgetimfilesConfig.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "pxtools.h"
+
+pxConfig *pzgetimfilesConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (! config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration!\n");
+        psFree(config);
+        return NULL;
+    }
+
+    psMetadata *args = psMetadataAlloc();
+    psMetadataAddStr(args , PS_LIST_TAIL, "-uri", 0,
+            "DataStore FileSet URI (required)", NULL);
+    psMetadataAddStr(args , PS_LIST_TAIL, "-filesetid", 0,
+            "FileSet ID (required)", NULL);
+    psMetadataAddStr(args, PS_LIST_TAIL, "-inst",  0,
+            "Camera ID (required)", NULL);
+    psMetadataAddStr(args, PS_LIST_TAIL, "-telescope",  0,
+            "Telescope ID (required)", NULL);
+    psMetadataAddS32(args, PS_LIST_TAIL, "-timeout",  0,
+            "HTTP timeout", 0);
+
+
+    bool status = false;
+    if (!psArgumentParse(args, &argc, argv)
+        || argc != 1
+        || (psMetadataLookupStr(&status, args, "-uri") == NULL)
+        || (psMetadataLookupStr(&status, args, "-filesetid") == NULL)
+        || (psMetadataLookupStr(&status, args, "-inst") == NULL)
+        || (psMetadataLookupStr(&status, args, "-telescope") == NULL)
+    ) {
+        fprintf(stderr, "error parsing arguments\n");
+        printf("\nPan-STARRS Phase Z Get Imfiles Tool\n");
+        printf("Usage: %s -uri <uri> -filesetid <id> -inst <camera> -telescope <telescope [-timeout <n>]\n\n", argv[0]);
+        psArgumentHelp(args);
+        psFree(args);
+        psFree(config);
+        return NULL;
+    }
+
+    config->args = args;
+    // don't free args here as it's silly to increment the ref count then
+    // "free" it
+
+    // define Database handle, if used
+    config->dbh = psMemIncrRefCounter(pmConfigDB(config->modules));
+    if(!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't connect to db\n");
+        psFree(config);
+        return NULL;
+    }
+
+    return config;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pztool.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pztool.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pztool.c	(revision 22157)
@@ -0,0 +1,916 @@
+/*
+ * pztool.c
+ *
+ * Copyright (C) 2006-2008  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+#include "pslib.h"
+#include "pxtools.h"
+#include "pxdata.h"
+#include "pztool.h"
+
+static bool adddatastoreMode(pxConfig *config);
+static bool datastoreMode(pxConfig *config);
+static bool seenMode(pxConfig *config);
+static bool pendingExpMode(pxConfig *config);
+static bool pendingImfileMode(pxConfig *config);
+
+static bool copydoneMode(pxConfig *config);
+static bool copiedMode(pxConfig *config);
+static bool updatecopiedMode(pxConfig *config);
+static bool revertcopiedMode(pxConfig *config);
+
+static bool clearcommonfaultsMode(pxConfig *config);
+static bool advanceMode(pxConfig *config);
+
+static bool copydoneCompleteExp(pxConfig *config, const char *exp_name, const char *camera, const char *telescope);
+static psArray *pzGetPendingCameras(pxConfig *config);
+static psArray *pzArrayZip(psArray *arraySet, psS64 limit);
+static bool pzDownloadExpSetState(pxConfig *config, const char *exp_name, const char *camera, const char *telescope, const char *state);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+                goto FAIL; \
+            } \
+    break;
+
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = pztoolConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(PZTOOL_MODE_ADDDATASTORE, adddatastoreMode);
+        MODECASE(PZTOOL_MODE_DATASTORE, datastoreMode);
+        MODECASE(PZTOOL_MODE_SEEN, seenMode);
+        MODECASE(PZTOOL_MODE_PENDINGEXP, pendingExpMode);
+        MODECASE(PZTOOL_MODE_PENDINGIMFILE, pendingImfileMode);
+        MODECASE(PZTOOL_MODE_COPYDONE, copydoneMode);
+        MODECASE(PZTOOL_MODE_COPIED, copiedMode);
+        MODECASE(PZTOOL_MODE_UPDATECOPIED, updatecopiedMode);
+        MODECASE(PZTOOL_MODE_REVERTCOPIED, revertcopiedMode);
+        MODECASE(PZTOOL_MODE_CLEARCOMMONFAULTS, clearcommonfaultsMode);
+        MODECASE(PZTOOL_MODE_ADVANCE, advanceMode);
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+static bool adddatastoreMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required
+    PXOPT_LOOKUP_STR(camera, config->args, "-inst", true, false);
+    PXOPT_LOOKUP_STR(telescope, config->args, "-telescope", true, false);
+    PXOPT_LOOKUP_STR(uri, config->args, "-uri", true, false);
+
+    if (!pzDataStoreInsert(config->dbh,
+            camera,
+            telescope,
+            uri,
+            NULL  // epoch
+        )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool datastoreMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    if (!p_psDBRunQuery(config->dbh, "SELECT * FROM pzDataStore")) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("pztool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "pzDataStore", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool seenMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where,  "-exp_name",     "exp_name", "==");
+    PXOPT_COPY_STR(config->args, where,  "-inst",         "camera", "==");
+    PXOPT_COPY_STR(config->args, where,  "-telescope",    "telescope", "==");
+    PXOPT_COPY_STR(config->args, where,  "-exp_type",     "exp_type", "==");
+
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = psStringCopy("SELECT * FROM summitExp");
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "summitExp");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("pztool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "summitExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool pendingExpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where,  "-exp_name",     "exp_name", "==");
+    PXOPT_COPY_STR(config->args, where,  "-inst",         "camera", "==");
+    PXOPT_COPY_STR(config->args, where,  "-telescope",    "telescope", "==");
+    PXOPT_COPY_STR(config->args, where,  "-exp_type",     "exp_type", "==");
+
+    PXOPT_LOOKUP_BOOL(desc, config->args, "-desc", false);
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    // XXX leave this query here ?
+    psString query = psStringCopy(
+            "SELECT"
+            "   summitExp.*"
+            " FROM summitExp"
+            " LEFT JOIN pzDownloadExp"
+            "   USING(exp_name, camera, telescope)"
+            " WHERE"
+            "   pzDownloadExp.exp_name IS NULL"
+            "   AND pzDownloadExp.camera IS NULL"
+            "   AND pzDownloadExp.telescope IS NULL"
+            "   AND summitExp.fault = 0"
+        );
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "pzDownloadExp");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    psStringAppend(&query, " ORDER BY summitExp.dateobs");
+    if (desc) {
+        psStringAppend(&query, " DESC");
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("pztool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "pzDownloadExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool pendingImfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where,  "-exp_name",     "exp_name", "==");
+    PXOPT_COPY_STR(config->args, where,  "-inst",         "camera", "==");
+    PXOPT_COPY_STR(config->args, where,  "-telescope",    "telescope", "==");
+    PXOPT_COPY_STR(config->args, where,  "-exp_type",     "exp_type", "==");
+
+    PXOPT_LOOKUP_BOOL(desc, config->args, "-desc", false);
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psArray *cameras = pzGetPendingCameras(config);
+    if (!cameras) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to find any cameras");
+        return false;
+    }
+
+    // array to hold the aggregate results
+    psArray *cameraImfiles = psArrayAlloc(0);
+
+    for (long i = 0; i < psArrayLength(cameras); i++) {
+        psString query = pxDataGet("pztool_pendingimfile.sql");
+        if (!query) {
+            psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+            psFree(cameraImfiles);
+            return false;
+        }
+
+        bool status;
+        psString camera = psMetadataLookupStr(&status, cameras->data[i], "camera");
+        psStringAppend(&query, " WHERE camera = \"%s\"", camera);
+
+        if (psListLength(where->list)) {
+            psString whereClause = psDBGenerateWhereConditionSQL(where, "pzDownloadImfile");
+            psStringAppend(&query, " AND %s", whereClause);
+            psFree(whereClause);
+        }
+
+        psStringAppend(&query, " ORDER BY dateobs");
+        if (desc) {
+            psStringAppend(&query, " DESC");
+        }
+
+        // request the full "limit" from each known camera and throw away any
+        // "extra" rows that we may have after merging the results.  This is
+        // a lot simplier than a complicated scheme (tried that) to attempt to
+        // request on the right number of rows for each camera
+        
+        // treat limit == 0 as "no limit"
+        if (limit) {
+            psString limitString = psDBGenerateLimitSQL(limit);
+            psStringAppend(&query, " %s", limitString);
+            psFree(limitString);
+        }
+
+        if (!p_psDBRunQuery(config->dbh, query)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(query);
+            psFree(cameraImfiles);
+            return false;
+        }
+        psFree(query);
+
+        psArray *result = p_psDBFetchResult(config->dbh);
+        if (!result) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(cameraImfiles);
+            return false;
+        }
+        if (!psArrayLength(result)) {
+            psTrace("pztool", PS_LOG_INFO, "no rows found");
+            psFree(result);
+            continue;
+        }
+
+        // add this query into the array of result set
+        psArrayAdd(cameraImfiles, 0, result);
+        psFree(result);
+    }
+    psFree(where);
+
+    // stitch the arrays of imfiles together
+    psArray *output = pzArrayZip(cameraImfiles, limit);
+    psFree(cameraImfiles);
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "pzDownloadImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool copydoneMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required
+    PXOPT_LOOKUP_STR(exp_name, config->args, "-exp_name", true, false);
+    PXOPT_LOOKUP_STR(camera, config->args, "-inst", true, false);
+    PXOPT_LOOKUP_STR(telescope, config->args, "-telescope", true, false);
+    PXOPT_LOOKUP_STR(class, config->args, "-class", true, false);
+    PXOPT_LOOKUP_STR(class_id, config->args, "-class_id", true, false);
+    PXOPT_LOOKUP_STR(uri, config->args, "-uri", true, false);
+
+    // default values
+    PXOPT_LOOKUP_S16(code, config->args, "-code", false, false);
+
+    // NOTE : the rest of the command-line args are parsed in copydoneCompleteExp
+
+    if (!pzDownloadImfileInsert(config->dbh,
+            exp_name,
+            camera,
+            telescope,
+            class,
+            class_id,
+            uri,
+            code,
+            NULL    // epoch
+    )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // start a transaction so it's all rows or nothing
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!copydoneCompleteExp(config, exp_name, camera, telescope)) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "copydoneCompleteExp() failed");
+        return false;
+    }
+
+    // point of no return
+    if (!psDBCommit(config->dbh)) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool copydoneCompleteExp(pxConfig *config, const char *exp_name, const char *camera, const char *telescope)
+{
+    // THIS FUNCTION MUST BE INVOKED FROM INSIDE A TRANSACTION!!!
+    
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // XXX this is an ugly hack!
+    // we are passing exp level info to a imfile level mode (-copydone)
+    // these options are thrown away unless we just -copydone'd the last imfile
+    // in an exp.  
+ 
+    // optional
+    PXOPT_LOOKUP_STR(workdir, config->args, "-workdir", false, false);
+    PXOPT_LOOKUP_STR(dvodb, config->args, "-dvodb", false, false);
+    PXOPT_LOOKUP_STR(tess_id, config->args, "-tess_id", false, false);
+    PXOPT_LOOKUP_STR(end_stage, config->args, "-end_stage", false, false);
+    PXOPT_LOOKUP_STR(label, config->args, "-label", false, false);
+
+    // find all exposures that have had all of their imfiles downloaded but do
+    // not appear in newExp
+    psString query = pxDataGet("pztool_find_completed_exp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // XXX this is a bit ugly : could just use the PXOPT_COPY_* methods 
+    // and not pass exp_name, camera, or telescope as additional args
+    psMetadata *where = psMetadataAlloc();
+    if (exp_name) {
+        if (!psMetadataAddStr(where, PS_LIST_TAIL, "exp_name", 0, "==", exp_name)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item exp_name");
+            psFree(where);
+            return false;
+        }
+    }
+
+    if (camera) {
+        if (!psMetadataAddStr(where, PS_LIST_TAIL, "camera", 0, "==", camera)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+            psFree(where);
+            return false;
+        }
+    }
+
+    if (telescope) {
+        if (!psMetadataAddStr(where, PS_LIST_TAIL, "telescope", 0, "==", telescope)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+            psFree(where);
+            return false;
+        }
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereSQL(where, NULL);
+        psStringAppend(&query, " %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("pztool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+   for (long i = 0; i < psArrayLength(output); i++) {
+        psMetadata *row = output->data[i];
+
+        pzDownloadExpRow *doneExp = pzDownloadExpObjectFromMetadata(row);
+        if (!doneExp) {
+            psError(PS_ERR_UNKNOWN, false, "pzDownloadExpObjectFromMetadata() failed");
+            psFree(doneExp);
+            psFree(output);
+            return false;
+        }
+
+        if (!newExpInsert(config->dbh,
+                    0x0,                // exp_id
+                    doneExp->exp_name,  // tmp_exp_name
+                    doneExp->camera,    // tmp_camera
+                    doneExp->telescope, // tmp_telescope
+                    "run",              // state
+                    workdir,            // workdir
+                    "dirty",            // workdir state
+                    NULL,               // reduction class
+                    dvodb,              // dvodb
+                    tess_id,            // tess_id
+                    end_stage,          // end_stage
+                    label,
+                    NULL                // epoch
+                )
+        ) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(doneExp);
+            psFree(output);
+            return false;
+        }
+
+        psS64 exp_id = psDBLastInsertID(config->dbh);
+
+        // insert newImfiles
+        {
+            char *query =
+                "INSERT INTO newImfile"
+                "   SElECT"
+                "       %" PRId64 "," // exp_id
+                "       pzDownloadImfile.class_id," // tmp_class_id
+                "       pzDownloadImfile.uri," // uri
+                "       NULL" // epoch
+                "   FROM pzDownloadImfile"
+                "   WHERE"
+                "       pzDownloadImfile.exp_name = '%s'"
+                "       AND pzDownloadImfile.camera = '%s'"
+                "       AND pzDownloadImfile.telescope = '%s'";
+
+            if (!p_psDBRunQuery(config->dbh, query, exp_id, doneExp->exp_name, doneExp->camera, doneExp->telescope)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+                psFree(doneExp);
+                psFree(output);
+                return false;
+            } 
+
+            // sanity check: we should have inserted at least one row
+            psU64 affected = psDBAffectedRows(config->dbh);
+            if (psDBAffectedRows(config->dbh) < 1) {
+                psError(PS_ERR_UNKNOWN, false, "should have affected at least 1 row but %" PRIu64 " rows were modified", affected);
+                psFree(doneExp);
+                psFree(output);
+                return false;
+            }
+        }
+
+        // set pzDownloadExp.state to 'stop'
+        if (!pzDownloadExpSetState(config, doneExp->exp_name, doneExp->camera, doneExp->telescope, "stop")) {
+            psError(PS_ERR_UNKNOWN, false, "failed to change pzDownloadExp.state for %s:%s:%s", doneExp->exp_name, doneExp->camera, doneExp->telescope);
+            psFree(doneExp);
+            psFree(output);
+            return false;
+        }
+
+        psFree(doneExp);
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static psArray *pzGetPendingCameras(pxConfig *config)
+{
+    // get a list of cameras we've seen exps for
+    if (!p_psDBRunQuery(config->dbh, "SELECT DISTINCT camera FROM pzDownloadExp")) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    psArray *cameras = p_psDBFetchResult(config->dbh);
+    if (!cameras) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return NULL;
+    }
+    if (!psArrayLength(cameras)) {
+        psTrace("pztool", PS_LOG_INFO, "no rows found");
+        psFree(cameras);
+        return psArrayAlloc(0);
+    }
+
+    return cameras;
+}
+
+static psArray *pzArrayZip(psArray *arraySet, psS64 limit)
+{
+    // figure out the combined size of all arrays in the set
+    long setSize = 0;
+    for (long i = 0; i < psArrayLength(arraySet); i++) {
+        setSize += psArrayLength(arraySet->data[i]);
+    }
+
+    // treat 0 as "no limit"
+    if (limit == 0) {
+        limit = setSize;
+    }
+
+    psArray *output = psArrayAllocEmpty(limit);
+    // loop over each array in the set forever
+    for (
+            // init
+            long counter = 0,   // the total number of elements zipped so far
+            i = 0,              // which array in the set 
+            index = 0;          // the depth into each array
+            // test
+            (counter < setSize)
+            && (counter < limit)
+            && (i < psArrayLength(arraySet));
+            // incr
+            counter++, ++i,
+            i = i % psArrayLength(arraySet),
+            i % psArrayLength(arraySet) ? : ++index
+        ) {
+
+        psArray *array = arraySet->data[i];
+        // make sure that this array has not run out of elements
+        if (!(index < psArrayLength(array))) {
+            continue;
+        }
+
+        psArrayAdd(output, 0, array->data[index]);
+    }
+
+    return output;
+}
+
+
+static bool copiedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+
+    PXOPT_COPY_STR(config->args, where,  "-exp_name", "exp_name", "==");
+    PXOPT_COPY_STR(config->args, where, "-inst", "inst", "==");
+    PXOPT_COPY_STR(config->args, where, "-telescope", "telescope", "==");
+    PXOPT_COPY_STR(config->args, where,  "-class", "class", "==");
+    PXOPT_COPY_STR(config->args, where,  "-class_id", "class_id", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_BOOL(faulted, config->args, "-faulted", false);
+
+    psString query = psStringCopy("SELECT * FROM pzDownloadImfile");
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "WHERE pzDownloadImfile.fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "WHERE pzDownloadImfile.fault = 0");
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "pzDownloadImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("pztool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "chipPendingImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool updatecopiedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where,  "-exp_name", "exp_name", "==");
+    PXOPT_COPY_STR(config->args, where, "-inst", "inst", "==");
+    PXOPT_COPY_STR(config->args, where, "-telescope", "telescope", "==");
+    PXOPT_COPY_STR(config->args, where,  "-class", "class", "==");
+    PXOPT_COPY_STR(config->args, where,  "-class_id", "class_id", "==");
+
+    PXOPT_LOOKUP_S16(code, config->args, "-code", true, false);
+
+    if (!pxSetFaultCode(config->dbh, "pzDownloadImfile", where, code)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to set set fault flag");
+	psFree (where);
+	return false;
+    }
+    psFree(where);
+
+    return true;
+}
+
+
+static bool revertcopiedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where,  "-exp_name", "exp_name", "==");
+    PXOPT_COPY_STR(config->args, where, "-inst", "inst", "==");
+    PXOPT_COPY_STR(config->args, where, "-telescope", "telescope", "==");
+    PXOPT_COPY_STR(config->args, where,  "-class", "class", "==");
+    PXOPT_COPY_STR(config->args, where,  "-class_id", "class_id", "==");
+
+    psString query = pxDataGet("pztool_revertcopied.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "pzDownloadImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    if (psDBAffectedRows(config->dbh) < 1) {
+        psError(PS_ERR_UNKNOWN, false, "should have affected atleast 1 row");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool clearcommonfaultsMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+{
+    psString query = pxDataGet("pztool_revert_downloadimfile_faults.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+}
+
+{
+    psString query = pxDataGet("pztool_revert_fileset_faults.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+}
+
+    return true;
+}
+
+static bool advanceMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // NOTE : the command-line args are parsed in copydoneCompleteExp
+
+    // start a transaction so it's all rows or nothing
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!copydoneCompleteExp(config, NULL, NULL, NULL)) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "copydoneCompleteExp() failed");
+        return false;
+    }
+
+    // point of no return
+    if (!psDBCommit(config->dbh)) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+
+    return true;
+}
+
+
+static bool pzDownloadExpSetState(pxConfig *config, const char *exp_name, const char *camera, const char *telescope, const char *state)
+{
+    PS_ASSERT_PTR_NON_NULL(state, false);
+
+    // check that state is a valid string value
+    if (!(
+            (strncmp(state, "run", 4) == 0)
+            || (strncmp(state, "stop", 5) == 0)
+            || (strncmp(state, "reg", 4) == 0)
+        )
+    ) {
+        psError(PS_ERR_UNKNOWN, false,
+                "invalid pzDownloadExp state: %s", state);
+        return false;
+    }
+
+    char *query = "UPDATE pzDownloadExp SET state = '%s' WHERE exp_name = '%s' and camera = '%s' and telescope = '%s'";
+    if (!p_psDBRunQuery(config->dbh, query, state, exp_name, camera, telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to change state for %s:%s:%s", exp_name, camera, telescope);
+        return false;
+    }
+
+    return true;
+}
+
+
+#if 0
+static psArray *pzArrayAddArray(psArray *array, psArray *input)
+{
+    for (long i = 0; i < psArrayLength(input); i++) {
+        psArrayAdd(array, psArrayLength(input), input->data[i]);
+    }
+
+    return array;
+}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pztool.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pztool.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pztool.h	(revision 22157)
@@ -0,0 +1,42 @@
+/*
+ * pztool.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PZTOOL_H
+#define PZTOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    PZTOOL_MODE_NONE      = 0x0,
+    PZTOOL_MODE_ADDDATASTORE,
+    PZTOOL_MODE_DATASTORE,
+    PZTOOL_MODE_SEEN,
+    PZTOOL_MODE_PENDINGEXP,
+    PZTOOL_MODE_PENDINGIMFILE,
+    PZTOOL_MODE_COPYDONE,
+    PZTOOL_MODE_COPIED,
+    PZTOOL_MODE_UPDATECOPIED,
+    PZTOOL_MODE_REVERTCOPIED,
+    PZTOOL_MODE_CLEARCOMMONFAULTS,
+    PZTOOL_MODE_ADVANCE
+} pztoolMode;
+
+pxConfig *pztoolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // PZTOOL_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/pztoolConfig.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/pztoolConfig.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/pztoolConfig.c	(revision 22157)
@@ -0,0 +1,175 @@
+/*
+ * pztoolConfig.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "pztool.h"
+
+pxConfig *pztoolConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (! config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration!\n");
+        psFree(config);
+        return NULL;
+    }
+
+    // -adddatastore
+    psMetadata *adddatastoreArgs = psMetadataAlloc();
+    psMetadataAddStr(adddatastoreArgs, PS_LIST_TAIL, "-inst", 0,            "define camera ID", NULL); 
+    psMetadataAddStr(adddatastoreArgs, PS_LIST_TAIL, "-telescope", 0,            "define telescope ID", NULL); 
+    psMetadataAddStr(adddatastoreArgs, PS_LIST_TAIL, "-uri", 0,            "define storage uri", NULL);
+    
+    // -datastore
+    psMetadata *datastoreArgs = psMetadataAlloc();
+    psMetadataAddBool(datastoreArgs, PS_LIST_TAIL, "-simple", 0,            "use the simple output format", false);
+
+    // -seen
+    psMetadata *seenArgs = psMetadataAlloc();
+    psMetadataAddStr(seenArgs, PS_LIST_TAIL, "-exp_name", 0,            "define exposure ID", NULL); 
+    psMetadataAddStr(seenArgs, PS_LIST_TAIL, "-inst", 0,            "define camera ID", NULL); 
+    psMetadataAddStr(seenArgs, PS_LIST_TAIL, "-telescope", 0,            "define telescope ID", NULL); 
+    psMetadataAddStr(seenArgs, PS_LIST_TAIL, "-exp_type", 0,            "define exposure type", NULL); 
+    psMetadataAddBool(seenArgs, PS_LIST_TAIL, "-simple", 0,            "use the simple output format", false);
+    
+    // -pendingexp
+    psMetadata *pendingexpArgs = psMetadataAlloc();
+    psMetadataAddStr(pendingexpArgs, PS_LIST_TAIL, "-exp_name", 0,            "define exposure ID", NULL); 
+    psMetadataAddStr(pendingexpArgs, PS_LIST_TAIL, "-inst", 0,            "define camera ID", NULL); 
+    psMetadataAddStr(pendingexpArgs, PS_LIST_TAIL, "-telescope", 0,            "define telescope ID", NULL); 
+    psMetadataAddStr(pendingexpArgs, PS_LIST_TAIL, "-exp_type", 0,            "define exposure type", NULL); 
+    psMetadataAddBool(pendingexpArgs, PS_LIST_TAIL, "-desc", 0,            "sort ouput in descending format", false);
+    psMetadataAddU64(pendingexpArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(pendingexpArgs, PS_LIST_TAIL, "-simple", 0,            "use the simple output format", false);
+
+    // -pendingimfile
+    psMetadata *pendingimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-exp_name", 0,            "define exposure ID", NULL); 
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-inst", 0,            "define camera ID", NULL); 
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-telescope", 0,            "define telescope ID", NULL); 
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-exp_type", 0,            "define exposure type", NULL); 
+    psMetadataAddBool(pendingimfileArgs, PS_LIST_TAIL, "-desc", 0,            "sort ouput in descending format", false);
+    psMetadataAddU64(pendingimfileArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(pendingimfileArgs, PS_LIST_TAIL, "-simple", 0,            "use the simple output format", false);
+
+    // -copydone
+    psMetadata *copydoneArgs = psMetadataAlloc();
+    psMetadataAddStr(copydoneArgs, PS_LIST_TAIL, "-exp_name", 0,            "define exposure ID (required)", NULL); 
+    psMetadataAddStr(copydoneArgs, PS_LIST_TAIL, "-inst", 0,            "define camera ID (required)", NULL); 
+    psMetadataAddStr(copydoneArgs, PS_LIST_TAIL, "-telescope", 0,            "define telescope ID (required)", NULL); 
+    psMetadataAddStr(copydoneArgs, PS_LIST_TAIL, "-class", 0,            "define class", NULL);
+    psMetadataAddStr(copydoneArgs, PS_LIST_TAIL, "-class_id", 0,            "define class_id", NULL);
+    psMetadataAddStr(copydoneArgs, PS_LIST_TAIL, "-uri", 0,            "define storage uri", NULL);
+    psMetadataAddStr(copydoneArgs, PS_LIST_TAIL, "-workdir",  0,        "define the \"default\" workdir for this exposure", NULL);
+    psMetadataAddStr(copydoneArgs, PS_LIST_TAIL, "-dvodb",  0,        "define the dvodb for the next processing step", NULL);
+    psMetadataAddStr(copydoneArgs, PS_LIST_TAIL, "-tess_id",  0,        "define the tess_id for the next processing step", NULL);
+    psMetadataAddStr(copydoneArgs, PS_LIST_TAIL, "-end_stage",  0,        "define the end goal processing step", NULL);
+    psMetadataAddStr(copydoneArgs, PS_LIST_TAIL, "-label",  0,        "define the label for the chip stage", NULL);
+    psMetadataAddS16(copydoneArgs, PS_LIST_TAIL, "-code",  0,            "set fault code", 0);
+
+    // -copied
+    psMetadata *copiedArgs = psMetadataAlloc();
+    psMetadataAddStr(copiedArgs, PS_LIST_TAIL, "-exp_name", 0,            "define exposure ID", NULL); 
+    psMetadataAddStr(copiedArgs, PS_LIST_TAIL, "-inst", 0,            "define camera ID", NULL); 
+    psMetadataAddStr(copiedArgs, PS_LIST_TAIL, "-telescope", 0,            "define telescope ID", NULL); 
+    psMetadataAddStr(copiedArgs, PS_LIST_TAIL, "-class", 0,            "define class", NULL);
+    psMetadataAddStr(copiedArgs, PS_LIST_TAIL, "-class_id", 0,            "define class_id", NULL);
+    psMetadataAddBool(copiedArgs, PS_LIST_TAIL, "-faulted",  0,            "only return imfiles with a fault status set", false);
+    psMetadataAddU64(copiedArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(copiedArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -updatecopied
+    psMetadata *updatecopiedArgs = psMetadataAlloc();
+    psMetadataAddStr(updatecopiedArgs, PS_LIST_TAIL, "-exp_name", 0,            "define exposure ID (required)", NULL); 
+    psMetadataAddStr(updatecopiedArgs, PS_LIST_TAIL, "-inst", 0,            "define camera ID (required)", NULL); 
+    psMetadataAddStr(updatecopiedArgs, PS_LIST_TAIL, "-telescope", 0,            "define telescope ID (required)", NULL); 
+    psMetadataAddStr(updatecopiedArgs, PS_LIST_TAIL, "-class", 0,            "define class", NULL);
+    psMetadataAddStr(updatecopiedArgs, PS_LIST_TAIL, "-class_id", 0,            "define class_id", NULL);
+    psMetadataAddS16(updatecopiedArgs, PS_LIST_TAIL, "-code",  0,            "search by fault code", 0);
+
+    // -revertcopied
+    psMetadata *revertcopiedArgs = psMetadataAlloc();
+    psMetadataAddStr(revertcopiedArgs, PS_LIST_TAIL, "-exp_name", 0,            "define exposure ID (required)", NULL); 
+    psMetadataAddStr(revertcopiedArgs, PS_LIST_TAIL, "-inst", 0,            "define camera ID (required)", NULL); 
+    psMetadataAddStr(revertcopiedArgs, PS_LIST_TAIL, "-telescope", 0,            "define telescope ID (required)", NULL); 
+    psMetadataAddStr(revertcopiedArgs, PS_LIST_TAIL, "-class", 0,            "define class", NULL);
+    psMetadataAddStr(revertcopiedArgs, PS_LIST_TAIL, "-class_id", 0,            "define class_id", NULL);
+    psMetadataAddS16(revertcopiedArgs, PS_LIST_TAIL, "-code",  0,            "search by fault code", 0);
+
+    // -clearcommonfaults
+    psMetadata *clearcommonfaultsArgs = psMetadataAlloc();
+
+    // -advance
+    psMetadata *advanceArgs = psMetadataAlloc();
+    psMetadataAddStr(advanceArgs, PS_LIST_TAIL, "-workdir",  0,        "define the \"default\" workdir for this exposure", NULL);
+    psMetadataAddStr(advanceArgs, PS_LIST_TAIL, "-dvodb",  0,        "define the dvodb for the next processing step", NULL);
+    psMetadataAddStr(advanceArgs, PS_LIST_TAIL, "-tess_id",  0,        "define the tess_id for the next processing step", NULL);
+    psMetadataAddStr(advanceArgs, PS_LIST_TAIL, "-end_stage",  0,        "define the end goal processing step", NULL);
+    psMetadataAddStr(advanceArgs, PS_LIST_TAIL, "-label",  0,        "define the label for the chip stage", NULL);
+
+    psMetadata *argSets = psMetadataAlloc();
+    psMetadata *modes = psMetadataAlloc();
+
+    PXOPT_ADD_MODE("-adddatastore",    "", PZTOOL_MODE_ADDDATASTORE, adddatastoreArgs);
+    PXOPT_ADD_MODE("-datastore",       "", PZTOOL_MODE_DATASTORE,    datastoreArgs);
+    PXOPT_ADD_MODE("-seen",            "", PZTOOL_MODE_SEEN,         seenArgs);
+    PXOPT_ADD_MODE("-pendingexp",      "", PZTOOL_MODE_PENDINGEXP,   pendingexpArgs);
+    PXOPT_ADD_MODE("-pendingimfile",   "", PZTOOL_MODE_PENDINGIMFILE,pendingimfileArgs);
+    PXOPT_ADD_MODE("-copydone",        "", PZTOOL_MODE_COPYDONE,     copydoneArgs);
+    PXOPT_ADD_MODE("-copied",          "", PZTOOL_MODE_COPIED,      copiedArgs);
+    PXOPT_ADD_MODE("-updatecopied",    "", PZTOOL_MODE_UPDATECOPIED,updatecopiedArgs);
+    PXOPT_ADD_MODE("-revertcopied",    "", PZTOOL_MODE_REVERTCOPIED,revertcopiedArgs);
+    PXOPT_ADD_MODE("-clearcommonfaults","", PZTOOL_MODE_CLEARCOMMONFAULTS,clearcommonfaultsArgs);
+    PXOPT_ADD_MODE("-advance",          "", PZTOOL_MODE_ADVANCE,    advanceArgs);
+
+    if (!pxGetOptions(stderr, argc, argv, config, modes, argSets)) {
+        psError(PS_ERR_UNKNOWN, true, "option parsing failed");
+        psFree(argSets);
+        psFree(modes);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+    psFree(modes);
+
+    // define Database handle, if used
+    // do this last so we don't setup a connection before CLI options are
+    // validated
+    config->dbh = psMemIncrRefCounter(pmConfigDB(config->modules));
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    return config;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/regtool.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/regtool.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/regtool.c	(revision 22157)
@@ -0,0 +1,1078 @@
+/*
+ * regtool.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <math.h>
+
+#include "pxtools.h"
+#include "pxdata.h"
+#include "regtool.h"
+#include "chiptool.h"
+
+// imfile
+static bool pendingimfileMode(pxConfig *config);
+static bool addprocessedimfileMode(pxConfig *config);
+static bool processedimfileMode(pxConfig *config);
+static bool revertprocessedimfileMode(pxConfig *config);
+static bool updateprocessedimfileMode(pxConfig *config);
+// exp
+static bool pendingexpMode(pxConfig *config);
+static bool addprocessedexpMode(pxConfig *config);
+static bool processedexpMode(pxConfig *config);
+static bool revertprocessedexpMode(pxConfig *config);
+static bool updateprocessedexpMode(pxConfig *config);
+static bool cleardupexpMode(pxConfig *config);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = regtoolConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        // imfile
+        MODECASE(REGTOOL_MODE_PENDINGIMFILE,         pendingimfileMode);
+        MODECASE(REGTOOL_MODE_ADDPROCESSEDIMFILE,    addprocessedimfileMode);
+        MODECASE(REGTOOL_MODE_PROCESSEDIMFILE,       processedimfileMode);
+        MODECASE(REGTOOL_MODE_REVERTPROCESSEDIMFILE, revertprocessedimfileMode);
+        MODECASE(REGTOOL_MODE_UPDATEPROCESSEDIMFILE, updateprocessedimfileMode);
+        // exp
+        MODECASE(REGTOOL_MODE_PENDINGEXP,            pendingexpMode);
+        MODECASE(REGTOOL_MODE_ADDPROCESSEDEXP,       addprocessedexpMode);
+        MODECASE(REGTOOL_MODE_PROCESSEDEXP,          processedexpMode);
+        MODECASE(REGTOOL_MODE_REVERTPROCESSEDEXP,    revertprocessedexpMode);
+        MODECASE(REGTOOL_MODE_UPDATEPROCESSEDEXP,    updateprocessedexpMode);
+        MODECASE(REGTOOL_MODE_CLEARDUPEXP,           cleardupexpMode);
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+
+static bool pendingimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    // select newImfiles that:
+    // exp_id is in newExp
+    // don't have their exp_id in rawExp
+    // XXX having the same exp_id in newExp and raw*Exp is probably an error
+    // that should be checked for
+
+    psString query = pxDataGet("regtool_pendingimfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        // XXX PS_EXIT_PROG_ERROR (incorrect SQL) or SYS_ERROR (database comms)
+        psError(PXTOOLS_ERR_PROG, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("regtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negate simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "regPendingImfile", !simple)) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool addprocessedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required
+    PXOPT_LOOKUP_S64(exp_id, config->args, "-exp_id", true, false);
+    PXOPT_LOOKUP_STR(exp_name, config->args, "-exp_name", true, false);
+    PXOPT_LOOKUP_STR(camera, config->args, "-inst", true, false);
+    PXOPT_LOOKUP_STR(telescope, config->args, "-telescope", true, false);
+    PXOPT_LOOKUP_STR(tmp_class_id, config->args, "-tmp_class_id", true, false);
+    PXOPT_LOOKUP_STR(class_id, config->args, "-class_id", true, false);
+    PXOPT_LOOKUP_STR(uri, config->args, "-uri", true, false);
+
+    // optional
+    PXOPT_LOOKUP_STR(exp_type, config->args, "-exp_type", false, false);
+    PXOPT_LOOKUP_STR(filelevel, config->args, "-filelevel", false, false);
+    PXOPT_LOOKUP_STR(filter, config->args, "-filter", false, false);
+    PXOPT_LOOKUP_STR(comment, config->args, "-comment", false, false);
+
+    PXOPT_LOOKUP_F32(airmass, config->args, "-airmass", false, false);
+    PXOPT_LOOKUP_F64(ra, config->args, "-ra", false, false);
+    PXOPT_LOOKUP_F64(decl, config->args, "-decl", false, false);
+    PXOPT_LOOKUP_F32(exp_time, config->args, "-exp_time", false, false);
+    PXOPT_LOOKUP_F32(sat_pixel_frac, config->args, "-sat_pixel_frac", false, false);
+    PXOPT_LOOKUP_F64(bg, config->args, "-bg", false, false);
+    PXOPT_LOOKUP_F64(bg_stdev, config->args, "-bg_stdev", false, false);
+    PXOPT_LOOKUP_F64(bg_mean_stdev, config->args, "-bg_mean_stdev", false, false);
+    PXOPT_LOOKUP_F64(alt, config->args, "-alt", false, false);
+    PXOPT_LOOKUP_F64(az, config->args, "-az", false, false);
+    PXOPT_LOOKUP_F32(ccd_temp, config->args, "-ccd_temp", false, false);
+    PXOPT_LOOKUP_F64(posang, config->args, "-posang", false, false);
+    PXOPT_LOOKUP_F32(m1_x, config->args, "-m1_x", false, false);
+    PXOPT_LOOKUP_F32(m1_y, config->args, "-m1_y", false, false);
+    PXOPT_LOOKUP_F32(m1_z, config->args, "-m1_z", false, false);
+    PXOPT_LOOKUP_F32(m1_tip, config->args, "-m1_tip", false, false);
+    PXOPT_LOOKUP_F32(m1_tilt, config->args, "-m1_tilt", false, false);
+    PXOPT_LOOKUP_F32(m2_x, config->args, "-m2_x", false, false);
+    PXOPT_LOOKUP_F32(m2_y, config->args, "-m2_y", false, false);
+    PXOPT_LOOKUP_F32(m2_z, config->args, "-m2_z", false, false);
+    PXOPT_LOOKUP_F32(m2_tip, config->args, "-m2_tip", false, false);
+    PXOPT_LOOKUP_F32(m2_tilt, config->args, "-m2_tilt", false, false);
+
+    PXOPT_LOOKUP_F32(env_temp, config->args, "-env_temperature", false, false);
+    PXOPT_LOOKUP_F32(env_humid, config->args, "-env_humidity", false, false);
+    PXOPT_LOOKUP_F32(env_wind, config->args, "-env_wind_speed", false, false);
+    PXOPT_LOOKUP_F32(env_dir, config->args, "-env_wind_dir", false, false);
+    PXOPT_LOOKUP_F32(teltemp_m1, config->args, "-teltemp_m1", false, false);
+    PXOPT_LOOKUP_F32(teltemp_m1cell, config->args, "-teltemp_m1cell", false, false);
+    PXOPT_LOOKUP_F32(teltemp_m2, config->args, "-teltemp_m2", false, false);
+    PXOPT_LOOKUP_F32(teltemp_spider, config->args, "-teltemp_spider", false, false);
+    PXOPT_LOOKUP_F32(teltemp_truss, config->args, "-teltemp_truss", false, false);
+    PXOPT_LOOKUP_F32(teltemp_extra, config->args, "-teltemp_extra", false, false);
+    PXOPT_LOOKUP_F32(pon_time, config->args, "-pon_time", false, false);
+    PXOPT_LOOKUP_F64(user_1, config->args, "-user_1", false, false);
+    PXOPT_LOOKUP_F64(user_2, config->args, "-user_2", false, false);
+    PXOPT_LOOKUP_F64(user_3, config->args, "-user_3", false, false);
+    PXOPT_LOOKUP_F64(user_4, config->args, "-user_4", false, false);
+    PXOPT_LOOKUP_F64(user_5, config->args, "-user_5", false, false);
+    PXOPT_LOOKUP_STR(object, config->args, "-object", false, false);
+    PXOPT_LOOKUP_TIME(dateobs, config->args, "-dateobs", false, false);
+    PXOPT_LOOKUP_STR(hostname, config->args, "-hostname", false, false);
+    PXOPT_LOOKUP_S16(code, config->args, "-code", false, false);
+
+    if (!rawImfileInsert(
+        config->dbh,
+        exp_id,
+        exp_name,
+        camera,
+        telescope,
+        dateobs,
+        tmp_class_id,
+        class_id,
+        uri,
+        exp_type,
+        filelevel,
+        filter,
+        comment,
+        airmass,
+        ra,
+        decl,
+        exp_time,
+        sat_pixel_frac,
+        bg,
+        bg_stdev,
+        bg_mean_stdev,
+        alt,
+        az,
+        ccd_temp,
+        posang,
+        m1_x,
+        m1_y,
+        m1_z,
+        m1_tip,
+        m1_tilt,
+        m2_x,
+        m2_y,
+        m2_z,
+        m2_tip,
+        m2_tilt,
+        env_temp,
+        env_humid,
+        env_wind,
+        env_dir,
+        teltemp_m1,
+        teltemp_m1cell,
+        teltemp_m2,
+        teltemp_spider,
+        teltemp_truss,
+        teltemp_extra,
+        pon_time,
+        user_1,
+        user_2,
+        user_3,
+        user_4,
+        user_5,
+        object,
+        hostname,
+        code,
+        NULL
+    )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool processedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where,  "-exp_id", "exp_id", "==");
+    PXOPT_COPY_STR(config->args, where,  "-exp_name", "exp_name", "==");
+    PXOPT_COPY_STR(config->args, where,  "-class_id", "class_id", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(faulted, config->args, "-faulted", false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("regtool_processedimfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        psFree(where);
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "rawImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "AND rawImfile.fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "AND rawImfile.fault = 0");
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("regtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "rawImfile", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool revertprocessedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where,  "-exp_id",       "exp_id", "==");
+    PXOPT_COPY_STR(config->args, where,  "-tmp_class_id", "tmp_class_id", "==");
+    PXOPT_COPY_STR(config->args, where,  "-class_id",     "class_id", "==");
+    PXOPT_COPY_S16(config->args, where,  "-code",         "fault", "==");
+
+    psString query = pxDataGet("regtool_revertprocessedimfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+	psFree(where);
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "rawImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    if (psDBAffectedRows(config->dbh) < 1) {
+        psError(PS_ERR_UNKNOWN, false, "should have affected atleast 1 row");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool updateprocessedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where,  "-exp_id",       "exp_id", "==");
+    PXOPT_COPY_STR(config->args, where,  "-class_id",     "class_id", "==");
+
+    PXOPT_LOOKUP_S16(code, config->args, "-code", true, false);
+
+    if (!pxSetFaultCode(config->dbh, "rawImfile", where, code)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to set set fault flag");
+	psFree (where);
+        return false;
+    }
+    psFree (where);
+
+    return true;
+}
+
+
+static bool pendingexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    // return only exps that:
+    // are not in rawExp
+    // have ALL of their imfiles in rawImfile (by count)
+    // and have no associated imfiles left in newImfile
+    psString query = pxDataGet("regtool_pendingexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // treat limit == 0 as "no limit"
+    psString limitString = NULL;
+    if (limit) {
+        limitString = psDBGenerateLimitSQL(limit);
+        // skip past the "hook" comment
+        psStringPrepend(&limitString, "\n");
+    }
+
+    // 1st arg: where hook, 2nd arg: limit hook
+    if (!p_psDBRunQuery(config->dbh, query, "", limitString ? limitString : "")) {
+        // XXX PS_EXIT_PROG_ERROR (incorrect SQL) or SYS_ERROR (database comms)
+        psError(PXTOOLS_ERR_PROG, false, "database error");
+        psFree(limitString);
+        psFree(query);
+        return false;
+    }
+    psFree(limitString);
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("regtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negate simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "regPendingExp", !simple)) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool addprocessedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // make sure that the exp_id(s) are ready to be updated based on:
+    // exp_id is not in rawExp
+    // exp_id is not in newImfile
+    // that the correct count of imfiles is in rawImfile
+
+    // required
+    PXOPT_LOOKUP_S64(exp_id, config->args, "-exp_id", true, false);
+    PXOPT_LOOKUP_STR(exp_name, config->args, "-exp_name", true, false);
+    PXOPT_LOOKUP_STR(camera, config->args, "-inst", true, false);
+    PXOPT_LOOKUP_STR(telescope, config->args, "-telescope", true, false);
+    PXOPT_LOOKUP_STR(exp_tag, config->args, "-exp_tag", true, false);
+    PXOPT_LOOKUP_STR(filelevel, config->args, "-filelevel", true, false);
+
+    // optional
+    PXOPT_LOOKUP_TIME(dateobs, config->args, "-dateobs", false, false);
+    PXOPT_LOOKUP_STR(exp_type, config->args, "-exp_type", false, false);
+    PXOPT_LOOKUP_STR(workdir, config->args, "-workdir", false, false);
+    PXOPT_LOOKUP_STR(dvodb, config->args, "-dvodb", false, false);
+    PXOPT_LOOKUP_STR(tess_id, config->args, "-tess_id", false, false);
+    PXOPT_LOOKUP_STR(end_stage, config->args, "-end_stage", false, false);
+    PXOPT_LOOKUP_STR(reduction, config->args, "-reduction", false, false);
+    PXOPT_LOOKUP_STR(filter, config->args, "-filter", false, false);
+    PXOPT_LOOKUP_STR(comment, config->args, "-comment", false, false);
+    PXOPT_LOOKUP_F32(airmass, config->args, "-airmass", false, false);
+    PXOPT_LOOKUP_F64(ra, config->args, "-ra", false, false);
+    PXOPT_LOOKUP_F64(decl, config->args, "-decl", false, false);
+    PXOPT_LOOKUP_F32(exp_time, config->args, "-exp_time", false, false);
+    PXOPT_LOOKUP_F32(sat_pixel_frac, config->args, "-sat_pixel_frac", false, false);
+    PXOPT_LOOKUP_F64(bg, config->args, "-bg", false, false);
+    PXOPT_LOOKUP_F64(bg_stdev, config->args, "-bg_stdev", false, false);
+    PXOPT_LOOKUP_F64(bg_mean_stdev, config->args, "-bg_mean_stdev", false, false);
+    PXOPT_LOOKUP_F64(alt, config->args, "-alt", false, false);
+    PXOPT_LOOKUP_F64(az, config->args, "-az", false, false);
+    PXOPT_LOOKUP_F32(ccd_temp, config->args, "-ccd_temp", false, false);
+    PXOPT_LOOKUP_F64(posang, config->args, "-posang", false, false);
+    PXOPT_LOOKUP_F32(m1_x, config->args, "-m1_x", false, false);
+    PXOPT_LOOKUP_F32(m1_y, config->args, "-m1_y", false, false);
+    PXOPT_LOOKUP_F32(m1_z, config->args, "-m1_z", false, false);
+    PXOPT_LOOKUP_F32(m1_tip, config->args, "-m1_tip", false, false);
+    PXOPT_LOOKUP_F32(m1_tilt, config->args, "-m1_tilt", false, false);
+    PXOPT_LOOKUP_F32(m2_x, config->args, "-m2_x", false, false);
+    PXOPT_LOOKUP_F32(m2_y, config->args, "-m2_y", false, false);
+    PXOPT_LOOKUP_F32(m2_z, config->args, "-m2_z", false, false);
+    PXOPT_LOOKUP_F32(m2_tip, config->args, "-m2_tip", false, false);
+    PXOPT_LOOKUP_F32(m2_tilt, config->args, "-m2_tilt", false, false);
+
+    PXOPT_LOOKUP_F32(env_temp, config->args, "-env_temperature", false, false);
+    PXOPT_LOOKUP_F32(env_humid, config->args, "-env_humidity", false, false);
+    PXOPT_LOOKUP_F32(env_wind, config->args, "-env_wind_speed", false, false);
+    PXOPT_LOOKUP_F32(env_dir, config->args, "-env_wind_dir", false, false);
+    PXOPT_LOOKUP_F32(teltemp_m1, config->args, "-teltemp_m1", false, false);
+    PXOPT_LOOKUP_F32(teltemp_m1cell, config->args, "-teltemp_m1cell", false, false);
+    PXOPT_LOOKUP_F32(teltemp_m2, config->args, "-teltemp_m2", false, false);
+    PXOPT_LOOKUP_F32(teltemp_spider, config->args, "-teltemp_spider", false, false);
+    PXOPT_LOOKUP_F32(teltemp_truss, config->args, "-teltemp_truss", false, false);
+    PXOPT_LOOKUP_F32(teltemp_extra, config->args, "-teltemp_extra", false, false);
+    PXOPT_LOOKUP_F32(pon_time, config->args, "-pon_time", false, false);
+    PXOPT_LOOKUP_F64(user_1, config->args, "-user_1", false, false);
+    PXOPT_LOOKUP_F64(user_2, config->args, "-user_2", false, false);
+    PXOPT_LOOKUP_F64(user_3, config->args, "-user_3", false, false);
+    PXOPT_LOOKUP_F64(user_4, config->args, "-user_4", false, false);
+    PXOPT_LOOKUP_F64(user_5, config->args, "-user_5", false, false);
+    PXOPT_LOOKUP_STR(object, config->args, "-object", false, false);
+    PXOPT_LOOKUP_STR(label,  config->args, "-label", false, false);
+    PXOPT_LOOKUP_STR(hostname, config->args, "-hostname", false, false);
+
+    // default
+    PXOPT_LOOKUP_S16(code, config->args, "-code", false, false);
+
+    psString query = pxDataGet("regtool_pendingexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    psString whereClause = NULL;
+    {
+        // build a query to search by exp_id
+        psMetadata *where = psMetadataAlloc();
+        if (!psMetadataAddS64(where, PS_LIST_TAIL, "newExp.exp_id", 0, "==", exp_id)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+            psFree(where);
+            psFree(query);
+            return false;
+        }
+
+        whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psFree(where);
+        if (whereClause) {
+            // skip past comment "hook"
+            psStringPrepend(&whereClause, "\n AND ");
+        }
+    }
+
+    // 1st arg: where hook, 2nd arg: limit hook
+    if (!p_psDBRunQuery(config->dbh, query, whereClause ? whereClause : "", "")) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(whereClause);
+        psFree(query);
+        return false;
+    }
+    psFree(whereClause);
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psError(PS_ERR_UNKNOWN, false, "no pending newExp rows found");
+        psFree(output);
+        return false;
+    }
+    // sanity check that we only got one row
+    if (psArrayLength(output) != 1) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "should have gotten 1 row but %lu rows were returned", psArrayLength(output));
+        psFree(output);
+        return NULL;
+    }
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(output);
+        return false;
+    }
+
+    // insert the exp into rawExp
+    psMetadata *row = output->data[0];
+    // convert metadata into a newExp object
+    psMetadataConfigPrint(stdout, row);
+    newExpRow *newExp = newExpObjectFromMetadata(row);
+    psFree(output);
+    if (!newExp) {
+        psError(PS_ERR_UNKNOWN, false, "this should not happen");
+        return false;
+    }
+
+    // carry through these values
+    // new CLI options overwrite existing values
+    workdir   = workdir   ? workdir   : newExp->workdir;
+    reduction = reduction ? reduction : newExp->reduction;
+    dvodb     = dvodb     ? dvodb     : newExp->dvodb;
+    tess_id   = tess_id   ? tess_id   : newExp->tess_id;
+    end_stage = end_stage ? end_stage : newExp->end_stage;
+    label     = label     ? label     : newExp->label;
+    // don't free newExp until just before we return, or these refs will break
+
+    if (!rawExpInsert(config->dbh,
+        exp_id,
+        exp_name,
+        camera,
+        telescope,
+        dateobs,
+        exp_tag,
+        exp_type,
+        filelevel,
+        workdir,
+        reduction,
+        dvodb,
+        tess_id,
+        end_stage,
+        filter,
+        comment,
+        airmass,
+        ra,
+        decl,
+        exp_time,
+        sat_pixel_frac,
+        bg,
+        bg_stdev,
+        bg_mean_stdev,
+        alt,
+        az,
+        ccd_temp,
+        posang,
+        m1_x,
+        m1_y,
+        m1_z,
+        m1_tip,
+        m1_tilt,
+        m2_x,
+        m2_y,
+        m2_z,
+        m2_tip,
+        m2_tilt,
+        env_temp,
+        env_humid,
+        env_wind,
+        env_dir,
+        teltemp_m1,
+        teltemp_m1cell,
+        teltemp_m2,
+        teltemp_spider,
+        teltemp_truss,
+        teltemp_extra,
+        pon_time,
+        user_1,
+        user_2,
+        user_3,
+        user_4,
+        user_5,
+        object,
+        0.0,
+        hostname,
+        code,
+        NULL
+    )) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+    psFree(newExp);
+        return false;
+    }
+
+    // set the state for the newExp to stop
+    if (!pxnewExpSetState(config, exp_id, "stop")) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "failed to change newExp.state for exp_id: %"PRId64, exp_id);
+    psFree(newExp);
+        return false;
+    }
+
+    // should we stop here and proceed on to the chip stage?
+    // NULL for end_stage means go as far as possible
+    if (end_stage && psStrcasestr(end_stage, "reg")) {
+        // then we are done here
+        if (!psDBCommit(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+
+    psFree(newExp);
+        return true;
+    }
+    // else continue on...
+
+
+    // insert an entry into the chipPendingExp table
+    // this can only be run as the newExp's state has been set to stop
+    if (!pxchipQueueByExpTag(config,
+                exp_id,
+                workdir,
+            label,
+                reduction,
+                NULL, // expgroup
+                dvodb,
+                tess_id,
+                end_stage
+    )) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "failed to queue chipPendingExp");
+    psFree(newExp);
+        return false;
+    }
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+    psFree(newExp);
+        return false;
+    }
+
+    psFree(newExp);
+    return true;
+}
+
+
+static bool processedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psString query = pxDataGet("regtool_processedexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // XX test this out; need to make this consistent with the list in regtoolConfig.c
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where,  "-exp_id", "exp_id", "==");
+    PXOPT_COPY_STR(config->args, where,  "-exp_name", "exp_name", "==");
+    PXOPT_COPY_STR(config->args, where,  "-inst", "camera", "==");
+    PXOPT_COPY_STR(config->args, where,  "-telescope", "telescope", "==");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_begin", "dateobs", ">=");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_end", "dateobs", "<=");
+    PXOPT_COPY_STR(config->args, where,  "-exp_tag", "exp_tag", "==");
+    PXOPT_COPY_STR(config->args, where,  "-exp_type", "exp_type", "==");
+    PXOPT_COPY_STR(config->args, where,  "-filelevel", "filelevel", "==");
+    PXOPT_COPY_STR(config->args, where,  "-reduction", "reduction", "==");
+    PXOPT_COPY_STR(config->args, where,  "-filter", "filter", "==");
+    PXOPT_COPY_F32(config->args, where,  "-airmass_min", "airmass", ">=");
+    PXOPT_COPY_F32(config->args, where,  "-airmass_max", "airmass", "<");
+    PXOPT_COPY_F64(config->args, where,  "-ra_min", "ra", ">=");
+    PXOPT_COPY_F64(config->args, where,  "-ra_max", "ra", "<");
+    PXOPT_COPY_F64(config->args, where,  "-decl_min", "decl", ">=");
+    PXOPT_COPY_F64(config->args, where,  "-decl_max", "decl", "<");
+    PXOPT_COPY_F32(config->args, where,  "-exp_time_min", "exp_time", ">=");
+    PXOPT_COPY_F32(config->args, where,  "-exp_time_max", "exp_time", "<");
+    PXOPT_COPY_F32(config->args, where,  "-sat_pixel_frac_min", "sat_pixel_frac", ">=");
+    PXOPT_COPY_F32(config->args, where,  "-sat_pixel_frac_max", "sat_pixel_frac", "<");
+    PXOPT_COPY_F64(config->args, where,  "-bg_min", "bg", ">=");
+    PXOPT_COPY_F64(config->args, where,  "-bg_max", "bg", "<");
+    PXOPT_COPY_F64(config->args, where,  "-bg_stdev_min", "bg_stdev", ">=");
+    PXOPT_COPY_F64(config->args, where,  "-bg_stdev_max", "bg_stdev", "<");
+    PXOPT_COPY_F64(config->args, where,  "-bg_mean_stdev_min", "bg_mean_stdev", ">=");
+    PXOPT_COPY_F64(config->args, where,  "-bg_mean_stdev_max", "bg_mean_stdev", "<");
+    PXOPT_COPY_F64(config->args, where,  "-alt_min", "alt", ">=");
+    PXOPT_COPY_F64(config->args, where,  "-alt_max", "alt", "<");
+    PXOPT_COPY_F64(config->args, where,  "-az_min", "az", ">=");
+    PXOPT_COPY_F64(config->args, where,  "-az_max", "az", "<");
+    PXOPT_COPY_F64(config->args, where,  "-ccd_temp_min", "ccd_temp", ">=");
+    PXOPT_COPY_F64(config->args, where,  "-ccd_temp_max", "ccd_temp", "<");
+    PXOPT_COPY_F64(config->args, where,  "-posang_min", "posang", ">=");
+    PXOPT_COPY_F64(config->args, where,  "-posang_max", "posang", "<");
+    PXOPT_COPY_STR(config->args, where,  "-object", "object", "==");
+    PXOPT_COPY_F32(config->args, where,  "-solang_min", "solang", ">=");
+    PXOPT_COPY_F32(config->args, where,  "-solang_max", "solang", "<");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_BOOL(faulted, config->args, "-faulted", false);
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "rawExp");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "AND rawExp.fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "AND rawExp.fault = 0");
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("regtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "rawExp", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool revertprocessedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where,  "-exp_id",       "exp_id", "==");
+    PXOPT_COPY_S16(config->args, where,  "-code",         "fault", "==");
+
+    psString query = pxDataGet("regtool_revertprocessedexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+	psFree(where);
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "rawExp");
+        psStringAppend(&query, " AND %s", whereClause);
+	psFree(where);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    if (psDBAffectedRows(config->dbh) < 1) {
+        psError(PS_ERR_UNKNOWN, false, "should have affected atleast 1 row");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool updateprocessedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where,  "-exp_id",       "exp_id", "==");
+
+    PXOPT_LOOKUP_S16(code, config->args, "-code", true, false);
+
+    if (!pxSetFaultCode(config->dbh, "rawExp", where, code)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to set set fault flag");
+	psFree(where);
+        return false;
+    }
+    psFree(where);
+
+    return true;
+}
+
+
+static bool cleardupexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+// create temp table of exp_ids to be removed
+{
+    psString query = pxDataGet("regtool_create_dup_table.sql");
+    if (!query) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+}
+
+// populate that table
+// XXX note that this query currently doesn't not correctly handle the case
+// where there is more than one duplicate
+{
+    psString query = pxDataGet("regtool_populate_dup_table.sql");
+    if (!query) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+}
+
+    if (!p_psDBRunQuery(config->dbh, "DELETE FROM rawImfile USING rawImfile, duplicate WHERE duplicate.exp_id = rawImfile.exp_id")) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!p_psDBRunQuery(config->dbh, "DELETE FROM rawExp USING rawExp, duplicate WHERE duplicate.exp_id = rawExp.exp_id")) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!p_psDBRunQuery(config->dbh, "DELETE FROM newImfile USING newImfile, duplicate WHERE duplicate.exp_id = newImfile.exp_id")) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!p_psDBRunQuery(config->dbh, "DELETE FROM newExp USING newExp, duplicate WHERE duplicate.exp_id = newExp.exp_id")) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/regtool.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/regtool.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/regtool.h	(revision 22157)
@@ -0,0 +1,42 @@
+/*
+ * regtool.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef REGTOOL_H
+#define REGTOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    REGTOOL_MODE_NONE      = 0x0,
+    REGTOOL_MODE_PENDINGIMFILE,
+    REGTOOL_MODE_ADDPROCESSEDIMFILE,
+    REGTOOL_MODE_PROCESSEDIMFILE,
+    REGTOOL_MODE_REVERTPROCESSEDIMFILE,
+    REGTOOL_MODE_UPDATEPROCESSEDIMFILE,
+    REGTOOL_MODE_PENDINGEXP,
+    REGTOOL_MODE_ADDPROCESSEDEXP,
+    REGTOOL_MODE_PROCESSEDEXP,
+    REGTOOL_MODE_REVERTPROCESSEDEXP,
+    REGTOOL_MODE_UPDATEPROCESSEDEXP,
+    REGTOOL_MODE_CLEARDUPEXP,
+} regtoolMode;
+
+pxConfig *regtoolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // REGTOOL_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/regtoolConfig.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/regtoolConfig.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/regtoolConfig.c	(revision 22157)
@@ -0,0 +1,295 @@
+/*
+ * regtoolConfig.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <math.h>
+#include <stdint.h>
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "regtool.h"
+
+#define ADD_OPT(TYPE,TARG,NAME,COMMENT,DEFAULT) psMetadataAdd##TYPE(TARG, PS_LIST_TAIL, NAME, 0, COMMENT, DEFAULT)
+
+pxConfig *regtoolConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (!config->modules) {
+        psError(PXTOOLS_ERR_CONFIG, false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    // -pendingimfile
+    psMetadata *pendingimfileArgs = psMetadataAlloc();
+    ADD_OPT(U64,  pendingimfileArgs, "-limit",     "limit result set to N items",  0);
+    ADD_OPT(Bool, pendingimfileArgs, "-simple",    "use the simple output format", false);
+
+    // -addprocessedimfile
+    psMetadata *addprocessedimfileArgs = psMetadataAlloc();
+    ADD_OPT(S64,  addprocessedimfileArgs, "-exp_id",         "define exposure ID (required)",        0);
+    ADD_OPT(Str,  addprocessedimfileArgs, "-exp_name",       "define the exp_name (required)",         NULL);
+    ADD_OPT(Str,  addprocessedimfileArgs, "-inst",           "define the camera name (required)",     NULL);
+    ADD_OPT(Str,  addprocessedimfileArgs, "-telescope",      "define the telescope name (required)",     NULL);
+    ADD_OPT(Str,  addprocessedimfileArgs, "-tmp_class_id",   "define temp. class ID (required)",     NULL);
+    ADD_OPT(Str,  addprocessedimfileArgs, "-class_id",       "define class ID (required)",         NULL);
+    ADD_OPT(Str,  addprocessedimfileArgs, "-uri",            "define URI (required)",             NULL);
+
+    ADD_OPT(Str,  addprocessedimfileArgs, "-exp_type",       "define exposure type",             NULL);
+    ADD_OPT(Str,  addprocessedimfileArgs, "-filelevel",      "define filelevel",             NULL);
+    ADD_OPT(Str,  addprocessedimfileArgs, "-filter",         "define filter ",                 NULL);
+    ADD_OPT(Str,  addprocessedimfileArgs, "-comment",        "define comment ",             NULL);
+
+    ADD_OPT(F32,  addprocessedimfileArgs, "-airmass",        "define airmass",                 NAN);
+    ADD_OPT(F64,  addprocessedimfileArgs, "-ra",             "define RA",                 NAN);
+    ADD_OPT(F64,  addprocessedimfileArgs, "-decl",           "define DEC",                 NAN);
+    ADD_OPT(F32,  addprocessedimfileArgs, "-exp_time",       "define exposure time",             NAN);
+    ADD_OPT(F32,  addprocessedimfileArgs, "-sat_pixel_frac", "define fraction of saturated pixels",     NAN);
+    ADD_OPT(F64,  addprocessedimfileArgs, "-bg",             "define exposue background",          NAN);
+    ADD_OPT(F64,  addprocessedimfileArgs, "-bg_stdev",       "define exposue background stdev",     NAN);
+    ADD_OPT(F64,  addprocessedimfileArgs, "-bg_mean_stdev",  "define exposue background mean stdev",     NAN);
+    ADD_OPT(F64,  addprocessedimfileArgs, "-alt",            "define altitute",                  NAN);
+    ADD_OPT(F64,  addprocessedimfileArgs, "-az",             "define azimuth",                      NAN);
+    ADD_OPT(F32,  addprocessedimfileArgs, "-ccd_temp",       "define ccd tempature",                NAN);
+    ADD_OPT(F64,  addprocessedimfileArgs, "-posang",         "define rotator position angle",         NAN);
+    ADD_OPT(F32,  addprocessedimfileArgs, "-m1_x",           "define M1 X position",                NAN);
+    ADD_OPT(F32,  addprocessedimfileArgs, "-m1_y",           "define M1 Y position",                NAN);
+    ADD_OPT(F32,  addprocessedimfileArgs, "-m1_z",           "define M1 Z position",                NAN);
+    ADD_OPT(F32,  addprocessedimfileArgs, "-m1_tip",         "define M1 TIP position",                NAN);
+    ADD_OPT(F32,  addprocessedimfileArgs, "-m1_tilt",        "define M1 TILT position",            NAN);
+    ADD_OPT(F32,  addprocessedimfileArgs, "-m2_x",           "define M2 X position",                NAN);
+    ADD_OPT(F32,  addprocessedimfileArgs, "-m2_y",           "define M2 Y position",                NAN);
+    ADD_OPT(F32,  addprocessedimfileArgs, "-m2_z",           "define M2 Z position",                NAN);
+    ADD_OPT(F32,  addprocessedimfileArgs, "-m2_tip",         "define M2 TIP position",                NAN);
+    ADD_OPT(F32,  addprocessedimfileArgs, "-m2_tilt",        "define M2 TILT position",         NAN);
+
+    ADD_OPT(F32,  addprocessedimfileArgs, "-env_temperature","define Environmental Temperature",     NAN);
+    ADD_OPT(F32,  addprocessedimfileArgs, "-env_humidity",   "define Environmental Humidity",         NAN);
+    ADD_OPT(F32,  addprocessedimfileArgs, "-env_wind_speed", "define Environmental Wind Speed",     NAN);
+    ADD_OPT(F32,  addprocessedimfileArgs, "-env_wind_dir",   "define Environmental Wind Direction",     NAN);
+    ADD_OPT(F32,  addprocessedimfileArgs, "-teltemp_m1",     "define Telescope Temperature : M1",     NAN);
+    ADD_OPT(F32,  addprocessedimfileArgs, "-teltemp_m1cell", "define Telescope Temperature : M1 Cell",     NAN);
+    ADD_OPT(F32,  addprocessedimfileArgs, "-teltemp_m2",     "define Telescope Temperature : M2",     NAN);
+    ADD_OPT(F32,  addprocessedimfileArgs, "-teltemp_spider", "define Telescope Temperature : Spider",     NAN);
+    ADD_OPT(F32,  addprocessedimfileArgs, "-teltemp_truss",  "define Telescope Temperature : Truss",     NAN);
+    ADD_OPT(F32,  addprocessedimfileArgs, "-teltemp_extra",  "define Telescope Temperature : Extra",     NAN);
+    ADD_OPT(F32,  addprocessedimfileArgs, "-pon_time",       "define time to last Power On",         NAN);
+    ADD_OPT(F64,  addprocessedimfileArgs, "-user_1",         "define user statistic (1)",         NAN);
+    ADD_OPT(F64,  addprocessedimfileArgs, "-user_2",         "define user statistic (2)",         NAN);
+    ADD_OPT(F64,  addprocessedimfileArgs, "-user_3",         "define user statistic (3)",         NAN);
+    ADD_OPT(F64,  addprocessedimfileArgs, "-user_4",         "define user statistic (4)",         NAN);
+    ADD_OPT(F64,  addprocessedimfileArgs, "-user_5",         "define user statistic (5)",         NAN);
+    ADD_OPT(Str,  addprocessedimfileArgs, "-object",         "define exposure object",             NULL);
+    ADD_OPT(Time, addprocessedimfileArgs, "-dateobs",        "define observation time",         NULL);
+    ADD_OPT(Str,  addprocessedimfileArgs, "-hostname",       "define host name",                NULL);
+    ADD_OPT(S16,  addprocessedimfileArgs, "-code",           "set fault code",                  0);
+
+    // -processedimfile
+    psMetadata *processedimfileArgs = psMetadataAlloc();
+   ADD_OPT(S64,  processedimfileArgs, "-exp_id",    "search by exposure ID",                 0);
+   ADD_OPT(Str,  processedimfileArgs, "-exp_name",  "search by exposure name",               NULL);
+   ADD_OPT(Str,  processedimfileArgs, "-class_id",  "search by class ID",                    NULL);
+   ADD_OPT(U64,  processedimfileArgs, "-limit",     "limit result set to N items",           0);
+   ADD_OPT(Bool, processedimfileArgs, "-faulted",   "only return imfiles with a fault status set", false);
+   ADD_OPT(Bool, processedimfileArgs, "-simple",    "use the simple output format",          false);
+
+    // -revertprocessedimfile
+    psMetadata *revertprocessedimfileArgs = psMetadataAlloc();
+    ADD_OPT(S64, revertprocessedimfileArgs, "-exp_id",        "search by exposure ID (required)", 0);
+    ADD_OPT(Str, revertprocessedimfileArgs, "-tmp_class_id",  "searcy by temp. class ID", NULL);
+    ADD_OPT(Str, revertprocessedimfileArgs, "-class_id",      "search by class ID", NULL);
+    ADD_OPT(S16, revertprocessedimfileArgs, "-code",          "search by fault code", 0);
+
+    // -updateprocessedimfile
+    psMetadata *updateprocessedimfileArgs = psMetadataAlloc();
+    ADD_OPT(S64, updateprocessedimfileArgs, "-exp_id",        "search by exposure ID", 0);
+    ADD_OPT(Str, updateprocessedimfileArgs, "-class_id",      "search by class ID", NULL);
+    ADD_OPT(S16, updateprocessedimfileArgs, "-code",          "set fault code (required)", INT16_MAX);
+
+    // -pendingexp
+    psMetadata *pendingexpArgs = psMetadataAlloc();
+    psMetadataAddU64(pendingexpArgs, PS_LIST_TAIL, "-limit",    0,        "limit result set to N items", 0);
+    psMetadataAddBool(pendingexpArgs, PS_LIST_TAIL, "-simple",  0,        "use the simple output format", false);
+
+    // -addprocessedexp
+    psMetadata *addprocessedexpArgs = psMetadataAlloc();
+    psMetadataAddS64(addprocessedexpArgs, PS_LIST_TAIL, "-exp_id",           0,        "exp_id to operate on (required)", 0);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-exp_name",         0,        "define the exp_name (required)", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-inst",             0,        "define the camera name (required)", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-telescope",        0,        "define the telescope name (required)", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-exp_tag",          0,        "define the external exposure tag name (required)", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-filelevel",        0,        "define the data partitioning level of this file (required)", NULL);
+
+    psMetadataAddTime(addprocessedexpArgs, PS_LIST_TAIL, "-dateobs",         0,        "define observation time", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-exp_type",         0,        "define exposure type", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-workdir",          0,        "define the \"default\" workdir for this exposure", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-dvodb",            0,        "define the dvodb for the next processing step", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-tess_id",          0,        "define the tess_id for the next processing step", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-end_stage",        0,        "define the end goal processing step", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-reduction",        0,        "define the \"default\" reduction class for this exposure", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-filter",           0,        "define filter ", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-comment",          0,        "define comment ", NULL);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-airmass",          0,        "define airmass", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-ra",               0,        "define RA", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-decl",             0,        "define DEC", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-exp_time",         0,        "define exposure time", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-sat_pixel_frac",   0,        "define fraction of saturated pixels", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-bg",               0,        "define exposue background", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-bg_stdev",         0,        "define exposue background stdev", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-bg_mean_stdev",    0,        "define exposue background mean stdev", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-alt",              0,        "define altitute", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-az",               0,        "define azimuth", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-ccd_temp",         0,        "define ccd tempature", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-posang",           0,        "define rotator position angle", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-m1_x",             0,        "define M1 X position", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-m1_y",             0,        "define M1 Y position", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-m1_z",             0,        "define M1 Z position", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-m1_tip",           0,        "define M1 TIP position", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-m1_tilt",          0,        "define M1 TILT position", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-m2_x",             0,        "define M2 X position", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-m2_y",             0,        "define M2 Y position", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-m2_z",             0,        "define M2 Z position", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-m2_tip",           0,        "define M2 TIP position", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-m2_tilt",          0,        "define M2 TILT position", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-env_temperature",  0,        "define Environmental Temperature", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-env_humidity",     0,        "define Environmental Humidity", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-env_wind_speed",   0,        "define Environmental Wind Speed", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-env_wind_dir",     0,        "define Environmental Wind Direction", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-teltemp_m1",       0,        "define Telescope Temperature : M1", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-teltemp_m1cell",   0,        "define Telescope Temperature : M1 Cell", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-teltemp_m2",       0,        "define Telescope Temperature : M2", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-teltemp_spider",   0,        "define Telescope Temperature : Spider", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-teltemp_truss",    0,        "define Telescope Temperature : Truss", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-teltemp_extra",    0,        "define Telescope Temperature : Extra", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-pon_time",         0,        "define time to last Power On", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-user_1",           0,        "define user statistic (1)", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-user_2",           0,        "define user statistic (2)", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-user_3",           0,        "define user statistic (3)", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-user_4",           0,        "define user statistic (4)", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-user_5",           0,        "define user statistic (5)", NAN);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-object",           0,        "define exposure object", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-label",            0,        "define label for chip stage (non-detrend data only)", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-hostname",         0,        "define host name", NULL);
+    psMetadataAddS16(addprocessedexpArgs, PS_LIST_TAIL, "-code",             0,        "set fault code", 0);
+
+    // -processedexp
+    psMetadata *processedexpArgs = psMetadataAlloc();
+    psMetadataAddS64(processedexpArgs,  PS_LIST_TAIL, "-exp_id",        0,            "search by exposure ID", 0);
+    psMetadataAddStr(processedexpArgs,  PS_LIST_TAIL, "-exp_name",      0,            "search by exp_name", NULL);
+    psMetadataAddStr(processedexpArgs,  PS_LIST_TAIL, "-inst",          0,            "search for camera", NULL);
+    psMetadataAddStr(processedexpArgs,  PS_LIST_TAIL, "-telescope",     0,            "search for telescope", NULL);
+    psMetadataAddTime(processedexpArgs, PS_LIST_TAIL, "-dateobs_begin", 0,            "search for exposures by time (>=)", NULL);
+    psMetadataAddTime(processedexpArgs, PS_LIST_TAIL, "-dateobs_end",   0,            "search for exposures by time (<)", NULL);
+    psMetadataAddStr(processedexpArgs,  PS_LIST_TAIL, "-exp_tag",       0,            "search by exp_tag", NULL);
+    psMetadataAddStr(processedexpArgs,  PS_LIST_TAIL, "-exp_type",      0,            "search by exp_type", "object");
+    psMetadataAddStr(processedexpArgs,  PS_LIST_TAIL, "-filelevel",     0,            "search by filelevel", NULL);
+    psMetadataAddStr(processedexpArgs,  PS_LIST_TAIL, "-reduction",     0,            "search by reduction class", NULL);
+    psMetadataAddStr(processedexpArgs,  PS_LIST_TAIL, "-filter",        0,            "search for filter", NULL);
+    psMetadataAddF32(processedexpArgs,  PS_LIST_TAIL, "-airmass_min",   0,            "search by min airmass", NAN);
+    psMetadataAddF32(processedexpArgs,  PS_LIST_TAIL, "-airmass_max",   0,            "search by max airmass", NAN);
+    psMetadataAddF64(processedexpArgs,  PS_LIST_TAIL, "-ra_min",        0,            "search by min", NAN);
+    psMetadataAddF64(processedexpArgs,  PS_LIST_TAIL, "-ra_max",        0,            "search by max", NAN);
+    psMetadataAddF64(processedexpArgs,  PS_LIST_TAIL, "-decl_min",      0,            "search by min", NAN);
+    psMetadataAddF64(processedexpArgs,  PS_LIST_TAIL, "-decl_max",      0,            "search by max", NAN);
+    psMetadataAddF32(processedexpArgs,  PS_LIST_TAIL, "-exp_time_min",  0,            "search by min", NAN);
+    psMetadataAddF32(processedexpArgs,  PS_LIST_TAIL, "-exp_time_max",  0,            "search by max", NAN);
+    psMetadataAddF32(processedexpArgs,  PS_LIST_TAIL, "-sat_pixel_frac_min",  0,      "search by max fraction of saturated pixels", NAN);
+    psMetadataAddF32(processedexpArgs,  PS_LIST_TAIL, "-sat_pixel_frac_max",  0,      "search by min fraction of saturated pixels", NAN);
+    psMetadataAddF64(processedexpArgs,  PS_LIST_TAIL, "-bg_min",        0,            "search by max", NAN);
+    psMetadataAddF64(processedexpArgs,  PS_LIST_TAIL, "-bg_max",        0,            "search by max", NAN);
+    psMetadataAddF64(processedexpArgs,  PS_LIST_TAIL, "-bg_stdev_min",  0,            "search by max", NAN);
+    psMetadataAddF64(processedexpArgs,  PS_LIST_TAIL, "-bg_stdev_max",  0,            "search by max", NAN);
+    psMetadataAddF64(processedexpArgs,  PS_LIST_TAIL, "-bg_mean_stdev_min",  0,       "search by max", NAN);
+    psMetadataAddF64(processedexpArgs,  PS_LIST_TAIL, "-bg_mean_stdev_max",  0,       "search by max", NAN);
+    psMetadataAddF64(processedexpArgs,  PS_LIST_TAIL, "-alt_min",       0,            "search by min", NAN);
+    psMetadataAddF64(processedexpArgs,  PS_LIST_TAIL, "-alt_max",       0,            "search by max", NAN);
+    psMetadataAddF64(processedexpArgs,  PS_LIST_TAIL, "-az_min",        0,            "search by min", NAN);
+    psMetadataAddF64(processedexpArgs,  PS_LIST_TAIL, "-az_max",        0,            "search by max", NAN);
+    psMetadataAddF64(processedexpArgs,  PS_LIST_TAIL, "-ccd_temp_min",  0,            "search by min ccd tempature", NAN);
+    psMetadataAddF64(processedexpArgs,  PS_LIST_TAIL, "-ccd_temp_max",  0,            "search by max ccd tempature", NAN);
+    psMetadataAddF64(processedexpArgs,  PS_LIST_TAIL, "-posang_min",    0,            "search by min rotator position angle", NAN);
+    psMetadataAddF64(processedexpArgs,  PS_LIST_TAIL, "-posang_max",    0,            "search by max rotator position angle", NAN);
+    psMetadataAddStr(processedexpArgs,  PS_LIST_TAIL, "-object",        0,            "search by exposure object", NULL);
+    psMetadataAddF32(processedexpArgs,  PS_LIST_TAIL, "-solang_min",    0,            "define min solar angle", NAN);
+    psMetadataAddF32(processedexpArgs,  PS_LIST_TAIL, "-solang_max",    0,            "define max solar angle", NAN);
+
+    psMetadataAddU64(processedexpArgs,  PS_LIST_TAIL, "-limit",         0,            "limit result set to N items", 0);
+    psMetadataAddBool(processedexpArgs, PS_LIST_TAIL, "-faulted",       0,            "only return imfiles with a fault status set", false);
+    psMetadataAddBool(processedexpArgs, PS_LIST_TAIL, "-simple",        0,            "use the simple output format", false);
+
+    // -revertprocessedexp
+    psMetadata *revertprocessedexpArgs = psMetadataAlloc();
+    psMetadataAddS64(revertprocessedexpArgs, PS_LIST_TAIL, "-exp_id",   0,            "search by exposure ID (required)", 0);
+    psMetadataAddS16(revertprocessedexpArgs, PS_LIST_TAIL, "-code",     0,            "search by fault code", 0);
+
+    // -updatedprocessedexp
+    psMetadata *updatedprocessedexpArgs = psMetadataAlloc();
+    psMetadataAddS64(updatedprocessedexpArgs, PS_LIST_TAIL, "-exp_id",  0,            "search by exposure ID", 0);
+    psMetadataAddS16(updatedprocessedexpArgs, PS_LIST_TAIL, "-code",    0,            "set fault code (required)", INT16_MAX);
+
+    // -cleardupexp
+    psMetadata *cleardupexpArgs = psMetadataAlloc();
+
+    psMetadata *argSets = psMetadataAlloc();
+    psMetadata *modes = psMetadataAlloc();
+
+    PXOPT_ADD_MODE("-pendingimfile",           "", REGTOOL_MODE_PENDINGIMFILE, pendingimfileArgs);
+    PXOPT_ADD_MODE("-addprocessedimfile",      "", REGTOOL_MODE_ADDPROCESSEDIMFILE, addprocessedimfileArgs);
+    PXOPT_ADD_MODE("-processedimfile",         "", REGTOOL_MODE_PROCESSEDIMFILE, processedimfileArgs);
+    PXOPT_ADD_MODE("-revertprocessedimfile",   "", REGTOOL_MODE_REVERTPROCESSEDIMFILE, revertprocessedimfileArgs);
+    PXOPT_ADD_MODE("-updateprocessedimfile",   "", REGTOOL_MODE_UPDATEPROCESSEDIMFILE, updateprocessedimfileArgs);
+    PXOPT_ADD_MODE("-pendingexp",              "", REGTOOL_MODE_PENDINGEXP,pendingexpArgs);
+    PXOPT_ADD_MODE("-addprocessedexp",         "", REGTOOL_MODE_ADDPROCESSEDEXP, addprocessedexpArgs);
+    PXOPT_ADD_MODE("-processedexp",            "", REGTOOL_MODE_PROCESSEDEXP, processedexpArgs);
+    PXOPT_ADD_MODE("-revertprocessedexp",      "", REGTOOL_MODE_REVERTPROCESSEDEXP, revertprocessedexpArgs);
+    PXOPT_ADD_MODE("-updateprocessedexp",      "", REGTOOL_MODE_UPDATEPROCESSEDEXP,      updatedprocessedexpArgs);
+    PXOPT_ADD_MODE("-cleardupexp",             "", REGTOOL_MODE_CLEARDUPEXP,      cleardupexpArgs);
+
+    if (!pxGetOptions(stderr, argc, argv, config, modes, argSets)) {
+        psError(PS_ERR_UNKNOWN, true, "option parsing failed");
+        psFree(argSets);
+        psFree(modes);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+    psFree(modes);
+
+    // define Database handle, if used
+    config->dbh = psMemIncrRefCounter(pmConfigDB(config->modules));
+    if (!config->dbh) {
+        psError(PXTOOLS_ERR_SYS, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    return config;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/stacktool.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/stacktool.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/stacktool.c	(revision 22157)
@@ -0,0 +1,864 @@
+/*
+ * stacktool.c
+ *
+ * Copyright (C) 2007-2008  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVB_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+#include <ippdb.h>
+
+#include "pxtools.h"
+#include "stacktool.h"
+
+static bool definebyqueryMode(pxConfig *config);
+static bool definerunMode(pxConfig *config);
+static bool updaterunMode(pxConfig *config);
+static bool addinputskyfileMode(pxConfig *config);
+static bool inputskyfileMode(pxConfig *config);
+static bool tosumMode(pxConfig *config);
+static bool addsumskyfileMode(pxConfig *config);
+static bool sumskyfileMode(pxConfig *config);
+static bool revertsumskyfileMode(pxConfig *config);
+
+static bool setstackRunState(pxConfig *config, psS64 stack_id, const char *state);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = stacktoolConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(STACKTOOL_MODE_DEFINEBYQUERY,              definebyqueryMode);
+        MODECASE(STACKTOOL_MODE_DEFINERUN,          definerunMode);
+        MODECASE(STACKTOOL_MODE_UPDATERUN,          updaterunMode);
+        MODECASE(STACKTOOL_MODE_ADDINPUTSKYFILE,    addinputskyfileMode);
+        MODECASE(STACKTOOL_MODE_INPUTSKYFILE,       inputskyfileMode);
+        MODECASE(STACKTOOL_MODE_TOSUM,              tosumMode);
+        MODECASE(STACKTOOL_MODE_ADDSUMSKYFILE,      addsumskyfileMode);
+        MODECASE(STACKTOOL_MODE_SUMSKYFILE,         sumskyfileMode);
+        MODECASE(STACKTOOL_MODE_REVERTSUMSKYFILE,   revertsumskyfileMode);
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+
+static bool definebyqueryMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required options
+    PXOPT_LOOKUP_STR(workdir, config->args, "-workdir", true, false);
+
+    psMetadata *whereRE = psMetadataAlloc(); // rawExp where
+    psMetadata *whereWSF = psMetadataAlloc(); // warpSkyfile where
+    // map -inst -> camera, etc
+    PXOPT_COPY_STR(config->args, whereRE, "-select_inst", "camera", "==");
+    PXOPT_COPY_STR(config->args, whereRE, "-select_telescope", "telescope", "==");
+    PXOPT_COPY_STR(config->args, whereRE, "-select_filter", "filter", "==");
+    PXOPT_COPY_STR(config->args, whereRE, "-select_uri", "uri", "==");
+    PXOPT_COPY_TIME(config->args, whereRE, "-select_dateobs_begin", "dateobs", ">=");
+    PXOPT_COPY_TIME(config->args, whereRE, "-select_dateobs_end", "dateobs", "<=");
+    PXOPT_COPY_F32(config->args, whereRE, "-select_airmass_min", "airmass", ">=");
+    PXOPT_COPY_F32(config->args, whereRE, "-select_airmass_max", "airmass", "<=");
+    PXOPT_COPY_F32(config->args, whereRE, "-select_sat_pixel_frac_max", "sat_pixel_frac", "<=");
+    PXOPT_COPY_F32(config->args, whereRE, "-select_exp_time_min", "exp_time", ">=");
+    PXOPT_COPY_F32(config->args, whereRE, "-select_exp_time_max", "exp_time", "<=");
+    PXOPT_COPY_F32(config->args, whereRE, "-select_ccd_temp_min", "ccd_temp", ">=");
+    PXOPT_COPY_F32(config->args, whereRE, "-select_ccd_temp_max", "ccd_temp", "<=");
+    PXOPT_COPY_F32(config->args, whereRE, "-select_posang_min", "posang", ">=");
+    PXOPT_COPY_F32(config->args, whereRE, "-select_posang_max", "posang", "<=");
+    PXOPT_COPY_F32(config->args, whereRE, "-select_solang_min", "solang", ">=");
+    PXOPT_COPY_F32(config->args, whereRE, "-select_solang_max", "solang", "<=");
+    PXOPT_COPY_STR(config->args, whereWSF, "-select_skycell_id", "skycell_id", "==");
+    PXOPT_LOOKUP_BOOL(randomSubset, config->args, "-random_subset", false);
+    PXOPT_LOOKUP_S32(randomLimit, config->args, "-random_limit", false, false);
+
+    if (!psListLength(whereRE->list) && !psListLength(whereWSF->list) &&
+        !psMetadataLookupBool(NULL, config->args, "-all")) {
+        psFree(whereRE);
+        psFree(whereWSF);
+        psError(PXTOOLS_ERR_DATA, false, "search parameters are required");
+        return false;
+    }
+
+    // default
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_TIME(registered, config->args, "-registered", false, false);
+    PXOPT_COPY_STR(config->args, whereRE, "-select_exp_type", "exp_type", "==");
+    PXOPT_COPY_F32(config->args, whereWSF, "-select_good_frac_min", "good_frac", ">=");
+
+    psString query = pxDataGet("stacktool_find_complete_warps.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (whereRE) {
+        psString whereClause = psDBGenerateWhereConditionSQL(whereRE, "rawExp");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(whereRE);
+
+    if (whereWSF) {
+        psString whereClause = psDBGenerateWhereConditionSQL(whereWSF, "warpSkyfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(whereWSF);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("stacktool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // valid_warpids should be NULL unless randomSubset is specified
+    psVector *valid_warpids = NULL;
+    if (randomSubset && (randomLimit < psArrayLength(output))) {
+        // loop over the array of metadata and figure out which warp_id are in
+        // the result set
+        psArray *warps = psArrayAlloc(0);
+        for (long i = 0; i < psArrayLength(output); i++) {
+//            psMetadata *md = output->data[i];
+
+            // pull out the warp_id
+            bool status = false;
+//            psS64 warp_id = psMetadataLookupS64(&status, md, "warp_id");
+            if (!status) {
+                psError(PS_ERR_UNKNOWN, false, "failed to lookup warp_id");
+                psFree(output);
+                psFree(warps);
+                return NULL;
+            }
+
+//            psArrayAdd(warps, 0, warp_id);
+        }
+
+        // generate a random-valued vector, return an index sorted by the
+        // random values
+        psVector *randomVector = psVectorAlloc(psArrayLength(output), PS_TYPE_F32);
+        psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, 0);
+        for (int i = 0; i < randomVector->n; i++) {
+            randomVector->data.F32[i] = psRandomUniform(rng);
+        }
+        psVector *indexVector = psVectorSortIndex(NULL, randomVector);
+        psFree(randomVector);
+
+        // accept for first n of the sequence
+        valid_warpids = psVectorAlloc(randomLimit, PS_TYPE_S64);
+        for (int i = 0; i < randomLimit; i++ ){
+//            int j = indexVector->data.S32[i];
+//            valid_warpids->data[i] = psMemIncrRefCounter (warps->data[j]);
+        }
+        psFree(indexVector);
+        psFree(warps);
+    }
+
+
+    psHash *stacks = psHashAlloc(psArrayLength(output));
+
+    // loop over the array of metadata and build a hash keyed by tess_id &
+    // skycell_id
+    for (long i = 0; i < psArrayLength(output); i++) {
+        psMetadata *md = output->data[i];
+
+        bool status;
+        if (valid_warpids) {
+//            psS64 warp_id = psMetadataLookupS64(&status, md, "warp_id");
+            if (!status) {
+                psError(PS_ERR_UNKNOWN, false, "failed to lookup warp_id");
+                psFree(valid_warpids);
+                psFree(output);
+                return NULL;
+            }
+
+            // is this a warp_id that should be left out of the stack set(s)?
+//            for (long i = 0; i < psArrayLength(valid_warpids); i++) {
+//                if (valid_warpids->data[i] == warp_id) {
+//                    continue;
+//                }
+//            }
+        }
+
+        // pull out the skycell_id, & tess_id
+        psString skycell_id = psMetadataLookupStr(&status, md, "skycell_id");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup skycell_id");
+            psFree(valid_warpids);
+            psFree(output);
+            return NULL;
+        }
+
+        psString tess_id = psMetadataLookupStr(&status, md, "tess_id");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup tess_id");
+            psFree(valid_warpids);
+            psFree(output);
+            return NULL;
+        }
+
+        // group the warps by skycell_id & tess_id
+        psString key = NULL;
+        psStringAppend(&key, "%s:%s", skycell_id, tess_id);
+
+        // check to see if the hash key already exists
+        psArray *col = psHashLookup(stacks, key);
+        if (!col) {
+            // if it doesn't, create an new psArray for this stack
+            col = psArrayAllocEmpty(0);
+            psHashAdd(stacks, key, col);
+        }
+        // add this warp to the stack for this key
+        psArrayAdd(col, 0, md);
+        psFree(key);
+
+    }
+    psFree(valid_warpids);
+    psFree(output);
+
+    psArray *grouped = psHashToArray(stacks);
+    psFree(stacks);
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // loop over groups
+    for (long i = 0; i < psArrayLength(grouped); i++) {
+        psArray *col = grouped->data[i];
+
+        // pull the skycell_id & tess_id from the first warp in the stack
+        bool status;
+        psString skycell_id = psMetadataLookupStr(&status, col->data[0], "skycell_id");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup skycell_id");
+            psFree(grouped);
+            return NULL;
+        }
+
+        psString tess_id = psMetadataLookupStr(&status, col->data[0], "tess_id");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup tess_id");
+            psFree(grouped);
+            return NULL;
+        }
+
+        // create a new stackRun for the group
+        stackRunRow *run = stackRunRowAlloc(
+            0,          // ID
+            "run",      // state
+            workdir,
+            NULL,       // dvodb
+            registered,
+            skycell_id,
+            tess_id
+        );
+        if (!stackRunInsertObject(config->dbh, run)) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(run);
+            psFree(grouped);
+            return false;
+        }
+
+        // figure out the new stack_id
+        psS64 stack_id = psDBLastInsertID(config->dbh);
+        run->stack_id = stack_id;
+
+        if (!stackRunPrintObject(stdout, run, !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print object");
+            psFree(run);
+            psFree(grouped);
+            return false;
+        }
+        psFree(run);
+
+        // loop over this stack and add each warp to the stackRun
+        for (long j = 0; j < psArrayLength(col); j++) {
+            bool status;
+            psS64 warp_id = psMetadataLookupS64(&status, col->data[j], "warp_id");
+            if (!status) {
+                if (!psDBRollback(config->dbh)) {
+                    psError(PS_ERR_UNKNOWN, false, "database error");
+                }
+                psError(PS_ERR_UNKNOWN, false, "failed to lookup warp_id");
+                psFree(grouped);
+                return NULL;
+            }
+
+            if (!stackInputSkyfileInsert(config->dbh,
+                stack_id,
+                warp_id
+            )) {
+                if (!psDBRollback(config->dbh)) {
+                    psError(PS_ERR_UNKNOWN, false, "database error");
+                }
+                psError(PS_ERR_UNKNOWN, false, "database error");
+                psFree(grouped);
+                return false;
+            }
+
+        }
+
+    }
+
+    // point of no return
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    psFree(grouped);
+
+    return true;
+}
+
+
+static bool definerunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required options
+    PXOPT_LOOKUP_STR(workdir, config->args, "-workdir", true, false);
+    PXOPT_LOOKUP_STR(skycell_id, config->args, "-skycell_id", true, false);
+    PXOPT_LOOKUP_STR(tess_id, config->args, "-tess_id", true, false);
+
+    // default
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_TIME(registered, config->args, "-registered", false, false);
+
+    // we have to support multipe exp_ids
+    psMetadataItem *warp_ids = psMetadataLookup(config->args, "-warp_id");
+    if (!warp_ids) {
+        // this shouldn't actually happen when using psArgs
+        psError(PS_ERR_UNKNOWN, true, "-warp_id is required");
+        return false;
+    }
+
+    stackRunRow *run = stackRunRowAlloc(
+            0,          // ID
+            "run",      // state
+            workdir,
+            NULL,       // dvodb
+            registered,
+            skycell_id,
+            tess_id
+    );
+    if (!run) {
+        psError(PS_ERR_UNKNOWN, false, "failed to alloc stackRun object");
+        return true;
+    }
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!stackRunInsertObject(config->dbh, run)) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(run);
+        return true;
+    }
+
+    // get the assigned warp_id
+    run->stack_id = psDBLastInsertID(config->dbh);
+
+    // insert the stackInputSkyfile rows
+    psListIterator *iter = psListIteratorAlloc(warp_ids->data.list, 0, false);
+    psMetadataItem *item = NULL;
+    while ((item = psListGetAndIncrement(iter))) {
+        // if the value is NULL this is probably the first pass through the
+        // loop and -warp_id was not specified at all
+        if (!item->data.V) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -warp_id");
+            return false;
+        }
+        if (!stackInputSkyfileInsert(config->dbh, run->stack_id, (psS64)atoll((char *)item->data.V))) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to insert stackInputSkyfile rows");
+            return false;
+        }
+    }
+    psFree(iter);
+
+    // point of no return
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!stackRunPrintObject(stdout, run, !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print object");
+            psFree(run);
+            return false;
+    }
+
+    psFree(run);
+
+    return true;
+}
+
+
+static bool updaterunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_S64(stack_id, config->args, "-stack_id", true, false);
+    PXOPT_LOOKUP_STR(state, config->args, "-state", true, false);
+
+    if (state) {
+        // set detRun.state to state
+        return setstackRunState(config, stack_id, state);
+    }
+
+    return true;
+}
+
+
+static bool addinputskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_S64(stack_id, config->args, "-stack_id", true, false);
+    PXOPT_LOOKUP_S64(warp_id, config->args, "-warp_id", true, false);
+
+    // XXX need to validate the warp_id here
+    // XXX instead of validiting it here we should just use forgein key
+    // constrants
+    if (!stackInputSkyfileInsert(config->dbh,
+            stack_id,
+            warp_id
+        )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool inputskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-warp_id", "warp_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-stack_id", "stack_id", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("stacktool_inputskyfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "stackInputSkyfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("stacktool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "stackInputSkyfile", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool tosumMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-warp_id", "warp_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-stack_id", "stack_id", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("stacktool_tosum.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "stackSumSkyfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("stacktool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "stackSumSkyfile", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool addsumskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required
+    PXOPT_LOOKUP_S64(stack_id, config->args, "-stack_id", true, false);
+
+    // optional
+    PXOPT_LOOKUP_STR(uri, config->args, "-uri", false, false);
+    PXOPT_LOOKUP_STR(path_base, config->args, "-path_base", false, false);
+    PXOPT_LOOKUP_F64(bg, config->args, "-bg", false, false);
+    PXOPT_LOOKUP_F64(bg_stdev, config->args, "-bg_stdev", false, false);
+    PXOPT_LOOKUP_F32(dtime_stack, config->args, "-dtime_stack", false, false);
+    PXOPT_LOOKUP_STR(hostname, config->args, "-hostname", false, false);
+    PXOPT_LOOKUP_F32(good_frac, config->args, "-good_frac", false, false);
+
+    // default values
+    PXOPT_LOOKUP_S16(code, config->args, "-code", false, false);
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // XXX need to validate the stack_id here
+    // XXX instead of validiting it here we should just use forgein key
+    // constrants
+    if (!stackSumSkyfileInsert(config->dbh,
+                               stack_id,
+                               uri,
+                               path_base,
+                               bg,
+                               bg_stdev,
+                               dtime_stack,
+                               hostname,
+                               good_frac,
+                               code
+          )) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!setstackRunState(config, stack_id, "stop")) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "failed to change stackRun's state");
+        return false;
+    }
+
+    // point of no return
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool sumskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-warp_id", "warp_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-stack_id", "stack_id", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("stacktool_sumskyfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "stackSumSkyfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("stacktool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        if (!ippdbPrintMetadatas(stdout, output, "stackSumSkyfile", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool revertsumskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-stack_id", "stack_id", "==");
+    PXOPT_COPY_S16(config->args, where, "-code", "fault", "==");
+
+    psString query = pxDataGet("stacktool_revertsumskyfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "stackSumSkyfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    if (psDBAffectedRows(config->dbh) < 1) {
+        psError(PS_ERR_UNKNOWN, false, "should have affected atleast 1 row");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool setstackRunState(pxConfig *config, psS64 stack_id, const char *state)
+{
+    PS_ASSERT_PTR_NON_NULL(state, false);
+
+    // check that state is a valid string value
+    if (!(
+            (strncmp(state, "run", 4) == 0)
+            || (strncmp(state, "stop", 5) == 0)
+            || (strncmp(state, "reg", 4) == 0)
+        )
+    ) {
+        psError(PS_ERR_UNKNOWN, false,
+                "invalid warpRun state: %s", state);
+        return false;
+    }
+
+    char *query = "UPDATE stackRun SET state = '%s' WHERE stack_id = %"PRId64;
+    if (!p_psDBRunQuery(config->dbh, query, state, stack_id)) {
+        psError(PS_ERR_UNKNOWN, false,
+                "failed to change state for stack_id %"PRId64, stack_id);
+        return false;
+    }
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/stacktool.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/stacktool.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/stacktool.h	(revision 22157)
@@ -0,0 +1,40 @@
+/*
+ * stacktool.h
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef STACKTOOL_H
+#define STACKTOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    STACKTOOL_MODE_NONE           = 0x0,
+    STACKTOOL_MODE_DEFINEBYQUERY,
+    STACKTOOL_MODE_DEFINERUN,
+    STACKTOOL_MODE_UPDATERUN,
+    STACKTOOL_MODE_ADDINPUTSKYFILE,
+    STACKTOOL_MODE_INPUTSKYFILE,
+    STACKTOOL_MODE_TOSUM,
+    STACKTOOL_MODE_ADDSUMSKYFILE,
+    STACKTOOL_MODE_SUMSKYFILE,
+    STACKTOOL_MODE_REVERTSUMSKYFILE,
+} stacktoolMode;
+
+pxConfig *stacktoolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // STACKTOOL_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/stacktoolConfig.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/stacktoolConfig.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/stacktoolConfig.c	(revision 22157)
@@ -0,0 +1,170 @@
+/*
+ * stacktoolConfig.c
+ *
+ * Copyright (C) 2007-2008  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "stacktool.h"
+
+pxConfig *stacktoolConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (!config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    psTime *now = psTimeGetNow(PS_TIME_TAI);
+
+    // -definebyquery
+    psMetadata *definebyqueryArgs = psMetadataAlloc();
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-workdir", 0,            "define workdir (required)", NULL);
+    psMetadataAddTime(definebyqueryArgs, PS_LIST_TAIL, "-registered",  0,            "time detrend run was registered", now);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-select_skycell_id",  0,            "search for skycell_id", NULL);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-select_good_frac_min",  0,            "define min good_frac", 0.0);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-select_exp_type",  0,            "search for exp_type", "object");
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-select_inst",  0,            "search for camera", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-select_telescope",  0,            "search for telescope", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-select_filter",  0,            "search for filter", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-select_uri",  0,            "search for uri", NULL);
+    psMetadataAddTime(definebyqueryArgs, PS_LIST_TAIL, "-select_dateobs_begin", 0,            "search for exposures by time (>=)", NULL);
+    psMetadataAddTime(definebyqueryArgs, PS_LIST_TAIL, "-select_dateobs_end", 0,            "search for exposures by time (<)", NULL);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-select_airmass_min",  0,            "define min airmass", NAN);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-select_airmass_max",  0,            "define max airmass", NAN);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-select_sat_pixel_frac_max",  0,            "define max fraction of saturated pixels", NAN);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-select_exp_time_min",  0,            "define min exposure time", NAN);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-select_exp_time_max",  0,            "define max exposure time", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-select_ccd_temp_min",  0,            "define min ccd tempature", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-select_ccd_temp_max",  0,            "define max ccd tempature", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-select_posang_min",  0,            "define min rotator position angle", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-select_posang_max",  0,            "define max rotator position angle", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-select_solang_min",  0,            "define min solar angle", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-select_solang_max",  0,            "define max solar angle", NAN);
+    psMetadataAddBool(definebyqueryArgs, PS_LIST_TAIL, "-random_subset",  0,            "use a random subset of elements", false);
+    psMetadataAddS32(definebyqueryArgs, PS_LIST_TAIL, "-random_limit",  0,            "use this number of random elements", 20);
+    psMetadataAddBool(definebyqueryArgs, PS_LIST_TAIL, "-all",  0,            "allow everything to be queued without search terms", false);
+    psMetadataAddBool(definebyqueryArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -definerun
+    psMetadata *definerunArgs = psMetadataAlloc();
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-workdir", 0,            "define workdir (required)", NULL);
+    psMetadataAddTime(definerunArgs, PS_LIST_TAIL, "-registered",  0,            "time detrend run was registered", now);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-skycell_id",  0,            "define skycell ID (required)", NULL);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-tess_id",  0,            "define tessellation ID (required)", NULL);
+    psMetadataAddS64(definerunArgs, PS_LIST_TAIL, "-warp_id",             PS_META_DUPLICATE_OK,             "include this warp ID (multiple OK, required)", 0);
+    psMetadataAddBool(definerunArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -updaterun
+    psMetadata *updaterunArgs = psMetadataAlloc();
+    psMetadataAddS64(updaterunArgs, PS_LIST_TAIL, "-stack_id", 0,            "define stack ID (required)", 0);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-state", 0,            "set state (required)", NULL);
+
+    // -addinputskyfile
+    psMetadata *addinputskyfileArgs = psMetadataAlloc();
+    psMetadataAddS64(addinputskyfileArgs, PS_LIST_TAIL, "-stack_id", 0,            "define stack ID (required)", 0);
+    psMetadataAddS64(addinputskyfileArgs, PS_LIST_TAIL, "-warp_id", 0,            "define warp ID (required)", 0);
+
+    // -inputskyfile
+    psMetadata *inputskyfileArgs = psMetadataAlloc();
+    psMetadataAddS64(inputskyfileArgs, PS_LIST_TAIL, "-stack_id", 0,            "search by stack ID", 0);
+    psMetadataAddS64(inputskyfileArgs, PS_LIST_TAIL, "-warp_id", 0,            "search by warp ID", 0);
+    psMetadataAddU64(inputskyfileArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(inputskyfileArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -tosum
+    psMetadata *tosumArgs = psMetadataAlloc();
+    psMetadataAddS64(tosumArgs, PS_LIST_TAIL, "-stack_id", 0,            "search by stack ID", 0);
+    psMetadataAddS64(tosumArgs, PS_LIST_TAIL, "-warp_id", 0,            "search by warp ID", 0);
+    psMetadataAddU64(tosumArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(tosumArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -addsumskyfile
+    psMetadata *addsumskyfileArgs = psMetadataAlloc();
+    psMetadataAddS64(addsumskyfileArgs, PS_LIST_TAIL, "-stack_id", 0,            "define stack ID (required)", 0);
+    psMetadataAddStr(addsumskyfileArgs, PS_LIST_TAIL, "-uri", 0,            "define URI of file", 0);
+    psMetadataAddStr(addsumskyfileArgs, PS_LIST_TAIL, "-path_base", 0,            "define base output location", 0);
+    psMetadataAddF64(addsumskyfileArgs, PS_LIST_TAIL, "-bg",  0,            "define exposue background", NAN);
+    psMetadataAddF64(addsumskyfileArgs, PS_LIST_TAIL, "-bg_stdev",  0,            "define exposue background mean stdev", NAN);
+    psMetadataAddF32(addsumskyfileArgs, PS_LIST_TAIL, "-dtime_stack",  0,            "define elapsed processing time", NAN);
+    psMetadataAddStr(addsumskyfileArgs, PS_LIST_TAIL, "-hostname", 0,            "define hostname", 0);
+    psMetadataAddF32(addsumskyfileArgs, PS_LIST_TAIL, "-good_frac",  0,            "define %% of good pixels", NAN);
+    psMetadataAddS16(addsumskyfileArgs, PS_LIST_TAIL, "-code",  0,            "set fault code", 0);
+
+    // -sumskyfile
+    psMetadata *sumskyfileArgs= psMetadataAlloc();
+    psMetadataAddS64(sumskyfileArgs, PS_LIST_TAIL, "-stack_id", 0,            "search by stack ID", 0);
+    psMetadataAddS64(sumskyfileArgs, PS_LIST_TAIL, "-warp_id", 0,            "search by warp ID", 0);
+    psMetadataAddU64(sumskyfileArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(sumskyfileArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -revertsumskyfile
+    psMetadata *revertsumskyfileArgs= psMetadataAlloc();
+    psMetadataAddS64(revertsumskyfileArgs, PS_LIST_TAIL, "-stack_id", 0,            "search by stack ID (required)", 0);
+    psMetadataAddS16(revertsumskyfileArgs, PS_LIST_TAIL, "-code",  0,            "search by fault code", 0);
+
+    psFree(now);
+
+    psMetadata *argSets = psMetadataAlloc();
+    psMetadata *modes   = psMetadataAlloc();
+
+    PXOPT_ADD_MODE("-definebyquery", "Define a new stackRun by searching for warp IDs", STACKTOOL_MODE_DEFINEBYQUERY,  definebyqueryArgs);
+    PXOPT_ADD_MODE("-definerun",       "", STACKTOOL_MODE_DEFINERUN,      definerunArgs);
+    PXOPT_ADD_MODE("-updaterun",       "", STACKTOOL_MODE_UPDATERUN,      updaterunArgs);
+    PXOPT_ADD_MODE("-addinputskyfile", "", STACKTOOL_MODE_ADDINPUTSKYFILE, addinputskyfileArgs);
+    PXOPT_ADD_MODE("-inputskyfile",    "", STACKTOOL_MODE_INPUTSKYFILE,    inputskyfileArgs);
+    PXOPT_ADD_MODE("-tosum",           "", STACKTOOL_MODE_TOSUM,          tosumArgs);
+    PXOPT_ADD_MODE("-addsumskyfile",   "", STACKTOOL_MODE_ADDSUMSKYFILE,   addsumskyfileArgs);
+    PXOPT_ADD_MODE("-sumskyfile",      "", STACKTOOL_MODE_SUMSKYFILE,      sumskyfileArgs);
+    PXOPT_ADD_MODE("-revertsumskyfile","", STACKTOOL_MODE_REVERTSUMSKYFILE,      revertsumskyfileArgs);
+
+    if (!pxGetOptions(stderr, argc, argv, config, modes, argSets)) {
+        psError(PS_ERR_UNKNOWN, true, "option parsing failed");
+        psFree(argSets);
+        psFree(modes);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+    psFree(modes);
+
+    // define Database handle, if used
+    // do this last so we don't setup a connection before CLI options are
+    // validated
+    config->dbh = psMemIncrRefCounter(pmConfigDB(config->modules));
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    return config;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/warptool.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/warptool.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/warptool.c	(revision 22157)
@@ -0,0 +1,1160 @@
+/*
+ * warptool.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVB_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+#include <ippdb.h>
+
+#include "pxtools.h"
+#include "warptool.h"
+#include "pxwarp.h"
+
+static psS64 definerunMode(pxConfig *config);
+static bool updaterunMode(pxConfig *config);
+static bool expMode(pxConfig *config);
+static bool imfileMode(pxConfig *config);
+static bool tooverlapMode(pxConfig *config);
+static bool addoverlapMode(pxConfig *config);
+static bool scmapMode(pxConfig *config);
+static bool towarpedMode(pxConfig *config);
+static bool addwarpedMode(pxConfig *config);
+static bool warpedMode(pxConfig *config);
+static bool revertwarpedMode(pxConfig *config);
+static bool blockMode(pxConfig *config);
+static bool maskedMode(pxConfig *config);
+static bool unblockMode(pxConfig *config);
+
+static bool parseAndInsertSkyCellMap(pxConfig *config, const char *mapfile);
+static bool isValidMode(pxConfig *config, const char *mode);
+bool warpCompletedRuns(pxConfig *config);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = warptoolConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(WARPTOOL_MODE_DEFINERUN,         definerunMode);
+        MODECASE(WARPTOOL_MODE_UPDATERUN,         updaterunMode);
+        MODECASE(WARPTOOL_MODE_EXP,               expMode);
+        MODECASE(WARPTOOL_MODE_IMFILE,            imfileMode);
+        MODECASE(WARPTOOL_MODE_TOOVERLAP,         tooverlapMode);
+        MODECASE(WARPTOOL_MODE_ADDOVERLAP,        addoverlapMode);
+        MODECASE(WARPTOOL_MODE_SCMAP,             scmapMode);
+        MODECASE(WARPTOOL_MODE_TOWARPED,          towarpedMode);
+        MODECASE(WARPTOOL_MODE_ADDWARPED,         addwarpedMode);
+        MODECASE(WARPTOOL_MODE_WARPED,            warpedMode);
+        MODECASE(WARPTOOL_MODE_REVERTWARPED,      revertwarpedMode);
+        MODECASE(WARPTOOL_MODE_BLOCK,             blockMode);
+        MODECASE(WARPTOOL_MODE_MASKED,            maskedMode);
+        MODECASE(WARPTOOL_MODE_UNBLOCK,           unblockMode);
+
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+
+static psS64 definerunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    
+    PXOPT_LOOKUP_S64(fake_id, config->args, "-fake_id", true, false); // required 
+    PXOPT_LOOKUP_STR(mode, config->args, "-mode", true, false); // required 
+    PXOPT_LOOKUP_STR(workdir, config->args, "-workdir", true, false); // required 
+    PXOPT_LOOKUP_STR(label, config->args, "-label", false, false);
+    PXOPT_LOOKUP_STR(dvodb, config->args, "-dvodb", false, false);
+    PXOPT_LOOKUP_STR(tess_id, config->args, "-tess_id", false, false);
+    PXOPT_LOOKUP_STR(end_stage, config->args, "-end_stage", false, false);
+    PXOPT_LOOKUP_TIME(registered, config->args, "-registered", false, false);
+    PXOPT_LOOKUP_BOOL(magiced, config->args, "-magiced", false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    // check mode
+    if (mode && !isValidMode(config, mode)) {
+        psError(PS_ERR_UNKNOWN, false, "invalid mode");
+        return false;
+    }
+
+    warpRunRow *warpRun = warpRunRowAlloc(
+            0,          // ID
+            fake_id,
+            mode,
+            "run",      // state
+            workdir,
+            "dirty",    // workdir_state
+            label,
+            dvodb,
+            tess_id,
+            end_stage,
+            registered,
+            magiced
+    );
+    if (!warpRun) {
+        psError(PS_ERR_UNKNOWN, false, "failed to alloc warpRun object");
+        return true;
+    }
+    if (!warpRunInsertObject(config->dbh, warpRun)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(warpRun);
+        return true;
+    }
+
+    // get the assigned warp_id
+    psS64 warp_id = psDBLastInsertID(config->dbh);
+    warpRun->warp_id = warp_id;
+
+    if (!warpRunPrintObject(stdout, warpRun, !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print object");
+            psFree(warpRun);
+            return false;
+    }
+
+    psFree(warpRun);
+
+    return warp_id;
+}
+
+
+static bool updaterunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-warp_id", "warp_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-fake_id", "fake_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-chip_id", "chip_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-exp_id", "exp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_name", "exp_name", "==");
+    PXOPT_COPY_STR(config->args, where, "-inst", "camera", "==");
+    PXOPT_COPY_STR(config->args, where, "-telescope", "telescope", "==");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_begin", "dateobs", ">=");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_end", "dateobs", "<=");
+    PXOPT_COPY_STR(config->args, where, "-exp_tag", "exp_tag", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_type", "exp_type", "==");
+    PXOPT_COPY_STR(config->args, where, "-filelevel", "filelevel", "==");
+    PXOPT_COPY_STR(config->args, where, "-reduction", "reduction", "==");
+    PXOPT_COPY_STR(config->args, where, "-filter", "filter", "==");
+    PXOPT_COPY_F32(config->args, where, "-airmass_min", "airmass", ">=");
+    PXOPT_COPY_F32(config->args, where, "-airmass_max", "airmass", "<");
+    PXOPT_COPY_F64(config->args, where, "-ra_min", "ra", ">=");
+    PXOPT_COPY_F64(config->args, where, "-ra_max", "ra", "<");
+    PXOPT_COPY_F64(config->args, where, "-decl_min", "decl", ">=");
+    PXOPT_COPY_F64(config->args, where, "-decl_max", "decl", "<");
+    PXOPT_COPY_F32(config->args, where, "-exp_time_min", "exp_time", ">=");
+    PXOPT_COPY_F32(config->args, where, "-exp_time_max", "exp_time", "<");
+    PXOPT_COPY_F32(config->args, where, "-sat_pixel_frac_min", "sat_pixel_frac", ">=");
+    PXOPT_COPY_F32(config->args, where, "-sat_pixel_frac_max", "sat_pixel_frac", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_min", "bt", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_max", "bt", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_stdev_min", "bg_stdev", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_stdev_max", "bg_stdev", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_mean_stdev_min", "bg_mean_stdev", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_mean_stdev_max", "bg_mean_stdev", "<");
+    PXOPT_COPY_F64(config->args, where, "-alt_min", "alt", ">=");
+    PXOPT_COPY_F64(config->args, where, "-alt_max", "alt", "<");
+    PXOPT_COPY_F64(config->args, where, "-az_min", "az", ">=");
+    PXOPT_COPY_F64(config->args, where, "-az_max", "az", "<");
+    PXOPT_COPY_F64(config->args, where, "-ccd_temp_min", "ccd_temp", ">=");
+    PXOPT_COPY_F64(config->args, where, "-ccd_temp_max", "ccd_temp", "<");
+    PXOPT_COPY_F64(config->args, where, "-posang_min", "posang", ">=");
+    PXOPT_COPY_F64(config->args, where, "-posang_max", "posang", "<");
+    PXOPT_COPY_STR(config->args, where, "-object", "object", "==");
+    PXOPT_COPY_F32(config->args, where, "-solang_min", "solang", ">=");
+    PXOPT_COPY_F32(config->args, where, "-solang_max", "solang", "<");
+
+    if (!psListLength(where->list)
+        && !psMetadataLookupBool(NULL, config->args, "-all")) {
+        psFree(where);
+        where = NULL;
+        psError(PXTOOLS_ERR_DATA, false, "search parameters are required");
+        return false;
+    }
+
+    PXOPT_LOOKUP_STR(state, config->args, "-state", false, false);
+    PXOPT_LOOKUP_STR(label, config->args, "-label", false, false);
+
+    if ((!state) && (!label)) {
+        psError(PXTOOLS_ERR_DATA, false, "parameters (-state or -label) are required");
+        psFree(where);
+        return false;
+    }
+
+    if (state) {
+        // set warpRun.state to state
+        if (!pxwarpRunSetStateByQuery(config, where, state)) {
+            psFree(where);
+            return false;
+        }
+    }
+
+    if (label) {
+        // set chipRun.label to label
+        if (!pxwarpRunSetLabelByQuery(config, where, label)) {
+            psFree(where);
+            return false;
+        }
+    }
+
+    psFree(where);
+
+    return true;
+}
+
+
+static bool expMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-warp_id", "warp_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-fake_id", "fake_id", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    // find all rawImfiles matching the default query
+    psString query = pxDataGet("warptool_exp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "warpRun");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("warptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "warpRun", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool imfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-warp_id", "warp_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-fake_id", "fake_id", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    // find all rawImfiles matching the default query
+    psString query = pxDataGet("warptool_imfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "warpRun");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("warptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "warpInputImfile", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool tooverlapMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-warp_id", "warp_id", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    // find all rawImfiles matching the default query
+    psString query = pxDataGet("warptool_tooverlap.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "warpRun");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("warptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "warpRun", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool addoverlapMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_STR(mapfile, config->args, "-mapfile", false, false);
+    PXOPT_LOOKUP_S64(warp_id, config->args, "-warp_id", false, false);
+    PXOPT_LOOKUP_S16(code, config->args, "-code", false, false);
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (code == 0) {
+        if (!parseAndInsertSkyCellMap(config, mapfile)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to inject mapfile: %s into the database", mapfile);
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            return false;
+        }
+    } else {
+        warpSkyCellMapInsert(config->dbh,
+            warp_id,
+            NULL,   // skycell_id
+            NULL,   // tess_id
+            NULL,   // class_id
+            code    // fault
+        );
+    }
+
+    // point of no return
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool parseAndInsertSkyCellMap(pxConfig *config, const char *mapfile)
+{
+    unsigned int nFail = 0;
+    psMetadata *skycells = psMetadataConfigRead(NULL, &nFail, mapfile, false);
+    if (!skycells) {
+        psError(PS_ERR_UNKNOWN, false, "failed to parse mapfile: %s", mapfile);
+        return false;
+    }
+    if (nFail) {
+        psError(PS_ERR_UNKNOWN, false, "there were %d errors parsing mapfile: %s", nFail, mapfile);
+        psFree(skycells);
+        return false;
+    }
+
+    psMetadataItem *item = NULL;
+    psMetadataIterator *iter = psMetadataIteratorAlloc(skycells, 0, NULL);
+    while ((item = psMetadataGetAndIncrement(iter))) {
+        if (item->type != PS_DATA_METADATA) {
+            psError(PS_ERR_UNKNOWN, false, "mapfile: %s is in the wrong format", mapfile);
+            psFree(iter);
+            psFree(skycells);
+            return false;
+        }
+
+        psMetadata *sc = item->data.md;
+        // this conversion isn't strictly nessicary but it's an easy way of
+        // validating the format
+        warpSkyCellMapRow *row = warpSkyCellMapObjectFromMetadata(sc);
+        if (!row) {
+            psError(PS_ERR_UNKNOWN, false, "failed to convert mapfile: %s metdata entry into a warpSkyCellMap object", mapfile);
+            psFree(iter);
+            psFree(skycells);
+            return false;
+        }
+
+        if (!warpSkyCellMapInsertObject(config->dbh, row)) {
+            psErrorCode err = psErrorCodeLast();
+            switch (err) {
+                case PS_ERR_DB_CLIENT:
+                    psError(PXTOOLS_ERR_SYS, false, "database error");
+                case PS_ERR_DB_SERVER:
+                    psError(PXTOOLS_ERR_PROG, false, "database error");
+                default:
+                    psError(PXTOOLS_ERR_PROG, false, "unknown error");
+            }
+            psFree(row);
+            psFree(iter);
+            psFree(skycells);
+            return false;
+        }
+
+        psFree(row);
+    }
+    psFree(iter);
+    psFree(skycells);
+
+    return true;
+}
+
+
+static bool scmapMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-warp_id", "warp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-skycell_id", "skycell_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-tess_id", "tess_id", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    // find all rawImfiles matching the default query
+    psString query = pxDataGet("warptool_scmap.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "warpSkyCellMap");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("warptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "warpSkyCellMap", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool towarpedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-warp_id", "warp_id", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    // find all rawImfiles matching the default query
+    psString query = pxDataGet("warptool_towarped.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "warpSkyCellMap");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("warptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "warpPendingSkyCell", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool addwarpedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_S64(warp_id, config->args, "-warp_id", true, false);
+    PXOPT_LOOKUP_STR(skycell_id, config->args, "-skycell_id", true, false);
+    PXOPT_LOOKUP_STR(tess_id, config->args, "-tess_id", true, false);
+
+    // optional
+    PXOPT_LOOKUP_STR(uri, config->args, "-uri", false, false);
+    PXOPT_LOOKUP_STR(path_base, config->args, "-path_base", false, false);
+    PXOPT_LOOKUP_F64(bg, config->args, "-bg", false, false);
+    PXOPT_LOOKUP_F64(bg_stdev, config->args, "-bg_stdev", false, false);
+    PXOPT_LOOKUP_F32(dtime_warp, config->args, "-dtime_warp", false, false);
+    PXOPT_LOOKUP_S32(xmin, config->args, "-xmin", false, false);
+    PXOPT_LOOKUP_S32(xmax, config->args, "-xmax", false, false);
+    PXOPT_LOOKUP_S32(ymin, config->args, "-ymin", false, false);
+    PXOPT_LOOKUP_S32(ymax, config->args, "-ymax", false, false);
+    PXOPT_LOOKUP_STR(hostname, config->args, "-hostname", false, false);
+    PXOPT_LOOKUP_F32(good_frac, config->args, "-good_frac", false, false);
+    PXOPT_LOOKUP_BOOL(ignore, config->args, "-ignore", false);
+
+    // default values
+    PXOPT_LOOKUP_S16(code, config->args, "-code", false, false);
+
+    // we don't want to insert the last skyfile in a run but then not mark the
+    // run as 'stop'
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // XXX need to validate that this coresponds to an warpInputImfile
+    if (!warpSkyfileInsert(config->dbh,
+                           warp_id,
+                           skycell_id,
+                           tess_id,
+                           uri,
+                           path_base,
+                           bg,
+                           bg_stdev,
+                           dtime_warp,
+                           hostname,
+                           good_frac,
+                           xmin,
+                           xmax,
+                           ymin,
+                           ymax,
+                           ignore,
+                           code
+        )) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!warpCompletedRuns(config)) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // point of no return
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+bool warpCompletedRuns(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // XXX this SQL has not been broken out to into seperate files as the MYSQL
+    // < 5 & MYSQL 5 versions need to be kept in sync
+
+#undef MYSQL5
+#if MYSQL5
+    // XXX at MySQL 4.1.21 (probably all of 4.1.x) chokes and dies on this
+    // statement as it thinks it is trying to select from the table being
+    // updated. The 4.1 manual says that nested sub-queries are explicited
+    // allowed to do this with update statements as a temporary table is
+    // created so that you are not actually selecting from the table you are
+    // modifying.
+    char *query =
+        "UPDATE warpRun\n"
+        "   SET warpRun.state = 'stop'\n"
+        " WHERE\n"
+        "   warpRun.warp_id =\n"
+        "   (SELECT DISTINCT\n"
+        "       warp_id\n"
+        "   FROM\n"
+        "       (SELECT DISTINCT\n"
+        "           warpRun.warp_id,\n"
+        "           warpSkyCellMap.warp_id as foo,\n"
+        "           warpSkyfile.warp_id as bar\n"
+        "       FROM warpRun\n"
+        "       JOIN warpSkyCellMap\n"
+        "           USING(warp_id)\n"
+        "       LEFT JOIN warpSkyfile\n"
+        "           USING(warp_id, skycell_id, tess_id)\n"
+        "       WHERE\n"
+        "           warpRun.state = 'run'\n"
+        "       GROUP BY\n"
+        "           warpRun.warp_id\n"
+        "       HAVING\n"
+        "       COUNT(warpSkyCellMap.warp_id) = COUNT(warpSkyfile.warp_id)\n"
+        "       ) as Foo\n"
+        "   )\n";
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+#else // if MYSQL5
+{
+    char *query =
+        "CREATE TEMPORARY TABLE finished\n"
+        " (warp_id INT, PRIMARY KEY(warp_id)) ENGINE=MEMORY\n";
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+}
+
+{
+    char *query =
+        "INSERT INTO finished\n"
+        " SELECT\n"
+        "   warp_id\n"
+        " FROM\n"
+        "   (SELECT DISTINCT\n"
+        "       warpRun.warp_id,\n"
+        "       warpSkyCellMap.warp_id as foo,\n"
+        "       warpSkyfile.warp_id as bar\n"
+        "   FROM warpRun\n"
+        "   JOIN warpSkyCellMap\n"
+        "       USING(warp_id)\n"
+        "   LEFT JOIN warpSkyfile\n"
+        "       USING(warp_id, skycell_id)\n"
+        "   WHERE\n"
+        "       warpRun.state = 'run'\n"
+        "   GROUP BY\n"
+        "       warpRun.warp_id\n"
+        "   HAVING\n"
+        "       COUNT(warpSkyCellMap.warp_id) = COUNT(warpSkyfile.warp_id)\n"
+        " ) as Foo \n";
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+}
+
+{
+    char *query =
+        "UPDATE warpRun\n"
+        "   SET warpRun.state = 'stop'\n"
+        " WHERE\n"
+        "   warpRun.warp_id =\n"
+        "   (SELECT DISTINCT\n"
+        "       warp_id\n"
+        "   FROM finished\n"
+        "   )\n";
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+}
+#endif // if MYSQL5
+
+    return true;
+}
+
+static bool warpedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-warp_id",    "warp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-skycell_id", "skycell_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-tess_id",    "tess_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-exp_id",     "exp_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-fake_id",    "fake_id", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    // find all rawImfiles matching the default query
+    psString query = pxDataGet("warptool_warped.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "warpSkyfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("warptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "warpSkyfile", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool revertwarpedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-warp_id", "warp_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-skycell_id", "skycell_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-tess_id", "tess_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-fake_id", "fake_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-chip_id", "chip_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-exp_id", "exp_id", "==");
+
+    // XXX These may need to be updated with the table name?
+    PXOPT_COPY_STR(config->args, where, "-exp_name", "exp_name", "==");
+    PXOPT_COPY_STR(config->args, where, "-inst", "camera", "==");
+    PXOPT_COPY_STR(config->args, where, "-telescope", "telescope", "==");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_begin", "dateobs", ">=");
+    PXOPT_COPY_TIME(config->args, where, "-dateobs_end", "dateobs", "<=");
+    PXOPT_COPY_STR(config->args, where, "-exp_tag", "exp_tag", "==");
+    PXOPT_COPY_STR(config->args, where, "-exp_type", "exp_type", "==");
+    PXOPT_COPY_STR(config->args, where, "-filelevel", "filelevel", "==");
+    PXOPT_COPY_STR(config->args, where, "-reduction", "reduction", "==");
+    PXOPT_COPY_STR(config->args, where, "-filter", "filter", "==");
+
+    PXOPT_COPY_F32(config->args, where, "-airmass_min", "airmass", ">=");
+    PXOPT_COPY_F32(config->args, where, "-airmass_max", "airmass", "<");
+    PXOPT_COPY_F64(config->args, where, "-ra_min", "ra", ">=");
+    PXOPT_COPY_F64(config->args, where, "-ra_max", "ra", "<");
+    PXOPT_COPY_F64(config->args, where, "-decl_min", "decl", ">=");
+    PXOPT_COPY_F64(config->args, where, "-decl_max", "decl", "<");
+    PXOPT_COPY_F32(config->args, where, "-exp_time_min", "exp_time", ">=");
+    PXOPT_COPY_F32(config->args, where, "-exp_time_max", "exp_time", "<");
+    PXOPT_COPY_F32(config->args, where, "-sat_pixel_frac_min", "sat_pixel_frac", ">=");
+    PXOPT_COPY_F32(config->args, where, "-sat_pixel_frac_max", "sat_pixel_frac", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_min", "bt", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_max", "bt", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_stdev_min", "bg_stdev", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_stdev_max", "bg_stdev", "<");
+    PXOPT_COPY_F64(config->args, where, "-bg_mean_stdev_min", "bg_mean_stdev", ">=");
+    PXOPT_COPY_F64(config->args, where, "-bg_mean_stdev_max", "bg_mean_stdev", "<");
+    PXOPT_COPY_F64(config->args, where, "-alt_min", "alt", ">=");
+    PXOPT_COPY_F64(config->args, where, "-alt_max", "alt", "<");
+    PXOPT_COPY_F64(config->args, where, "-az_min", "az", ">=");
+    PXOPT_COPY_F64(config->args, where, "-az_max", "az", "<");
+    PXOPT_COPY_F64(config->args, where, "-ccd_temp_min", "ccd_temp", ">=");
+    PXOPT_COPY_F64(config->args, where, "-ccd_temp_max", "ccd_temp", "<");
+    PXOPT_COPY_F64(config->args, where, "-posang_min", "posang", ">=");
+    PXOPT_COPY_F64(config->args, where, "-posang_max", "posang", "<");
+    PXOPT_COPY_STR(config->args, where, "-object", "object", "==");
+    PXOPT_COPY_F32(config->args, where, "-solang_min", "solang", ">=");
+    PXOPT_COPY_F32(config->args, where, "-solang_max", "solang", "<");
+    PXOPT_COPY_S16(config->args, where, "-code", "fault", "==");
+
+    if (!psListLength(where->list)
+        && !psMetadataLookupBool(NULL, config->args, "-all")) {
+        psFree(where);
+        psError(PXTOOLS_ERR_DATA, false, "search parameters are required");
+        return false;
+    }
+
+    psString query = pxDataGet("warptool_revertwarped.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        psFree(where);
+        return false;
+    }
+
+    if (where && psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    psFree(where);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    return true;
+}
+
+
+static bool blockMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_STR(label, config->args, "-label", true, false);
+
+    if (!warpMaskInsert(config->dbh, label)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool maskedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = psStringCopy("SELECT * FROM warpMask");
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("warpool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "warpMask", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool unblockMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_STR(label, config->args, "-label", true, false);
+
+    char *query = "DELETE FROM warpMask WHERE label = '%s'";
+
+    if (!p_psDBRunQuery(config->dbh, query, label)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool isValidMode(pxConfig *config, const char *mode)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(mode, false);
+
+    // check that state is a valid string value
+    if (!(
+            (strncmp(mode, "warp", 5) == 0)
+            || (strncmp(mode, "diff", 5) == 0)
+            || (strncmp(mode, "stack", 6) == 0)
+            || (strncmp(mode, "magic", 6) == 0)
+        )
+    ) {
+        psError(PS_ERR_UNKNOWN, false,
+                "invalid warpRun mode: %s", mode);
+        return false;
+    }
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/warptool.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/warptool.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/warptool.h	(revision 22157)
@@ -0,0 +1,47 @@
+/*
+ * warptool.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef WARPTOOL_H
+#define WARPTOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    WARPTOOL_MODE_NONE           = 0x0,
+    WARPTOOL_MODE_DEFINERUN,
+    WARPTOOL_MODE_RUNONE,
+    WARPTOOL_MODE_UPDATERUN,
+    WARPTOOL_MODE_ADDINPUTEXP,
+    WARPTOOL_MODE_EXP,
+    WARPTOOL_MODE_IMFILE,
+    WARPTOOL_MODE_TOOVERLAP,
+    WARPTOOL_MODE_ADDOVERLAP,
+    WARPTOOL_MODE_SCMAP,
+    WARPTOOL_MODE_TOWARPED,
+    WARPTOOL_MODE_ADDWARPED,
+    WARPTOOL_MODE_WARPED,
+    WARPTOOL_MODE_REVERTWARPED,
+    WARPTOOL_MODE_BLOCK,
+    WARPTOOL_MODE_MASKED,
+    WARPTOOL_MODE_UNBLOCK,
+} warptoolMode;
+
+pxConfig *warptoolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // WARPTOOL_H
Index: /branches/eam_branches/eam_branch_20080706/ippTools/src/warptoolConfig.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippTools/src/warptoolConfig.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippTools/src/warptoolConfig.c	(revision 22157)
@@ -0,0 +1,294 @@
+/*
+ * warptoolConfig.c
+ *
+ * Copyright (C) 2006-2007  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdint.h>
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "warptool.h"
+
+pxConfig *warptoolConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (!config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    psTime *now = psTimeGetNow(PS_TIME_TAI);
+
+    // -definerun
+    psMetadata *definerunArgs = psMetadataAlloc();
+    psMetadataAddS64(definerunArgs, PS_LIST_TAIL, "-fake_id", 0,            "define camtool ID (required)", 0);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-mode", 0,            "define mode (required)", NULL);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-workdir", 0,            "define workdir (required)", NULL);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-label", 0,            "define label", NULL);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-dvodb", 0,            "define dvodb", NULL);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-tess_id", 0,            "define tess_id", NULL);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-end_stage", 0,            "define end stage", NULL);
+    psMetadataAddTime(definerunArgs, PS_LIST_TAIL, "-registered",  0,            "time detrend run was registered", now);
+    psMetadataAddBool(definerunArgs, PS_LIST_TAIL, "-magiced",  0,            "has this exposure been magiced", false);
+    psMetadataAddBool(definerunArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -updaterun
+    // XXX need to allow multiple fake_ids
+    // XXX need to allow multiple fake_ids
+    // XXX need to allow multiple chip_ids
+    // XXX need to allow multiple exp_ids
+    psMetadata *updaterunArgs = psMetadataAlloc();
+    psMetadataAddS64(updaterunArgs, PS_LIST_TAIL, "-warp_id", 0,            "search by warptool ID", 0);
+    psMetadataAddS64(updaterunArgs, PS_LIST_TAIL, "-fake_id",  0,            "search by fake_id", 0);
+    psMetadataAddS64(updaterunArgs, PS_LIST_TAIL, "-chip_id",  0,            "search by chip_id", 0);
+    psMetadataAddS64(updaterunArgs, PS_LIST_TAIL, "-exp_id",  0,            "search by exp_id", 0);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-exp_name",  0,            "search by exp_name", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-inst",  0,            "search for camera", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-telescope",  0,            "search for telescope", NULL);
+    psMetadataAddTime(updaterunArgs, PS_LIST_TAIL, "-dateobs_begin", 0,            "search for exposures by time (>=)", NULL);
+    psMetadataAddTime(updaterunArgs, PS_LIST_TAIL, "-dateobs_end", 0,            "search for exposures by time (<)", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-exp_tag",  0,            "search by exp_tag", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-exp_type",  0,            "search by exp_type", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-filelevel",  0,            "search by filelevel", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-reduction",  0,            "search by reduction class", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-filter",  0,            "search for filter", NULL);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-airmass_min",  0,            "define min airmass", NAN);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-airmass_max",  0,            "define max airmass", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-ra_min",  0,            "define min", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-ra_max",  0,            "define max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-decl_min",  0,            "define min", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-decl_max",  0,            "define max", NAN);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-exp_time_min",  0,            "define min", NAN);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-exp_time_max",  0,            "define max", NAN);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-sat_pixel_frac_min",  0,            "define max fraction of saturated pixels", NAN);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-sat_pixel_frac_max",  0,            "define min fraction of saturated pixels", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-bg_min",  0,            "define max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-bg_max",  0,            "define max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-bg_stdev_min",  0,            "define max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-bg_stdev_max",  0,            "define max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-bg_mean_stdev_min",  0,            "define max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-bg_mean_stdev_max",  0,            "define max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-alt_min",  0,            "define min", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-alt_max",  0,            "define max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-az_min",  0,            "define min", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-az_max",  0,            "define max", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-ccd_temp_min",  0,            "define min ccd tempature", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-ccd_temp_max",  0,            "define max ccd tempature", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-posang_min",  0,            "define min rotator position angle", NAN);
+    psMetadataAddF64(updaterunArgs, PS_LIST_TAIL, "-posang_max",  0,            "define max rotator position angle", NAN);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-object",  0,            "search by exposure object", NULL);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-solang_min",  0,            "define min solar angle", NAN);
+    psMetadataAddF32(updaterunArgs, PS_LIST_TAIL, "-solang_max",  0,            "define max solar angle", NAN);
+
+    psMetadataAddBool(updaterunArgs, PS_LIST_TAIL, "-all",  0,            "allow everything to be queued without search terms", false);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-state", 0,            "set state", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-label", 0,            "set label", NULL);
+
+#if 0
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-workdir", 0,            "define workdir (required)", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-registered",  0,            "time detrend run was registered", now);
+#endif
+
+    // -exp
+    psMetadata *expArgs = psMetadataAlloc();
+    psMetadataAddS64(expArgs, PS_LIST_TAIL, "-warp_id", 0,            "search by warptool ID", 0);
+    psMetadataAddS64(expArgs, PS_LIST_TAIL, "-fake_id", 0,            "search by camtool ID", 0);
+    psMetadataAddU64(expArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(expArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -imfile
+    psMetadata *imfileArgs = psMetadataAlloc();
+    psMetadataAddS64(imfileArgs, PS_LIST_TAIL, "-warp_id", 0,            "search by warptool ID", 0);
+    psMetadataAddS64(imfileArgs, PS_LIST_TAIL, "-fake_id", 0,            "search by camtool ID", 0);
+    psMetadataAddU64(imfileArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(imfileArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -tooverlap
+    psMetadata *tooverlapArgs = psMetadataAlloc();
+    psMetadataAddS64(tooverlapArgs, PS_LIST_TAIL, "-warp_id", 0,            "search by warp ID", 0);
+    psMetadataAddU64(tooverlapArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(tooverlapArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -addoverlap
+    psMetadata *addoverlapArgs = psMetadataAlloc();
+    psMetadataAddStr(addoverlapArgs, PS_LIST_TAIL, "-mapfile", 0,            "path to skycell <-> imfile mapping file", NULL);
+    psMetadataAddS64(addoverlapArgs, PS_LIST_TAIL, "-warp_id",  0,            "set warp ID", 0);
+    psMetadataAddS16(addoverlapArgs, PS_LIST_TAIL, "-code",  0,            "set fault code", 0);
+
+    // -scmap
+    psMetadata *scmapArgs = psMetadataAlloc();
+    psMetadataAddS64(scmapArgs, PS_LIST_TAIL, "-warp_id", 0,            "search by warptool ID", 0);
+    psMetadataAddStr(scmapArgs, PS_LIST_TAIL, "-skycell_id", 0,            "search by skycell ID", NULL);
+    psMetadataAddStr(scmapArgs, PS_LIST_TAIL, "-tess_id", 0,            "search by tess ID", NULL);
+    psMetadataAddU64(scmapArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(scmapArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -towarped
+    psMetadata *towarpedArgs = psMetadataAlloc();
+    psMetadataAddS64(towarpedArgs, PS_LIST_TAIL, "-warp_id", 0,            "search by warptool ID", 0);
+    psMetadataAddU64(towarpedArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(towarpedArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -addwarped
+    psMetadata *addwarpedArgs = psMetadataAlloc();
+    psMetadataAddS64(addwarpedArgs, PS_LIST_TAIL, "-warp_id", 0,            "define warptool ID (required)", 0);
+    psMetadataAddStr(addwarpedArgs, PS_LIST_TAIL, "-skycell_id",  0,            "define skycell ID (required)", NULL);
+    psMetadataAddStr(addwarpedArgs, PS_LIST_TAIL, "-tess_id",  0,            "define tessellation ID (required)", NULL);
+    psMetadataAddStr(addwarpedArgs, PS_LIST_TAIL, "-uri", 0,            "define URI of file", 0);
+    psMetadataAddStr(addwarpedArgs, PS_LIST_TAIL, "-path_base", 0,            "define base output location", 0);
+    psMetadataAddF64(addwarpedArgs, PS_LIST_TAIL, "-bg",  0,            "define exposure background", NAN);
+    psMetadataAddF64(addwarpedArgs, PS_LIST_TAIL, "-bg_stdev",  0,            "define exposure background stdev", NAN);
+    psMetadataAddF32(addwarpedArgs, PS_LIST_TAIL, "-dtime_warp",  0,            "define elapsed processing time", NAN);
+    psMetadataAddS32(addwarpedArgs, PS_LIST_TAIL, "-xmin",  0,            "define minimum x value", INT_MAX);
+    psMetadataAddS32(addwarpedArgs, PS_LIST_TAIL, "-xmax",  0,            "define maximum x value", -INT_MAX);
+    psMetadataAddS32(addwarpedArgs, PS_LIST_TAIL, "-ymin",  0,            "define minimum y value", INT_MAX);
+    psMetadataAddS32(addwarpedArgs, PS_LIST_TAIL, "-ymax",  0,            "define maximum y value", -INT_MAX);
+    psMetadataAddStr(addwarpedArgs, PS_LIST_TAIL, "-hostname", 0,            "define hostname", 0);
+    psMetadataAddF32(addwarpedArgs, PS_LIST_TAIL, "-good_frac",  0,            "define %% of good pixels", NAN);
+    psMetadataAddBool(addwarpedArgs, PS_LIST_TAIL, "-ignore",  0,            "define if this skycell should be ignored", false);
+    psMetadataAddS16(addwarpedArgs, PS_LIST_TAIL, "-code",  0,            "set fault code", 0);
+
+    // -warped
+    psMetadata *warpedArgs = psMetadataAlloc();
+    psMetadataAddS64(warpedArgs, PS_LIST_TAIL, "-warp_id", 0,            "search by warptool ID", 0);
+    psMetadataAddStr(warpedArgs, PS_LIST_TAIL, "-skycell_id",  0,            "define skycell ID", NULL);
+    psMetadataAddStr(warpedArgs, PS_LIST_TAIL, "-tess_id",  0,            "define tessellation ID", NULL);
+    psMetadataAddS64(warpedArgs, PS_LIST_TAIL, "-exp_id", 0,            "define exposure tag", 0);
+    psMetadataAddS64(warpedArgs, PS_LIST_TAIL, "-fake_id", 0,            "define phase 3 version of exposure tag", 0);
+    psMetadataAddU64(warpedArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(warpedArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -revertwarped
+    // XXX need to allow multiple fake_ids
+    // XXX need to allow multiple fake_ids
+    // XXX need to allow multiple chip_ids
+    // XXX need to allow multiple exp_ids
+    psMetadata *revertwarpedArgs = psMetadataAlloc();
+    psMetadataAddS64(revertwarpedArgs, PS_LIST_TAIL, "-warp_id", 0,            "search by warptool ID", 0);
+    psMetadataAddStr(revertwarpedArgs, PS_LIST_TAIL, "-skycell_id",  0,            "search by skycell ID", NULL);
+    psMetadataAddStr(revertwarpedArgs, PS_LIST_TAIL, "-tess_id",  0,            "searcy by tessellation ID", NULL);
+    psMetadataAddS64(revertwarpedArgs, PS_LIST_TAIL, "-fake_id",  0,            "search by fake_id", 0);
+    psMetadataAddS64(revertwarpedArgs, PS_LIST_TAIL, "-chip_id",  0,            "search by chip_id", 0);
+    psMetadataAddS64(revertwarpedArgs, PS_LIST_TAIL, "-exp_id",  0,            "search by exp_id", 0);
+    psMetadataAddStr(revertwarpedArgs, PS_LIST_TAIL, "-exp_name",  0,            "search by exp_name", NULL);
+    psMetadataAddStr(revertwarpedArgs, PS_LIST_TAIL, "-inst",  0,            "search for camera", NULL);
+    psMetadataAddStr(revertwarpedArgs, PS_LIST_TAIL, "-telescope",  0,            "search for telescope", NULL);
+    psMetadataAddTime(revertwarpedArgs, PS_LIST_TAIL, "-dateobs_begin", 0,            "search for exposures by time (>=)", NULL);
+    psMetadataAddTime(revertwarpedArgs, PS_LIST_TAIL, "-dateobs_end", 0,            "search for exposures by time (<)", NULL);
+    psMetadataAddStr(revertwarpedArgs, PS_LIST_TAIL, "-exp_tag",  0,            "search by exp_tag", NULL);
+    psMetadataAddStr(revertwarpedArgs, PS_LIST_TAIL, "-exp_type",  0,            "search by exp_type", NULL);
+    psMetadataAddStr(revertwarpedArgs, PS_LIST_TAIL, "-filelevel",  0,            "search by filelevel", NULL);
+    psMetadataAddStr(revertwarpedArgs, PS_LIST_TAIL, "-reduction",  0,            "search by reduction class", NULL);
+    psMetadataAddStr(revertwarpedArgs, PS_LIST_TAIL, "-filter",  0,            "search for filter", NULL);
+    psMetadataAddF32(revertwarpedArgs, PS_LIST_TAIL, "-airmass_min",  0,            "define min airmass", NAN);
+    psMetadataAddF32(revertwarpedArgs, PS_LIST_TAIL, "-airmass_max",  0,            "define max airmass", NAN);
+    psMetadataAddF64(revertwarpedArgs, PS_LIST_TAIL, "-ra_min",  0,            "define min", NAN);
+    psMetadataAddF64(revertwarpedArgs, PS_LIST_TAIL, "-ra_max",  0,            "define max", NAN);
+    psMetadataAddF64(revertwarpedArgs, PS_LIST_TAIL, "-decl_min",  0,            "define min", NAN);
+    psMetadataAddF64(revertwarpedArgs, PS_LIST_TAIL, "-decl_max",  0,            "define max", NAN);
+    psMetadataAddF32(revertwarpedArgs, PS_LIST_TAIL, "-exp_time_min",  0,            "define min", NAN);
+    psMetadataAddF32(revertwarpedArgs, PS_LIST_TAIL, "-exp_time_max",  0,            "define max", NAN);
+    psMetadataAddF32(revertwarpedArgs, PS_LIST_TAIL, "-sat_pixel_frac_min",  0,            "define max fraction of saturated pixels", NAN);
+    psMetadataAddF32(revertwarpedArgs, PS_LIST_TAIL, "-sat_pixel_frac_max",  0,            "define min fraction of saturated pixels", NAN);
+    psMetadataAddF64(revertwarpedArgs, PS_LIST_TAIL, "-bg_min",  0,            "define max", NAN);
+    psMetadataAddF64(revertwarpedArgs, PS_LIST_TAIL, "-bg_max",  0,            "define max", NAN);
+    psMetadataAddF64(revertwarpedArgs, PS_LIST_TAIL, "-bg_stdev_min",  0,            "define max", NAN);
+    psMetadataAddF64(revertwarpedArgs, PS_LIST_TAIL, "-bg_stdev_max",  0,            "define max", NAN);
+    psMetadataAddF64(revertwarpedArgs, PS_LIST_TAIL, "-bg_mean_stdev_min",  0,            "define max", NAN);
+    psMetadataAddF64(revertwarpedArgs, PS_LIST_TAIL, "-bg_mean_stdev_max",  0,            "define max", NAN);
+    psMetadataAddF64(revertwarpedArgs, PS_LIST_TAIL, "-alt_min",  0,            "define min", NAN);
+    psMetadataAddF64(revertwarpedArgs, PS_LIST_TAIL, "-alt_max",  0,            "define max", NAN);
+    psMetadataAddF64(revertwarpedArgs, PS_LIST_TAIL, "-az_min",  0,            "define min", NAN);
+    psMetadataAddF64(revertwarpedArgs, PS_LIST_TAIL, "-az_max",  0,            "define max", NAN);
+    psMetadataAddF64(revertwarpedArgs, PS_LIST_TAIL, "-ccd_temp_min",  0,            "define min ccd tempature", NAN);
+    psMetadataAddF64(revertwarpedArgs, PS_LIST_TAIL, "-ccd_temp_max",  0,            "define max ccd tempature", NAN);
+    psMetadataAddF64(revertwarpedArgs, PS_LIST_TAIL, "-posang_min",  0,            "define min rotator position angle", NAN);
+    psMetadataAddF64(revertwarpedArgs, PS_LIST_TAIL, "-posang_max",  0,            "define max rotator position angle", NAN);
+    psMetadataAddStr(revertwarpedArgs, PS_LIST_TAIL, "-object",  0,            "search by exposure object", NULL);
+    psMetadataAddF32(revertwarpedArgs, PS_LIST_TAIL, "-solang_min",  0,            "define min solar angle", NAN);
+    psMetadataAddF32(revertwarpedArgs, PS_LIST_TAIL, "-solang_max",  0,            "define max solar angle", NAN);
+
+    psMetadataAddS16(revertwarpedArgs, PS_LIST_TAIL, "-code",  0,            "search by fault code", 0);
+    psMetadataAddBool(revertwarpedArgs, PS_LIST_TAIL, "-all",  0,            "allow everything to be queued without search terms", false);
+
+    // -block
+    psMetadata *blockArgs = psMetadataAlloc();
+    psMetadataAddStr(blockArgs, PS_LIST_TAIL, "-label",  0,            "name of a label to mask out (required)", NULL);
+
+    // -masked
+    psMetadata *maskedArgs = psMetadataAlloc();
+    psMetadataAddBool(maskedArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -unblock
+    psMetadata *unblockArgs = psMetadataAlloc();
+    psMetadataAddStr(unblockArgs, PS_LIST_TAIL, "-label",  0,            "name of a label to unmask (required)", NULL);
+
+    psFree(now);
+    psMetadata *argSets = psMetadataAlloc();
+    psMetadata *modes   = psMetadataAlloc();
+
+    PXOPT_ADD_MODE("-definerun",       "", WARPTOOL_MODE_DEFINERUN,      definerunArgs);
+    PXOPT_ADD_MODE("-updaterun",       "", WARPTOOL_MODE_UPDATERUN,      updaterunArgs);
+    PXOPT_ADD_MODE("-exp",             "", WARPTOOL_MODE_EXP,            expArgs);
+    PXOPT_ADD_MODE("-imfile",          "", WARPTOOL_MODE_IMFILE,         imfileArgs);
+    PXOPT_ADD_MODE("-tooverlap",       "", WARPTOOL_MODE_TOOVERLAP,      tooverlapArgs);
+    PXOPT_ADD_MODE("-addoverlap",      "", WARPTOOL_MODE_ADDOVERLAP,     addoverlapArgs);
+    PXOPT_ADD_MODE("-scmap",           "", WARPTOOL_MODE_SCMAP,          scmapArgs);
+    PXOPT_ADD_MODE("-towarped",        "", WARPTOOL_MODE_TOWARPED,       towarpedArgs);
+    PXOPT_ADD_MODE("-addwarped",       "", WARPTOOL_MODE_ADDWARPED,      addwarpedArgs);
+    PXOPT_ADD_MODE("-warped",          "", WARPTOOL_MODE_WARPED,         warpedArgs);
+    PXOPT_ADD_MODE("-revertwarped",    "", WARPTOOL_MODE_REVERTWARPED,   revertwarpedArgs);
+    PXOPT_ADD_MODE("-block",           "set a label block", WARPTOOL_MODE_BLOCK,          blockArgs);
+    PXOPT_ADD_MODE("-masked",          "show blocked lables", WARPTOOL_MODE_MASKED,         maskedArgs);
+    PXOPT_ADD_MODE("-unblock",         "remove a label block", WARPTOOL_MODE_UNBLOCK,        unblockArgs);
+
+    if (!pxGetOptions(stderr, argc, argv, config, modes, argSets)) {
+        psError(PS_ERR_UNKNOWN, true, "option parsing failed");
+        psFree(argSets);
+        psFree(modes);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+    psFree(modes);
+
+    // define Database handle, if used
+    // do this last so we don't setup a connection before CLI options are
+    // validated
+    config->dbh = psMemIncrRefCounter(pmConfigDB(config->modules));
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    return config;
+}
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/.cvsignore	(revision 22157)
@@ -0,0 +1,14 @@
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+config.log
+config.status
+configure
+dvo.site
+install-sh
+ippconfig-0.0.1.tar.bz2
+ippconfig-0.0.1.tar.gz
+missing
+ipprc.config
+site.config
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/Makefile.am	(revision 22157)
@@ -0,0 +1,56 @@
+SUBDIRS = \
+	recipes \
+	isp \
+	gpc1 \
+	cfh12k \
+	megacam \
+	mosaic2 \
+	simple \
+	simmosaic \
+	simtest \
+	tc3 \
+	skycell \
+	sdssmosaic \
+	sdss \
+	esowfi \
+	lbc_red \
+	lulin \
+	uh8k
+
+install_files = \
+	system.config \
+	GSCregions.tbl \
+	dvo.photcodes
+
+built_sources = \
+	ipprc.config.in \
+	dvo.site.in
+
+built_files = \
+	ipprc.config \
+	dvo.site
+
+installdir = $(datadir)/ippconfig
+
+install_DATA = $(install_files) $(built_files)
+
+install-data-hook:
+	chmod 0755 $(installdir) 
+
+EXTRA_DIST = $(install_files) $(built_sources)
+
+BUILT_SOURCES = $(built_files)
+
+#ACLOCAL_AMFLAGS = -I m4
+
+# From the autoconf manual, section 4.7.2 "Installation Directory Variables"
+edit = sed \
+	-e 's|@pkgdatadir[@]|$(pkgdatadir)|g' \
+	-e 's|@prefix[@]|$(prefix)|g'
+
+ipprc.config dvo.site: Makefile
+	rm -f $@
+	$(edit) '$(srcdir)/$@.in' > $@
+
+ipprc.config: $(srcdir)/ipprc.config.in
+dvo.site: $(srcdir)/dvo.site.in
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/README
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/README	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/README	(revision 22157)
@@ -0,0 +1,18 @@
+
+Please read installation section of the IPP User's Guide for more details.
+
+To build and install the IPP configuration scripts, run the following:
+
+configure
+make
+make install
+
+After installation:
+
+Copy dvo.site to ~/.ptolemyrc
+
+Copy ipprc.config to ~/.ipprc
+
+Edit this file:
+  - Set the DBUSER and DBPASSWORD to your mysql username and password
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/Recipes.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/Recipes.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/Recipes.txt	(revision 22157)
@@ -0,0 +1,20 @@
+
+The recipes may be defined by files at two levels: system and camera.
+
+A single recipe (eg, PPIMAGE) must have a system-level file which
+defines all of the recipe variables.  The camera-level recipe may
+superceed values define in the system-level file.  
+
+Any recipe file may also define alternative symbolic names for
+recipes by including metadata folders named by the symbolic name.  
+
+The order of precedence is: 
+
+* primary system-level values
+* symbolic system-level values
+* primary camera-level values
+* symbolic camera-level values
+
+XXX the MULTI RESET thing is broken: the flag is not being carried
+through to the psMetadataItemAdd function...
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/autogen.sh
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/autogen.sh	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/autogen.sh	(revision 22157)
@@ -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=ippConfig
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL="aclocal $ACLOCAL_FLAGS"
+AUTOHEADER=autoheader
+AUTOMAKE=automake
+AUTOCONF=autoconf
+
+#($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+#        echo
+#        echo "You must have $LIBTOOlIZE installed to compile $PROJECT."
+#        echo "Download the appropriate package for your distribution,"
+#        echo "or get the source tarball at http://ftp.gnu.org/gnu/libtool/"
+#        DIE=1
+#}
+
+($ACLOCAL --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $ACLOCAL installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+#($AUTOHEADER --version) < /dev/null > /dev/null 2>&1 || {
+#        echo
+#        echo "You must have $AUTOHEADER installed to compile $PROJECT."
+#        echo "Download the appropriate package for your distribution,"
+#        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+#        DIE=1
+#}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOMAKE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOCONF installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+if test "$DIE" -eq 1; then
+        exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+        echo "You must run this script in the top-level $PROJECT directory"
+        exit 1
+}
+
+if test -z "$*"; then
+        echo "I am going to run ./configure with no arguments - if you wish "
+        echo "to pass any to it, please specify them on the $0 command line."
+fi
+
+#$LIBTOOLIZE --copy --force || echo "$LIBTOOlIZE failed"
+$ACLOCAL || echo "$ACLOCAL failed"
+#$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/.cvsignore	(revision 22157)
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/Makefile.am	(revision 22157)
@@ -0,0 +1,27 @@
+
+installdir = $(datadir)/ippconfig/cfh12k
+
+install_files = \
+	dvo.config \
+	dvo.layout \
+	rejections.config \
+	camera.config \
+	format_mef.config \
+	format_mef_early.config \
+	format_split.config \
+	format_split_early.config \
+	format_split_cmf.config \
+	ppImage.config \
+	ppMerge.config \
+	psastro.config \
+	psphot.config \
+	dark.config
+
+install_DATA = $(install_files)
+
+install-data-hook:
+	chmod 0755 $(installdir)
+
+EXTRA_DIST = $(install_files)
+
+ACLOCAL_AMFLAGS = -I m4
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/camera.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/camera.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/camera.config	(revision 22157)
@@ -0,0 +1,133 @@
+# Camera configuration file for Cfh12k: describes the camera
+
+# File formats that we know about
+FORMATS		METADATA
+#	EARLY	STR	cfh12k/format_mef_early.config
+#	ESPLIT	STR	cfh12k/format_split_early.config
+	MEF	STR	cfh12k/format_mef.config
+	SPLIT	STR	cfh12k/format_split.config
+	CMF	STR	cfh12k/format_split_cmf.config
+END
+
+# Description of camera --- all the chips and the cells that comprise them
+FPA	METADATA
+	ccd00	STR	Amp
+	ccd01	STR	Amp
+	ccd02	STR	Amp
+	ccd03	STR	Amp
+	ccd04	STR	Amp
+	ccd05	STR	Amp
+	ccd06	STR	Amp
+	ccd07	STR	Amp
+	ccd08	STR	Amp
+	ccd09	STR	Amp
+	ccd10	STR	Amp
+	ccd11	STR	Amp
+END
+
+# move this elsewhere?  we need a lookup table to go from filter ID to abstract name
+FILTER.ID       METADATA
+   B       STR    B           	
+   V       STR    V           	
+   R       STR    R           	
+   I       STR    I           	
+   	              		
+   Z       MULTI
+   Z       STR    Z           	
+   Z       STR    Zp           
+   Z       STR    Zprime       
+   	              		
+   Ha      MULTI
+   Ha      STR    Ha           
+   Ha      STR    Ha.MP7605    
+   Ha      STR    Halpha       
+   Ha      STR    Haalpha.on   
+   	              		
+   HaOff   MULTI
+   HaOff   STR    HaOFF        
+   HaOff   STR    HaOFF.MP7604 
+   HaOff   STR    Halpha.off   
+   	              		
+   Hb      STR    Hb           
+   HbOff   STR    HbOff        
+   	              		
+   CN      MULTI
+   CN      STR    CN           
+   CN      STR    CN.MP780     
+   CN      STR    cn.MP7803    
+   CN      STR    CN.MP7803    
+   	              		
+   TiO     MULTI
+   TiO     STR    TiO          
+   TiO     STR    TiO.MP77     
+   TiO     STR    tio.MP7701   
+   TiO     STR    TiO.MP7701   
+   	              		
+   NB920   STR    NB920        
+   	              		
+   B2F     MULTI
+   B2F     STR    B2F          
+   B2F     STR    UKN:B2F
+END
+
+# Table of strings to use for the class identifier (see ippdb) according to the file level.
+CLASSID		METADATA
+	fpa	STR	fpa
+	chip	STR	{CHIP.NAME}
+	cell	STR	{CHIP.NAME}:{CELL.NAME}
+	FPA	STR	fpa
+	CHIP	STR	{CHIP.NAME}
+	CELL	STR	{CHIP.NAME}:{CELL.NAME}
+END
+
+DVO.CAMERADIR	STR	cfh12k		# Camera directory for DVO
+
+DARK.NORM 	STR cfh12k/dark.config
+DARK.NORM.KEY 	STR fpa_{CHIP.NAME}_Amp
+# DARK.NORM.KEY STR BASIC ## rough version from global stats
+
+# convert supplied FPA.OBSTYPE values to abstract exptype names
+OBSTYPE.TABLE METADATA
+  bias 	   STR BIAS
+  zero 	   STR BIAS
+  dark 	   STR DARK
+  flat 	   STR SKYFLAT
+  skyflat  STR SKYFLAT
+  domeflat STR DOMEFLAT
+  object   STR OBJECT
+  science  STR OBJECT
+END
+
+# Recipe options
+RECIPES		METADATA
+	# Other recipes
+        PSPHOT          STR     cfh12k/psphot.config           # psphot details
+        PSASTRO         STR     cfh12k/psastro.config          # psastro details
+	PPIMAGE         STR     cfh12k/ppImage.config		# ppImage recipe
+	REJECTIONS      STR     cfh12k/rejections.config	# rejection recipe
+END
+
+# reduction classes (recipes which are grouped together)
+REDUCTION	STR	recipes/reductionClasses.mdc
+
+FITSTYPES       STR     recipes/fitstypes.mdc
+
+FILERULES	STR	recipes/filerules-mef.mdc		# File rules appropriate for MEF format
+
+EXTNAME.RULES METADATA
+  CMF.HEAD STR {CHIP.NAME}.hdr
+  CMF.DATA STR {CHIP.NAME}.psf # use .PSF and .EXT?
+  CMF.XSRC STR {CHIP.NAME}.xsrc # use .PSF and .EXT?
+  CMF.XFIT STR {CHIP.NAME}.xfit # use .PSF and .EXT?
+
+  PSF.HEAD  STR {CHIP.NAME}.hdr
+  PSF.TABLE STR {CHIP.NAME}.psf_model
+  PSF.RESID STR {CHIP.NAME}.psf_resid
+END
+
+BLANK.HEADERS	METADATA
+	FPA.TIME	STR	MJD-OBS
+	FPA.EXPOSURE	STR	EXPTIME
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.NAME	STR	EXPNUM
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/dark.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/dark.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/dark.config	(revision 22157)
@@ -0,0 +1,106 @@
+
+# basic fit to mosaic-level bg
+BASIC METADATA
+   NORDER_X         S32       2                # number of x orders
+   VAL_X00          F64      -1.366
+   VAL_X01          F64       8.974e-03
+   VAL_X02          F64       7.864e-06
+   NELEMENTS        S32       3                # number of unmasked components
+END
+
+fpa_ccd00_Amp METADATA 
+   NORDER_X  S32 2    
+   VAL_X00   F64 -2.42585939637 
+   VAL_X01   F64 0.0218932386738 
+   VAL_X02   F64 1.63910422999e-05 
+   NELEMENTS S32 3     
+END 
+
+fpa_ccd01_Amp METADATA 
+   NORDER_X  S32 2    
+   VAL_X00   F64 -0.673311998929 
+   VAL_X01   F64 0.00351724865032 
+   VAL_X02   F64 3.82106321907e-06 
+   NELEMENTS S32 3     
+END 
+
+fpa_ccd02_Amp METADATA 
+   NORDER_X  S32 2    
+   VAL_X00   F64 -0.516624899086 
+   VAL_X01   F64 0.002322522999 
+   VAL_X02   F64 3.24144163863e-06 
+   NELEMENTS S32 3     
+END 
+
+fpa_ccd03_Amp METADATA 
+   NORDER_X  S32 2    
+   VAL_X00   F64 -0.571626302054 
+   VAL_X01   F64 0.00233441005459 
+   VAL_X02   F64 2.35198601494e-06 
+   NELEMENTS S32 3     
+END 
+
+fpa_ccd04_Amp METADATA 
+   NORDER_X  S32 2    
+   VAL_X00   F64 -2.06687190434 
+   VAL_X01   F64 0.00214124485304 
+   VAL_X02   F64 2.26141245447e-06 
+   NELEMENTS S32 3     
+END 
+
+fpa_ccd05_Amp METADATA 
+   NORDER_X  S32 2    
+   VAL_X00   F64 -0.938425768984 
+   VAL_X01   F64 0.00787794459813 
+   VAL_X02   F64 6.18544058024e-06 
+   NELEMENTS S32 3     
+END 
+
+fpa_ccd06_Amp METADATA 
+   NORDER_X  S32 2    
+   VAL_X00   F64 -1.01351177035 
+   VAL_X01   F64 0.00665585528024 
+   VAL_X02   F64 6.34327926043e-06 
+   NELEMENTS S32 3     
+END 
+
+fpa_ccd07_Amp METADATA 
+   NORDER_X  S32 2    
+   VAL_X00   F64 -0.475706521997 
+   VAL_X01   F64 0.00151765156459 
+   VAL_X02   F64 3.19325558938e-06 
+   NELEMENTS S32 3     
+END 
+
+fpa_ccd08_Amp METADATA 
+   NORDER_X  S32 2    
+   VAL_X00   F64 -0.555167026441 
+   VAL_X01   F64 0.00287603928732 
+   VAL_X02   F64 4.31016033628e-06 
+   NELEMENTS S32 3     
+END 
+
+fpa_ccd09_Amp METADATA 
+   NORDER_X  S32 2    
+   VAL_X00   F64 -0.999503837142 
+   VAL_X01   F64 0.00530642880692 
+   VAL_X02   F64 5.64208407175e-06 
+   NELEMENTS S32 3     
+END 
+
+fpa_ccd10_Amp METADATA 
+   NORDER_X  S32 2    
+   VAL_X00   F64 -1.66026335274 
+   VAL_X01   F64 0.0163728070101 
+   VAL_X02   F64 1.28840808261e-05 
+   NELEMENTS S32 3     
+END 
+
+fpa_ccd11_Amp METADATA 
+   NORDER_X  S32 2    
+   VAL_X00   F64 -4.47794069907 
+   VAL_X01   F64 0.0347579009973 
+   VAL_X02   F64 2.78713468204e-05 
+   NELEMENTS S32 3     
+END 
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/dvo.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/dvo.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/dvo.config	(revision 22157)
@@ -0,0 +1,64 @@
+
+# location of DVO database tables
+CATDIR			/data/alala.0/eugene/isp/catdir
+
+# keywords used by DVO to interpret the headers
+
+# keyword abstractions:
+#DATE-KEYWORD		DATE-OBS
+#DATE-MODE		yyyy-mm-dd
+#UT-KEYWORD		UTC-OBS
+JD-KEYWORD		NONE
+MJD-KEYWORD		MJD-OBS
+
+# other keyword abstractions
+EXPTIME-KEYWORD		EXPTIME
+AIRMASS-KEYWORD		AIRMASS
+CCDNUM-KEYWORD		EXTNAME
+EXTNAME-KEYWORD		IMAGEID
+ST-KEYWORD		NONE
+OBSERVATORY-LATITUDE	NONE
+OBSERVATORY-LONGITUDE	NONE
+SUBPIX_DATAFILE		NONE
+
+# instrumental magnitude range for calibration mode 
+CAL_INSTMAG_MAX		0
+CAL_INSTMAG_MIN		0
+
+# exclude overscan region from the dB image boundaries
+XOVERSCAN		0
+YOVERSCAN		0
+
+# only upload stars within region; a value of 0 means ignore the limit
+ADDSTAR_XMIN		0
+ADDSTAR_XMAX		0
+ADDSTAR_YMIN		0
+ADDSTAR_YMAX		0
+
+# exclude stars with SN > SNLIMIT (ADDSTAR_SNLIMIT overrides old name MIN_SN_FSTAT)
+ADDSTAR_SNLIMIT		0
+
+# correlation radius (arcseconds)
+ADDSTAR_RADIUS		1.0
+
+# scaled correlation radius 
+ADDSTAR_NSIGMA		0
+
+IMAGE_SCATTER           0.075  # mark images POOR if stdev(Mcal) > IMAGE_SCATTER
+STAR_SCATTER            0.005  # mark stars POOR if stdev(Mrel) > STAR_SCATTER
+IMAGE_OFFSET            0.100  # mark images POOR if abs(delta(Mcal)) > IMAGE_OFFSET
+STAR_CHISQ              10.0   # mark stars POOR if Xm > STAR_CHISQ
+STAR_TOOFEW              3     # mark star FEW if N(good) < STAR_TOOFEW
+IMAGE_TOOFEW            10     # mark image FEW if N(good) < IMAGE_TOOFEW
+IMAGE_GOOD_FRACTION     0.05   # mark image FEW if N(good) < IMAGE_GOOD_FRACTION * Nstars
+
+SCATTER_LIM             15.0
+MAG_LIM                 20.0   # select stars brighter than this for relphot analysis
+SIGMA_LIM               0.015  # select measurements with dM < SIGMA_LIM for relphot analysis
+# INST_MAG_MIN         -17     # optional constraints on magnitude ranges
+# INST_MAG_MAX         -13     # optional constraints on magnitude ranges
+
+RELPHOT_GRID_BINNING    256
+RELPHOT_GRID_X 6
+RELPHOT_GRID_Y 14
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/dvo.layout
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/dvo.layout	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/dvo.layout	(revision 22157)
@@ -0,0 +1,29 @@
+# this file defines the layout of the mosaic imager:
+
+NCCD 12
+NAXIS1 2080
+NAXIS2 4128
+
+MOSAIC_X 6
+MOSAIC_Y 2
+
+# lines need to contain:
+
+CHIPID_KEYWORD CHIPID
+
+# ID  CHIPID xoffset yoffset xflip yflip datasec         biassec                 Xo    Yo     theta
+CCD.0  chip00      0       0     0     0 [6:2049,5:4100] [2050:2080,2:4128]     0.0    0.0   0.00
+CCD.1  chip01      1       0     0     0 [6:2049,5:4100] [2050:2080,2:4128]  2000.0    0.0   0.00
+CCD.2  chip02      2       0     1     0 [6:2049,5:4100] [2050:2080,2:4128]  6000.0    0.0   0.00
+CCD.3  chip03      3       0     1     0 [6:2049,5:4100] [2050:2080,2:4128]  8000.0    0.0   0.00
+CCD.4  chip04      4       0     0     0 [6:2049,5:4100] [2050:2080,2:4128]  8000.0    0.0   0.00
+CCD.5  chip05      5       0     0     0 [6:2049,5:4100] [2050:2080,2:4128] 10000.0    0.0   0.00
+CCD.6  chip06      0       1     0     1 [6:2049,5:4100] [2050:2080,2:4128]     0.0 8000.0   1.00
+CCD.7  chip07      1       1     0     1 [6:2049,5:4100] [2050:2080,2:4128]  2000.0 8000.0   0.00
+CCD.8  chip08      2       1     0     1 [6:2049,5:4100] [2050:2080,2:4128]  4000.0 8000.0   0.00
+CCD.9  chip09      3       1     0     1 [6:2049,5:4100] [2050:2080,2:4128]  6000.0 8000.0   0.00
+CCD.10 chip10      4       1     0     1 [6:2049,5:4100] [2050:2080,2:4128]  8000.0 8000.0   0.00
+CCD.11 chip11      5       1     0     1 [6:2049,5:4100] [2050:2080,2:4128] 10000.0 8000.0   0.00
+
+# can we use header to find biassec
+USE_BIASSEC 1
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/format_mef.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/format_mef.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/format_mef.config	(revision 22157)
@@ -0,0 +1,166 @@
+# "mcshort" is a MegaCam camera with only the central six chips --- it's faster than the entire FPA.
+# The spliced MecaCam data is stored in single extensions for each chip
+
+# How to recognise this type
+RULE	METADATA
+	TELESCOP	STR	CFHT 3.6m
+	DETECTOR	STR	CFH12K
+	INSTRUME	STR	CFH12K Mosaic
+	EXTEND		BOOL	T
+END
+
+FILE	METADATA
+	# How to read this data
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	CHIP	# The extensions represent chips
+	FPA.OBS		STR	EXPNUM	# A PHU keyword for unique identifier
+	CHIP.NAME	STR	IMAGEID	# An extension keyword for unique identifie
+	EXTWORD         STR     IMAGEID
+END
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# Extension name, chip name:component cell
+	 0		STR	ccd00:MIT-LL
+	 1		STR	ccd01:MIT-LL
+	 2		STR	ccd02:MIT-LL
+	 3		STR	ccd03:MIT-LL
+	 4		STR	ccd04:MIT-LL
+	 5		STR	ccd05:MIT-LL
+	 6		STR	ccd06:MIT-LL
+	 7		STR	ccd07:MIT-LL
+	 8		STR	ccd08:MIT-LL
+	 9		STR	ccd09:MIT-LL
+	10		STR	ccd10:MIT-LL
+	11		STR	ccd11:MIT-LL
+END
+
+# Specify the chips
+CHIPS		METADATA
+	# Chip type, cellName:cellType
+	MIT-LL	STR	Amp:cell
+END
+
+# Specify the cells
+CELLS		METADATA
+	cell		METADATA
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BIASSEC
+		CELL.TRIMSEC		STR	DATASEC
+		CELL.X0			S32	0
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.OBSTYPE	STR	OBSTYPE
+	FPA.OBJECT	STR	OBJECT
+        FPA.AIRMASS     STR     AIRMASS
+        FPA.FILTERID    STR     FILTER
+        FPA.FILTER      STR     FILTER
+        FPA.POSANGLE    STR     ROTANGLE
+        FPA.RA          STR     RA
+        FPA.DEC         STR     DEC
+        FPA.RADECSYS    STR     RADECSYS
+	FPA.FOCUS	STR	TELFOCUS
+	FPA.TIME	STR	MJD-OBS
+	FPA.TIMESYS	STR	TIMESYS
+	FPA.ALT		STR	TELALT
+	FPA.AZ		STR	TELAZ
+	FPA.TEMP	STR	DETTEM
+	FPA.EXPOSURE	STR	EXPTIME
+	CHIP.TEMP	STR	DETTEM
+        CELL.EXPOSURE   STR     EXPTIME
+        CELL.DARKTIME   STR     DARKTIME
+	CELL.TIME	STR	MJD-OBS
+        CELL.GAIN       STR     GAIN
+        CELL.SATURATION STR     SATURATE
+	CELL.TIMESYS	STR	TIMESYS
+        CELL.XBIN       STR     CCDBIN1
+        CELL.YBIN       STR     CCDBIN2
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS        METADATA
+	FPA.TELESCOPE		STR	CFHT
+	FPA.INSTRUMENT		STR	CFH12K		# this value is used in ipprc.config to find the recipes
+	FPA.DETECTOR	        STR	CFH12K
+	CHIP.XSIZE		S32	2048
+	CHIP.YSIZE		S32	4096
+	CELL.XSIZE		S32	2048
+	CELL.YSIZE		S32	4096
+	CELL.READNOISE          F32     10.0
+	CELL.READDIR		S32	1		# Cell is read in x direction
+        CELL.BAD                S32     0
+	CELL.XPARITY		S32	1
+	CELL.YPARITY		S32	1
+	CELL.Y0			S32	0
+	CHIP.X0.DEPEND		STR	CHIP.NAME
+	CHIP.X0		METADATA
+		ccd00	S32	10240
+		ccd01	S32	8192
+		ccd02	S32	6144
+		ccd03	S32	4096
+		ccd04	S32	2048
+		ccd05	S32	0
+		ccd06	S32	10240
+		ccd07	S32	8192 
+		ccd08	S32	6144 
+		ccd09	S32	4096 
+		ccd10	S32	2048 
+		ccd11	S32	0    
+	END
+	CHIP.Y0.DEPEND		STR	CHIP.NAME
+	CHIP.Y0		METADATA
+		ccd00	S32	0
+		ccd01	S32	0
+		ccd02	S32	0
+		ccd03	S32	0
+		ccd04	S32	0
+		ccd05	S32	0
+		ccd06	S32	0
+		ccd07	S32	0
+		ccd08	S32	0
+		ccd09	S32	0
+		ccd10	S32	0
+		ccd11	S32	0
+	END
+	CHIP.XPARITY		S32	-1
+	CHIP.YPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.YPARITY	METADATA
+		ccd00	S32	-1
+		ccd01	S32	-1
+		ccd02	S32	-1
+		ccd03	S32	-1
+		ccd04	S32	-1
+		ccd05	S32	-1
+		ccd06	S32	+1
+		ccd07	S32	+1
+		ccd08	S32	+1
+		ccd09	S32	+1
+		ccd10	S32	+1
+		ccd11	S32	+1
+	END
+END
+
+# How to translation PS concepts into database lookups
+DATABASE	METADATA
+	TYPE		dbEntry		TABLE		COLUMN		GIVENDBCOL	GIVENPS
+#	CELL.GAIN	dbEntry		Camera		gain		chipId,cellId	CHIP.NAME,CELL.NAME
+#	CELL.READNOISE	dbEntry		Camera		readNoise	chipId,cellId	CHIP.NAME,CELL.NAME
+
+# A database entry refers to a particular column (COLUMN) in a
+# particular table (TABLE), given certain PS concepts (GIVENPS) that
+# match certain database columns (GIVENDBCOL).
+END		
+
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
+ 
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/format_mef_early.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/format_mef_early.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/format_mef_early.config	(revision 22157)
@@ -0,0 +1,184 @@
+# "mcshort" is a MegaCam camera with only the central six chips --- it's faster than the entire FPA.
+# The spliced MecaCam data is stored in single extensions for each chip
+
+# How to recognise this type
+RULE	METADATA
+	TELESCOP	STR	CFHT 3.6m
+	DETECTOR	STR	CFH12K
+	INSTRUME	STR	CFH12K Mosaic
+	EXTEND		BOOL	T
+        IMAGESWV        MULTI
+	IMAGESWV	STR     CFHT DetCom v3.25 (Jun 13 2001)    
+	IMAGESWV	STR     CFHT DetCom v3.28 (Jul 11 2001)    
+	IMAGESWV	STR     CFHT DetCom v3.35 (May 01 2002)
+END
+
+FILE	METADATA
+	# How to read this data
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	CHIP	# The extensions represent chips
+	FPA.OBS		STR	EXPNUM	# A PHU keyword for unique identifier
+	CHIP.NAME	STR	IMAGEID	# An extension keyword for unique identifie
+	EXTWORD         STR     IMAGEID
+END
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# Extension name, chip name:component cell
+	 0		STR	ccd00:MIT-LL
+	 1		STR	ccd01:MIT-LL
+	 2		STR	ccd02:MIT-LL
+	 3		STR	ccd03:MIT-LL
+	 4		STR	ccd04:MIT-LL
+	 5		STR	ccd05:MIT-LL
+	 6		STR	ccd06:MIT-LL
+	 7		STR	ccd07:MIT-LL
+	 8		STR	ccd08:MIT-LL
+	 9		STR	ccd09:MIT-LL
+	10		STR	ccd10:MIT-LL
+	11		STR	ccd11:MIT-LL
+END
+
+# Specify the chips
+CHIPS		METADATA
+	# Chip type, cellName:cellType
+	MIT-LL	STR	Amp:cell
+END
+
+# Specify the cells
+CELLS		METADATA
+	cell		METADATA
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BIASSEC
+		CELL.TRIMSEC		STR	DATASEC
+		CELL.X0			S32	0
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+        FPA.AIRMASS     STR     AIRMASS
+	FPA.OBSTYPE	STR	OBSTYPE
+	FPA.OBJECT	STR	OBJECT
+        FPA.FILTERID    STR     FILTER
+        FPA.FILTER      STR     FILTER
+        FPA.POSANGLE    STR     ROTANGLE
+        FPA.RA          STR     RA
+        FPA.DEC         STR     DEC
+        FPA.RADECSYS    STR     RADECSYS
+	FPA.FOCUS	STR	TELFOCUS
+	FPA.TIME	STR	MJD-OBS
+	FPA.TIMESYS	STR	TIMESYS
+	FPA.ALT		STR	TELALT
+	FPA.AZ		STR	TELAZ
+	FPA.TEMP	STR	DETTEM
+	FPA.EXPOSURE	STR	EXPTIME
+	CHIP.TEMP	STR	DETTEM
+        CELL.EXPOSURE   STR     EXPTIME
+        CELL.DARKTIME   STR     DARKTIME
+	CELL.TIME	STR	MJD-OBS
+        CELL.GAIN       STR     GAIN
+        CELL.SATURATION STR     SATURATE
+	CELL.TIMESYS	STR	TIMESYS
+        CELL.XBIN       STR     CCDBIN1
+        CELL.YBIN       STR     CCDBIN2
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS        METADATA
+	FPA.TELESCOPE		STR	CFHT
+	FPA.INSTRUMENT		STR	CFH12K		# this value is used in ipprc.config to find the recipes
+	FPA.DETECTOR	        STR	CFH12K
+	CHIP.XSIZE		S32	2048
+	CHIP.YSIZE		S32	4096
+	CELL.XSIZE		S32	2048
+	CELL.YSIZE		S32	4096
+	CELL.READNOISE          F32     10.0
+	CELL.READDIR		S32	1		# Cell is read in x direction
+        CELL.BAD                S32     0
+	CELL.XPARITY		S32	1
+	CELL.YPARITY		S32	1
+	CELL.Y0			S32	0
+	CHIP.X0.DEPEND		STR	CHIP.NAME
+	CHIP.X0		METADATA
+		ccd00	S32	10
+		ccd01	S32	2048
+		ccd02	S32	4096
+		ccd03	S32	6144
+		ccd04	S32	8192
+		ccd05	S32	10240
+		ccd06	S32	0    
+		ccd07	S32	2048 
+		ccd08	S32	4096 
+		ccd09	S32	6144 
+		ccd10	S32	8192 
+		ccd11	S32	10240
+	END
+	CHIP.Y0.DEPEND		STR	CHIP.NAME
+	CHIP.Y0		METADATA
+		ccd00	S32	10
+		ccd01	S32	10
+		ccd02	S32	10
+		ccd03	S32	10
+		ccd04	S32	10
+		ccd05	S32	10
+		ccd06	S32	4128
+		ccd07	S32	4128
+		ccd08	S32	4128
+		ccd09	S32	4128
+		ccd10	S32	4128
+		ccd11	S32	4128
+	END
+	CHIP.XPARITY		S32	1
+	CHIP.YPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.YPARITY	METADATA
+		ccd00	S32	+1
+		ccd01	S32	+1
+		ccd02	S32	+1
+		ccd03	S32	+1
+		ccd04	S32	+1
+		ccd05	S32	+1
+		ccd06	S32	-1
+		ccd07	S32	-1
+		ccd08	S32	-1
+		ccd09	S32	-1
+		ccd10	S32	-1
+		ccd11	S32	-1
+	END
+END
+
+# How to translation PS concepts into database lookups
+DATABASE	METADATA
+	TYPE		dbEntry		TABLE		COLUMN		GIVENDBCOL	GIVENPS
+#	CELL.GAIN	dbEntry		Camera		gain		chipId,cellId	CHIP.NAME,CELL.NAME
+#	CELL.READNOISE	dbEntry		Camera		readNoise	chipId,cellId	CHIP.NAME,CELL.NAME
+
+# A database entry refers to a particular column (COLUMN) in a
+# particular table (TABLE), given certain PS concepts (GIVENPS) that
+# match certain database columns (GIVENDBCOL).
+END		
+
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
+ 
+#  fits  CFHT 12kcom v2 2 (Sep 28 1999)  
+#  fits  CFHT 12kcom v2 6 (Dec 01 1999)  
+#  fits  CFHT DetCom v3 1 (Oct 19 2000)  
+#  fits  CFHT DetCom v3 2 (Dec 23 2000)  
+#  fits  CFHT DetCom v3 22 (Apr 12 2001)  
+#  fits  CFHT DetCom v3 25 (Jun 13 2001)  
+#  fits  CFHT DetCom v3 32 (Oct 20 2001)  
+#  fits  CFHT DetCom v3 33 (Dec 09 2001)  
+#  fits  CFHT DetCom v3 35 (Jan 07 2002)  
+#  fits  CFHT DetCom v3 35 (Mar 09 2002)  
+#  fits  CFHT DetCom v3 35 (May 01 2002)  
+#  fits  CFHT DetCom v3 7 (Jun 29 2000)  
+#  fits  CFHT DetCom v3 8 (Aug 02 2000)  
+#  fits  CFHT DetCom v3 9 (Sep 15 2000)  
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/format_split.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/format_split.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/format_split.config	(revision 22157)
@@ -0,0 +1,169 @@
+# "mcshort" is a MegaCam camera with only the central six chips --- it's faster than the entire FPA.
+# The spliced MecaCam data is stored in single extensions for each chip
+
+# How to recognise this type
+RULE	METADATA
+	TELESCOP	STR	CFHT 3.6m
+	DETECTOR	STR	CFH12K
+	INSTRUME	STR	CFH12K Mosaic
+	NAXIS		S32	2
+END
+
+FILE	METADATA
+	# How to read this data
+	PHU		STR	CHIP	 # The FITS file represents an entire FPA
+	EXTENSIONS	STR	NONE	 # The extensions represent chips
+	FPA.OBS		STR	EXPNUM	 # A PHU keyword for unique identifier
+	CHIP.NAME	STR	IMAGEID	 # An extension keyword for unique identifie
+	EXTWORD         STR     IMAGEID
+	CONTENT		STR	IMAGEID	 # Key to the CONTENTS menu
+	CONTENT.RULE	STR	{CHIP.N} # How to derive the CONTENT when writing
+END
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# Content key   ->      chip name:component cell
+	0		STR	ccd00:MIT-LL
+	1		STR	ccd01:MIT-LL
+	2		STR	ccd02:MIT-LL
+	3		STR	ccd03:MIT-LL
+	4		STR	ccd04:MIT-LL
+	5		STR	ccd05:MIT-LL
+	6		STR	ccd06:MIT-LL
+	7		STR	ccd07:MIT-LL
+	8		STR	ccd08:MIT-LL
+	9		STR	ccd09:MIT-LL
+	10		STR	ccd10:MIT-LL
+	11		STR	ccd11:MIT-LL
+END
+
+# Specify the chips
+CHIPS		METADATA
+	MIT-LL	STR	Amp:cell
+END
+
+
+# Specify the cells
+CELLS		METADATA
+	cell		METADATA
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BIASSEC
+		CELL.TRIMSEC		STR	DATASEC
+		CELL.X0			S32	0
+		CELL.GAIN.SOURCE	STR	HEADER
+		CELL.GAIN		STR	GAIN
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.OBSTYPE	STR	OBSTYPE
+	FPA.OBJECT	STR	OBSTYPE
+        FPA.AIRMASS     STR     AIRMASS
+        FPA.FILTERID    STR     FILTER
+        FPA.FILTER      STR     FILTER
+        FPA.POSANGLE    STR     ROTANGLE
+        FPA.RA          STR     RA
+        FPA.DEC         STR     DEC
+        FPA.RADECSYS    STR     RADECSYS
+	FPA.FOCUS	STR	TELFOCUS
+	FPA.TIME	STR	MJD-OBS
+	FPA.TIMESYS	STR	TIMESYS
+	FPA.ALT		STR	TELALT
+	FPA.AZ		STR	TELAZ
+	FPA.TEMP	STR	DETTEM
+	FPA.EXPOSURE	STR	EXPTIME
+	CHIP.TEMP	STR	DETTEM
+        CELL.EXPOSURE   STR     EXPTIME
+        CELL.DARKTIME   STR     DARKTIME
+        CELL.SATURATION STR     SATURATE
+	CELL.TIME	STR	MJD-OBS
+	CELL.TIMESYS	STR	TIMESYS
+        CELL.XBIN       STR     CCDBIN1
+        CELL.YBIN       STR     CCDBIN2
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS        METADATA
+	FPA.TELESCOPE		STR	CFHT
+	FPA.INSTRUMENT		STR	CFH12K		# this value is used in ipprc.config to find the recipes
+	FPA.DETECTOR	        STR	CFH12K
+	CHIP.XSIZE		S32	2048
+	CHIP.YSIZE		S32	4096
+	CELL.XSIZE		S32	2048
+	CELL.YSIZE		S32	4096
+	CELL.READNOISE          F32     10.0
+	CELL.READDIR		S32	1		# Cell is read in x direction
+        CELL.BAD                S32     0
+	CELL.XPARITY		S32	1
+	CELL.YPARITY		S32	1
+	CELL.Y0			S32	0
+	CHIP.X0.DEPEND		STR	CHIP.NAME
+	CHIP.X0		METADATA
+		ccd00	S32	0
+		ccd01	S32	2048
+		ccd02	S32	4096
+		ccd03	S32	6144
+		ccd04	S32	8192
+		ccd05	S32	10240
+		ccd06	S32	0    
+		ccd07	S32	2048 
+		ccd08	S32	4096 
+		ccd09	S32	6144 
+		ccd10	S32	8192 
+		ccd11	S32	10240
+	END
+	CHIP.Y0.DEPEND		STR	CHIP.NAME
+	CHIP.Y0		METADATA
+		ccd00	S32	0
+		ccd01	S32	0
+		ccd02	S32	0
+		ccd03	S32	0
+		ccd04	S32	0
+		ccd05	S32	0
+		ccd06	S32	4128
+		ccd07	S32	4128
+		ccd08	S32	4128
+		ccd09	S32	4128
+		ccd10	S32	4128
+		ccd11	S32	4128
+	END
+	CHIP.XPARITY		S32	1
+	CHIP.YPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.YPARITY	METADATA
+		ccd00	S32	+1
+		ccd01	S32	+1
+		ccd02	S32	+1
+		ccd03	S32	+1
+		ccd04	S32	+1
+		ccd05	S32	+1
+		ccd06	S32	-1
+		ccd07	S32	-1
+		ccd08	S32	-1
+		ccd09	S32	-1
+		ccd10	S32	-1
+		ccd11	S32	-1
+	END
+END
+
+# How to translation PS concepts into database lookups
+DATABASE	METADATA
+	TYPE		dbEntry		TABLE		COLUMN		GIVENDBCOL	GIVENPS
+#	CELL.GAIN	dbEntry		Camera		gain		chipId,cellId	CHIP.NAME,CELL.NAME
+#	CELL.READNOISE	dbEntry		Camera		readNoise	chipId,cellId	CHIP.NAME,CELL.NAME
+
+# A database entry refers to a particular column (COLUMN) in a
+# particular table (TABLE), given certain PS concepts (GIVENPS) that
+# match certain database columns (GIVENDBCOL).
+END		
+
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
+ 
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/format_split_cmf.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/format_split_cmf.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/format_split_cmf.config	(revision 22157)
@@ -0,0 +1,169 @@
+# "mcshort" is a MegaCam camera with only the central six chips --- it's faster than the entire FPA.
+# The spliced MecaCam data is stored in single extensions for each chip
+
+# How to recognise this type
+RULE	METADATA
+	TELESCOP	STR	CFHT 3.6m
+	DETECTOR	STR	CFH12K
+	INSTRUME	STR	CFH12K Mosaic
+	NAXIS		S32	0
+END
+
+FILE	METADATA
+	# How to read this data
+	PHU		STR	CHIP	 # The FITS file represents an entire FPA
+	EXTENSIONS	STR	NONE	 # The extensions represent chips
+	FPA.OBS		STR	EXPNUM	 # A PHU keyword for unique identifier
+	CHIP.NAME	STR	IMAGEID	 # An extension keyword for unique identifie
+	EXTWORD         STR     IMAGEID
+	CONTENT		STR	IMAGEID	 # Key to the CONTENTS menu
+	CONTENT.RULE	STR	{CHIP.N} # How to derive the CONTENT when writing
+END
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# Content key   ->      chip name:component cell
+	ccd00.hdr	STR	ccd00:MIT-LL
+	ccd01.hdr	STR	ccd01:MIT-LL
+	ccd02.hdr	STR	ccd02:MIT-LL
+	ccd03.hdr	STR	ccd03:MIT-LL
+	ccd04.hdr	STR	ccd04:MIT-LL
+	ccd05.hdr	STR	ccd05:MIT-LL
+	ccd06.hdr	STR	ccd06:MIT-LL
+	ccd07.hdr	STR	ccd07:MIT-LL
+	ccd08.hdr	STR	ccd08:MIT-LL
+	ccd09.hdr	STR	ccd09:MIT-LL
+	ccd10.hdr	STR	ccd10:MIT-LL
+	ccd11.hdr	STR	ccd11:MIT-LL
+END
+
+# Specify the chips
+CHIPS		METADATA
+	MIT-LL	STR	Amp:cell
+END
+
+
+# Specify the cells
+CELLS		METADATA
+	cell		METADATA
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BIASSEC
+		CELL.TRIMSEC		STR	DATASEC
+		CELL.X0			S32	0
+		CELL.GAIN.SOURCE	STR	HEADER
+		CELL.GAIN		STR	GAIN
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.OBSTYPE	STR	OBSTYPE
+	FPA.OBJECT	STR	OBSTYPE
+        FPA.AIRMASS     STR     AIRMASS
+        FPA.FILTERID    STR     FILTER
+        FPA.FILTER      STR     FILTER
+        FPA.POSANGLE    STR     ROTANGLE
+        FPA.RA          STR     RA
+        FPA.DEC         STR     DEC
+        FPA.RADECSYS    STR     RADECSYS
+	FPA.FOCUS	STR	TELFOCUS
+	FPA.TIME	STR	MJD-OBS
+	FPA.TIMESYS	STR	TIMESYS
+	FPA.ALT		STR	TELALT
+	FPA.AZ		STR	TELAZ
+	FPA.TEMP	STR	DETTEM
+	FPA.EXPOSURE	STR	EXPTIME
+	CHIP.TEMP	STR	DETTEM
+        CELL.EXPOSURE   STR     EXPTIME
+        CELL.DARKTIME   STR     DARKTIME
+        CELL.SATURATION STR     SATURATE
+	CELL.TIME	STR	MJD-OBS
+	CELL.TIMESYS	STR	TIMESYS
+        CELL.XBIN       STR     CCDBIN1
+        CELL.YBIN       STR     CCDBIN2
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS        METADATA
+	FPA.TELESCOPE		STR	CFHT
+	FPA.INSTRUMENT		STR	CFH12K		# this value is used in ipprc.config to find the recipes
+	FPA.DETECTOR	        STR	CFH12K
+	CHIP.XSIZE		S32	2048
+	CHIP.YSIZE		S32	4096
+	CELL.XSIZE		S32	2048
+	CELL.YSIZE		S32	4096
+	CELL.READNOISE          F32     10.0
+	CELL.READDIR		S32	1		# Cell is read in x direction
+        CELL.BAD                S32     0
+	CELL.XPARITY		S32	1
+	CELL.YPARITY		S32	1
+	CELL.Y0			S32	0
+	CHIP.X0.DEPEND		STR	CHIP.NAME
+	CHIP.X0		METADATA
+		ccd00	S32	0
+		ccd01	S32	2048
+		ccd02	S32	4096
+		ccd03	S32	6144
+		ccd04	S32	8192
+		ccd05	S32	10240
+		ccd06	S32	0    
+		ccd07	S32	2048 
+		ccd08	S32	4096 
+		ccd09	S32	6144 
+		ccd10	S32	8192 
+		ccd11	S32	10240
+	END
+	CHIP.Y0.DEPEND		STR	CHIP.NAME
+	CHIP.Y0		METADATA
+		ccd00	S32	0
+		ccd01	S32	0
+		ccd02	S32	0
+		ccd03	S32	0
+		ccd04	S32	0
+		ccd05	S32	0
+		ccd06	S32	4128
+		ccd07	S32	4128
+		ccd08	S32	4128
+		ccd09	S32	4128
+		ccd10	S32	4128
+		ccd11	S32	4128
+	END
+	CHIP.XPARITY		S32	1
+	CHIP.YPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.YPARITY	METADATA
+		ccd00	S32	+1
+		ccd01	S32	+1
+		ccd02	S32	+1
+		ccd03	S32	+1
+		ccd04	S32	+1
+		ccd05	S32	+1
+		ccd06	S32	-1
+		ccd07	S32	-1
+		ccd08	S32	-1
+		ccd09	S32	-1
+		ccd10	S32	-1
+		ccd11	S32	-1
+	END
+END
+
+# How to translation PS concepts into database lookups
+DATABASE	METADATA
+	TYPE		dbEntry		TABLE		COLUMN		GIVENDBCOL	GIVENPS
+#	CELL.GAIN	dbEntry		Camera		gain		chipId,cellId	CHIP.NAME,CELL.NAME
+#	CELL.READNOISE	dbEntry		Camera		readNoise	chipId,cellId	CHIP.NAME,CELL.NAME
+
+# A database entry refers to a particular column (COLUMN) in a
+# particular table (TABLE), given certain PS concepts (GIVENPS) that
+# match certain database columns (GIVENDBCOL).
+END		
+
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
+ 
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/format_split_early.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/format_split_early.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/format_split_early.config	(revision 22157)
@@ -0,0 +1,173 @@
+# This is the format file for split CFH12K images from the period when the 
+# EXTNAME field was not being populated
+
+# How to recognise this type
+RULE	METADATA
+	TELESCOP	STR	CFHT 3.6m
+	DETECTOR	STR	CFH12K
+	INSTRUME	STR	CFH12K Mosaic
+	NAXIS		S32	2
+        IMAGESWV        MULTI
+	IMAGESWV	STR     CFHT DetCom v3.25 (Jun 13 2001)    
+	IMAGESWV	STR     CFHT DetCom v3.28 (Jul 11 2001)    
+	IMAGESWV	STR     CFHT DetCom v3.35 (May 01 2002)
+END
+
+FILE	METADATA
+	# How to read this data
+	PHU		STR	CHIP	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	NONE	# The extensions represent chips
+	FPA.OBS		STR	EXPNUM	# A PHU keyword for unique identifier
+	CHIP.NAME	STR	IMAGEID	# An extension keyword for unique identifie
+	EXTWORD         STR     IMAGEID
+	CONTENT		STR	IMAGEID
+	CONTENT.RULE	STR	{CHIP.N}
+END
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# Extension name, chip name:component cell
+	 0		STR	ccd00:MIT-LL
+	 1		STR	ccd01:MIT-LL
+	 2		STR	ccd02:MIT-LL
+	 3		STR	ccd03:MIT-LL
+	 4		STR	ccd04:MIT-LL
+	 5		STR	ccd05:MIT-LL
+	 6		STR	ccd06:MIT-LL
+	 7		STR	ccd07:MIT-LL
+	 8		STR	ccd08:MIT-LL
+	 9		STR	ccd09:MIT-LL
+	10		STR	ccd10:MIT-LL
+	11		STR	ccd11:MIT-LL
+END
+
+# Specify the chips
+CHIPS		METADATA
+	# Chip type, cellName:cellType
+	MIT-LL	STR	Amp:cell
+END
+
+# Specify the cells
+CELLS		METADATA
+	cell		METADATA
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BIASSEC
+		CELL.TRIMSEC		STR	DATASEC
+		CELL.X0			S32	0
+		CELL.GAIN.SOURCE	STR	HEADER
+		CELL.GAIN		STR	GAIN
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.OBSTYPE	STR	OBSTYPE
+	FPA.OBJECT	STR	OBSTYPE
+        FPA.AIRMASS     STR     AIRMASS
+        FPA.FILTERID    STR     FILTER
+        FPA.FILTER      STR     FILTER
+        FPA.POSANGLE    STR     ROTANGLE
+        FPA.RA          STR     RA
+        FPA.DEC         STR     DEC
+        FPA.RADECSYS    STR     RADECSYS
+	FPA.FOCUS	STR	TELFOCUS
+	FPA.TIME	STR	MJD-OBS
+	FPA.TIMESYS	STR	TIMESYS
+	FPA.ALT		STR	TELALT
+	FPA.AZ		STR	TELAZ
+	FPA.TEMP	STR	DETTEM
+	FPA.EXPOSURE	STR	EXPTIME
+	CHIP.TEMP	STR	DETTEM
+        CELL.EXPOSURE   STR     EXPTIME
+        CELL.DARKTIME   STR     DARKTIME
+        CELL.SATURATION STR     SATURATE
+	CELL.TIME	STR	MJD-OBS
+	CELL.TIMESYS	STR	TIMESYS
+        CELL.XBIN       STR     CCDBIN1
+        CELL.YBIN       STR     CCDBIN2
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS        METADATA
+	FPA.TELESCOPE		STR	CFHT
+	FPA.INSTRUMENT		STR	CFH12K		# this value is used in ipprc.config to find the recipes
+	FPA.DETECTOR	        STR	CFH12K
+	CHIP.XSIZE		S32	2048
+	CHIP.YSIZE		S32	4096
+	CELL.XSIZE		S32	2048
+	CELL.YSIZE		S32	4096
+	CELL.READNOISE          F32     10.0
+	CELL.READDIR		S32	1		# Cell is read in x direction
+        CELL.BAD                S32     0
+	CELL.XPARITY		S32	1
+	CELL.YPARITY		S32	1
+	CELL.Y0			S32	0
+	CHIP.X0.DEPEND		STR	CHIP.NAME
+	CHIP.X0		METADATA
+		ccd00	S32	0
+		ccd01	S32	2048
+		ccd02	S32	4096
+		ccd03	S32	6144
+		ccd04	S32	8192
+		ccd05	S32	10240
+		ccd06	S32	0    
+		ccd07	S32	2048 
+		ccd08	S32	4096 
+		ccd09	S32	6144 
+		ccd10	S32	8192 
+		ccd11	S32	10240
+	END
+	CHIP.Y0.DEPEND		STR	CHIP.NAME
+	CHIP.Y0		METADATA
+		ccd00	S32	0
+		ccd01	S32	0
+		ccd02	S32	0
+		ccd03	S32	0
+		ccd04	S32	0
+		ccd05	S32	0
+		ccd06	S32	4128
+		ccd07	S32	4128
+		ccd08	S32	4128
+		ccd09	S32	4128
+		ccd10	S32	4128
+		ccd11	S32	4128
+	END
+	CHIP.XPARITY		S32	1
+	CHIP.YPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.YPARITY	METADATA
+		ccd00	S32	+1
+		ccd01	S32	+1
+		ccd02	S32	+1
+		ccd03	S32	+1
+		ccd04	S32	+1
+		ccd05	S32	+1
+		ccd06	S32	-1
+		ccd07	S32	-1
+		ccd08	S32	-1
+		ccd09	S32	-1
+		ccd10	S32	-1
+		ccd11	S32	-1
+	END
+END
+
+# How to translation PS concepts into database lookups
+DATABASE	METADATA
+	TYPE		dbEntry		TABLE		COLUMN		GIVENDBCOL	GIVENPS
+#	CELL.GAIN	dbEntry		Camera		gain		chipId,cellId	CHIP.NAME,CELL.NAME
+#	CELL.READNOISE	dbEntry		Camera		readNoise	chipId,cellId	CHIP.NAME,CELL.NAME
+
+# A database entry refers to a particular column (COLUMN) in a
+# particular table (TABLE), given certain PS concepts (GIVENPS) that
+# match certain database columns (GIVENDBCOL).
+END		
+
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
+ 
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/ppImage.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/ppImage.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/ppImage.config	(revision 22157)
@@ -0,0 +1,59 @@
+### ppImage recipe configuration file for cfh12k
+
+# Overscan subtraction
+OVERSCAN.SINGLE		BOOL	FALSE		# Reduce overscan to a single value?
+OVERSCAN.FIT		STR	POLYNOMIAL	# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER		S32	3		# Order of polynomial fit
+OVERSCAN.STAT		STR	MEDIAN		# MEAN | MEDIAN
+
+# for a test, turn off selected detrend types
+OVERSCAN	BOOL	FALSE		# Overscan subtraction
+BIAS		BOOL	FALSE		# Bias subtraction
+DARK		BOOL	TRUE		# Dark subtraction
+FLAT		BOOL	TRUE		# Flat-field normalisation
+
+# binned output image options
+BIN1.XBIN		S32	12
+BIN1.YBIN		S32	12
+BIN2.XBIN		S32	96
+BIN2.YBIN		S32	96
+
+# cfh12k needs fringe subtraction
+FRINGE		BOOL	TRUE		# Fringe subtraction
+
+# this table lists extra constraints which should be applied when
+# selecting the detrend images.
+# note: camera and time are always applied
+
+DETREND.CONSTRAINTS  METADATA
+  BIAS METADATA
+  END
+  DARK METADATA
+#   EXPTIME STR FPA.EXPOSURE
+  END
+  FLAT METADATA
+    FILTER  STR FPA.FILTERID
+#   DETTYPE STR DOMEFLAT.RAW
+#   VERSION STR SKYFLAT.RAW
+#   VERSION STR DOMEFLAT.CORR.XX
+#   VERSION STR SKYFLAT.CORR
+  END
+  FLAT_CORRECTION METADATA
+    FILTER   STR FPA.FILTER
+  END
+  FRINGE METADATA
+    FILTER   STR FPA.FILTER
+#   AIRMASS  STR FPA.AIRMASS
+#   TWILIGHT STR FPA.TWILIGHT
+  END
+  SHUTTER METADATA
+  END	
+END
+
+# only apply the fringe for these filters
+FRINGE.FILTERS  MULTI
+FRINGE.FILTERS	STR r
+FRINGE.FILTERS	STR i
+FRINGE.FILTERS	STR z
+
+PHOTCODE.RULE		STR	{DETECTOR}.{FILTER.ID}.{CHIP.N}
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/ppMerge.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/ppMerge.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/ppMerge.config	(revision 22157)
@@ -0,0 +1,43 @@
+
+# Bias combination --- don't want min/max rejection
+PPMERGE_BIAS	METADATA
+   REJ		F32	3.0		# Rejection threshold (sigma)
+   ITER		S32	2		# Number of rejection iterations
+   FRACHIGH	F32	0.0		# Fraction of high pixels to reject immediately
+   FRACLOW	F32	0.0		# Fraction of low pixels to reject immediately
+   WEIGHTS	BOOL	FALSE		# Use image weights?
+   COMBINE	STR	CLIPPED		# Statistic to use for combination: 
+END
+
+# Dark combination --- don't want min/max rejection
+# More aggressive clipping than bias, so as to remove CRs
+PPMERGE_DARK	METADATA
+  REJ		F32	2.0		# Rejection threshold (sigma)
+  ITER		S32	4		# Number of rejection iterations
+  FRACHIGH	F32	0.0		# Fraction of high pixels to reject immediately
+  FRACLOW	F32	0.0		# Fraction of low pixels to reject immediately
+  WEIGHTS	BOOL	FALSE		# Use image weights?
+  COMBINE	STR	CLIPPED		# Statistic to use for combination: 
+END
+
+# Flat combination --- use min/max rejection
+PPMERGE_FLAT	METADATA
+	REJ		F32	3.0		# Rejection threshold (sigma)
+	ITER		S32	1		# Number of rejection iterations
+	FRACHIGH	F32	0.3		# Fraction of high pixels to reject immediately
+	FRACLOW		F32	0.1		# Fraction of low pixels to reject immediately
+	NKEEP		S32	5		# Minimum number of pixels in stack to keep
+	WEIGHTS		BOOL	TRUE		# Use image weights?
+	COMBINE		STR	MEAN		# Statistic to use for combination: 
+END
+
+
+# Fringe combination --- already included in default, above
+PPMERGE_FRINGE	METADATA
+	FRACHIGH	F32	0.1		# Fraction of high pixels to reject immediately
+	WEIGHTS		BOOL	TRUE		# Use image weights?
+END
+
+# Mask generation --- already included in default, above
+PPMERGE_MASK	METADATA
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/psastro.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/psastro.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/psastro.config	(revision 22157)
@@ -0,0 +1,79 @@
+
+# astrometry matching parameters
+
+# nominal plate scale (mm / pixel)
+PSASTRO.PIXEL.SCALE    F32  1.0
+
+# pmAstromGridAngle
+# max grid offset in FP units (microns)
+# use plate-scale to make this in pixels?
+PSASTRO.GRID.OFFSET    F32   2500.0
+PSASTRO.GRID.SCALE     F32     50.0
+
+# extra field for ref stars:
+PSASTRO.FIELD.PADDING  F32 1.0
+
+# these tweak are in FP units (pixels, currently)
+PSASTRO.TWEAK.SCALE     F32      1
+PSASTRO.TWEAK.RANGE     F32     75
+PSASTRO.TWEAK.SMOOTH    F32      2
+PSASTRO.TWEAK.NSIGMA    F32      3
+
+# single-chip radius match in pixels
+PSASTRO.MATCH.FIT.NITER S32   3
+PSASTRO.MATCH.RADIUS    F32   12.0
+PSASTRO.MATCH.RADIUS.N0 F32   15.0
+PSASTRO.MATCH.RADIUS.N1 F32   10.0
+PSASTRO.MATCH.RADIUS.N2 F32    5.0
+
+PSASTRO.MAX.NRAW       S32      1500   # max stars accepted for fitting (0 for all)
+PSASTRO.MAX.NREF       S32      1500   # max stars accepted for fitting (0 for all)
+
+PSASTRO.MIN.INST.MAG.RAW       F32      -17.0  # min instrumental magnitude for stars accepted for fitting
+PSASTRO.MAX.INST.MAG.RAW       F32       -8.0  # max instrumental magnitude for stars accepted for fitting
+
+PSASTRO.MAX.ERROR      F32      10.0 # max error in pixels
+PSASTRO.MIN.NSTAR      S32      10   # min fitted stars in solution
+
+PSASTRO.MOSAIC.MODE           BOOL     FALSE
+
+PSASTRO.MOSAIC.MAX.ERROR.N0 F32    1.50 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N1 F32    1.50 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N2 F32    0.90 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N3 F32    0.90 # max allow error for valid solution (arcsec)
+
+PSASTRO.MOSAIC.RADIUS.N0    F32   10.0 # initial match (after chip astrom)
+PSASTRO.MOSAIC.RADIUS.N1    F32    8.0 # (after first mosaic pass)
+PSASTRO.MOSAIC.RADIUS.N2    F32    8.0 # do not refine the match
+PSASTRO.MOSAIC.RADIUS.N3    F32    4.0 # 
+
+PSASTRO.MOSAIC.CHIP.ORDER.N0  S32      0 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N1  S32      1 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N2  S32      1 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N3  S32      3 # fit order (-1 means use default)
+
+PSASTRO.MOSAIC.GRADIENT.NX    S32     2   # number of x-dir cells per chip
+PSASTRO.MOSAIC.GRADIENT.NY    S32     4   # number of y-dir cells per chip
+
+# use this recipe to set a tight constraint
+PSASTRO.FINE METADATA
+  PSASTRO.MOSAIC.MAX.ERROR.N0 F32    1.50 # max allow error for valid solution (arcsec)
+  PSASTRO.MOSAIC.MAX.ERROR.N1 F32    1.50 # max allow error for valid solution (arcsec)
+  PSASTRO.MOSAIC.MAX.ERROR.N2 F32    0.90 # max allow error for valid solution (arcsec)
+  PSASTRO.MOSAIC.MAX.ERROR.N3 F32    0.90 # max allow error for valid solution (arcsec)
+
+  PSASTRO.MOSAIC.RADIUS.N0    F32    8.0
+  PSASTRO.MOSAIC.RADIUS.N1    F32    0.0
+  PSASTRO.MOSAIC.RADIUS.N2    F32    4.0
+  PSASTRO.MOSAIC.RADIUS.N3    F32    4.0
+
+  PSASTRO.MOSAIC.CHIP.ORDER.N0  S32      0 # fit order (-1 means use default)
+  PSASTRO.MOSAIC.CHIP.ORDER.N1  S32      1 # fit order (-1 means use default)
+  PSASTRO.MOSAIC.CHIP.ORDER.N2  S32      1 # fit order (-1 means use default)
+  PSASTRO.MOSAIC.CHIP.ORDER.N3  S32      3 # fit order (-1 means use default)
+END
+
+PSASTRO.GRID.MIN.SCALE F32  0.98
+PSASTRO.GRID.MAX.SCALE F32  1.04
+PSASTRO.GRID.DEL.SCALE F32  0.02
+PSASTRO.GRID.MIN.SIGMA F32  5.0
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/psphot.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/psphot.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/psphot.config	(revision 22157)
@@ -0,0 +1,40 @@
+
+# turn these on to see specific outputs
+SAVE.OUTPUT	BOOL 	TRUE
+SAVE.BACKMDL	BOOL 	TRUE
+SAVE.PSF	BOOL 	TRUE
+SAVE.PLOTS	BOOL 	TRUE
+
+BACKGROUND.XBIN	    S32   128           # size of background superpixels
+BACKGROUND.YBIN	    S32   128           # size of background superpixels
+
+# image background parameters
+IMSTATS_NPIX        S32  10000    	 # number of pixels to use for sky estimate boxes:
+
+PSF_SN_LIM          F32  50              # minimum S/N for stars used for PSF model
+PSF_MAX_NSTARS      S32  300             # limit number of stars used for PSF model
+
+PEAKS_NSIGMA_LIMIT     F32   25.0            # peak significance threshold
+PEAKS_NSIGMA_LIMIT_2   F32   10.0            # peak significance threshold
+
+# PSF model parameters : choose the PSF model desired
+PSF_MODEL           STR  PS_MODEL_QGAUSS
+
+MOMENTS_SN_MIN       F32  20.0           # min S/N to measure moments
+EXT_MIN_SN           F32  50.0           # fit galaxies above this S/N limit
+FULL_FIT_SN_LIM      F32  50.0
+AP_MIN_SN            F32  25.0
+MEASURE.APTREND	                    BOOL  FALSE
+
+# BREAK_POINT may be one of (PEAKS, MOMENTS, PSFMODEL, ENSEMBLE)
+# BREAK_POINT         STR ENSEMBLE
+
+OUTPUT.FORMAT       STR PS1_DEV_1
+
+# masking parameters (XXX EAM : rework this to use psRegion like ANALYSIS_REGION)
+XMIN                F32   +10        	 # minimum valid x-coord
+XMAX                F32   -10        	 # maximum valid x-coord
+YMIN                F32   +10        	 # minimum valid y-coord
+YMAX                F32   -10        	 # maximum valid y-coord
+
+USE_FOOTPRINTS                      BOOL  TRUE       	  # use new pmFootprint peak packaging
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/rejections.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/rejections.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/cfh12k/rejections.config	(revision 22157)
@@ -0,0 +1,49 @@
+
+BIAS METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  2.0
+  IMFILE.STDEV       F32 10.0
+  IMFILE.MEANSTDEV   F32  5.0
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  5.0
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  2.0
+  EXP.STDEV          F32 10.0
+  EXP.MEANSTDEV      F32  5.0
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  5.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  3.0
+  ENSEMBLE.STDEV     F32  3.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+DARK METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  0.0
+  IMFILE.STDEV       F32 10.0
+  IMFILE.MEANSTDEV   F32  0.0
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.0
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  2.0
+  EXP.MEAN           F32  2.0
+  EXP.STDEV          F32 10.0
+  EXP.MEANSTDEV      F32  2.0
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  2.0
+  ENSEMBLE.MEAN      F32  3.0
+  ENSEMBLE.STDEV     F32  3.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/configure.ac
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/configure.ac	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/configure.ac	(revision 22157)
@@ -0,0 +1,31 @@
+AC_PREREQ(2.59)
+
+AC_INIT([ippConfig], [1.1.0], [ipp-support@ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([ipprc.config.in])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_MAINTAINER_MODE
+
+AC_PROG_INSTALL
+
+AC_CONFIG_FILES([
+  Makefile
+  recipes/Makefile
+  isp/Makefile
+  gpc1/Makefile
+  cfh12k/Makefile
+  megacam/Makefile
+  mosaic2/Makefile
+  simple/Makefile
+  simmosaic/Makefile
+  simtest/Makefile
+  tc3/Makefile
+  skycell/Makefile
+  sdssmosaic/Makefile
+  sdss/Makefile
+  esowfi/Makefile
+  lbc_red/Makefile
+  lulin/Makefile
+  uh8k/Makefile
+])
+AC_OUTPUT
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/dvo.photcodes
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/dvo.photcodes	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/dvo.photcodes	(revision 22157)
@@ -0,0 +1,851 @@
+
+# this file contains the list of valid photometry codes.  there should
+# be one code for each combination of CCD, filter, and telescope (and
+# possibly additional ones if filter transmission reduces with time).
+# The code on each line is a unique number, while the name is a
+# representative name.  The type defines how the photometry is
+# treated.  There can only be one primary photcode.  There should only
+# be a few secondary photcodes: these are the calibrated values for
+# stars.  The dependent photcodes are those which are equivalent to a
+# primary or secondary photcode, and are equated after photometric
+# solutions.  Reference photcodes are for anything without a dynamic
+# solution: the reference values are provided externally.
+
+# NOTE: the photcodes defined below supplement CFHT Elixir values.
+# Do not use this photcode file with old CFHT DVO databases.
+
+# camera ranges used in this file:
+# megacam:  u: 100-139, g: 200-239, r: 300-339, i: 400-439, z: 500-539
+# cfh12k:   B: 140-152, V: 240-252, R: 340-352, I: 440-452, Z: 540-552, NB: 600-693
+# mosaic2:  B: 160-167, V: 260-267, VR: 360-367, R: 460-467, I: 560-567
+# PS1 ISP:  g: 3200,      r: 3300,      i: 3400,      z: 3500,      y: 3600
+# PS1 TC3:  g: 3280-3296, r: 3380-3396, i: 3480-3496, z: 3580-3596, y: 3680-3696, 
+# PS1 GPC1: g: 3201-3264, r: 3301-3364, i: 3401-3464, z: 3501-3564, y: 3601-3664
+
+# external references: 1000 - 2999
+
+#                                                                                       astrometry  photometry     astr mask      phot mask
+# code name		    type  zero  airmass offset  c1    c2  slope  <color> eq     sys  scale  sys  scale     poor   bad     poor   bad 
+  1     g                    sec   0.000  0.000 0.000  1003  1004 0.0160     0  1051   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  2     r                    sec   0.000  0.000 0.000  1003  1004 0.0080     0  1052   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  3     i                    sec   0.000  0.000 0.000  1003  1004 0.0280     0  1053   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  4     z                    sec   0.000  0.000 0.000  1003  1004 0.1070     0  1054   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  5     y                    sec   0.000  0.000 0.000     -     - 0.0000     0  1054   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+													       																		
+  1010  U_L92                ref   0.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1011  B_L92                ref   0.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1012  V_L92                ref   0.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1013  R_L92                ref   0.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1014  I_L92                ref   0.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+													       																		
+  1020  U_STET               ref   0.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1021  B_STET               ref   0.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1022  V_STET               ref   0.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1023  R_STET               ref   0.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1024  I_STET               ref   0.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+													       																		
+  1050  u_SDSS               ref   0.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1051  g_SDSS               ref   0.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1052  r_SDSS               ref   0.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1053  i_SDSS               ref   0.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1054  z_SDSS               ref   0.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+													       																		
+  1055  U_SDSS               dep  25.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1056  G_SDSS               dep  25.000  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1057  R_SDSS               dep  25.000  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1058  I_SDSS               dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1059  Z_SDSS               dep  25.000  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+													       																		
+  1100  GSC                  ref   0.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+													       																		
+  1000  USNO_BLUE            ref   0.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1001  USNO_RED             ref   0.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1002  USNO_J               ref   0.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1003  USNO_F               ref   0.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1004  USNO_N               ref   0.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+													       																		
+  1110  USNO.098.RED         dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1111  USNO.098-0.RED70     dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1112  USNO.098-0.RG630     dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1113  USNO.103AD.MULTI     dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1114  USNO.103AD.YEL3      dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1115  USNO.103AD.YEL8      dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1116  USNO.103AE.#12       dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1117  USNO.103AE.AMB2      dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1118  USNO.103AE.AMB3      dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1119  USNO.103AE.AMB4      dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1120  USNO.103AE.AMB5      dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1121  USNO.103AE.AMB6      dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1122  USNO.103AE.AMB7      dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1123  USNO.103AE.AMB8      dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1124  USNO.103AE.NONE      dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1125  USNO.103AE.RED66     dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1126  USNO.103AE.RED67     dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1127  USNO.103AE.RED68     dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1128  USNO.103AE.RED69     dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1129  USNO.103AE.RED70     dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1130  USNO.103AE.RED71     dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1131  USNO.103AE.RED73     dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1132  USNO.103AE.RG2444    dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1133  USNO.103AE.RP2444    dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1134  USNO.103AO.NONE      dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1135  USNO.IIIAF.OG590     dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1136  USNO.IIIAF.RG600     dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1137  USNO.IIIAF.RG610     dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1138  USNO.IIIAF.RG630     dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1139  USNO.IIIAJ.GG358     dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1140  USNO.IIIAJ.GG385     dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1141  USNO.IIIAJ.GG395     dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1142  USNO.IVN.RG715       dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1143  USNO.IVN.RG9         dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  1144  USNO.IVN.WR88A       dep  20.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+													       																		
+  2020  TYCHO_B              ref   0.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  2021  TYCHO_V              ref   0.000  0.000 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+													       																		
+  2011  2MASS_J              ref   0.000  0.000 0.000     -     - 0.0000     0     -   0.050 0.000 1.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  2012  2MASS_H              ref   0.000  0.000 0.000     -     - 0.0000     0     -   0.050 0.000 1.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  2013  2MASS_K              ref   0.000  0.000 0.000     -     - 0.0000     0     -   0.050 0.000 1.000  0.000   0x0000 0x0000  0x0000 0x0000	
+													       																		
+# these were used for the synthetic photometry catalog based on 2mass					       										
+  3001  PS_g                 ref   0.000  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  3002  PS_r                 ref   0.000  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  3003  PS_i                 ref   0.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  3004  PS_z                 ref   0.000  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  3005  PS_y                 ref   0.000  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+													       																		
+### megacam photcodes:											       																
+  100   MEGACAM.u.00         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  101   MEGACAM.u.01         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  102   MEGACAM.u.02         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  103   MEGACAM.u.03         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  104   MEGACAM.u.04         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  105   MEGACAM.u.05         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  106   MEGACAM.u.06         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  107   MEGACAM.u.07         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  108   MEGACAM.u.08         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  109   MEGACAM.u.09         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  110   MEGACAM.u.10         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  111   MEGACAM.u.11         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  112   MEGACAM.u.12         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  113   MEGACAM.u.13         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  114   MEGACAM.u.14         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  115   MEGACAM.u.15         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  116   MEGACAM.u.16         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  117   MEGACAM.u.17         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  118   MEGACAM.u.18         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  119   MEGACAM.u.19         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  120   MEGACAM.u.20         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  121   MEGACAM.u.21         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  122   MEGACAM.u.22         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  123   MEGACAM.u.23         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  124   MEGACAM.u.24         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  125   MEGACAM.u.25         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  126   MEGACAM.u.26         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  127   MEGACAM.u.27         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  128   MEGACAM.u.28         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  129   MEGACAM.u.29         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  130   MEGACAM.u.30         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  131   MEGACAM.u.31         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  132   MEGACAM.u.32         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  133   MEGACAM.u.33         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  134   MEGACAM.u.34         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  135   MEGACAM.u.35         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  136   MEGACAM.u.36         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  137   MEGACAM.u.37         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  138   MEGACAM.u.38         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  139   MEGACAM.u.39         dep  25.280 -0.350 0.000     -     - 0.0000     0     -   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+													       																		
+  200   MEGACAM.g.00         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  201   MEGACAM.g.01         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  202   MEGACAM.g.02         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  203   MEGACAM.g.03         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  204   MEGACAM.g.04         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  205   MEGACAM.g.05         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  206   MEGACAM.g.06         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  207   MEGACAM.g.07         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  208   MEGACAM.g.08         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  209   MEGACAM.g.09         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  210   MEGACAM.g.10         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  211   MEGACAM.g.11         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  212   MEGACAM.g.12         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  213   MEGACAM.g.13         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  214   MEGACAM.g.14         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  215   MEGACAM.g.15         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  216   MEGACAM.g.16         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  217   MEGACAM.g.17         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  218   MEGACAM.g.18         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  219   MEGACAM.g.19         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  220   MEGACAM.g.20         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  221   MEGACAM.g.21         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  222   MEGACAM.g.22         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  223   MEGACAM.g.23         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  224   MEGACAM.g.24         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  225   MEGACAM.g.25         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  226   MEGACAM.g.26         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  227   MEGACAM.g.27         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  228   MEGACAM.g.28         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  229   MEGACAM.g.29         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  230   MEGACAM.g.30         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  231   MEGACAM.g.31         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  232   MEGACAM.g.32         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  233   MEGACAM.g.33         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  234   MEGACAM.g.34         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  235   MEGACAM.g.35         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  236   MEGACAM.g.36         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  237   MEGACAM.g.37         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  238   MEGACAM.g.38         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  239   MEGACAM.g.39         dep  26.460 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+													       																		
+  300   MEGACAM.r.00         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  301   MEGACAM.r.01         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  302   MEGACAM.r.02         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  303   MEGACAM.r.03         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  304   MEGACAM.r.04         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  305   MEGACAM.r.05         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  306   MEGACAM.r.06         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  307   MEGACAM.r.07         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  308   MEGACAM.r.08         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  309   MEGACAM.r.09         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  310   MEGACAM.r.10         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  311   MEGACAM.r.11         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  312   MEGACAM.r.12         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  313   MEGACAM.r.13         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  314   MEGACAM.r.14         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  315   MEGACAM.r.15         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  316   MEGACAM.r.16         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  317   MEGACAM.r.17         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  318   MEGACAM.r.18         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  319   MEGACAM.r.19         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  320   MEGACAM.r.20         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  321   MEGACAM.r.21         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  322   MEGACAM.r.22         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  323   MEGACAM.r.23         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  324   MEGACAM.r.24         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  325   MEGACAM.r.25         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  326   MEGACAM.r.26         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  327   MEGACAM.r.27         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  328   MEGACAM.r.28         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  329   MEGACAM.r.29         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  330   MEGACAM.r.30         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  331   MEGACAM.r.31         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  332   MEGACAM.r.32         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  333   MEGACAM.r.33         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  334   MEGACAM.r.34         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  335   MEGACAM.r.35         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  336   MEGACAM.r.36         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  337   MEGACAM.r.37         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  338   MEGACAM.r.38         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  339   MEGACAM.r.39         dep  25.978 -0.100 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+													       																		
+  400   MEGACAM.i.00         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  401   MEGACAM.i.01         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  402   MEGACAM.i.02         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  403   MEGACAM.i.03         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  404   MEGACAM.i.04         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  405   MEGACAM.i.05         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  406   MEGACAM.i.06         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  407   MEGACAM.i.07         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  408   MEGACAM.i.08         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  409   MEGACAM.i.09         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  410   MEGACAM.i.10         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  411   MEGACAM.i.11         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  412   MEGACAM.i.12         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  413   MEGACAM.i.13         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  414   MEGACAM.i.14         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  415   MEGACAM.i.15         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  416   MEGACAM.i.16         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  417   MEGACAM.i.17         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  418   MEGACAM.i.18         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  419   MEGACAM.i.19         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  420   MEGACAM.i.20         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  421   MEGACAM.i.21         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  422   MEGACAM.i.22         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  423   MEGACAM.i.23         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  424   MEGACAM.i.24         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  425   MEGACAM.i.25         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  426   MEGACAM.i.26         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  427   MEGACAM.i.27         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  428   MEGACAM.i.28         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  429   MEGACAM.i.29         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  430   MEGACAM.i.30         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  431   MEGACAM.i.31         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  432   MEGACAM.i.32         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  433   MEGACAM.i.33         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  434   MEGACAM.i.34         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  435   MEGACAM.i.35         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  436   MEGACAM.i.36         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  437   MEGACAM.i.37         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  438   MEGACAM.i.38         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  439   MEGACAM.i.39         dep  25.744 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+													       																		
+  500   MEGACAM.z.00         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  501   MEGACAM.z.01         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  502   MEGACAM.z.02         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  503   MEGACAM.z.03         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  504   MEGACAM.z.04         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  505   MEGACAM.z.05         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  506   MEGACAM.z.06         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  507   MEGACAM.z.07         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  508   MEGACAM.z.08         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  509   MEGACAM.z.09         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  510   MEGACAM.z.10         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  511   MEGACAM.z.11         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  512   MEGACAM.z.12         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  513   MEGACAM.z.13         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  514   MEGACAM.z.14         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  515   MEGACAM.z.15         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  516   MEGACAM.z.16         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  517   MEGACAM.z.17         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  518   MEGACAM.z.18         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  519   MEGACAM.z.19         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  520   MEGACAM.z.20         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  521   MEGACAM.z.21         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  522   MEGACAM.z.22         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  523   MEGACAM.z.23         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  524   MEGACAM.z.24         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  525   MEGACAM.z.25         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  526   MEGACAM.z.26         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  527   MEGACAM.z.27         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  528   MEGACAM.z.28         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  529   MEGACAM.z.29         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  530   MEGACAM.z.30         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  531   MEGACAM.z.31         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  532   MEGACAM.z.32         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  533   MEGACAM.z.33         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  534   MEGACAM.z.34         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  535   MEGACAM.z.35         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  536   MEGACAM.z.36         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  537   MEGACAM.z.37         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  538   MEGACAM.z.38         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  539   MEGACAM.z.39         dep  24.800 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+													       																		
+### cfh12k photcodes											       																
+  140   CFH12K.B.00          dep  26.000 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  141   CFH12K.B.01          dep  26.000 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  142   CFH12K.B.02          dep  26.000 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  143   CFH12K.B.03          dep  26.000 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  144   CFH12K.B.04          dep  26.000 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  145   CFH12K.B.05          dep  26.000 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  146   CFH12K.B.06          dep  26.000 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  147   CFH12K.B.07          dep  26.000 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  148   CFH12K.B.08          dep  26.000 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  149   CFH12K.B.09          dep  26.000 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  150   CFH12K.B.10          dep  26.000 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  151   CFH12K.B.11          dep  26.000 -0.150 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+													       																		
+  240   CFH12K.V.00          dep  26.150 -0.120 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  241   CFH12K.V.01          dep  26.150 -0.120 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  242   CFH12K.V.02          dep  26.150 -0.120 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  243   CFH12K.V.03          dep  26.150 -0.120 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  244   CFH12K.V.04          dep  26.150 -0.120 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  245   CFH12K.V.05          dep  26.150 -0.120 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  246   CFH12K.V.06          dep  26.150 -0.120 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  247   CFH12K.V.07          dep  26.150 -0.120 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  248   CFH12K.V.08          dep  26.150 -0.120 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  249   CFH12K.V.09          dep  26.150 -0.120 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  250   CFH12K.V.10          dep  26.150 -0.120 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  251   CFH12K.V.11          dep  26.150 -0.120 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+													       																		
+  340   CFH12K.R.00          dep  26.190 -0.090 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  341   CFH12K.R.01          dep  26.190 -0.090 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  342   CFH12K.R.02          dep  26.190 -0.090 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  343   CFH12K.R.03          dep  26.190 -0.090 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  344   CFH12K.R.04          dep  26.190 -0.090 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  345   CFH12K.R.05          dep  26.190 -0.090 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  346   CFH12K.R.06          dep  26.190 -0.090 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  347   CFH12K.R.07          dep  26.190 -0.090 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  348   CFH12K.R.08          dep  26.190 -0.090 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  349   CFH12K.R.09          dep  26.190 -0.090 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  350   CFH12K.R.10          dep  26.190 -0.090 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  351   CFH12K.R.11          dep  26.190 -0.090 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+													       																		
+  440   CFH12K.I.00          dep  26.135 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  441   CFH12K.I.01          dep  26.135 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  442   CFH12K.I.02          dep  26.135 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  443   CFH12K.I.03          dep  26.135 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  444   CFH12K.I.04          dep  26.135 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  445   CFH12K.I.05          dep  26.135 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  446   CFH12K.I.06          dep  26.135 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  447   CFH12K.I.07          dep  26.135 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  448   CFH12K.I.08          dep  26.135 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  449   CFH12K.I.09          dep  26.135 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  450   CFH12K.I.10          dep  26.135 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  451   CFH12K.I.11          dep  26.135 -0.040 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+													       																		
+  540   CFH12K.Z.00          dep  25.830 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  541   CFH12K.Z.01          dep  25.830 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  542   CFH12K.Z.02          dep  25.830 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  543   CFH12K.Z.03          dep  25.830 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  544   CFH12K.Z.04          dep  25.830 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  545   CFH12K.Z.05          dep  25.830 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  546   CFH12K.Z.06          dep  25.830 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  547   CFH12K.Z.07          dep  25.830 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  548   CFH12K.Z.08          dep  25.830 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  549   CFH12K.Z.09          dep  25.830 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  550   CFH12K.Z.10          dep  25.830 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  551   CFH12K.Z.11          dep  25.830 -0.030 0.000     -     - 0.0000     0     4   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+													       																		
+  600   CFH12K.Ha.00         dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  601   CFH12K.Ha.01         dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  602   CFH12K.Ha.02         dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  603   CFH12K.Ha.03         dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  604   CFH12K.Ha.04         dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  605   CFH12K.Ha.05         dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  606   CFH12K.Ha.06         dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  607   CFH12K.Ha.07         dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  608   CFH12K.Ha.08         dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  609   CFH12K.Ha.09         dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  610   CFH12K.Ha.10         dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  611   CFH12K.Ha.11         dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+													       																		
+  612   CFH12K.HaOff.00      dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  613   CFH12K.HaOff.01      dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  614   CFH12K.HaOff.02      dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  615   CFH12K.HaOff.03      dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  616   CFH12K.HaOff.04      dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  617   CFH12K.HaOff.05      dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  618   CFH12K.HaOff.06      dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  619   CFH12K.HaOff.07      dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  620   CFH12K.HaOff.08      dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  621   CFH12K.HaOff.09      dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  622   CFH12K.HaOff.10      dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  623   CFH12K.HaOff.11      dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  624   CFH12K.CN.00         dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  625   CFH12K.CN.01         dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  626   CFH12K.CN.02         dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  627   CFH12K.CN.03         dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  628   CFH12K.CN.04         dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  629   CFH12K.CN.05         dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  630   CFH12K.CN.06         dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  631   CFH12K.CN.07         dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  632   CFH12K.CN.08         dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  633   CFH12K.CN.09         dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  634   CFH12K.CN.10         dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  635   CFH12K.CN.11         dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  636   CFH12K.TiO.00        dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  637   CFH12K.TiO.01        dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  638   CFH12K.TiO.02        dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  639   CFH12K.TiO.03        dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  640   CFH12K.TiO.04        dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  641   CFH12K.TiO.05        dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  642   CFH12K.TiO.06        dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  643   CFH12K.TiO.07        dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  644   CFH12K.TiO.08        dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  645   CFH12K.TiO.09        dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  646   CFH12K.TiO.10        dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  647   CFH12K.TiO.11        dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  648   CFH12K.NB920.00      dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  649   CFH12K.NB920.01      dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  650   CFH12K.NB920.02      dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  651   CFH12K.NB920.03      dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  652   CFH12K.NB920.04      dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  653   CFH12K.NB920.05      dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  654   CFH12K.NB920.06      dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  655   CFH12K.NB920.07      dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  678   CFH12K.NB920.08      dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  679   CFH12K.NB920.09      dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  680   CFH12K.NB920.10      dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  681   CFH12K.NB920.11      dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  682   CFH12K.B2F.00        dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  683   CFH12K.B2F.01        dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  684   CFH12K.B2F.02        dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  685   CFH12K.B2F.03        dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  686   CFH12K.B2F.04        dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  687   CFH12K.B2F.05        dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  688   CFH12K.B2F.06        dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  689   CFH12K.B2F.07        dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  690   CFH12K.B2F.08        dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  691   CFH12K.B2F.09        dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  692   CFH12K.B2F.10        dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  693   CFH12K.B2F.11        dep  25.000  0.040 0.000     -     - 0.0000     0     -   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+													       																		
+  3200  ISP-Apogee-01.g      dep  19.250  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  3300  ISP-Apogee-01.r      dep  19.150  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  3400  ISP-Apogee-01.i      dep  18.500  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  3500  ISP-Apogee-01.z      dep  17.250  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  3600  ISP-Apogee-01.y      dep  14.150  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+													       																		
+  4100  SIMTEST.g            dep  25.000  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  4200  SIMTEST.r            dep  25.000  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  4300  SIMTEST.i            dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  4400  SIMTEST.z            dep  25.000  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  4500  SIMTEST.y            dep  25.000  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+													       																		
+  4101  SIMMOSAIC.g.00       dep  25.000  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  4102  SIMMOSAIC.g.01       dep  25.000  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  4103  SIMMOSAIC.g.02       dep  25.000  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  4104  SIMMOSAIC.g.03       dep  25.000  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  4201  SIMMOSAIC.r.00       dep  25.000  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  4202  SIMMOSAIC.r.01       dep  25.000  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  4203  SIMMOSAIC.r.02       dep  25.000  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  4204  SIMMOSAIC.r.03       dep  25.000  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  4301  SIMMOSAIC.i.00       dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  4302  SIMMOSAIC.i.01       dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  4303  SIMMOSAIC.i.02       dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  4304  SIMMOSAIC.i.03       dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  4401  SIMMOSAIC.z.00       dep  25.000  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  4402  SIMMOSAIC.z.01       dep  25.000  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  4403  SIMMOSAIC.z.02       dep  25.000  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  4404  SIMMOSAIC.z.03       dep  25.000  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  4501  SIMMOSAIC.y.00       dep  25.000  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  4502  SIMMOSAIC.y.01       dep  25.000  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  4503  SIMMOSAIC.y.02       dep  25.000  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  4504  SIMMOSAIC.y.03       dep  25.000  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+													       																		
+# 2007.10.02 : predicted g zero-points from 2006 NSF proposal						       											
+  10001 GPC1.g.XY01          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10002 GPC1.g.XY02          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10003 GPC1.g.XY03          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10004 GPC1.g.XY04          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10005 GPC1.g.XY05          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10006 GPC1.g.XY06          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10010 GPC1.g.XY10          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10011 GPC1.g.XY11          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10012 GPC1.g.XY12          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10013 GPC1.g.XY13          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10014 GPC1.g.XY14          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10015 GPC1.g.XY15          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10016 GPC1.g.XY16          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10017 GPC1.g.XY17          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10020 GPC1.g.XY20          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10021 GPC1.g.XY21          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10022 GPC1.g.XY22          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10023 GPC1.g.XY23          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10024 GPC1.g.XY24          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10025 GPC1.g.XY25          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10026 GPC1.g.XY26          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10027 GPC1.g.XY27          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10030 GPC1.g.XY30          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10031 GPC1.g.XY31          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10032 GPC1.g.XY32          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10033 GPC1.g.XY33          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10034 GPC1.g.XY34          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10035 GPC1.g.XY35          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10036 GPC1.g.XY36          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10037 GPC1.g.XY37          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10040 GPC1.g.XY40          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10041 GPC1.g.XY41          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10042 GPC1.g.XY42          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10043 GPC1.g.XY43          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10044 GPC1.g.XY44          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10045 GPC1.g.XY45          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10046 GPC1.g.XY46          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10047 GPC1.g.XY47          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10050 GPC1.g.XY50          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10051 GPC1.g.XY51          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10052 GPC1.g.XY52          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10053 GPC1.g.XY53          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10054 GPC1.g.XY54          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10055 GPC1.g.XY55          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10056 GPC1.g.XY56          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10057 GPC1.g.XY57          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10060 GPC1.g.XY60          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10061 GPC1.g.XY61          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10062 GPC1.g.XY62          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10063 GPC1.g.XY63          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10064 GPC1.g.XY64          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10065 GPC1.g.XY65          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10066 GPC1.g.XY66          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10067 GPC1.g.XY67          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10071 GPC1.g.XY71          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10072 GPC1.g.XY72          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10073 GPC1.g.XY73          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10074 GPC1.g.XY74          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10075 GPC1.g.XY75          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10076 GPC1.g.XY76          dep  24.900  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+													       																		
+# 2007.10.02 : predicted r zero-points from 2006 NSF proposal						       											
+  10101 GPC1.r.XY01          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10102 GPC1.r.XY02          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10103 GPC1.r.XY03          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10104 GPC1.r.XY04          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10105 GPC1.r.XY05          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10106 GPC1.r.XY06          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10110 GPC1.r.XY10          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10111 GPC1.r.XY11          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10112 GPC1.r.XY12          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10113 GPC1.r.XY13          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10114 GPC1.r.XY14          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10115 GPC1.r.XY15          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10116 GPC1.r.XY16          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10117 GPC1.r.XY17          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10120 GPC1.r.XY20          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10121 GPC1.r.XY21          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10122 GPC1.r.XY22          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10123 GPC1.r.XY23          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10124 GPC1.r.XY24          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10125 GPC1.r.XY25          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10126 GPC1.r.XY26          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10127 GPC1.r.XY27          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10130 GPC1.r.XY30          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10131 GPC1.r.XY31          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10132 GPC1.r.XY32          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10133 GPC1.r.XY33          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10134 GPC1.r.XY34          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10135 GPC1.r.XY35          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10136 GPC1.r.XY36          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10137 GPC1.r.XY37          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10140 GPC1.r.XY40          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10141 GPC1.r.XY41          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10142 GPC1.r.XY42          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10143 GPC1.r.XY43          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10144 GPC1.r.XY44          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10145 GPC1.r.XY45          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10146 GPC1.r.XY46          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10147 GPC1.r.XY47          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10150 GPC1.r.XY50          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10151 GPC1.r.XY51          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10152 GPC1.r.XY52          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10153 GPC1.r.XY53          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10154 GPC1.r.XY54          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10155 GPC1.r.XY55          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10156 GPC1.r.XY56          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10157 GPC1.r.XY57          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10160 GPC1.r.XY60          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10161 GPC1.r.XY61          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10162 GPC1.r.XY62          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10163 GPC1.r.XY63          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10164 GPC1.r.XY64          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10165 GPC1.r.XY65          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10166 GPC1.r.XY66          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10167 GPC1.r.XY67          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10171 GPC1.r.XY71          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10172 GPC1.r.XY72          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10173 GPC1.r.XY73          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10174 GPC1.r.XY74          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10175 GPC1.r.XY75          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10176 GPC1.r.XY76          dep  25.100  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+													       																		
+# 2007.10.02 : predicted i zero-points from 2006 NSF proposal						       											
+  10201 GPC1.i.XY01          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10202 GPC1.i.XY02          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10203 GPC1.i.XY03          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10204 GPC1.i.XY04          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10205 GPC1.i.XY05          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10206 GPC1.i.XY06          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10210 GPC1.i.XY10          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10211 GPC1.i.XY11          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10212 GPC1.i.XY12          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10213 GPC1.i.XY13          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10214 GPC1.i.XY14          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10215 GPC1.i.XY15          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10216 GPC1.i.XY16          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10217 GPC1.i.XY17          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10220 GPC1.i.XY20          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10221 GPC1.i.XY21          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10222 GPC1.i.XY22          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10223 GPC1.i.XY23          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10224 GPC1.i.XY24          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10225 GPC1.i.XY25          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10226 GPC1.i.XY26          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10227 GPC1.i.XY27          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10230 GPC1.i.XY30          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10231 GPC1.i.XY31          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10232 GPC1.i.XY32          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10233 GPC1.i.XY33          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10234 GPC1.i.XY34          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10235 GPC1.i.XY35          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10236 GPC1.i.XY36          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10237 GPC1.i.XY37          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10240 GPC1.i.XY40          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10241 GPC1.i.XY41          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10242 GPC1.i.XY42          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10243 GPC1.i.XY43          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10244 GPC1.i.XY44          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10245 GPC1.i.XY45          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10246 GPC1.i.XY46          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10247 GPC1.i.XY47          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10250 GPC1.i.XY50          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10251 GPC1.i.XY51          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10252 GPC1.i.XY52          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10253 GPC1.i.XY53          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10254 GPC1.i.XY54          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10255 GPC1.i.XY55          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10256 GPC1.i.XY56          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10257 GPC1.i.XY57          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10260 GPC1.i.XY60          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10261 GPC1.i.XY61          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10262 GPC1.i.XY62          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10263 GPC1.i.XY63          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10264 GPC1.i.XY64          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10265 GPC1.i.XY65          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10266 GPC1.i.XY66          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10267 GPC1.i.XY67          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10271 GPC1.i.XY71          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10272 GPC1.i.XY72          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10273 GPC1.i.XY73          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10274 GPC1.i.XY74          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10275 GPC1.i.XY75          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10276 GPC1.i.XY76          dep  25.000  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+													       																		
+# 2007.10.02 : predicted z zero-points from 2006 NSF proposal						       											
+  10301 GPC1.z.XY01          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10302 GPC1.z.XY02          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10303 GPC1.z.XY03          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10304 GPC1.z.XY04          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10305 GPC1.z.XY05          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10306 GPC1.z.XY06          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10310 GPC1.z.XY10          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10311 GPC1.z.XY11          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10312 GPC1.z.XY12          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10313 GPC1.z.XY13          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10314 GPC1.z.XY14          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10315 GPC1.z.XY15          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10316 GPC1.z.XY16          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10317 GPC1.z.XY17          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10320 GPC1.z.XY20          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10321 GPC1.z.XY21          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10322 GPC1.z.XY22          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10323 GPC1.z.XY23          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10324 GPC1.z.XY24          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10325 GPC1.z.XY25          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10326 GPC1.z.XY26          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10327 GPC1.z.XY27          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10330 GPC1.z.XY30          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10331 GPC1.z.XY31          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10332 GPC1.z.XY32          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10333 GPC1.z.XY33          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10334 GPC1.z.XY34          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10335 GPC1.z.XY35          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10336 GPC1.z.XY36          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10337 GPC1.z.XY37          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10340 GPC1.z.XY40          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10341 GPC1.z.XY41          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10342 GPC1.z.XY42          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10343 GPC1.z.XY43          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10344 GPC1.z.XY44          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10345 GPC1.z.XY45          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10346 GPC1.z.XY46          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10347 GPC1.z.XY47          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10350 GPC1.z.XY50          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10351 GPC1.z.XY51          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10352 GPC1.z.XY52          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10353 GPC1.z.XY53          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10354 GPC1.z.XY54          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10355 GPC1.z.XY55          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10356 GPC1.z.XY56          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10357 GPC1.z.XY57          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10360 GPC1.z.XY60          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10361 GPC1.z.XY61          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10362 GPC1.z.XY62          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10363 GPC1.z.XY63          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10364 GPC1.z.XY64          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10365 GPC1.z.XY65          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10366 GPC1.z.XY66          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10367 GPC1.z.XY67          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10371 GPC1.z.XY71          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10372 GPC1.z.XY72          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10373 GPC1.z.XY73          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10374 GPC1.z.XY74          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10375 GPC1.z.XY75          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10376 GPC1.z.XY76          dep  24.600  0.000 0.000     -     - 0.0000     0     4   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+													       										
+# 2007.10.02 : predicted y zero-points from 2006 NSF proposal						       											
+  10401 GPC1.y.XY01          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10402 GPC1.y.XY02          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10403 GPC1.y.XY03          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10404 GPC1.y.XY04          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10405 GPC1.y.XY05          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10406 GPC1.y.XY06          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10410 GPC1.y.XY10          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10411 GPC1.y.XY11          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10412 GPC1.y.XY12          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10413 GPC1.y.XY13          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10414 GPC1.y.XY14          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10415 GPC1.y.XY15          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10416 GPC1.y.XY16          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10417 GPC1.y.XY17          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10420 GPC1.y.XY20          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10421 GPC1.y.XY21          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10422 GPC1.y.XY22          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10423 GPC1.y.XY23          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10424 GPC1.y.XY24          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10425 GPC1.y.XY25          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10426 GPC1.y.XY26          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10427 GPC1.y.XY27          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10430 GPC1.y.XY30          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10431 GPC1.y.XY31          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10432 GPC1.y.XY32          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10433 GPC1.y.XY33          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10434 GPC1.y.XY34          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10435 GPC1.y.XY35          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10436 GPC1.y.XY36          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10437 GPC1.y.XY37          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10440 GPC1.y.XY40          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10441 GPC1.y.XY41          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10442 GPC1.y.XY42          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10443 GPC1.y.XY43          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10444 GPC1.y.XY44          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10445 GPC1.y.XY45          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10446 GPC1.y.XY46          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10447 GPC1.y.XY47          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10450 GPC1.y.XY50          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10451 GPC1.y.XY51          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10452 GPC1.y.XY52          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10453 GPC1.y.XY53          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10454 GPC1.y.XY54          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10455 GPC1.y.XY55          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10456 GPC1.y.XY56          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10457 GPC1.y.XY57          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10460 GPC1.y.XY60          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10461 GPC1.y.XY61          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10462 GPC1.y.XY62          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10463 GPC1.y.XY63          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10464 GPC1.y.XY64          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10465 GPC1.y.XY65          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10466 GPC1.y.XY66          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10467 GPC1.y.XY67          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10471 GPC1.y.XY71          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10472 GPC1.y.XY72          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10473 GPC1.y.XY73          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10474 GPC1.y.XY74          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10475 GPC1.y.XY75          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+  10476 GPC1.y.XY76          dep  23.600  0.000 0.000     -     - 0.0000     0     5   0.000 0.000 0.000  0.000   0x0000 0x0000  0x0000 0x0000	
+
+# mosaic2 photcodes
+
+  160  MOSAIC2.B.00          dep  25.107  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  161  MOSAIC2.B.01          dep  25.120  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  162  MOSAIC2.B.02          dep  25.115  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  163  MOSAIC2.B.03          dep  25.111  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  164  MOSAIC2.B.04          dep  25.111  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  165  MOSAIC2.B.05          dep  25.101  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  166  MOSAIC2.B.06          dep  25.106  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  167  MOSAIC2.B.07          dep  25.104  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+
+  260  MOSAIC2.V.00          dep  25.448  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  261  MOSAIC2.V.01          dep  25.472  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  262  MOSAIC2.V.02          dep  25.472  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  263  MOSAIC2.V.03          dep  25.476  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  264  MOSAIC2.V.04          dep  25.475  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  265  MOSAIC2.V.05          dep  25.471  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  266  MOSAIC2.V.06          dep  25.472  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  267  MOSAIC2.V.07          dep  25.475  0.000 0.000     -     - 0.0000     0     1   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+
+  360  MOSAIC2.VR.00         dep  25.636  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  361  MOSAIC2.VR.01         dep  25.633  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  362  MOSAIC2.VR.02         dep  25.648  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  363  MOSAIC2.VR.03         dep  25.632  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  364  MOSAIC2.VR.04         dep  25.628  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  365  MOSAIC2.VR.05         dep  25.616  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  366  MOSAIC2.VR.06         dep  25.629  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  367  MOSAIC2.VR.07         dep  25.624  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+
+  460  MOSAIC2.R.00          dep  25.636  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  461  MOSAIC2.R.01          dep  25.633  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  462  MOSAIC2.R.02          dep  25.648  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  463  MOSAIC2.R.03          dep  25.632  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  464  MOSAIC2.R.04          dep  25.628  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  465  MOSAIC2.R.05          dep  25.616  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  466  MOSAIC2.R.06          dep  25.629  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  467  MOSAIC2.R.07          dep  25.624  0.000 0.000     -     - 0.0000     0     2   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+
+  560  MOSAIC2.I.00          dep  24.911  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  561  MOSAIC2.I.01          dep  24.882  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  562  MOSAIC2.I.02          dep  24.924  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  563  MOSAIC2.I.03          dep  24.906  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  564  MOSAIC2.I.04          dep  24.906  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  565  MOSAIC2.I.05          dep  24.877  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  566  MOSAIC2.I.06          dep  24.887  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
+  567  MOSAIC2.I.07          dep  24.912  0.000 0.000     -     - 0.0000     0     3   0.000 0.000 1.000  0.000   0x0000 0x3888  0x0000 0x0000	
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/dvo.site.in
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/dvo.site.in	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/dvo.site.in	(revision 22157)
@@ -0,0 +1,35 @@
+
+CONFDIR                 @pkgdatadir@
+CAMERA                  default
+
+# location of DVO database tables
+CATDIR			default
+
+# location of possible data sources
+2MASS_DIR_AS		NONE
+2MASS_DIR_DR2		NONE
+GSCDIR			NONE
+USNO_A_DIR		NONE
+USNO_B_DIR		NONE
+TYCHO_DIR		NONE
+
+PHOTCODE_FILE		$CONFDIR/dvo.photcodes
+GSCFILE			$CONFDIR/GSCregions.tbl
+CATMODE			SPLIT
+CATFORMAT		PANSTARRS_DEV_1
+SKY_DEPTH               3
+# SKY_TABLE		this may be used to override GSCFILE
+ZERO_PT			25.0
+
+# access control for client/server addstar mode 
+# PASSWORD
+# HOSTNAME
+# VALID_IP
+
+# camera dependent parameter files:
+FILTER_LIST             $CONFDIR/$CAMERA/dvo.filters
+CAMERA_CONFIG           $CONFDIR/$CAMERA/dvo.layout
+DETREND_RECIPES         $CONFDIR/$CAMERA/dvo.recipe
+
+# load the camera-specific configuration files
+input                   $CONFDIR/$CAMERA/dvo.config
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/.cvsignore	(revision 22157)
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/Makefile.am	(revision 22157)
@@ -0,0 +1,21 @@
+
+installdir = $(datadir)/ippconfig/esowfi
+
+install_files = \
+	dvo.config \
+	camera.config \
+	format_split.config \
+	format_together.config \
+	ppImage.config \
+	psphot.config \
+	psastro.config \
+	rejections.config
+
+install_DATA = $(install_files)
+
+install-data-hook:
+	chmod 0755 $(installdir)
+
+EXTRA_DIST = $(install_files)
+
+ACLOCAL_AMFLAGS = -I m4
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/camera.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/camera.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/camera.config	(revision 22157)
@@ -0,0 +1,101 @@
+# Camera configuration file for simulated mosaic
+
+# File formats that we know about
+FORMATS         METADATA
+  TOGETHER STR  esowfi/format_together.config
+  SPLIT    STR  esowfi/format_split.config
+END
+
+# Description of camera --- all the chips and the cells that comprise them
+FPA     METADATA
+        Chip50    STR  Cell00
+        Chip51    STR  Cell00
+        Chip52    STR  Cell00
+        Chip53    STR  Cell00
+        Chip54    STR  Cell00
+        Chip55    STR  Cell00
+        Chip56    STR  Cell00
+        Chip57    STR  Cell00
+END
+
+# move this elsewhere?  we need a lookup table to go from filter ID to abstract name
+FILTER.ID       METADATA
+  NONE  STR  NONE
+  416  STR    416/29_ESO872
+  U    STR    U/50_ESO877
+  B    STR    B/123_ESO878
+  V    STR    V/89_ESO843
+  R    STR    Rc/162_ESO844
+  I    STR    I/203_ESO879
+  #g  STR    g
+  #r  STR    r
+  #i  STR    i
+  #z  STR    z
+END
+
+# Table of strings to use for the class identifier (see ippdb) according to the file level.
+CLASSID    METADATA
+  FPA   STR  fpa
+  CHIP  STR  {CHIP.NAME}
+  CELL  STR  {CHIP.NAME}.{CELL.NAME}
+  fpa   STR  fpa
+  chip  STR  {CHIP.NAME}
+  cell  STR  {CHIP.NAME}.{CELL.NAME}
+END
+
+DVO.CAMERADIR  STR  esowfi    # Camera directory for DVO
+
+# convert supplied FPA.OBSTYPE values to abstract exptype names
+OBSTYPE.TABLE METADATA
+  bias     STR BIAS
+  skyflat  STR FLAT,SKY
+  domeflat STR FLAT,SCREEN
+  object   STR OBJECT
+  science  STR OBJECT
+  #zero    STR BIAS
+  #dark    STR DARK
+  #flat    STR SKYFLAT
+END
+
+# Recipe options
+RECIPES    METADATA
+  PPIMAGE     STR    esowfi/ppImage.config     # Default: all (normal) options on
+  PSPHOT      STR    esowfi/psphot.config      # psphot details
+  PSASTRO     STR    esowfi/psastro.config     # psastro details
+  REJECTIONS  STR    esowfi/rejections.config  # Rejection limits
+END
+
+# reduction classes (recipes which are grouped together)
+REDUCTION	STR	recipes/reductionClasses.mdc
+
+FITSTYPES       STR     recipes/fitstypes.mdc
+
+FILERULES	STR	recipes/filerules-mef.mdc		# File rules appropriate for MEF format
+
+# FPA file defines properties of a possible input|output object
+# user can set the filename (I|O), filename rules (O), or abstract source (@FILES, @DETDB) (I)
+# user can set the extension name, if used
+# user can set the file type (IMAGE, JPEG, RAW, SX, OBJ, CMP, CMF) : but these are not variable in most cases!
+# user can set the file depth: only valid for output files
+# user can set the data depth: must be >= file depth
+# user can set the file format: only valid for newly created FPAs
+# user can set the colormap, scaling method, scaling range (JPEG only)
+# user can set the extension name for the data and header segments (CMF only)
+
+
+EXTNAME.RULES  METADATA
+  CMF.HEAD   STR  {CHIP.NAME}.hdr
+  CMF.DATA   STR  {CHIP.NAME}.psf
+  CMF.XSRC   STR {CHIP.NAME}.xsrc # use .PSF and .EXT?
+  CMF.XFIT   STR {CHIP.NAME}.xfit # use .PSF and .EXT?
+
+  PSF.HEAD   STR  {CHIP.NAME}.hdr
+  PSF.TABLE  STR  {CHIP.NAME}.psf_model
+  PSF.RESID  STR  {CHIP.NAME}.psf_resid
+END
+
+BLANK.HEADERS  METADATA
+  FPA.TIME      STR  MJD-OBS
+  FPA.EXPOSURE  STR  EXPTIME
+  FPA.AIRMASS   STR  AIRMASS
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/dvo.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/dvo.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/dvo.config	(revision 22157)
@@ -0,0 +1,58 @@
+
+# keywords used by DVO to interpret the headers
+
+# keyword abstractions:
+DATE-KEYWORD		NONE
+UT-KEYWORD		NONE
+JD-KEYWORD		NONE
+MJD-KEYWORD		MJD-OBS
+
+# other keyword abstractions
+EXPTIME-KEYWORD		EXPTIME
+AIRMASS-KEYWORD		AIRMASS
+CCDNUM-KEYWORD		EXTNAME
+ST-KEYWORD		NONE
+OBSERVATORY-LATITUDE	NONE
+OBSERVATORY-LONGITUDE	NONE
+SUBPIX_DATAFILE		NONE
+
+# instrumental magnitude range for calibration mode 
+CAL_INSTMAG_MAX		0
+CAL_INSTMAG_MIN		0
+
+# exclude overscan region from the dB image boundaries
+XOVERSCAN		0
+YOVERSCAN		0
+
+# only upload stars within region; a value of 0 means ignore the limit
+ADDSTAR_XMIN		0
+ADDSTAR_XMAX		0
+ADDSTAR_YMIN		0
+ADDSTAR_YMAX		0
+
+# exclude stars with SN > SNLIMIT (ADDSTAR_SNLIMIT overrides old name MIN_SN_FSTAT)
+ADDSTAR_SNLIMIT		0
+
+# correlation radius (arcseconds)
+ADDSTAR_RADIUS		1.0
+
+# scaled correlation radius 
+ADDSTAR_NSIGMA		0
+
+IMAGE_SCATTER           0.075  # mark images POOR if stdev(Mcal) > IMAGE_SCATTER
+STAR_SCATTER            0.005  # mark stars POOR if stdev(Mrel) > STAR_SCATTER
+IMAGE_OFFSET            0.100  # mark images POOR if abs(delta(Mcal)) > IMAGE_OFFSET
+STAR_CHISQ              10.0   # mark stars POOR if Xm > STAR_CHISQ
+STAR_TOOFEW              3     # mark star FEW if N(good) < STAR_TOOFEW
+IMAGE_TOOFEW            10     # mark image FEW if N(good) < IMAGE_TOOFEW
+IMAGE_GOOD_FRACTION     0.05   # mark image FEW if N(good) < IMAGE_GOOD_FRACTION * Nstars
+
+SCATTER_LIM             15.0
+MAG_LIM                 20.0   # select stars brighter than this for relphot analysis
+SIGMA_LIM               0.015  # select measurements with dM < SIGMA_LIM for relphot analysis
+# INST_MAG_MIN         -17     # optional constraints on magnitude ranges
+# INST_MAG_MAX         -13     # optional constraints on magnitude ranges
+
+RELPHOT_GRID_BINNING    256
+RELPHOT_GRID_X 6
+RELPHOT_GRID_Y 14
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/format_split.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/format_split.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/format_split.config	(revision 22157)
@@ -0,0 +1,117 @@
+# Simulation test camera with single chip with single cell
+
+# How to identify this type
+RULE	METADATA
+	ORIGIN	STR		ESO
+	NAXIS			S32	2
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	CHIP		# The FITS file represents an entire FPA
+	EXTENSIONS	STR	NONE		# There are no extensions
+	FPA.OBS		STR	ORIGIN		# A PHU keyword for unique FPA identifier
+	CONTENT		STR	EXTNAME 	# Key to the CONTENTS menu
+	CONTENT.RULE	STR	{CHIP.NAME}	# How to derive the CONTENT when writing
+END
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# File identifier with corresponding file layout name
+	WIN1.CHIP1.OUT1		STR	Chip51:Chip
+	WIN1.CHIP2.OUT1		STR	Chip52:Chip
+	WIN1.CHIP3.OUT1		STR	Chip53:Chip
+	WIN1.CHIP4.OUT1		STR	Chip54:Chip
+	WIN1.CHIP5.OUT1		STR	Chip55:Chip
+	WIN1.CHIP6.OUT1		STR	Chip56:Chip
+	WIN1.CHIP7.OUT1		STR	Chip57:Chip
+	WIN1.CHIP8.OUT1		STR	Chip58:Chip
+END
+
+# Specify the layout
+CHIPS	METADATA
+	Chip		STR		Cell00:Cell
+END
+
+
+# Specify the cell data
+CELLS	METADATA
+	Cell		METADATA
+		CELL.TRIMSEC.SOURCE	STR	VALUE
+		CELL.TRIMSEC				STR	[49:2094,1:4098]
+		CELL.BIASSEC.SOURCE	STR	VALUE
+		CELL.BIASSEC				STR	[1:48,1:4098]
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.TELESCOPE		STR		TELESCOP
+	FPA.INSTRUMENT	STR		INSTRUME
+	FPA.OBSTYPE			STR		IMAGETYPE
+	FPA.AIRMASS			STR		AIRMASS
+	FPA.FILTER			STR		FILTER1
+	FPA.FILTERID		STR		FILTER1
+	FPA.RA					STR		RA
+	FPA.DEC					STR		DEC
+	FPA.OBJECT			STR		OBJECT
+	FPA.TIME				STR		MJD-OBS
+	FPA.EXPOSURE		STR		EXPTIME
+	CELL.EXPOSURE		STR		EXPTIME
+	CELL.DARKTIME		STR		EXPTIME
+	CELL.TIME				STR		MJD-OBS
+	#CELL.XBIN			STR		XBIN
+	#CELL.YBIN			STR		YBIN
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	FPA.DETECTOR		STR		WFI
+	FPA.TIMESYS			STR		UTC
+	FPA.RADECSYS		STR		ICRS
+	CELL.SATURATION	F32		65535
+	CELL.BAD				F32		0
+	CELL.READDIR		S32		1
+	CELL.TIMESYS		STR		UTC
+	CELL.XPARITY		S32		1
+	CELL.YPARITY		S32		1
+	CHIP.XPARITY		S32		1
+	CHIP.YPARITY		S32		1
+	CELL.X0					S32   0
+	CELL.Y0					S32   0
+	CHIP.X0.DEPEND	STR	CHIP.NAME
+	CHIP.X0		METADATA
+		Chip50	S32	1
+		Chip51	S32	2047
+		Chip52	S32	4093
+		Chip53	S32	6138
+		Chip54	S32	1
+		Chip55	S32	2047
+		Chip56	S32	4093
+		Chip57	S32	6138
+	END
+	CHIP.Y0.DEPEND	STR	CHIP.NAME
+	CHIP.Y0		METADATA
+		Chip50	S32	8196
+		Chip51	S32	8196
+		Chip52	S32	8196
+		Chip53	S32	8196
+		Chip54	S32	1
+		Chip55	S32	1
+		Chip56	S32	1
+		Chip57	S32	1
+	END
+	CELL.XSIZE			S32	2142
+	CELL.YSIZE			S32	4128
+	CELL.XWINDOW		S32	0
+	CELL.YWINDOW		S32	0
+	CELL.GAIN				F32	2.0
+	CELL.READNOISE	F32	3.0
+END
+
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/format_together.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/format_together.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/format_together.config	(revision 22157)
@@ -0,0 +1,117 @@
+# Simulation test camera with single chip with single cell
+
+# How to identify this type
+RULE	METADATA
+	TELESCOP	STR	MPI-2.2
+	INSTRUME	STR	WFI
+	NAXIS		S32	0
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR		FPA		# The FITS file represents an entire FPA
+	EXTENSIONS	STR		CHIP		# There are no extensions
+	FPA.OBS		STR		ESO OBS NAME	# A PHU keyword for unique FPA identifier
+END
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# Extension name ---> chip_name:chip_type
+	WIN1.CHIP1.OUT1		STR		Chip50:WFIChip
+	WIN1.CHIP2.OUT1		STR		Chip51:WFIChip
+	WIN1.CHIP3.OUT1		STR		Chip52:WFIChip
+	WIN1.CHIP4.OUT1		STR		Chip53:WFIChip
+	WIN1.CHIP5.OUT2		STR		Chip54:WFIChip
+	WIN1.CHIP6.OUT1		STR		Chip55:WFIChip
+	WIN1.CHIP7.OUT1		STR		Chip56:WFIChip
+	WIN1.CHIP8.OUT1		STR		Chip57:WFIChip
+END
+
+# Specify the layout
+CHIPS	METADATA
+	# Chip type ---> cell_name:cell_type
+	WFIChip		STR	Cell00:WFICell
+END
+
+
+# Specify the cell data
+CELLS	METADATA
+	WFICell		METADATA
+		CELL.TRIMSEC.SOURCE	STR	VALUE
+		CELL.TRIMSEC		STR	[49:2094,1:4098]
+		CELL.BIASSEC.SOURCE	STR	VALUE
+		CELL.BIASSEC		STR	[1:48,1:4098]
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.TELESCOPE		STR		TELESCOP
+	FPA.INSTRUMENT		STR		INSTRUME
+	FPA.OBSTYPE		STR		IMAGETYP
+	FPA.AIRMASS		STR		AIRMASS
+	FPA.FILTER		STR		FILTER1
+	FPA.FILTERID		STR		FILTER1
+	FPA.RA			STR		RA
+	FPA.DEC			STR		DEC
+	FPA.OBJECT		STR		OBJECT
+	FPA.TIME		STR		MJD-OBS
+	FPA.EXPOSURE		STR		EXPTIME
+	CELL.EXPOSURE		STR		EXPTIME
+	CELL.DARKTIME		STR		EXPTIME
+	CELL.TIME		STR		MJD-OBS
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	FPA.DETECTOR		STR		WFI
+	FPA.TIMESYS		STR		UTC
+	FPA.RADECSYS		STR		ICRS
+	CELL.SATURATION		F32		65535
+	CELL.BAD		F32		0
+	CELL.READDIR		S32		1
+	CELL.TIMESYS		STR		UTC
+	CELL.XPARITY		S32		1
+	CELL.YPARITY		S32		1
+	CHIP.XPARITY		S32		1
+	CHIP.YPARITY		S32		1
+	CELL.XBIN		S32		1
+	CELL.YBIN		S32		1
+	CELL.X0			S32   0
+	CELL.Y0			S32   0
+	CHIP.X0.DEPEND	STR	CHIP.NAME
+	CHIP.X0		METADATA
+		Chip50	S32	1
+		Chip51	S32	2047
+		Chip52	S32	4093
+		Chip53	S32	6138
+		Chip54	S32	1
+		Chip55	S32	2047
+		Chip56	S32	4093
+		Chip57	S32	6138
+	END
+	CHIP.Y0.DEPEND	STR	CHIP.NAME
+	CHIP.Y0		METADATA
+		Chip50	S32	8196
+		Chip51	S32	8196
+		Chip52	S32	8196
+		Chip53	S32	8196
+		Chip54	S32	1
+		Chip55	S32	1
+		Chip56	S32	1
+		Chip57	S32	1
+	END
+	CELL.XSIZE		S32	2142
+	CELL.YSIZE		S32	4128
+	CELL.XWINDOW		S32	0
+	CELL.YWINDOW		S32	0
+	CELL.GAIN		F32	2.0
+	CELL.READNOISE		F32	3.0
+END
+
+FORMATS		METADATA
+	FPA.RA		STR	DEGREES
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/ppImage.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/ppImage.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/ppImage.config	(revision 22157)
@@ -0,0 +1,222 @@
+### ppImage recipe configuration file for simulated test camera
+
+# List of tasks to perform
+
+BASE.FITS       BOOL    FALSE           # Save base detrended image?
+CHIP.FITS       BOOL    TRUE            # Save chip-mosaicked image?
+BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+
+# binned output image options
+BIN1.XBIN               S32     8
+BIN1.YBIN               S32     8
+BIN2.XBIN               S32     64
+BIN2.YBIN               S32     64
+
+# Overscan subtraction
+OVERSCAN.SINGLE         BOOL    FALSE           # Reduce overscan to a single value?
+OVERSCAN.FIT            STR     POLYNOMIAL      # NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER          S32     7               # Order of polynomial fit
+OVERSCAN.STAT           STR     MEAN            # MEAN | MEDIAN
+
+# How to select the appropriate detrend image
+DETREND.CONSTRAINTS  METADATA
+  BIAS METADATA
+  END
+  MASK METADATA
+  END
+  DARK METADATA
+  END
+  FLAT METADATA
+    FILTER  STR FPA.FILTER
+  END
+  FRINGE METADATA
+    FILTER   STR FPA.FILTER
+  END
+  SHUTTER METADATA
+  END   
+END
+
+########################################################################################
+# Need the following in order to turn off overscan and shutter for the site-level recipe
+########################################################################################
+
+# Overscan subtraction only
+PPIMAGE_O         METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Dark subtraction only
+PPIMAGE_D         METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Shutter correction only
+PPIMAGE_S         METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark
+PPIMAGE_OBD      METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter
+PPIMAGE_OBDS      METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field
+PPIMAGE_OBDSF     METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe
+PPIMAGE_OBDSFR    METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom
+PPIMAGE_OBDSFRP   METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_OBDSFRA   METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/psastro.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/psastro.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/psastro.config	(revision 22157)
@@ -0,0 +1,4 @@
+PSASTRO.CATDIR	STR	SYNTH.SIMTEST
+
+#PSASTRO.MOSAIC.GRADIENT.NX    S32     1   # number of x-dir cells per chip
+#PSASTRO.MOSAIC.GRADIENT.NY    S32     1   # number of y-dir cells per chip
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/psphot.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/psphot.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/psphot.config	(revision 22157)
@@ -0,0 +1,30 @@
+
+SAVE.BACKMDL	BOOL 	TRUE
+SAVE.BACKMDL.STDEV BOOL 	TRUE
+SAVE.BACKGND	BOOL 	TRUE
+SAVE.BACKSUB	BOOL 	TRUE
+SAVE.PLOTS      BOOL    FALSE
+SAVE.RESID	BOOL 	TRUE
+SAVE.PSF	BOOL 	TRUE
+LOAD.PSF	BOOL 	FALSE
+
+IMSTATS_NPIX        S32  3000    	 # number of pixels to use for sky estimate boxes:
+
+#PSPHOT_TEST METADATA
+# SAVE.BACKSUB	BOOL 	FALSE
+# SAVE.RESID	BOOL 	TRUE
+#END
+#
+#PEAKS_OUTPUT_FILE   STR  peaks.dat
+#MOMENTS_OUTPUT_FILE STR  moments.dat
+
+#PSF_SN_LIM          F32  20.0            # minimum S/N for stars used for PSF model
+MOMENTS_SN_MIN      F32  20.0            # min S/N to measure moments
+FULL_FIT_SN_LIM     F32  100.0
+EXT_MIN_SN          F32  100.0           # fit galaxies above this S/N limit
+
+PEAKS_NSIGMA_LIMIT  F32  10.0 	   	 # peak significance threshold
+BREAK_POINT         STR  ENSEMBLE        # limit total processing for now (for speed)
+
+
+SKY_STAT            STR  ROBUST_MEDIAN   # statistic used to measure background
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/rejections.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/rejections.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/esowfi/rejections.config	(revision 22157)
@@ -0,0 +1,45 @@
+
+DARK METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  2.0
+  IMFILE.STDEV       F32  0.0
+  IMFILE.MEANSTDEV   F32  0.0
+  IMFILE.SN          F32  0.0
+  IMFILE.BIN.STDEV   F32  1.0
+  IMFILE.BIN.SN      F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  1.0
+  EXP.STDEV          F32  0.0
+  EXP.MEANSTDEV      F32  0.5
+  EXP.SN             F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SN         F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  3.0
+  ENSEMBLE.STDEV     F32  3.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+
+SHUTTER METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  0.0
+  IMFILE.STDEV       F32  0.0
+  IMFILE.MEANSTDEV   F32  0.0
+  IMFILE.SN          F32  0.0
+  IMFILE.BIN.STDEV   F32  0.0
+  IMFILE.BIN.SN      F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  0.0
+  EXP.STDEV          F32  0.0
+  EXP.MEANSTDEV      F32  0.0
+  EXP.SN             F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SN         F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  0.0
+  ENSEMBLE.STDEV     F32  0.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/.cvsignore	(revision 22157)
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/Makefile.am	(revision 22157)
@@ -0,0 +1,23 @@
+
+installdir = $(datadir)/ippconfig/gpc1
+
+install_files = \
+	dvo.config \
+	dvo.layout \
+	camera.config \
+	format_raw.config \
+	format_mef.config \
+	ppImage.config \
+        ppMerge.config \
+	psphot.config \
+	psastro.config \
+        pswarp.config
+
+install_DATA = $(install_files)
+
+install-data-hook:
+	chmod 0755 $(installdir)
+
+EXTRA_DIST = $(install_files)
+
+ACLOCAL_AMFLAGS = -I m4
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/camera.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/camera.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/camera.config	(revision 22157)
@@ -0,0 +1,125 @@
+# Camera configuration file for GPC1: describes the camera
+
+# File formats that we know about
+FORMATS		METADATA
+	MEF	STR	gpc1/format_mef.config
+	RAW	STR	gpc1/format_raw.config
+END
+
+
+# Description of camera --- all the chips and the cells that comprise them
+FPA	METADATA
+        XY01  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY02  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY03  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY04  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY05  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY06  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY10  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY11  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY12  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY13  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY14  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY15  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY16  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY17  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY20  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY21  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY22  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY23  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY24  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY25  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY26  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY27  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY30  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY31  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY32  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY33  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY34  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY35  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY36  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY37  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY40  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY41  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY42  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY43  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY44  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY45  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY46  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY47  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY50  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY51  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY52  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY53  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY54  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY55  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY56  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY57  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY60  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY61  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY62  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY63  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY64  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY65  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY66  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY67  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY71  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY72  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY73  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY74  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY75  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+        XY76  STR xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+END	   
+
+# internal to external filter names
+FILTER.ID       METADATA
+   g        STR   g.00000
+   r        STR   r.00000
+   i        STR   i.00000
+   z        STR   z.00000
+   y        STR   y.00000
+   OPEN     STR   OPEN
+END
+
+# Table of strings to use for the class identifier (see ippdb) according to the file level.
+CLASSID         METADATA
+        FPA     STR     fpa
+        CHIP    STR     {CHIP.NAME}
+        CELL    STR     {CHIP.NAME}:{CELL.NAME}
+END
+
+DVO.CAMERADIR	STR	gpc1		# Camera directory for DVO
+
+# Recipe options
+RECIPES		METADATA
+	PPIMAGE		STR	gpc1/ppImage.config
+	PPMERGE		STR	gpc1/ppMerge.config
+	PSASTRO		STR	gpc1/psastro.config
+	PSPHOT		STR	gpc1/psphot.config
+	PSWARP		STR	gpc1/pswarp.config
+END
+
+# reduction classes (recipes which are grouped together)
+REDUCTION	STR	recipes/reductionClasses.mdc
+
+FITSTYPES       STR     recipes/fitstypes.mdc
+
+FILERULES	STR	recipes/filerules-split.mdc
+
+EXTNAME.RULES METADATA
+  CMF.HEAD STR {CHIP.NAME}.hdr
+  CMF.DATA STR {CHIP.NAME}.psf # use .PSF and .EXT?
+  CMF.XSRC STR {CHIP.NAME}.xsrc # use .PSF and .EXT?
+  CMF.XFIT STR {CHIP.NAME}.xfit # use .PSF and .EXT?
+
+  PSF.HEAD  STR	{CHIP.NAME}.hdr
+  PSF.TABLE STR {CHIP.NAME}.psf_model
+  PSF.RESID STR {CHIP.NAME}.psf_resid
+END
+
+BLANK.HEADERS	METADATA
+	FPA.TIME	STR	MJD-OBS
+	FPA.EXPOSURE	STR	EXPTIME
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.NAME	STR	EXPNUM
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/chips-layout.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/chips-layout.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/chips-layout.txt	(revision 22157)
@@ -0,0 +1,62 @@
+# 1st: from focal-plane layout gif
+# 2nd: from filename (*NM.fits)
+  CCID58-1-01b1  2  5    CCID58-1-01b1	2 5  
+  CCID58-1-01b2  3  5    CCID58-1-01b2	3 5  
+  CCID58-1-02a1  2  3    CCID58-1-02a1	2 3  
+  CCID58-1-02a2  3  3    CCID58-1-02a2	3 3  
+  CCID58-1-02b1  3  6    CCID58-1-02b1	3 6  
+  CCID58-1-02b2  6  3    CCID58-1-02b2	6 3  
+  CCID58-1-03b1  6  1    CCID58-1-03b1	6 1  
+  CCID58-1-03b2  4  1    CCID58-1-03b2	4 1  
+  CCID58-1-04a1  2  1    CCID58-1-04a1	2 1  
+  CCID58-1-04a2  2  4    CCID58-1-04a2	2 4  ** (not same as GIF)
+  CCID58-1-04b1  5  2    CCID58-1-04b1	5 2  
+  CCID58-1-05a2  4  4    CCID58-1-05a2	4 4  
+  CCID58-1-05b2  5  3    CCID58-1-05b2	5 3  
+  CCID58-1-07a1  6  4    CCID58-1-07a1	6 4  
+  CCID58-1-07b1  4  2    CCID58-1-07b1	4 2  
+  CCID58-1-09a1  0  4    CCID58-1-09a1	0 4  
+  CCID58-1-09a2  1  4    CCID58-1-09a2	1 4  
+  CCID58-1-09b1  1  6    CCID58-1-09b1	1 6  
+  CCID58-1-10a2  5  6    CCID58-1-10a2	5 6  
+  CCID58-1-12a1  3  0    CCID58-1-12a1	3 0  
+  CCID58-1-13a1  0  3    CCID58-1-13a1	0 3  
+  CCID58-1-14a2  1  3    CCID58-1-14a2	1 3  
+  CCID58-1-14b1  7  2    CCID58-1-14b1	7 2  
+  CCID58-1-17a2  1  2    CCID58-1-17a2	1 2  
+  CCID58-1-17b1  5  0    CCID58-1-17b1	5 0  
+  CCID58-1-18a1  7  4    CCID58-1-18a1	7 4  
+  CCID58-1-18b1  2  7    CCID58-1-18b1	2 7  
+  CCID58-1-19a1  2  0    CCID58-1-19a1	2 0  
+  CCID58-1-21a1  4  7    CCID58-1-21a1	4 7  
+  CCID58-1-21a2  4  5    CCID58-1-21a2	4 5  
+  CCID58-1-25b1  7  3    CCID58-1-25b1	7 3  
+  CCID58-2-01a1  3  1    CCID58-2-01a1	3 1  
+  CCID58-2-01a2  3  2    CCID58-2-01a2	3 2  
+  CCID58-2-04a1  3  4    CCID58-2-04a1	3 4  
+  CCID58-2-04b2  7  1    CCID58-2-04b2	7 1  
+  CCID58-2-05a1  7  6    CCID58-2-05a1	7 6  
+  CCID58-2-07a2  4  6    CCID58-2-07a2	4 6  
+  CCID58-2-07b2  2  6    CCID58-2-07b2	2 6  
+  CCID58-2-09a1  5  4    CCID58-2-09a1	5 4  
+  CCID58-2-09a2  2  2    CCID58-2-09a2	2 2  
+  CCID58-2-09b1  0  6    CCID58-2-09b1	0 6  
+  CCID58-2-09b2  5  1    CCID58-2-09b2	5 1  
+  CCID58-2-10b2  1  7    CCID58-2-10b2	3 7  **
+  CCID58-2-11b2  4  3    CCID58-2-11b2	4 3  
+  CCID58-2-12a1  5  7    CCID58-2-12a1	5 7  
+  CCID58-2-12a2  5  5    CCID58-2-12a2	5 5  
+  CCID58-2-13a2  1  1    CCID58-2-13a2 	1 1  
+  CCID58-2-13b1  1  5    CCID58-2-13b1	1 5  
+  CCID58-2-14a1  6  6    CCID58-2-14a1	6 6  
+  CCID58-2-15a1  0  1    CCID58-2-15a1	0 1  
+  CCID58-2-16a1  7  5    CCID58-2-16a1	7 5  
+  CCID58-2-16a2  6  7    CCID58-2-16a2	6 7  
+  CCID58-2-16b1  0  5    CCID58-2-16b1	0 5  
+  CCID58-2-16b2  3  7    CCID58-2-16b2	1 7  **
+  CCID58-2-17a2  6  5    CCID58-2-17a2	6 5  
+  CCID58-2-18a1  0  2    CCID58-2-18a1	0 2  
+  CCID58-2-18b2  4  0    CCID58-2-18b2	4 0  
+  CCID58-2-22a1  1  0    CCID58-2-22a1	1 0  
+  CCID58-2-22b2  6  0    CCID58-2-22b2	6 0  
+  CCID58-2-23b2  6  2    CCID58-2-23b2	6 2  
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/chips-script.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/chips-script.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/chips-script.txt	(revision 22157)
@@ -0,0 +1,11 @@
+
+# generate the offsets
+awk '{x=$2}{dx=157}($2 < 4){{x=$2+1}{dx=0}}(NR>2){printf "          XY%d%d  S32     %d\n", $2, $3, x*4971+dx}' chips-layout.txt | sort -k 1 > tmp
+awk '{y=$3}{dy=306}($2 < 4){{y=$3+1}{dy=0}}(NR>2){printf "          XY%d%d  S32     %d\n", $2, $3, y*5143+dy}' chips-layout.txt | sort -k 1 > tmp
+
+# generate the parities
+awk '{x=1}($2 < 4){x=-1}(NR>2){printf "          XY%d%d  S32     %d\n", $2, $3, x}' chips-layout.txt | sort -k 1 > tmp
+awk '{y=1}($2 < 4){y=-1}(NR>2){printf "          XY%d%d  S32     %d\n", $2, $3, y}' chips-layout.txt | sort -k 1 > tmp
+
+# generate the CONTENT table
+awk '(NR > 2){printf "        %s  STR  XY%d%d:GPCChip\n", $1, $2, $3}' chips-layout.txt | sort -k 3 > tmp
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/dvo.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/dvo.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/dvo.config	(revision 22157)
@@ -0,0 +1,67 @@
+
+# location of DVO database tables
+CATDIR			/data/alala.0/eugene/isp/catdir
+
+# keywords used by DVO to interpret the headers
+
+# keyword abstractions:
+#DATE-KEYWORD		DATE-OBS
+#DATE-MODE		yyyy-mm-dd
+#UT-KEYWORD		UTC-OBS
+JD-KEYWORD		NONE
+MJD-KEYWORD		MJD-OBS
+
+# other keyword abstractions
+EXPTIME-KEYWORD		EXPTIME
+AIRMASS-KEYWORD		AIRMASS
+CCDNUM-KEYWORD		EXTNAME
+ST-KEYWORD		NONE
+OBSERVATORY-LATITUDE	NONE
+OBSERVATORY-LONGITUDE	NONE
+SUBPIX_DATAFILE		NONE
+
+# instrumental magnitude range for calibration mode 
+CAL_INSTMAG_MAX		0
+CAL_INSTMAG_MIN		0
+
+# exclude overscan region from the dB image boundaries
+XOVERSCAN		0
+YOVERSCAN		0
+
+# only upload stars within region; a value of 0 means ignore the limit
+ADDSTAR_XMIN		0
+ADDSTAR_XMAX		0
+ADDSTAR_YMIN		0
+ADDSTAR_YMAX		0
+
+# exclude stars with SN > SNLIMIT (ADDSTAR_SNLIMIT overrides old name MIN_SN_FSTAT)
+ADDSTAR_SNLIMIT		0
+
+# correlation radius (arcseconds)
+ADDSTAR_RADIUS		1.0
+
+# scaled correlation radius 
+ADDSTAR_NSIGMA		0
+
+IMAGE_SCATTER           0.075  # mark images POOR if stdev(Mcal) > IMAGE_SCATTER
+STAR_SCATTER            0.005  # mark stars POOR if stdev(Mrel) > STAR_SCATTER
+IMAGE_OFFSET            0.100  # mark images POOR if abs(delta(Mcal)) > IMAGE_OFFSET
+STAR_CHISQ              10.0   # mark stars POOR if Xm > STAR_CHISQ
+STAR_TOOFEW              3     # mark star FEW if N(good) < STAR_TOOFEW
+IMAGE_TOOFEW            10     # mark image FEW if N(good) < IMAGE_TOOFEW
+IMAGE_GOOD_FRACTION     0.05   # mark image FEW if N(good) < IMAGE_GOOD_FRACTION * Nstars
+
+SCATTER_LIM             15.0
+MAG_LIM                 20.0   # select stars brighter than this for relphot analysis
+SIGMA_LIM               0.015  # select measurements with dM < SIGMA_LIM for relphot analysis
+# INST_MAG_MIN         -17     # optional constraints on magnitude ranges
+# INST_MAG_MAX         -13     # optional constraints on magnitude ranges
+
+RELPHOT_GRID_BINNING    256
+RELPHOT_GRID_X 6
+RELPHOT_GRID_Y 14
+
+PM_DT_MIN               0.25
+PM_TOOFEW               4
+POS_TOOFEW              1
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/dvo.layout
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/dvo.layout	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/dvo.layout	(revision 22157)
@@ -0,0 +1,58 @@
+# this file defines the layout of the mosaic imager:
+
+# NCCD 40  # this needs work: sometimes 36, sometimes 40
+NCCD 36
+NAXIS1 2112
+NAXIS2 4644
+
+MOSAIC_X 11
+MOSAIC_Y  4
+
+# lines need to contain:
+
+CHIPID_KEYWORD EXTNAME
+
+# ID     CHIPID xoffset yoffset xflip yflip datasec         biassec            Xo      Yo      theta
+CCD.0     ccd00       1       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.1     ccd01       2       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.2     ccd02       3       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.3     ccd03       4       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.4     ccd04       5       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.5     ccd05       6       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.6     ccd06       7       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.7     ccd07       8       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.8     ccd08       9       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.9     ccd09       1       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.10    ccd10       2       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.11    ccd11       3       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.12    ccd12       4       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.13    ccd13       5       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.14    ccd14       6       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.15    ccd15       7       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.16    ccd16       8       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.17    ccd17       9       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.18    ccd18       1       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.19    ccd19       2       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.20    ccd20       3       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.21    ccd21       4       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.22    ccd22       5       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.23    ccd23       6       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.24    ccd24       7       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.25    ccd25       8       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.26    ccd26       9       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.27    ccd27       1       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.28    ccd28       2       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.29    ccd29       3       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.30    ccd30       4       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.31    ccd31       5       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.32    ccd32       6       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.33    ccd33       7       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.34    ccd34       8       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.35    ccd35       9       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.36    ccd36       0       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.37    ccd37      10       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.38    ccd38       0       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.39    ccd39      10       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+
+# can we use header to find biassec
+USE_BIASSEC 1
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/format_mef.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/format_mef.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/format_mef.config	(revision 22157)
@@ -0,0 +1,497 @@
+# The raw GPC data comes off the telescope with each of the chips stored in separate files
+
+# How to identify this type
+RULE	METADATA
+#	TELESCOP	STR	PS1
+#	DETECTOR	STR	GPC1
+	CONTROLR	STR	STARGRASP
+	PSFORMAT        STR     MEF
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA	   # The FITS file represents a complete camera fpa
+	EXTENSIONS	STR	CHIP	   # The extensions represent chips
+	FPA.OBS		STR	CONTROLR   # A PHU keyword for unique identifier within the hierarchy level
+END
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# CONTENT      =    chip name : type
+        CCID58-2-15a1  STR  XY01:GPCChip
+        CCID58-2-18a1  STR  XY02:GPCChip
+        CCID58-1-13a1  STR  XY03:GPCChip
+        CCID58-1-09a1  STR  XY04:GPCChip
+        CCID58-2-16b1  STR  XY05:GPCChip
+        CCID58-2-09b1  STR  XY06:GPCChip
+        CCID58-2-22a1  STR  XY10:GPCChip
+        CCID58-2-13a2  STR  XY11:GPCChip
+        CCID58-1-17a2  STR  XY12:GPCChip
+        CCID58-1-14a2  STR  XY13:GPCChip
+        CCID58-1-09a2  STR  XY14:GPCChip
+        CCID58-2-13b1  STR  XY15:GPCChip
+        CCID58-1-09b1  STR  XY16:GPCChip
+        CCID58-2-10b2  STR  XY17:GPCChip
+        CCID58-1-19a1  STR  XY20:GPCChip
+        CCID58-1-04a1  STR  XY21:GPCChip
+        CCID58-2-09a2  STR  XY22:GPCChip
+        CCID58-1-02a1  STR  XY23:GPCChip
+        CCID58-1-04a2  STR  XY24:GPCChip
+        CCID58-1-01b1  STR  XY25:GPCChip
+        CCID58-2-07b2  STR  XY26:GPCChip
+        CCID58-1-18b1  STR  XY27:GPCChip
+        CCID58-1-12a1  STR  XY30:GPCChip
+        CCID58-2-01a1  STR  XY31:GPCChip
+        CCID58-2-01a2  STR  XY32:GPCChip
+        CCID58-1-02a2  STR  XY33:GPCChip
+        CCID58-2-04a1  STR  XY34:GPCChip
+        CCID58-1-01b2  STR  XY35:GPCChip
+        CCID58-1-02b1  STR  XY36:GPCChip
+        CCID58-2-16b2  STR  XY37:GPCChip
+        CCID58-2-18b2  STR  XY40:GPCChip
+        CCID58-1-03b2  STR  XY41:GPCChip
+        CCID58-1-07b1  STR  XY42:GPCChip
+        CCID58-2-11b2  STR  XY43:GPCChip
+        CCID58-1-05a2  STR  XY44:GPCChip
+        CCID58-1-21a2  STR  XY45:GPCChip
+        CCID58-2-07a2  STR  XY46:GPCChip
+        CCID58-1-21a1  STR  XY47:GPCChip
+        CCID58-1-17b1  STR  XY50:GPCChip
+        CCID58-2-09b2  STR  XY51:GPCChip
+        CCID58-1-04b1  STR  XY52:GPCChip
+        CCID58-1-05b2  STR  XY53:GPCChip
+        CCID58-2-09a1  STR  XY54:GPCChip
+        CCID58-2-12a2  STR  XY55:GPCChip
+        CCID58-1-10a2  STR  XY56:GPCChip
+        CCID58-2-12a1  STR  XY57:GPCChip
+        CCID58-2-22b2  STR  XY60:GPCChip
+        CCID58-1-03b1  STR  XY61:GPCChip
+        CCID58-2-23b2  STR  XY62:GPCChip
+        CCID58-1-02b2  STR  XY63:GPCChip
+        CCID58-1-07a1  STR  XY64:GPCChip
+        CCID58-2-17a2  STR  XY65:GPCChip
+        CCID58-2-14a1  STR  XY66:GPCChip
+        CCID58-2-16a2  STR  XY67:GPCChip
+        CCID58-2-04b2  STR  XY71:GPCChip
+        CCID58-1-14b1  STR  XY72:GPCChip
+        CCID58-1-25b1  STR  XY73:GPCChip
+        CCID58-1-18a1  STR  XY74:GPCChip
+        CCID58-2-16a1  STR  XY75:GPCChip
+        CCID58-2-05a1  STR  XY76:GPCChip
+END
+
+CHIPS   METADATA
+	GPCChip		METADATA
+		# Extension name, cellName:cellType
+		xy00   STR  xy00:GPCCell
+		xy10   STR  xy10:GPCCell
+		xy20   STR  xy20:GPCCell
+		xy30   STR  xy30:GPCCell
+		xy40   STR  xy40:GPCCell
+		xy50   STR  xy50:GPCCell
+		xy60   STR  xy60:GPCCell
+		xy70   STR  xy70:GPCCell
+		xy01   STR  xy01:GPCCell
+		xy11   STR  xy11:GPCCell
+		xy21   STR  xy21:GPCCell
+		xy31   STR  xy31:GPCCell
+		xy41   STR  xy41:GPCCell
+		xy51   STR  xy51:GPCCell
+		xy61   STR  xy61:GPCCell
+		xy71   STR  xy71:GPCCell
+		xy02   STR  xy02:GPCCell
+		xy12   STR  xy12:GPCCell
+		xy22   STR  xy22:GPCCell
+		xy32   STR  xy32:GPCCell
+		xy42   STR  xy42:GPCCell
+		xy52   STR  xy52:GPCCell
+		xy62   STR  xy62:GPCCell
+		xy72   STR  xy72:GPCCell
+		xy03   STR  xy03:GPCCell
+		xy13   STR  xy13:GPCCell
+		xy23   STR  xy23:GPCCell
+		xy33   STR  xy33:GPCCell
+		xy43   STR  xy43:GPCCell
+		xy53   STR  xy53:GPCCell
+		xy63   STR  xy63:GPCCell
+		xy73   STR  xy73:GPCCell
+		xy04   STR  xy04:GPCCell
+		xy14   STR  xy14:GPCCell
+		xy24   STR  xy24:GPCCell
+		xy34   STR  xy34:GPCCell
+		xy44   STR  xy44:GPCCell
+		xy54   STR  xy54:GPCCell
+		xy64   STR  xy64:GPCCell
+		xy74   STR  xy74:GPCCell
+		xy05   STR  xy05:GPCCell
+		xy15   STR  xy15:GPCCell
+		xy25   STR  xy25:GPCCell
+		xy35   STR  xy35:GPCCell
+		xy45   STR  xy45:GPCCell
+		xy55   STR  xy55:GPCCell
+		xy65   STR  xy65:GPCCell
+		xy75   STR  xy75:GPCCell
+		xy06   STR  xy06:GPCCell
+		xy16   STR  xy16:GPCCell
+		xy26   STR  xy26:GPCCell
+		xy36   STR  xy36:GPCCell
+		xy46   STR  xy46:GPCCell
+		xy56   STR  xy56:GPCCell
+		xy66   STR  xy66:GPCCell
+		xy76   STR  xy76:GPCCell
+		xy07   STR  xy07:GPCCell
+		xy17   STR  xy17:GPCCell
+		xy27   STR  xy27:GPCCell
+		xy37   STR  xy37:GPCCell
+		xy47   STR  xy47:GPCCell
+		xy57   STR  xy57:GPCCell
+		xy67   STR  xy67:GPCCell
+		xy77   STR  xy77:GPCCell
+	END
+END
+
+# Specify the cell data
+CELLS	METADATA
+	GPCCell		METADATA
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC		STR	DATASEC
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BIASSEC
+	END
+END
+
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+        FPA.FILTERID    STR     FILTERID
+        FPA.FILTER      STR     FILTERID
+        FPA.RA          STR     RA
+        FPA.DEC         STR     DEC
+        FPA.RADECSYS    STR     RADECSYS
+	FPA.OBSTYPE     STR     OBSTYPE
+	FPA.OBJECT	STR	OBJECT
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.POSANGLE	STR	POSANGLE
+	FPA.FOCUS	STR	M2Z
+	FPA.TIME	STR	MJD-OBS
+	FPA.ALT		STR	ALT
+	FPA.AZ		STR	AZ
+	FPA.TEMP	STR	DETTEM
+	CHIP.ID         STR	DETECTOR
+	CELL.XBIN	STR	CCDSUM
+	CELL.YBIN	STR	CCDSUM
+	CELL.X0		STR	IMNPIX1
+	CELL.Y0		STR	IMNPIX2
+ 	CELL.XPARITY	STR	LTM1_1
+	CELL.YPARITY	STR	LTM2_2
+#	CELL.SATURATION	STR	SATURATE
+	FPA.EXPOSURE	STR	EXPREQ		# Requested exposure time, presumably camera exposure time
+	CELL.EXPOSURE	STR	EXPTIME		# Exposure time measured by shutter
+	CELL.DARKTIME	STR	DARKTIME	# Exposure time for camera
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	FPA.TELESCOPE	STR	PS1
+	FPA.INSTRUMENT	STR	GPC1
+	FPA.DETECTOR	STR	GPC1
+	FPA.OBS	S32	12345
+	FPA.TIMESYS	STR	UTC
+	CHIP.XPARITY	S32	1
+	CHIP.YPARITY	S32	1
+	CHIP.X0.DEPEND		STR	CHIP.NAME
+	CHIP.X0		METADATA
+          XY01  S32     4971
+          XY02  S32     4971
+          XY03  S32     4971
+          XY04  S32     4971
+          XY05  S32     4971
+          XY06  S32     4971
+          XY10  S32     9942
+          XY11  S32     9942
+          XY12  S32     9942
+          XY13  S32     9942
+          XY14  S32     9942
+          XY15  S32     9942
+          XY16  S32     9942
+          XY17  S32     9942
+          XY20  S32     14913
+          XY21  S32     14913
+          XY22  S32     14913
+          XY23  S32     14913
+          XY24  S32     14913
+          XY25  S32     14913
+          XY26  S32     14913
+          XY27  S32     14913
+          XY30  S32     19884
+          XY31  S32     19884
+          XY32  S32     19884
+          XY33  S32     19884
+          XY34  S32     19884
+          XY35  S32     19884
+          XY36  S32     19884
+          XY37  S32     19884
+          XY40  S32     20041
+          XY41  S32     20041
+          XY42  S32     20041
+          XY43  S32     20041
+          XY44  S32     20041
+          XY45  S32     20041
+          XY46  S32     20041
+          XY47  S32     20041
+          XY50  S32     25012
+          XY51  S32     25012
+          XY52  S32     25012
+          XY53  S32     25012
+          XY54  S32     25012
+          XY55  S32     25012
+          XY56  S32     25012
+          XY57  S32     25012
+          XY60  S32     29983
+          XY61  S32     29983
+          XY62  S32     29983
+          XY63  S32     29983
+          XY64  S32     29983
+          XY65  S32     29983
+          XY66  S32     29983
+          XY67  S32     29983
+          XY71  S32     34954
+          XY72  S32     34954
+          XY73  S32     34954
+          XY74  S32     34954
+          XY75  S32     34954
+          XY76  S32     34954
+	END
+	CHIP.Y0.DEPEND		STR	CHIP.NAME
+	CHIP.Y0		METADATA
+          XY01  S32     10286
+          XY02  S32     15429
+          XY03  S32     20572
+          XY04  S32     25715
+          XY05  S32     30858
+          XY06  S32     36001
+          XY10  S32     5143
+          XY11  S32     10286
+          XY12  S32     15429
+          XY13  S32     20572
+          XY14  S32     25715
+          XY15  S32     30858
+          XY16  S32     36001
+          XY17  S32     41144
+          XY20  S32     5143
+          XY21  S32     10286
+          XY22  S32     15429
+          XY23  S32     20572
+          XY24  S32     25715
+          XY25  S32     30858
+          XY26  S32     36001
+          XY27  S32     41144
+          XY30  S32     5143
+          XY31  S32     10286
+          XY32  S32     15429
+          XY33  S32     20572
+          XY34  S32     25715
+          XY35  S32     30858
+          XY36  S32     36001
+          XY37  S32     41144
+          XY40  S32     306
+          XY41  S32     5449
+          XY42  S32     10592
+          XY43  S32     15735
+          XY44  S32     20878
+          XY45  S32     26021
+          XY46  S32     31164
+          XY47  S32     36307
+          XY50  S32     306
+          XY51  S32     5449
+          XY52  S32     10592
+          XY53  S32     15735
+          XY54  S32     20878
+          XY55  S32     26021
+          XY56  S32     31164
+          XY57  S32     36307
+          XY60  S32     306
+          XY61  S32     5449
+          XY62  S32     10592
+          XY63  S32     15735
+          XY64  S32     20878
+          XY65  S32     26021
+          XY66  S32     31164
+          XY67  S32     36307
+          XY71  S32     5449
+          XY72  S32     10592
+          XY73  S32     15735
+          XY74  S32     20878
+          XY75  S32     26021
+          XY76  S32     31164
+        END
+	CHIP.XPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.XPARITY	METADATA
+          XY01  S32     -1
+          XY02  S32     -1
+          XY03  S32     -1
+          XY04  S32     -1
+          XY05  S32     -1
+          XY06  S32     -1
+          XY10  S32     -1
+          XY11  S32     -1
+          XY12  S32     -1
+          XY13  S32     -1
+          XY14  S32     -1
+          XY15  S32     -1
+          XY16  S32     -1
+          XY17  S32     -1
+          XY20  S32     -1
+          XY21  S32     -1
+          XY22  S32     -1
+          XY23  S32     -1
+          XY24  S32     -1
+          XY25  S32     -1
+          XY26  S32     -1
+          XY27  S32     -1
+          XY30  S32     -1
+          XY31  S32     -1
+          XY32  S32     -1
+          XY33  S32     -1
+          XY34  S32     -1
+          XY35  S32     -1
+          XY36  S32     -1
+          XY37  S32     -1
+          XY40  S32     1
+          XY41  S32     1
+          XY42  S32     1
+          XY43  S32     1
+          XY44  S32     1
+          XY45  S32     1
+          XY46  S32     1
+          XY47  S32     1
+          XY50  S32     1
+          XY51  S32     1
+          XY52  S32     1
+          XY53  S32     1
+          XY54  S32     1
+          XY55  S32     1
+          XY56  S32     1
+          XY57  S32     1
+          XY60  S32     1
+          XY61  S32     1
+          XY62  S32     1
+          XY63  S32     1
+          XY64  S32     1
+          XY65  S32     1
+          XY66  S32     1
+          XY67  S32     1
+          XY71  S32     1
+          XY72  S32     1
+          XY73  S32     1
+          XY74  S32     1
+          XY75  S32     1
+          XY76  S32     1
+	END
+	CHIP.YPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.YPARITY	METADATA
+          XY01  S32     -1
+          XY02  S32     -1
+          XY03  S32     -1
+          XY04  S32     -1
+          XY05  S32     -1
+          XY06  S32     -1
+          XY10  S32     -1
+          XY11  S32     -1
+          XY12  S32     -1
+          XY13  S32     -1
+          XY14  S32     -1
+          XY15  S32     -1
+          XY16  S32     -1
+          XY17  S32     -1
+          XY20  S32     -1
+          XY21  S32     -1
+          XY22  S32     -1
+          XY23  S32     -1
+          XY24  S32     -1
+          XY25  S32     -1
+          XY26  S32     -1
+          XY27  S32     -1
+          XY30  S32     -1
+          XY31  S32     -1
+          XY32  S32     -1
+          XY33  S32     -1
+          XY34  S32     -1
+          XY35  S32     -1
+          XY36  S32     -1
+          XY37  S32     -1
+          XY40  S32     1
+          XY41  S32     1
+          XY42  S32     1
+          XY43  S32     1
+          XY44  S32     1
+          XY45  S32     1
+          XY46  S32     1
+          XY47  S32     1
+          XY50  S32     1
+          XY51  S32     1
+          XY52  S32     1
+          XY53  S32     1
+          XY54  S32     1
+          XY55  S32     1
+          XY56  S32     1
+          XY57  S32     1
+          XY60  S32     1
+          XY61  S32     1
+          XY62  S32     1
+          XY63  S32     1
+          XY64  S32     1
+          XY65  S32     1
+          XY66  S32     1
+          XY67  S32     1
+          XY71  S32     1
+          XY72  S32     1
+          XY73  S32     1
+          XY74  S32     1
+          XY75  S32     1
+          XY76  S32     1
+	END
+	CELL.GAIN	F32	1.0
+	CELL.READNOISE	F32	0.0
+	CELL.READDIR	S32	1
+	CELL.BAD	S32	0
+#	CELL.TIME	STR	MJD-OBS
+#	CELL.TIMESYS	STR	TIMESYS
+#	CELL.SATURATION	STR	60000
+END
+
+# How to translation PS concepts into database lookups
+DATABASE	METADATA
+	TYPE		dbEntry		TABLE		COLUMN		GIVENDBCOL	GIVENPS
+#	CELL.GAIN	dbEntry		Camera		gain		chipId,cellId	CHIP,CELL
+#	CELL.READNOISE	dbEntry		Camera		readNoise	chipId,cellId	CHIP,CELL
+
+# A database entry refers to a particular column (COLUMN) in a
+# particular table (TABLE), given certain PS concepts (GIVENPS) that
+# match certain database columns (GIVENDBCOL).
+
+END
+
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+	FPA.RA		STR	DEGREES
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+	CELL.BINNING	STR	TOGETHER
+	CELL.X0		STR	FORTRAN
+	CELL.Y0		STR	FORTRAN
+END
+ 
+# Recipe options
+RECIPES		METADATA
+END
+ 
+# How to get the supplementary stuff: mask and weight
+SUPPLEMENTARY	METADATA
+	MASK.SOURCE	STR	FILE		# Source type for mask: EXT | FILE
+	MASK.NAME	STR	%a_mask.fits	# Name for mask extension or filename
+	WEIGHT.SOURCE	STR	FILE		# Source type for weight: EXT | FILE
+	WEIGHT.NAME	STR	%a_weight.fits	# Name for weight extension or filename
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/format_raw.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/format_raw.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/format_raw.config	(revision 22157)
@@ -0,0 +1,588 @@
+# The raw GPC data comes off the telescope with each of the chips stored in separate files
+
+# How to identify this type
+RULE    METADATA
+#	ORIGIN		STR	PS1
+#        TELESCOP        STR     PS1
+###	SOMETHING	STR	GPC1
+        CONTROLR        STR     STARGRASP
+        NAMPS           S32     64
+END
+
+# How to read this data
+FILE    METADATA
+        PHU             STR     CHIP       # The FITS file represents a single chip
+        EXTENSIONS      STR     CELL       # The extensions represent cells
+        FPA.OBS         STR     CONTROLR   # A PHU keyword for unique identifier within the hierarchy level
+        CONTENT         STR     DETECTOR   # How to determine content of FITS file
+        CONTENT.RULE    STR     {CHIP.ID}  # How to derive the CONTENT when writing
+END
+
+# What's in the FITS file?
+CONTENTS        METADATA
+        # CONTENT      =    chip name : type
+        CCID58-2-15a1  STR  XY01:GPCChip  # chip 00
+        CCID58-2-18a1  STR  XY02:GPCChip  # chip 01
+        CCID58-1-13a1  STR  XY03:GPCChip  # chip 02
+        CCID58-1-09a1  STR  XY04:GPCChip  # chip 03
+        CCID58-2-16b1  STR  XY05:GPCChip  # chip 04
+        CCID58-2-09b1  STR  XY06:GPCChip  # chip 05
+        CCID58-2-22a1  STR  XY10:GPCChip  # chip 06
+        CCID58-2-13a2  STR  XY11:GPCChip  # chip 07
+        CCID58-1-17a2  STR  XY12:GPCChip  # chip 08
+        CCID58-1-14a2  STR  XY13:GPCChip  # chip 09
+        CCID58-1-09a2  STR  XY14:GPCChip  # chip 10
+        CCID58-2-13b1  STR  XY15:GPCChip  # chip 11
+        CCID58-1-09b1  STR  XY16:GPCChip  # chip 12
+        CCID58-2-10b2  STR  XY17:GPCChip  # chip 13
+        CCID58-1-19a1  STR  XY20:GPCChip  # chip 14
+        CCID58-1-04a1  STR  XY21:GPCChip  # chip 15
+        CCID58-2-09a2  STR  XY22:GPCChip  # chip 16
+        CCID58-1-02a1  STR  XY23:GPCChip  # chip 17
+        CCID58-1-04a2  STR  XY24:GPCChip  # chip 18
+        CCID58-1-01b1  STR  XY25:GPCChip  # chip 19
+        CCID58-2-07b2  STR  XY26:GPCChip  # chip 20
+        CCID58-1-18b1  STR  XY27:GPCChip  # chip 21
+        CCID58-1-12a1  STR  XY30:GPCChip  # chip 22
+        CCID58-2-01a1  STR  XY31:GPCChip  # chip 23
+        CCID58-2-01a2  STR  XY32:GPCChip  # chip 24
+        CCID58-1-02a2  STR  XY33:GPCChip  # chip 25
+        CCID58-2-04a1  STR  XY34:GPCChip  # chip 26
+        CCID58-1-01b2  STR  XY35:GPCChip  # chip 27
+        CCID58-1-02b1  STR  XY36:GPCChip  # chip 28
+        CCID58-2-16b2  STR  XY37:GPCChip  # chip 29
+        CCID58-2-18b2  STR  XY40:GPCChip  # chip 30
+        CCID58-1-03b2  STR  XY41:GPCChip  # chip 31
+        CCID58-1-07b1  STR  XY42:GPCChip  # chip 32
+        CCID58-2-11b2  STR  XY43:GPCChip  # chip 33
+        CCID58-1-05a2  STR  XY44:GPCChip  # chip 34
+        CCID58-1-21a2  STR  XY45:GPCChip  # chip 35
+        CCID58-2-07a2  STR  XY46:GPCChip  # chip 36
+        CCID58-1-21a1  STR  XY47:GPCChip  # chip 37
+        CCID58-1-17b1  STR  XY50:GPCChip  # chip 38
+        CCID58-2-09b2  STR  XY51:GPCChip  # chip 39
+        CCID58-1-04b1  STR  XY52:GPCChip  # chip 40
+        CCID58-1-05b2  STR  XY53:GPCChip  # chip 41
+        CCID58-2-09a1  STR  XY54:GPCChip  # chip 42
+        CCID58-2-12a2  STR  XY55:GPCChip  # chip 43
+        CCID58-1-10a2  STR  XY56:GPCChip  # chip 44
+        CCID58-2-12a1  STR  XY57:GPCChip  # chip 45
+        CCID58-2-22b2  STR  XY60:GPCChip  # chip 46
+        CCID58-1-03b1  STR  XY61:GPCChip  # chip 47
+        CCID58-2-23b2  STR  XY62:GPCChip  # chip 48
+        CCID58-1-02b2  STR  XY63:GPCChip  # chip 49
+        CCID58-1-07a1  STR  XY64:GPCChip  # chip 50
+        CCID58-2-17a2  STR  XY65:GPCChip  # chip 51
+        CCID58-2-14a1  STR  XY66:GPCChip  # chip 52
+        CCID58-2-16a2  STR  XY67:GPCChip  # chip 53
+        CCID58-2-04b2  STR  XY71:GPCChip  # chip 54
+        CCID58-1-14b1  STR  XY72:GPCChip  # chip 55
+        CCID58-1-25b1  STR  XY73:GPCChip  # chip 56
+        CCID58-1-18a1  STR  XY74:GPCChip  # chip 57
+        CCID58-2-16a1  STR  XY75:GPCChip  # chip 58
+        CCID58-2-05a1  STR  XY76:GPCChip  # chip 59
+END
+
+CHIPS   METADATA
+        GPCChip         METADATA
+                # Extension name, cellName:cellType
+                xy00   STR  xy00:GPCCell
+                xy10   STR  xy10:GPCCell
+                xy20   STR  xy20:GPCCell
+                xy30   STR  xy30:GPCCell
+                xy40   STR  xy40:GPCCell
+                xy50   STR  xy50:GPCCell
+                xy60   STR  xy60:GPCCell
+                xy70   STR  xy70:GPCCell
+                xy01   STR  xy01:GPCCell
+                xy11   STR  xy11:GPCCell
+                xy21   STR  xy21:GPCCell
+                xy31   STR  xy31:GPCCell
+                xy41   STR  xy41:GPCCell
+                xy51   STR  xy51:GPCCell
+                xy61   STR  xy61:GPCCell
+                xy71   STR  xy71:GPCCell
+                xy02   STR  xy02:GPCCell
+                xy12   STR  xy12:GPCCell
+                xy22   STR  xy22:GPCCell
+                xy32   STR  xy32:GPCCell
+                xy42   STR  xy42:GPCCell
+                xy52   STR  xy52:GPCCell
+                xy62   STR  xy62:GPCCell
+                xy72   STR  xy72:GPCCell
+                xy03   STR  xy03:GPCCell
+                xy13   STR  xy13:GPCCell
+                xy23   STR  xy23:GPCCell
+                xy33   STR  xy33:GPCCell
+                xy43   STR  xy43:GPCCell
+                xy53   STR  xy53:GPCCell
+                xy63   STR  xy63:GPCCell
+                xy73   STR  xy73:GPCCell
+                xy04   STR  xy04:GPCCell
+                xy14   STR  xy14:GPCCell
+                xy24   STR  xy24:GPCCell
+                xy34   STR  xy34:GPCCell
+                xy44   STR  xy44:GPCCell
+                xy54   STR  xy54:GPCCell
+                xy64   STR  xy64:GPCCell
+                xy74   STR  xy74:GPCCell
+                xy05   STR  xy05:GPCCell
+                xy15   STR  xy15:GPCCell
+                xy25   STR  xy25:GPCCell
+                xy35   STR  xy35:GPCCell
+                xy45   STR  xy45:GPCCell
+                xy55   STR  xy55:GPCCell
+                xy65   STR  xy65:GPCCell
+                xy75   STR  xy75:GPCCell
+                xy06   STR  xy06:GPCCell
+                xy16   STR  xy16:GPCCell
+                xy26   STR  xy26:GPCCell
+                xy36   STR  xy36:GPCCell
+                xy46   STR  xy46:GPCCell
+                xy56   STR  xy56:GPCCell
+                xy66   STR  xy66:GPCCell
+                xy76   STR  xy76:GPCCell
+                xy07   STR  xy07:GPCCell
+                xy17   STR  xy17:GPCCell
+                xy27   STR  xy27:GPCCell
+                xy37   STR  xy37:GPCCell
+                xy47   STR  xy47:GPCCell
+                xy57   STR  xy57:GPCCell
+                xy67   STR  xy67:GPCCell
+                xy77   STR  xy77:GPCCell
+        END
+END
+
+# Specify the cell data
+CELLS   METADATA
+        GPCCell         METADATA
+                CELL.TRIMSEC.SOURCE     STR     HEADER
+                CELL.TRIMSEC            STR     DATASEC
+                CELL.BIASSEC.SOURCE     STR     HEADER
+                CELL.BIASSEC            STR     BIASSEC
+        END
+END
+
+
+# How to translate PS concepts into FITS headers
+TRANSLATION     METADATA
+        FPA.FILTERID    STR     FILTERID
+        FPA.FILTER      STR     FILTERID
+        FPA.RA          STR     RA
+        FPA.DEC         STR     DEC
+        # FPA.RA          STR     COMRA
+        # FPA.DEC         STR     COMDEC
+        FPA.RADECSYS    STR     RADECSYS
+        FPA.OBSTYPE     STR     OBSTYPE
+        FPA.OBJECT      STR     OBJECT
+        FPA.COMMENT     STR     CMMTOBS
+        FPA.AIRMASS     STR     AIRMASS
+        FPA.POSANGLE    STR     POSANGLE
+        FPA.FOCUS       STR     M2Z
+        FPA.TIME        STR     MJD-OBS
+        FPA.ALT         STR     ALT
+        FPA.AZ          STR     AZ
+        FPA.TEMP        STR     DETTEM
+        FPA.M1X         STR     M1X
+        FPA.M1Y         STR     M1Y   
+        FPA.M1Z         STR     M1Z   
+        FPA.M1TIP       STR     M1TIP 
+        FPA.M1TILT      STR     M1TILT
+        FPA.M2X         STR     M2X
+        FPA.M2Y         STR     M2Y   
+        FPA.M2Z         STR     M2Z   
+        FPA.M2TIP       STR     M2TIP 
+        FPA.M2TILT      STR     M2TILT
+        FPA.ENV.TEMP    STR     ENVTEM
+        FPA.ENV.HUMID   STR     ENVHUM
+        FPA.ENV.WIND    STR     ENVWIN
+        FPA.ENV.DIR     STR     ENVDIR
+
+        FPA.TELTEMP.M1     STR  TELTEMM1 # Primary mirror temps (C) 
+        FPA.TELTEMP.M1CELL STR  TELTEMMS # Primary mirror support temps (C)   
+        FPA.TELTEMP.M2     STR  TELTEMM2 # Secondary mirror temps (C
+        FPA.TELTEMP.SPIDER STR  TELTEMSP # Spider temperatures (C)  
+        FPA.TELTEMP.TRUSS  STR  TELTEMTR # Mid truss temperatures (C
+        FPA.TELTEMP.EXTRA  STR  TELTEMEX # Miscellaneous temperatures (C)     
+
+        FPA.PON.TIME    STR     PONTIME
+
+        CHIP.ID         STR     DETECTOR
+        CELL.XBIN       STR     CCDSUM
+        CELL.YBIN       STR     CCDSUM
+        CELL.X0         STR     IMNPIX1
+        CELL.Y0         STR     IMNPIX2
+        CELL.XPARITY    STR     LTM1_1
+        CELL.YPARITY    STR     LTM2_2
+#       CELL.SATURATION STR     SATURATE
+
+#        CHIP.TEMP       STR     DETTEM	# XY24 is bad, so get it from the database; all others from header
+	CHIP.TEMP.DEPEND	STR	CHIP.NAME
+	CHIP.TEMP	METADATA
+		XY01	STR	DETTEM
+		XY02	STR	DETTEM
+		XY03	STR	DETTEM
+		XY04	STR	DETTEM
+		XY05	STR	DETTEM
+		XY06	STR	DETTEM
+		XY10	STR	DETTEM
+		XY11	STR	DETTEM
+		XY12	STR	DETTEM
+		XY13	STR	DETTEM
+		XY14	STR	DETTEM
+		XY15	STR	DETTEM
+		XY16	STR	DETTEM
+		XY17	STR	DETTEM
+		XY20	STR	DETTEM
+		XY21	STR	DETTEM
+		XY22	STR	DETTEM
+		XY23	STR	DETTEM
+#		XY24	STR	DETTEM	# This is currently bad --- get it from the database
+		XY25	STR	DETTEM
+		XY26	STR	DETTEM
+		XY27	STR	DETTEM
+		XY30	STR	DETTEM
+		XY31	STR	DETTEM
+		XY32	STR	DETTEM
+		XY33	STR	DETTEM
+		XY34	STR	DETTEM
+		XY35	STR	DETTEM
+		XY36	STR	DETTEM
+		XY37	STR	DETTEM
+		XY40	STR	DETTEM
+		XY41	STR	DETTEM
+		XY42	STR	DETTEM
+		XY43	STR	DETTEM
+		XY44	STR	DETTEM
+		XY45	STR	DETTEM
+		XY46	STR	DETTEM
+		XY47	STR	DETTEM
+		XY50	STR	DETTEM
+		XY51	STR	DETTEM
+		XY52	STR	DETTEM
+		XY53	STR	DETTEM
+		XY54	STR	DETTEM
+		XY55	STR	DETTEM
+		XY56	STR	DETTEM
+		XY57	STR	DETTEM
+		XY60	STR	DETTEM
+		XY61	STR	DETTEM
+		XY62	STR	DETTEM
+		XY63	STR	DETTEM
+		XY64	STR	DETTEM
+		XY65	STR	DETTEM
+		XY66	STR	DETTEM
+		XY67	STR	DETTEM
+		XY71	STR	DETTEM
+		XY72	STR	DETTEM
+		XY73	STR	DETTEM
+		XY74	STR	DETTEM
+		XY75	STR	DETTEM
+		XY76	STR	DETTEM
+	END
+
+        FPA.EXPOSURE    STR     EXPREQ          # Requested exposure time, presumably camera exposure time
+        CELL.EXPOSURE   STR     EXPTIME         # Exposure time measured by shutter
+        CELL.DARKTIME   STR     DARKTIME        # Exposure time for camera
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS        METADATA
+        FPA.TELESCOPE   STR     PS1
+        FPA.INSTRUMENT  STR     GPC1
+        FPA.DETECTOR    STR     GPC1
+        FPA.TIMESYS     STR     UTC
+        CHIP.XPARITY    S32     1
+        CHIP.YPARITY    S32     1
+        CHIP.X0.DEPEND          STR     CHIP.NAME
+        CHIP.X0         METADATA
+          XY01  S32     4971
+          XY02  S32     4971
+          XY03  S32     4971
+          XY04  S32     4971
+          XY05  S32     4971
+          XY06  S32     4971
+          XY10  S32     9942
+          XY11  S32     9942
+          XY12  S32     9942
+          XY13  S32     9942
+          XY14  S32     9942
+          XY15  S32     9942
+          XY16  S32     9942
+          XY17  S32     9942
+          XY20  S32     14913
+          XY21  S32     14913
+          XY22  S32     14913
+          XY23  S32     14913
+          XY24  S32     14913
+          XY25  S32     14913
+          XY26  S32     14913
+          XY27  S32     14913
+          XY30  S32     19884
+          XY31  S32     19884
+          XY32  S32     19884
+          XY33  S32     19884
+          XY34  S32     19884
+          XY35  S32     19884
+          XY36  S32     19884
+          XY37  S32     19884
+          XY40  S32     20041
+          XY41  S32     20041
+          XY42  S32     20041
+          XY43  S32     20041
+          XY44  S32     20041
+          XY45  S32     20041
+          XY46  S32     20041
+          XY47  S32     20041
+          XY50  S32     25012
+          XY51  S32     25012
+          XY52  S32     25012
+          XY53  S32     25012
+          XY54  S32     25012
+          XY55  S32     25012
+          XY56  S32     25012
+          XY57  S32     25012
+          XY60  S32     29983
+          XY61  S32     29983
+          XY62  S32     29983
+          XY63  S32     29983
+          XY64  S32     29983
+          XY65  S32     29983
+          XY66  S32     29983
+          XY67  S32     29983
+          XY71  S32     34954
+          XY72  S32     34954
+          XY73  S32     34954
+          XY74  S32     34954
+          XY75  S32     34954
+          XY76  S32     34954
+        END
+        CHIP.Y0.DEPEND          STR     CHIP.NAME
+        CHIP.Y0         METADATA
+          XY01  S32     10286
+          XY02  S32     15429
+          XY03  S32     20572
+          XY04  S32     25715
+          XY05  S32     30858
+          XY06  S32     36001
+          XY10  S32     5143
+          XY11  S32     10286
+          XY12  S32     15429
+          XY13  S32     20572
+          XY14  S32     25715
+          XY15  S32     30858
+          XY16  S32     36001
+          XY17  S32     41144
+          XY20  S32     5143
+          XY21  S32     10286
+          XY22  S32     15429
+          XY23  S32     20572
+          XY24  S32     25715
+          XY25  S32     30858
+          XY26  S32     36001
+          XY27  S32     41144
+          XY30  S32     5143
+          XY31  S32     10286
+          XY32  S32     15429
+          XY33  S32     20572
+          XY34  S32     25715
+          XY35  S32     30858
+          XY36  S32     36001
+          XY37  S32     41144
+          XY40  S32     306
+          XY41  S32     5449
+          XY42  S32     10592
+          XY43  S32     15735
+          XY44  S32     20878
+          XY45  S32     26021
+          XY46  S32     31164
+          XY47  S32     36307
+          XY50  S32     306
+          XY51  S32     5449
+          XY52  S32     10592
+          XY53  S32     15735
+          XY54  S32     20878
+          XY55  S32     26021
+          XY56  S32     31164
+          XY57  S32     36307
+          XY60  S32     306
+          XY61  S32     5449
+          XY62  S32     10592
+          XY63  S32     15735
+          XY64  S32     20878
+          XY65  S32     26021
+          XY66  S32     31164
+          XY67  S32     36307
+          XY71  S32     5449
+          XY72  S32     10592
+          XY73  S32     15735
+          XY74  S32     20878
+          XY75  S32     26021
+          XY76  S32     31164
+        END
+        CHIP.XPARITY.DEPEND     STR     CHIP.NAME
+        CHIP.XPARITY    METADATA
+          XY01  S32     -1
+          XY02  S32     -1
+          XY03  S32     -1
+          XY04  S32     -1
+          XY05  S32     -1
+          XY06  S32     -1
+          XY10  S32     -1
+          XY11  S32     -1
+          XY12  S32     -1
+          XY13  S32     -1
+          XY14  S32     -1
+          XY15  S32     -1
+          XY16  S32     -1
+          XY17  S32     -1
+          XY20  S32     -1
+          XY21  S32     -1
+          XY22  S32     -1
+          XY23  S32     -1
+          XY24  S32     -1
+          XY25  S32     -1
+          XY26  S32     -1
+          XY27  S32     -1
+          XY30  S32     -1
+          XY31  S32     -1
+          XY32  S32     -1
+          XY33  S32     -1
+          XY34  S32     -1
+          XY35  S32     -1
+          XY36  S32     -1
+          XY37  S32     -1
+          XY40  S32     1
+          XY41  S32     1
+          XY42  S32     1
+          XY43  S32     1
+          XY44  S32     1
+          XY45  S32     1
+          XY46  S32     1
+          XY47  S32     1
+          XY50  S32     1
+          XY51  S32     1
+          XY52  S32     1
+          XY53  S32     1
+          XY54  S32     1
+          XY55  S32     1
+          XY56  S32     1
+          XY57  S32     1
+          XY60  S32     1
+          XY61  S32     1
+          XY62  S32     1
+          XY63  S32     1
+          XY64  S32     1
+          XY65  S32     1
+          XY66  S32     1
+          XY67  S32     1
+          XY71  S32     1
+          XY72  S32     1
+          XY73  S32     1
+          XY74  S32     1
+          XY75  S32     1
+          XY76  S32     1
+        END
+        CHIP.YPARITY.DEPEND     STR     CHIP.NAME
+        CHIP.YPARITY    METADATA
+          XY01  S32     -1
+          XY02  S32     -1
+          XY03  S32     -1
+          XY04  S32     -1
+          XY05  S32     -1
+          XY06  S32     -1
+          XY10  S32     -1
+          XY11  S32     -1
+          XY12  S32     -1
+          XY13  S32     -1
+          XY14  S32     -1
+          XY15  S32     -1
+          XY16  S32     -1
+          XY17  S32     -1
+          XY20  S32     -1
+          XY21  S32     -1
+          XY22  S32     -1
+          XY23  S32     -1
+          XY24  S32     -1
+          XY25  S32     -1
+          XY26  S32     -1
+          XY27  S32     -1
+          XY30  S32     -1
+          XY31  S32     -1
+          XY32  S32     -1
+          XY33  S32     -1
+          XY34  S32     -1
+          XY35  S32     -1
+          XY36  S32     -1
+          XY37  S32     -1
+          XY40  S32     1
+          XY41  S32     1
+          XY42  S32     1
+          XY43  S32     1
+          XY44  S32     1
+          XY45  S32     1
+          XY46  S32     1
+          XY47  S32     1
+          XY50  S32     1
+          XY51  S32     1
+          XY52  S32     1
+          XY53  S32     1
+          XY54  S32     1
+          XY55  S32     1
+          XY56  S32     1
+          XY57  S32     1
+          XY60  S32     1
+          XY61  S32     1
+          XY62  S32     1
+          XY63  S32     1
+          XY64  S32     1
+          XY65  S32     1
+          XY66  S32     1
+          XY67  S32     1
+          XY71  S32     1
+          XY72  S32     1
+          XY73  S32     1
+          XY74  S32     1
+          XY75  S32     1
+          XY76  S32     1
+        END
+        CELL.GAIN       F32     1.0
+        CELL.READNOISE  F32     15.0
+        CELL.READDIR    S32     1
+        CELL.BAD        S32     -100
+#       CELL.TIME       STR     MJD-OBS
+#       CELL.TIMESYS    STR     TIMESYS
+        CELL.SATURATION F32     40000.0
+END
+
+# How to translation PS concepts into database lookups
+DATABASE        METADATA
+	CHIP.TEMP.DEPEND	STR	CHIP.NAME
+	CHIP.TEMP		METADATA
+		XY24	STR	SELECT AVG(ccd_temp) FROM rawImfile WHERE dateobs = '{FPA.TIME}' AND class_id != 'XY24'
+	END
+END
+
+
+# Where there might be some ambiguity, specify the format
+FORMATS         METADATA
+        FPA.RA          STR     DEGREES
+        FPA.DEC         STR     DEGREES
+        FPA.TIME        STR     MJD
+        CELL.TIME       STR     MJD
+        CELL.BINNING    STR     TOGETHER
+        CELL.X0         STR     FORTRAN
+        CELL.Y0         STR     FORTRAN
+END
+ 
+# Recipe options
+RECIPES         METADATA
+END
+ 
+# How to get the supplementary stuff: mask and weight
+SUPPLEMENTARY   METADATA
+        MASK.SOURCE     STR     FILE            # Source type for mask: EXT | FILE
+        MASK.NAME       STR     %a_mask.fits    # Name for mask extension or filename
+        WEIGHT.SOURCE   STR     FILE            # Source type for weight: EXT | FILE
+        WEIGHT.NAME     STR     %a_weight.fits  # Name for weight extension or filename
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/ppImage.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/ppImage.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/ppImage.config	(revision 22157)
@@ -0,0 +1,320 @@
+
+# binned output image options
+BIN1.XBIN               S32      16
+BIN1.YBIN               S32      16
+BIN2.XBIN               S32     150
+BIN2.YBIN               S32     150
+
+OVERSCAN.SINGLE         BOOL    FALSE            # Reduce overscan to a single value?
+OVERSCAN.STAT           STR     MEDIAN
+OVERSCAN.BOXCAR         S32     3
+
+PHOTCODE.RULE           STR     {DETECTOR}.{FILTER.ID}.{CHIP.NAME}
+OLDDARK                 BOOL    FALSE
+
+## XXX use these local variations until we are building real detrend images
+
+DETREND.CONSTRAINTS  METADATA
+  BIAS METADATA
+  END
+  DARK METADATA
+    EXPTIME STR FPA.EXPOSURE
+  END
+  FLAT METADATA
+    FILTER  STR FPA.FILTERID
+  END
+  FLAT_CORRECTION METADATA
+    FILTER   STR FPA.FILTER
+  END
+  FRINGE METADATA
+    FILTER   STR FPA.FILTER
+  END
+  SHUTTER METADATA
+  END
+  MASK METADATA
+  END	
+  ASTROM METADATA
+  END	
+END
+
+# Overscan, bias
+PPIMAGE_OB         METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark
+PPIMAGE_OBD        METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter
+PPIMAGE_OBDS       METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter
+PPIMAGE_OBDSF      METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_DET_ONLY   METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    TRUE            # Mask bad pixels
+  REPLACE.MASKED   BOOL    TRUE            # Fill in masked pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+  FPA1.FITS        BOOL    TRUE            # Save 1st binned fpa image? 
+  FPA2.FITS        BOOL    TRUE            # Save 2nd binned fpa image? 
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe
+PPIMAGE_OBDSFR     METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    TRUE            # Mask bad pixels
+  REPLACE.MASKED   BOOL    TRUE            # Fill in masked pixels
+  REPLACE.MODE     STR     MODEL           # VALUE, MODEL
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_OBDSFRP    METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    TRUE            # Mask bad pixels
+  REPLACE.MASKED   BOOL    TRUE            # Fill in masked pixels
+  REPLACE.MODE     STR     MODEL           # VALUE, MODEL
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM           BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_OBDSFRA    METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    TRUE            # Mask bad pixels
+  REPLACE.MASKED   BOOL    TRUE            # Fill in masked pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM           BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Photometry and astrometry only (for pre-reduced data)
+PPIMAGE_PA         METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+  BIN1.JPEG        BOOL    TRUE            # Save 1st binned jpeg?
+  BIN2.JPEG        BOOL    TRUE            # Save 2nd binned jpeg?
+  PHOTOM           BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# JPEG images for different types of residual images
+# Save JPEG from BIN1 for bias
+PPIMAGE_J1_RESID_B      METADATA
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BASE.FITS        BOOL    FALSE           # Save base image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  BIN1.FITS        BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS        BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG        BOOL    TRUE            # Save 1st binned jpeg?
+  BIN2.JPEG        BOOL    FALSE           # Save 2nd binned jpeg?
+  BIN1.XBIN        S32     1               # Image is already binned
+  BIN1.YBIN        S32     1               # Image is already binned
+  BIN2.XBIN        S32     1               # Image is already binned
+  BIN2.YBIN        S32     1               # Image is already binned
+
+  # GPC1 has significant dark structures: use a wide greyscale range
+  PPIMAGE.JPEG1    METADATA
+    COLORMAP       STR     -greyscale
+    SCALE.MODE     STR     VALUE
+    SCALE.MIN      F32     -50.0
+    SCALE.MAX      F32     +50.0
+  END
+END
+
+# Save JPEG from BIN2 for bias
+PPIMAGE_J2_RESID_B        METADATA
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BASE.FITS        BOOL    FALSE           # Save base image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  BIN1.FITS        BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS        BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG        BOOL    FALSE           # Save 1st binned jpeg?
+  BIN2.JPEG        BOOL    TRUE            # Save 2nd binned jpeg?
+  BIN1.XBIN        S32     1               # Image is already binned
+  BIN1.YBIN        S32     1               # Image is already binned
+  BIN2.XBIN        S32     1               # Image is already binned
+  BIN2.YBIN        S32     1               # Image is already binned
+
+  # GPC1 has significant dark structures: use a wide greyscale range
+  PPIMAGE.JPEG2    METADATA
+    COLORMAP       STR     -greyscale
+    SCALE.MODE     STR     VALUE
+    SCALE.MIN      F32     -50.0
+    SCALE.MAX      F32     +50.0
+  END
+END
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/ppMerge.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/ppMerge.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/ppMerge.config	(revision 22157)
@@ -0,0 +1,29 @@
+# Mask generation --- already included in default, above
+PPMERGE_DARKMASK METADATA
+  ITER           S32    3
+  MASK.SUSPECT	 F32	3.0		# Threshold for suspect pixels (sigma)
+  MASK.BAD	 F32	0.1		# Threshold for bad pixels 
+  MASK.MODE	 STR	FRACTION	# Meaning of threshold : value, sigma
+  SAMPLE         S32    100000          # Sampling factor for measuring the background
+END
+
+PPMERGE_FLATMASK METADATA
+  ITER           S32    3
+  MASK.SUSPECT	 F32	3.0		# Threshold for suspect pixels (sigma)
+  MASK.BAD	 F32	0.1		# Threshold for bad pixels (sigma)
+  MASK.MODE	 STR	FRACTION	# Threshold for bad pixels (sigma)
+  SAMPLE         S32    100000          # Sampling factor for measuring the background
+END
+
+ROWS		S32	32		# Number of rows to read at once
+
+# Ordinates for fitting dark current
+DARK.ORDINATES	METADATA
+	CELL.DARKTIME	S32	1
+	CHIP.TEMP	METADATA
+		ORDER	S32	1
+		SCALE	BOOL	TRUE
+		MIN	F32	-95
+		MAX	F32	-50
+	END
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/psastro.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/psastro.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/psastro.config	(revision 22157)
@@ -0,0 +1,93 @@
+
+# astrometry matching parameters
+
+# nominal plate scale (microns / pixel)
+PSASTRO.PIXEL.SCALE    F32  1.0
+
+# pmAstromGridAngle
+# max grid offset in FP units (microns)
+# use plate-scale to make this in pixels?
+PSASTRO.GRID.OFFSET    F32    1000.
+PSASTRO.GRID.SCALE     F32      50
+PSASTRO.GRID.NSTAR.MAX S32     800 # max stars accepted for fitting
+
+PSASTRO.MAX.NRAW      S32       200   # max stars accepted for fitting (0 for all)
+PSASTRO.MAX.NREF      S32      5000   # max stars accepted for fitting (0 for all)
+
+# extra field for ref stars:
+PSASTRO.FIELD.PADDING  F32     0.3   # pad by this fraction of the full mosaic (may need to be larger for single chips)
+
+# 
+PSASTRO.MIN.INST.MAG.RAW       F32     -17.0   # min instrumental magnitude for stars accepted for fitting
+PSASTRO.MAX.INST.MAG.RAW       F32     -10.0   # max instrumental magnitude for stars accepted for fitting
+
+#
+PSASTRO.GRID.MIN.ANGLE F32 -1.0 # start angle (degrees)
+PSASTRO.GRID.MAX.ANGLE F32 +1.0
+PSASTRO.GRID.DEL.ANGLE F32  0.5
+
+#
+PSASTRO.GRID.MIN.SCALE F32  1.00
+PSASTRO.GRID.MAX.SCALE F32  1.00
+# PSASTRO.GRID.MIN.SCALE F32  0.98
+# PSASTRO.GRID.MAX.SCALE F32  1.021
+PSASTRO.GRID.DEL.SCALE F32  0.01
+
+# single-chip radius match in pixels
+PSASTRO.MATCH.RADIUS.N0 F32    90
+PSASTRO.MATCH.RADIUS.N1 F32    60
+PSASTRO.MATCH.RADIUS.N2 F32    30
+PSASTRO.MATCH.RADIUS.N3 F32    20
+PSASTRO.MATCH.RADIUS.N4 F32    20
+PSASTRO.MATCH.RADIUS.N5 F32    20
+PSASTRO.MATCH.RADIUS.N6 F32    10
+PSASTRO.MATCH.RADIUS.N7 F32    10
+PSASTRO.MATCH.FIT.NITER S32     8
+
+# XXX Test for taurus
+PSASTRO.GRID.MIN.SIGMA  F32  1.0
+PSASTRO.IGNORE          STR  SATURATED,CRLIMIT,DEFECT,BLEND,FAIL
+
+# these tweak are in FP units (pixels, currently)
+PSASTRO.TWEAK.SCALE     F32      1
+PSASTRO.TWEAK.RANGE     F32    150
+PSASTRO.TWEAK.SMOOTH    F32      2
+PSASTRO.TWEAK.NSIGMA    F32      3
+
+PSASTRO.USE.MODEL             BOOL     TRUE
+
+# PSASTRO.FIX.CHIPS             BOOL     TRUE
+PSASTRO.PIXEL.TOLERANCE       F32      20.0
+PSASTRO.ANGLE.TOLERANCE       F32      1.0
+
+# Mosaic Astrometry options
+PSASTRO.MOSAIC.MODE         BOOL   FALSE
+# PSASTRO.MOSAIC.MAX.ERROR.N0 F32    1.80 # max allow error for valid solution (arcsec)
+# PSASTRO.MOSAIC.MAX.ERROR.N1 F32    1.20 # max allow error for valid solution (arcsec)
+# PSASTRO.MOSAIC.MAX.ERROR.N2 F32    0.80 # max allow error for valid solution (arcsec)
+# PSASTRO.MOSAIC.MAX.ERROR.N3 F32    0.50 # max allow error for valid solution (arcsec)
+
+PSASTRO.MOSAIC.MAX.ERROR.N0 F32    5.00 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N1 F32    5.00 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N2 F32    5.00 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N3 F32    5.00 # max allow error for valid solution (arcsec)
+
+# mosaic radius match in pixels
+PSASTRO.MOSAIC.RADIUS.N0    F32    10
+PSASTRO.MOSAIC.RADIUS.N1    F32    5
+PSASTRO.MOSAIC.RADIUS.N2    F32    3
+
+PSASTRO.MOSAIC.CHIP.ORDER     S32      1  # limit chip-fit order to 1
+PSASTRO.MOSAIC.CHIP.ORDER.N0  S32      0 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N1  S32      0 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N2  S32      1 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N3  S32      1 # fit order (-1 means use default)
+
+# we need to allow a loose fit if we are fitting to 2mass (50mas internal error -> 100mas limit?)
+# if we are fitting against quality digital data, we can require tighter constraints
+
+PSASTRO.MODEL.REF.CHIP        STR      XY33
+
+PSASTRO.CATDIR              STR      2MASS
+DVO.GETSTAR.PHOTCODE        STR      2MASS_J
+DVO.GETSTAR.MAG.MAX         F32      21.0
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/psphot.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/psphot.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/psphot.config	(revision 22157)
@@ -0,0 +1,51 @@
+
+# turn these on to see specific outputs
+SAVE.OUTPUT	BOOL 	TRUE
+SAVE.BACKMDL	BOOL 	TRUE
+#SAVE.RESID	BOOL 	TRUE
+#SAVE.BACKGND	BOOL 	TRUE
+#SAVE.BACKSUB	BOOL 	TRUE
+#SAVE.PSF	BOOL 	TRUE
+#LOAD.PSF	BOOL 	FALSE
+#SAVE.PLOTS     	BOOL    TRUE
+
+BACKGROUND.XBIN	    S32  256            # size of background superpixels
+BACKGROUND.YBIN	    S32  256            # size of background superpixels
+
+# image background parameters
+IMSTATS_NPIX        S32  10000    	 # number of pixels to use for sky estimate boxes:
+SKY_STAT            STR  FITTED_MEAN_V4  # statistic used to measure background
+SKY_CLIP_SIGMA      F32  2.0             # statistic used to measure background
+
+PEAKS_SMOOTH_SIGMA  F32   2.5            # smoothing kernel sigma in pixels
+PEAKS_NMAX          S32   5000           # on first pass, only keep NMAX peaks (0 == all)
+PEAKS_NSIGMA_LIMIT  F32   25.0            # peak significance threshold
+PEAKS_NSIGMA_LIMIT_2 F32  10.0             # peak significance threshold
+
+PSF_SN_LIM          F32  25              # minimum S/N for stars used for PSF model
+PSF_MAX_NSTARS      S32  300             # limit number of stars used for PSF model
+
+# PSF model parameters : choose the PSF model desired
+PSF_MODEL           STR  PS_MODEL_QGAUSS
+
+MOMENTS_SN_MIN      F32   30.0
+EXT_MIN_SN           F32  50.0           # fit galaxies above this S/N limit
+FULL_FIT_SN_LIM      F32  50.0
+AP_MIN_SN            F32  50.0
+
+OUTPUT.FORMAT       STR PS1_DEV_1
+
+PSF_SHAPE_NSIGMA     F32  3.0		 # max significance for shape variation
+PSF_MAX_CHI          F32  50.0		 # reject objects worse that this
+APTREND.ORDER.MAX    S32  1
+MEASURE.APTREND      BOOL  FALSE  ### XXX for now, skip this (too many errors)
+
+# BREAK_POINT STR ENSEMBLE
+
+DIAGNOSTIC.PLOTS                    METADATA
+  IMAGE.BACKGROUND.CELL.HISTOGRAM   BOOL  FALSE
+  IMAGE.BACKGROUND.CELL.HISTOGRAM.X S32   32
+  IMAGE.BACKGROUND.CELL.HISTOGRAM.Y S32   15
+END
+
+USE_FOOTPRINTS                      BOOL  TRUE       	  # use new pmFootprint peak packaging
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/pswarp.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/pswarp.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/gpc1/pswarp.config	(revision 22157)
@@ -0,0 +1,1 @@
+ASTROM.SOURCE		STR	PSASTRO.OUTPUT.MEF	# Source file rule for astrometry, or NULL
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/ipprc.config.in
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/ipprc.config.in	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/ipprc.config.in	(revision 22157)
@@ -0,0 +1,24 @@
+## Example .ipprc file : copy to your home directory and modify as desired
+
+# Default search path for configuration files (add $HOME if desired)
+# Note: do not include $HOME in the distributed copy used by ippMonitor
+# PATH            STR     @pkgdatadir@:$HOME/.ipp:.
+PATH              STR     @pkgdatadir@:.
+
+# load the site-specific information from here
+SITE              STR     site.config
+
+# load the system configuration information from here
+SYSTEM            STR     system.config
+
+# logging verbosity levels
+LOGLEVEL	S32	9			# Logging level; 3=INFO
+LOGFORMAT	STR	THLNM			# Log format
+LOGDEST		STR	STDERR			# Log destination
+TRACEDEST	STR	STDERR			# Trace destination
+
+# place default trace lines here
+TRACE		METADATA			# Trace levels
+  err		S32	10
+# psLib.db      S32	10
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/isp/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/isp/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/isp/.cvsignore	(revision 22157)
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/isp/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/isp/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/isp/Makefile.am	(revision 22157)
@@ -0,0 +1,24 @@
+
+installdir = $(datadir)/ippconfig/isp
+
+install_files = \
+	dvo.config \
+	dvo.layout \
+	camera.config \
+	format.config \
+	cmp.config \
+	cmf.config \
+	ppImage.config \
+	ppMerge.config \
+	psastro.config \
+	psphot.config \
+	rejections.config
+
+install_DATA = $(install_files)
+
+install-data-hook:
+	chmod 0755 $(installdir)
+
+EXTRA_DIST = $(install_files)
+
+ACLOCAL_AMFLAGS = -I m4
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/isp/camera.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/isp/camera.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/isp/camera.config	(revision 22157)
@@ -0,0 +1,74 @@
+# Camera configuration file for the Pan-STARRS Imaging Sky Probe
+
+# File formats that we know about
+FORMATS         METADATA
+        SIMPLE  STR     isp/format.config
+        CMP     STR     isp/cmp.config
+        CMF     STR     isp/cmf.config
+END
+ 
+# Description of camera --- all the chips and the cells that comprise them
+FPA     METADATA
+        Chip            STR     Cell
+END
+
+# valid filter names and corresponding IDs
+FILTER.ID       METADATA
+        g       STR     g
+        r       STR     r
+        i       STR     i
+        z       STR     z
+        y       STR     y
+END
+
+# Table of strings to use for the class identifier (see ippdb) according to the file level.
+CLASSID		METADATA
+	FPA	STR	fpa
+END
+
+DVO.CAMERADIR	STR	isp		# Camera directory for DVO
+
+# convert supplied FPA.OBSTYPE values to abstract exptype names
+OBSTYPE.TABLE METADATA
+  bias 	   STR BIAS
+  zero 	   STR BIAS
+  dark 	   STR DARK
+  flat 	   STR SKYFLAT
+  skyflat  STR SKYFLAT
+  domeflat STR DOMEFLAT
+  object   STR OBJECT
+  science  STR OBJECT
+END
+
+# Recipe options
+RECIPES         METADATA
+        PSPHOT          STR     isp/psphot.config               # psphot details
+        PSASTRO         STR     isp/psastro.config              # psastro details
+        PPIMAGE         STR     isp/ppImage.config              # Recipes for ppImage
+        PPMERGE         STR     isp/ppMerge.config              # Recipes for ppMerge
+	REJECTIONS	STR     isp/rejections.config
+END
+
+# reduction classes (recipes which are grouped together)
+REDUCTION	STR	recipes/reductionClasses.mdc
+
+FITSTYPES       STR     recipes/fitstypes.mdc
+
+FILERULES	STR	recipes/filerules-simple.mdc
+
+EXTNAME.RULES METADATA
+  CMF.HEAD STR cmf.hdr
+  CMF.DATA STR cmf.psf # use .PSF and .EXT?
+  CMF.XSRC STR {CHIP.NAME}.xsrc # use .PSF and .EXT?
+  CMF.XFIT STR {CHIP.NAME}.xfit # use .PSF and .EXT?
+
+  PSF.HEAD  STR	hdr
+  PSF.TABLE STR psf_model
+  PSF.RESID STR psf_resid
+END
+
+BLANK.HEADERS	METADATA
+	FPA.TIME	STR	MJD-OBS
+	FPA.EXPOSURE	STR	EXPTIME
+	FPA.AIRMASS	STR	AIRMASS
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/isp/cmf.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/isp/cmf.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/isp/cmf.config	(revision 22157)
@@ -0,0 +1,84 @@
+# Pan-STARRS Imaging Sky Probe
+
+# How to identify this type
+RULE	METADATA
+	SIMPLE		BOOL	TRUE
+	NAXIS		S32	0
+	TELESCOP	STR	ISP-1 
+	INSTRUME	STR	ISP-Apogee
+	DETECTOR	STR	ISP-Apogee-01
+	ISPCAMER	STR	Apogee U42
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	NONE	# There are no extensions
+	FPA.NAME	STR	SEQID	# A PHU keyword for unique identifier within the hierarchy level
+END
+
+# What's in the FITS file?
+CONTENTS	STR	Chip:Cell:amplifier
+
+# Specify the cell data
+CELLS	METADATA
+	amplifier	METADATA
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC		STR	TRIMSEC
+		CELL.BIASSEC		STR	BIASSEC
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.OBSTYPE	STR	OBSTYPE
+	FPA.OBJECT	STR	OBSTYPE
+	FPA.FILTER	STR	FILTNAME
+	FPA.RA		STR	RA
+	FPA.DEC		STR	DEC
+	FPA.RADECSYS	STR	RADECSYS
+	FPA.ALT		STR	ALT
+	FPA.AZ		STR	AZ
+	FPA.POSANGLE	STR	ROTANGLE
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.TIME	STR	MJD-OBS
+	CHIP.TEMP	STR	CCDTEMP
+	CELL.EXPOSURE	STR	EXPTIME
+	CELL.DARKTIME	STR	DARKTIME
+	CELL.TIME	STR	MJD-OBS
+	CELL.GAIN	STR	GAIN
+	CELL.READNOISE	STR	RDNOISE
+	CELL.XBIN	STR	XBIN
+	CELL.YBIN	STR	YBIN
+#	CELL.SATURATION	STR	SATURATE	### Currently set to 0 ???
+	CELL.BAD	STR	BADLEVEL
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	FPA.TIMESYS	STR	UTC
+	CELL.SATURATION	F32	65535
+	CELL.READDIR	S32	1
+	CELL.TIMESYS	STR	UTC
+	CHIP.XPARITY	S32	1
+	CHIP.YPARITY	S32	1
+	CHIP.X0		S32	0
+	CHIP.Y0		S32	0
+	CELL.XPARITY	S32	1
+	CELL.YPARITY	S32	1
+	CELL.X0		S32	0
+	CELL.Y0		S32	0
+END
+
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
+
+# PS Concepts to get from the database
+DATABASE	METADATA
+# None.
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/isp/cmp.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/isp/cmp.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/isp/cmp.config	(revision 22157)
@@ -0,0 +1,84 @@
+# Pan-STARRS Imaging Sky Probe
+
+# How to identify this type
+RULE	METADATA
+	SIMPLE		BOOL	FALSE
+	NAXIS		S32	0
+	TELESCOP	STR	ISP-1 
+	INSTRUME	STR	ISP-Apogee
+	DETECTOR	STR	ISP-Apogee-01
+	ISPCAMER	STR	Apogee U42
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	NONE	# There are no extensions
+	FPA.NAME	STR	SEQID	# A PHU keyword for unique identifier within the hierarchy level
+END
+
+# What's in the FITS file?
+CONTENTS	STR	Chip:Cell:amplifier
+
+# Specify the cell data
+CELLS	METADATA
+	amplifier	METADATA
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC		STR	TRIMSEC
+		CELL.BIASSEC		STR	BIASSEC
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.OBSTYPE	STR	OBSTYPE
+	FPA.OBJECT	STR	OBSTYPE
+	FPA.FILTER	STR	FILTNAME
+	FPA.RA		STR	RA
+	FPA.DEC		STR	DEC
+	FPA.RADECSYS	STR	RADECSYS
+	FPA.ALT		STR	ALT
+	FPA.AZ		STR	AZ
+	FPA.POSANGLE	STR	ROTANGLE
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.TIME	STR	MJD-OBS
+	CHIP.TEMP	STR	CCDTEMP
+	CELL.EXPOSURE	STR	EXPTIME
+	CELL.DARKTIME	STR	DARKTIME
+	CELL.TIME	STR	MJD-OBS
+	CELL.GAIN	STR	GAIN
+	CELL.READNOISE	STR	RDNOISE
+	CELL.XBIN	STR	XBIN
+	CELL.YBIN	STR	YBIN
+#	CELL.SATURATION	STR	SATURATE	### Currently set to 0 ???
+	CELL.BAD	STR	BADLEVEL
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	FPA.TIMESYS	STR	UTC
+	CELL.SATURATION	F32	65535
+	CELL.READDIR	S32	1
+	CELL.TIMESYS	STR	UTC
+	CHIP.XPARITY	S32	1
+	CHIP.YPARITY	S32	1
+	CHIP.X0		S32	0
+	CHIP.Y0		S32	0
+	CELL.XPARITY	S32	1
+	CELL.YPARITY	S32	1
+	CELL.X0		S32	0
+	CELL.Y0		S32	0
+END
+
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
+
+# PS Concepts to get from the database
+DATABASE	METADATA
+# None.
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/isp/dvo.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/isp/dvo.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/isp/dvo.config	(revision 22157)
@@ -0,0 +1,50 @@
+
+# location of DVO database tables
+CATDIR			/data/alala/eugene/isp/catdir
+
+# keywords used by DVO to interpret the headers
+
+# used by parse_time to find time-related keywords
+MJD-KEYWORD		MJD-OBS
+DATE-KEYWORD		NONE
+DATE-MODE		NONE
+UT-KEYWORD		NONE
+JD-KEYWORD		NONE
+
+# other keyword abstractions
+EXPTIME-KEYWORD		EXPTIME
+AIRMASS-KEYWORD		AIRMASS
+CCDNUM-KEYWORD		EXTNAME
+ST-KEYWORD		NONE
+OBSERVATORY-LATITUDE	NONE
+OBSERVATORY-LONGITUDE	NONE
+SUBPIX_DATAFILE		NONE
+
+# instrumental magnitude range for calibration mode 
+CAL_INSTMAG_MAX		-7.0
+CAL_INSTMAG_MIN		-9.5
+
+# exclude overscan region from the dB image boundaries
+XOVERSCAN		0
+YOVERSCAN		0
+
+# only upload stars within region; a value of 0 means ignore the limit
+ADDSTAR_XMIN		0
+ADDSTAR_XMAX		0
+ADDSTAR_YMIN		0
+ADDSTAR_YMAX		0
+
+# exclude stars with SN > SNLIMIT (ADDSTAR_SNLIMIT overrides old name MIN_SN_FSTAT)
+ADDSTAR_SNLIMIT		0
+
+# allowed astrometry error (arcseconds)
+ADDSTAR_MAX_CERROR	5.0
+
+# correlation radius (arcseconds)
+ADDSTAR_RADIUS		5.0
+
+# scaled correlation radius 
+ADDSTAR_NSIGMA		0
+
+CATMODE			MEF
+CATFORMAT		PANSTARRS_DEV_0
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/isp/dvo.layout
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/isp/dvo.layout	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/isp/dvo.layout	(revision 22157)
@@ -0,0 +1,58 @@
+# this file defines the layout of the mosaic imager:
+
+# NCCD 40  # this needs work: sometimes 36, sometimes 40
+NCCD 36
+NAXIS1 2112
+NAXIS2 4644
+
+MOSAIC_X 11
+MOSAIC_Y  4
+
+# lines need to contain:
+
+CHIPID_KEYWORD EXTNAME
+
+# ID     CHIPID xoffset yoffset xflip yflip datasec         biassec            Xo      Yo      theta
+CCD.0     ccd00       1       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.1     ccd01       2       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.2     ccd02       3       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.3     ccd03       4       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.4     ccd04       5       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.5     ccd05       6       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.6     ccd06       7       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.7     ccd07       8       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.8     ccd08       9       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.9     ccd09       1       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.10    ccd10       2       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.11    ccd11       3       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.12    ccd12       4       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.13    ccd13       5       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.14    ccd14       6       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.15    ccd15       7       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.16    ccd16       8       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.17    ccd17       9       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.18    ccd18       1       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.19    ccd19       2       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.20    ccd20       3       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.21    ccd21       4       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.22    ccd22       5       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.23    ccd23       6       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.24    ccd24       7       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.25    ccd25       8       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.26    ccd26       9       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.27    ccd27       1       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.28    ccd28       2       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.29    ccd29       3       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.30    ccd30       4       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.31    ccd31       5       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.32    ccd32       6       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.33    ccd33       7       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.34    ccd34       8       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.35    ccd35       9       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.36    ccd36       0       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.37    ccd37      10       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.38    ccd38       0       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.39    ccd39      10       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+
+# can we use header to find biassec
+USE_BIASSEC 1
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/isp/format.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/isp/format.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/isp/format.config	(revision 22157)
@@ -0,0 +1,101 @@
+# Pan-STARRS Imaging Sky Probe
+
+# How to identify this type
+RULE	METADATA
+	SIMPLE		BOOL	TRUE
+	NAXIS		S32	2
+	TELESCOP	STR	ISP-1 
+	INSTRUME	STR	ISP-Apogee
+	DETECTOR	STR	ISP-Apogee-01
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	NONE	# There are no extensions
+	FPA.OBS	STR	SEQID	# A PHU keyword for unique identifier within the hierarchy level
+END
+
+# What's in the FITS file?
+CONTENTS	STR	Chip:Cell:amplifier
+
+# Specify the cell data
+CELLS	METADATA
+	amplifier	METADATA
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC		STR	TRIMSEC
+		CELL.BIASSEC		STR	BIASSEC
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.TELESCOPE	STR	TELESCOP
+	FPA.INSTRUMENT	STR	INSTRUME
+	FPA.DETECTOR	STR	DETECTOR
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.OBSTYPE	STR	OBSTYPE
+	FPA.OBJECT	STR	OBSTYPE
+	FPA.FILTERID	STR	FILTER
+	FPA.FILTER	STR	FILTER
+	FPA.POSANGLE	STR	ROTANGLE
+	FPA.RA		STR	RA
+	FPA.DEC		STR	DEC
+	FPA.RADECSYS	STR	RADECSYS
+#	FPA.FOCUS	STR	TELFOCUS
+	FPA.TIME	STR	MJD-OBS
+	FPA.TIMESYS	STR	TIMESYS
+	FPA.ALT		STR	ALT
+	FPA.AZ		STR	AZ
+	FPA.TEMP	STR	CCDTEMP
+	FPA.EXPOSURE	STR	EXPTIME
+	CHIP.TEMP	STR	CCDTEMP
+	CELL.EXPOSURE	STR	EXPTIME
+	CELL.DARKTIME	STR	DARKTIME
+	CELL.TIME	STR	MJD-OBS
+	CELL.GAIN	STR	GAIN
+	CELL.READNOISE	STR	RDNOISE
+#	CELL.SATURATION	STR	SATURATE	### Currently set to 0 ???
+#	CELL.TIMESYS	STR	TIMESYS
+	CELL.XBIN	STR	CCDBIN1
+	CELL.YBIN	STR	CCDBIN2
+# these were used for some early data
+#	CELL.XBIN	STR	XBIN
+#	CELL.YBIN	STR	YBIN
+	CELL.BAD	STR	BADLEVEL
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	CHIP.XSIZE	S32	2048
+	CHIP.YSIZE	S32	2048
+	CELL.XSIZE	S32	2048
+	CELL.YSIZE	S32	2048
+	FPA.TIMESYS	STR	UTC
+	CELL.SATURATION	F32	60000
+	CELL.READDIR	S32	1
+	CELL.TIMESYS	STR	UTC
+	CHIP.XPARITY	S32	1
+	CHIP.YPARITY	S32	1
+	CHIP.X0		S32	0
+	CHIP.Y0		S32	0
+	CELL.XPARITY	S32	1
+	CELL.YPARITY	S32	1
+	CELL.X0		S32	0
+	CELL.Y0		S32	0
+#	CELL.XBIN	S32	1
+#	CELL.YBIN	S32	1
+END
+
+FORMATS		METADATA
+	FPA.RA		STR	DEGREES
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
+
+# PS Concepts to get from the database
+DATABASE	METADATA
+# None.
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/isp/ppImage.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/isp/ppImage.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/isp/ppImage.config	(revision 22157)
@@ -0,0 +1,120 @@
+### ppImage recipe configuration file
+
+# Inherit everything from the top-level recipe file, except for what's below.
+
+PHOTCODE.RULE		STR	{DETECTOR}.{FILTER.ID}
+
+FRINGE.FILTERS  MULTI
+FRINGE.FILTERS	STR z
+FRINGE.FILTERS	STR y
+
+# apply the following constraints when selecting a detrend image
+DETREND.CONSTRAINTS  METADATA
+  BIAS METADATA
+  END
+  DARK METADATA
+  END
+  SHUTTER METADATA
+  END	
+  FLAT METADATA
+    FILTER  STR FPA.FILTERID
+#   VERSION STR RAW
+  END
+  FRINGE METADATA
+    FILTER   STR FPA.FILTERID
+#   AIRMASS  STR FPA.AIRMASS
+#   TWILIGHT STR TWILIGHT:FPA.TIME
+  END
+END
+
+## these science-image analysis recipes are placed here set MASK = FALSE
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_DET_ONLY  METADATA
+  BASE.FITS       BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL   FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL   FALSE           # Save base detrended image?
+  CHIP.FITS       BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL   TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL   TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom
+PPIMAGE_OBDSFRP   METADATA
+  BASE.FITS       BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL   FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL   FALSE           # Save base detrended image?
+  CHIP.FITS       BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL   TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL   TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM          BOOL    TRUE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_OBDSFRA   METADATA
+  BASE.FITS       BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL   FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL   FALSE           # Save base detrended image?
+  CHIP.FITS       BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL   TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL   TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM          BOOL    TRUE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
+
+# Overscan, bias, photometry, astrometry
+PPIMAGE_OA   METADATA
+  BASE.FITS       BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL   FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL   FALSE           # Save base detrended image?
+  CHIP.FITS       BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL   FALSE           # Save base detrended image?
+  CHIP.WEIGHT.FITS BOOL   FALSE           # Save base detrended image?
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS       BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS       BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG       BOOL	  TRUE 		# Save 1st binned jpeg?
+  BIN2.JPEG       BOOL	  TRUE 		# Save 2nd binned jpeg?
+  PHOTOM          BOOL    TRUE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/isp/ppMerge.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/isp/ppMerge.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/isp/ppMerge.config	(revision 22157)
@@ -0,0 +1,3 @@
+### ppImage recipe configuration file
+
+# Inherit everything from the top-level recipe file, except for what's below.
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/isp/psastro.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/isp/psastro.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/isp/psastro.config	(revision 22157)
@@ -0,0 +1,38 @@
+
+# astrometry matching parameters
+
+# nominal plate scale (microns / pixel)
+PSASTRO.PIXEL.SCALE    F32  10.0
+
+# pmAstromGridMatch:
+PSASTRO.GRID.MIN.ANGLE F32 -2.0
+PSASTRO.GRID.MAX.ANGLE F32 +2.0
+PSASTRO.GRID.DEL.ANGLE F32  0.25
+PSASTRO.GRID.MIN.SIGMA F32  5.0
+
+# pmAstromGridAngle
+# max grid offset in FP units (microns)
+# use plate-scale to make this in pixels?
+PSASTRO.GRID.OFFSET    F32   10000.
+PSASTRO.GRID.SCALE     F32     500
+
+# these tweak are in FP units (pixels, currently)
+PSASTRO.TWEAK.SCALE     F32      1
+PSASTRO.TWEAK.RANGE     F32     75
+PSASTRO.TWEAK.SMOOTH    F32      2
+PSASTRO.TWEAK.NSIGMA    F32      3
+
+# match radius in pixels for CHIP astrometry
+PSASTRO.MATCH.RADIUS   F32    8
+
+# pmAstromMatchFit
+PSASTRO.CHIP.ORDER     S32      3  # fit order
+PSASTRO.CHIP.NITER     S32      3  # fit clipping iterations
+PSASTRO.CHIP.NSIGMA    F32      3  # fit clipping sigmas
+
+PSASTRO.MAX.ERROR      F32      10.0 # max error in pixels
+PSASTRO.MIN.NSTAR      S32      10   # min fitted stars in solution
+
+MAG_MAX                F32      10
+PSASTRO.CATDIR         STR      SYNTH.BRIGHT
+PSASTRO.MATCH.LUMFUNC  BOOL     TRUE
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/isp/psphot.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/isp/psphot.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/isp/psphot.config	(revision 22157)
@@ -0,0 +1,76 @@
+
+# turn these on to see specific outputs
+SAVE.BACKMDL	BOOL 	TRUE
+SAVE.BACKGND	BOOL 	TRUE
+SAVE.BACKSUB	BOOL 	TRUE
+SAVE.RESID	BOOL 	TRUE
+SAVE.PSF	BOOL 	TRUE
+SAVE.PLOTS      BOOL    TRUE
+
+# image statistics parameters
+IMSTATS_NPIX        S32  5000    	 # number of pixels to use for sky estimate boxes:
+SKY_STAT            STR  FITTED_MEAN_V4  # statistic used to measure background
+
+PSF_SN_LIM          F32  30              # minimum S/N for stars used for PSF model
+PSF_MAX_NSTARS      S32  300             # limit number of stars used for PSF model
+
+PSF_MODEL           STR  PS_MODEL_PGAUSS
+
+MOMENTS_SN_MIN      F32   30.0
+MOMENTS_AR_MAX      F32   2.0		 # maximum axial ratio: 1 / AR < (sx / sy) < AR
+
+#EXT_MIN_SN           F32  50.0           # fit galaxies above this S/N limit
+#FULL_FIT_SN_LIM      F32  50.0
+#AP_MIN_SN            F32  20.0
+PSF_CLUMP_NSIGMA   F32  3.5             # region of Sx,Sy plane to use for selecting PSF stars
+
+# PSFTREND must be a 2D polynomial
+# the specified values are ignored but define the active components of the polynomial
+PSF.TREND.MASK  METADATA  
+   NORDER_X         S32       3                # number of x orders
+   NORDER_Y         S32       3                # number of y orders
+   VAL_X00_Y00      F64       1                # polynomial coefficient
+
+   VAL_X01_Y00      F64       1                # polynomial coefficient
+   VAL_X00_Y01      F64       1                # polynomial coefficient
+
+   VAL_X02_Y00      F64       1                # polynomial coefficient
+   VAL_X01_Y01      F64       1                # polynomial coefficient
+   VAL_X00_Y02      F64       1                # polynomial coefficient
+
+   VAL_X03_Y00      F64       1                # polynomial coefficient
+   VAL_X02_Y01      F64       1                # polynomial coefficient
+   VAL_X01_Y02      F64       1                # polynomial coefficient
+   VAL_X00_Y03      F64       1                # polynomial coefficient
+   NELEMENTS        S32       10               # number of unmasked components
+END  # folder for 4D polynomial
+
+PSF.TREND.NX S32 4
+PSF.TREND.NY S32 4
+
+XMIN F32 15
+
+PSF.RESIDUALS       BOOL true
+PSF.RESIDUALS.SPATIAL_ORDER S32 1
+
+BREAK_POINT         STR  ENSEMBLE
+OUTPUT.FORMAT       STR  PS1_DEV_0
+
+PSPHOT.SUMMIT METADATA
+ PEAKS_SMOOTH_SIGMA  F32  0.8 	   	 # peak significance threshold
+ PEAKS_NSIGMA_LIMIT  F32  50.0 	   	 # peak significance threshold
+ PEAKS_NMAX          S32  1000
+ SAVE.RESID	BOOL 	FALSE
+ PSF_SN_LIM          F32  25              # minimum S/N for stars used for PSF model
+ MOMENTS_SN_MIN      F32   25.0
+ PSF_MODEL         STR  PS_MODEL_PGAUSS
+END
+
+DIAGNOSTIC.PLOTS		METADATA
+  IMAGE.BACKGROUND.CELL.HISTOGRAM   BOOL FALSE
+  IMAGE.BACKGROUND.CELL.HISTOGRAM.X S32  4
+  IMAGE.BACKGROUND.CELL.HISTOGRAM.Y S32  7
+END
+
+SKY_BIAS F32 0.5
+AP_MIN_SN            F32  30.0
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/isp/rejections.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/isp/rejections.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/isp/rejections.config	(revision 22157)
@@ -0,0 +1,49 @@
+
+DARK METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  0.0
+  IMFILE.STDEV       F32  0.0
+  IMFILE.MEANSTDEV   F32  0.0
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.0
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  2.0
+  EXP.MEAN           F32  0.0
+  EXP.STDEV          F32  0.0
+  EXP.MEANSTDEV      F32  0.0
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  2.0
+  ENSEMBLE.MEAN      F32  3.0
+  ENSEMBLE.STDEV     F32  3.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+FRINGE METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  0.0
+  IMFILE.STDEV       F32  0.0
+  IMFILE.MEANSTDEV   F32  0.0
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.0
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  0.0
+  EXP.STDEV          F32  0.0
+  EXP.MEANSTDEV      F32  0.0
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32 10.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  0.0
+  ENSEMBLE.STDEV     F32  0.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/.cvsignore	(revision 22157)
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/Makefile.am	(revision 22157)
@@ -0,0 +1,22 @@
+
+installdir = $(datadir)/ippconfig/lbc_red
+
+install_files = \
+	dvo.config \
+	dvo.layout \
+	camera.config \
+	format.config \
+	ppImage.config \
+	ppMerge.config \
+	psastro.config \
+	psphot.config \
+	rejections.config
+
+install_DATA = $(install_files)
+
+install-data-hook:
+	chmod 0755 $(installdir)
+
+EXTRA_DIST = $(install_files)
+
+ACLOCAL_AMFLAGS = -I m4
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/camera.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/camera.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/camera.config	(revision 22157)
@@ -0,0 +1,77 @@
+# Camera configuration file for LBCred: describes the camera
+
+# File formats that we know about
+FORMATS         METADATA
+        MEF 	STR     lbc_red/format.config
+END
+
+# Description of camera --- all the chips and the cells that comprise them
+FPA     METADATA
+	CHIP1	STR	Cell
+	CHIP2	STR	Cell
+	CHIP3	STR	Cell
+	CHIP4	STR	Cell
+END
+
+# move this elsewhere?  we need a lookup table to go from filter ID to abstract name
+FILTER.ID       METADATA
+   Y	  STR	Y-FAN
+END
+
+# Table of strings to use for the class identifier (see ippdb) according to the file level.
+CLASSID         METADATA
+        FPA     STR     fpa
+        CHIP    STR     {CHIP.NAME}
+        CELL    STR     {CHIP.NAME}:{CELL.NAME}
+END
+
+DVO.CAMERADIR	STR	isp		# Camera directory for DVO
+
+# convert supplied FPA.OBSTYPE values to abstract exptype names
+OBSTYPE.TABLE METADATA
+  BinoBias STR BIAS
+  bias     STR BIAS
+  zero     STR BIAS
+  dark     STR DARK
+  flat     STR SKYFLAT
+  SkyFlat  STR SKYFLAT
+  domeflat STR DOMEFLAT
+  object   STR OBJECT
+  science  STR OBJECT
+END
+
+# Recipe options
+RECIPES         METADATA
+        # Other recipes
+        PSPHOT          STR     lbc_red/psphot.config           # psphot details
+        PSASTRO         STR     lbc_red/psastro.config          # psastro details
+        PPIMAGE         STR     lbc_red/ppImage.config          # ppImage recipe
+	REJECTIONS      STR     lbc_red/rejections.config       # rejection recipe
+END
+
+# reduction classes (recipes which are grouped together)
+REDUCTION	STR	recipes/reductionClasses.mdc
+
+FITSTYPES       STR     recipes/fitstypes.mdc
+
+FILERULES	STR	lbc_red/filerules.mdc	# File rules
+
+EXTNAME.RULES METADATA
+  CMF.HEAD   STR {CHIP.NAME}.hdr
+  CMF.DATA   STR {CHIP.NAME}.psf # use .PSF and .EXT?
+  CMF.XSRC STR {CHIP.NAME}.xsrc # use .PSF and .EXT?
+  CMF.XFIT STR {CHIP.NAME}.xfit # use .PSF and .EXT?
+
+  PSF.HEAD   STR {CHIP.NAME}.hdr
+  PSF.TABLE  STR {CHIP.NAME}.psf_model
+  PSF.RESID  STR {CHIP.NAME}.psf_resid
+
+  REF.ASTROM STR {CHIP.NAME}.ref_astrom
+END
+
+BLANK.HEADERS	METADATA
+	FPA.TIME	STR	MJD-OBS
+	FPA.EXPOSURE	STR	EXPTIME
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.NAME	STR	EXPNUM
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/dvo.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/dvo.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/dvo.config	(revision 22157)
@@ -0,0 +1,50 @@
+
+# location of DVO database tables
+CATDIR			/data/ipp001.0/jester/stripe82/coadd/catdir
+
+# keywords used by DVO to interpret the headers
+
+# used by parse_time to find time-related keywords
+MJD-KEYWORD		NONE
+DATE-KEYWORD		DATE-OBS
+DATE-MODE		YYYY-MM-DD
+UT-KEYWORD		TAIHMS
+JD-KEYWORD		NONE
+
+# other keyword abstractions
+EXPTIME-KEYWORD		EXPTIME
+AIRMASS-KEYWORD		AIRMASS
+CCDNUM-KEYWORD		EXTNAME
+ST-KEYWORD		NONE
+OBSERVATORY-LATITUDE	NONE
+OBSERVATORY-LONGITUDE	NONE
+SUBPIX_DATAFILE		NONE
+
+# instrumental magnitude range for calibration mode 
+CAL_INSTMAG_MAX		-7.0
+CAL_INSTMAG_MIN		-9.5
+
+# exclude overscan region from the dB image boundaries
+XOVERSCAN		0
+YOVERSCAN		0
+
+# only upload stars within region; a value of 0 means ignore the limit
+ADDSTAR_XMIN		0
+ADDSTAR_XMAX		0
+ADDSTAR_YMIN		0
+ADDSTAR_YMAX		0
+
+# exclude stars with SN > SNLIMIT (ADDSTAR_SNLIMIT overrides old name MIN_SN_FSTAT)
+ADDSTAR_SNLIMIT		0
+
+# allowed astrometry error (arcseconds)
+ADDSTAR_MAX_CERROR	5.0
+
+# correlation radius (arcseconds)
+ADDSTAR_RADIUS		5.0
+
+# scaled correlation radius 
+ADDSTAR_NSIGMA		0
+
+CATMODE			MEF
+CATFORMAT		PANSTARRS_DEV_0
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/dvo.layout
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/dvo.layout	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/dvo.layout	(revision 22157)
@@ -0,0 +1,58 @@
+# this file defines the layout of the mosaic imager:
+
+# NCCD 40  # this needs work: sometimes 36, sometimes 40
+NCCD 36
+NAXIS1 2112
+NAXIS2 4644
+
+MOSAIC_X 11
+MOSAIC_Y  4
+
+# lines need to contain:
+
+CHIPID_KEYWORD EXTNAME
+
+# ID     CHIPID xoffset yoffset xflip yflip datasec         biassec            Xo      Yo      theta
+CCD.0     ccd00       1       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.1     ccd01       2       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.2     ccd02       3       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.3     ccd03       4       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.4     ccd04       5       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.5     ccd05       6       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.6     ccd06       7       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.7     ccd07       8       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.8     ccd08       9       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.9     ccd09       1       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.10    ccd10       2       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.11    ccd11       3       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.12    ccd12       4       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.13    ccd13       5       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.14    ccd14       6       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.15    ccd15       7       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.16    ccd16       8       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.17    ccd17       9       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.18    ccd18       1       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.19    ccd19       2       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.20    ccd20       3       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.21    ccd21       4       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.22    ccd22       5       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.23    ccd23       6       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.24    ccd24       7       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.25    ccd25       8       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.26    ccd26       9       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.27    ccd27       1       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.28    ccd28       2       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.29    ccd29       3       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.30    ccd30       4       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.31    ccd31       5       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.32    ccd32       6       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.33    ccd33       7       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.34    ccd34       8       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.35    ccd35       9       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.36    ccd36       0       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.37    ccd37      10       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.38    ccd38       0       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.39    ccd39      10       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+
+# can we use header to find biassec
+USE_BIASSEC 1
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/filerules.mdc
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/filerules.mdc	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/filerules.mdc	(revision 22157)
@@ -0,0 +1,165 @@
+### File rules for PHU=FPA, EXT=CHIP
+
+### Redirections
+PPIMAGE.OUTPUT        STR PPIMAGE.OUTPUT.MEF
+PPIMAGE.OUTPUT.MASK   STR PPIMAGE.OUT.MK.MEF
+PPIMAGE.OUTPUT.WEIGHT STR PPIMAGE.OUT.WT.MEF
+PPIMAGE.CHIP          STR PPIMAGE.CHIP.MEF
+PPIMAGE.CHIP.MASK     STR PPIMAGE.CHIP.MK.MEF
+PPIMAGE.CHIP.WEIGHT   STR PPIMAGE.CHIP.WT.MEF
+PPIMAGE.OUTPUT.FPA1   STR PPIMAGE.OUTPUT.FPA1.MEF
+PPIMAGE.OUTPUT.FPA2   STR PPIMAGE.OUTPUT.FPA2.MEF
+PPIMAGE.BIN1          STR PPIMAGE.BIN1.MEF
+PPIMAGE.BIN2          STR PPIMAGE.BIN2.MEF
+
+PPSTAMP.CHIP          STR PPSTAMP.CHIP.MEF
+
+PSASTRO.INPUT         STR PSASTRO.INPUT.CMF
+PSASTRO.OUTPUT        STR PSASTRO.OUT.CMF.MEF
+PSASTRO.OUTPUT.MEF    STR PSASTRO.OUT.CMF.MEF
+PSPHOT.OUTPUT         STR PSPHOT.OUT.CMF.MEF
+DVOCORR.OUTPUT        STR DVOCORR.MEF.OUTPUT
+DVOFLAT.OUTPUT        STR DVOFLAT.MEF.OUTPUT
+
+### input file definitions
+### use @DETDB entries to get the detrend images from the database
+### replace @DETDB with @FILES if you want to require it from the 
+### command line, or with an explicit name to require a specific file
+TYPE               INPUT FILENAME.RULE DATA.LEVEL FILE.TYPE
+
+## files used by ppImage
+PPIMAGE.INPUT      INPUT @FILES        CHIP       IMAGE
+PPIMAGE.MASK       INPUT @DETDB        CHIP       MASK
+PPIMAGE.BIAS       INPUT @DETDB        CHIP       IMAGE
+PPIMAGE.DARK       INPUT @DETDB        CHIP       IMAGE
+PPIMAGE.FLAT       INPUT @DETDB        CHIP       IMAGE
+PPIMAGE.FRINGE     INPUT @DETDB        CHIP       FRINGE
+PPIMAGE.SHUTTER    INPUT @DETDB        CHIP       IMAGE     
+
+## files used to build and apply the flat-field correction images
+DVOCORR.INPUT      INPUT @FILES        CHIP       IMAGE
+DVOCORR.REFHEAD    INPUT @FILES        CHIP       HEADER
+DVOFLAT.INPUT      INPUT @FILES        CHIP       IMAGE
+DVOFLAT.CORR       INPUT @DETDB        CHIP       IMAGE
+
+## files used by psphot 
+PSPHOT.LOAD        INPUT @FILES        CHIP       IMAGE
+PSPHOT.INPUT       INPUT @FILES        CHIP       IMAGE
+PSPHOT.MASK        INPUT @FILES        CHIP       MASK     
+PSPHOT.WEIGHT      INPUT @FILES        CHIP       WEIGHT     
+PSPHOT.PSF.LOAD    INPUT @FILES        CHIP       PSF       
+
+## files used by psastro 
+PSASTRO.INPUT.CMP  INPUT @FILES        CHIP       CMP
+PSASTRO.INPUT.CMF  INPUT @FILES        CHIP       CMF
+
+## files used by pswarp
+PSWARP.INPUT       INPUT @FILES        CHIP       IMAGE
+PSWARP.WEIGHT      INPUT @FILES        CHIP       WEIGHT
+PSWARP.MASK        INPUT @FILES        CHIP       MASK
+PSWARP.SKYCELL     INPUT @FILES        CHIP       IMAGE
+PSWARP.ASTROM      INPUT @FILES        CHIP       CMF
+
+PPSUB.INPUT        INPUT @FILES        FPA        IMAGE
+PPSUB.INPUT.MASK   INPUT @FILES        FPA        MASK
+PPSUB.INPUT.WEIGHT INPUT @FILES        FPA        WEIGHT
+PPSUB.REF          INPUT @FILES        FPA        IMAGE
+PPSUB.REF.MASK     INPUT @FILES        FPA        MASK
+PPSUB.REF.WEIGHT   INPUT @FILES        FPA        WEIGHT
+PPSUB.SOURCES      INPUT @FILES        FPA        CMF
+
+PPSTACK.INPUT      INPUT @FILES        FPA        IMAGE
+PPSTACK.INPUT.MASK INPUT @FILES        FPA        MASK
+PPSTACK.INPUT.WEIGHT INPUT @FILES      FPA        WEIGHT
+PPSTACK.SOURCES    INPUT @FILES        FPA        CMF
+
+PPSTAMP.INPUT      INPUT @FILES        CHIP       IMAGE
+
+PPARITH.INPUT.IMAGE INPUT @FILES        CHIP       IMAGE
+PPARITH.INPUT.MASK  INPUT @FILES        CHIP       MASK
+
+### output file definitions
+TYPE                  OUTPUT FILENAME.RULE                    FILE.TYPE FITS.TYPE DATA.LEVEL FILE.SAVE FILE.FORMAT
+PPIMAGE.OUTPUT.MEF    OUTPUT {OUTPUT}.b0.fits                 IMAGE     NONE      CHIP       TRUE      MEF
+PPIMAGE.OUT.MK.MEF    OUTPUT {OUTPUT}.mk.fits                 MASK      NONE      CHIP       TRUE      MEF
+PPIMAGE.OUT.WT.MEF    OUTPUT {OUTPUT}.wt.fits                 WEIGHT    NONE      CHIP       TRUE      MEF
+PPIMAGE.OUTPUT.SPL    OUTPUT {OUTPUT}.{CHIP.NAME}.b0.fits     IMAGE     NONE      CHIP       TRUE      SPLIT
+PPIMAGE.OUT.MK.SPL    OUTPUT {OUTPUT}.{CHIP.NAME}.mk.fits     MASK      NONE      CHIP       TRUE      SPLIT
+PPIMAGE.OUT.WT.SPL    OUTPUT {OUTPUT}.{CHIP.NAME}.wt.fits     WEIGHT    NONE      CHIP       TRUE      SPLIT
+
+PPIMAGE.CHIP.MEF      OUTPUT {OUTPUT}.ch.fits                 IMAGE     NONE      CHIP       TRUE      MEF
+PPIMAGE.CHIP.MK.MEF   OUTPUT {OUTPUT}.ch.mk.fits              MASK      NONE      CHIP       TRUE      MEF
+PPIMAGE.CHIP.WT.MEF   OUTPUT {OUTPUT}.ch.wt.fits              WEIGHT    NONE      CHIP       TRUE      MEF
+PPIMAGE.CHIP.SPL      OUTPUT {OUTPUT}.{CHIP.NAME}.ch.fits     IMAGE     NONE      CHIP       TRUE      SPLIT
+PPIMAGE.CHIP.MK.SPL   OUTPUT {OUTPUT}.{CHIP.NAME}.ch.mk.fits  MASK      NONE      CHIP       TRUE      SPLIT
+PPIMAGE.CHIP.WT.SPL   OUTPUT {OUTPUT}.{CHIP.NAME}.ch.wt.fits  WEIGHT    NONE      CHIP       TRUE      SPLIT
+
+PPIMAGE.OUTPUT.FPA1.MEF OUTPUT {OUTPUT}.b1.fits               IMAGE     NONE      FPA        TRUE      MEF
+PPIMAGE.OUTPUT.FPA2.MEF OUTPUT {OUTPUT}.b2.fits               IMAGE     NONE      FPA        TRUE      MEF
+PPIMAGE.OUTPUT.FPA1.SPL OUTPUT {OUTPUT}.{CHIP.NAME}.b1.fits   IMAGE     NONE      FPA        TRUE      SPLIT
+PPIMAGE.OUTPUT.FPA2.SPL OUTPUT {OUTPUT}.{CHIP.NAME}.b2.fits   IMAGE     NONE      FPA        TRUE      SPLIT
+
+PPIMAGE.STATS         OUTPUT {OUTPUT}.stats                   STATS     NONE      FPA        TRUE      MEF
+
+PPIMAGE.BIN1.MEF      OUTPUT {OUTPUT}.b1c.fits                IMAGE     NONE      CHIP       TRUE      MEF
+PPIMAGE.BIN2.MEF      OUTPUT {OUTPUT}.b2c.fits                IMAGE     NONE      CHIP       TRUE      MEF
+PPIMAGE.BIN1.SPL      OUTPUT {OUTPUT}.{CHIP.NAME}.b1.fits     IMAGE     NONE      CHIP       TRUE      SPLIT
+PPIMAGE.BIN2.SPL      OUTPUT {OUTPUT}.{CHIP.NAME}.b2.fits     IMAGE     NONE      CHIP       TRUE      SPLIT
+
+PPIMAGE.JPEG1         OUTPUT {OUTPUT}.b1.jpg                  JPEG      NONE      FPA        TRUE      NONE
+PPIMAGE.JPEG2         OUTPUT {OUTPUT}.b2.jpg                  JPEG      NONE      FPA        TRUE      NONE
+
+DVOCORR.MEF.OUTPUT    OUTPUT {OUTPUT}.fc.fits                 IMAGE     NONE      CHIP       TRUE      MEF
+DVOCORR.SPL.OUTPUT    OUTPUT {OUTPUT}.{CHIP.NAME}.fc.fits     IMAGE     NONE      CHIP       TRUE      SPLIT
+DVOFLAT.MEF.OUTPUT    OUTPUT {OUTPUT}.co.fits                 IMAGE     NONE      CHIP       TRUE      MEF
+DVOFLAT.SPL.OUTPUT    OUTPUT {OUTPUT}.{CHIP.NAME}.co.fits     IMAGE     NONE      CHIP       TRUE      SPLIT
+
+PSPHOT.RESID          OUTPUT {OUTPUT}.res.fits                IMAGE     NONE      CHIP       FALSE     NONE
+PSPHOT.BACKGND        OUTPUT {OUTPUT}.bck.fits                IMAGE     NONE      CHIP       FALSE     NONE
+PSPHOT.BACKSUB        OUTPUT {OUTPUT}.sub.fits                IMAGE     NONE      CHIP       FALSE     NONE
+PSPHOT.BACKMDL        OUTPUT {OUTPUT}.mdl.fits                IMAGE     NONE      CHIP       FALSE     NONE
+
+PSPHOT.OUTPUT.RAW     OUTPUT {OUTPUT}.{CHIP.NAME}             RAW       NONE      CHIP       TRUE      NONE
+PSPHOT.OUTPUT.SX      OUTPUT {OUTPUT}.sx                      SX        NONE      FPA        TRUE      NONE
+PSPHOT.OUTPUT.OBJ     OUTPUT {OUTPUT}.obj                     OBJ       NONE      FPA        TRUE      NONE
+PSPHOT.OUTPUT.CMP     OUTPUT {OUTPUT}.{CHIP.NAME}.cmp         CMP       NONE      CHIP       TRUE      NONE
+PSPHOT.OUT.CMF.SPL    OUTPUT {OUTPUT}.{CHIP.NAME}.cmf         CMF       NONE      CHIP       TRUE      SPLIT
+PSPHOT.OUT.CMF.MEF    OUTPUT {OUTPUT}.cmf                     CMF       NONE      CHIP       TRUE      NONE
+
+PSPHOT.PSF.SAVE       OUTPUT {OUTPUT}.psf                     PSF       NONE      CHIP       TRUE      NONE
+
+SOURCE.PLOT.MOMENTS   OUTPUT {OUTPUT}.mnt.png                 KAPA      NONE      FPA        TRUE      NONE
+SOURCE.PLOT.PSFMODEL  OUTPUT {OUTPUT}.psf.png                 KAPA      NONE      FPA        TRUE      NONE
+SOURCE.PLOT.APRESID   OUTPUT {OUTPUT}.dap.png                 KAPA      NONE      FPA        TRUE      NONE
+
+PSASTRO.OUTPUT.CMP    OUTPUT {OUTPUT}.{CHIP.NAME}.smp         CMP       NONE      CHIP       TRUE      NONE
+PSASTRO.OUT.CMF.SPL   OUTPUT {OUTPUT}.{CHIP.NAME}.smf         CMF       NONE      CHIP       TRUE      NONE
+PSASTRO.OUT.CMF.MEF   OUTPUT {OUTPUT}.smf                     CMF       NONE      FPA        TRUE      NONE
+PSASTRO.OUT.ASTROM    OUTPUT {OUTPUT}.ast                     ASTROM    NONE      FPA        TRUE      NONE
+
+PSWARP.OUTPUT         OUTPUT {OUTPUT}.fits                    IMAGE     NONE      FPA        TRUE      NONE
+PSWARP.OUTPUT.MASK    OUTPUT {OUTPUT}.mk.fits                 MASK      NONE      FPA        TRUE      NONE
+PSWARP.OUTPUT.WEIGHT  OUTPUT {OUTPUT}.wt.fits                 WEIGHT    NONE      FPA        TRUE      NONE
+PSWARP.OUTPUT.SOURCES OUTPUT {OUTPUT}.cmf                     CMF       NONE      FPA        TRUE      NONE
+PSWARP.BIN1           OUTPUT {OUTPUT}.b1.fits                 IMAGE     NONE      FPA        TRUE      NONE
+PSWARP.BIN2           OUTPUT {OUTPUT}.b2.fits                 IMAGE     NONE      FPA        TRUE      NONE
+
+PPSUB.OUTPUT          OUTPUT {OUTPUT}.fits                    IMAGE     NONE      FPA        TRUE      NONE
+PPSUB.OUTPUT.MASK     OUTPUT {OUTPUT}.mk.fits                 MASK      NONE      FPA        TRUE      NONE
+PPSUB.OUTPUT.WEIGHT   OUTPUT {OUTPUT}.wt.fits                 WEIGHT    NONE      FPA        TRUE      NONE
+
+PPSTACK.OUTPUT        OUTPUT {OUTPUT}.fits                    IMAGE     NONE      FPA        TRUE      NONE
+PPSTACK.OUTPUT.MASK   OUTPUT {OUTPUT}.mk.fits                 MASK      NONE      FPA        TRUE      NONE
+PPSTACK.OUTPUT.WEIGHT OUTPUT {OUTPUT}.wt.fits                 WEIGHT    NONE      FPA        TRUE      NONE
+
+PPSTAMP.OUTPUT        OUTPUT {OUTPUT}.fits                    IMAGE     NONE      FPA        TRUE      NONE
+PPSTAMP.CHIP.MEF      OUTPUT {OUTPUT}.ch.fits                 IMAGE     NONE      CHIP       FALSE     MEF
+
+PPSIM.OUTPUT          OUTPUT {OUTPUT}.{CHIP.NAME}.fits        IMAGE     NONE      CHIP       TRUE      SPLIT
+
+PPARITH.OUTPUT.IMAGE  OUTPUT {OUTPUT}.fits                    IMAGE     NONE      CHIP       TRUE      NONE
+PPARITH.OUTPUT.MASK   OUTPUT {OUTPUT}.fits                    MASK      NONE      CHIP       TRUE      NONE
+
+LOG.IMFILE            OUTPUT {OUTPUT}.{CHIP.NAME}.log         TEXT      NONE      CHIP       TRUE      NONE
+LOG.EXP               OUTPUT {OUTPUT}.log                     TEXT      NONE      FPA        TRUE      NONE
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/format.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/format.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/format.config	(revision 22157)
@@ -0,0 +1,154 @@
+# LBCred format.config, copied from MegaCam format_mef.config
+# Like Megacam, LBC has an FPA in a multi-extension fits file, with one chip per extension
+
+
+# How to recognise this type
+RULE	METADATA
+	TELESCOP	STR	LBT-DX
+	INSTRUME	STR	LBC-RED
+	NAXIS           S32     0
+	EXTEND		BOOL	T
+END
+
+FILE	METADATA
+	# How to read this data
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	CHIP	# The extensions represent chips
+	FPA.OBS	STR	OBS_ID	# A PHU keyword for unique identifier
+	CHIP.NAME	STR	EXTNAME	# An extension keyword for unique identifie
+END
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# Extension name, chipName:chipType
+	LBCCHIP1	STR	CHIP1:Chip
+	LBCCHIP2	STR	CHIP2:Chip
+	LBCCHIP3	STR	CHIP3:Chip
+	LBCCHIP4	STR	CHIP4:Chip
+END
+
+# Specify the chips
+CHIPS		METADATA
+	# Chip type, cellName:cellType
+	Chip	STR	Cell:Cell
+END
+
+
+# Specify the cells
+CELLS		METADATA
+	# Cell type, concepts to use
+	Cell	METADATA
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BIASSEC
+		CELL.TRIMSEC		STR	TRIMSEC
+	# Not sure about these...
+		CELL.X0			S32	0
+		CELL.Y0			S32	0
+		CELL.GAIN.SOURCE	STR	HEADER
+		CELL.GAIN		STR	GAIN
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+        FPA.AIRMASS     STR     AIRMASS
+        FPA.FILTERID    STR     FILTER
+        FPA.FILTER      STR     FILTER
+        FPA.POSANGLE    STR     ROTANGLE
+        FPA.RA          STR     OBSRA
+        FPA.DEC         STR     OBSDEC
+        FPA.RADECSYS    STR     RADECSYS
+	FPA.OBSTYPE	STR	IMAGETYPE
+	FPA.OBJECT	STR	OBJECT
+	FPA.FOCUS	STR	Z4 # ???
+	FPA.TIME	STR	MJD_OBS
+#	FPA.TIMESYS	STR	TIMESYS
+	FPA.ALT		STR	TELALT
+	FPA.AZ		STR	TELAZ
+	FPA.TEMP	STR	CCDTEM
+	FPA.EXPOSURE	STR	EXPTIME
+	CHIP.TEMP	STR	DETTEM
+        CELL.EXPOSURE   STR     EXPTIME
+        CELL.DARKTIME   STR     EXPTIME
+        CELL.READNOISE  STR     RDNOISE
+	CELL.TIME	STR	MJD_OBS
+#	CELL.TIMESYS	STR	TIMESYS
+        CELL.XBIN       STR     LBCBIN
+        CELL.YBIN       STR     LBCBIN
+	CELL.SATURATION	STR	SATURATE
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS        METADATA
+	FPA.TELESCOPE		STR	LBT-DX
+	FPA.INSTRUMENT		STR	LBC-RED		# this value is used in ipprc.config to find the recipes
+	FPA.DETECTOR	        STR	LBC-RED
+	CELL.TIMESYS		STR	UTC
+	FPA.TIMESYS		STR	UTC
+	CHIP.XSIZE		S32	2048
+	CHIP.YSIZE		S32	4608
+	CELL.XSIZE		S32	2048
+	CELL.YSIZE		S32	4608
+	CELL.XWINDOW		S32	0
+	CELL.YWINDOW		S32	0
+	CELL.READDIR		S32	1		# Cell is read in x direction
+        CELL.SATURATION		F32	65536.0
+        CELL.BAD                F32     0
+	CELL.XPARITY		S32	1
+	CELL.YPARITY		S32	1
+	CHIP.X0.DEPEND		STR	CHIP.NAME
+	# Do the x0, y0 positions need to account for the gaps??? 
+	# How do I specify rotation?
+	# 
+	# LBC layout is
+	#
+	#   444444444
+	#   444444444
+	#   444444444
+	#
+	#  333 222 111
+	#  333 222 111
+	#  333 222 111
+	#  333 222 111
+	#  333 222 111
+	#  333 222 111
+	# 
+	CHIP.X0		METADATA
+		CHIP1	S32	4096
+		CHIP2	S32	2048
+		CHIP3	S32	0
+	# This is probably wrong!
+		CHIP4	S32	1024
+	END
+	CHIP.Y0.DEPEND		STR	CHIP.NAME
+	CHIP.Y0		METADATA
+		CHIP1	S32	0
+		CHIP2	S32	0
+		CHIP3	S32	0
+		CHIP4	S32	4608
+	END
+	CHIP.XPARITY	S32	1
+	CHIP.YPARITY	S32	1
+END
+
+
+# How to translation PS concepts into database lookups
+DATABASE	METADATA
+	TYPE		dbEntry		TABLE		COLUMN		GIVENDBCOL	GIVENPS
+#	CELL.GAIN	dbEntry		Camera		gain		chipId,cellId	CHIP.NAME,CELL.NAME
+#	CELL.READNOISE	dbEntry		Camera		readNoise	chipId,cellId	CHIP.NAME,CELL.NAME
+
+# A database entry refers to a particular column (COLUMN) in a
+# particular table (TABLE), given certain PS concepts (GIVENPS) that
+# match certain database columns (GIVENDBCOL).
+END		
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
+ 
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/ppImage.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/ppImage.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/ppImage.config	(revision 22157)
@@ -0,0 +1,173 @@
+### ppImage recipe configuration file for Megacam
+
+# Overscan subtraction
+OVERSCAN.SINGLE		BOOL	FALSE		# Reduce overscan to a single value?
+OVERSCAN.FIT		STR	NONE		# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER		S32	0		# Order of polynomial fit
+OVERSCAN.STAT		STR	MEAN		# MEAN | MEDIAN
+
+# for a test, turn off selected detrend types
+OVERSCAN	BOOL	FALSE		# Overscan subtraction
+BIAS		BOOL	TRUE		# Bias subtraction
+DARK		BOOL	FALSE		# Dark subtraction
+FLAT		BOOL	TRUE		# Flat-field normalisation
+
+# binned output image options
+BIN1.XBIN               S32     18
+BIN1.YBIN               S32     18
+BIN2.XBIN               S32     144
+BIN2.YBIN               S32     144
+
+# LBCred  needs fringe subtraction (?)
+FRINGE		BOOL	TRUE		# Fringe subtraction
+
+# this table lists extra constraints which should be applied when
+# selecting the detrend images.
+# note: camera and time are always applied
+
+DETREND.CONSTRAINTS  METADATA
+  BIAS METADATA
+  END
+  DARK METADATA
+#   EXPTIME STR FPA.EXPOSURE
+  END
+  FLAT METADATA
+    FILTER  STR FPA.FILTERID
+#   DETTYPE STR DOMEFLAT.RAW
+#   VERSION STR SKYFLAT.RAW
+#   VERSION STR DOMEFLAT.CORR.XX
+#   VERSION STR SKYFLAT.CORR
+  END
+  FLAT_CORRECTION METADATA
+    FILTER   STR FPA.FILTER
+  END
+  FRINGE METADATA
+    FILTER   STR FPA.FILTER
+#   AIRMASS  STR FPA.AIRMASS
+#   TWILIGHT STR FPA.TWILIGHT
+  END
+  SHUTTER METADATA
+  END
+  MASK METADATA
+  END	
+END
+
+# only apply the fringe for these filters
+FRINGE.FILTERS  MULTI
+FRINGE.FILTERS	STR i
+FRINGE.FILTERS	STR z
+FRINGE.FILTERS	STR y
+
+PHOTCODE.RULE		STR	{DETECTOR}.{FILTER.ID}.{CHIP.N}
+
+
+
+# Overscan, bias, dark, shutter
+PPIMAGE_OBDS       METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field
+PPIMAGE_OBDSF      METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe
+PPIMAGE_OBDSFR     METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    TRUE            # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom
+PPIMAGE_OBDSFRP    METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    TRUE            # Mask bad pixels
+  FRINGE           BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM           BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_OBDSFRA    METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    TRUE            # Mask bad pixels
+  FRINGE           BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM           BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END 
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/ppMerge.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/ppMerge.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/ppMerge.config	(revision 22157)
@@ -0,0 +1,43 @@
+
+# Bias combination --- don't want min/max rejection
+PPMERGE_BIAS	METADATA
+   REJ		F32	3.0		# Rejection threshold (sigma)
+   ITER		S32	2		# Number of rejection iterations
+   FRACHIGH	F32	0.0		# Fraction of high pixels to reject immediately
+   FRACLOW	F32	0.0		# Fraction of low pixels to reject immediately
+   WEIGHTS	BOOL	FALSE		# Use image weights?
+   COMBINE	STR	CLIPPED		# Statistic to use for combination: 
+END
+
+# Dark combination --- don't want min/max rejection
+# More aggressive clipping than bias, so as to remove CRs
+PPMERGE_DARK	METADATA
+  REJ		F32	2.0		# Rejection threshold (sigma)
+  ITER		S32	4		# Number of rejection iterations
+  FRACHIGH	F32	0.0		# Fraction of high pixels to reject immediately
+  FRACLOW	F32	0.0		# Fraction of low pixels to reject immediately
+  WEIGHTS	BOOL	FALSE		# Use image weights?
+  COMBINE	STR	CLIPPED		# Statistic to use for combination: 
+END
+
+# Flat combination --- use min/max rejection
+PPMERGE_FLAT	METADATA
+	REJ		F32	3.0		# Rejection threshold (sigma)
+	ITER		S32	1		# Number of rejection iterations
+	FRACHIGH	F32	0.3		# Fraction of high pixels to reject immediately
+	FRACLOW		F32	0.1		# Fraction of low pixels to reject immediately
+	NKEEP		S32	5		# Minimum number of pixels in stack to keep
+	WEIGHTS		BOOL	TRUE		# Use image weights?
+	COMBINE		STR	MEAN		# Statistic to use for combination: 
+END
+
+
+# Fringe combination --- already included in default, above
+PPMERGE_FRINGE	METADATA
+	FRACHIGH	F32	0.1		# Fraction of high pixels to reject immediately
+	WEIGHTS		BOOL	TRUE		# Use image weights?
+END
+
+# Mask generation --- already included in default, above
+PPMERGE_MASK	METADATA
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/psastro.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/psastro.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/psastro.config	(revision 22157)
@@ -0,0 +1,67 @@
+
+# astrometry matching parameters
+
+# nominal plate scale (microns / pixel)
+PSASTRO.PIXEL.SCALE    F32  1.0
+
+# pmAstromGridAngle
+# max grid offset in FP units (microns)
+# use plate-scale to make this in pixels?
+PSASTRO.GRID.OFFSET    F32    1000.
+PSASTRO.GRID.SCALE     F32      50.
+
+# these tweak are in FP units (pixels, currently)
+PSASTRO.TWEAK.SCALE     F32      1
+PSASTRO.TWEAK.RANGE     F32     75
+PSASTRO.TWEAK.SMOOTH    F32      2
+PSASTRO.TWEAK.NSIGMA    F32      3
+
+PSASTRO.MATCH.RADIUS   F32     12.0
+
+PSASTRO.MAX.NRAW       S32      1500   # max stars accepted for fitting (0 for all)
+PSASTRO.MAX.NREF       S32      1500   # max stars accepted for fitting (0 for all)
+
+PSASTRO.MIN.INST.MAG.RAW       F32      -14.5  # min instrumental magnitude for stars accepted for fitting
+PSASTRO.MAX.INST.MAG.RAW       F32       -8.0  # max instrumental magnitude for stars accepted for fitting
+
+PSASTRO.MAX.ERROR      F32      10.0 # max error in pixels
+PSASTRO.MIN.NSTAR      S32      10   # min fitted stars in solution
+
+PSASTRO.MOSAIC.MAX.ERROR.N0 F32    1.50 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N1 F32    1.50 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N2 F32    0.90 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N3 F32    0.90 # max allow error for valid solution (arcsec)
+
+PSASTRO.MOSAIC.RADIUS.N0    F32    12.0
+PSASTRO.MOSAIC.RADIUS.N1    F32    0.0 # do not refine the match
+PSASTRO.MOSAIC.RADIUS.N2    F32    0.0 # do not refine the match
+PSASTRO.MOSAIC.RADIUS.N3    F32    6.0 
+
+PSASTRO.MOSAIC.CHIP.ORDER.N0  S32      0 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N1  S32      0 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N2  S32      1 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N3  S32      1 # fit order (-1 means use default)
+
+PSASTRO.MOSAIC.GRADIENT.NX    S32      2   # number of x-dir cells per chip
+PSASTRO.MOSAIC.GRADIENT.NY    S32      4   # number of y-dir cells per chip
+
+# use this recipe to set a tight constraint
+PSASTRO.FINE METADATA
+  PSASTRO.MOSAIC.MAX.ERROR.N0 F32    1.50 # max allow error for valid solution (arcsec)
+  PSASTRO.MOSAIC.MAX.ERROR.N1 F32    1.50 # max allow error for valid solution (arcsec)
+  PSASTRO.MOSAIC.MAX.ERROR.N2 F32    0.90 # max allow error for valid solution (arcsec)
+  PSASTRO.MOSAIC.MAX.ERROR.N3 F32    0.90 # max allow error for valid solution (arcsec)
+
+  PSASTRO.MOSAIC.RADIUS.N0    F32    8.0
+  PSASTRO.MOSAIC.RADIUS.N1    F32    0.0
+  PSASTRO.MOSAIC.RADIUS.N2    F32    4.0
+  PSASTRO.MOSAIC.RADIUS.N3    F32    2.0
+
+  PSASTRO.MOSAIC.CHIP.ORDER.N0  S32      0 # fit order (-1 means use default)
+  PSASTRO.MOSAIC.CHIP.ORDER.N1  S32      1 # fit order (-1 means use default)
+  PSASTRO.MOSAIC.CHIP.ORDER.N2  S32      1 # fit order (-1 means use default)
+  PSASTRO.MOSAIC.CHIP.ORDER.N3  S32      3 # fit order (-1 means use default)
+END
+
+# XXX Test for taurus
+# PSASTRO.GRID.MIN.SIGMA F32  3.0
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/psphot.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/psphot.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/psphot.config	(revision 22157)
@@ -0,0 +1,42 @@
+
+# turn these on to see specific outputs
+SAVE.OUTPUT	BOOL 	TRUE
+SAVE.BACKMDL	BOOL 	TRUE
+SAVE.PSF	BOOL 	TRUE
+SAVE.PLOTS     	BOOL    TRUE
+
+BACKGROUND.XBIN	    S32  128            # size of background superpixels
+BACKGROUND.YBIN	    S32  128            # size of background superpixels
+
+# image background parameters
+IMSTATS_NPIX        S32  10000    	 # number of pixels to use for sky estimate boxes:
+SKY_STAT            STR  FITTED_MEAN_V4  # statistic used to measure background
+SKY_CLIP_SIGMA      F32  2.0             # statistic used to measure background
+
+PSF_SN_LIM          F32  100             # minimum S/N for stars used for PSF model
+PSF_MAX_NSTARS      S32  300             # limit number of stars used for PSF model
+
+# PSF model parameters : choose the PSF model desired
+PSF_MODEL           STR  PS_MODEL_QGAUSS
+
+MOMENTS_SN_MIN      F32   30.0
+EXT_MIN_SN           F32  50.0           # fit galaxies above this S/N limit
+FULL_FIT_SN_LIM      F32  50.0
+AP_MIN_SN            F32  50.0
+
+# OUTPUT.FORMAT       STR SMPDATA
+OUTPUT.FORMAT       STR PS1_DEV_1
+
+PSF_SHAPE_NSIGMA     F32  3.0		 # max significance for shape variation
+PSF_MAX_CHI          F32  50.0		 # reject objects worse that this
+
+#PSF.TREND.MODE STR MAP
+#PSF.TREND.NX   S32 1
+#PSF.TREND.NY   S32 1
+
+DIAGNOSTIC.PLOTS		METADATA
+  IMAGE.BACKGROUND.CELL.HISTOGRAM   BOOL FALSE
+  IMAGE.BACKGROUND.CELL.HISTOGRAM.X S32 -1
+  IMAGE.BACKGROUND.CELL.HISTOGRAM.Y S32 12
+END
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/rejections.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/rejections.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lbc_red/rejections.config	(revision 22157)
@@ -0,0 +1,38 @@
+
+BIAS METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  2.0
+  IMFILE.STDEV       F32 10.0
+  IMFILE.MEANSTDEV   F32  0.5
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.5
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  1.0
+  EXP.STDEV          F32 10.0
+  EXP.MEANSTDEV      F32  0.5
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  3.0
+  ENSEMBLE.STDEV     F32  3.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+# FILTER is an additional qualifier, and may be "*" (or absent!), in which case it matches everything
+# EXPECTED is the expected mean value
+# IMFILE.MEAN is the maximum permitted mean value for an imfile, relative to the standard deviation
+# IMFILE.STDEV is the maximum permitted standard deviation for an imfile
+# EXP.MEAN is the maximum permitted mean value for an exposure, relative to the standard deviation
+# EXP.STDEV is the maximum permitted standard deviation for an exposure
+# EXP.MEANSTDEV is the maximum permitted mean standard deviation for an exposure relative to the mean
+# ENSEMBLE.MEAN is the maximum permitted mean for an ensemble of exposures
+# ENSEMBLE.STDEV is the maximum permitted standard deviation for an ensemble of exposures
+# ENSEMBLE.MEANSTDEV is the maximum permitted mean standard deviation for an ensemble of exposures
+# IMFILE.SNR is the minimum permitted signal-to-noise for an imfile
+# EXP.SNR is the minimum permitted signal-to-noise for an exposure
+# These values (all except FILTER) may be zero, in which case no clipping is applied.
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lris_blue/camera.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lris_blue/camera.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lris_blue/camera.config	(revision 22157)
@@ -0,0 +1,20 @@
+# Camera configuration file for LRIS Blue: describes the camera
+
+# File formats that we know about
+FORMATS         METADATA
+	RAW	STR	lris_blue/format_raw.config
+	MEF	STR	lris_blue/format_mef.config
+END
+                                                                                
+                                                                                
+# Description of camera --- all the chips and the cells that comprise them
+FPA     METADATA
+        LeftChip	STR	LeftAmp RightAmp
+        RightChip	STR	LeftAmp RightAmp
+END
+
+
+# Recipe options
+RECIPES		METADATA
+#	PSPHOT		STR	psphot.config		# psphot details
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lris_blue/format_mef.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lris_blue/format_mef.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lris_blue/format_mef.config	(revision 22157)
@@ -0,0 +1,79 @@
+# The Low Resolution Imager and Spectrograph (LRIS) blue side
+
+# How to identify this type
+RULE	METADATA
+	TELESCOP	STR	Keck I
+	INSTRUME	STR	LRISBLUE
+	FORMAT		STR	MEF
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	CHIP	# There are no extensions
+	FPA.OBS		STR	OBSNUM	# A PHU keyword for unique identifier within the hierarchy level
+END
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	ccd1	STR	LeftChip:LeftAmp:left LeftChip:RightAmp:right
+	ccd2	STR	RightChip:LeftAmp:left RightChip:RightAmp:right
+END
+
+# Specify the cell data
+CELLS	METADATA
+	left		METADATA
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.GAIN.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BSECL1 BSECL2
+		CELL.TRIMSEC		STR	TSECL
+		CELL.GAIN		STR	GAINL
+		CELL.X0			S32	0
+	END
+
+	right	METADATA
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.GAIN.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BSECR1 BSECR2
+		CELL.TRIMSEC		STR	TSECR
+		CELL.GAIN		STR	GAINR
+		CELL.X0			S32	1024
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.AIRMASS		STR	AIRMASS
+	FPA.FILTER		STR	BLUFILT
+	FPA.POSANGLE		STR	ROTPOSN
+	FPA.RA			STR	RA
+	FPA.DEC			STR	DEC
+	CELL.EXPOSURE		STR	EXPOSURE
+	CELL.DARKTIME		STR	EXPOSURE	# No special darktime header; use exposure time
+	CELL.TIME		STR	MJD-OBS
+	CELL.XBIN		STR	CCDSUM
+	CELL.YBIN		STR	CCDSUM
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	FPA.RADECSYS		STR	ICRS
+	CELL.TIMESYS		STR	UTC
+	CELL.READDIR		S32	1
+	CELL.XPARITY		S32	1
+	CELL.YPARITY		S32	1
+	CELL.Y0			S32	0
+	CELL.READNOISE		F32	2.5
+	CELL.SATURATION		F32	65536
+	CELL.BAD		F32	0
+END
+
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	CELL.TIME	STR	MJD
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lris_blue/format_raw.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lris_blue/format_raw.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lris_blue/format_raw.config	(revision 22157)
@@ -0,0 +1,113 @@
+# The Low Resolution Imager and Spectrograph (LRIS) blue side
+
+# We have no choice but to hard-code the various regions, because Keck
+# only stores them as:
+# WINDOW  = '1,0,0,2048,4096'
+# PREPIX  =                   51
+# POSTPIX =                   80
+# BINNING = '1,1     '
+# AMPPSIZE= '[1:1024,1:4096]'
+
+# I don't know how we would get the IPP to react to changes in the
+# windowing on the fly --- we have no mechanism for setting the region
+# sizes on the basis of the above keywords.  Therefore, we hard-code
+# the regions and assert on our assumptions in the RULE.
+
+
+# How to identify this type
+RULE	METADATA
+	TELESCOP	STR	Keck I
+	INSTRUME	STR	LRISBLUE
+	AMPLIST		STR	1,4,0,0
+	WINDOW		STR	1,0,0,2048,4096
+	PREPIX		S32	51
+	POSTPIX		S32	80
+	BINNING		STR	1,1
+	AMPPSIZE	STR	[1:1024,1:4096]
+	NAXIS1		S32	4620
+	NAXIS2		S32	4096
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	NONE	# There are no extensions
+	FPA.OBS		STR	OBSNUM	# A PHU keyword for unique identifier within the hierarchy level
+END
+
+# What's in the FITS file?
+CONTENTS	STR	LeftChip:LeftAmp:amp1 LeftChip:RightAmp:amp2 RightChip:LeftAmp:amp3 RightChip:RightAmp:amp4
+
+# Specify the cell data
+CELLS	METADATA
+	amp1		METADATA
+		CELL.BIASSEC.SOURCE	STR	VALUE
+		CELL.TRIMSEC.SOURCE	STR	VALUE
+		CELL.BIASSEC		STR	[1:51,1:4096];[4301:4380,1:4096]
+		CELL.TRIMSEC		STR	[205:1228,1:4096]
+		CELL.GAIN		F32	1.542
+		CELL.X0			S32	0
+	END
+
+	amp2	METADATA
+		CELL.BIASSEC.SOURCE	STR	VALUE
+		CELL.TRIMSEC.SOURCE	STR	VALUE
+		CELL.BIASSEC		STR	[52:102,1:4096];[4381:4460,1:4096]
+		CELL.TRIMSEC		STR	[1229:2252,1:4096]
+		CELL.GAIN		F32	1.546
+		CELL.X0			S32	1024
+	END
+
+	amp3		METADATA
+		CELL.BIASSEC.SOURCE	STR	VALUE
+		CELL.TRIMSEC.SOURCE	STR	VALUE
+		CELL.BIASSEC		STR	[103:153,1:4096];[4461:4540,1:4096]
+		CELL.TRIMSEC		STR	[2253:3276,1:4096]
+		CELL.GAIN		F32	1.632
+		CELL.X0			S32	0
+	END
+
+	amp4	METADATA
+		CELL.BIASSEC.SOURCE	STR	VALUE
+		CELL.TRIMSEC.SOURCE	STR	VALUE
+		CELL.BIASSEC		STR	[154:204,1:4096];[4541:4620,1:4096]
+		CELL.TRIMSEC		STR	[3277:4300,1:4096]
+		CELL.GAIN		F32	1.633
+		CELL.X0			S32	1024
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.AIRMASS		STR	AIRMASS
+	FPA.FILTER		STR	BLUFILT
+	FPA.POSANGLE		STR	ROTPOSN
+	FPA.RA			STR	RA
+	FPA.DEC			STR	DEC
+	CELL.EXPOSURE		STR	EXPOSURE
+	CELL.DARKTIME		STR	EXPOSURE	# No special darktime header; use exposure time
+	CELL.TIME		STR	MJD-OBS
+	CELL.XBIN		STR	CCDSUM
+	CELL.YBIN		STR	CCDSUM
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	FPA.RADECSYS		STR	ICRS
+	CELL.TIMESYS		STR	UTC
+	CELL.READDIR		S32	1
+	CELL.XPARITY		S32	1
+	CELL.YPARITY		S32	1
+	CELL.Y0			S32	0
+	CELL.READNOISE		F32	2.5
+	CELL.SATURATION		F32	65536
+	CELL.BAD		F32	0
+END
+
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	CELL.TIME	STR	MJD
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lris_red/camera.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lris_red/camera.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lris_red/camera.config	(revision 22157)
@@ -0,0 +1,19 @@
+# Camera configuration file for LRIS-Red: describes the camera
+
+# File formats that we know about
+FORMATS         METADATA
+        RAW     STR     lris_red/format_raw.config
+	SPEC	STR	lris_red/format_spec.config
+END
+ 
+ 
+# Description of camera --- all the chips and the cells that comprise them
+FPA     METADATA
+        RedChip		STR	LeftSide RightSide
+END
+
+
+# Recipes to use when processing this camera
+RECIPES		METADATA
+	PPMERGE		STR	ppMerge.config		# Recipe to use for ppMerge
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lris_red/format_raw.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lris_red/format_raw.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lris_red/format_raw.config	(revision 22157)
@@ -0,0 +1,95 @@
+# The Low Resolution Imager and Spectrograph (LRIS) red side
+
+# We have no choice but to hard-code the various regions, because Keck
+# only stores them as:
+# WINDOW  = '0,0,0,2048,2048'
+# PREPIX  =                   20
+# POSTPIX =                   80
+# BINNING = '1,1     '
+# AMPPSIZE= '[1:1024,1:4096]'
+
+# I don't know how we would get the IPP to react to changes in the
+# windowing on the fly --- we have no mechanism for setting the region
+# sizes on the basis of the above keywords.  Therefore, we hard-code
+# the regions and assert on our assumptions in the RULE.
+
+
+# How to identify this type
+RULE	METADATA
+	TELESCOP	STR	Keck I
+	INSTRUME	STR	LRIS
+	AMPLIST		STR	2,1,0,0
+	WINDOW		STR	0,0,0,2048,2048
+	PREPIX		S32	20
+	POSTPIX		S32	80
+	BINNING		STR	1, 1
+	CCDPSIZE	STR	[1:2048,1:2048]
+	NAXIS1		S32	2248
+	NAXIS2		S32	2048
+	IMTYPE		STR	TWOAMPTOP
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA	# The FITS file represents a single chip
+	EXTENSIONS	STR	NONE	# There are no extensions
+	FPA.OBS		STR	OBSNUM	# A PHU keyword for unique identifier within the hierarchy level
+END
+
+# What's in the FITS file?
+CONTENTS	STR	RedChip:LeftSide:left RedChip:RightSide:right
+
+# Specify the cell data
+CELLS	METADATA
+	left	METADATA
+		CELL.BIASSEC.SOURCE	STR	VALUE
+		CELL.TRIMSEC.SOURCE	STR	VALUE
+		CELL.BIASSEC		STR	[1:20,1:2048];[2089:2168,1:2048]
+		CELL.TRIMSEC		STR	[41:1064,1:2048]
+		CELL.GAIN		F32	1.98
+		CELL.READNOISE		F32	6.1
+		CELL.X0			S32	0
+	END
+
+	right	METADATA
+		CELL.BIASSEC.SOURCE	STR	VALUE
+		CELL.TRIMSEC.SOURCE	STR	VALUE
+		CELL.BIASSEC		STR	[21:40,1:2048];[2169:2248,1:2048]
+		CELL.TRIMSEC		STR	[1065:2088,1:2048]
+		CELL.GAIN		F32	2.17
+		CELL.READNOISE		F32	6.3
+		CELL.X0			S32	1023
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.FILTER	STR	FILTER
+	FPA.POSANGLE	STR	POSANG
+	FPA.RA		STR	OBJ-RA
+	FPA.DEC		STR	OBJ-DEC
+	CELL.EXPOSURE	STR	EXPTIME
+	CELL.DARKTIME	STR	DARKTIME
+	CELL.TIME	STR	MJD-OBS
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	FPA.RADECSYS	STR	ICRS
+	CELL.XPARITY	S32	1
+	CELL.YPARITY	S32	1
+	CELL.SATURATION	F32	65535
+	CELL.BAD	F32	0
+	CELL.XBIN	S32	1
+	CELL.YBIN	S32	1
+	CELL.READDIR	S32	1
+	CELL.TIMESYS	STR	UTC
+END
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+        FPA.RA          STR     HOURS
+        FPA.DEC         STR     DEGREES
+        CELL.TIME       STR     MJD
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lris_red/format_spec.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lris_red/format_spec.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lris_red/format_spec.config	(revision 22157)
@@ -0,0 +1,96 @@
+# The Low Resolution Imager and Spectrograph (LRIS) red side
+
+# We have no choice but to hard-code the various regions, because Keck
+# only stores them as:
+# WINDOW  = '0,0,550,2048,1000'
+# PREPIX  =                   20
+# POSTPIX =                   80
+# BINNING = '1,1     '
+# AMPPSIZE= '[1:1024,1:4096]'
+
+# I don't know how we would get the IPP to react to changes in the
+# windowing on the fly --- we have no mechanism for setting the region
+# sizes on the basis of the above keywords.  Therefore, we hard-code
+# the regions and assert on our assumptions in the RULE.
+
+
+# How to identify this type
+RULE	METADATA
+	TELESCOP	STR	Keck I
+	INSTRUME	STR	LRIS
+	AMPLIST		STR	2,1,0,0
+	WINDOW		STR	0,0,550,2048,1000	# Narrow spectroscopy format
+	PREPIX		S32	20
+	POSTPIX		S32	80
+	BINNING		STR	1, 1
+	CCDPSIZE	STR	[1:2048,1:2048]
+	NAXIS1		S32	2248
+	NAXIS2		S32	1000
+	IMTYPE		STR	TWOAMPTOP
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA	# The FITS file represents a single chip
+	EXTENSIONS	STR	NONE	# There are no extensions
+	FPA.OBS		STR	OBSNUM	# A PHU keyword for unique identifier within the hierarchy level
+	CHIP.NAME	STR	INSTRUME	# An extension keyword for unique identifier
+END
+
+# What's in the FITS file?
+CONTENTS	STR	RedChip:LeftSide:left RedChip:RightSide:right
+
+# Specify the cell data
+CELLS	METADATA
+	left	METADATA
+		CELL.BIASSEC.SOURCE	STR	VALUE
+		CELL.TRIMSEC.SOURCE	STR	VALUE
+		CELL.BIASSEC		STR	[1:20,1:1000];[2089:2168,1:1000]
+		CELL.TRIMSEC		STR	[41:1064,1:1000]
+		CELL.GAIN		F32	1.98
+		CELL.READNOISE		F32	6.1
+		CELL.X0			S32	0
+	END
+
+	right	METADATA
+		CELL.BIASSEC.SOURCE	STR	VALUE
+		CELL.TRIMSEC.SOURCE	STR	VALUE
+		CELL.BIASSEC		STR	[21:40,1:1000];[2169:2248,1:1000]
+		CELL.TRIMSEC		STR	[1065:2088,1:1000]
+		CELL.GAIN		F32	2.17
+		CELL.READNOISE		F32	6.3
+		CELL.X0			S32	1023
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.FILTER	STR	FILTER
+	FPA.POSANGLE	STR	POSANG
+	FPA.RA		STR	OBJ-RA
+	FPA.DEC		STR	OBJ-DEC
+	CELL.EXPOSURE	STR	EXPTIME
+	CELL.DARKTIME	STR	DARKTIME
+	CELL.TIME	STR	MJD-OBS
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	FPA.RADECSYS	STR	ICRS
+	CELL.XPARITY	S32	1
+	CELL.YPARITY	S32	1
+	CELL.SATURATION	F32	65535
+	CELL.BAD	F32	0
+	CELL.XBIN	S32	1
+	CELL.YBIN	S32	1
+	CELL.READDIR	S32	1
+	CELL.TIMESYS	STR	UTC
+END
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+        FPA.RA          STR     HOURS
+        FPA.DEC         STR     DEGREES
+        CELL.TIME       STR     MJD
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/.cvsignore	(revision 22157)
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/Makefile.am	(revision 22157)
@@ -0,0 +1,22 @@
+
+installdir = $(datadir)/ippconfig/lulin
+
+install_files = \
+	dvo.config \
+	camera.config \
+	format.config \
+	ppImage.config \
+	ppMerge.config \
+	psastro.config \
+	psphot.config \
+	pswarp.config \
+	rejections.config
+
+install_DATA = $(install_files)
+
+install-data-hook:
+	chmod 0755 $(installdir)
+
+EXTRA_DIST = $(install_files)
+
+ACLOCAL_AMFLAGS = -I m4
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/camera.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/camera.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/camera.config	(revision 22157)
@@ -0,0 +1,78 @@
+# Camera configuration file for Lulin 1m Optical Telescope
+
+# File formats that we know about
+FORMATS         METADATA
+        SIMPLE	STR     lulin/format.config
+END
+
+# Description of camera --- all the chips and the cells that comprise them
+FPA     METADATA
+        Chip	STR	Cell
+END
+
+# Lookup table for filter name to filter abstract name
+FILTER.ID       METADATA 
+	UNKNOWN	STR	UNKNOWN
+END
+
+# Table of strings to use for the class identifier (see ippdb) according to the file level.
+CLASSID         METADATA
+        FPA     STR     fpa
+        CHIP    STR     {CHIP.NAME}
+        CELL    STR     {CHIP.NAME}:{CELL.NAME}
+        fpa     STR     fpa
+        chip    STR     {CHIP.NAME}
+        cell    STR     {CHIP.NAME}:{CELL.NAME}
+END
+
+DVO.CAMERADIR	STR	lulin		# Camera directory for DVO
+
+# convert supplied FPA.OBSTYPE values to abstract exptype names
+OBSTYPE.TABLE METADATA
+  bias     STR BIAS
+  zero     STR BIAS
+  dark     STR DARK
+  flat     STR SKYFLAT
+  skyflat  STR SKYFLAT
+  domeflat STR DOMEFLAT
+  object   STR LIGHT
+  science  STR LIGHT
+END
+
+# Recipe options
+RECIPES         METADATA
+        # Other recipes
+        PSPHOT          STR     lulin/psphot.config           # psphot details
+        PSASTRO         STR     lulin/psastro.config          # psastro details
+	PSWARP		STR	lulin/pswarp.config		# pswarp details
+        PPIMAGE         STR     lulin/ppImage.config          # ppImage recipe
+	REJECTIONS      STR     lulin/rejections.config       # rejection recipe
+	PPMERGE		STR	lulin/ppMerge.config		# ppMerge recipe
+END
+
+# reduction classes (recipes which are grouped together)
+REDUCTION	STR	recipes/reductionClasses.mdc
+
+FITSTYPES       STR     recipes/fitstypes.mdc
+
+FILERULES	STR	recipes/filerules-simple.mdc		# File rules appropriate for MEF format
+
+EXTNAME.RULES METADATA
+  CMF.HEAD   STR {CHIP.NAME}.hdr
+  CMF.DATA   STR {CHIP.NAME}.psf # use .PSF and .EXT?
+  CMF.XSRC STR {CHIP.NAME}.xsrc # use .PSF and .EXT?
+  CMF.XFIT STR {CHIP.NAME}.xfit # use .PSF and .EXT?
+
+  PSF.HEAD   STR {CHIP.NAME}.hdr
+  PSF.TABLE  STR {CHIP.NAME}.psf_model
+  PSF.RESID  STR {CHIP.NAME}.psf_resid
+
+  REF.ASTROM STR {CHIP.NAME}.ref_astrom
+END
+
+BLANK.HEADERS	METADATA
+	FPA.TIME	STR	MJD-OBS
+	FPA.EXPOSURE	STR	EXPTIME
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.NAME	STR	EXPNUM
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/dvo.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/dvo.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/dvo.config	(revision 22157)
@@ -0,0 +1,67 @@
+
+# location of DVO database tables
+CATDIR			/data/alala.0/eugene/isp/catdir
+
+# keywords used by DVO to interpret the headers
+
+# keyword abstractions:
+#DATE-KEYWORD		DATE-OBS
+#DATE-MODE		yyyy-mm-dd
+#UT-KEYWORD		UTC-OBS
+JD-KEYWORD		NONE
+MJD-KEYWORD		MJD-OBS
+
+# other keyword abstractions
+EXPTIME-KEYWORD		EXPTIME
+AIRMASS-KEYWORD		AIRMASS
+CCDNUM-KEYWORD		EXTNAME
+ST-KEYWORD		NONE
+OBSERVATORY-LATITUDE	NONE
+OBSERVATORY-LONGITUDE	NONE
+SUBPIX_DATAFILE		NONE
+
+# instrumental magnitude range for calibration mode 
+CAL_INSTMAG_MAX		0
+CAL_INSTMAG_MIN		0
+
+# exclude overscan region from the dB image boundaries
+XOVERSCAN		0
+YOVERSCAN		0
+
+# only upload stars within region; a value of 0 means ignore the limit
+ADDSTAR_XMIN		0
+ADDSTAR_XMAX		0
+ADDSTAR_YMIN		0
+ADDSTAR_YMAX		0
+
+# exclude stars with SN > SNLIMIT (ADDSTAR_SNLIMIT overrides old name MIN_SN_FSTAT)
+ADDSTAR_SNLIMIT		0
+
+# correlation radius (arcseconds)
+ADDSTAR_RADIUS		1.0
+
+# scaled correlation radius 
+ADDSTAR_NSIGMA		0
+
+IMAGE_SCATTER           0.075  # mark images POOR if stdev(Mcal) > IMAGE_SCATTER
+STAR_SCATTER            0.005  # mark stars POOR if stdev(Mrel) > STAR_SCATTER
+IMAGE_OFFSET            0.100  # mark images POOR if abs(delta(Mcal)) > IMAGE_OFFSET
+STAR_CHISQ              10.0   # mark stars POOR if Xm > STAR_CHISQ
+STAR_TOOFEW              3     # mark star FEW if N(good) < STAR_TOOFEW
+IMAGE_TOOFEW            10     # mark image FEW if N(good) < IMAGE_TOOFEW
+IMAGE_GOOD_FRACTION     0.05   # mark image FEW if N(good) < IMAGE_GOOD_FRACTION * Nstars
+
+SCATTER_LIM             15.0
+MAG_LIM                 20.0   # select stars brighter than this for relphot analysis
+SIGMA_LIM               0.015  # select measurements with dM < SIGMA_LIM for relphot analysis
+# INST_MAG_MIN         -17     # optional constraints on magnitude ranges
+# INST_MAG_MAX         -13     # optional constraints on magnitude ranges
+
+RELPHOT_GRID_BINNING    256
+RELPHOT_GRID_X 6
+RELPHOT_GRID_Y 14
+
+PM_DT_MIN               0.25
+PM_TOOFEW               4
+POS_TOOFEW              1
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/format.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/format.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/format.config	(revision 22157)
@@ -0,0 +1,101 @@
+# The LOT1m data is a single chip with single cell
+
+# How to recognise this type
+RULE	METADATA
+	TELESCOP	STR	LOT1m
+	INSTRUME	STR	Roper
+	OBSERVAT	STR	NCU Lulin observatory, Taiwan
+	CAMERA		STR	Princeton Instruments 1300B
+	DETECTOR	STR	EEV 1340x1300 CCD36-4
+	NAXIS           S32     2
+	SWCREATE	STR	MaxIm DL Version 4.10
+END
+
+FILE	METADATA
+	# How to read this data
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	NONE	# There are no extensions
+	FPA.OBS		STR	OWNER	# A PHU keyword for unique identifier
+END
+
+# What's in the FITS file?
+CONTENTS	STR	Chip:Cell:Amplifier
+
+# Specify the cells
+CELLS		METADATA
+	# Cell type, concepts to use
+	Amplifier	METADATA
+		CELL.BIASSEC.SOURCE	STR	NONE
+		CELL.TRIMSEC.SOURCE	STR	VALUE
+		CELL.TRIMSEC		STR	[0:0,0:0]
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.TELESCOPE	STR	TELESCOP
+	FPA.INSTRUMENT	STR	INSTRUME
+	FPA.DETECTOR	STR	DETECTOR
+	FPA.OBSTYPE	STR	IMAGETYP
+	FPA.TIME	STR	DATE-OBS TIME-OBS
+	FPA.TIMESYS	STR	TIMESYS
+        FPA.RA          STR     OBJCTRA
+        FPA.DEC         STR     OBJCTDEC
+	FPA.EXPOSURE	STR	EXPTIME
+	FPA.TEMP	STR	CCD-TEMP
+	CHIP.TEMP	STR	CCD-TEMP
+        CELL.EXPOSURE   STR     EXPTIME
+        CELL.DARKTIME   STR     EXPTIME
+        CELL.READNOISE  STR     SLOWRN
+	CELL.GAIN	STR	SLOWGAIN
+	CELL.TIME	STR	DATE-OBS TIME-OBS
+	CELL.TIMESYS	STR	TIMESYS
+        CELL.XBIN       STR     XBINNING
+        CELL.YBIN       STR     YBINNING
+	CELL.XWINDOW	STR	XORGSUBF
+	CELL.YWINDOW	STR	YORGSUBF
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS        METADATA
+        FPA.AIRMASS     	F32	0.0
+        FPA.FILTERID    	STR     UNKNOWN
+        FPA.FILTER      	STR     UNKNOWN
+	FPA.OBJECT		STR	Unknown object
+        FPA.RADECSYS    	STR     ICRS
+        FPA.POSANGLE    	STR     0.0
+	FPA.FOCUS		STR	0.0
+	FPA.ALT			STR	0.0
+	FPA.AZ			STR	0.0
+
+	CHIP.XSIZE		S32	1340
+	CHIP.YSIZE		S32	1300
+	CELL.XSIZE		S32	1340
+	CELL.YSIZE		S32	1300
+	CELL.READDIR		S32	1		# Cell is read in x direction
+        CELL.SATURATION		F32	50000.0
+        CELL.BAD                F32     0
+	CELL.XPARITY		S32	1
+	CELL.YPARITY		S32	1
+	CELL.X0			S32	0
+	CELL.Y0			S32	0
+	CHIP.X0			S32	0
+	CHIP.Y0			S32	0
+	CHIP.XPARITY		S32	1
+	CHIP.YPARITY		S32	1
+END
+
+
+# How to translation PS concepts into database lookups
+DATABASE	METADATA
+#	CELL.GAIN	STR	SELECT gain FROM camera WHERE chip_id = '{CHIP.NAME}' AND cell_id = '{CELL.NAME}'
+END		
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	SEPARATE YEAR.FIRST
+	CELL.TIME	STR	SEPARATE YEAR.FIRST
+END
+ 
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/ppImage.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/ppImage.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/ppImage.config	(revision 22157)
@@ -0,0 +1,172 @@
+### ppImage recipe configuration file for Megacam
+
+# Overscan subtraction
+OVERSCAN.SINGLE		BOOL	FALSE		# Reduce overscan to a single value?
+OVERSCAN.FIT		STR	NONE		# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER		S32	0		# Order of polynomial fit
+OVERSCAN.STAT		STR	MEAN		# MEAN | MEDIAN
+
+# for a test, turn off selected detrend types
+OVERSCAN	BOOL	FALSE		# Overscan subtraction
+BIAS		BOOL	FALSE		# Bias subtraction
+DARK		BOOL	TRUE		# Dark subtraction
+FLAT		BOOL	TRUE		# Flat-field normalisation
+
+# binned output image options
+BIN1.XBIN               S32     18
+BIN1.YBIN               S32     18
+BIN2.XBIN               S32     144
+BIN2.YBIN               S32     144
+
+# megacam needs fringe subtraction
+FRINGE		BOOL	TRUE		# Fringe subtraction
+
+# this table lists extra constraints which should be applied when
+# selecting the detrend images.
+# note: camera and time are always applied
+
+DETREND.CONSTRAINTS  METADATA
+  BIAS METADATA
+  END
+  DARK METADATA
+#   EXPTIME STR FPA.EXPOSURE
+  END
+  FLAT METADATA
+    FILTER  STR FPA.FILTERID
+#   DETTYPE STR DOMEFLAT.RAW
+#   VERSION STR SKYFLAT.RAW
+#   VERSION STR DOMEFLAT.CORR.XX
+#   VERSION STR SKYFLAT.CORR
+  END
+  FLAT_CORRECTION METADATA
+    FILTER   STR FPA.FILTER
+  END
+  FRINGE METADATA
+    FILTER   STR FPA.FILTER
+#   AIRMASS  STR FPA.AIRMASS
+#   TWILIGHT STR FPA.TWILIGHT
+  END
+  SHUTTER METADATA
+  END
+  MASK METADATA
+  END	
+END
+
+# only apply the fringe for these filters
+FRINGE.FILTERS  MULTI
+FRINGE.FILTERS	STR i
+FRINGE.FILTERS	STR z
+
+PHOTCODE.RULE		STR	{DETECTOR}.{FILTER.ID}.{CHIP.N}
+
+
+
+# Overscan, bias, dark, shutter
+PPIMAGE_OBDS       METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field
+PPIMAGE_OBDSF      METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe
+PPIMAGE_OBDSFR     METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    TRUE            # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom
+PPIMAGE_OBDSFRP    METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    TRUE            # Mask bad pixels
+  FRINGE           BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM           BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_OBDSFRA    METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    TRUE            # Mask bad pixels
+  FRINGE           BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM           BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END 
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/ppMerge.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/ppMerge.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/ppMerge.config	(revision 22157)
@@ -0,0 +1,40 @@
+
+# Bias combination --- don't want min/max rejection
+PPMERGE_BIAS	METADATA
+   REJ		F32	3.0		# Rejection threshold (sigma)
+   ITER		S32	2		# Number of rejection iterations
+   FRACHIGH	F32	0.0		# Fraction of high pixels to reject immediately
+   FRACLOW	F32	0.0		# Fraction of low pixels to reject immediately
+   WEIGHTS	BOOL	FALSE		# Use image weights?
+   COMBINE	STR	CLIPPED		# Statistic to use for combination: 
+END
+
+# Dark combination --- don't want min/max rejection
+# More aggressive clipping than bias, so as to remove CRs
+PPMERGE_DARK	METADATA
+  REJ		F32	2.0		# Rejection threshold (sigma)
+  ITER		S32	4		# Number of rejection iterations
+  FRACHIGH	F32	0.0		# Fraction of high pixels to reject immediately
+  FRACLOW	F32	0.0		# Fraction of low pixels to reject immediately
+  WEIGHTS	BOOL	FALSE		# Use image weights?
+  COMBINE	STR	CLIPPED		# Statistic to use for combination: 
+END
+
+# Flat combination --- use min/max rejection
+PPMERGE_FLAT	METADATA
+	REJ		F32	3.0		# Rejection threshold (sigma)
+	ITER		S32	1		# Number of rejection iterations
+	FRACHIGH	F32	0.3		# Fraction of high pixels to reject immediately
+	FRACLOW		F32	0.1		# Fraction of low pixels to reject immediately
+	NKEEP		S32	5		# Minimum number of pixels in stack to keep
+	WEIGHTS		BOOL	FALSE		# Use image weights?
+	COMBINE		STR	MEAN		# Statistic to use for combination: 
+END
+
+
+# Fringe combination --- already included in default, above
+PPMERGE_FRINGE	METADATA
+	FRACHIGH	F32	0.1		# Fraction of high pixels to reject immediately
+	WEIGHTS		BOOL	FALSE		# Use image weights?
+END
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/psastro.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/psastro.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/psastro.config	(revision 22157)
@@ -0,0 +1,74 @@
+
+# astrometry matching parameters
+
+# nominal plate scale (microns / pixel)
+PSASTRO.PIXEL.SCALE    F32  1.0
+
+# pmAstromGridAngle
+# max grid offset in FP units (microns)
+# use plate-scale to make this in pixels?
+PSASTRO.GRID.OFFSET    F32    2500.
+PSASTRO.GRID.SCALE     F32      50.
+
+# extra field for ref stars:
+PSASTRO.FIELD.PADDING  F32 1.0
+
+# these tweak are in FP units (pixels, currently)
+PSASTRO.TWEAK.SCALE     F32      1
+PSASTRO.TWEAK.RANGE     F32     75
+PSASTRO.TWEAK.SMOOTH    F32      2
+PSASTRO.TWEAK.NSIGMA    F32      3
+
+# single-chip radius match in pixels
+PSASTRO.MATCH.FIT.NITER S32   3
+PSASTRO.MATCH.RADIUS    F32   12.0
+PSASTRO.MATCH.RADIUS.N0 F32   15.0
+PSASTRO.MATCH.RADIUS.N1 F32   10.0
+PSASTRO.MATCH.RADIUS.N2 F32    5.0
+
+PSASTRO.MAX.NRAW       S32      1500   # max stars accepted for fitting (0 for all)
+PSASTRO.MAX.NREF       S32      1500   # max stars accepted for fitting (0 for all)
+
+PSASTRO.MIN.INST.MAG.RAW       F32      -15.5  # min instrumental magnitude for stars accepted for fitting
+PSASTRO.MAX.INST.MAG.RAW       F32       -8.0  # max instrumental magnitude for stars accepted for fitting
+
+PSASTRO.MAX.ERROR      F32      10.0 # max error in pixels
+PSASTRO.MIN.NSTAR      S32      10   # min fitted stars in solution
+
+PSASTRO.MOSAIC.MODE           BOOL     FALSE
+
+PSASTRO.MOSAIC.MAX.ERROR.N0 F32    1.50 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N1 F32    1.50 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N2 F32    0.90 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N3 F32    0.90 # max allow error for valid solution (arcsec)
+
+PSASTRO.MOSAIC.RADIUS.N0    F32    10.0
+PSASTRO.MOSAIC.RADIUS.N1    F32     8.0 # do not refine the match
+PSASTRO.MOSAIC.RADIUS.N2    F32     8.0 # do not refine the match
+PSASTRO.MOSAIC.RADIUS.N3    F32     4.0 
+
+PSASTRO.MOSAIC.CHIP.ORDER.N0  S32      0 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N1  S32      1 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N2  S32      1 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N3  S32      3 # fit order (-1 means use default)
+
+PSASTRO.MOSAIC.GRADIENT.NX    S32      2   # number of x-dir cells per chip
+PSASTRO.MOSAIC.GRADIENT.NY    S32      4   # number of y-dir cells per chip
+
+# use this recipe to set a tight constraint
+PSASTRO.FINE METADATA
+  PSASTRO.MOSAIC.MAX.ERROR.N0 F32    1.50 # max allow error for valid solution (arcsec)
+  PSASTRO.MOSAIC.MAX.ERROR.N1 F32    1.50 # max allow error for valid solution (arcsec)
+  PSASTRO.MOSAIC.MAX.ERROR.N2 F32    0.90 # max allow error for valid solution (arcsec)
+  PSASTRO.MOSAIC.MAX.ERROR.N3 F32    0.90 # max allow error for valid solution (arcsec)
+
+  PSASTRO.MOSAIC.RADIUS.N0    F32    8.0
+  PSASTRO.MOSAIC.RADIUS.N1    F32    0.0
+  PSASTRO.MOSAIC.RADIUS.N2    F32    4.0
+  PSASTRO.MOSAIC.RADIUS.N3    F32    4.0
+
+  PSASTRO.MOSAIC.CHIP.ORDER.N0  S32      0 # fit order (-1 means use default)
+  PSASTRO.MOSAIC.CHIP.ORDER.N1  S32      1 # fit order (-1 means use default)
+  PSASTRO.MOSAIC.CHIP.ORDER.N2  S32      1 # fit order (-1 means use default)
+  PSASTRO.MOSAIC.CHIP.ORDER.N3  S32      1 # fit order (-1 means use default)
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/psphot.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/psphot.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/psphot.config	(revision 22157)
@@ -0,0 +1,43 @@
+
+# turn these on to see specific outputs
+SAVE.OUTPUT	BOOL 	TRUE
+SAVE.BACKMDL	BOOL 	TRUE
+SAVE.PSF	BOOL 	TRUE
+SAVE.PLOTS     	BOOL    TRUE
+
+BACKGROUND.XBIN	    S32  128            # size of background superpixels
+BACKGROUND.YBIN	    S32  128            # size of background superpixels
+
+# image background parameters
+IMSTATS_NPIX        S32  10000    	 # number of pixels to use for sky estimate boxes:
+SKY_STAT            STR  FITTED_MEAN_V4  # statistic used to measure background
+SKY_CLIP_SIGMA      F32  2.0             # statistic used to measure background
+
+PSF_SN_LIM          F32  100             # minimum S/N for stars used for PSF model
+PSF_MAX_NSTARS      S32  300             # limit number of stars used for PSF model
+
+# PSF model parameters : choose the PSF model desired
+PSF_MODEL           STR  PS_MODEL_QGAUSS
+
+MOMENTS_SN_MIN      F32   30.0
+EXT_MIN_SN           F32  50.0           # fit galaxies above this S/N limit
+FULL_FIT_SN_LIM      F32  50.0
+AP_MIN_SN            F32  50.0
+
+# OUTPUT.FORMAT       STR SMPDATA
+OUTPUT.FORMAT       STR PS1_DEV_1
+
+PSF_SHAPE_NSIGMA     F32  3.0		 # max significance for shape variation
+PSF_MAX_CHI          F32  50.0		 # reject objects worse that this
+
+#PSF.TREND.MODE STR MAP
+#PSF.TREND.NX   S32 1
+#PSF.TREND.NY   S32 1
+
+DIAGNOSTIC.PLOTS		METADATA
+  IMAGE.BACKGROUND.CELL.HISTOGRAM   BOOL FALSE
+  IMAGE.BACKGROUND.CELL.HISTOGRAM.X S32 -1
+  IMAGE.BACKGROUND.CELL.HISTOGRAM.Y S32 12
+END
+
+USE_FOOTPRINTS                      BOOL  TRUE       	  # use new pmFootprint peak packaging
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/pswarp.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/pswarp.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/pswarp.config	(revision 22157)
@@ -0,0 +1,3 @@
+### Megacam recipe for pswarp
+
+ASTROM.SOURCE	STR	PSASTRO.OUTPUT	# Source file rule for astrometry, or NULL
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/rejections.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/rejections.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/lulin/rejections.config	(revision 22157)
@@ -0,0 +1,38 @@
+
+BIAS METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  2.0
+  IMFILE.STDEV       F32 10.0
+  IMFILE.MEANSTDEV   F32  0.5
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.5
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  1.0
+  EXP.STDEV          F32 10.0
+  EXP.MEANSTDEV      F32  0.5
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  3.0
+  ENSEMBLE.STDEV     F32  3.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+# FILTER is an additional qualifier, and may be "*" (or absent!), in which case it matches everything
+# EXPECTED is the expected mean value
+# IMFILE.MEAN is the maximum permitted mean value for an imfile, relative to the standard deviation
+# IMFILE.STDEV is the maximum permitted standard deviation for an imfile
+# EXP.MEAN is the maximum permitted mean value for an exposure, relative to the standard deviation
+# EXP.STDEV is the maximum permitted standard deviation for an exposure
+# EXP.MEANSTDEV is the maximum permitted mean standard deviation for an exposure relative to the mean
+# ENSEMBLE.MEAN is the maximum permitted mean for an ensemble of exposures
+# ENSEMBLE.STDEV is the maximum permitted standard deviation for an ensemble of exposures
+# ENSEMBLE.MEANSTDEV is the maximum permitted mean standard deviation for an ensemble of exposures
+# IMFILE.SNR is the minimum permitted signal-to-noise for an imfile
+# EXP.SNR is the minimum permitted signal-to-noise for an exposure
+# These values (all except FILTER) may be zero, in which case no clipping is applied.
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/mcshort/camera.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/mcshort/camera.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/mcshort/camera.config	(revision 22157)
@@ -0,0 +1,103 @@
+# "mcshort" is a MegaCam camera with only the central six chips --- it's faster than the entire FPA.
+# Camera configuration file for MegaCam: describes the camera
+
+# File formats that we know about
+FORMATS		METADATA
+	RAW	STR	mcshort/format_raw.config
+	SPLICED	STR	mcshort/format_spliced.config
+	SPLIT	STR	mcshort/format_split.config
+END
+
+# Description of camera --- all the chips and the cells that comprise them
+FPA	METADATA
+#	ccd00	STR	LeftAmp RightAmp
+#	ccd01	STR	LeftAmp RightAmp
+#	ccd02	STR	LeftAmp RightAmp
+#	ccd03	STR	LeftAmp RightAmp
+#	ccd04	STR	LeftAmp RightAmp
+#	ccd05	STR	LeftAmp RightAmp
+#	ccd06	STR	LeftAmp RightAmp
+#	ccd07	STR	LeftAmp RightAmp
+#	ccd08	STR	LeftAmp RightAmp
+#	ccd09	STR	LeftAmp RightAmp
+#	ccd10	STR	LeftAmp RightAmp
+#	ccd11	STR	LeftAmp RightAmp
+	ccd12	STR	LeftAmp RightAmp
+	ccd13	STR	LeftAmp RightAmp
+	ccd14	STR	LeftAmp RightAmp
+#	ccd15	STR	LeftAmp RightAmp
+#	ccd16	STR	LeftAmp RightAmp
+#	ccd17	STR	LeftAmp RightAmp
+#	ccd18	STR	LeftAmp RightAmp
+#	ccd19	STR	LeftAmp RightAmp
+#	ccd20	STR	LeftAmp RightAmp
+	ccd21	STR	LeftAmp RightAmp
+	ccd22	STR	LeftAmp RightAmp
+	ccd23	STR	LeftAmp RightAmp
+#	ccd24	STR	LeftAmp RightAmp
+#	ccd25	STR	LeftAmp RightAmp
+#	ccd26	STR	LeftAmp RightAmp
+#	ccd27	STR	LeftAmp RightAmp
+#	ccd28	STR	LeftAmp RightAmp
+#	ccd29	STR	LeftAmp RightAmp
+#	ccd30	STR	LeftAmp RightAmp
+#	ccd31	STR	LeftAmp RightAmp
+#	ccd32	STR	LeftAmp RightAmp
+#	ccd33	STR	LeftAmp RightAmp
+#	ccd34	STR	LeftAmp RightAmp
+#	ccd35	STR	LeftAmp RightAmp
+END
+
+# move this elsewhere?  we need a lookup table to go from filter ID to abstract name
+FILTER.ID       METADATA
+   u        STR   u.MP9301     
+   g        STR   g.MP9401     
+   r        STR   r.MP9601     
+   i        STR   i.MP9701     
+   z        MULTI
+   z        STR   z.MP9801     
+   z        STR   Zp           
+   z        STR   Zprime       
+
+   Ha       MULTI
+   Ha       STR   Ha.MP7605    
+   Ha       STR   Halpha       
+   Ha       STR   Haalpha.on   
+   HaOff    STR   HaOFF.MP7604 
+   	              		
+   CN       MULTI
+   CN       STR   CN.MP780     
+   CN       STR   cn.MP7803    
+   CN       STR   CN.MP7803    
+   	              		
+   TiO      MULTI
+   TiO      STR   TiO.MP77     
+   TiO      STR   tio.MP7701   
+   TiO      STR   TiO.MP7701   
+   NB920    STR   NB920        
+   	              		
+   B2F      STR   B2F          
+   Bj       STR   Bj           
+   Vj       STR   Vj           
+   Rj       STR   Rj           
+   Ij       STR   Ij           
+   Hb       STR   Hb           
+   HbOff    STR   HbOff        
+END
+
+# Recipe options
+RECIPES		METADATA
+	# Other recipes
+        PSPHOT          STR     megacam/psphot.config           # psphot details
+        PSASTRO         STR     megacam/psastro.config          # psastro details
+	PPSTATS		STR	megacam/ppStats.config		# ppStats recipe
+	PPIMAGE         STR     megacam/ppImage.config		# ppImage recipe
+END
+
+
+# reduction classes (recipes which are grouped together)
+REDUCTION	STR	recipes/reductionClasses.mdc
+
+FITSTYPES       STR     recipes/fitstypes.mdc
+FILERULES       STR     recipes/filerules-mef.mdc
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/mcshort/format_raw.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/mcshort/format_raw.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/mcshort/format_raw.config	(revision 22157)
@@ -0,0 +1,336 @@
+# "mcshort" is a MegaCam camera with only the central six chips --- it's faster than the entire FPA.
+# The raw MegaCam data comes off the telescope with each of the chips stored in extensions of a MEF file.
+
+# How to identify this type
+RULE	METADATA
+	TELESCOP	STR	CFHT 3.6m
+	DETECTOR	STR	MegaCam
+	EXTEND		BOOL	T
+	NEXTEND		S32	72
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	CELL	# The extensions represent cells
+	FPA.OBS		STR	EXPNUM	# A PHU keyword for unique identifier within the hierarchy level
+END
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# Extension name, chip name:type
+#	amp00		STR	ccd00:LeftAmp:left
+#	amp01		STR	ccd00:RightAmp:right
+#	amp02		STR	ccd01:LeftAmp:left
+#	amp03		STR	ccd01:RightAmp:right
+#	amp04		STR	ccd02:LeftAmp:left
+#	amp05		STR	ccd02:RightAmp:right
+#	amp06		STR	ccd03:LeftAmp:left
+#	amp07		STR	ccd03:RightAmp:right
+#	amp08		STR	ccd04:LeftAmp:left
+#	amp09		STR	ccd04:RightAmp:right
+#	amp10		STR	ccd05:LeftAmp:left
+#	amp11		STR	ccd05:RightAmp:right
+#	amp12		STR	ccd06:LeftAmp:left
+#	amp13		STR	ccd06:RightAmp:right
+#	amp14		STR	ccd07:LeftAmp:left
+#	amp15		STR	ccd07:RightAmp:right
+#	amp16		STR	ccd08:LeftAmp:left
+#	amp17		STR	ccd08:RightAmp:right
+#	amp18		STR	ccd09:LeftAmp:left
+#	amp19		STR	ccd09:RightAmp:right
+#	amp20		STR	ccd10:LeftAmp:left
+#	amp21		STR	ccd10:RightAmp:right
+#	amp22		STR	ccd11:LeftAmp:left
+#	amp23		STR	ccd11:RightAmp:right
+	amp24		STR	ccd12:LeftAmp:left
+	amp25		STR	ccd12:RightAmp:right
+	amp26		STR	ccd13:LeftAmp:left
+	amp27		STR	ccd13:RightAmp:right
+	amp28		STR	ccd14:LeftAmp:left
+	amp29		STR	ccd14:RightAmp:right
+#	amp30		STR	ccd15:LeftAmp:left
+#	amp31		STR	ccd15:RightAmp:right
+#	amp32		STR	ccd16:LeftAmp:left
+#	amp33		STR	ccd16:RightAmp:right
+#	amp34		STR	ccd17:LeftAmp:left
+#	amp35		STR	ccd17:RightAmp:right
+#	amp36		STR	ccd18:LeftAmp:left
+#	amp37		STR	ccd18:RightAmp:right
+#	amp38		STR	ccd19:LeftAmp:left
+#	amp39		STR	ccd19:RightAmp:right
+#	amp40		STR	ccd20:LeftAmp:left
+#	amp41		STR	ccd20:RightAmp:right
+	amp42		STR	ccd21:LeftAmp:left
+	amp43		STR	ccd21:RightAmp:right
+	amp44		STR	ccd22:LeftAmp:left
+	amp45		STR	ccd22:RightAmp:right
+	amp46		STR	ccd23:LeftAmp:left
+	amp47		STR	ccd23:RightAmp:right
+#	amp48		STR	ccd24:LeftAmp:left
+#	amp49		STR	ccd24:RightAmp:right
+#	amp50		STR	ccd25:LeftAmp:left
+#	amp51		STR	ccd25:RightAmp:right
+#	amp52		STR	ccd26:LeftAmp:left
+#	amp53		STR	ccd26:RightAmp:right
+#	amp54		STR	ccd27:LeftAmp:left
+#	amp55		STR	ccd27:RightAmp:right
+#	amp56		STR	ccd28:LeftAmp:left
+#	amp57		STR	ccd28:RightAmp:right
+#	amp58		STR	ccd29:LeftAmp:left
+#	amp59		STR	ccd29:RightAmp:right
+#	amp60		STR	ccd30:LeftAmp:left
+#	amp61		STR	ccd30:RightAmp:right
+#	amp62		STR	ccd31:LeftAmp:left
+#	amp63		STR	ccd31:RightAmp:right
+#	amp64		STR	ccd32:LeftAmp:left
+#	amp65		STR	ccd32:RightAmp:right
+#	amp66		STR	ccd33:LeftAmp:left
+#	amp67		STR	ccd33:RightAmp:right
+#	amp68		STR	ccd34:LeftAmp:left
+#	amp69		STR	ccd34:RightAmp:right
+#	amp70		STR	ccd35:LeftAmp:left
+#	amp71		STR	ccd35:RightAmp:right
+END
+
+# Specify the cell data
+CELLS	METADATA
+	left	METADATA	# Left amplifier
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BIASSEC
+		CELL.TRIMSEC		STR	DATASEC
+		CELL.XPARITY		S32	1 # We could have specified this as a DEFAULT, but this works
+		CELL.X0			S32	1
+		CELL.Y0			S32	1
+	END
+	right	METADATA	# Right amplifier
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BIASSEC
+		CELL.TRIMSEC		STR	DATASEC
+		CELL.XPARITY		S32	-1 # This cell is read out in the opposite direction
+		CELL.X0			S32	2048
+		CELL.Y0			S32	1
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.OBS		STR	EXPNUM
+	FPA.AIRMASS		STR	AIRMASS
+	FPA.FILTER		STR	FILTER
+	FPA.POSANGLE		STR	ROTANGLE
+	FPA.RA			STR	RA
+	FPA.DEC			STR	DEC
+	FPA.RADECSYS		STR	RADECSYS
+	FPA.OBSTYPE		STR	OBSTYPE
+	FPA.OBJECT		STR	CMMTOBS
+	FPA.TIME		STR	MJD-OBS
+	FPA.TIMESYS		STR	TIMESYS
+	FPA.ALT			STR	TELALT
+	FPA.AZ			STR	TELAZ
+	CHIP.TEMP		STR	DETTEM
+	CELL.EXPOSURE		STR	EXPTIME
+	CELL.DARKTIME		STR	DARKTIME
+	CELL.GAIN		STR	GAIN
+	CELL.READNOISE		STR	RDNOISE
+	CELL.SATURATION		STR	SATURATE
+	CELL.TIME		STR	MJD-OBS
+	CELL.TIMESYS		STR	TIMESYS
+	CELL.XBIN		STR	CCDBIN1
+	CELL.YBIN		STR	CCDBIN2
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	CELL.READDIR		S32	1		# Cell is read in x direction
+	CELL.BAD		S32	0
+	CELL.YPARITY		S32	1
+
+	CHIP.X0.DEPEND		STR	CHIP.NAME
+	CHIP.X0		METADATA
+#		ccd00	S32	0
+#		ccd01	S32	2048
+#		ccd02	S32	4096
+#		ccd03	S32	6144
+#		ccd04	S32	8192
+#		ccd05	S32	10240
+#		ccd06	S32	12288
+#		ccd07	S32	14336
+#		ccd08	S32	16384
+#		ccd09	S32	0
+#		ccd10	S32	2048
+#		ccd11	S32	4096
+		ccd12	S32	6144
+		ccd13	S32	8192
+		ccd14	S32	10240
+#		ccd15	S32	12288
+#		ccd16	S32	14336
+#		ccd17	S32	16384
+#		ccd18	S32	0
+#		ccd19	S32	2048
+#		ccd20	S32	4096
+		ccd21	S32	6144
+		ccd22	S32	8192
+		ccd23	S32	10240
+#		ccd24	S32	12288
+#		ccd25	S32	14336
+#		ccd26	S32	16384
+#		ccd27	S32	0
+#		ccd28	S32	2048
+#		ccd29	S32	4096
+#		ccd30	S32	6144
+#		ccd31	S32	8192
+#		ccd32	S32	10240
+#		ccd33	S32	12288
+#		ccd34	S32	14336
+#		ccd35	S32	16384
+	END
+	CHIP.Y0.DEPEND		STR	CHIP.NAME
+	CHIP.Y0		METADATA
+#		ccd00	S32	18447
+#		ccd01	S32	18447
+#		ccd02	S32	18447
+#		ccd03	S32	18447
+#		ccd04	S32	18447
+#		ccd05	S32	18447
+#		ccd06	S32	18447
+#		ccd07	S32	18447
+#		ccd08	S32	18447
+#		ccd09	S32	13835
+#		ccd10	S32	13835
+#		ccd11	S32	13835
+		ccd12	S32	13835
+		ccd13	S32	13835
+		ccd14	S32	13835
+#		ccd15	S32	13835
+#		ccd16	S32	13835
+#		ccd17	S32	13835
+#		ccd18	S32	13835
+#		ccd19	S32	4612
+#		ccd20	S32	4612
+		ccd21	S32	4612
+		ccd22	S32	4612
+		ccd23	S32	4612
+#		ccd24	S32	4612
+#		ccd25	S32	4612
+#		ccd26	S32	4612
+#		ccd27	S32	0
+#		ccd28	S32	0
+#		ccd29	S32	0
+#		ccd30	S32	0
+#		ccd31	S32	0
+#		ccd32	S32	0
+#		ccd33	S32	0
+#		ccd34	S32	0
+#		ccd35	S32	0
+	END
+	CHIP.XPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.XPARITY	METADATA
+#		ccd00	S32	1
+#		ccd01	S32	1
+#		ccd02	S32	1
+#		ccd03	S32	1
+#		ccd04	S32	1
+#		ccd05	S32	1
+#		ccd06	S32	1
+#		ccd07	S32	1
+#		ccd08	S32	1
+#		ccd09	S32	1
+#		ccd10	S32	1
+#		ccd11	S32	1
+		ccd12	S32	1
+		ccd13	S32	1
+		ccd14	S32	1
+#		ccd15	S32	1
+#		ccd16	S32	1
+#		ccd17	S32	1
+#		ccd18	S32	1
+#		ccd19	S32	1
+#		ccd20	S32	1
+		ccd21	S32	1
+		ccd22	S32	1
+		ccd23	S32	1
+#		ccd24	S32	1
+#		ccd25	S32	1
+#		ccd26	S32	1
+#		ccd27	S32	1
+#		ccd28	S32	1
+#		ccd29	S32	1
+#		ccd30	S32	1
+#		ccd31	S32	1
+#		ccd32	S32	1
+#		ccd33	S32	1
+#		ccd34	S32	1
+#		ccd35	S32	1
+	END
+	CHIP.YPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.YPARITY	METADATA
+#		ccd00	S32	-1
+#		ccd01	S32	-1
+#		ccd02	S32	-1
+#		ccd03	S32	-1
+#		ccd04	S32	-1
+#		ccd05	S32	-1
+#		ccd06	S32	-1
+#		ccd07	S32	-1
+#		ccd08	S32	-1
+#		ccd09	S32	-1
+#		ccd10	S32	-1
+#		ccd11	S32	-1
+		ccd12	S32	-1
+		ccd13	S32	-1
+		ccd14	S32	-1
+#		ccd15	S32	-1
+#		ccd16	S32	-1
+#		ccd17	S32	-1
+#		ccd18	S32	1
+#		ccd19	S32	1
+#		ccd20	S32	1
+		ccd21	S32	1
+		ccd22	S32	1
+		ccd23	S32	1
+#		ccd24	S32	1
+#		ccd25	S32	1
+#		ccd26	S32	1
+#		ccd27	S32	1
+#		ccd28	S32	1
+#		ccd29	S32	1
+#		ccd30	S32	1
+#		ccd31	S32	1
+#		ccd32	S32	1
+#		ccd33	S32	1
+#		ccd34	S32	1
+#		ccd35	S32	1
+	END
+END
+
+# How to translation PS concepts into database lookups
+DATABASE	METADATA
+	TYPE		dbEntry		TABLE		COLUMN		GIVENDBCOL	GIVENPS
+#	FPA.BIAS	METADATA
+#		TABLE	STR	Camera
+#		COLUMN	STR	gain
+#		chipId	STR	{CHIP.NAME}
+#		cellId	STR	{CELL.NAME}
+#		time	STR	{CELL.TIME}
+#	END
+#	CELL.GAIN	dbEntry		Camera		gain		chipId,cellId	CHIP.NAME,CELL.NAME
+#	CELL.READNOISE	dbEntry		Camera		readNoise	chipId,cellId	CHIP.NAME,CELL.NAME
+
+# A database entry refers to a particular column (COLUMN) in a
+# particular table (TABLE), given certain PS concepts (GIVENPS) that
+# match certain database columns (GIVENDBCOL).
+END
+
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+	CELL.X0		STR	FORTRAN
+	CELL.Y0		STR	FORTRAN
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/mcshort/format_spliced.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/mcshort/format_spliced.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/mcshort/format_spliced.config	(revision 22157)
@@ -0,0 +1,304 @@
+# "mcshort" is a MegaCam camera with only the central six chips --- it's faster than the entire FPA.
+# The spliced MecaCam data is stored in single extensions for each chip
+
+# How to recognise this type
+RULE	METADATA
+	TELESCOP	STR	CFHT 3.6m
+	DETECTOR	STR	MegaCam
+	EXTEND		BOOL	T
+	NEXTEND		S32	36
+END
+
+FILE	METADATA
+	# How to read this data
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	CHIP	# The extensions represent chips
+	FPA.OBS		STR	EXPNUM	# A PHU keyword for unique identifier
+	CHIP.NAME	STR	EXTNAME	# An extension keyword for unique identifie
+END
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# Extension name, chipName:chipType
+#	ccd00		STR	ccd00:CCD42-90
+#	ccd01		STR	ccd01:CCD42-90
+#	ccd02		STR	ccd02:CCD42-90
+#	ccd03		STR	ccd03:CCD42-90
+#	ccd04		STR	ccd04:CCD42-90
+#	ccd05		STR	ccd05:CCD42-90
+#	ccd06		STR	ccd06:CCD42-90
+#	ccd07		STR	ccd07:CCD42-90
+#	ccd08		STR	ccd08:CCD42-90
+#	ccd09		STR	ccd09:CCD42-90
+#	ccd10		STR	ccd10:CCD42-90
+#	ccd11		STR	ccd11:CCD42-90
+	ccd12		STR	ccd12:CCD42-90
+	ccd13		STR	ccd13:CCD42-90
+	ccd14		STR	ccd14:CCD42-90
+#	ccd15		STR	ccd15:CCD42-90
+#	ccd16		STR	ccd16:CCD42-90
+#	ccd17		STR	ccd17:CCD42-90
+#	ccd18		STR	ccd18:CCD42-90
+#	ccd19		STR	ccd19:CCD42-90
+#	ccd20		STR	ccd20:CCD42-90
+	ccd21		STR	ccd21:CCD42-90
+	ccd22		STR	ccd22:CCD42-90
+	ccd23		STR	ccd23:CCD42-90
+#	ccd24		STR	ccd24:CCD42-90
+#	ccd25		STR	ccd25:CCD42-90
+#	ccd26		STR	ccd26:CCD42-90
+#	ccd27		STR	ccd27:CCD42-90
+#	ccd28		STR	ccd28:CCD42-90
+#	ccd29		STR	ccd29:CCD42-90
+#	ccd30		STR	ccd30:CCD42-90
+#	ccd31		STR	ccd31:CCD42-90
+#	ccd32		STR	ccd32:CCD42-90
+#	ccd33		STR	ccd33:CCD42-90
+#	ccd34		STR	ccd34:CCD42-90
+#	ccd35		STR	ccd35:CCD42-90
+END
+
+# Specify the chips
+CHIPS		METADATA
+	# Chip type, cellName:cellType
+	CCD42-90	STR	LeftAmp:left RightAmp:right
+END
+
+# Specify the cells
+CELLS		METADATA
+	left		METADATA
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BSECA
+		CELL.TRIMSEC		STR	TSECA
+		CELL.X0			S32	0
+		CELL.GAIN.SOURCE	STR	HEADER
+		CELL.GAIN		STR	GAINA
+	END
+
+	right		METADATA
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BSECB
+		CELL.TRIMSEC		STR	TSECB
+		CELL.X0			S32	1024
+		CELL.GAIN.SOURCE	STR	HEADER
+		CELL.GAIN		STR	GAINB
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+        FPA.OBS        STR     EXPNUM
+        FPA.AIRMASS     STR     AIRMASS
+        FPA.FILTER      STR     FILTER
+        FPA.POSANGLE    STR     ROTANGLE
+        FPA.RA          STR     RA
+        FPA.DEC         STR     DEC
+        FPA.RADECSYS    STR     RADECSYS
+	FPA.OBSTYPE	STR	OBSTYPE
+	FPA.OBJECT	STR	CMMTOBS
+	FPA.TIME	STR	MJD-OBS
+	FPA.TIMESYS	STR	TIMESYS
+	FPA.ALT		STR	TELALT
+	FPA.AZ		STR	TELAZ
+	CHIP.TEMP	STR	DETTEM
+        CELL.EXPOSURE   STR     EXPTIME
+        CELL.DARKTIME   STR     DARKTIME
+        CELL.READNOISE  STR     RDNOISE
+        CELL.SATURATION STR     SATURATE
+	CELL.TIME	STR	MJD-OBS
+	CELL.TIMESYS	STR	TIMESYS
+        CELL.XBIN       STR     CCDBIN1
+        CELL.YBIN       STR     CCDBIN2
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS        METADATA
+	FPA.TIMESYS		STR	UTC
+	CELL.READDIR		S32	1		# Cell is read in x direction
+        CELL.BAD                S32     0
+	CELL.XPARITY		S32	1
+	CELL.YPARITY		S32	1
+	CELL.Y0			S32	0
+#	PPMERGE.SCALE		F32	1.0
+#	PPMERGE.ZERO		F32	0.0
+	CHIP.X0.DEPEND		STR	CHIP.NAME
+	CHIP.X0		METADATA
+#		ccd00	S32	
+#		ccd01	S32
+#		ccd02	S32
+#		ccd03	S32
+#		ccd04	S32
+#		ccd05	S32
+#		ccd06	S32
+#		ccd07	S32
+#		ccd08	S32
+#		ccd09	S32
+#		ccd10	S32
+#		ccd11	S32
+		ccd12	S32	0
+		ccd13	S32	2048
+		ccd14	S32	4096
+#		ccd15	S32
+#		ccd16	S32
+#		ccd17	S32
+#		ccd18	S32
+#		ccd19	S32
+#		ccd20	S32
+		ccd21	S32	0
+		ccd22	S32	2048
+		ccd23	S32	4096
+#		ccd24	S32
+#		ccd25	S32
+#		ccd26	S32
+#		ccd27	S32
+#		ccd28	S32
+#		ccd29	S32
+#		ccd30	S32
+#		ccd31	S32
+#		ccd32	S32
+#		ccd33	S32
+#		ccd34	S32
+#		ccd35	S32
+	END
+	CHIP.Y0.DEPEND		STR	CHIP.NAME
+	CHIP.Y0		METADATA
+#		ccd00	S32	
+#		ccd01	S32
+#		ccd02	S32
+#		ccd03	S32
+#		ccd04	S32
+#		ccd05	S32
+#		ccd06	S32
+#		ccd07	S32
+#		ccd08	S32
+#		ccd09	S32
+#		ccd10	S32
+#		ccd11	S32
+		ccd12	S32	9223
+		ccd13	S32	9223
+		ccd14	S32	9223
+#		ccd15	S32
+#		ccd16	S32
+#		ccd17	S32
+#		ccd18	S32
+#		ccd19	S32
+#		ccd20	S32
+		ccd21	S32	0
+		ccd22	S32	0
+		ccd23	S32	0
+#		ccd24	S32
+#		ccd25	S32
+#		ccd26	S32
+#		ccd27	S32
+#		ccd28	S32
+#		ccd29	S32
+#		ccd30	S32
+#		ccd31	S32
+#		ccd32	S32
+#		ccd33	S32
+#		ccd34	S32
+#		ccd35	S32
+	END
+	CHIP.XPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.XPARITY	METADATA
+#		ccd00	S32	
+#		ccd01	S32
+#		ccd02	S32
+#		ccd03	S32
+#		ccd04	S32
+#		ccd05	S32
+#		ccd06	S32
+#		ccd07	S32
+#		ccd08	S32
+#		ccd09	S32
+#		ccd10	S32
+#		ccd11	S32
+		ccd12	S32	1
+		ccd13	S32	1
+		ccd14	S32	1
+#		ccd15	S32
+#		ccd16	S32
+#		ccd17	S32
+#		ccd18	S32
+#		ccd19	S32
+#		ccd20	S32
+		ccd21	S32	1
+		ccd22	S32	1
+		ccd23	S32	1
+#		ccd24	S32
+#		ccd25	S32
+#		ccd26	S32
+#		ccd27	S32
+#		ccd28	S32
+#		ccd29	S32
+#		ccd30	S32
+#		ccd31	S32
+#		ccd32	S32
+#		ccd33	S32
+#		ccd34	S32
+#		ccd35	S32
+	END
+	CHIP.YPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.YPARITY	METADATA
+#		ccd00	S32	
+#		ccd01	S32
+#		ccd02	S32
+#		ccd03	S32
+#		ccd04	S32
+#		ccd05	S32
+#		ccd06	S32
+#		ccd07	S32
+#		ccd08	S32
+#		ccd09	S32
+#		ccd10	S32
+#		ccd11	S32
+		ccd12	S32	-1
+		ccd13	S32	-1
+		ccd14	S32	-1
+#		ccd15	S32
+#		ccd16	S32
+#		ccd17	S32
+#		ccd18	S32
+#		ccd19	S32
+#		ccd20	S32
+		ccd21	S32	1
+		ccd22	S32	1
+		ccd23	S32	1
+#		ccd24	S32
+#		ccd25	S32
+#		ccd26	S32
+#		ccd27	S32
+#		ccd28	S32
+#		ccd29	S32
+#		ccd30	S32
+#		ccd31	S32
+#		ccd32	S32
+#		ccd33	S32
+#		ccd34	S32
+#		ccd35	S32
+	END
+END
+
+
+# How to translation PS concepts into database lookups
+DATABASE	METADATA
+	TYPE		dbEntry		TABLE		COLUMN		GIVENDBCOL	GIVENPS
+#	CELL.GAIN	dbEntry		Camera		gain		chipId,cellId	CHIP.NAME,CELL.NAME
+#	CELL.READNOISE	dbEntry		Camera		readNoise	chipId,cellId	CHIP.NAME,CELL.NAME
+
+# A database entry refers to a particular column (COLUMN) in a
+# particular table (TABLE), given certain PS concepts (GIVENPS) that
+# match certain database columns (GIVENDBCOL).
+END		
+
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
+ 
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/mcshort/format_split.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/mcshort/format_split.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/mcshort/format_split.config	(revision 22157)
@@ -0,0 +1,301 @@
+# "mcshort" is a MegaCam camera with only the central six chips --- it's faster than the entire FPA.
+# The spliced MecaCam data is stored in single extensions for each chip
+
+# How to recognise this type
+RULE	METADATA
+	TELESCOP	STR	CFHT 3.6m
+	DETECTOR	STR	MegaCam
+END
+
+FILE	METADATA
+	# How to read this data
+	PHU		STR	CHIP	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	NONE	# The extensions represent chips
+	FPA.OBS		STR	EXPNUM	# A PHU keyword for unique identifier
+	CONTENT		STR	EXTNAME	# Key to the CONTENTS menu
+END
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# Extension name, chip name:component cell
+#	ccd00		STR	ccd00:CCD42-90
+#	ccd01		STR	ccd01:CCD42-90
+#	ccd02		STR	ccd02:CCD42-90
+#	ccd03		STR	ccd03:CCD42-90
+#	ccd04		STR	ccd04:CCD42-90
+#	ccd05		STR	ccd05:CCD42-90
+#	ccd06		STR	ccd06:CCD42-90
+#	ccd07		STR	ccd07:CCD42-90
+#	ccd08		STR	ccd08:CCD42-90
+#	ccd09		STR	ccd09:CCD42-90
+#	ccd10		STR	ccd10:CCD42-90
+#	ccd11		STR	ccd11:CCD42-90
+	ccd12		STR	ccd12:CCD42-90
+	ccd13		STR	ccd13:CCD42-90
+	ccd14		STR	ccd14:CCD42-90
+#	ccd15		STR	ccd15:CCD42-90
+#	ccd16		STR	ccd16:CCD42-90
+#	ccd17		STR	ccd17:CCD42-90
+#	ccd18		STR	ccd18:CCD42-90
+#	ccd19		STR	ccd19:CCD42-90
+#	ccd20		STR	ccd20:CCD42-90
+	ccd21		STR	ccd21:CCD42-90
+	ccd22		STR	ccd22:CCD42-90
+	ccd23		STR	ccd23:CCD42-90
+#	ccd24		STR	ccd24:CCD42-90
+#	ccd25		STR	ccd25:CCD42-90
+#	ccd26		STR	ccd26:CCD42-90
+#	ccd27		STR	ccd27:CCD42-90
+#	ccd28		STR	ccd28:CCD42-90
+#	ccd29		STR	ccd29:CCD42-90
+#	ccd30		STR	ccd30:CCD42-90
+#	ccd31		STR	ccd31:CCD42-90
+#	ccd32		STR	ccd32:CCD42-90
+#	ccd33		STR	ccd33:CCD42-90
+#	ccd34		STR	ccd34:CCD42-90
+#	ccd35		STR	ccd35:CCD42-90
+END
+
+# Specify the chips
+CHIPS		METADATA
+	# Chip type, cellName:cellType
+	CCD42-90	STR	LeftAmp:left RightAmp:right
+END
+
+# Specify the cells
+CELLS		METADATA
+	left		METADATA
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BSECA
+		CELL.TRIMSEC		STR	TSECA
+		CELL.X0			S32	0
+		CELL.GAIN.SOURCE	STR	HEADER
+		CELL.GAIN		STR	GAINA
+	END
+
+	right		METADATA
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BSECB
+		CELL.TRIMSEC		STR	TSECB
+		CELL.X0			S32	1024
+		CELL.GAIN.SOURCE	STR	HEADER
+		CELL.GAIN		STR	GAINB
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+        FPA.OBS        STR     EXPNUM
+        FPA.AIRMASS     STR     AIRMASS
+        FPA.FILTER      STR     FILTER
+        FPA.POSANGLE    STR     ROTANGLE
+        FPA.RA          STR     RA
+        FPA.DEC         STR     DEC
+        FPA.RADECSYS    STR     RADECSYS
+	FPA.OBSTYPE	STR	OBSTYPE
+	FPA.OBJECT	STR	CMMTOBS
+	FPA.TIME	STR	MJD-OBS
+	FPA.TIMESYS	STR	TIMESYS
+	FPA.ALT		STR	TELALT
+	FPA.AZ		STR	TELAZ
+	CHIP.TEMP	STR	DETTEM
+        CELL.EXPOSURE   STR     EXPTIME
+        CELL.DARKTIME   STR     DARKTIME
+        CELL.READNOISE  STR     RDNOISE
+        CELL.SATURATION STR     SATURATE
+	CELL.TIME	STR	MJD-OBS
+	CELL.TIMESYS	STR	TIMESYS
+        CELL.XBIN       STR     CCDBIN1
+        CELL.YBIN       STR     CCDBIN2
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS        METADATA
+	CELL.READDIR		S32	1		# Cell is read in x direction
+        CELL.BAD                S32     0
+	CELL.XPARITY		S32	1
+	CELL.YPARITY		S32	1
+	CELL.Y0			S32	0
+#	PPMERGE.SCALE		F32	1.0
+#	PPMERGE.ZERO		F32	0.0
+	CHIP.X0.DEPEND		STR	CHIP.NAME
+	CHIP.X0		METADATA
+#		ccd00	S32	
+#		ccd01	S32
+#		ccd02	S32
+#		ccd03	S32
+#		ccd04	S32
+#		ccd05	S32
+#		ccd06	S32
+#		ccd07	S32
+#		ccd08	S32
+#		ccd09	S32
+#		ccd10	S32
+#		ccd11	S32
+		ccd12	S32	0
+		ccd13	S32	2048
+		ccd14	S32	4096
+#		ccd15	S32
+#		ccd16	S32
+#		ccd17	S32
+#		ccd18	S32
+#		ccd19	S32
+#		ccd20	S32
+		ccd21	S32	0
+		ccd22	S32	2048
+		ccd23	S32	4096
+#		ccd24	S32
+#		ccd25	S32
+#		ccd26	S32
+#		ccd27	S32
+#		ccd28	S32
+#		ccd29	S32
+#		ccd30	S32
+#		ccd31	S32
+#		ccd32	S32
+#		ccd33	S32
+#		ccd34	S32
+#		ccd35	S32
+	END
+	CHIP.Y0.DEPEND		STR	CHIP.NAME
+	CHIP.Y0		METADATA
+#		ccd00	S32	
+#		ccd01	S32
+#		ccd02	S32
+#		ccd03	S32
+#		ccd04	S32
+#		ccd05	S32
+#		ccd06	S32
+#		ccd07	S32
+#		ccd08	S32
+#		ccd09	S32
+#		ccd10	S32
+#		ccd11	S32
+		ccd12	S32	9223
+		ccd13	S32	9223
+		ccd14	S32	9223
+#		ccd15	S32
+#		ccd16	S32
+#		ccd17	S32
+#		ccd18	S32
+#		ccd19	S32
+#		ccd20	S32
+		ccd21	S32	0
+		ccd22	S32	0
+		ccd23	S32	0
+#		ccd24	S32
+#		ccd25	S32
+#		ccd26	S32
+#		ccd27	S32
+#		ccd28	S32
+#		ccd29	S32
+#		ccd30	S32
+#		ccd31	S32
+#		ccd32	S32
+#		ccd33	S32
+#		ccd34	S32
+#		ccd35	S32
+	END
+	CHIP.XPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.XPARITY	METADATA
+#		ccd00	S32	
+#		ccd01	S32
+#		ccd02	S32
+#		ccd03	S32
+#		ccd04	S32
+#		ccd05	S32
+#		ccd06	S32
+#		ccd07	S32
+#		ccd08	S32
+#		ccd09	S32
+#		ccd10	S32
+#		ccd11	S32
+		ccd12	S32	1
+		ccd13	S32	1
+		ccd14	S32	1
+#		ccd15	S32
+#		ccd16	S32
+#		ccd17	S32
+#		ccd18	S32
+#		ccd19	S32
+#		ccd20	S32
+		ccd21	S32	1
+		ccd22	S32	1
+		ccd23	S32	1
+#		ccd24	S32
+#		ccd25	S32
+#		ccd26	S32
+#		ccd27	S32
+#		ccd28	S32
+#		ccd29	S32
+#		ccd30	S32
+#		ccd31	S32
+#		ccd32	S32
+#		ccd33	S32
+#		ccd34	S32
+#		ccd35	S32
+	END
+	CHIP.YPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.YPARITY	METADATA
+#		ccd00	S32	
+#		ccd01	S32
+#		ccd02	S32
+#		ccd03	S32
+#		ccd04	S32
+#		ccd05	S32
+#		ccd06	S32
+#		ccd07	S32
+#		ccd08	S32
+#		ccd09	S32
+#		ccd10	S32
+#		ccd11	S32
+		ccd12	S32	-1
+		ccd13	S32	-1
+		ccd14	S32	-1
+#		ccd15	S32
+#		ccd16	S32
+#		ccd17	S32
+#		ccd18	S32
+#		ccd19	S32
+#		ccd20	S32
+		ccd21	S32	1
+		ccd22	S32	1
+		ccd23	S32	1
+#		ccd24	S32
+#		ccd25	S32
+#		ccd26	S32
+#		ccd27	S32
+#		ccd28	S32
+#		ccd29	S32
+#		ccd30	S32
+#		ccd31	S32
+#		ccd32	S32
+#		ccd33	S32
+#		ccd34	S32
+#		ccd35	S32
+	END
+END
+
+
+# How to translation PS concepts into database lookups
+DATABASE	METADATA
+	TYPE		dbEntry		TABLE		COLUMN		GIVENDBCOL	GIVENPS
+#	CELL.GAIN	dbEntry		Camera		gain		chipId,cellId	CHIP.NAME,CELL.NAME
+#	CELL.READNOISE	dbEntry		Camera		readNoise	chipId,cellId	CHIP.NAME,CELL.NAME
+
+# A database entry refers to a particular column (COLUMN) in a
+# particular table (TABLE), given certain PS concepts (GIVENPS) that
+# match certain database columns (GIVENDBCOL).
+END		
+
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
+ 
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/.cvsignore	(revision 22157)
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/Makefile.am	(revision 22157)
@@ -0,0 +1,27 @@
+
+installdir = $(datadir)/ippconfig/megacam
+
+install_files = \
+	dvo.config \
+	dvo.layout \
+	camera.config \
+	format_raw.config \
+	format_mef.config \
+	format_pre_03Am04_raw.config \
+	format_pre_03Am04_mef.config \
+	format_split.config \
+	ppImage.config \
+	ppMerge.config \
+	psastro.config \
+	psphot.config \
+	pswarp.config \
+	rejections.config
+
+install_DATA = $(install_files)
+
+install-data-hook:
+	chmod 0755 $(installdir)
+
+EXTRA_DIST = $(install_files)
+
+ACLOCAL_AMFLAGS = -I m4
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/camera.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/camera.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/camera.config	(revision 22157)
@@ -0,0 +1,149 @@
+# Camera configuration file for MegaCam: describes the camera
+
+# File formats that we know about
+FORMATS         METADATA
+        MEF 	STR     megacam/format_mef.config
+        RAW     STR     megacam/format_raw.config
+        MEF_03 	STR     megacam/format_pre_03Am04_mef.config
+        RAW_03  STR     megacam/format_pre_03Am04_raw.config
+        SPLIT   STR     megacam/format_split.config
+END
+
+# Description of camera --- all the chips and the cells that comprise them
+FPA     METADATA
+        ccd00   STR     LeftAmp RightAmp
+        ccd01   STR     LeftAmp RightAmp
+        ccd02   STR     LeftAmp RightAmp
+        ccd03   STR     LeftAmp RightAmp
+        ccd04   STR     LeftAmp RightAmp
+        ccd05   STR     LeftAmp RightAmp
+        ccd06   STR     LeftAmp RightAmp
+        ccd07   STR     LeftAmp RightAmp
+        ccd08   STR     LeftAmp RightAmp
+        ccd09   STR     LeftAmp RightAmp
+        ccd10   STR     LeftAmp RightAmp
+        ccd11   STR     LeftAmp RightAmp
+        ccd12   STR     LeftAmp RightAmp
+        ccd13   STR     LeftAmp RightAmp
+        ccd14   STR     LeftAmp RightAmp
+        ccd15   STR     LeftAmp RightAmp
+        ccd16   STR     LeftAmp RightAmp
+        ccd17   STR     LeftAmp RightAmp
+        ccd18   STR     LeftAmp RightAmp
+        ccd19   STR     LeftAmp RightAmp
+        ccd20   STR     LeftAmp RightAmp
+        ccd21   STR     LeftAmp RightAmp
+        ccd22   STR     LeftAmp RightAmp
+        ccd23   STR     LeftAmp RightAmp
+        ccd24   STR     LeftAmp RightAmp
+        ccd25   STR     LeftAmp RightAmp
+        ccd26   STR     LeftAmp RightAmp
+        ccd27   STR     LeftAmp RightAmp
+        ccd28   STR     LeftAmp RightAmp
+        ccd29   STR     LeftAmp RightAmp
+        ccd30   STR     LeftAmp RightAmp
+        ccd31   STR     LeftAmp RightAmp
+        ccd32   STR     LeftAmp RightAmp
+        ccd33   STR     LeftAmp RightAmp
+        ccd34   STR     LeftAmp RightAmp
+        ccd35   STR     LeftAmp RightAmp
+END
+
+# move this elsewhere?  we need a lookup table to go from filter ID to abstract name
+FILTER.ID       METADATA
+   u        STR   u.MP9301     
+   g        STR   g.MP9401     
+   r        STR   r.MP9601     
+   i        STR   i.MP9701     
+   z        MULTI
+   z        STR   z.MP9801     
+   z        STR   Zp           
+   z        STR   Zprime       
+
+   Ha       MULTI
+   Ha       STR   Ha.MP7605    
+   Ha       STR   Halpha       
+   Ha       STR   Haalpha.on   
+   HaOff    STR   HaOFF.MP7604 
+   	              		
+   CN       MULTI
+   CN       STR   CN.MP780     
+   CN       STR   cn.MP7803    
+   CN       STR   CN.MP7803    
+   	              		
+   TiO      MULTI
+   TiO      STR   TiO.MP77     
+   TiO      STR   tio.MP7701   
+   TiO      STR   TiO.MP7701   
+   NB920    STR   NB920        
+   	              		
+   B2F      STR   B2F          
+   Bj       STR   Bj           
+   Vj       STR   Vj           
+   Rj       STR   Rj           
+   Ij       STR   Ij           
+   Hb       STR   Hb           
+   HbOff    STR   HbOff        
+END
+
+# Table of strings to use for the class identifier (see ippdb) according to the file level.
+CLASSID         METADATA
+        FPA     STR     fpa
+        CHIP    STR     {CHIP.NAME}
+        CELL    STR     {CHIP.NAME}:{CELL.NAME}
+        fpa     STR     fpa
+        chip    STR     {CHIP.NAME}
+        cell    STR     {CHIP.NAME}:{CELL.NAME}
+END
+
+DVO.CAMERADIR	STR	megacam		# Camera directory for DVO
+
+# convert supplied FPA.OBSTYPE values to abstract exptype names
+OBSTYPE.TABLE METADATA
+  bias     STR BIAS
+  zero     STR BIAS
+  dark     STR DARK
+  flat     STR SKYFLAT
+  skyflat  STR SKYFLAT
+  domeflat STR DOMEFLAT
+  object   STR OBJECT
+  science  STR OBJECT
+END
+
+# Recipe options
+RECIPES         METADATA
+        # Other recipes
+        PSPHOT          STR     megacam/psphot.config           # psphot details
+        PSASTRO         STR     megacam/psastro.config          # psastro details
+	PSWARP		STR	megacam/pswarp.config		# pswarp details
+        PPIMAGE         STR     megacam/ppImage.config          # ppImage recipe
+	REJECTIONS      STR     megacam/rejections.config       # rejection recipe
+	PPMERGE		STR	megacam/ppMerge.config		# ppMerge recipe
+END
+
+# reduction classes (recipes which are grouped together)
+REDUCTION	STR	recipes/reductionClasses.mdc
+
+FITSTYPES       STR     recipes/fitstypes.mdc
+
+FILERULES	STR	recipes/filerules-mef.mdc		# File rules appropriate for MEF format
+
+EXTNAME.RULES METADATA
+  CMF.HEAD   STR {CHIP.NAME}.hdr
+  CMF.DATA   STR {CHIP.NAME}.psf # use .PSF and .EXT?
+  CMF.XSRC STR {CHIP.NAME}.xsrc # use .PSF and .EXT?
+  CMF.XFIT STR {CHIP.NAME}.xfit # use .PSF and .EXT?
+
+  PSF.HEAD   STR {CHIP.NAME}.hdr
+  PSF.TABLE  STR {CHIP.NAME}.psf_model
+  PSF.RESID  STR {CHIP.NAME}.psf_resid
+
+  REF.ASTROM STR {CHIP.NAME}.ref_astrom
+END
+
+BLANK.HEADERS	METADATA
+	FPA.TIME	STR	MJD-OBS
+	FPA.EXPOSURE	STR	EXPTIME
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.NAME	STR	EXPNUM
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/dvo.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/dvo.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/dvo.config	(revision 22157)
@@ -0,0 +1,67 @@
+
+# location of DVO database tables
+CATDIR			/data/alala.0/eugene/isp/catdir
+
+# keywords used by DVO to interpret the headers
+
+# keyword abstractions:
+#DATE-KEYWORD		DATE-OBS
+#DATE-MODE		yyyy-mm-dd
+#UT-KEYWORD		UTC-OBS
+JD-KEYWORD		NONE
+MJD-KEYWORD		MJD-OBS
+
+# other keyword abstractions
+EXPTIME-KEYWORD		EXPTIME
+AIRMASS-KEYWORD		AIRMASS
+CCDNUM-KEYWORD		EXTNAME
+ST-KEYWORD		NONE
+OBSERVATORY-LATITUDE	NONE
+OBSERVATORY-LONGITUDE	NONE
+SUBPIX_DATAFILE		NONE
+
+# instrumental magnitude range for calibration mode 
+CAL_INSTMAG_MAX		0
+CAL_INSTMAG_MIN		0
+
+# exclude overscan region from the dB image boundaries
+XOVERSCAN		0
+YOVERSCAN		0
+
+# only upload stars within region; a value of 0 means ignore the limit
+ADDSTAR_XMIN		0
+ADDSTAR_XMAX		0
+ADDSTAR_YMIN		0
+ADDSTAR_YMAX		0
+
+# exclude stars with SN > SNLIMIT (ADDSTAR_SNLIMIT overrides old name MIN_SN_FSTAT)
+ADDSTAR_SNLIMIT		0
+
+# correlation radius (arcseconds)
+ADDSTAR_RADIUS		1.0
+
+# scaled correlation radius 
+ADDSTAR_NSIGMA		0
+
+IMAGE_SCATTER           0.075  # mark images POOR if stdev(Mcal) > IMAGE_SCATTER
+STAR_SCATTER            0.005  # mark stars POOR if stdev(Mrel) > STAR_SCATTER
+IMAGE_OFFSET            0.100  # mark images POOR if abs(delta(Mcal)) > IMAGE_OFFSET
+STAR_CHISQ              10.0   # mark stars POOR if Xm > STAR_CHISQ
+STAR_TOOFEW              3     # mark star FEW if N(good) < STAR_TOOFEW
+IMAGE_TOOFEW            10     # mark image FEW if N(good) < IMAGE_TOOFEW
+IMAGE_GOOD_FRACTION     0.05   # mark image FEW if N(good) < IMAGE_GOOD_FRACTION * Nstars
+
+SCATTER_LIM             15.0
+MAG_LIM                 20.0   # select stars brighter than this for relphot analysis
+SIGMA_LIM               0.015  # select measurements with dM < SIGMA_LIM for relphot analysis
+# INST_MAG_MIN         -17     # optional constraints on magnitude ranges
+# INST_MAG_MAX         -13     # optional constraints on magnitude ranges
+
+RELPHOT_GRID_BINNING    256
+RELPHOT_GRID_X 6
+RELPHOT_GRID_Y 14
+
+PM_DT_MIN               0.25
+PM_TOOFEW               4
+POS_TOOFEW              1
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/dvo.layout
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/dvo.layout	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/dvo.layout	(revision 22157)
@@ -0,0 +1,58 @@
+# this file defines the layout of the mosaic imager:
+
+# NCCD 40  # this needs work: sometimes 36, sometimes 40
+NCCD 36
+NAXIS1 2112
+NAXIS2 4644
+
+MOSAIC_X 11
+MOSAIC_Y  4
+
+# lines need to contain:
+
+CHIPID_KEYWORD EXTNAME
+
+# ID     CHIPID xoffset yoffset xflip yflip datasec         biassec            Xo      Yo      theta
+CCD.0     ccd00       1       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.1     ccd01       2       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.2     ccd02       3       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.3     ccd03       4       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.4     ccd04       5       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.5     ccd05       6       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.6     ccd06       7       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.7     ccd07       8       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.8     ccd08       9       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.9     ccd09       1       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.10    ccd10       2       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.11    ccd11       3       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.12    ccd12       4       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.13    ccd13       5       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.14    ccd14       6       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.15    ccd15       7       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.16    ccd16       8       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.17    ccd17       9       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.18    ccd18       1       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.19    ccd19       2       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.20    ccd20       3       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.21    ccd21       4       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.22    ccd22       5       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.23    ccd23       6       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.24    ccd24       7       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.25    ccd25       8       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.26    ccd26       9       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.27    ccd27       1       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.28    ccd28       2       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.29    ccd29       3       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.30    ccd30       4       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.31    ccd31       5       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.32    ccd32       6       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.33    ccd33       7       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.34    ccd34       8       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.35    ccd35       9       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.36    ccd36       0       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.37    ccd37      10       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.38    ccd38       0       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.39    ccd39      10       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+
+# can we use header to find biassec
+USE_BIASSEC 1
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/format_mef.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/format_mef.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/format_mef.config	(revision 22157)
@@ -0,0 +1,308 @@
+# The spliced MecaCam data is stored in single extensions for each chip
+
+# How to recognise this type
+RULE	METADATA
+	TELESCOP	STR	CFHT 3.6m # added at start of 03Am04
+	DETECTOR	STR	MegaCam
+	NAXIS           S32     0
+	EXTEND		BOOL	T
+        HISTORY         STR     imsplice: Splicing results in all detectors as if read from A amplifier
+END
+
+FILE	METADATA
+	# How to read this data
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	CHIP	# The extensions represent chips
+	FPA.OBS		STR	EXPNUM	# A PHU keyword for unique identifier
+	# CHIP.NAME	STR	EXTNAME	# An extension keyword for unique identifie
+END
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# Extension name, chipName:chipType
+	ccd00		STR	ccd00:CCD42-90
+	ccd01		STR	ccd01:CCD42-90
+	ccd02		STR	ccd02:CCD42-90
+	ccd03		STR	ccd03:CCD42-90
+	ccd04		STR	ccd04:CCD42-90
+	ccd05		STR	ccd05:CCD42-90
+	ccd06		STR	ccd06:CCD42-90
+	ccd07		STR	ccd07:CCD42-90
+	ccd08		STR	ccd08:CCD42-90
+	ccd09		STR	ccd09:CCD42-90
+	ccd10		STR	ccd10:CCD42-90
+	ccd11		STR	ccd11:CCD42-90
+	ccd12		STR	ccd12:CCD42-90
+	ccd13		STR	ccd13:CCD42-90
+	ccd14		STR	ccd14:CCD42-90
+	ccd15		STR	ccd15:CCD42-90
+	ccd16		STR	ccd16:CCD42-90
+	ccd17		STR	ccd17:CCD42-90
+	ccd18		STR	ccd18:CCD42-90
+	ccd19		STR	ccd19:CCD42-90
+	ccd20		STR	ccd20:CCD42-90
+	ccd21		STR	ccd21:CCD42-90
+	ccd22		STR	ccd22:CCD42-90
+	ccd23		STR	ccd23:CCD42-90
+	ccd24		STR	ccd24:CCD42-90
+	ccd25		STR	ccd25:CCD42-90
+	ccd26		STR	ccd26:CCD42-90
+	ccd27		STR	ccd27:CCD42-90
+	ccd28		STR	ccd28:CCD42-90
+	ccd29		STR	ccd29:CCD42-90
+	ccd30		STR	ccd30:CCD42-90
+	ccd31		STR	ccd31:CCD42-90
+	ccd32		STR	ccd32:CCD42-90
+	ccd33		STR	ccd33:CCD42-90
+	ccd34		STR	ccd34:CCD42-90
+	ccd35		STR	ccd35:CCD42-90
+END
+
+# Specify the chips
+CHIPS		METADATA
+	# Chip type, cellName:cellType
+	CCD42-90	STR	LeftAmp:left RightAmp:right
+END
+
+
+# Specify the cells
+CELLS		METADATA
+	# Cell type, concepts to use
+	left		METADATA
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BSECA
+		CELL.TRIMSEC		STR	TSECA
+		CELL.X0			S32	0
+		CELL.GAIN.SOURCE	STR	HEADER
+		CELL.GAIN		STR	GAINA
+	END
+
+	right		METADATA
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BSECB
+		CELL.TRIMSEC		STR	TSECB
+		CELL.X0			S32	1024
+		CELL.GAIN.SOURCE	STR	HEADER
+		CELL.GAIN		STR	GAINB
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+        FPA.AIRMASS     STR     AIRMASS
+        FPA.FILTERID    STR     FILTER
+        FPA.FILTER      STR     FILTER
+	FPA.OBSTYPE	STR	OBSTYPE
+	FPA.OBJECT	STR	OBJECT
+	FPA.TIME	STR	MJD-OBS
+	FPA.TIMESYS	STR	TIMESYS
+        FPA.RA          STR     RA
+        FPA.DEC         STR     DEC
+        FPA.RADECSYS    STR     RADECSYS
+        FPA.POSANGLE    STR     ROTANGLE # added at start of 03Am04
+	FPA.FOCUS	STR	TELFOCUS # added at start of 03Am04
+	FPA.ALT		STR	TELALT	 # added at start of 03Am04
+	FPA.AZ		STR	TELAZ	 # added at start of 03Am04
+	FPA.TEMP	STR	DETTEM
+	FPA.EXPOSURE	STR	EXPTIME
+	CHIP.TEMP	STR	DETTEM
+        CELL.EXPOSURE   STR     EXPTIME
+        CELL.DARKTIME   STR     DARKTIME
+        CELL.READNOISE  STR     RDNOISE
+	CELL.TIME	STR	MJD-OBS
+	CELL.TIMESYS	STR	TIMESYS
+        CELL.XBIN       STR     CCDBIN1
+        CELL.YBIN       STR     CCDBIN2
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS        METADATA
+	FPA.TELESCOPE		STR	CFHT
+	FPA.INSTRUMENT		STR	MEGACAM		# this value is used in ipprc.config to find the recipes
+	FPA.DETECTOR	        STR	MEGACAM
+	CHIP.XSIZE		S32	2048
+	CHIP.YSIZE		S32	4612
+	CELL.XSIZE		S32	1024
+	CELL.YSIZE		S32	4612
+	CELL.XWINDOW		S32	0
+	CELL.YWINDOW		S32	0
+	CELL.READDIR		S32	1		# Cell is read in x direction
+        CELL.SATURATION		F32	50000.0
+        CELL.BAD                F32     0
+	CELL.XPARITY		S32	1
+	CELL.YPARITY		S32	1
+	CELL.Y0			S32	0
+	CHIP.X0.DEPEND		STR	CHIP.NAME
+	CHIP.X0		METADATA
+		ccd00	S32	0
+		ccd01	S32	2048
+		ccd02	S32	4096
+		ccd03	S32	6144
+		ccd04	S32	8192
+		ccd05	S32	10240
+		ccd06	S32	12288
+		ccd07	S32	14336
+		ccd08	S32	16384
+		ccd09	S32	0
+		ccd10	S32	2048
+		ccd11	S32	4096
+		ccd12	S32	6144
+		ccd13	S32	8192
+		ccd14	S32	10240
+		ccd15	S32	12288
+		ccd16	S32	14336
+		ccd17	S32	16384
+		ccd18	S32    -2048
+		ccd19	S32	0
+		ccd20	S32	2048 
+		ccd21	S32	4096 
+		ccd22	S32	6144 
+		ccd23	S32	8192 
+		ccd24	S32	10240
+		ccd25	S32	12288
+		ccd26	S32	14336
+		ccd27	S32    -2048
+		ccd28	S32	0
+		ccd29	S32	2048 
+		ccd30	S32	4096 
+		ccd31	S32	6144 
+		ccd32	S32	8192 
+		ccd33	S32	10240
+		ccd34	S32	12288
+		ccd35	S32	14336
+	END
+	CHIP.Y0.DEPEND		STR	CHIP.NAME
+	CHIP.Y0		METADATA
+		ccd00	S32	18447
+		ccd01	S32	18447
+		ccd02	S32	18447
+		ccd03	S32	18447
+		ccd04	S32	18447
+		ccd05	S32	18447
+		ccd06	S32	18447
+		ccd07	S32	18447
+		ccd08	S32	18447
+		ccd09	S32	13835
+		ccd10	S32	13835
+		ccd11	S32	13835
+		ccd12	S32	13835
+		ccd13	S32	13835
+		ccd14	S32	13835
+		ccd15	S32	13835
+		ccd16	S32	13835
+		ccd17	S32	13835
+		ccd18	S32	4612
+		ccd19	S32	4612
+		ccd20	S32	4612
+		ccd21	S32	4612
+		ccd22	S32	4612
+		ccd23	S32	4612
+		ccd24	S32	4612
+		ccd25	S32	4612
+		ccd26	S32	4612
+		ccd27	S32	0
+		ccd28	S32	0
+		ccd29	S32	0
+		ccd30	S32	0
+		ccd31	S32	0
+		ccd32	S32	0
+		ccd33	S32	0
+		ccd34	S32	0
+		ccd35	S32	0
+	END
+	CHIP.XPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.XPARITY	METADATA
+		ccd00	S32	-1
+		ccd01	S32	-1
+		ccd02	S32	-1
+		ccd03	S32	-1
+		ccd04	S32	-1
+		ccd05	S32	-1
+		ccd06	S32	-1
+		ccd07	S32	-1
+		ccd08	S32	-1
+		ccd09	S32	-1
+		ccd10	S32	-1
+		ccd11	S32	-1
+		ccd12	S32	-1
+		ccd13	S32	-1
+		ccd14	S32	-1
+		ccd15	S32	-1
+		ccd16	S32	-1
+		ccd17	S32	-1
+		ccd18	S32	1
+		ccd19	S32	1
+		ccd20	S32	1
+		ccd21	S32	1
+		ccd22	S32	1
+		ccd23	S32	1
+		ccd24	S32	1
+		ccd25	S32	1
+		ccd26	S32	1
+		ccd27	S32	1
+		ccd28	S32	1
+		ccd29	S32	1
+		ccd30	S32	1
+		ccd31	S32	1
+		ccd32	S32	1
+		ccd33	S32	1
+		ccd34	S32	1
+		ccd35	S32	1
+	END
+	CHIP.YPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.YPARITY	METADATA
+		ccd00	S32	-1
+		ccd01	S32	-1
+		ccd02	S32	-1
+		ccd03	S32	-1
+		ccd04	S32	-1
+		ccd05	S32	-1
+		ccd06	S32	-1
+		ccd07	S32	-1
+		ccd08	S32	-1
+		ccd09	S32	-1
+		ccd10	S32	-1
+		ccd11	S32	-1
+		ccd12	S32	-1
+		ccd13	S32	-1
+		ccd14	S32	-1
+		ccd15	S32	-1
+		ccd16	S32	-1
+		ccd17	S32	-1
+		ccd18	S32	1
+		ccd19	S32	1
+		ccd20	S32	1
+		ccd21	S32	1
+		ccd22	S32	1
+		ccd23	S32	1
+		ccd24	S32	1
+		ccd25	S32	1
+		ccd26	S32	1
+		ccd27	S32	1
+		ccd28	S32	1
+		ccd29	S32	1
+		ccd30	S32	1
+		ccd31	S32	1
+		ccd32	S32	1
+		ccd33	S32	1
+		ccd34	S32	1
+		ccd35	S32	1
+	END
+END
+
+
+# How to translation PS concepts into database lookups
+DATABASE	METADATA
+#	CELL.GAIN	STR	SELECT gain FROM camera WHERE chip_id = '{CHIP.NAME}' AND cell_id = '{CELL.NAME}'
+END		
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
+ 
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/format_pre_03Am04_mef.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/format_pre_03Am04_mef.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/format_pre_03Am04_mef.config	(revision 22157)
@@ -0,0 +1,313 @@
+# The spliced MecaCam data is stored in single extensions for each chip
+
+# How to recognise this type
+RULE	METADATA
+	DETECTOR	STR	MegaCam
+	NAXIS           S32     0
+	EXTEND		BOOL	T
+        HISTORY         STR     imsplice: Splicing results in all detectors as if read from A amplifier
+END
+
+FILE	METADATA
+	# How to read this data
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	CHIP	# The extensions represent chips
+	FPA.OBS		STR	EXPNUM	# A PHU keyword for unique identifier
+	# CHIP.NAME	STR	EXTNAME	# An extension keyword for unique identifie
+END
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# Extension name, chipName:chipType
+	ccd00		STR	ccd00:CCD42-90
+	ccd01		STR	ccd01:CCD42-90
+	ccd02		STR	ccd02:CCD42-90
+	ccd03		STR	ccd03:CCD42-90
+	ccd04		STR	ccd04:CCD42-90
+	ccd05		STR	ccd05:CCD42-90
+	ccd06		STR	ccd06:CCD42-90
+	ccd07		STR	ccd07:CCD42-90
+	ccd08		STR	ccd08:CCD42-90
+	ccd09		STR	ccd09:CCD42-90
+	ccd10		STR	ccd10:CCD42-90
+	ccd11		STR	ccd11:CCD42-90
+	ccd12		STR	ccd12:CCD42-90
+	ccd13		STR	ccd13:CCD42-90
+	ccd14		STR	ccd14:CCD42-90
+	ccd15		STR	ccd15:CCD42-90
+	ccd16		STR	ccd16:CCD42-90
+	ccd17		STR	ccd17:CCD42-90
+	ccd18		STR	ccd18:CCD42-90
+	ccd19		STR	ccd19:CCD42-90
+	ccd20		STR	ccd20:CCD42-90
+	ccd21		STR	ccd21:CCD42-90
+	ccd22		STR	ccd22:CCD42-90
+	ccd23		STR	ccd23:CCD42-90
+	ccd24		STR	ccd24:CCD42-90
+	ccd25		STR	ccd25:CCD42-90
+	ccd26		STR	ccd26:CCD42-90
+	ccd27		STR	ccd27:CCD42-90
+	ccd28		STR	ccd28:CCD42-90
+	ccd29		STR	ccd29:CCD42-90
+	ccd30		STR	ccd30:CCD42-90
+	ccd31		STR	ccd31:CCD42-90
+	ccd32		STR	ccd32:CCD42-90
+	ccd33		STR	ccd33:CCD42-90
+	ccd34		STR	ccd34:CCD42-90
+	ccd35		STR	ccd35:CCD42-90
+END
+
+# Specify the chips
+CHIPS		METADATA
+	# Chip type, cellName:cellType
+	CCD42-90	STR	LeftAmp:left RightAmp:right
+END
+
+
+# Specify the cells
+CELLS		METADATA
+	# Cell type, concepts to use
+	left		METADATA
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BSECA
+		CELL.TRIMSEC		STR	TSECA
+		CELL.X0			S32	0
+		CELL.GAIN.SOURCE	STR	HEADER
+		CELL.GAIN		STR	GAINA
+	END
+
+	right		METADATA
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BSECB
+		CELL.TRIMSEC		STR	TSECB
+		CELL.X0			S32	1024
+		CELL.GAIN.SOURCE	STR	HEADER
+		CELL.GAIN		STR	GAINB
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+        FPA.AIRMASS     STR     AIRMASS
+        FPA.FILTERID    STR     FILTER
+        FPA.FILTER      STR     FILTER
+	FPA.OBSTYPE	STR	OBSTYPE
+	FPA.OBJECT	STR	OBJECT
+	FPA.TIME	STR	MJD-OBS
+	FPA.TIMESYS	STR	TIMESYS
+        FPA.RA          STR     RA
+        FPA.DEC         STR     DEC
+        FPA.RADECSYS    STR     RADECSYS
+	FPA.TEMP	STR	DETTEM
+	FPA.EXPOSURE	STR	EXPTIME
+	CHIP.TEMP	STR	DETTEM
+        CELL.EXPOSURE   STR     EXPTIME
+        CELL.DARKTIME   STR     DARKTIME
+        CELL.READNOISE  STR     RDNOISE
+	CELL.TIME	STR	MJD-OBS
+	CELL.TIMESYS	STR	TIMESYS
+        CELL.XBIN       STR     CCDBIN1
+        CELL.YBIN       STR     CCDBIN2
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS        METADATA
+	FPA.TELESCOPE		STR	CFHT
+	FPA.INSTRUMENT		STR	MEGACAM		# this value is used in ipprc.config to find the recipes
+	FPA.DETECTOR	        STR	MEGACAM
+	FPA.POSANGLE		F32	0.0     # not reported until 03Am04
+	FPA.FOCUS		F32	0.0     # not reported until 03Am04
+	FPA.ALT			F32	0.0     # not reported until 03Am04
+	FPA.AZ			F32	0.0     # not reported until 03Am04
+	CHIP.XSIZE		S32	2048
+	CHIP.YSIZE		S32	4612
+	CELL.XSIZE		S32	1024
+	CELL.YSIZE		S32	4612
+	CELL.XWINDOW		S32	0
+	CELL.YWINDOW		S32	0
+	CELL.READDIR		S32	1		# Cell is read in x direction
+        CELL.SATURATION		F32	50000.0
+        CELL.BAD                F32     0
+	CELL.XPARITY		S32	1
+	CELL.YPARITY		S32	1
+	CELL.Y0			S32	0
+	CHIP.X0.DEPEND		STR	CHIP.NAME
+	CHIP.X0		METADATA
+		ccd00	S32	0
+		ccd01	S32	2048
+		ccd02	S32	4096
+		ccd03	S32	6144
+		ccd04	S32	8192
+		ccd05	S32	10240
+		ccd06	S32	12288
+		ccd07	S32	14336
+		ccd08	S32	16384
+		ccd09	S32	0
+		ccd10	S32	2048
+		ccd11	S32	4096
+		ccd12	S32	6144
+		ccd13	S32	8192
+		ccd14	S32	10240
+		ccd15	S32	12288
+		ccd16	S32	14336
+		ccd17	S32	16384
+		ccd18	S32    -2048
+		ccd19	S32	0
+		ccd20	S32	2048 
+		ccd21	S32	4096 
+		ccd22	S32	6144 
+		ccd23	S32	8192 
+		ccd24	S32	10240
+		ccd25	S32	12288
+		ccd26	S32	14336
+		ccd27	S32    -2048
+		ccd28	S32	0
+		ccd29	S32	2048 
+		ccd30	S32	4096 
+		ccd31	S32	6144 
+		ccd32	S32	8192 
+		ccd33	S32	10240
+		ccd34	S32	12288
+		ccd35	S32	14336
+	END
+	CHIP.Y0.DEPEND		STR	CHIP.NAME
+	CHIP.Y0		METADATA
+		ccd00	S32	18447
+		ccd01	S32	18447
+		ccd02	S32	18447
+		ccd03	S32	18447
+		ccd04	S32	18447
+		ccd05	S32	18447
+		ccd06	S32	18447
+		ccd07	S32	18447
+		ccd08	S32	18447
+		ccd09	S32	13835
+		ccd10	S32	13835
+		ccd11	S32	13835
+		ccd12	S32	13835
+		ccd13	S32	13835
+		ccd14	S32	13835
+		ccd15	S32	13835
+		ccd16	S32	13835
+		ccd17	S32	13835
+		ccd18	S32	4612
+		ccd19	S32	4612
+		ccd20	S32	4612
+		ccd21	S32	4612
+		ccd22	S32	4612
+		ccd23	S32	4612
+		ccd24	S32	4612
+		ccd25	S32	4612
+		ccd26	S32	4612
+		ccd27	S32	0
+		ccd28	S32	0
+		ccd29	S32	0
+		ccd30	S32	0
+		ccd31	S32	0
+		ccd32	S32	0
+		ccd33	S32	0
+		ccd34	S32	0
+		ccd35	S32	0
+	END
+	CHIP.XPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.XPARITY	METADATA
+		ccd00	S32	-1
+		ccd01	S32	-1
+		ccd02	S32	-1
+		ccd03	S32	-1
+		ccd04	S32	-1
+		ccd05	S32	-1
+		ccd06	S32	-1
+		ccd07	S32	-1
+		ccd08	S32	-1
+		ccd09	S32	-1
+		ccd10	S32	-1
+		ccd11	S32	-1
+		ccd12	S32	-1
+		ccd13	S32	-1
+		ccd14	S32	-1
+		ccd15	S32	-1
+		ccd16	S32	-1
+		ccd17	S32	-1
+		ccd18	S32	1
+		ccd19	S32	1
+		ccd20	S32	1
+		ccd21	S32	1
+		ccd22	S32	1
+		ccd23	S32	1
+		ccd24	S32	1
+		ccd25	S32	1
+		ccd26	S32	1
+		ccd27	S32	1
+		ccd28	S32	1
+		ccd29	S32	1
+		ccd30	S32	1
+		ccd31	S32	1
+		ccd32	S32	1
+		ccd33	S32	1
+		ccd34	S32	1
+		ccd35	S32	1
+	END
+	CHIP.YPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.YPARITY	METADATA
+		ccd00	S32	-1
+		ccd01	S32	-1
+		ccd02	S32	-1
+		ccd03	S32	-1
+		ccd04	S32	-1
+		ccd05	S32	-1
+		ccd06	S32	-1
+		ccd07	S32	-1
+		ccd08	S32	-1
+		ccd09	S32	-1
+		ccd10	S32	-1
+		ccd11	S32	-1
+		ccd12	S32	-1
+		ccd13	S32	-1
+		ccd14	S32	-1
+		ccd15	S32	-1
+		ccd16	S32	-1
+		ccd17	S32	-1
+		ccd18	S32	1
+		ccd19	S32	1
+		ccd20	S32	1
+		ccd21	S32	1
+		ccd22	S32	1
+		ccd23	S32	1
+		ccd24	S32	1
+		ccd25	S32	1
+		ccd26	S32	1
+		ccd27	S32	1
+		ccd28	S32	1
+		ccd29	S32	1
+		ccd30	S32	1
+		ccd31	S32	1
+		ccd32	S32	1
+		ccd33	S32	1
+		ccd34	S32	1
+		ccd35	S32	1
+	END
+END
+
+
+# How to translation PS concepts into database lookups
+DATABASE	METADATA
+	TYPE		dbEntry		TABLE		COLUMN		GIVENDBCOL	GIVENPS
+#	CELL.GAIN	dbEntry		Camera		gain		chipId,cellId	CHIP.NAME,CELL.NAME
+#	CELL.READNOISE	dbEntry		Camera		readNoise	chipId,cellId	CHIP.NAME,CELL.NAME
+
+# A database entry refers to a particular column (COLUMN) in a
+# particular table (TABLE), given certain PS concepts (GIVENPS) that
+# match certain database columns (GIVENDBCOL).
+END		
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
+ 
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/format_pre_03Am04_raw.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/format_pre_03Am04_raw.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/format_pre_03Am04_raw.config	(revision 22157)
@@ -0,0 +1,347 @@
+# "mcshort" is a MegaCam camera with only the central six chips --- it's faster than the entire FPA.
+# The raw MegaCam data comes off the telescope with each of the chips stored in extensions of a MEF file.
+
+# How to identify this type
+RULE	METADATA
+	DETECTOR	STR	MegaCam
+	NAXIS           S32     0
+	EXTEND		BOOL	T
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	CELL	# The extensions represent cells
+	FPA.OBS		STR	EXPNUM	# A PHU keyword for unique identifier within the hierarchy level
+END
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# Extension name, chip name:type
+	amp00		STR	ccd00:LeftAmp:left
+	amp01		STR	ccd00:RightAmp:right
+	amp02		STR	ccd01:LeftAmp:left
+	amp03		STR	ccd01:RightAmp:right
+	amp04		STR	ccd02:LeftAmp:left
+	amp05		STR	ccd02:RightAmp:right
+	amp06		STR	ccd03:LeftAmp:left
+	amp07		STR	ccd03:RightAmp:right
+	amp08		STR	ccd04:LeftAmp:left
+	amp09		STR	ccd04:RightAmp:right
+	amp10		STR	ccd05:LeftAmp:left
+	amp11		STR	ccd05:RightAmp:right
+	amp12		STR	ccd06:LeftAmp:left
+	amp13		STR	ccd06:RightAmp:right
+	amp14		STR	ccd07:LeftAmp:left
+	amp15		STR	ccd07:RightAmp:right
+	amp16		STR	ccd08:LeftAmp:left
+	amp17		STR	ccd08:RightAmp:right
+	amp18		STR	ccd09:LeftAmp:left
+	amp19		STR	ccd09:RightAmp:right
+	amp20		STR	ccd10:LeftAmp:left
+	amp21		STR	ccd10:RightAmp:right
+	amp22		STR	ccd11:LeftAmp:left
+	amp23		STR	ccd11:RightAmp:right
+	amp24		STR	ccd12:LeftAmp:left
+	amp25		STR	ccd12:RightAmp:right
+	amp26		STR	ccd13:LeftAmp:left
+	amp27		STR	ccd13:RightAmp:right
+	amp28		STR	ccd14:LeftAmp:left
+	amp29		STR	ccd14:RightAmp:right
+	amp30		STR	ccd15:LeftAmp:left
+	amp31		STR	ccd15:RightAmp:right
+	amp32		STR	ccd16:LeftAmp:left
+	amp33		STR	ccd16:RightAmp:right
+	amp34		STR	ccd17:LeftAmp:left
+	amp35		STR	ccd17:RightAmp:right
+	amp36		STR	ccd18:LeftAmp:left
+	amp37		STR	ccd18:RightAmp:right
+	amp38		STR	ccd19:LeftAmp:left
+	amp39		STR	ccd19:RightAmp:right
+	amp40		STR	ccd20:LeftAmp:left
+	amp41		STR	ccd20:RightAmp:right
+	amp42		STR	ccd21:LeftAmp:left
+	amp43		STR	ccd21:RightAmp:right
+	amp44		STR	ccd22:LeftAmp:left
+	amp45		STR	ccd22:RightAmp:right
+	amp46		STR	ccd23:LeftAmp:left
+	amp47		STR	ccd23:RightAmp:right
+	amp48		STR	ccd24:LeftAmp:left
+	amp49		STR	ccd24:RightAmp:right
+	amp50		STR	ccd25:LeftAmp:left
+	amp51		STR	ccd25:RightAmp:right
+	amp52		STR	ccd26:LeftAmp:left
+	amp53		STR	ccd26:RightAmp:right
+	amp54		STR	ccd27:LeftAmp:left
+	amp55		STR	ccd27:RightAmp:right
+	amp56		STR	ccd28:LeftAmp:left
+	amp57		STR	ccd28:RightAmp:right
+	amp58		STR	ccd29:LeftAmp:left
+	amp59		STR	ccd29:RightAmp:right
+	amp60		STR	ccd30:LeftAmp:left
+	amp61		STR	ccd30:RightAmp:right
+	amp62		STR	ccd31:LeftAmp:left
+	amp63		STR	ccd31:RightAmp:right
+	amp64		STR	ccd32:LeftAmp:left
+	amp65		STR	ccd32:RightAmp:right
+	amp66		STR	ccd33:LeftAmp:left
+	amp67		STR	ccd33:RightAmp:right
+	amp68		STR	ccd34:LeftAmp:left
+	amp69		STR	ccd34:RightAmp:right
+	amp70		STR	ccd35:LeftAmp:left
+	amp71		STR	ccd35:RightAmp:right
+END
+
+# Specify the cell data
+CELLS	METADATA
+	left	METADATA	# Left amplifier
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BIASSEC
+		CELL.TRIMSEC		STR	DATASEC
+		CELL.XPARITY		S32	1 # We could have specified this as a DEFAULT, but this works
+		CELL.X0			S32	1
+		CELL.Y0			S32	1
+	END
+	right	METADATA	# Right amplifier
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BIASSEC
+		CELL.TRIMSEC		STR	DATASEC
+		CELL.XPARITY		S32	-1 # This cell is read out in the opposite direction
+		CELL.X0			S32	2048
+		CELL.Y0			S32	1
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.AIRMASS		STR	AIRMASS
+	FPA.FILTERID		STR	FILTER
+	FPA.FILTER		STR	FILTER
+	FPA.OBSTYPE		STR	OBSTYPE
+	FPA.OBJECT		STR	OBJECT
+	FPA.TIME		STR	MJD-OBS
+	FPA.TIMESYS		STR	TIMESYS
+	FPA.RA			STR	RA
+	FPA.DEC			STR	DEC
+	FPA.RADECSYS		STR	RADECSYS
+	FPA.TEMP		STR	DETTEM
+	FPA.EXPOSURE		STR	EXPTIME
+	CHIP.TEMP		STR	DETTEM
+	CELL.EXPOSURE		STR	EXPTIME
+	CELL.DARKTIME		STR	DARKTIME
+	CELL.GAIN		STR	GAIN     # only in raw; replaced by GAINA, GAINB in mef
+	CELL.READNOISE		STR	RDNOISE
+	CELL.TIME		STR	MJD-OBS
+	CELL.TIMESYS		STR	TIMESYS
+	CELL.XBIN		STR	CCDBIN1
+	CELL.YBIN		STR	CCDBIN2
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	FPA.TELESCOPE		STR	CFHT
+	FPA.INSTRUMENT		STR	MEGACAM
+	FPA.DETECTOR		STR	MEGACAM
+	FPA.POSANGLE		F32	0.0     # not reported until 03Am04
+	FPA.FOCUS		F32	0.0     # not reported until 03Am04
+	FPA.ALT			F32	0.0     # not reported until 03Am04
+	FPA.AZ			F32	0.0     # not reported until 03Am04
+	CHIP.XSIZE		S32	2048
+	CHIP.YSIZE		S32	4612
+	CELL.XSIZE		S32	1024
+	CELL.YSIZE		S32	4612
+	CELL.XWINDOW		S32	0
+	CELL.YWINDOW		S32	0
+	CELL.READDIR		S32	1		# Cell is read in x direction
+        CELL.SATURATION		F32	50000.0
+	CELL.BAD		F32	0
+	CELL.YPARITY		S32	1
+
+	CHIP.X0.DEPEND		STR	CHIP.NAME
+	CHIP.X0		METADATA
+		ccd00	S32	0
+		ccd01	S32	2048
+		ccd02	S32	4096
+		ccd03	S32	6144
+		ccd04	S32	8192
+		ccd05	S32	10240
+		ccd06	S32	12288
+		ccd07	S32	14336
+		ccd08	S32	16384
+		ccd09	S32	0
+		ccd10	S32	2048
+		ccd11	S32	4096
+		ccd12	S32	6144
+		ccd13	S32	8192
+		ccd14	S32	10240
+		ccd15	S32	12288
+		ccd16	S32	14336
+		ccd17	S32	16384
+		ccd18	S32    -2048
+		ccd19	S32	0    
+		ccd20	S32	2048 
+		ccd21	S32	4096 
+		ccd22	S32	6144 
+		ccd23	S32	8192 
+		ccd24	S32	10240
+		ccd25	S32	12288
+		ccd26	S32	14336
+		ccd27	S32    -2048
+		ccd28	S32	0    
+		ccd29	S32	2048 
+		ccd30	S32	4096 
+		ccd31	S32	6144 
+		ccd32	S32	8192 
+		ccd33	S32	10240
+		ccd34	S32	12288
+		ccd35	S32	14336
+	END
+	CHIP.Y0.DEPEND		STR	CHIP.NAME
+	CHIP.Y0		METADATA
+		ccd00	S32	18447
+		ccd01	S32	18447
+		ccd02	S32	18447
+		ccd03	S32	18447
+		ccd04	S32	18447
+		ccd05	S32	18447
+		ccd06	S32	18447
+		ccd07	S32	18447
+		ccd08	S32	18447
+		ccd09	S32	13835
+		ccd10	S32	13835
+		ccd11	S32	13835
+		ccd12	S32	13835
+		ccd13	S32	13835
+		ccd14	S32	13835
+		ccd15	S32	13835
+		ccd16	S32	13835
+		ccd17	S32	13835
+		ccd18	S32	4612
+		ccd19	S32	4612
+		ccd20	S32	4612
+		ccd21	S32	4612
+		ccd22	S32	4612
+		ccd23	S32	4612
+		ccd24	S32	4612
+		ccd25	S32	4612
+		ccd26	S32	4612
+		ccd27	S32	0
+		ccd28	S32	0
+		ccd29	S32	0
+		ccd30	S32	0
+		ccd31	S32	0
+		ccd32	S32	0
+		ccd33	S32	0
+		ccd34	S32	0
+		ccd35	S32	0
+	END
+	CHIP.XPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.XPARITY	METADATA
+		ccd00	S32	-1
+		ccd01	S32	-1
+		ccd02	S32	-1
+		ccd03	S32	-1
+		ccd04	S32	-1
+		ccd05	S32	-1
+		ccd06	S32	-1
+		ccd07	S32	-1
+		ccd08	S32	-1
+		ccd09	S32	-1
+		ccd10	S32	-1
+		ccd11	S32	-1
+		ccd12	S32	-1
+		ccd13	S32	-1
+		ccd14	S32	-1
+		ccd15	S32	-1
+		ccd16	S32	-1
+		ccd17	S32	-1
+		ccd18	S32	1
+		ccd19	S32	1
+		ccd20	S32	1
+		ccd21	S32	1
+		ccd22	S32	1
+		ccd23	S32	1
+		ccd24	S32	1
+		ccd25	S32	1
+		ccd26	S32	1
+		ccd27	S32	1
+		ccd28	S32	1
+		ccd29	S32	1
+		ccd30	S32	1
+		ccd31	S32	1
+		ccd32	S32	1
+		ccd33	S32	1
+		ccd34	S32	1
+		ccd35	S32	1
+	END
+	CHIP.YPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.YPARITY	METADATA
+		ccd00	S32	-1
+		ccd01	S32	-1
+		ccd02	S32	-1
+		ccd03	S32	-1
+		ccd04	S32	-1
+		ccd05	S32	-1
+		ccd06	S32	-1
+		ccd07	S32	-1
+		ccd08	S32	-1
+		ccd09	S32	-1
+		ccd10	S32	-1
+		ccd11	S32	-1
+		ccd12	S32	-1
+		ccd13	S32	-1
+		ccd14	S32	-1
+		ccd15	S32	-1
+		ccd16	S32	-1
+		ccd17	S32	-1
+		ccd18	S32	1
+		ccd19	S32	1
+		ccd20	S32	1
+		ccd21	S32	1
+		ccd22	S32	1
+		ccd23	S32	1
+		ccd24	S32	1
+		ccd25	S32	1
+		ccd26	S32	1
+		ccd27	S32	1
+		ccd28	S32	1
+		ccd29	S32	1
+		ccd30	S32	1
+		ccd31	S32	1
+		ccd32	S32	1
+		ccd33	S32	1
+		ccd34	S32	1
+		ccd35	S32	1
+	END
+END
+
+# How to translation PS concepts into database lookups
+DATABASE	METADATA
+	TYPE		dbEntry		TABLE		COLUMN		GIVENDBCOL	GIVENPS
+#	FPA.BIAS	METADATA
+#		TABLE	STR	Camera
+#		COLUMN	STR	gain
+#		chipId	STR	{CHIP.NAME}
+#		cellId	STR	{CELL.NAME}
+#		time	STR	{CELL.TIME}
+#	END
+#	CELL.GAIN	dbEntry		Camera		gain		chipId,cellId	CHIP.NAME,CELL.NAME
+#	CELL.READNOISE	dbEntry		Camera		readNoise	chipId,cellId	CHIP.NAME,CELL.NAME
+
+# A database entry refers to a particular column (COLUMN) in a
+# particular table (TABLE), given certain PS concepts (GIVENPS) that
+# match certain database columns (GIVENDBCOL).
+END
+
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+	CELL.X0		STR	FORTRAN
+	CELL.Y0		STR	FORTRAN
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/format_raw.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/format_raw.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/format_raw.config	(revision 22157)
@@ -0,0 +1,348 @@
+# "mcshort" is a MegaCam camera with only the central six chips --- it's faster than the entire FPA.
+# The raw MegaCam data comes off the telescope with each of the chips stored in extensions of a MEF file.
+
+# How to identify this type
+RULE	METADATA
+	TELESCOP	STR	CFHT 3.6m # added at start of 03Am04
+	DETECTOR	STR	MegaCam
+	NAXIS           S32     0
+	EXTEND		BOOL	T
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	CELL	# The extensions represent cells
+	FPA.OBS		STR	EXPNUM	# A PHU keyword for unique identifier within the hierarchy level
+END
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# Extension name, chip name:type
+	amp00		STR	ccd00:LeftAmp:left
+	amp01		STR	ccd00:RightAmp:right
+	amp02		STR	ccd01:LeftAmp:left
+	amp03		STR	ccd01:RightAmp:right
+	amp04		STR	ccd02:LeftAmp:left
+	amp05		STR	ccd02:RightAmp:right
+	amp06		STR	ccd03:LeftAmp:left
+	amp07		STR	ccd03:RightAmp:right
+	amp08		STR	ccd04:LeftAmp:left
+	amp09		STR	ccd04:RightAmp:right
+	amp10		STR	ccd05:LeftAmp:left
+	amp11		STR	ccd05:RightAmp:right
+	amp12		STR	ccd06:LeftAmp:left
+	amp13		STR	ccd06:RightAmp:right
+	amp14		STR	ccd07:LeftAmp:left
+	amp15		STR	ccd07:RightAmp:right
+	amp16		STR	ccd08:LeftAmp:left
+	amp17		STR	ccd08:RightAmp:right
+	amp18		STR	ccd09:LeftAmp:left
+	amp19		STR	ccd09:RightAmp:right
+	amp20		STR	ccd10:LeftAmp:left
+	amp21		STR	ccd10:RightAmp:right
+	amp22		STR	ccd11:LeftAmp:left
+	amp23		STR	ccd11:RightAmp:right
+	amp24		STR	ccd12:LeftAmp:left
+	amp25		STR	ccd12:RightAmp:right
+	amp26		STR	ccd13:LeftAmp:left
+	amp27		STR	ccd13:RightAmp:right
+	amp28		STR	ccd14:LeftAmp:left
+	amp29		STR	ccd14:RightAmp:right
+	amp30		STR	ccd15:LeftAmp:left
+	amp31		STR	ccd15:RightAmp:right
+	amp32		STR	ccd16:LeftAmp:left
+	amp33		STR	ccd16:RightAmp:right
+	amp34		STR	ccd17:LeftAmp:left
+	amp35		STR	ccd17:RightAmp:right
+	amp36		STR	ccd18:LeftAmp:left
+	amp37		STR	ccd18:RightAmp:right
+	amp38		STR	ccd19:LeftAmp:left
+	amp39		STR	ccd19:RightAmp:right
+	amp40		STR	ccd20:LeftAmp:left
+	amp41		STR	ccd20:RightAmp:right
+	amp42		STR	ccd21:LeftAmp:left
+	amp43		STR	ccd21:RightAmp:right
+	amp44		STR	ccd22:LeftAmp:left
+	amp45		STR	ccd22:RightAmp:right
+	amp46		STR	ccd23:LeftAmp:left
+	amp47		STR	ccd23:RightAmp:right
+	amp48		STR	ccd24:LeftAmp:left
+	amp49		STR	ccd24:RightAmp:right
+	amp50		STR	ccd25:LeftAmp:left
+	amp51		STR	ccd25:RightAmp:right
+	amp52		STR	ccd26:LeftAmp:left
+	amp53		STR	ccd26:RightAmp:right
+	amp54		STR	ccd27:LeftAmp:left
+	amp55		STR	ccd27:RightAmp:right
+	amp56		STR	ccd28:LeftAmp:left
+	amp57		STR	ccd28:RightAmp:right
+	amp58		STR	ccd29:LeftAmp:left
+	amp59		STR	ccd29:RightAmp:right
+	amp60		STR	ccd30:LeftAmp:left
+	amp61		STR	ccd30:RightAmp:right
+	amp62		STR	ccd31:LeftAmp:left
+	amp63		STR	ccd31:RightAmp:right
+	amp64		STR	ccd32:LeftAmp:left
+	amp65		STR	ccd32:RightAmp:right
+	amp66		STR	ccd33:LeftAmp:left
+	amp67		STR	ccd33:RightAmp:right
+	amp68		STR	ccd34:LeftAmp:left
+	amp69		STR	ccd34:RightAmp:right
+	amp70		STR	ccd35:LeftAmp:left
+	amp71		STR	ccd35:RightAmp:right
+END
+
+# Specify the cell data
+CELLS	METADATA
+	left	METADATA	# Left amplifier
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BIASSEC
+		CELL.TRIMSEC		STR	DATASEC
+		CELL.XPARITY		S32	1 # We could have specified this as a DEFAULT, but this works
+		CELL.X0			S32	1
+		CELL.Y0			S32	1
+	END
+	right	METADATA	# Right amplifier
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BIASSEC
+		CELL.TRIMSEC		STR	DATASEC
+		CELL.XPARITY		S32	-1 # This cell is read out in the opposite direction
+		CELL.X0			S32	2048
+		CELL.Y0			S32	1
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.AIRMASS		STR	AIRMASS
+	FPA.FILTERID		STR	FILTER
+	FPA.FILTER		STR	FILTER
+	FPA.OBSTYPE		STR	OBSTYPE
+	FPA.OBJECT		STR	OBJECT
+	FPA.TIME		STR	MJD-OBS
+	FPA.TIMESYS		STR	TIMESYS
+	FPA.RA			STR	RA
+	FPA.DEC			STR	DEC
+	FPA.RADECSYS		STR	RADECSYS
+	FPA.POSANGLE		STR	ROTANGLE # added at start of 03Am04
+	FPA.FOCUS		STR	TELFOCUS # added at start of 03Am04
+	FPA.ALT			STR	TELALT   # added at start of 03Am04
+	FPA.AZ			STR	TELAZ    # added at start of 03Am04
+	FPA.TEMP		STR	DETTEM
+	FPA.EXPOSURE		STR	EXPTIME
+	CHIP.TEMP		STR	DETTEM
+	CELL.EXPOSURE		STR	EXPTIME
+	CELL.DARKTIME		STR	DARKTIME
+	CELL.GAIN		STR	GAIN     # only in raw; replaced by GAINA, GAINB in mef
+	CELL.READNOISE		STR	RDNOISE
+	CELL.TIME		STR	MJD-OBS
+	CELL.TIMESYS		STR	TIMESYS
+	CELL.XBIN		STR	CCDBIN1
+	CELL.YBIN		STR	CCDBIN2
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	FPA.TELESCOPE		STR	CFHT
+	FPA.INSTRUMENT		STR	MEGACAM
+	FPA.DETECTOR		STR	MEGACAM
+	CHIP.XSIZE		S32	2048
+	CHIP.YSIZE		S32	4612
+	CELL.XSIZE		S32	1024
+	CELL.YSIZE		S32	4612
+	CELL.XWINDOW		S32	0
+	CELL.YWINDOW		S32	0
+	CELL.READDIR		S32	1		# Cell is read in x direction
+        CELL.SATURATION		F32	50000.0         # probably need to define this by cell
+	CELL.BAD		F32	0
+	CELL.YPARITY		S32	1
+
+	CHIP.X0.DEPEND		STR	CHIP.NAME
+	CHIP.X0		METADATA
+		ccd00	S32	0
+		ccd01	S32	2048
+		ccd02	S32	4096
+		ccd03	S32	6144
+		ccd04	S32	8192
+		ccd05	S32	10240
+		ccd06	S32	12288
+		ccd07	S32	14336
+		ccd08	S32	16384
+		ccd09	S32	0
+		ccd10	S32	2048
+		ccd11	S32	4096
+		ccd12	S32	6144
+		ccd13	S32	8192
+		ccd14	S32	10240
+		ccd15	S32	12288
+		ccd16	S32	14336
+		ccd17	S32	16384
+		ccd18	S32    -2048
+		ccd19	S32	0    
+		ccd20	S32	2048 
+		ccd21	S32	4096 
+		ccd22	S32	6144 
+		ccd23	S32	8192 
+		ccd24	S32	10240
+		ccd25	S32	12288
+		ccd26	S32	14336
+		ccd27	S32    -2048
+		ccd28	S32	0    
+		ccd29	S32	2048 
+		ccd30	S32	4096 
+		ccd31	S32	6144 
+		ccd32	S32	8192 
+		ccd33	S32	10240
+		ccd34	S32	12288
+		ccd35	S32	14336
+	END
+	CHIP.Y0.DEPEND		STR	CHIP.NAME
+	CHIP.Y0		METADATA
+		ccd00	S32	18447
+		ccd01	S32	18447
+		ccd02	S32	18447
+		ccd03	S32	18447
+		ccd04	S32	18447
+		ccd05	S32	18447
+		ccd06	S32	18447
+		ccd07	S32	18447
+		ccd08	S32	18447
+		ccd09	S32	13835
+		ccd10	S32	13835
+		ccd11	S32	13835
+		ccd12	S32	13835
+		ccd13	S32	13835
+		ccd14	S32	13835
+		ccd15	S32	13835
+		ccd16	S32	13835
+		ccd17	S32	13835
+		ccd18	S32	4612
+		ccd19	S32	4612
+		ccd20	S32	4612
+		ccd21	S32	4612
+		ccd22	S32	4612
+		ccd23	S32	4612
+		ccd24	S32	4612
+		ccd25	S32	4612
+		ccd26	S32	4612
+		ccd27	S32	0
+		ccd28	S32	0
+		ccd29	S32	0
+		ccd30	S32	0
+		ccd31	S32	0
+		ccd32	S32	0
+		ccd33	S32	0
+		ccd34	S32	0
+		ccd35	S32	0
+	END
+	CHIP.XPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.XPARITY	METADATA
+		ccd00	S32	-1
+		ccd01	S32	-1
+		ccd02	S32	-1
+		ccd03	S32	-1
+		ccd04	S32	-1
+		ccd05	S32	-1
+		ccd06	S32	-1
+		ccd07	S32	-1
+		ccd08	S32	-1
+		ccd09	S32	-1
+		ccd10	S32	-1
+		ccd11	S32	-1
+		ccd12	S32	-1
+		ccd13	S32	-1
+		ccd14	S32	-1
+		ccd15	S32	-1
+		ccd16	S32	-1
+		ccd17	S32	-1
+		ccd18	S32	1
+		ccd19	S32	1
+		ccd20	S32	1
+		ccd21	S32	1
+		ccd22	S32	1
+		ccd23	S32	1
+		ccd24	S32	1
+		ccd25	S32	1
+		ccd26	S32	1
+		ccd27	S32	1
+		ccd28	S32	1
+		ccd29	S32	1
+		ccd30	S32	1
+		ccd31	S32	1
+		ccd32	S32	1
+		ccd33	S32	1
+		ccd34	S32	1
+		ccd35	S32	1
+	END
+	CHIP.YPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.YPARITY	METADATA
+		ccd00	S32	-1
+		ccd01	S32	-1
+		ccd02	S32	-1
+		ccd03	S32	-1
+		ccd04	S32	-1
+		ccd05	S32	-1
+		ccd06	S32	-1
+		ccd07	S32	-1
+		ccd08	S32	-1
+		ccd09	S32	-1
+		ccd10	S32	-1
+		ccd11	S32	-1
+		ccd12	S32	-1
+		ccd13	S32	-1
+		ccd14	S32	-1
+		ccd15	S32	-1
+		ccd16	S32	-1
+		ccd17	S32	-1
+		ccd18	S32	1
+		ccd19	S32	1
+		ccd20	S32	1
+		ccd21	S32	1
+		ccd22	S32	1
+		ccd23	S32	1
+		ccd24	S32	1
+		ccd25	S32	1
+		ccd26	S32	1
+		ccd27	S32	1
+		ccd28	S32	1
+		ccd29	S32	1
+		ccd30	S32	1
+		ccd31	S32	1
+		ccd32	S32	1
+		ccd33	S32	1
+		ccd34	S32	1
+		ccd35	S32	1
+	END
+END
+
+# How to translation PS concepts into database lookups
+DATABASE	METADATA
+	TYPE		dbEntry		TABLE		COLUMN		GIVENDBCOL	GIVENPS
+#	FPA.BIAS	METADATA
+#		TABLE	STR	Camera
+#		COLUMN	STR	gain
+#		chipId	STR	{CHIP.NAME}
+#		cellId	STR	{CELL.NAME}
+#		time	STR	{CELL.TIME}
+#	END
+#	CELL.GAIN	dbEntry		Camera		gain		chipId,cellId	CHIP.NAME,CELL.NAME
+#	CELL.READNOISE	dbEntry		Camera		readNoise	chipId,cellId	CHIP.NAME,CELL.NAME
+
+# A database entry refers to a particular column (COLUMN) in a
+# particular table (TABLE), given certain PS concepts (GIVENPS) that
+# match certain database columns (GIVENDBCOL).
+END
+
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+	CELL.X0		STR	FORTRAN
+	CELL.Y0		STR	FORTRAN
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/format_split.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/format_split.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/format_split.config	(revision 22157)
@@ -0,0 +1,316 @@
+# The split MecaCam data is stored as single files for each chip, each a simple fits image
+
+# How to recognise this type
+RULE	METADATA
+	TELESCOP	STR	CFHT 3.6m
+	DETECTOR	STR	MegaCam
+	NAXIS           S32     2
+END
+
+FILE	METADATA
+	# How to read this data
+	PHU		STR	CHIP	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	NONE	# The extensions represent chips
+	FPA.OBS		STR	EXPNUM	# A PHU keyword for unique identifier
+	CONTENT		STR	EXTNAME	# Key to the CONTENTS menu
+	CONTENT.RULE	STR	{CHIP.NAME} # How to derive the CONTENT when writing
+END
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# Content name, chipType
+	ccd00		STR	ccd00:CCD42-90
+	ccd01		STR	ccd01:CCD42-90
+	ccd02		STR	ccd02:CCD42-90
+	ccd03		STR	ccd03:CCD42-90
+	ccd04		STR	ccd04:CCD42-90
+	ccd05		STR	ccd05:CCD42-90
+	ccd06		STR	ccd06:CCD42-90
+	ccd07		STR	ccd07:CCD42-90
+	ccd08		STR	ccd08:CCD42-90
+	ccd09		STR	ccd09:CCD42-90
+	ccd10		STR	ccd10:CCD42-90
+	ccd11		STR	ccd11:CCD42-90
+	ccd12		STR	ccd12:CCD42-90
+	ccd13		STR	ccd13:CCD42-90
+	ccd14		STR	ccd14:CCD42-90
+	ccd15		STR	ccd15:CCD42-90
+	ccd16		STR	ccd16:CCD42-90
+	ccd17		STR	ccd17:CCD42-90
+	ccd18		STR	ccd18:CCD42-90
+	ccd19		STR	ccd19:CCD42-90
+	ccd20		STR	ccd20:CCD42-90
+	ccd21		STR	ccd21:CCD42-90
+	ccd22		STR	ccd22:CCD42-90
+	ccd23		STR	ccd23:CCD42-90
+	ccd24		STR	ccd24:CCD42-90
+	ccd25		STR	ccd25:CCD42-90
+	ccd26		STR	ccd26:CCD42-90
+	ccd27		STR	ccd27:CCD42-90
+	ccd28		STR	ccd28:CCD42-90
+	ccd29		STR	ccd29:CCD42-90
+	ccd30		STR	ccd30:CCD42-90
+	ccd31		STR	ccd31:CCD42-90
+	ccd32		STR	ccd32:CCD42-90
+	ccd33		STR	ccd33:CCD42-90
+	ccd34		STR	ccd34:CCD42-90
+	ccd35		STR	ccd35:CCD42-90
+END
+
+# Specify the chips
+CHIPS		METADATA
+	# Chip type, cellName:cellType
+	CCD42-90	STR	LeftAmp:left RightAmp:right
+END
+
+
+# Specify the cells
+CELLS		METADATA
+	# Cell type, concepts for this type
+	left		METADATA
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BSECA
+		CELL.TRIMSEC		STR	TSECA
+		CELL.X0			S32	0
+		CELL.GAIN.SOURCE	STR	HEADER
+		CELL.GAIN		STR	GAINA
+	END
+
+	right		METADATA
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BSECB
+		CELL.TRIMSEC		STR	TSECB
+		CELL.X0			S32	1024
+		CELL.GAIN.SOURCE	STR	HEADER
+		CELL.GAIN		STR	GAINB
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+        FPA.OBS        STR     EXPNUM
+        FPA.AIRMASS     STR     AIRMASS
+        FPA.FILTER      STR     FILTER
+        FPA.FILTERID    STR     FILTER
+        FPA.POSANGLE    STR     ROTANGLE
+        FPA.RA          STR     RA
+        FPA.DEC         STR     DEC
+        FPA.RADECSYS    STR     RADECSYS
+	FPA.OBSTYPE	STR	OBSTYPE
+	FPA.OBJECT	STR	OBJECT
+	FPA.FOCUS	STR	TELFOCUS
+	FPA.TIME	STR	MJD-OBS
+	FPA.TIMESYS	STR	TIMESYS
+	FPA.ALT		STR	TELALT
+	FPA.AZ		STR	TELAZ
+	FPA.TEMP	STR	DETTEM
+	FPA.EXPOSURE	STR	EXPTIME
+	CHIP.TEMP	STR	DETTEM
+        CELL.EXPOSURE   STR     EXPTIME
+        CELL.DARKTIME   STR     DARKTIME
+        CELL.READNOISE  STR     RDNOISE
+	CELL.TIME	STR	MJD-OBS
+	CELL.TIMESYS	STR	TIMESYS
+        CELL.XBIN       STR     CCDBIN1
+        CELL.YBIN       STR     CCDBIN2
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS        METADATA
+	FPA.TELESCOPE		STR	CFHT
+	FPA.INSTRUMENT		STR	MEGACAM
+	FPA.DETECTOR	        STR	MEGACAM
+	CHIP.XSIZE		S32	2048
+	CHIP.YSIZE		S32	4612
+	CELL.XSIZE		S32	1024
+	CELL.YSIZE		S32	4612
+	CELL.XWINDOW		S32	0
+	CELL.YWINDOW		S32	0
+        CELL.SATURATION         S32     60000
+	CELL.READDIR		S32	1		# Cell is read in x direction
+        CELL.SATURATION		F32	50000.0
+        CELL.BAD                F32     0
+	CELL.XPARITY		S32	1
+	CELL.YPARITY		S32	1
+	CELL.Y0			S32	0
+	CHIP.X0.DEPEND		STR	CHIP.NAME
+	CHIP.X0		METADATA
+		ccd00	S32	0
+		ccd01	S32	2048
+		ccd02	S32	4096
+		ccd03	S32	6144
+		ccd04	S32	8192
+		ccd05	S32	10240
+		ccd06	S32	12288
+		ccd07	S32	14336
+		ccd08	S32	16384
+		ccd09	S32	0
+		ccd10	S32	2048
+		ccd11	S32	4096
+		ccd12	S32	6144
+		ccd13	S32	8192
+		ccd14	S32	10240
+		ccd15	S32	12288
+		ccd16	S32	14336
+		ccd17	S32	16384
+		ccd18	S32    -2048
+		ccd19	S32	0    
+		ccd20	S32	2048 
+		ccd21	S32	4096 
+		ccd22	S32	6144 
+		ccd23	S32	8192 
+		ccd24	S32	10240
+		ccd25	S32	12288
+		ccd26	S32	14336
+		ccd27	S32    -2048
+		ccd28	S32	0    
+		ccd29	S32	2048 
+		ccd30	S32	4096 
+		ccd31	S32	6144 
+		ccd32	S32	8192 
+		ccd33	S32	10240
+		ccd34	S32	12288
+		ccd35	S32	14336
+	END
+	CHIP.Y0.DEPEND		STR	CHIP.NAME
+	CHIP.Y0		METADATA
+		ccd00	S32	18447
+		ccd01	S32	18447
+		ccd02	S32	18447
+		ccd03	S32	18447
+		ccd04	S32	18447
+		ccd05	S32	18447
+		ccd06	S32	18447
+		ccd07	S32	18447
+		ccd08	S32	18447
+		ccd09	S32	13835
+		ccd10	S32	13835
+		ccd11	S32	13835
+		ccd12	S32	13835
+		ccd13	S32	13835
+		ccd14	S32	13835
+		ccd15	S32	13835
+		ccd16	S32	13835
+		ccd17	S32	13835
+		ccd18	S32	4612
+		ccd19	S32	4612
+		ccd20	S32	4612
+		ccd21	S32	4612
+		ccd22	S32	4612
+		ccd23	S32	4612
+		ccd24	S32	4612
+		ccd25	S32	4612
+		ccd26	S32	4612
+		ccd27	S32	0
+		ccd28	S32	0
+		ccd29	S32	0
+		ccd30	S32	0
+		ccd31	S32	0
+		ccd32	S32	0
+		ccd33	S32	0
+		ccd34	S32	0
+		ccd35	S32	0
+	END
+	CHIP.XPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.XPARITY	METADATA
+		ccd00	S32	-1
+		ccd01	S32	-1
+		ccd02	S32	-1
+		ccd03	S32	-1
+		ccd04	S32	-1
+		ccd05	S32	-1
+		ccd06	S32	-1
+		ccd07	S32	-1
+		ccd08	S32	-1
+		ccd09	S32	-1
+		ccd10	S32	-1
+		ccd11	S32	-1
+		ccd12	S32	-1
+		ccd13	S32	-1
+		ccd14	S32	-1
+		ccd15	S32	-1
+		ccd16	S32	-1
+		ccd17	S32	-1
+		ccd18	S32	1
+		ccd19	S32	1
+		ccd20	S32	1
+		ccd21	S32	1
+		ccd22	S32	1
+		ccd23	S32	1
+		ccd24	S32	1
+		ccd25	S32	1
+		ccd26	S32	1
+		ccd27	S32	1
+		ccd28	S32	1
+		ccd29	S32	1
+		ccd30	S32	1
+		ccd31	S32	1
+		ccd32	S32	1
+		ccd33	S32	1
+		ccd34	S32	1
+		ccd35	S32	1
+	END
+	CHIP.YPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.YPARITY	METADATA
+		ccd00	S32	-1
+		ccd01	S32	-1
+		ccd02	S32	-1
+		ccd03	S32	-1
+		ccd04	S32	-1
+		ccd05	S32	-1
+		ccd06	S32	-1
+		ccd07	S32	-1
+		ccd08	S32	-1
+		ccd09	S32	-1
+		ccd10	S32	-1
+		ccd11	S32	-1
+		ccd12	S32	-1
+		ccd13	S32	-1
+		ccd14	S32	-1
+		ccd15	S32	-1
+		ccd16	S32	-1
+		ccd17	S32	-1
+		ccd18	S32	1
+		ccd19	S32	1
+		ccd20	S32	1
+		ccd21	S32	1
+		ccd22	S32	1
+		ccd23	S32	1
+		ccd24	S32	1
+		ccd25	S32	1
+		ccd26	S32	1
+		ccd27	S32	1
+		ccd28	S32	1
+		ccd29	S32	1
+		ccd30	S32	1
+		ccd31	S32	1
+		ccd32	S32	1
+		ccd33	S32	1
+		ccd34	S32	1
+		ccd35	S32	1
+	END
+END
+
+
+# How to translation PS concepts into database lookups
+DATABASE	METADATA
+	TYPE		dbEntry		TABLE		COLUMN		GIVENDBCOL	GIVENPS
+#	CELL.GAIN	dbEntry		Camera		gain		chipId,cellId	CHIP.NAME,CELL.NAME
+#	CELL.READNOISE	dbEntry		Camera		readNoise	chipId,cellId	CHIP.NAME,CELL.NAME
+
+# A database entry refers to a particular column (COLUMN) in a
+# particular table (TABLE), given certain PS concepts (GIVENPS) that
+# match certain database columns (GIVENDBCOL).
+END		
+
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
+ 
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/ppImage.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/ppImage.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/ppImage.config	(revision 22157)
@@ -0,0 +1,172 @@
+### ppImage recipe configuration file for Megacam
+
+# Overscan subtraction
+OVERSCAN.SINGLE		BOOL	FALSE		# Reduce overscan to a single value?
+OVERSCAN.FIT		STR	NONE		# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER		S32	0		# Order of polynomial fit
+OVERSCAN.STAT		STR	MEAN		# MEAN | MEDIAN
+
+# for a test, turn off selected detrend types
+OVERSCAN	BOOL	FALSE		# Overscan subtraction
+BIAS		BOOL	FALSE		# Bias subtraction
+DARK		BOOL	TRUE		# Dark subtraction
+FLAT		BOOL	TRUE		# Flat-field normalisation
+
+# binned output image options
+BIN1.XBIN               S32     18
+BIN1.YBIN               S32     18
+BIN2.XBIN               S32     144
+BIN2.YBIN               S32     144
+
+# megacam needs fringe subtraction
+FRINGE		BOOL	TRUE		# Fringe subtraction
+
+# this table lists extra constraints which should be applied when
+# selecting the detrend images.
+# note: camera and time are always applied
+
+DETREND.CONSTRAINTS  METADATA
+  BIAS METADATA
+  END
+  DARK METADATA
+#   EXPTIME STR FPA.EXPOSURE
+  END
+  FLAT METADATA
+    FILTER  STR FPA.FILTERID
+#   DETTYPE STR DOMEFLAT.RAW
+#   VERSION STR SKYFLAT.RAW
+#   VERSION STR DOMEFLAT.CORR.XX
+#   VERSION STR SKYFLAT.CORR
+  END
+  FLAT_CORRECTION METADATA
+    FILTER   STR FPA.FILTER
+  END
+  FRINGE METADATA
+    FILTER   STR FPA.FILTER
+#   AIRMASS  STR FPA.AIRMASS
+#   TWILIGHT STR FPA.TWILIGHT
+  END
+  SHUTTER METADATA
+  END
+  MASK METADATA
+  END	
+END
+
+# only apply the fringe for these filters
+FRINGE.FILTERS  MULTI
+FRINGE.FILTERS	STR i
+FRINGE.FILTERS	STR z
+
+PHOTCODE.RULE		STR	{DETECTOR}.{FILTER.ID}.{CHIP.N}
+
+
+
+# Overscan, bias, dark, shutter
+PPIMAGE_OBDS       METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field
+PPIMAGE_OBDSF      METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe
+PPIMAGE_OBDSFR     METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    TRUE            # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom
+PPIMAGE_OBDSFRP    METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    TRUE            # Mask bad pixels
+  FRINGE           BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM           BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_OBDSFRA    METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    TRUE            # Mask bad pixels
+  FRINGE           BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM           BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END 
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/ppMerge.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/ppMerge.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/ppMerge.config	(revision 22157)
@@ -0,0 +1,40 @@
+
+# Bias combination --- don't want min/max rejection
+PPMERGE_BIAS	METADATA
+   REJ		F32	3.0		# Rejection threshold (sigma)
+   ITER		S32	2		# Number of rejection iterations
+   FRACHIGH	F32	0.0		# Fraction of high pixels to reject immediately
+   FRACLOW	F32	0.0		# Fraction of low pixels to reject immediately
+   WEIGHTS	BOOL	FALSE		# Use image weights?
+   COMBINE	STR	CLIPPED		# Statistic to use for combination: 
+END
+
+# Dark combination --- don't want min/max rejection
+# More aggressive clipping than bias, so as to remove CRs
+PPMERGE_DARK	METADATA
+  REJ		F32	2.0		# Rejection threshold (sigma)
+  ITER		S32	4		# Number of rejection iterations
+  FRACHIGH	F32	0.0		# Fraction of high pixels to reject immediately
+  FRACLOW	F32	0.0		# Fraction of low pixels to reject immediately
+  WEIGHTS	BOOL	FALSE		# Use image weights?
+  COMBINE	STR	CLIPPED		# Statistic to use for combination: 
+END
+
+# Flat combination --- use min/max rejection
+PPMERGE_FLAT	METADATA
+	REJ		F32	3.0		# Rejection threshold (sigma)
+	ITER		S32	1		# Number of rejection iterations
+	FRACHIGH	F32	0.3		# Fraction of high pixels to reject immediately
+	FRACLOW		F32	0.1		# Fraction of low pixels to reject immediately
+	NKEEP		S32	5		# Minimum number of pixels in stack to keep
+	WEIGHTS		BOOL	FALSE		# Use image weights?
+	COMBINE		STR	MEAN		# Statistic to use for combination: 
+END
+
+
+# Fringe combination --- already included in default, above
+PPMERGE_FRINGE	METADATA
+	FRACHIGH	F32	0.1		# Fraction of high pixels to reject immediately
+	WEIGHTS		BOOL	FALSE		# Use image weights?
+END
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/psastro.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/psastro.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/psastro.config	(revision 22157)
@@ -0,0 +1,74 @@
+
+# astrometry matching parameters
+
+# nominal plate scale (microns / pixel)
+PSASTRO.PIXEL.SCALE    F32  1.0
+
+# pmAstromGridAngle
+# max grid offset in FP units (microns)
+# use plate-scale to make this in pixels?
+PSASTRO.GRID.OFFSET    F32    2500.
+PSASTRO.GRID.SCALE     F32      50.
+
+# extra field for ref stars:
+PSASTRO.FIELD.PADDING  F32 1.0
+
+# these tweak are in FP units (pixels, currently)
+PSASTRO.TWEAK.SCALE     F32      1
+PSASTRO.TWEAK.RANGE     F32     75
+PSASTRO.TWEAK.SMOOTH    F32      2
+PSASTRO.TWEAK.NSIGMA    F32      3
+
+# single-chip radius match in pixels
+PSASTRO.MATCH.FIT.NITER S32   3
+PSASTRO.MATCH.RADIUS    F32   12.0
+PSASTRO.MATCH.RADIUS.N0 F32   15.0
+PSASTRO.MATCH.RADIUS.N1 F32   10.0
+PSASTRO.MATCH.RADIUS.N2 F32    5.0
+
+PSASTRO.MAX.NRAW       S32      1500   # max stars accepted for fitting (0 for all)
+PSASTRO.MAX.NREF       S32      1500   # max stars accepted for fitting (0 for all)
+
+PSASTRO.MIN.INST.MAG.RAW       F32      -15.5  # min instrumental magnitude for stars accepted for fitting
+PSASTRO.MAX.INST.MAG.RAW       F32       -8.0  # max instrumental magnitude for stars accepted for fitting
+
+PSASTRO.MAX.ERROR      F32      10.0 # max error in pixels
+PSASTRO.MIN.NSTAR      S32      10   # min fitted stars in solution
+
+PSASTRO.MOSAIC.MODE           BOOL     FALSE
+
+PSASTRO.MOSAIC.MAX.ERROR.N0 F32    1.50 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N1 F32    1.50 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N2 F32    0.90 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N3 F32    0.90 # max allow error for valid solution (arcsec)
+
+PSASTRO.MOSAIC.RADIUS.N0    F32    10.0
+PSASTRO.MOSAIC.RADIUS.N1    F32     8.0 # do not refine the match
+PSASTRO.MOSAIC.RADIUS.N2    F32     8.0 # do not refine the match
+PSASTRO.MOSAIC.RADIUS.N3    F32     4.0 
+
+PSASTRO.MOSAIC.CHIP.ORDER.N0  S32      0 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N1  S32      1 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N2  S32      1 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N3  S32      3 # fit order (-1 means use default)
+
+PSASTRO.MOSAIC.GRADIENT.NX    S32      2   # number of x-dir cells per chip
+PSASTRO.MOSAIC.GRADIENT.NY    S32      4   # number of y-dir cells per chip
+
+# use this recipe to set a tight constraint
+PSASTRO.FINE METADATA
+  PSASTRO.MOSAIC.MAX.ERROR.N0 F32    1.50 # max allow error for valid solution (arcsec)
+  PSASTRO.MOSAIC.MAX.ERROR.N1 F32    1.50 # max allow error for valid solution (arcsec)
+  PSASTRO.MOSAIC.MAX.ERROR.N2 F32    0.90 # max allow error for valid solution (arcsec)
+  PSASTRO.MOSAIC.MAX.ERROR.N3 F32    0.90 # max allow error for valid solution (arcsec)
+
+  PSASTRO.MOSAIC.RADIUS.N0    F32    8.0
+  PSASTRO.MOSAIC.RADIUS.N1    F32    0.0
+  PSASTRO.MOSAIC.RADIUS.N2    F32    4.0
+  PSASTRO.MOSAIC.RADIUS.N3    F32    4.0
+
+  PSASTRO.MOSAIC.CHIP.ORDER.N0  S32      0 # fit order (-1 means use default)
+  PSASTRO.MOSAIC.CHIP.ORDER.N1  S32      1 # fit order (-1 means use default)
+  PSASTRO.MOSAIC.CHIP.ORDER.N2  S32      1 # fit order (-1 means use default)
+  PSASTRO.MOSAIC.CHIP.ORDER.N3  S32      1 # fit order (-1 means use default)
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/psphot.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/psphot.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/psphot.config	(revision 22157)
@@ -0,0 +1,43 @@
+
+# turn these on to see specific outputs
+SAVE.OUTPUT	BOOL 	TRUE
+SAVE.BACKMDL	BOOL 	TRUE
+SAVE.PSF	BOOL 	TRUE
+SAVE.PLOTS     	BOOL    TRUE
+
+BACKGROUND.XBIN	    S32  128            # size of background superpixels
+BACKGROUND.YBIN	    S32  128            # size of background superpixels
+
+# image background parameters
+IMSTATS_NPIX        S32  10000    	 # number of pixels to use for sky estimate boxes:
+SKY_STAT            STR  FITTED_MEAN_V4  # statistic used to measure background
+SKY_CLIP_SIGMA      F32  2.0             # statistic used to measure background
+
+PSF_SN_LIM          F32  100             # minimum S/N for stars used for PSF model
+PSF_MAX_NSTARS      S32  300             # limit number of stars used for PSF model
+
+# PSF model parameters : choose the PSF model desired
+PSF_MODEL           STR  PS_MODEL_QGAUSS
+
+MOMENTS_SN_MIN      F32   30.0
+EXT_MIN_SN           F32  50.0           # fit galaxies above this S/N limit
+FULL_FIT_SN_LIM      F32  50.0
+AP_MIN_SN            F32  50.0
+
+# OUTPUT.FORMAT       STR SMPDATA
+OUTPUT.FORMAT       STR PS1_DEV_1
+
+PSF_SHAPE_NSIGMA     F32  3.0		 # max significance for shape variation
+PSF_MAX_CHI          F32  50.0		 # reject objects worse that this
+
+#PSF.TREND.MODE STR MAP
+#PSF.TREND.NX   S32 1
+#PSF.TREND.NY   S32 1
+
+DIAGNOSTIC.PLOTS		METADATA
+  IMAGE.BACKGROUND.CELL.HISTOGRAM   BOOL FALSE
+  IMAGE.BACKGROUND.CELL.HISTOGRAM.X S32 -1
+  IMAGE.BACKGROUND.CELL.HISTOGRAM.Y S32 12
+END
+
+USE_FOOTPRINTS                      BOOL  TRUE       	  # use new pmFootprint peak packaging
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/pswarp.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/pswarp.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/pswarp.config	(revision 22157)
@@ -0,0 +1,3 @@
+### Megacam recipe for pswarp
+
+ASTROM.SOURCE	STR	PSASTRO.OUTPUT	# Source file rule for astrometry, or NULL
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/rejections.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/rejections.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/megacam/rejections.config	(revision 22157)
@@ -0,0 +1,38 @@
+
+BIAS METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  2.0
+  IMFILE.STDEV       F32 10.0
+  IMFILE.MEANSTDEV   F32  0.5
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.5
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  1.0
+  EXP.STDEV          F32 10.0
+  EXP.MEANSTDEV      F32  0.5
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  3.0
+  ENSEMBLE.STDEV     F32  3.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+# FILTER is an additional qualifier, and may be "*" (or absent!), in which case it matches everything
+# EXPECTED is the expected mean value
+# IMFILE.MEAN is the maximum permitted mean value for an imfile, relative to the standard deviation
+# IMFILE.STDEV is the maximum permitted standard deviation for an imfile
+# EXP.MEAN is the maximum permitted mean value for an exposure, relative to the standard deviation
+# EXP.STDEV is the maximum permitted standard deviation for an exposure
+# EXP.MEANSTDEV is the maximum permitted mean standard deviation for an exposure relative to the mean
+# ENSEMBLE.MEAN is the maximum permitted mean for an ensemble of exposures
+# ENSEMBLE.STDEV is the maximum permitted standard deviation for an ensemble of exposures
+# ENSEMBLE.MEANSTDEV is the maximum permitted mean standard deviation for an ensemble of exposures
+# IMFILE.SNR is the minimum permitted signal-to-noise for an imfile
+# EXP.SNR is the minimum permitted signal-to-noise for an exposure
+# These values (all except FILTER) may be zero, in which case no clipping is applied.
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/.cvsignore	(revision 22157)
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/Makefile.am	(revision 22157)
@@ -0,0 +1,22 @@
+
+installdir = $(datadir)/ippconfig/mosaic2
+
+install_files = \
+	dvo.config \
+	dvo.layout \
+	camera.config \
+	format.config \
+	ppImage.config \
+        psastro.config \
+	psphot.config \
+	ppMerge.config \
+	rejections.config
+
+install_DATA = $(install_files)
+
+install-data-hook:
+	chmod 0755 $(installdir)
+
+EXTRA_DIST = $(install_files)
+
+ACLOCAL_AMFLAGS = -I m4
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/camera.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/camera.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/camera.config	(revision 22157)
@@ -0,0 +1,102 @@
+# Camera configuration file for the CTIO MOSAIC2 : describes the camera
+
+# File formats that we know about
+FORMATS		METADATA
+    MEF		STR	mosaic2/format.config
+END
+
+# Description of camera --- all the chips and the cells that comprise them
+FPA	METADATA
+	ccd00	STR	LeftAmp RightAmp
+	ccd01	STR	LeftAmp RightAmp
+	ccd02	STR	LeftAmp RightAmp
+	ccd03	STR	LeftAmp RightAmp
+	ccd04	STR	LeftAmp RightAmp
+	ccd05	STR	LeftAmp RightAmp
+	ccd06	STR	LeftAmp RightAmp
+	ccd07	STR	LeftAmp RightAmp
+END
+
+# menu of possible filter names:  internal  STR  external name
+FILTER.ID       METADATA
+   VR MULTI
+   VR STR VR.Supermacho.c6027 
+   VR STR VR Supermacho c6027 
+   VR STR VR SuperMacho c6027 
+   R  STR R Harris c6004      
+   I  STR I c6028             
+   U  STR U c6001
+   B  STR B Harris c6002
+   V  STR V Harris c6026
+   # Minor filters 
+   g  MULTI
+   g  STR g SDSS c6017
+   g  STR SDSS g c6017
+   r  MULTI
+   r  STR SDSS r c6018
+   r  STR r SDSS c6018
+   i  MULTI
+   i  STR i SDSS c6019
+   i  STR SDSS i c6019
+   # Unused filters (only used in zeros, and therefore irrelevent)
+   NULL MULTI
+   NULL STR z SDSS c6020
+   NULL STR SDSS z c6020
+   NULL STR ha8 H-alpha+8nm c6011
+   NULL STR M Washington c6007
+   NULL STR u SDSS c6021
+   NULL STR VRr Supermacho c6027 
+END
+
+# Table of strings to use for the class identifier (see ippdb) according to the file level.
+CLASSID		METADATA
+	FPA	STR	fpa
+END
+
+DVO.CAMERADIR	STR	mosaic2		# Camera directory for DVO
+
+# convert supplied FPA.OBSTYPE values to abstract exptype names
+OBSTYPE.TABLE METADATA
+  bias 	   STR BIAS
+  zero 	   STR BIAS
+  dark 	   STR DARK
+  flat 	   STR SKYFLAT
+  skyflat  STR SKYFLAT
+  domeflat STR DOMEFLAT
+  object   STR OBJECT
+  science  STR OBJECT
+END
+
+# Recipe options
+RECIPES		METADATA
+	# Recipes for ppImage
+        PPIMAGE         STR     mosaic2/ppImage.config
+	PPMERGE		STR	mosaic2/ppMerge.config
+        PSPHOT          STR     mosaic2/psphot.config
+	REJECTIONS	STR	mosaic2/rejections.config	# Detrend rejection limits
+END
+
+# Reduction classes
+REDUCTION	STR	recipes/reductionClasses.mdc
+
+FITSTYPES       STR     recipes/fitstypes.mdc
+
+# Set file rules appropriate for mef format (common to all mef cameras)
+FILERULES   STR   recipes/filerules-mef.mdc 
+
+EXTNAME.RULES METADATA
+  CMF.HEAD STR {CHIP.NAME}.hdr
+  CMF.DATA STR {CHIP.NAME}.psf # use .PSF and .EXT?
+  CMF.XSRC STR {CHIP.NAME}.xsrc # use .PSF and .EXT?
+  CMF.XFIT STR {CHIP.NAME}.xfit # use .PSF and .EXT?
+
+  PSF.HEAD  STR {CHIP.NAME}.hdr
+  PSF.TABLE STR {CHIP.NAME}.psf_model
+  PSF.RESID STR {CHIP.NAME}.psf_resid
+END
+
+BLANK.HEADERS	METADATA
+	FPA.TIME	STR	MJD-OBS
+	FPA.EXPOSURE	STR	EXPTIME
+	FPA.AIRMASS	STR	AIRMASS
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/dvo.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/dvo.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/dvo.config	(revision 22157)
@@ -0,0 +1,63 @@
+
+# location of DVO database tables
+CATDIR			/data/alala.0/eugene/isp/catdir
+
+# keywords used by DVO to interpret the headers
+
+# keyword abstractions:
+DATE-KEYWORD		DATE-OBS
+DATE-MODE		yyyy-mm-dd
+UT-KEYWORD		NONE
+JD-KEYWORD		NONE
+MJD-KEYWORD		MJD-OBS
+
+# other keyword abstractions
+EXPTIME-KEYWORD		EXPTIME
+AIRMASS-KEYWORD		AIRMASS
+CCDNUM-KEYWORD		EXTNAME
+ST-KEYWORD		NONE
+OBSERVATORY-LATITUDE	NONE
+OBSERVATORY-LONGITUDE	NONE
+SUBPIX_DATAFILE		NONE
+
+# instrumental magnitude range for calibration mode 
+CAL_INSTMAG_MAX		0
+CAL_INSTMAG_MIN		0
+
+# exclude overscan region from the dB image boundaries
+XOVERSCAN		0
+YOVERSCAN		0
+
+# only upload stars within region; a value of 0 means ignore the limit
+ADDSTAR_XMIN		0
+ADDSTAR_XMAX		0
+ADDSTAR_YMIN		0
+ADDSTAR_YMAX		0
+
+# exclude stars with SN > SNLIMIT (ADDSTAR_SNLIMIT overrides old name MIN_SN_FSTAT)
+ADDSTAR_SNLIMIT		0
+
+# correlation radius (arcseconds)
+ADDSTAR_RADIUS		1.0
+
+# scaled correlation radius 
+ADDSTAR_NSIGMA		0
+
+IMAGE_SCATTER           0.075  # mark images POOR if stdev(Mcal) > IMAGE_SCATTER
+STAR_SCATTER            0.005  # mark stars POOR if stdev(Mrel) > STAR_SCATTER
+IMAGE_OFFSET            0.100  # mark images POOR if abs(delta(Mcal)) > IMAGE_OFFSET
+STAR_CHISQ              10.0   # mark stars POOR if Xm > STAR_CHISQ
+STAR_TOOFEW              3     # mark star FEW if N(good) < STAR_TOOFEW
+IMAGE_TOOFEW            10     # mark image FEW if N(good) < IMAGE_TOOFEW
+IMAGE_GOOD_FRACTION     0.05   # mark image FEW if N(good) < IMAGE_GOOD_FRACTION * Nstars
+
+SCATTER_LIM             15.0
+MAG_LIM                 20.0   # select stars brighter than this for relphot analysis
+SIGMA_LIM               0.015  # select measurements with dM < SIGMA_LIM for relphot analysis
+# INST_MAG_MIN         -17     # optional constraints on magnitude ranges
+# INST_MAG_MAX         -13     # optional constraints on magnitude ranges
+
+RELPHOT_GRID_BINNING    256
+RELPHOT_GRID_X 6
+RELPHOT_GRID_Y 14
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/dvo.layout
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/dvo.layout	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/dvo.layout	(revision 22157)
@@ -0,0 +1,29 @@
+# this file defines the layout of the mosaic imager:
+
+NCCD 12
+NAXIS1 2080
+NAXIS2 4128
+
+MOSAIC_X 6
+MOSAIC_Y 2
+
+# lines need to contain:
+
+CHIPID_KEYWORD CHIPID
+
+# ID  CHIPID xoffset yoffset xflip yflip datasec         biassec                 Xo    Yo     theta
+CCD.0  chip00      0       0     0     0 [6:2049,5:4100] [2050:2080,2:4128]     0.0    0.0   0.00
+CCD.1  chip01      1       0     0     0 [6:2049,5:4100] [2050:2080,2:4128]  2000.0    0.0   0.00
+CCD.2  chip02      2       0     1     0 [6:2049,5:4100] [2050:2080,2:4128]  6000.0    0.0   0.00
+CCD.3  chip03      3       0     1     0 [6:2049,5:4100] [2050:2080,2:4128]  8000.0    0.0   0.00
+CCD.4  chip04      4       0     0     0 [6:2049,5:4100] [2050:2080,2:4128]  8000.0    0.0   0.00
+CCD.5  chip05      5       0     0     0 [6:2049,5:4100] [2050:2080,2:4128] 10000.0    0.0   0.00
+CCD.6  chip06      0       1     0     1 [6:2049,5:4100] [2050:2080,2:4128]     0.0 8000.0   1.00
+CCD.7  chip07      1       1     0     1 [6:2049,5:4100] [2050:2080,2:4128]  2000.0 8000.0   0.00
+CCD.8  chip08      2       1     0     1 [6:2049,5:4100] [2050:2080,2:4128]  4000.0 8000.0   0.00
+CCD.9  chip09      3       1     0     1 [6:2049,5:4100] [2050:2080,2:4128]  6000.0 8000.0   0.00
+CCD.10 chip10      4       1     0     1 [6:2049,5:4100] [2050:2080,2:4128]  8000.0 8000.0   0.00
+CCD.11 chip11      5       1     0     1 [6:2049,5:4100] [2050:2080,2:4128] 10000.0 8000.0   0.00
+
+# can we use header to find biassec
+USE_BIASSEC 1
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/format.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/format.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/format.config	(revision 22157)
@@ -0,0 +1,162 @@
+# Format file for CTIO Mosaic2 camera used by ESSENCE
+
+# How to identify this type
+RULE	METADATA
+	TELESCOP	STR	CTIO 4.0 meter telescope
+	DETECTOR	STR	Mosaic2
+	EXTEND		BOOL	T
+	NEXTEND		S32	16
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA	 # The FITS file represents an entire FPA
+	EXTENSIONS	STR	CELL	 # The extensions represent cells
+	FPA.OBS	STR	FILENAME # A PHU keyword for unique identifier within the hierarchy level
+END
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# Extension name, chip name:type
+	im1		STR	ccd00:LeftAmp:left
+	im2		STR	ccd00:RightAmp:right
+	im3		STR	ccd01:LeftAmp:left  
+	im4		STR	ccd01:RightAmp:right
+	im5		STR	ccd02:LeftAmp:left  
+	im6		STR	ccd02:RightAmp:right
+	im7		STR	ccd03:LeftAmp:left  
+	im8		STR	ccd03:RightAmp:right
+	im9		STR	ccd04:LeftAmp:left  
+	im10		STR	ccd04:RightAmp:right
+	im11		STR	ccd05:LeftAmp:left  
+	im12		STR	ccd05:RightAmp:right
+	im13		STR	ccd06:LeftAmp:left  
+	im14		STR	ccd06:RightAmp:right
+	im15		STR	ccd07:LeftAmp:left  
+	im16		STR	ccd07:RightAmp:right
+END
+
+# Specify the cell data
+CELLS	METADATA
+	left	METADATA
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BIASSEC
+		CELL.TRIMSEC		STR	TRIMSEC
+		CELL.XPARITY		S32	1
+		CELL.X0			S32	0
+	END
+	right	METADATA
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BIASSEC
+		CELL.TRIMSEC		STR	TRIMSEC
+		CELL.XPARITY		S32	1
+		CELL.X0			S32	1024
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.AIRMASS		STR	AIRMASS
+	FPA.FILTERID		STR	FILTER
+	FPA.FILTER		STR	FILTER
+	FPA.RA			STR	RA
+	FPA.DEC			STR	DEC
+	FPA.RADECSYS		STR	RADECSYS
+	FPA.OBSTYPE		STR	OBSTYPE
+	FPA.OBJECT		STR	OBJECT
+	FPA.TIME		STR	MJD-OBS
+	CHIP.TEMP		STR	CCDTEM
+	CELL.EXPOSURE		STR	EXPTIME
+	CELL.DARKTIME		STR	DARKTIME
+	CELL.GAIN		STR	GAIN
+	CELL.READNOISE		STR	RDNOISE
+	CELL.SATURATION		STR	SATURATE
+	CELL.TIME		STR	MJD-OBS
+	CELL.XBIN		STR	CCDSUM
+	CELL.YBIN		STR	CCDSUM
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+        FPA.TELESCOPE           STR     BLANCO
+        FPA.INSTRUMENT          STR     MOSAIC2
+        FPA.DETECTOR            STR     CTIO_MOSAIC2
+	FPA.POSANGLE		F32	0.0
+	FPA.TIMESYS		STR	UTC		# Header says "UTC approximate"
+	FPA.ALT			F32	0.0		# Not present in header
+	FPA.AZ			F32	0.0		# Not present in header
+	CELL.TIMESYS		STR	UTC		# Header says "UTC approximate"
+	CELL.READDIR		S32	1		# Cell is read in x direction
+	CELL.BAD		S32	0
+	CELL.YPARITY		S32	1
+	CELL.Y0			S32	0
+
+	CHIP.X0.DEPEND		STR	CHIP.NAME
+	CHIP.X0		METADATA
+		ccd00	S32	0
+		ccd01	S32	2048
+		ccd02	S32	4096
+		ccd03	S32	6144
+		ccd04	S32	0
+		ccd05	S32	2048
+		ccd06	S32	4096
+		ccd07	S32	6144
+	END
+	CHIP.Y0.DEPEND		STR	CHIP.NAME
+	CHIP.Y0		METADATA
+		ccd00	S32	0
+		ccd01	S32	0
+		ccd02	S32	0
+		ccd03	S32	0
+		ccd04	S32	4096
+		ccd05	S32	4096
+		ccd06	S32	4096
+		ccd07	S32	4096
+	END
+	CHIP.XPARITY	S32	1
+	CHIP.YPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.YPARITY	METADATA
+		ccd00	S32	1
+		ccd01	S32	1
+		ccd02	S32	1
+		ccd03	S32	1
+		ccd04	S32	1
+		ccd05	S32	1
+		ccd06	S32	1
+		ccd07	S32	1
+	END
+
+END
+
+# How to translation PS concepts into database lookups
+DATABASE	METADATA
+	TYPE		dbEntry		TABLE		COLUMN		GIVENDBCOL	GIVENPS
+#	FPA.BIAS	METADATA
+#		TABLE	STR	Camera
+#		COLUMN	STR	gain
+#		chipId	STR	{CHIP.NAME}
+#		cellId	STR	{CELL.NAME}
+#		time	STR	{CELL.TIME}
+#	END
+#	CELL.GAIN	dbEntry		Camera		gain		chipId,cellId	CHIP.NAME,CELL.NAME
+#	CELL.READNOISE	dbEntry		Camera		readNoise	chipId,cellId	CHIP.NAME,CELL.NAME
+
+# A database entry refers to a particular column (COLUMN) in a
+# particular table (TABLE), given certain PS concepts (GIVENPS) that
+# match certain database columns (GIVENDBCOL).
+END
+
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+#	CHIP.X0		STR	FORTRAN
+#	CHIP.Y0		STR	FORTRAN
+#	CELL.X0		STR	FORTRAN
+#	CELL.Y0		STR	FORTRAN
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/ppImage.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/ppImage.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/ppImage.config	(revision 22157)
@@ -0,0 +1,150 @@
+### ppImage recipe configuration file for CTIO MOSAIC2
+
+# List of tasks to perform
+SHUTTER         BOOL    FALSE           # Shutter correction
+DARK		BOOL	TRUE		# Dark correction
+
+BASE.FITS       BOOL    TRUE            # Save base detrended image?
+CHIP.FITS       BOOL    TRUE            # Save base detrended image?
+BIN1.FITS	BOOL    TRUE            # Save 1st binned chip image?
+BIN2.FITS	BOOL    TRUE		# Save 2nd binned chip image?
+
+# binned output image options
+BIN1.XBIN               S32     8
+BIN1.YBIN               S32     8
+BIN2.XBIN               S32     64
+BIN2.YBIN               S32     64
+
+# Overscan subtraction
+OVERSCAN.SINGLE		BOOL	FALSE		# Reduce overscan to a single value?
+OVERSCAN.FIT		STR	POLYNOMIAL	# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER		S32	3		# Order of polynomial fit
+OVERSCAN.STAT		STR	MEAN		# MEAN | MEDIAN
+
+DETREND.CONSTRAINTS  METADATA
+  BIAS METADATA
+  END
+  DARK METADATA
+#   EXPTIME STR FPA.EXPOSURE
+  END
+  FLAT METADATA
+    FILTER  STR FPA.FILTERID
+#   DETTYPE STR DOMEFLAT.RAW
+#   VERSION STR SKYFLAT.RAW
+#   VERSION STR DOMEFLAT.CORR.XX
+#   VERSION STR SKYFLAT.CORR
+  END
+  FLAT_CORRECTION METADATA
+    FILTER   STR FPA.FILTER
+  END
+  FRINGE METADATA
+    FILTER   STR FPA.FILTER
+#   AIRMASS  STR FPA.AIRMASS
+#   TWILIGHT STR FPA.TWILIGHT
+  END
+  SHUTTER METADATA
+  END
+  MASK METADATA
+  END	
+END
+
+####################################################################################
+# Need the following in order to turn off options from the site-level recipes...
+#
+#  ESSENCE data does not need a fringe or a shutter correction.
+####################################################################################
+
+# Dark subtraction only
+PPIMAGE_D         METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           #  Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+END
+
+# Shutter correction only
+PPIMAGE_S         METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           #  Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+END
+
+# Overscan, bias, dark
+PPIMAGE_OBD      METADATA
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           #  Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+END
+
+# Overscan, bias, dark, shutter
+PPIMAGE_OBDS      METADATA
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           #  Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+END
+
+# Overscan, bias, dark, shutter, flat-field
+PPIMAGE_OBDSF     METADATA
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           #  Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe
+PPIMAGE_OBDSFR    METADATA
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+END
+
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photometry
+PPIMAGE_OBDSFRP   METADATA
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    TRUE            # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photometry & astrometry
+PPIMAGE_OBDSFRA   METADATA
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    TRUE            # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    TRUE            # Astrometry for mosaic?
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/ppMerge.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/ppMerge.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/ppMerge.config	(revision 22157)
@@ -0,0 +1,4 @@
+# Ordinates for fitting dark current
+DARK.ORDINATES	METADATA
+END
+DARK.NORM	STR	CELL.DARKTIME		# Dark normalisation concept
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/psastro.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/psastro.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/psastro.config	(revision 22157)
@@ -0,0 +1,76 @@
+
+# astrometry matching parameters
+
+# nominal plate scale (mm / pixel)
+PSASTRO.PIXEL.SCALE    F32  1.0
+
+# pmAstromGridAngle
+# max grid offset in FP units (microns)
+# use plate-scale to make this in pixels?
+PSASTRO.GRID.OFFSET    F32   1000.0
+PSASTRO.GRID.SCALE     F32     50.0
+
+# these tweak are in FP units (pixels, currently)
+PSASTRO.TWEAK.SCALE     F32      1
+PSASTRO.TWEAK.RANGE     F32     75
+PSASTRO.TWEAK.SMOOTH    F32      2
+PSASTRO.TWEAK.NSIGMA    F32      3
+
+# single-chip radius match in pixels
+PSASTRO.MATCH.FIT.NITER S32   3
+PSASTRO.MATCH.RADIUS    F32   12.0
+PSASTRO.MATCH.RADIUS.N0 F32   15.0
+PSASTRO.MATCH.RADIUS.N1 F32   10.0
+PSASTRO.MATCH.RADIUS.N2 F32    5.0
+
+PSASTRO.MAX.NRAW       S32      1500   # max stars accepted for fitting (0 for all)
+PSASTRO.MAX.NREF       S32      1500   # max stars accepted for fitting (0 for all)
+
+PSASTRO.MIN.INST.MAG.RAW       F32      -17.0  # min instrumental magnitude for stars accepted for fitting
+PSASTRO.MAX.INST.MAG.RAW       F32       -8.0  # max instrumental magnitude for stars accepted for fitting
+
+PSASTRO.MAX.ERROR      F32      10.0 # max error in pixels
+PSASTRO.MIN.NSTAR      S32      10   # min fitted stars in solution
+
+PSASTRO.MOSAIC.MAX.ERROR.N0 F32    1.50 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N1 F32    1.50 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N2 F32    0.90 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N3 F32    0.90 # max allow error for valid solution (arcsec)
+
+PSASTRO.MOSAIC.RADIUS.N0    F32   10.0 # initial match (after chip astrom)
+PSASTRO.MOSAIC.RADIUS.N1    F32    8.0 # (after first mosaic pass)
+PSASTRO.MOSAIC.RADIUS.N2    F32    8.0 # do not refine the match
+PSASTRO.MOSAIC.RADIUS.N3    F32    4.0 # 
+
+PSASTRO.MOSAIC.CHIP.ORDER.N0  S32      0 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N1  S32      1 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N2  S32      1 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N3  S32      3 # fit order (-1 means use default)
+
+PSASTRO.MOSAIC.GRADIENT.NX    S32     2   # number of x-dir cells per chip
+PSASTRO.MOSAIC.GRADIENT.NY    S32     4   # number of y-dir cells per chip
+
+PSASTRO.MOSAIC.MODE           BOOL     FALSE
+
+# use this recipe to set a tight constraint
+PSASTRO.FINE METADATA
+  PSASTRO.MOSAIC.MAX.ERROR.N0 F32    1.50 # max allow error for valid solution (arcsec)
+  PSASTRO.MOSAIC.MAX.ERROR.N1 F32    1.50 # max allow error for valid solution (arcsec)
+  PSASTRO.MOSAIC.MAX.ERROR.N2 F32    0.90 # max allow error for valid solution (arcsec)
+  PSASTRO.MOSAIC.MAX.ERROR.N3 F32    0.90 # max allow error for valid solution (arcsec)
+
+  PSASTRO.MOSAIC.RADIUS.N0    F32    8.0
+  PSASTRO.MOSAIC.RADIUS.N1    F32    0.0
+  PSASTRO.MOSAIC.RADIUS.N2    F32    4.0
+  PSASTRO.MOSAIC.RADIUS.N3    F32    4.0
+
+  PSASTRO.MOSAIC.CHIP.ORDER.N0  S32      0 # fit order (-1 means use default)
+  PSASTRO.MOSAIC.CHIP.ORDER.N1  S32      1 # fit order (-1 means use default)
+  PSASTRO.MOSAIC.CHIP.ORDER.N2  S32      1 # fit order (-1 means use default)
+  PSASTRO.MOSAIC.CHIP.ORDER.N3  S32      3 # fit order (-1 means use default)
+END
+
+PSASTRO.GRID.MIN.SCALE F32  0.98
+PSASTRO.GRID.MAX.SCALE F32  1.04
+PSASTRO.GRID.DEL.SCALE F32  0.02
+PSASTRO.GRID.MIN.SIGMA F32  5.0
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/psphot.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/psphot.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/psphot.config	(revision 22157)
@@ -0,0 +1,42 @@
+	
+# turn these on to see specific outputs
+SAVE.RESID	BOOL 	TRUE
+
+IMSTATS_NPIX        S32  1000    	 # number of pixels to use for sky estimate boxes:
+
+PSF_SN_LIM          F32  100             # minimum S/N for stars used for PSF model
+PSF_MAX_NSTARS      S32  300             # limit number of stars used for PSF model
+
+# PSF model parameters : choose the PSF model
+# list as many PSF_MODEL options as desired
+# if you want to list multiple entries, uncomment 'MULTI' here
+# PSF_MODEL           MULTI
+PSF_MODEL           STR  PS_MODEL_QGAUSS
+# PSF_MODEL           STR  PS_MODEL_GAUSS
+# PSF_MODEL           STR  PS_MODEL_PGAUSS
+# PSF_MODEL           STR  PS_MODEL_TGAUSS  ## not well tested, not very successful
+
+MOMENTS_SN_MIN      F32  20.0           # min S/N to measure moments
+EXT_MIN_SN           F32  20.0           # fit galaxies above this S/N limit
+FULL_FIT_SN_LIM      F32  20.0
+AP_MIN_SN            F32  25.0
+
+USE_FOOTPRINTS                      BOOL  TRUE       	  # use new pmFootprint peak packaging
+
+## gene's overrides for testing:
+PEAKS_NSIGMA_LIMIT_2 F32  10.0 	   	 # peak significance threshold
+
+EXT_MODEL           STR  PS_MODEL_QGAUSS
+
+TEST_FIT            BOOL FALSE
+TEST_FIT_X          F32  781
+TEST_FIT_Y          F32  830
+
+TEST_FIT_MODE       STR  CONV
+#TEST_FIT_MODE       STR PSF
+#TEST_FIT_MODE       STR  EXT
+
+#TEST_FIT_MODEL      STR  PS_MODEL_SERSIC
+TEST_FIT_MODEL      STR  PS_MODEL_QGAUSS
+TEST_MOMENTS_RADIUS F32 9.0
+OUTPUT.FORMAT       STR PS1_DEV_1
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/rejections.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/rejections.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/mosaic2/rejections.config	(revision 22157)
@@ -0,0 +1,184 @@
+
+BIAS METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  2.0
+  IMFILE.STDEV       F32 10.0
+  IMFILE.MEANSTDEV   F32  1.0
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.5
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  1.0
+  EXP.STDEV          F32 10.0
+  EXP.MEANSTDEV      F32  0.5
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  3.0
+  ENSEMBLE.STDEV     F32  3.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+DARK METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  2.0
+  IMFILE.STDEV       F32 10.0
+  IMFILE.MEANSTDEV   F32  1.0
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.0
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  1.0
+  EXP.STDEV          F32 10.0
+  EXP.MEANSTDEV      F32  0.5
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  3.0
+  ENSEMBLE.STDEV     F32  3.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+SHUTTER METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  0.0
+  IMFILE.STDEV       F32  0.0
+  IMFILE.MEANSTDEV   F32  0.0
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.0
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  0.0
+  EXP.STDEV          F32  0.0
+  EXP.MEANSTDEV      F32  0.0
+  EXP.SNR            F32 20.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  0.0
+  ENSEMBLE.STDEV     F32  0.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+
+FLAT METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  0.0
+  IMFILE.STDEV       F32  0.0
+  IMFILE.MEANSTDEV   F32  0.0
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.0
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  0.0
+  EXP.STDEV          F32  0.0
+  EXP.MEANSTDEV      F32  0.0
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  0.0
+  ENSEMBLE.STDEV     F32  0.0
+  ENSEMBLE.MEANSTDEV F32  3.0
+END
+
+
+FRINGE METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  0.0
+  IMFILE.STDEV       F32  0.0
+  IMFILE.MEANSTDEV   F32  0.0
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.0
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  0.0
+  EXP.STDEV          F32  0.0
+  EXP.MEANSTDEV      F32  0.0
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  0.0
+  ENSEMBLE.STDEV     F32  0.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+DARKMASK METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  0.0
+  IMFILE.STDEV       F32  0.0
+  IMFILE.MEANSTDEV   F32  0.0
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.0
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  0.0
+  EXP.STDEV          F32  0.0
+  EXP.MEANSTDEV      F32  0.0
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  0.0
+  ENSEMBLE.STDEV     F32  0.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+FLATMASK METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  0.0
+  IMFILE.STDEV       F32  0.0
+  IMFILE.MEANSTDEV   F32  0.0
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.0
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  0.0
+  EXP.STDEV          F32  0.0
+  EXP.MEANSTDEV      F32  0.0
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  0.0
+  ENSEMBLE.STDEV     F32  0.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+# FILTER is an additional qualifier, and may be "*" (or absent!), in which case it matches everything
+# EXPECTED is the expected mean value
+# IMFILE.MEAN is the maximum permitted mean value for an imfile, relative to the standard deviation
+# IMFILE.STDEV is the maximum permitted standard deviation for an imfile
+# EXP.MEAN is the maximum permitted mean value for an exposure, relative to the standard deviation
+# EXP.STDEV is the maximum permitted standard deviation for an exposure
+# EXP.MEANSTDEV is the maximum permitted mean standard deviation for an exposure relative to the mean
+# ENSEMBLE.MEAN is the maximum permitted mean for an ensemble of exposures
+# ENSEMBLE.STDEV is the maximum permitted standard deviation for an ensemble of exposures
+# ENSEMBLE.MEANSTDEV is the maximum permitted mean standard deviation for an ensemble of exposures
+# IMFILE.SNR is the minimum permitted signal-to-noise for an imfile
+# EXP.SNR is the minimum permitted signal-to-noise for an exposure
+# These values (all except FILTER) may be zero, in which case no clipping is applied.
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/notes.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/notes.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/notes.txt	(revision 22157)
@@ -0,0 +1,16 @@
+
+creating a new camera:
+
+* add the camera to the CAMERAS list (ipp/ippconfig/ipprc.config)
+* create the camera directory (ipp/ippconfig/CAMERA)
+* copy example recipe files from a similar camera (eg, ipp/ippconfig/megacam)
+* copy Makefile.am from a similar camera (eg, ipp/ippconfig/megacam)
+* edit camera.config to describe your camera
+  - list the ccds and chips in FPA METADATA
+  - list the filters of interest (header name STR internal name)
+    give all varients used in the headers
+  - fix paths which refer to the original paths.
+* edit the format*.config files to match your camera:
+  - set up a rule to recognize the camera
+    - define keywords which will uniquely recognize your headers
+     
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/.cvsignore	(revision 22157)
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/Makefile.am	(revision 22157)
@@ -0,0 +1,29 @@
+
+installdir = $(datadir)/ippconfig/recipes
+
+install_files = \
+	filerules-simple.mdc \
+	filerules-mef.mdc \
+	filerules-split.mdc \
+	fitstypes.mdc \
+	masks.config \
+	reductionClasses.mdc \
+	rejections.config \
+	ppImage.config \
+	ppMerge.config \
+	ppStack.config \
+	ppStats.config \
+	psastro.config \
+	psphot.config \
+	pswarp.config \
+	ppSim.config \
+	ppSub.config
+
+install_DATA = $(install_files)
+
+install-data-hook:
+	chmod 0755 $(installdir)
+
+EXTRA_DIST = $(install_files)
+
+ACLOCAL_AMFLAGS = -I m4
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/filerules-mef.mdc
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/filerules-mef.mdc	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/filerules-mef.mdc	(revision 22157)
@@ -0,0 +1,226 @@
+### File rules for PHU=FPA, EXT=CHIP (eg, Megacam)
+### this file specifies compression types for various output files
+
+### Redirections (MEF)
+PPIMAGE.OUTPUT        STR PPIMAGE.OUTPUT.MEF
+PPIMAGE.OUTPUT.MASK   STR PPIMAGE.OUT.MK.MEF
+PPIMAGE.OUTPUT.WEIGHT STR PPIMAGE.OUT.WT.MEF
+
+PPIMAGE.CHIP          STR PPIMAGE.CHIP.MEF
+PPIMAGE.CHIP.MASK     STR PPIMAGE.CHIP.MK.MEF
+PPIMAGE.CHIP.WEIGHT   STR PPIMAGE.CHIP.WT.MEF
+
+PPIMAGE.BIN1          STR PPIMAGE.BIN1.MEF
+PPIMAGE.BIN2          STR PPIMAGE.BIN2.MEF
+
+PSASTRO.OUTPUT        STR PSASTRO.OUT.CMF.MEF
+PSPHOT.OUTPUT         STR PSPHOT.OUT.CMF.MEF
+
+### Redirections (SPL)
+# PPIMAGE.OUTPUT        STR PPIMAGE.OUTPUT.SPL
+# PPIMAGE.OUTPUT.MASK   STR PPIMAGE.OUT.MK.SPL
+# PPIMAGE.OUTPUT.WEIGHT STR PPIMAGE.OUT.WT.SPL
+# 
+# PPIMAGE.CHIP          STR PPIMAGE.CHIP.SPL
+# PPIMAGE.CHIP.MASK     STR PPIMAGE.CHIP.MK.SPL
+# PPIMAGE.CHIP.WEIGHT   STR PPIMAGE.CHIP.WT.SPL
+# 
+# PPIMAGE.BIN1          STR PPIMAGE.BIN1.SPL
+# PPIMAGE.BIN2          STR PPIMAGE.BIN2.SPL
+# 
+# PSASTRO.OUTPUT        STR PSASTRO.OUT.CMF.SPL
+# PSPHOT.OUTPUT         STR PSPHOT.OUT.CMF.SPL
+
+### Redirections (common)
+
+PPIMAGE.OUTPUT.FPA1   STR PPIMAGE.OUTPUT.FPA1.MEF
+PPIMAGE.OUTPUT.FPA2   STR PPIMAGE.OUTPUT.FPA2.MEF
+PPSTAMP.CHIP          STR PPSTAMP.CHIP.MEF
+PSASTRO.INPUT         STR PSASTRO.INPUT.CMF
+PSASTRO.OUTPUT.MEF    STR PSASTRO.OUT.CMF.MEF
+DVOCORR.OUTPUT        STR DVOCORR.MEF.OUTPUT
+DVOFLAT.OUTPUT        STR DVOFLAT.MEF.OUTPUT
+
+### input file definitions
+### use @DETDB entries to get the detrend images from the database
+### replace @DETDB with @FILES if you want to require it from the 
+### command line, or with an explicit name to require a specific file
+TYPE                    INPUT    FILENAME.RULE DATA.LEVEL FILE.TYPE
+
+## files used by ppImage
+PPIMAGE.INPUT           INPUT    @FILES        CHIP       IMAGE
+PPIMAGE.INPUT.MASK      INPUT    @FILES        CHIP       MASK
+PPIMAGE.INPUT.WEIGHT    INPUT    @FILES        CHIP       WEIGHT
+PPIMAGE.INPUT.PSF       INPUT    @FILES        CHIP       PSF
+PPIMAGE.INPUT.SRC       INPUT    @FILES        CHIP       CMF
+PPIMAGE.MASK       	INPUT 	 @DETDB        CHIP       MASK
+PPIMAGE.BIAS       	INPUT 	 @DETDB        CHIP       IMAGE
+PPIMAGE.DARK       	INPUT 	 @DETDB        CHIP       DARK
+PPIMAGE.FLAT       	INPUT 	 @DETDB        CHIP       IMAGE
+PPIMAGE.FRINGE     	INPUT 	 @DETDB        CHIP       FRINGE
+PPIMAGE.SHUTTER    	INPUT 	 @DETDB        CHIP       IMAGE     
+
+## Files used by ppMerge
+PPMERGE.INPUT           INPUT 	 @FILES        CHIP       IMAGE
+PPMERGE.INPUT.MASK      INPUT 	 @FILES        CHIP       MASK
+PPMERGE.INPUT.WEIGHT    INPUT 	 @FILES        CHIP       WEIGHT
+
+## files used to build and apply the flat-field correction images
+DVOCORR.INPUT      	INPUT 	 @FILES        CHIP       IMAGE
+DVOCORR.REFHEAD    	INPUT 	 @FILES        CHIP       HEADER
+DVOFLAT.INPUT      	INPUT 	 @FILES        CHIP       IMAGE
+DVOFLAT.CORR       	INPUT 	 @DETDB        CHIP       IMAGE
+
+## files used by psphot 
+PSPHOT.LOAD        	INPUT 	 @FILES        CHIP       IMAGE
+PSPHOT.INPUT       	INPUT 	 @FILES        CHIP       IMAGE
+PSPHOT.MASK        	INPUT 	 @FILES        CHIP       MASK     
+PSPHOT.WEIGHT      	INPUT 	 @FILES        CHIP       WEIGHT     
+PSPHOT.PSF.LOAD    	INPUT 	 @FILES        CHIP       PSF       
+PSPHOT.INPUT.CMF   	INPUT 	 @FILES        CHIP       CMF       
+
+## files used by psastro 
+PSASTRO.INPUT.CMP  	INPUT 	 @FILES        CHIP       CMP
+PSASTRO.INPUT.CMF  	INPUT 	 @FILES        CHIP       CMF
+PSASTRO.WCS        	INPUT 	 @FILES        CHIP       CMF
+PSASTRO.MODEL      	INPUT 	 @DETDB        FPA        ASTROM
+
+## files used by pswarp
+PSWARP.INPUT       	INPUT 	 @FILES        CHIP       IMAGE
+PSWARP.WEIGHT      	INPUT 	 @FILES        CHIP       WEIGHT
+PSWARP.MASK        	INPUT 	 @FILES        CHIP       MASK
+PSWARP.SKYCELL     	INPUT 	 @FILES        CHIP       IMAGE
+PSWARP.ASTROM      	INPUT 	 @FILES        CHIP       CMF
+
+## files used by ppsub
+PPSUB.INPUT        	INPUT 	 @FILES        FPA        IMAGE
+PPSUB.INPUT.MASK   	INPUT 	 @FILES        FPA        MASK
+PPSUB.INPUT.WEIGHT 	INPUT 	 @FILES        FPA        WEIGHT
+PPSUB.REF          	INPUT 	 @FILES        FPA        IMAGE
+PPSUB.REF.MASK     	INPUT 	 @FILES        FPA        MASK
+PPSUB.REF.WEIGHT   	INPUT 	 @FILES        FPA        WEIGHT
+PPSUB.SOURCES      	INPUT 	 @FILES        FPA        CMF
+
+## files used by ppstack
+PPSTACK.INPUT      	INPUT 	 @FILES        FPA        IMAGE
+PPSTACK.INPUT.MASK 	INPUT 	 @FILES        FPA        MASK
+PPSTACK.INPUT.WEIGHT    INPUT 	 @FILES        FPA        WEIGHT
+PPSTACK.INPUT.PSF  	INPUT 	 @FILES        CHIP       PSF
+PPSTACK.INPUT.SOURCES  	INPUT 	 @FILES        FPA        CMF
+
+## files used by ppstamp
+PPSTAMP.INPUT           INPUT 	 @FILES        CHIP       IMAGE
+
+# files used by pparith
+PPARITH.INPUT.IMAGE 	INPUT 	 @FILES        CHIP       IMAGE
+PPARITH.INPUT.MASK  	INPUT 	 @FILES        CHIP       MASK
+
+### output file definitions
+TYPE                    OUTPUT FILENAME.RULE                     FILE.TYPE FITS.TYPE  DATA.LEVEL FILE.SAVE FILE.FORMAT
+PPIMAGE.OUTPUT.MEF      OUTPUT {OUTPUT}.b0.fits                  IMAGE     COMP_IMG   CHIP       TRUE      MEF
+PPIMAGE.OUT.MK.MEF      OUTPUT {OUTPUT}.mk.fits                  MASK      COMP_MASK  CHIP       TRUE      MEF
+PPIMAGE.OUT.WT.MEF      OUTPUT {OUTPUT}.wt.fits                  WEIGHT    COMP_WT    CHIP       TRUE      MEF
+PPIMAGE.OUTPUT.SPL      OUTPUT {OUTPUT}.{CHIP.NAME}.b0.fits      IMAGE     NONE       CHIP       TRUE      SPLIT
+PPIMAGE.OUT.MK.SPL      OUTPUT {OUTPUT}.{CHIP.NAME}.mk.fits      MASK      NONE       CHIP       TRUE      SPLIT
+PPIMAGE.OUT.WT.SPL      OUTPUT {OUTPUT}.{CHIP.NAME}.wt.fits      WEIGHT    NONE       CHIP       TRUE      SPLIT
+PPIMAGE.OUTPUT.DETMASK  OUTPUT {OUTPUT}.fits                     IMAGE     MASK       CHIP       TRUE      MEF
+PPIMAGE.OUTPUT.RESID    OUTPUT {OUTPUT}.b0.fits                  IMAGE     COMP_SUB   CHIP       TRUE      MEF
+		        					        		     
+PPIMAGE.CHIP.MEF        OUTPUT {OUTPUT}.ch.fits                  IMAGE     COMP_IMG   CHIP       TRUE      MEF
+PPIMAGE.CHIP.MK.MEF     OUTPUT {OUTPUT}.ch.mk.fits               MASK      COMP_MASK  CHIP       TRUE      MEF
+PPIMAGE.CHIP.WT.MEF     OUTPUT {OUTPUT}.ch.wt.fits               WEIGHT    COMP_WT    CHIP       TRUE      MEF
+PPIMAGE.CHIP.SPL        OUTPUT {OUTPUT}.{CHIP.NAME}.ch.fits      IMAGE     NONE       CHIP       TRUE      SPLIT
+PPIMAGE.CHIP.MK.SPL     OUTPUT {OUTPUT}.{CHIP.NAME}.ch.mk.fits   MASK      NONE       CHIP       TRUE      SPLIT
+PPIMAGE.CHIP.WT.SPL     OUTPUT {OUTPUT}.{CHIP.NAME}.ch.wt.fits   WEIGHT    NONE       CHIP       TRUE      SPLIT
+							        		     
+PPIMAGE.OUTPUT.FPA1.MEF OUTPUT {OUTPUT}.b1.fits                  IMAGE     COMP_IMG   FPA        TRUE      MEF
+PPIMAGE.OUTPUT.FPA2.MEF OUTPUT {OUTPUT}.b2.fits                  IMAGE     COMP_IMG   FPA        TRUE      MEF
+PPIMAGE.OUTPUT.FPA1.SPL OUTPUT {OUTPUT}.{CHIP.NAME}.b1.fits      IMAGE     NONE       CHIP       TRUE      SPLIT
+PPIMAGE.OUTPUT.FPA2.SPL OUTPUT {OUTPUT}.{CHIP.NAME}.b2.fits      IMAGE     NONE       CHIP       TRUE      SPLIT
+							        		     
+PPIMAGE.BIN1.MEF      	OUTPUT {OUTPUT}.b1c.fits                 IMAGE     COMP_IMG   CHIP       TRUE      MEF
+PPIMAGE.BIN2.MEF      	OUTPUT {OUTPUT}.b2c.fits                 IMAGE     COMP_IMG   CHIP       TRUE      MEF
+PPIMAGE.BIN1.SPL      	OUTPUT {OUTPUT}.{CHIP.NAME}.b1.fits      IMAGE     COMP_IMG   CHIP       TRUE      SPLIT
+PPIMAGE.BIN2.SPL      	OUTPUT {OUTPUT}.{CHIP.NAME}.b2.fits      IMAGE     COMP_IMG   CHIP       TRUE      SPLIT
+		      						       		     
+PPIMAGE.JPEG1         	OUTPUT {OUTPUT}.b1.jpg                   JPEG      NONE       FPA        TRUE      NONE
+PPIMAGE.JPEG2         	OUTPUT {OUTPUT}.b2.jpg                   JPEG      NONE       FPA        TRUE      NONE
+		      						       		     
+PPIMAGE.STATS         	OUTPUT {OUTPUT}.stats                    STATS     NONE       FPA        TRUE      MEF
+
+## note: these use the same output naming convention since they are used for different ppMerge runs
+PPMERGE.OUTPUT.MASK     OUTPUT {OUTPUT}.fits                   	 MASK      NONE       CHIP       TRUE      NONE
+PPMERGE.OUTPUT.BIAS     OUTPUT {OUTPUT}.fits                   	 IMAGE     NONE       CHIP       TRUE      NONE
+PPMERGE.OUTPUT.DARK     OUTPUT {OUTPUT}.fits                   	 DARK      NONE       CHIP       TRUE      NONE
+PPMERGE.OUTPUT.SHUTTER  OUTPUT {OUTPUT}.fits                   	 IMAGE     NONE       CHIP       TRUE      NONE
+PPMERGE.OUTPUT.FLAT     OUTPUT {OUTPUT}.fits                   	 IMAGE     NONE       CHIP       TRUE      NONE
+PPMERGE.OUTPUT.FRINGE   OUTPUT {OUTPUT}.fits                   	 FRINGE    NONE       CHIP       TRUE      NONE
+PPMERGE.OUTPUT.SIGMA    OUTPUT {OUTPUT}.sigma.fits             	 IMAGE     NONE       CHIP       TRUE      NONE
+PPMERGE.OUTPUT.COUNT    OUTPUT {OUTPUT}.count.fits             	 IMAGE     NONE       CHIP       TRUE      NONE
+							        		     
+DVOCORR.MEF.OUTPUT    	OUTPUT {OUTPUT}.fc.fits                  IMAGE     NONE       CHIP       TRUE      MEF
+DVOCORR.SPL.OUTPUT    	OUTPUT {OUTPUT}.{CHIP.NAME}.fc.fits      IMAGE     NONE       CHIP       TRUE      SPLIT
+DVOFLAT.MEF.OUTPUT    	OUTPUT {OUTPUT}.co.fits                  IMAGE     NONE       CHIP       TRUE      MEF
+DVOFLAT.SPL.OUTPUT    	OUTPUT {OUTPUT}.{CHIP.NAME}.co.fits      IMAGE     NONE       CHIP       TRUE      SPLIT
+		      						       		     
+PSPHOT.RESID          	OUTPUT {OUTPUT}.res.fits                 IMAGE     NONE       CHIP       FALSE     MEF
+PSPHOT.BACKGND        	OUTPUT {OUTPUT}.bck.fits                 IMAGE     NONE       CHIP       FALSE     MEF
+PSPHOT.BACKSUB        	OUTPUT {OUTPUT}.sub.fits                 IMAGE     NONE       CHIP       FALSE     MEF
+PSPHOT.BACKMDL        	OUTPUT {OUTPUT}.mdl.fits                 IMAGE     NONE       CHIP       FALSE     MEF
+		      						       		     
+PSPHOT.OUTPUT.RAW     	OUTPUT {OUTPUT}.{CHIP.NAME}              RAW       NONE       CHIP       TRUE      NONE
+PSPHOT.OUTPUT.SX      	OUTPUT {OUTPUT}.{CHIP.NAME}.sx           SX        NONE       CHIP       TRUE      NONE
+PSPHOT.OUTPUT.OBJ     	OUTPUT {OUTPUT}.{CHIP.NAME}.obj          OBJ       NONE       CHIP       TRUE      NONE
+PSPHOT.OUTPUT.CMP     	OUTPUT {OUTPUT}.{CHIP.NAME}.cmp          CMP       NONE       CHIP       TRUE      NONE
+PSPHOT.OUT.CMF.SPL    	OUTPUT {OUTPUT}.{CHIP.NAME}.cmf          CMF       NONE       CHIP       TRUE      SPLIT
+PSPHOT.OUT.CMF.MEF    	OUTPUT {OUTPUT}.cmf                      CMF       NONE       CHIP       TRUE      MEF
+PSPHOT.PSF.SAVE       	OUTPUT {OUTPUT}.psf                      PSF       NONE       CHIP       TRUE      MEF
+		      						       		     
+SOURCE.PLOT.MOMENTS   	OUTPUT {OUTPUT}.mnt.png                  KAPA      NONE       FPA        TRUE      NONE
+SOURCE.PLOT.PSFMODEL  	OUTPUT {OUTPUT}.psf.png                  KAPA      NONE       FPA        TRUE      NONE
+SOURCE.PLOT.APRESID   	OUTPUT {OUTPUT}.dap.png                  KAPA      NONE       FPA        TRUE      NONE
+		      						       		     
+PSASTRO.OUTPUT.CMP    	OUTPUT {OUTPUT}.{CHIP.NAME}.smp          CMP       NONE       CHIP       TRUE      NONE
+PSASTRO.OUT.CMF.SPL   	OUTPUT {OUTPUT}.{CHIP.NAME}.smf          CMF       NONE       CHIP       TRUE      SPLIT
+PSASTRO.OUT.CMF.MEF   	OUTPUT {OUTPUT}.smf                      CMF       NONE       FPA        TRUE      MEF
+PSASTRO.OUT.MODEL     	OUTPUT {OUTPUT}.asm                ASTROM.MODEL    NONE       FPA        TRUE      NONE
+PSASTRO.OUT.REFSTARS  	OUTPUT {OUTPUT}.aref.fits          ASTROM.REFSTARS NONE       FPA        TRUE      NONE
+PSASTRO.STATS           OUTPUT {OUTPUT}.stats                    STATS     NONE       FPA        TRUE      NONE
+	      									     
+PSWARP.OUTPUT         	OUTPUT {OUTPUT}.fits                     IMAGE     COMP_IMG   FPA        TRUE      NONE
+PSWARP.OUTPUT.MASK    	OUTPUT {OUTPUT}.mk.fits                  MASK      COMP_MASK  FPA        TRUE      NONE
+PSWARP.OUTPUT.WEIGHT  	OUTPUT {OUTPUT}.wt.fits                  WEIGHT    COMP_WT    FPA        TRUE      NONE
+PSWARP.OUTPUT.SOURCES 	OUTPUT {OUTPUT}.cmf                      CMF       NONE       FPA        TRUE      NONE
+PSWARP.BIN1           	OUTPUT {OUTPUT}.b1.fits                  IMAGE     COMP_IMG   FPA        TRUE      NONE
+PSWARP.BIN2           	OUTPUT {OUTPUT}.b2.fits                  IMAGE     COMP_IMG   FPA        TRUE      NONE
+		      						       		     
+SKYCELL.STATS         	OUTPUT {OUTPUT}.stats                    STATS     NONE       FPA        TRUE      NONE
+SKYCELL.TEMPLATE      	OUTPUT {OUTPUT}.skycell                  SKYCELL   NONE       FPA        TRUE      NONE
+		      						       		     
+PPSUB.OUTPUT          	OUTPUT {OUTPUT}.fits                     IMAGE     COMP_IMG   FPA        TRUE      NONE
+PPSUB.OUTPUT.MASK     	OUTPUT {OUTPUT}.mk.fits                  MASK      COMP_MASK  FPA        TRUE      NONE
+PPSUB.OUTPUT.WEIGHT   	OUTPUT {OUTPUT}.wt.fits                  WEIGHT    COMP_WT    FPA        TRUE      NONE
+		      						       		     
+PPSTACK.OUTPUT        	OUTPUT {OUTPUT}.fits                     IMAGE     COMP_IMG   FPA        TRUE      NONE
+PPSTACK.OUTPUT.MASK   	OUTPUT {OUTPUT}.mk.fits                  MASK      COMP_MASK  FPA        TRUE      NONE
+PPSTACK.OUTPUT.WEIGHT 	OUTPUT {OUTPUT}.wt.fits                  WEIGHT    COMP_WT    FPA        TRUE      NONE
+		      						       		     
+PPSTAMP.OUTPUT        	OUTPUT {OUTPUT}.fits                     IMAGE     NONE       FPA        TRUE      NONE
+PPSTAMP.CHIP.MEF      	OUTPUT {OUTPUT}.ch.fits                  IMAGE     NONE       CHIP       FALSE     MEF
+		      						       		     
+PPSIM.OUTPUT.MEF      	OUTPUT {OUTPUT}.fits                     IMAGE     NONE       CHIP       TRUE      MEF
+PPSIM.OUTPUT.SPL      	OUTPUT {OUTPUT}.{CHIP.NAME}.fits         IMAGE     NONE       CHIP       TRUE      SPLIT
+PPSIM.FAKE.CHIP        	OUTPUT {OUTPUT}.fake.fits     		 IMAGE     NONE       FPA        TRUE      SIMPLE
+PPSIM.FORCE.CHIP       	OUTPUT {OUTPUT}.force.fits     		 IMAGE     NONE       FPA        TRUE      SIMPLE
+PPSIM.SOURCES         	OUTPUT {OUTPUT}.cmf                      CMF       NONE       FPA        TRUE      MEF
+PPSIM.FAKE.SOURCES     	OUTPUT {OUTPUT}.fake.cmf                 CMF       NONE       FPA        TRUE      NONE
+PPSIM.FORCE.SOURCES    	OUTPUT {OUTPUT}.force.cmf                CMF       NONE       FPA        TRUE      NONE
+		      						       		     
+PPARITH.OUTPUT.IMAGE  	OUTPUT {OUTPUT}.fits                     IMAGE     COMP_IMG   CHIP       TRUE      NONE
+PPARITH.OUTPUT.MASK   	OUTPUT {OUTPUT}.fits                     MASK      COMP_MASK  CHIP       TRUE      NONE
+		      						       		     
+LOG.IMFILE            	OUTPUT {OUTPUT}.{CHIP.NAME}.log          TEXT      NONE       CHIP       TRUE      NONE
+LOG.EXP               	OUTPUT {OUTPUT}.log                      TEXT      NONE       FPA        TRUE      NONE
+		      						       		     
+TRACE.IMFILE          	OUTPUT {OUTPUT}.{CHIP.NAME}.trace        TEXT      NONE       CHIP       TRUE      NONE
+TRACE.EXP             	OUTPUT {OUTPUT}.trace                    TEXT      NONE       FPA        TRUE      NONE
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/filerules-simple.mdc
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/filerules-simple.mdc	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/filerules-simple.mdc	(revision 22157)
@@ -0,0 +1,178 @@
+### File rules for PHU=FPA, EXT=NONE
+
+PSASTRO.INPUT      STR PSASTRO.INPUT.CMF
+PSASTRO.OUTPUT     STR PSASTRO.OUTPUT.CMF
+PSASTRO.OUTPUT.MEF STR PSASTRO.OUTPUT.CMF
+PSPHOT.OUTPUT      STR PSPHOT.OUTPUT.CMF
+
+### input file definitions
+### use @DETDB entries to get the detrend images from the database
+### replace @DETDB with @FILES if you want to require it from the 
+### command line, or with an explicit name to require a specific file
+TYPE                    INPUT    FILENAME.RULE DATA.LEVEL FILE.TYPE
+
+## files used by ppImage
+PPIMAGE.INPUT           INPUT    @FILES        FPA        IMAGE     
+PPIMAGE.INPUT.MASK      INPUT    @FILES        FPA        MASK      
+PPIMAGE.INPUT.WEIGHT    INPUT    @FILES        FPA        WEIGHT    
+PPIMAGE.INPUT.PSF       INPUT    @FILES        READOUT    PSF       
+PPIMAGE.INPUT.SRC       INPUT    @FILES        READOUT    CMF       
+PPIMAGE.MASK       	INPUT    @DETDB        CHIP       IMAGE     
+PPIMAGE.BIAS       	INPUT    @DETDB        CHIP       IMAGE     
+PPIMAGE.DARK       	INPUT    @DETDB        CHIP       DARK     
+PPIMAGE.FLAT       	INPUT    @DETDB        CHIP       IMAGE     
+PPIMAGE.FRINGE     	INPUT 	 @DETDB        CHIP       FRINGE
+PPIMAGE.SHUTTER    	INPUT    @DETDB        CHIP       IMAGE     
+
+## Files used by ppMerge
+PPMERGE.INPUT      	INPUT    @FILES        CHIP       IMAGE
+PPMERGE.INPUT.MASK 	INPUT    @FILES        CHIP       MASK
+PPMERGE.INPUT.WEIGHT    INPUT    @FILES        CHIP       WEIGHT
+
+## files used to build and apply the flat-field correction images
+DVOCORR.INPUT      	INPUT 	 @FILES        CHIP       IMAGE
+DVOCORR.REFHEAD    	INPUT 	 @FILES        CHIP       HEADER
+DVOFLAT.INPUT      	INPUT 	 @FILES        CHIP       IMAGE
+DVOFLAT.CORR       	INPUT 	 @DETDB        CHIP       IMAGE
+
+## files used by psphot 
+PSPHOT.LOAD        	INPUT    @FILES        CHIP       IMAGE
+PSPHOT.INPUT       	INPUT    @FILES        CHIP       IMAGE     
+PSPHOT.MASK             INPUT    @FILES        CHIP       MASK     
+PSPHOT.WEIGHT           INPUT    @FILES        CHIP       WEIGHT     
+PSPHOT.PSF.LOAD         INPUT    @FILES        CHIP       PSF       
+PSPHOT.INPUT.CMF        INPUT    @FILES        CHIP       CMF       
+
+## files used by psastro 
+PSASTRO.INPUT.CMP  	INPUT    @FILES        CHIP       CMP       
+PSASTRO.INPUT.CMF  	INPUT    @FILES        CHIP       CMF       
+PSASTRO.WCS             INPUT    @FILES        CHIP       CMF
+PSASTRO.MODEL           INPUT    @DETDB        FPA        ASTROM
+
+## files used by pswarp
+PSWARP.INPUT       	INPUT    @FILES        FPA        IMAGE     
+PSWARP.WEIGHT      	INPUT    @FILES        FPA        WEIGHT
+PSWARP.MASK        	INPUT    @FILES        FPA        MASK
+PSWARP.SKYCELL     	INPUT    @FILES        FPA        IMAGE     
+PSWARP.ASTROM      	INPUT    @FILES        FPA        CMF
+
+## files used by ppsub
+PPSUB.INPUT        	INPUT    @FILES        FPA        IMAGE
+PPSUB.INPUT.MASK   	INPUT    @FILES        FPA        MASK
+PPSUB.INPUT.WEIGHT 	INPUT    @FILES        FPA        WEIGHT
+PPSUB.REF          	INPUT    @FILES        FPA        IMAGE
+PPSUB.REF.MASK     	INPUT    @FILES        FPA        MASK
+PPSUB.REF.WEIGHT   	INPUT    @FILES        FPA        WEIGHT
+PPSUB.SOURCES      	INPUT 	 @FILES        FPA        CMF
+
+## files used by ppstack
+PPSTACK.INPUT      	INPUT    @FILES        FPA        IMAGE
+PPSTACK.INPUT.MASK 	INPUT    @FILES        FPA        MASK
+PPSTACK.INPUT.WEIGHT    INPUT    @FILES        FPA        WEIGHT
+PPSTACK.INPUT.PSF  	INPUT 	 @FILES        CHIP       PSF
+PPSTACK.INPUT.SOURCES   INPUT    @FILES        FPA        CMF
+
+## files used by ppstamp
+PPSTAMP.INPUT           INPUT    @FILES        FPA        IMAGE
+
+# files used by pparith
+PPARITH.INPUT.IMAGE    	INPUT    @FILES        CHIP       IMAGE
+PPARITH.INPUT.MASK     	INPUT    @FILES        CHIP       MASK
+
+PPSIM.INPUT             INPUT    @FILES        FPA        IMAGE     
+PPSIM.REAL.SOURCES      INPUT    @FILES        CHIP       CMF       
+
+### output file definitions
+TYPE                    OUTPUT FILENAME.RULE         FILE.TYPE FITS.TYPE  DATA.LEVEL FILE.SAVE FILE.FORMAT
+PPIMAGE.OUTPUT          OUTPUT {OUTPUT}.fits         IMAGE     NONE       FPA        TRUE      SIMPLE
+PPIMAGE.OUTPUT.MASK     OUTPUT {OUTPUT}.mk.fits      MASK      NONE       FPA        TRUE      SIMPLE
+PPIMAGE.OUTPUT.WEIGHT   OUTPUT {OUTPUT}.wt.fits      WEIGHT    NONE       FPA        TRUE      SIMPLE
+PPIMAGE.OUTPUT.DETMASK  OUTPUT {OUTPUT}.fits         IMAGE     NONE       FPA        TRUE      SIMPLE
+PPIMAGE.OUTPUT.RESID    OUTPUT {OUTPUT}.fits         IMAGE     NONE       FPA        TRUE      SIMPLE
+						     
+PPIMAGE.CHIP        	OUTPUT {OUTPUT}.ch.fits      IMAGE     NONE       FPA        TRUE      SIMPLE
+PPIMAGE.CHIP.MASK   	OUTPUT {OUTPUT}.ch.mk.fits   MASK      NONE       FPA        TRUE      SIMPLE
+PPIMAGE.CHIP.WEIGHT 	OUTPUT {OUTPUT}.ch.wt.fits   WEIGHT    NONE       FPA        TRUE      SIMPLE
+						     
+PPIMAGE.OUTPUT.FPA1 	OUTPUT {OUTPUT}.b1.fits      IMAGE     NONE       FPA        TRUE      SIMPLE
+PPIMAGE.OUTPUT.FPA2 	OUTPUT {OUTPUT}.b2.fits      IMAGE     NONE       FPA        TRUE      SIMPLE
+						     
+PPIMAGE.BIN1        	OUTPUT {OUTPUT}.b1.fits      IMAGE     NONE       FPA        TRUE      SIMPLE
+PPIMAGE.BIN2        	OUTPUT {OUTPUT}.b2.fits      IMAGE     NONE       FPA        TRUE      SIMPLE
+						     
+PPIMAGE.JPEG1       	OUTPUT {OUTPUT}.b1.jpg       JPEG      NONE       FPA        TRUE      NONE
+PPIMAGE.JPEG2       	OUTPUT {OUTPUT}.b2.jpg       JPEG      NONE       FPA        TRUE      NONE
+						     
+PPIMAGE.STATS       	OUTPUT {OUTPUT}.stats        STATS     NONE       FPA        TRUE      NONE
+
+## note: these use the  same output naming convention since they are used for different ppMerge runs
+PPMERGE.OUTPUT.MASK   	OUTPUT {OUTPUT}.fits         MASK      NONE       FPA        TRUE      SIMPLE
+PPMERGE.OUTPUT.BIAS   	OUTPUT {OUTPUT}.fits         IMAGE     NONE       FPA        TRUE      SIMPLE
+PPMERGE.OUTPUT.DARK   	OUTPUT {OUTPUT}.fits         DARK      NONE       FPA        TRUE      SIMPLE
+PPMERGE.OUTPUT.SHUTTER	OUTPUT {OUTPUT}.fits         IMAGE     NONE       FPA        TRUE      SIMPLE
+PPMERGE.OUTPUT.FLAT   	OUTPUT {OUTPUT}.fits         IMAGE     NONE       FPA        TRUE      SIMPLE
+PPMERGE.OUTPUT.FRINGE 	OUTPUT {OUTPUT}.fits         FRINGE    NONE       FPA        TRUE      SIMPLE
+PPMERGE.OUTPUT.SIGMA  	OUTPUT {OUTPUT}.sigma.fits   IMAGE     NONE       FPA        TRUE      SIMPLE
+PPMERGE.OUTPUT.COUNT  	OUTPUT {OUTPUT}.count.fits   IMAGE     NONE       FPA        TRUE      SIMPLE
+						     
+DVOCORR.OUTPUT      	OUTPUT {OUTPUT}.fc.fits      IMAGE     NONE       FPA        TRUE      SIMPLE
+DVOFLAT.OUTPUT      	OUTPUT {OUTPUT}.co.fits      IMAGE     NONE       FPA        TRUE      SIMPLE
+						     
+PSPHOT.RESID        	OUTPUT {OUTPUT}.res.fits     IMAGE     NONE       FPA        TRUE      SIMPLE
+PSPHOT.BACKGND      	OUTPUT {OUTPUT}.bck.fits     IMAGE     NONE       FPA        TRUE      SIMPLE
+PSPHOT.BACKSUB      	OUTPUT {OUTPUT}.sub.fits     IMAGE     NONE       FPA        TRUE      SIMPLE
+PSPHOT.BACKMDL      	OUTPUT {OUTPUT}.mdl.fits     IMAGE     NONE       FPA        TRUE      SIMPLE
+						     
+PSPHOT.OUTPUT.RAW   	OUTPUT {OUTPUT}              RAW       NONE       FPA        TRUE      NONE
+PSPHOT.OUTPUT.SX    	OUTPUT {OUTPUT}.sx           SX        NONE       FPA        TRUE      NONE
+PSPHOT.OUTPUT.OBJ   	OUTPUT {OUTPUT}.obj          OBJ       NONE       FPA        TRUE      NONE
+PSPHOT.OUTPUT.CMP   	OUTPUT {OUTPUT}.cmp          CMP       NONE       FPA        TRUE      NONE
+PSPHOT.OUTPUT.CMF   	OUTPUT {OUTPUT}.cmf          CMF       NONE       CHIP       TRUE      NONE
+PSPHOT.PSF.SAVE     	OUTPUT {OUTPUT}.psf          PSF       NONE       FPA        TRUE      NONE
+						     
+SOURCE.PLOT.MOMENTS  	OUTPUT {OUTPUT}.mnt.png      KAPA      NONE       FPA        TRUE      NONE
+SOURCE.PLOT.PSFMODEL 	OUTPUT {OUTPUT}.psf.png      KAPA      NONE       FPA        TRUE      NONE
+SOURCE.PLOT.APRESID  	OUTPUT {OUTPUT}.dap.png      KAPA      NONE       FPA        TRUE      NONE
+						     
+PSASTRO.OUTPUT.CMP  	OUTPUT {OUTPUT}.smp          CMP       NONE       FPA        TRUE      NONE
+PSASTRO.OUTPUT.CMF  	OUTPUT {OUTPUT}.smf          CMF       NONE       FPA        TRUE      NONE
+PSASTRO.OUT.MODEL       OUTPUT {OUTPUT}.asm          ASTROM.MODEL    NONE FPA        TRUE      NONE
+PSASTRO.OUT.REFSTARS    OUTPUT {OUTPUT}.aref.fits    ASTROM.REFSTARS NONE FPA        TRUE      NONE
+PSASTRO.STATS       	OUTPUT {OUTPUT}.stats        STATS     NONE       FPA        TRUE      NONE
+						     
+PSWARP.OUTPUT       	OUTPUT {OUTPUT}.fits         IMAGE     NONE       FPA        TRUE      NONE
+PSWARP.OUTPUT.MASK  	OUTPUT {OUTPUT}.mask.fits    MASK      NONE       FPA        TRUE      NONE
+PSWARP.OUTPUT.WEIGHT    OUTPUT {OUTPUT}.wt.fits      WEIGHT    NONE       FPA        TRUE      NONE
+PSWARP.OUTPUT.SOURCES   OUTPUT {OUTPUT}.cmf          CMF       NONE       FPA        TRUE      NONE
+PSWARP.BIN1         	OUTPUT {OUTPUT}.b1.fits      IMAGE     NONE       FPA        TRUE      NONE
+PSWARP.BIN2         	OUTPUT {OUTPUT}.b2.fits      IMAGE     NONE       FPA        TRUE      NONE
+						     
+SKYCELL.STATS       	OUTPUT {OUTPUT}.stats        STATS     NONE       FPA        TRUE      NONE
+SKYCELL.TEMPLATE    	OUTPUT {OUTPUT}.skycell      SKYCELL   NONE       FPA        TRUE      NONE
+						     
+PPSUB.OUTPUT        	OUTPUT {OUTPUT}.fits         IMAGE     NONE       FPA        TRUE      NONE
+PPSUB.OUTPUT.MASK   	OUTPUT {OUTPUT}.mask.fits    MASK      NONE       FPA        TRUE      NONE
+PPSUB.OUTPUT.WEIGHT 	OUTPUT {OUTPUT}.wt.fits      WEIGHT    NONE       FPA        TRUE      NONE
+						     
+PPSTACK.OUTPUT      	OUTPUT {OUTPUT}.fits         IMAGE     NONE       FPA        TRUE      NONE
+PPSTACK.OUTPUT.MASK 	OUTPUT {OUTPUT}.mask.fits    MASK      NONE       FPA        TRUE      NONE
+PPSTACK.OUTPUT.WEIGHT   OUTPUT {OUTPUT}.weight.fits  WEIGHT    NONE       FPA        TRUE      NONE
+						     
+PPSTAMP.OUTPUT      	OUTPUT {OUTPUT}.fits         IMAGE     NONE       FPA        TRUE      SIMPLE
+PPSTAMP.CHIP        	OUTPUT {OUTPUT}.ch.fits      IMAGE     NONE       CHIP       FALSE     SIMPLE
+						     
+PPSIM.OUTPUT        	OUTPUT {OUTPUT}.fits         IMAGE     NONE       FPA        TRUE      SIMPLE
+PPSIM.FAKE.CHIP        	OUTPUT {OUTPUT}.fake.fits    IMAGE     NONE       FPA        TRUE      SIMPLE
+PPSIM.FORCE.CHIP       	OUTPUT {OUTPUT}.force.fits   IMAGE     NONE       FPA        TRUE      SIMPLE
+PPSIM.SOURCES       	OUTPUT {OUTPUT}.cmf          CMF       NONE       FPA        TRUE      NONE
+PPSIM.FAKE.SOURCES     	OUTPUT {OUTPUT}.fake.cmf     CMF       NONE       FPA        TRUE      NONE
+PPSIM.FORCE.SOURCES    	OUTPUT {OUTPUT}.force.cmf    CMF       NONE       FPA        TRUE      NONE
+						     
+PPARITH.OUTPUT.IMAGE  	OUTPUT {OUTPUT}.fits         IMAGE     NONE       FPA        TRUE      SIMPLE
+PPARITH.OUTPUT.MASK   	OUTPUT {OUTPUT}.fits         MASK      NONE       FPA        TRUE      SIMPLE
+
+LOG.IMFILE            	OUTPUT {OUTPUT}.imfile.log   TEXT      NONE       FPA        TRUE      NONE
+LOG.EXP               	OUTPUT {OUTPUT}.exp.log      TEXT      NONE       FPA        TRUE      NONE
+
+TRACE.IMFILE          	OUTPUT {OUTPUT}.imfile.trace TEXT      NONE       FPA        TRUE      NONE
+TRACE.EXP             	OUTPUT {OUTPUT}.exp.trace    TEXT      NONE       FPA        TRUE      NONE
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/filerules-split.mdc
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/filerules-split.mdc	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/filerules-split.mdc	(revision 22157)
@@ -0,0 +1,189 @@
+### File rules for PHU=CHIP, EXT=CELL (eg, GPC1)
+### this file specifies compression types for various output files
+
+# for basic chip analysis, psphot should produce SPLIT output
+# for basic camera analysis, psastro should produce MEF output
+PSPHOT.OUTPUT      STR PSPHOT.OUT.CMF.SPL
+PSASTRO.INPUT      STR PSASTRO.INPUT.CMF
+# PSASTRO.OUTPUT     STR PSASTRO.OUT.CMF.MEF
+# PSASTRO.OUTPUT.SPL STR PSASTRO.OUT.CMF.SPL
+# PSASTRO.OUTPUT.MEF STR PSASTRO.OUT.CMF.MEF
+
+### input file definitions
+### use @DETDB entries to get the detrend images from the database
+### replace @DETDB with @FILES if you want to require it from the 
+### command line, or with an explicit name to require a specific file
+TYPE                    INPUT    FILENAME.RULE DATA.LEVEL FILE.TYPE
+
+## files used by ppImage
+PPIMAGE.INPUT           INPUT    @FILES        CHIP       IMAGE
+PPIMAGE.INPUT.MASK      INPUT    @FILES        CHIP       MASK
+PPIMAGE.INPUT.WEIGHT    INPUT    @FILES        CHIP       WEIGHT
+PPIMAGE.INPUT.PSF       INPUT    @FILES        CHIP       PSF
+PPIMAGE.INPUT.SRC       INPUT    @FILES        CHIP       CMF
+PPIMAGE.MASK            INPUT    @DETDB        CHIP       MASK
+PPIMAGE.BIAS            INPUT    @DETDB        CHIP       IMAGE
+PPIMAGE.DARK            INPUT    @DETDB        CHIP       DARK
+PPIMAGE.FLAT            INPUT    @DETDB        CHIP       IMAGE
+PPIMAGE.FRINGE     	INPUT 	 @DETDB        CHIP       FRINGE
+PPIMAGE.SHUTTER         INPUT    @DETDB        CHIP       IMAGE
+
+## Files used by ppMerge
+PPMERGE.INPUT           INPUT    @FILES        CHIP       IMAGE
+PPMERGE.INPUT.MASK      INPUT    @FILES        CHIP       MASK
+PPMERGE.INPUT.WEIGHT    INPUT    @FILES        CHIP       WEIGHT
+
+## files used to build and apply the flat-field correction images
+DVOCORR.INPUT      	INPUT 	 @FILES        CHIP       IMAGE
+DVOCORR.REFHEAD    	INPUT 	 @FILES        CHIP       HEADER
+DVOFLAT.INPUT      	INPUT 	 @FILES        CHIP       IMAGE
+DVOFLAT.CORR       	INPUT 	 @DETDB        CHIP       IMAGE
+
+## files used by psphot
+PSPHOT.LOAD             INPUT    @FILES        CHIP       IMAGE
+PSPHOT.INPUT            INPUT    @FILES        CHIP       IMAGE
+PSPHOT.MASK             INPUT    @FILES        CHIP       MASK     
+PSPHOT.WEIGHT           INPUT    @FILES        CHIP       WEIGHT     
+PSPHOT.PSF.LOAD         INPUT    @FILES        CHIP       PSF       
+PSPHOT.INPUT.CMF        INPUT    @FILES        CHIP       CMF       
+
+## files used by psastro 
+PSASTRO.INPUT.CMP       INPUT    @FILES        CHIP       CMP
+PSASTRO.INPUT.CMF       INPUT    @FILES        CHIP       CMF
+PSASTRO.WCS             INPUT    @FILES        CHIP       CMF
+PSASTRO.MODEL           INPUT    @DETDB        FPA        ASTROM
+
+## files used by pswarp
+PSWARP.INPUT            INPUT    @FILES        CHIP       IMAGE
+PSWARP.WEIGHT           INPUT    @FILES        CHIP       WEIGHT
+PSWARP.MASK             INPUT    @FILES        CHIP       MASK
+PSWARP.SKYCELL          INPUT    @FILES        FPA        IMAGE
+PSWARP.ASTROM           INPUT    @FILES        CHIP       CMF
+
+## files used by ppsub
+PPSUB.INPUT             INPUT    @FILES        FPA        IMAGE
+PPSUB.INPUT.MASK        INPUT    @FILES        FPA        MASK
+PPSUB.INPUT.WEIGHT      INPUT    @FILES        FPA        WEIGHT
+PPSUB.REF               INPUT    @FILES        FPA        IMAGE
+PPSUB.REF.MASK          INPUT    @FILES        FPA        MASK
+PPSUB.REF.WEIGHT        INPUT    @FILES        FPA        WEIGHT
+PPSUB.SOURCES      	INPUT 	 @FILES        FPA        CMF
+
+## files used by ppstack
+PPSTACK.INPUT           INPUT    @FILES        FPA        IMAGE
+PPSTACK.INPUT.MASK      INPUT    @FILES        FPA        MASK
+PPSTACK.INPUT.WEIGHT    INPUT    @FILES        FPA        WEIGHT
+PPSTACK.INPUT.PSF  	INPUT 	 @FILES        CHIP       PSF
+PPSTACK.INPUT.SOURCES   INPUT    @FILES        FPA        CMF
+
+## files used by ppstamp
+PPSTAMP.INPUT           INPUT    @FILES        CHIP       IMAGE
+
+# files used by pparith
+PPARITH.INPUT.IMAGE     INPUT    @FILES        CHIP       IMAGE
+PPARITH.INPUT.MASK      INPUT    @FILES        CHIP       MASK
+
+### output file definitions
+TYPE                    OUTPUT FILENAME.RULE                     FILE.TYPE FITS.TYPE  DATA.LEVEL FILE.SAVE FILE.FORMAT
+PPIMAGE.OUTPUT          OUTPUT {OUTPUT}.{CHIP.NAME}.fits         IMAGE     COMP_IMG   CHIP       TRUE      NONE
+PPIMAGE.OUTPUT.MASK     OUTPUT {OUTPUT}.{CHIP.NAME}.mk.fits      MASK      COMP_MASK  CHIP       TRUE      NONE
+PPIMAGE.OUTPUT.WEIGHT   OUTPUT {OUTPUT}.{CHIP.NAME}.wt.fits      WEIGHT    COMP_WT    CHIP       TRUE      NONE
+PPIMAGE.OUTPUT.DETMASK  OUTPUT {OUTPUT}.{CHIP.NAME}.fits         IMAGE     DET_MASK   CHIP       TRUE      NONE
+PPIMAGE.OUTPUT.RESID    OUTPUT {OUTPUT}.{CHIP.NAME}.fits         IMAGE     COMP_SUB   CHIP       TRUE      NONE
+
+#PPIMAGE.OUTPUT          OUTPUT {OUTPUT}.{CHIP.NAME}.fits         IMAGE     NONE  CHIP       TRUE      NONE
+#PPIMAGE.OUTPUT.MASK     OUTPUT {OUTPUT}.{CHIP.NAME}.mk.fits      MASK      NONE  CHIP       TRUE      NONE
+#PPIMAGE.OUTPUT.WEIGHT   OUTPUT {OUTPUT}.{CHIP.NAME}.wt.fits      WEIGHT    NONE  CHIP       TRUE      NONE
+#PPIMAGE.OUTPUT.DETMASK  OUTPUT {OUTPUT}.{CHIP.NAME}.fits         IMAGE     NONE  CHIP       TRUE      NONE
+	        									        
+PPIMAGE.CHIP            OUTPUT {OUTPUT}.{CHIP.NAME}.ch.fits      IMAGE     COMP_IMG   CHIP       TRUE      NONE
+PPIMAGE.CHIP.DETMASK    OUTPUT {OUTPUT}.{CHIP.NAME}.ch.fits      IMAGE     NONE       CHIP       TRUE      NONE
+PPIMAGE.CHIP.MASK       OUTPUT {OUTPUT}.{CHIP.NAME}.ch.mk.fits   MASK      COMP_MASK  CHIP       TRUE      NONE
+PPIMAGE.CHIP.WEIGHT     OUTPUT {OUTPUT}.{CHIP.NAME}.ch.wt.fits   WEIGHT    COMP_WT    CHIP       TRUE      NONE
+PPIMAGE.CHIP.RESID      OUTPUT {OUTPUT}.{CHIP.NAME}.ch.fits      IMAGE     COMP_SUB   CHIP       TRUE      NONE
+		        									        
+PPIMAGE.OUTPUT.FPA1     OUTPUT {OUTPUT}.b1.fits                  IMAGE     NONE       FPA        TRUE      NONE
+PPIMAGE.OUTPUT.FPA2     OUTPUT {OUTPUT}.b2.fits                  IMAGE     NONE       FPA        TRUE      NONE
+		        									        
+PPIMAGE.BIN1            OUTPUT {OUTPUT}.{CHIP.NAME}.b1.fits      IMAGE     NONE       CHIP       TRUE      NONE
+PPIMAGE.BIN2            OUTPUT {OUTPUT}.{CHIP.NAME}.b2.fits      IMAGE     NONE       CHIP       TRUE      NONE
+		        									        
+PPIMAGE.JPEG1           OUTPUT {OUTPUT}.b1.jpg                   JPEG      NONE       FPA        TRUE      NONE
+PPIMAGE.JPEG2           OUTPUT {OUTPUT}.b2.jpg                   JPEG      NONE       FPA        TRUE      NONE
+		        									        
+PPIMAGE.STATS           OUTPUT {OUTPUT}.{CHIP.NAME}.stats        STATS     NONE       CHIP       TRUE      NONE
+		        									        
+## note: these use the  same output naming convention since they are used for different ppMerge runs
+PPMERGE.OUTPUT.MASK     OUTPUT {OUTPUT}.{CHIP.NAME}.fits         MASK      NONE       CHIP       TRUE      NONE
+PPMERGE.OUTPUT.BIAS     OUTPUT {OUTPUT}.{CHIP.NAME}.fits         IMAGE     NONE       CHIP       TRUE      NONE
+PPMERGE.OUTPUT.DARK     OUTPUT {OUTPUT}.{CHIP.NAME}.fits         DARK      NONE       CHIP       TRUE      NONE
+PPMERGE.OUTPUT.SHUTTER  OUTPUT {OUTPUT}.{CHIP.NAME}.fits         IMAGE     NONE       CHIP       TRUE      NONE
+PPMERGE.OUTPUT.FLAT     OUTPUT {OUTPUT}.{CHIP.NAME}.fits         IMAGE     NONE       CHIP       TRUE      NONE
+PPMERGE.OUTPUT.FRINGE   OUTPUT {OUTPUT}.{CHIP.NAME}.fits         FRINGE    NONE       CHIP       TRUE      NONE
+PPMERGE.OUTPUT.SIGMA    OUTPUT {OUTPUT}.{CHIP.NAME}.sigma.fits   IMAGE     NONE       CHIP       TRUE      NONE
+PPMERGE.OUTPUT.COUNT    OUTPUT {OUTPUT}.{CHIP.NAME}.count.fits   IMAGE     NONE       CHIP       TRUE      NONE
+		        									        
+DVOCORR.OUTPUT          OUTPUT {OUTPUT}.{CHIP.NAME}.fc.fits      IMAGE     NONE       CHIP       TRUE      NONE
+DVOFLAT.OUTPUT          OUTPUT {OUTPUT}.{CHIP.NAME}.co.fits      IMAGE     NONE       CHIP       TRUE      NONE
+		        									        
+PSPHOT.RESID            OUTPUT {OUTPUT}.{CHIP.NAME}.res.fits     IMAGE     COMP_SUB   CHIP       TRUE      NONE
+PSPHOT.BACKGND          OUTPUT {OUTPUT}.{CHIP.NAME}.bck.fits     IMAGE     COMP_IMG   CHIP       TRUE      NONE
+PSPHOT.BACKSUB          OUTPUT {OUTPUT}.{CHIP.NAME}.sub.fits     IMAGE     COMP_SUB   CHIP       TRUE      NONE
+PSPHOT.BACKMDL          OUTPUT {OUTPUT}.{CHIP.NAME}.mdl.fits     IMAGE     COMP_IMG   CHIP       TRUE      NONE
+		        									        
+PSPHOT.OUTPUT.RAW       OUTPUT {OUTPUT}.{CHIP.NAME}              RAW       NONE       CHIP       TRUE      NONE
+PSPHOT.OUTPUT.SX        OUTPUT {OUTPUT}.{CHIP.NAME}.sx           SX        NONE       CHIP       TRUE      NONE
+PSPHOT.OUTPUT.OBJ       OUTPUT {OUTPUT}.{CHIP.NAME}.obj          OBJ       NONE       CHIP       TRUE      NONE
+PSPHOT.OUTPUT.CMP       OUTPUT {OUTPUT}.{CHIP.NAME}.cmp          CMP       NONE       CHIP       TRUE      NONE
+PSPHOT.OUT.CMF.SPL      OUTPUT {OUTPUT}.{CHIP.NAME}.cmf          CMF       NONE       CHIP       TRUE      NONE
+PSPHOT.OUT.CMF.MEF      OUTPUT {OUTPUT}.cmf                      CMF       NONE       FPA        TRUE      MEF
+PSPHOT.PSF.SAVE         OUTPUT {OUTPUT}.{CHIP.NAME}.psf          PSF       NONE       CHIP       TRUE      MEF
+		        									        
+SOURCE.PLOT.MOMENTS     OUTPUT {OUTPUT}.{CHIP.NAME}.mnt.png      KAPA      NONE       CHIP       TRUE      NONE
+SOURCE.PLOT.PSFMODEL    OUTPUT {OUTPUT}.{CHIP.NAME}.psf.png      KAPA      NONE       CHIP       TRUE      NONE
+SOURCE.PLOT.APRESID     OUTPUT {OUTPUT}.{CHIP.NAME}.dap.png      KAPA      NONE       CHIP       TRUE      NONE
+		        									        
+# PSASTRO.OUTPUT.CMP      OUTPUT {OUTPUT}.{CHIP.NAME}.smp          CMP       NONE       CHIP       TRUE      NONE
+# PSASTRO.OUT.CMF.SPL     OUTPUT {OUTPUT}.{CHIP.NAME}.smf          CMF       NONE       CHIP       TRUE      NONE
+# PSASTRO.OUT.CMF.MEF     OUTPUT {OUTPUT}.smf                      CMF       NONE       FPA        TRUE      MEF
+PSASTRO.OUT.MODEL       OUTPUT {OUTPUT}.asm               ASTROM.MODEL     NONE       FPA        TRUE      NONE
+PSASTRO.OUT.REFSTARS    OUTPUT {OUTPUT}.aref.fits         ASTROM.REFSTARS  NONE       FPA        TRUE      NONE
+PSASTRO.OUTPUT          OUTPUT {OUTPUT}.smf                      CMF       NONE       FPA        TRUE      MEF
+PSASTRO.STATS           OUTPUT {OUTPUT}.stats                    STATS     NONE       FPA        TRUE      NONE
+		        									        
+PSWARP.OUTPUT           OUTPUT {OUTPUT}.fits                     IMAGE     COMP_IMG   FPA        TRUE      NONE
+PSWARP.OUTPUT.MASK      OUTPUT {OUTPUT}.mask.fits                MASK      COMP_MASK  FPA        TRUE      NONE
+PSWARP.OUTPUT.WEIGHT    OUTPUT {OUTPUT}.wt.fits                  WEIGHT    COMP_WT    FPA        TRUE      NONE
+PSWARP.OUTPUT.SOURCES   OUTPUT {OUTPUT}.cmf                      CMF       NONE       FPA        TRUE      NONE
+PSWARP.BIN1             OUTPUT {OUTPUT}.b1.fits                  IMAGE     NONE       FPA        TRUE      NONE
+PSWARP.BIN2             OUTPUT {OUTPUT}.b2.fits                  IMAGE     NONE       FPA        TRUE      NONE
+		        									        
+SKYCELL.STATS           OUTPUT {OUTPUT}.stats                    STATS     NONE       FPA        TRUE      NONE
+SKYCELL.TEMPLATE        OUTPUT {OUTPUT}.skycell                  SKYCELL   NONE       FPA        TRUE      NONE
+		        									        
+PPSUB.OUTPUT            OUTPUT {OUTPUT}.fits                     IMAGE     COMP_SUB   FPA        TRUE      NONE
+PPSUB.OUTPUT.MASK       OUTPUT {OUTPUT}.mask.fits                MASK      COMP_MASK  FPA        TRUE      NONE
+PPSUB.OUTPUT.WEIGHT     OUTPUT {OUTPUT}.wt.fits                  WEIGHT    COMP_WT    FPA        TRUE      NONE
+											        
+PPSTACK.OUTPUT          OUTPUT {OUTPUT}.fits                     IMAGE     COMP_IMG   FPA        TRUE      NONE
+PPSTACK.OUTPUT.MASK     OUTPUT {OUTPUT}.mask.fits                MASK      COMP_MASK  FPA        TRUE      NONE
+PPSTACK.OUTPUT.WEIGHT   OUTPUT {OUTPUT}.wt.fits                  WEIGHT    COMP_WT    FPA        TRUE      NONE
+		        									        
+PPSTAMP.OUTPUT          OUTPUT {OUTPUT}.fits                     IMAGE     NONE       FPA        TRUE      NONE
+PPSTAMP.CHIP            OUTPUT {OUTPUT}.ch.fits                  IMAGE     NONE       CHIP       FALSE     MEF
+		        									        
+PPSIM.OUTPUT            OUTPUT {OUTPUT}.fits                     IMAGE     NONE       FPA        TRUE      MEF
+PPSIM.FAKE.CHIP        	OUTPUT {OUTPUT}.fake.fits     		 IMAGE     NONE       FPA        TRUE      SIMPLE
+PPSIM.FORCE.CHIP       	OUTPUT {OUTPUT}.force.fits     		 IMAGE     NONE       FPA        TRUE      SIMPLE
+PPSIM.SOURCES           OUTPUT {OUTPUT}.cmf                      CMF       NONE       FPA        TRUE      MEF
+PPSIM.FAKE.SOURCES     	OUTPUT {OUTPUT}.fake.cmf                 CMF       NONE       FPA        TRUE      NONE
+PPSIM.FORCE.SOURCES    	OUTPUT {OUTPUT}.force.cmf                CMF       NONE       FPA        TRUE      NONE
+											        
+PPARITH.OUTPUT.IMAGE   	OUTPUT {OUTPUT}.fits                     IMAGE     COMP_IMG   CHIP       TRUE      NONE
+PPARITH.OUTPUT.MASK    	OUTPUT {OUTPUT}.fits                     MASK      COMP_MASK  CHIP       TRUE      NONE
+		       										        
+LOG.IMFILE             	OUTPUT {OUTPUT}.{CHIP.NAME}.log          TEXT      NONE       CHIP       TRUE      NONE
+LOG.EXP                	OUTPUT {OUTPUT}.log                      TEXT      NONE       FPA        TRUE      NONE
+		       										        
+TRACE.IMFILE           	OUTPUT {OUTPUT}.{CHIP.NAME}.trace        TEXT      NONE       CHIP       TRUE      NONE
+TRACE.EXP              	OUTPUT {OUTPUT}.trace                    TEXT      NONE       FPA        TRUE      NONE
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/fitstypes.mdc
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/fitstypes.mdc	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/fitstypes.mdc	(revision 22157)
@@ -0,0 +1,87 @@
+
+# BITPIX is the bits per pixel for writing the output data
+# COMP = NONE|RICE|GZIP|HCOMPRESS|PLIO is the compression algorithm
+# TILE.[XYZ] are the tile sizes.  0 means entire the dimension, so (0,1,1) forms tiles from rows
+# NOISE [0..16] is the number of "noise bits" to preserve when quantising floating point data; 16 for no loss
+# HSCALE is the scale factor for lossy compression with HCOMPRESS; 0 or 1 for none; 2*RMS --> 10x compression
+# HSMOOTH is the smoothing to apply to HCOMPRESSed data when decompressing; 0 for none
+
+# BITPIX(S32) is the bits per pixel for writing the output data
+# COMPRESSION(STR) = NONE|RICE|GZIP|HCOMPRESS|PLIO is the compression algorithm
+# TILE.[XYZ](S32) are the tile sizes.  0 means entire the dimension, so (0,1,1) forms tiles from rows
+# NOISE(S32) [0..16] is the number of "noise bits" to preserve when quantising floating point data
+# HSCALE(S32) is the scale factor for lossy compression with HCOMPRESS; 0 or 1 for none; 2*RMS --> 10x compression
+# HSMOOTH(S32) is the smoothing to apply to HCOMPRESSed data when decompressing; 0 for none
+# SCALING(STR) = NONE|RANGE|STDEV_POSITIVE|STDEV_NEGATIVE|STDEV_BOTH|MANUAL is the scaling scheme
+# BSCALE(F32) is the manual scaling to apply (when SCALING = MANUAL)
+# BZERO(F32) is the manual zero-point to apply (when SCALING = MANUAL)
+# STDEV.BITS(S32) is the number of bits to map to a standard deviation (when SCALING = STDEV_*)
+# STDEV.NUM(F32) is the number of standard deviations to the edge (when SCALING = STDEV_NEGATIVE|STDEV_POSITIVE)
+# FLOAT(STR) is the name of a custom floating-point type
+
+DET_IMAGE	METADATA
+	BITPIX		S32	-32
+END
+DET_MASK	METADATA
+	BITPIX		S32	8
+END
+DET_WEIGHT	METADATA
+	BITPIX		S32	-32
+END
+
+SKY_IMAGE	METADATA
+	BITPIX		S32	-32
+END
+SKY_MASK	METADATA
+	BITPIX		S32	8
+END
+SKY_WEIGHT	METADATA
+	BITPIX		S32	-32
+END
+
+# Compressed positive image
+COMP_IMG	METADATA
+	BITPIX		S32	16
+	SCALING		STR	STDEV_POSITIVE
+	STDEV.BITS	S32	4
+	STDEV.NUM	F32	10
+	COMPRESSION	STR	RICE
+	TILE.X		S32	0
+	TILE.Y		S32	1
+	TILE.Z		S32	1
+	NOISE		S32	8
+END
+# Compressed mask
+COMP_MASK	METADATA
+	BITPIX		S32	8
+	COMPRESSION	STR	PLIO
+	TILE.X		S32	0
+	TILE.Y		S32	1
+	TILE.Z		S32	1
+	NOISE		S32	8
+END
+# Compressed weight image
+COMP_WT		METADATA
+	BITPIX		S32	16
+	SCALING		STR	STDEV_POSITIVE
+	STDEV.BITS	S32	4
+	STDEV.NUM	F32	10
+	COMPRESSION	STR	RICE
+	TILE.X		S32	0
+	TILE.Y		S32	1
+	TILE.Z		S32	1
+	NOISE		S32	8
+END
+# Compressed subtraction image
+COMP_SUB	METADATA
+	BITPIX		S32	16
+	SCALING		STR	STDEV_BOTH
+	STDEV.BITS	S32	4
+	STDEV.NUM	F32	5
+	COMPRESSION	STR	RICE
+	TILE.X		S32	0
+	TILE.Y		S32	1
+	TILE.Z		S32	1
+	NOISE		S32	8
+END
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/masks.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/masks.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/masks.config	(revision 22157)
@@ -0,0 +1,30 @@
+### Recipe specifying values for various mask concepts
+
+# this file defines bit values for various abstract mask concepts.  It
+# is not necessary for all bit values to be unique.  Note that there
+# are two main classes: detector-intrinsic and detector-extrinsic
+# effects.  The former will default to DETECTOR and the latter to
+# RANGE if those values are not specifed
+
+# Note that it is necessary to leave at least a single unassigned bit
+# for internal marking (or ppImage will complain)
+
+# these mask values are used for detector intrinsic effects
+# BLANK and FLAT will be set to DETECTOR if not specified
+DETECTOR	U8	0x01		# Generic detector-intrinsic defect
+BLANK		U8	0x01		# The pixel is blank or has no (valid) data
+DARK		U8	0x02		# The pixel is not correctable for dark current
+FLAT		U8	0x02		# The pixel is non-positive in the flat-field
+
+# these mask values are used for detector extrinsic effects
+# SAT and BAD will be set to RANGE if not specified
+RANGE		U8	0x04		# The pixel is out of range in the image of interest
+SAT		U8	0x04		# The pixel is saturated in the image of interest
+BAD		U8	0x04		# The pixel is low in the image of interest
+
+CR		U8	0x08		# The pixel is probably a CR
+GHOST		U8	0x08		# The pixel is probably a CR
+
+
+BAD.WARP	U8	0x04		# The pixel is low in the image of interest
+POOR.WARP	U8	0x04		# The pixel is low in the image of interest
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/ppImage.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/ppImage.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/ppImage.config	(revision 22157)
@@ -0,0 +1,1301 @@
+### ppImage recipe configuration file
+### Here we have turned on our 'best guess' defaults for a simple camera
+### Camera-specific options may be turned on by recipes in the camera configuration
+
+# List of tasks to perform
+NONLIN           BOOL    FALSE           # Non-linearity correction; not implemented
+OVERSCAN         BOOL    TRUE            # Overscan subtraction
+BIAS             BOOL    TRUE            # Bias subtraction
+DARK             BOOL    TRUE            # Dark subtraction
+SHUTTER          BOOL    FALSE           # Shutter correction
+FLAT             BOOL    TRUE            # Flat-field normalisation
+MASK             BOOL    FALSE           # Mask bad pixels
+
+# XXX EAM : this is being deprecated
+# MASK.VALUE       STR     SAT,BAD,DETECTOR # Mask pixels with these attributes
+
+MASK.BUILD       BOOL    FALSE           # Build internal mask image
+REPLACE.MASKED   BOOL    FALSE           # Fill in masked pixels
+REPLACE.MODE     STR     VALUE           # VALUE, MODEL
+REPLACE.VALUE    F32     0.0             # Fill in masked pixels with this value (if mode == value)
+
+WEIGHT.BUILD     BOOL    FALSE           # Build internal weight image
+FRINGE           BOOL    FALSE           # Fringe subtraction
+PHOTOM           BOOL    FALSE           # Source identification and photometry
+ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+
+# output data formats to save
+BASE.FITS        BOOL    FALSE           # Save base detrended image?
+BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+FPA1.FITS        BOOL    FALSE           # Save 1st binned fpa image? 
+FPA2.FITS        BOOL    FALSE           # Save 2nd binned fpa image? 
+BIN1.FITS        BOOL    FALSE           # Save 1st binned chip image?
+BIN2.FITS        BOOL    FALSE           # Save 2nd binned chip image?
+BIN1.JPEG        BOOL    FALSE           # Save 1st binned jpeg?
+BIN2.JPEG        BOOL    FALSE           # Save 2nd binned jpeg?
+
+# Non-linearity correction
+NONLIN.SOURCE           STR     CHIP.NAME       # How to determine the source
+NONLIN.DATA             STR     nonlin.dat      # Filename for lookup table
+
+OLDDARK		BOOL	FALSE		# Use old-style darks?
+
+# examples of other possible non-linearity correction representations
+#@NONLIN.DATA           F32     0.0 1.001 0.001 # A polynomial
+#NONLIN.DATA            METADATA                # Source of non-linearity data
+#       ccd00           STR     nonlin00.dat    # A lookup table 
+#       @ccd01          F32     0.0 1.001 0.001 # A polynomial
+#END
+
+# Overscan subtraction
+OVERSCAN.SINGLE         BOOL    TRUE            # Reduce overscan to a single value?
+OVERSCAN.FIT            STR     NONE            # NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER          S32     5               # Order of polynomial fit
+OVERSCAN.STAT           STR     MEAN            # MEAN | MEDIAN
+OVERSCAN.BOXCAR		S32	0		# Boxcar smoothing radius
+OVERSCAN.GAUSS		F32	0.0		# Gaussian smoothing sigma
+OVERSCAN.CONSTANT	BOOL	FALSE		# Apply a known, fixed value?
+OVERSCAN.VALUE	        F32	0.0		# value to apply, if requested
+
+# Fringe subtraction options
+FRINGE.ITER     S32     10              # Number of rejection iterations for fringe solution
+FRINGE.REJ      F32     2.0             # Rejection threshold for fringe solution
+FRINGE.KEEP     F32     0.5             # Minimum fraction to keep in fringe solution
+
+# binned output image options
+BIN1.XBIN               S32      4
+BIN1.YBIN               S32      4
+BIN2.XBIN               S32     16
+BIN2.YBIN               S32     16
+
+PPIMAGE.JPEG1  METADATA
+  COLORMAP      STR     -greyscale
+  SCALE.MODE    STR     RANGE
+  SCALE.MIN     F32      -5.0
+  SCALE.MAX     F32     +10.0
+END
+
+PPIMAGE.JPEG2  METADATA
+  COLORMAP      STR     -greyscale
+  SCALE.MODE    STR     RANGE
+  SCALE.MIN     STR      -5.0
+  SCALE.MAX     STR     +10.0
+END
+
+PHOTCODE.RULE           STR     {DETECTOR}.{FILTER.ID}.{CHIP.N}
+
+FRINGE.FILTERS  MULTI
+FRINGE.FILTERS  STR UNDEF
+
+DETREND.CONSTRAINTS  METADATA
+END
+
+################################################################################
+# Diffferent processing options, which may be loaded symbolically
+################################################################################
+
+# No operation except rebinning
+PPIMAGE_BIN        METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# No operation except rebinning and potential normalisation
+PPIMAGE_N          METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan subtraction only
+PPIMAGE_O          METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Bias subtraction only
+PPIMAGE_B          METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Dark subtraction only
+PPIMAGE_D          METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Dark subtraction only
+PPIMAGE_M          METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    TRUE            # Mask bad pixels
+  REPLACE.MASKED   BOOL    TRUE            # Fill in masked pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Shutter correction only
+PPIMAGE_S          METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    TRUE            # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Flat-fielding only
+PPIMAGE_F          METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Fringe correction only
+PPIMAGE_R          METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    TRUE            # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Photometry only
+PPIMAGE_P          METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Photometry & Astrometry
+PPIMAGE_A          METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astromtery per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias
+PPIMAGE_OB         METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark
+PPIMAGE_OBD        METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter
+PPIMAGE_OBDS       METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field
+PPIMAGE_OBDSF      METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe
+PPIMAGE_OBDSFR     METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    TRUE            # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_DET_ONLY   METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    TRUE            # Mask bad pixels
+  FRINGE           BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom
+PPIMAGE_OBDSFRP    METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    TRUE            # Mask bad pixels
+  FRINGE           BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM           BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_OBDSFRA    METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    TRUE            # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    TRUE            # Mask bad pixels
+  FRINGE           BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM           BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_MASKPHOT   METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    TRUE            # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM           BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Save JPEG from BIN1
+PPIMAGE_JPEG       METADATA
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BASE.FITS        BOOL    FALSE           # Save base image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  BIN1.FITS        BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS        BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG        BOOL    TRUE            # Save 1st binned jpeg?
+  BIN2.JPEG        BOOL    TRUE            # Save 2nd binned jpeg?
+END
+
+# Save JPEG from BIN1
+PPIMAGE_J1         METADATA
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BASE.FITS        BOOL    FALSE           # Save base image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  BIN1.FITS        BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS        BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG        BOOL    TRUE            # Save 1st binned jpeg?
+  BIN2.JPEG        BOOL    FALSE           # Save 2nd binned jpeg?
+  BIN1.XBIN        S32     1               # Image is already binned
+  BIN1.YBIN        S32     1               # Image is already binned
+  BIN2.XBIN        S32     1               # Image is already binned
+  BIN2.YBIN        S32     1               # Image is already binned
+END
+
+# Save JPEG from BIN2
+PPIMAGE_J2         METADATA
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BASE.FITS        BOOL    FALSE           # Save base image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  BIN1.FITS        BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS        BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG        BOOL    FALSE           # Save 1st binned jpeg?
+  BIN2.JPEG        BOOL    TRUE            # Save 2nd binned jpeg?
+  BIN1.XBIN        S32     1               # Image is already binned
+  BIN1.YBIN        S32     1               # Image is already binned
+  BIN2.XBIN        S32     1               # Image is already binned
+  BIN2.YBIN        S32     1               # Image is already binned
+END
+
+# Overscan, bias, photometry, astrometry (for, eg, summit ISP analysis)
+PPIMAGE_OA         METADATA
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS        BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS        BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG        BOOL    TRUE          # Save 1st binned jpeg?
+  BIN2.JPEG        BOOL    TRUE          # Save 2nd binned jpeg?
+  PHOTOM           BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, photometry (for quick analysis)
+PPIMAGE_OP         METADATA
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS        BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS        BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG        BOOL    TRUE            # Save 1st binned jpeg?
+  BIN2.JPEG        BOOL    TRUE            # Save 2nd binned jpeg?
+  PHOTOM           BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Photometry and astrometry only (for pre-reduced data)
+PPIMAGE_PA         METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+  BIN1.JPEG        BOOL    TRUE            # Save 1st binned jpeg?
+  BIN2.JPEG        BOOL    TRUE            # Save 2nd binned jpeg?
+  PHOTOM           BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# JPEG images for different types of residual images
+# Save JPEG from BIN1 for bias
+PPIMAGE_J1_RESID_B      METADATA
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BASE.FITS        BOOL    FALSE           # Save base image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  BIN1.FITS        BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS        BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG        BOOL    TRUE            # Save 1st binned jpeg?
+  BIN2.JPEG        BOOL    FALSE           # Save 2nd binned jpeg?
+  BIN1.XBIN        S32     1               # Image is already binned
+  BIN1.YBIN        S32     1               # Image is already binned
+  BIN2.XBIN        S32     1               # Image is already binned
+  BIN2.YBIN        S32     1               # Image is already binned
+
+  PPIMAGE.JPEG1    METADATA
+    COLORMAP       STR     -greyscale
+    SCALE.MODE     STR     VALUE
+    SCALE.MIN      F32     -5.0
+    SCALE.MAX      F32     +5.0
+  END
+END
+
+# Save JPEG from BIN2 for bias
+PPIMAGE_J2_RESID_B        METADATA
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BASE.FITS        BOOL    FALSE           # Save base image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  BIN1.FITS        BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS        BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG        BOOL    FALSE           # Save 1st binned jpeg?
+  BIN2.JPEG        BOOL    TRUE            # Save 2nd binned jpeg?
+  BIN1.XBIN        S32     1               # Image is already binned
+  BIN1.YBIN        S32     1               # Image is already binned
+  BIN2.XBIN        S32     1               # Image is already binned
+  BIN2.YBIN        S32     1               # Image is already binned
+
+  PPIMAGE.JPEG2    METADATA
+    COLORMAP       STR     -greyscale
+    SCALE.MODE     STR     VALUE
+    SCALE.MIN      F32     -5.0
+    SCALE.MAX      F32     +5.0
+  END
+END
+
+# Save JPEG from BIN1 for flat
+PPIMAGE_J1_RESID_F      METADATA
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BASE.FITS        BOOL    FALSE           # Save base image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  BIN1.FITS        BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS        BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG        BOOL    TRUE            # Save 1st binned jpeg?
+  BIN2.JPEG        BOOL    FALSE           # Save 2nd binned jpeg?
+  BIN1.XBIN        S32     1               # Image is already binned
+  BIN1.YBIN        S32     1               # Image is already binned
+  BIN2.XBIN        S32     1               # Image is already binned
+  BIN2.YBIN        S32     1               # Image is already binned
+
+  PPIMAGE.JPEG1    METADATA
+    COLORMAP       STR     -greyscale
+    SCALE.MODE     STR     FRACTION
+    SCALE.MIN      F32     0.95
+    SCALE.MAX      F32     1.05
+  END
+END
+
+# Save JPEG from BIN2 for flat
+PPIMAGE_J2_RESID_F        METADATA
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BASE.FITS        BOOL    FALSE           # Save base image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  BIN1.FITS        BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS        BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG        BOOL    FALSE           # Save 1st binned jpeg?
+  BIN2.JPEG        BOOL    TRUE            # Save 2nd binned jpeg?
+  BIN1.XBIN        S32     1               # Image is already binned
+  BIN1.YBIN        S32     1               # Image is already binned
+  BIN2.XBIN        S32     1               # Image is already binned
+  BIN2.YBIN        S32     1               # Image is already binned
+		   
+  PPIMAGE.JPEG2    METADATA
+    COLORMAP       STR     -greyscale
+    SCALE.MODE     STR     FRACTION
+    SCALE.MIN      F32     0.95
+    SCALE.MAX      F32     1.05
+  END
+END
+
+# Save JPEG from BIN1 for flat
+PPIMAGE_J1_RESID_R      METADATA
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BASE.FITS        BOOL    FALSE           # Save base image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  BIN1.FITS        BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS        BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG        BOOL    TRUE            # Save 1st binned jpeg?
+  BIN2.JPEG        BOOL    FALSE           # Save 2nd binned jpeg?
+  BIN1.XBIN        S32     1               # Image is already binned
+  BIN1.YBIN        S32     1               # Image is already binned
+  BIN2.XBIN        S32     1               # Image is already binned
+  BIN2.YBIN        S32     1               # Image is already binned
+		   
+  PPIMAGE.JPEG1    METADATA
+    COLORMAP       STR     -greyscale
+    SCALE.MODE     STR     RANGE
+    SCALE.MIN      F32     -2.0
+    SCALE.MAX      F32     +3.0
+  END
+END
+
+# Save JPEG from BIN2 for flat
+PPIMAGE_J2_RESID_R        METADATA
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BASE.FITS        BOOL    FALSE           # Save base image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  BIN1.FITS        BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS        BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG        BOOL    FALSE           # Save 1st binned jpeg?
+  BIN2.JPEG        BOOL    TRUE            # Save 2nd binned jpeg?
+  BIN1.XBIN        S32     1               # Image is already binned
+  BIN1.YBIN        S32     1               # Image is already binned
+  BIN2.XBIN        S32     1               # Image is already binned
+  BIN2.YBIN        S32     1               # Image is already binned
+		   
+  PPIMAGE.JPEG2    METADATA
+    COLORMAP       STR     -greyscale
+    SCALE.MODE     STR     RANGE
+    SCALE.MIN      F32     -2.0
+    SCALE.MAX      F32     +3.0
+  END
+END
+
+# JPEG images for different types of positive images
+# Save JPEG from BIN1 for bias
+PPIMAGE_J1_IMAGE_B      METADATA
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BASE.FITS        BOOL    FALSE           # Save base image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  BIN1.FITS        BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS        BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG        BOOL    TRUE            # Save 1st binned jpeg?
+  BIN2.JPEG        BOOL    FALSE           # Save 2nd binned jpeg?
+  BIN1.XBIN        S32     1               # Image is already binned
+  BIN1.YBIN        S32     1               # Image is already binned
+  BIN2.XBIN        S32     1               # Image is already binned
+  BIN2.YBIN        S32     1               # Image is already binned
+		   
+  PPIMAGE.JPEG1    METADATA
+    COLORMAP       STR     -greyscale
+    SCALE.MODE     STR     RANGE
+    SCALE.MIN      F32     -5.0
+    SCALE.MAX      F32     +5.0
+  END
+END
+
+# Save JPEG from BIN2 for bias
+PPIMAGE_J2_IMAGE_B        METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+  BASE.FITS       BOOL    FALSE           # Save base image?
+  BASE.MASK.FITS  BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL   FALSE           # Save base detrended image?
+  CHIP.FITS       BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS  BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL   FALSE           # Save chip-mosaic-ed image? 
+  BIN1.FITS       BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS       BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG       BOOL    FALSE           # Save 1st binned jpeg?
+  BIN2.JPEG       BOOL    TRUE            # Save 2nd binned jpeg?
+  BIN1.XBIN       S32     1               # Image is already binned
+  BIN1.YBIN       S32     1               # Image is already binned
+  BIN2.XBIN       S32     1               # Image is already binned
+  BIN2.YBIN       S32     1               # Image is already binned
+
+  PPIMAGE.JPEG2  METADATA
+    COLORMAP      STR     -greyscale
+    SCALE.MODE    STR     RANGE
+    SCALE.MIN     F32     -5.0
+    SCALE.MAX     F32     +5.0
+  END
+END
+
+# Save JPEG from BIN1 for flat
+PPIMAGE_J1_IMAGE_F      METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+  BASE.FITS       BOOL    FALSE           # Save base image?
+  BASE.MASK.FITS  BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL   FALSE           # Save base detrended image?
+  CHIP.FITS       BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS  BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL   FALSE           # Save chip-mosaic-ed image? 
+  BIN1.FITS       BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS       BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG       BOOL    TRUE            # Save 1st binned jpeg?
+  BIN2.JPEG       BOOL    FALSE           # Save 2nd binned jpeg?
+  BIN1.XBIN       S32     1               # Image is already binned
+  BIN1.YBIN       S32     1               # Image is already binned
+  BIN2.XBIN       S32     1               # Image is already binned
+  BIN2.YBIN       S32     1               # Image is already binned
+
+  PPIMAGE.JPEG1  METADATA
+    COLORMAP      STR     -greyscale
+    SCALE.MODE    STR     RANGE
+    SCALE.MIN     F32     -5.0
+    SCALE.MAX     F32     +5.0
+  END
+END
+
+# Save JPEG from BIN2 for flat
+PPIMAGE_J2_IMAGE_F        METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+  BASE.FITS       BOOL    FALSE           # Save base image?
+  BASE.MASK.FITS  BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL   FALSE           # Save base detrended image?
+  CHIP.FITS       BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS  BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL   FALSE           # Save chip-mosaic-ed image? 
+  BIN1.FITS       BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS       BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG       BOOL    FALSE           # Save 1st binned jpeg?
+  BIN2.JPEG       BOOL    TRUE            # Save 2nd binned jpeg?
+  BIN1.XBIN       S32     1               # Image is already binned
+  BIN1.YBIN       S32     1               # Image is already binned
+  BIN2.XBIN       S32     1               # Image is already binned
+  BIN2.YBIN       S32     1               # Image is already binned
+
+  PPIMAGE.JPEG2  METADATA
+    COLORMAP      STR     -greyscale
+    SCALE.MODE    STR     RANGE
+    SCALE.MIN     F32     -5.0
+    SCALE.MAX     F32     +5.0
+  END
+END
+
+# Save JPEG from BIN1 for flat
+PPIMAGE_J1_IMAGE_R      METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+  BASE.FITS       BOOL    FALSE           # Save base image?
+  BASE.MASK.FITS  BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL   FALSE           # Save base detrended image?
+  CHIP.FITS       BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS  BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL   FALSE           # Save chip-mosaic-ed image? 
+  BIN1.FITS       BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS       BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG       BOOL    TRUE            # Save 1st binned jpeg?
+  BIN2.JPEG       BOOL    FALSE           # Save 2nd binned jpeg?
+  BIN1.XBIN       S32     1               # Image is already binned
+  BIN1.YBIN       S32     1               # Image is already binned
+  BIN2.XBIN       S32     1               # Image is already binned
+  BIN2.YBIN       S32     1               # Image is already binned
+
+  PPIMAGE.JPEG1  METADATA
+    COLORMAP      STR     -greyscale
+    SCALE.MODE    STR     RANGE
+    SCALE.MIN     F32     -2.0
+    SCALE.MAX     F32     +3.0
+  END
+END
+
+# Save JPEG from BIN2 for flat
+PPIMAGE_J2_IMAGE_R        METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+  BASE.FITS       BOOL    FALSE           # Save base image?
+  BASE.MASK.FITS  BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL   FALSE           # Save base detrended image?
+  CHIP.FITS       BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS  BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL   FALSE           # Save chip-mosaic-ed image? 
+  BIN1.FITS       BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS       BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG       BOOL    FALSE           # Save 1st binned jpeg?
+  BIN2.JPEG       BOOL    TRUE            # Save 2nd binned jpeg?
+  BIN1.XBIN       S32     1               # Image is already binned
+  BIN1.YBIN       S32     1               # Image is already binned
+  BIN2.XBIN       S32     1               # Image is already binned
+  BIN2.YBIN       S32     1               # Image is already binned
+
+  PPIMAGE.JPEG2  METADATA
+    COLORMAP      STR     -greyscale
+    SCALE.MODE    STR     RANGE
+    SCALE.MIN     F32     -2.0
+    SCALE.MAX     F32     +3.0
+  END
+END
+
+# Save JPEG from BIN1 for mask
+PPIMAGE_J1_IMAGE_M        METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+  BASE.FITS       BOOL    FALSE           # Save base image?
+  BASE.MASK.FITS  BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL   FALSE           # Save base detrended image?
+  CHIP.FITS       BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS  BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL   FALSE           # Save chip-mosaic-ed image? 
+  BIN1.FITS       BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS       BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG       BOOL    TRUE            # Save 1st binned jpeg?
+  BIN2.JPEG       BOOL    FALSE           # Save 2nd binned jpeg?
+  BIN1.XBIN       S32     1               # Image is already binned
+  BIN1.YBIN       S32     1               # Image is already binned
+  BIN2.XBIN       S32     1               # Image is already binned
+  BIN2.YBIN       S32     1               # Image is already binned
+
+  PPIMAGE.JPEG1    METADATA
+    COLORMAP       STR     -greyscale
+    SCALE.MODE     STR     FRACTION
+    SCALE.MIN      F32     0.90
+    SCALE.MAX      F32     1.10
+  END
+END
+
+# Save JPEG from BIN2 for mask
+PPIMAGE_J2_IMAGE_M        METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+  BASE.FITS       BOOL    FALSE           # Save base image?
+  BASE.MASK.FITS  BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL   FALSE           # Save base detrended image?
+  CHIP.FITS       BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS  BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL   FALSE           # Save chip-mosaic-ed image? 
+  BIN1.FITS       BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS       BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG       BOOL    FALSE           # Save 1st binned jpeg?
+  BIN2.JPEG       BOOL    TRUE            # Save 2nd binned jpeg?
+  BIN1.XBIN       S32     1               # Image is already binned
+  BIN1.YBIN       S32     1               # Image is already binned
+  BIN2.XBIN       S32     1               # Image is already binned
+  BIN2.YBIN       S32     1               # Image is already binned
+
+  PPIMAGE.JPEG2    METADATA
+    COLORMAP       STR     -greyscale
+    SCALE.MODE     STR     FRACTION
+    SCALE.MIN      F32     0.95
+    SCALE.MAX      F32     1.05
+  END
+END
+
+# Save JPEG from BIN1 for mask
+PPIMAGE_J1_RESID_M        METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+  BASE.FITS       BOOL    FALSE           # Save base image?
+  BASE.MASK.FITS  BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL   FALSE           # Save base detrended image?
+  CHIP.FITS       BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS  BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL   FALSE           # Save chip-mosaic-ed image? 
+  BIN1.FITS       BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS       BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG       BOOL    TRUE            # Save 1st binned jpeg?
+  BIN2.JPEG       BOOL    FALSE           # Save 2nd binned jpeg?
+  BIN1.XBIN       S32     1               # Image is already binned
+  BIN1.YBIN       S32     1               # Image is already binned
+  BIN2.XBIN       S32     1               # Image is already binned
+  BIN2.YBIN       S32     1               # Image is already binned
+
+  PPIMAGE.JPEG1    METADATA
+    COLORMAP       STR     -greyscale
+    SCALE.MODE     STR     FRACTION
+    SCALE.MIN      F32     0.95
+    SCALE.MAX      F32     1.05
+  END
+END
+
+# Save JPEG from BIN2 for mask
+PPIMAGE_J2_RESID_M        METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+  BASE.FITS       BOOL    FALSE           # Save base image?
+  BASE.MASK.FITS  BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL   FALSE           # Save base detrended image?
+  CHIP.FITS       BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS  BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL   FALSE           # Save chip-mosaic-ed image? 
+  BIN1.FITS       BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS       BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG       BOOL    FALSE           # Save 1st binned jpeg?
+  BIN2.JPEG       BOOL    TRUE            # Save 2nd binned jpeg?
+  BIN1.XBIN       S32     1               # Image is already binned
+  BIN1.YBIN       S32     1               # Image is already binned
+  BIN2.XBIN       S32     1               # Image is already binned
+  BIN2.YBIN       S32     1               # Image is already binned
+
+  PPIMAGE.JPEG2    METADATA
+    COLORMAP       STR     -greyscale
+    SCALE.MODE     STR     FRACTION
+    SCALE.MIN      F32     0.95
+    SCALE.MAX      F32     1.05
+  END
+END
+
+# JPEG images for different types of residual images
+# Save JPEG from BIN1 for bias
+PPIMAGE_MOSAIC     METADATA
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BASE.FITS        BOOL    FALSE           # Save base image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  BIN1.FITS        BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS        BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG        BOOL    FALSE           # Save 1st binned jpeg?
+  BIN2.JPEG        BOOL    FALSE           # Save 2nd binned jpeg?
+  FPA1.FITS        BOOL    TRUE            # Save 1st binned fpa image? 
+  FPA2.FITS        BOOL    FALSE           # Save 2nd binned fpa image? 
+  BIN1.XBIN        S32     1               # Image is already binned
+  BIN1.YBIN        S32     1               # Image is already binned
+  BIN2.XBIN        S32     1               # Image is already binned
+  BIN2.YBIN        S32     1               # Image is already binned
+END
+
+# generate chip-mosaiced image
+PPIMAGE_CHIPMOSAIC     METADATA
+  OVERSCAN         BOOL    FALSE           # Overscan subtraction
+  BIAS             BOOL    FALSE           # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BASE.FITS        BOOL    FALSE           # Save base image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  BIN1.FITS        BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS        BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG        BOOL    FALSE           # Save 1st binned jpeg?
+  BIN2.JPEG        BOOL    FALSE           # Save 2nd binned jpeg?
+  FPA1.FITS        BOOL    FALSE            # Save 1st binned fpa image? 
+  FPA2.FITS        BOOL    FALSE           # Save 2nd binned fpa image? 
+  BIN1.XBIN        S32     1               # Image is already binned
+  BIN1.YBIN        S32     1               # Image is already binned
+  BIN2.XBIN        S32     1               # Image is already binned
+  BIN2.YBIN        S32     1               # Image is already binned
+END
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/ppMerge.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/ppMerge.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/ppMerge.config	(revision 22157)
@@ -0,0 +1,100 @@
+# Recipe configuration for ppMerge
+
+ROWS            S32     128		# Number of rows to read at once
+ELECTRONS       F32     100.0           # Minimum number of electrons for useful signal
+SAMPLE          S32     100000          # Sampling factor for measuring the background
+REJ		F32	3.0		# Rejection threshold (sigma)
+ITER		S32	2		# Number of rejection iterations
+FRACHIGH	F32	0.0		# Fraction of high pixels to reject immediately
+FRACLOW		F32	0.0		# Fraction of low pixels to reject immediately
+NKEEP		S32	5		# Minimum number of pixels in stack to keep
+WEIGHTS		BOOL	FALSE		# Use image weights in combination?
+FRINGE.NUM	S32	10000		# Number of fringe regions
+FRINGE.SIZE	S32	5		# Half-size of fringe regions
+FRINGE.XSMOOTH	S32	5		# Number of smoothing regions in x
+FRINGE.YSMOOTH	S32	11		# Number of smoothing regions in y
+SHUTTER.SIZE	S32	128		# Size for shutter measurement regions
+MASK.SUSPECT	F32	5.0		# Threshold for suspect pixels (sigma)
+MASK.BAD	F32	0.2		# Threshold for bad pixels
+MASK.MODE	STR	FRACTION	# Mode for identifying bad pixels in the suspect map
+MASK.CHIPSTATS	BOOL	TRUE		# Measure stats for masking by chip (otherwise by readout)?
+MASK.GROW	S32	0		# Grow bad pixels by this radius
+MASK.SET.VALUE	STR	FLAT		# set this bit in the output mask
+
+COMBINE		STR	CLIPPED		# Statistic to use for combination
+MEAN		STR	ROBUST_MEDIAN	# Statistic to use to measure the mean
+STDEV		STR	ROBUST_STDEV	# Statistic to use to measure the stdev
+
+MASK.SMOOTH.SUSPECT BOOL TRUE           # smooth the suspect-pixel image before making mask
+MASK.SMOOTH.SCALE   F32  3.0            # sigma (pixels) of smoothing kernel
+
+STATS.BY.CHIP   BOOL    TRUE            # measure stats for masking by chip (or by readout)
+MASK.GROW.NPIX  S32     3               # measure stats for masking by chip (or by readout)
+
+# Ordinates for fitting dark current
+DARK.ORDINATES	METADATA
+	CELL.DARKTIME	S32	1	# Traditional dark current term
+END
+DARK.NORM	STR	NONE		# Dark normalisation concept
+
+# Bias combination --- don't want min/max rejection
+PPMERGE_BIAS	METADATA
+	REJ		F32	3.0		# Rejection threshold (sigma)
+	ITER		S32	2		# Number of rejection iterations
+	FRACHIGH	F32	0.0		# Fraction of high pixels to reject immediately
+	FRACLOW		F32	0.0		# Fraction of low pixels to reject immediately
+	WEIGHTS		BOOL	FALSE		# Use image weights?
+	COMBINE		STR	CLIPPED		# Statistic to use for combination: 
+END
+
+
+# Dark combination --- don't want min/max rejection
+# More aggressive clipping than bias, so as to remove CRs
+PPMERGE_DARK	METADATA
+	REJ		F32	3.0		# Rejection threshold (sigma)
+	ITER		S32	2		# Number of rejection iterations
+	FRACHIGH	F32	0.0		# Fraction of high pixels to reject immediately
+	FRACLOW		F32	0.0		# Fraction of low pixels to reject immediately
+	WEIGHTS		BOOL	TRUE		# Use image weights?
+	COMBINE		STR	CLIPPED		# Statistic to use for combination: 
+END
+
+# Flat combination --- use min/max rejection
+PPMERGE_FLAT	METADATA
+	REJ		F32	3.0		# Rejection threshold (sigma)
+	ITER		S32	1		# Number of rejection iterations
+	FRACHIGH	F32	0.3		# Fraction of high pixels to reject immediately
+	FRACLOW		F32	0.1		# Fraction of low pixels to reject immediately
+	NKEEP		S32	5		# Minimum number of pixels in stack to keep
+	WEIGHTS		BOOL	TRUE		# Use image weights?
+	COMBINE		STR	MEAN		# Statistic to use for combination: 
+END
+
+
+# Fringe combination --- already included in default, above
+PPMERGE_FRINGE	METADATA
+	FRACHIGH	F32	0.1		# Fraction of high pixels to reject immediately
+	WEIGHTS		BOOL	TRUE		# Use image weights?
+END
+
+# Mask generation --- already included in default, above
+PPMERGE_DARKMASK METADATA
+	ITER		S32	2		# Number of iterations
+	MASK.BAD	F32	0.2		# Threshold for bad pixels (sigma)
+	MASK.MODE	STR	FRACTION	# Mode for identifying bad pixels in the suspect map
+        MASK.SET.VALUE	STR	DARK		# set this bit in the output mask
+END
+
+# Mask generation --- already included in default, above
+PPMERGE_FLATMASK METADATA
+	ITER		S32	2		# Number of iterations
+	MASK.BAD	F32	0.2		# Threshold for bad pixels (sigma)
+	MASK.MODE	STR	FRACTION	# Mode for identifying bad pixels in the suspect map
+        MASK.SET.VALUE	STR	FLAT		# set this bit in the output mask
+END
+
+# Shutter generation --- already included in default, above
+PPMERGE_SHUTTER	METADATA
+	REJ		F32	2.0		# Rejection threshold (sigma)
+	ITER		S32	1		# Number of rejection iterations
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/ppSim.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/ppSim.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/ppSim.config	(revision 22157)
@@ -0,0 +1,139 @@
+### Recipe values for ppSim, the Pan-STARRS data simulator
+
+## XXX need to consider this better: used to run / skip fake, should be a function of chip (and context?)
+SKIP.FAKE       BOOL    TRUE
+
+IMAGE.TYPE      STR     BIAS            # default image type
+
+BIAS            BOOL    TRUE
+BIAS.LEVEL	F32	1000.0		# Bias level (e)
+BIAS.RANGE	F32	20.0		# Range of bias values (e)
+BIAS.ORDER	S32	7		# Order for overscan polynomial
+
+DARK            BOOL    TRUE
+DARK.RATE	F32	5.0		# Dark current (e/s)
+
+FLAT            BOOL    TRUE
+FLAT.SIGMA	F32	3.0		# Flat gaussian width (FPA sizes)
+FLAT.RATE	F32	2000.0		# Flat illumination rate (e/s)
+
+SHUTTER.TIME	F32	1.0		# Time for shutter to fully open
+SCATTER.FRAC    F32     0.0
+
+SKY		BOOL	TRUE		# Sky illumination rate (e/s)
+SKY.RATE	F32	20.0		# Sky illumination rate (e/s)
+
+PHOTOM          BOOL    FALSE           # perform photometry on the faked stars?
+
+FORCED.PHOT     BOOL    FALSE           # measure forced photometry for specific points?
+FORCED.CATDIR   STR     NONE            # source of the forced photometry positions
+FORCED.PHOTCODE STR     r               # source of the forced photometry positions
+
+STARS.REAL	BOOL	TRUE		# Add stars from a catalogue?
+MATCH.DENSITY   BOOL    TRUE            # significance of faintest sources
+
+STARS.FAKE      BOOL    TRUE		# Add fake stars, randomly distributed?
+STARS.LUM	F32	0.6		# Stellar luminosity function slope (magnitude slope)
+STARS.MAG	F32	15.5		# Brightest magnitude for fake stars
+STARS.DENSITY	F32     50.0		# Stellar density (per square degree) at the brightest magnitude
+STARS.SIGMA.LIM F32      5.0            # significance of faintest sources
+
+STARS.FLAT.LUM  BOOL    FALSE           # use a (non-physical) constant number of stars per mag bin?
+STARS.FLAT.NUM  S32     5000            # number of stars to generate for flat magnitude distribution
+PSF.MODEL       STR     PS_MODEL_QGAUSS
+
+GALAXY.FAKE     BOOL    FALSE		# Generate fake galaxies?
+
+GALAXY.LUM	  F32	-0.8		# Stellar luminosity function slope
+GALAXY.MAG	  F32	11.0		# Brightest magnitude for fake stars
+GALAXY.DENSITY	  F32	6.0		# Galaxy density (per square degree) at the brightest magnitude
+
+GALAXY.RMAJOR.MIN F32	2.0		# Minimum semi-major axis for galaxy models
+GALAXY.RMAJOR.MAX F32	10.0		# Maximum semi-major axis for galaxy models
+
+GALAXY.ARATIO.MIN F32	1.0		# Minimum axis ratio for galaxy models
+GALAXY.ARATIO.MAX F32	0.2		# Maximum axis ratio for galaxy models
+
+GALAXY.INDEX.MIN F32    0.25		# Minimum Sersic index for galaxy models
+GALAXY.INDEX.MAX F32	0.500           # Maximum Sersic index for galaxy models
+
+GALAXY.THETA.MIN F32    0.0		# Minimum position angle for galaxy models
+GALAXY.THETA.MAX F32	3.14            # Maximum position angle for galaxy models
+
+GALAXY.GRID      BOOL   FALSE		# Generate a (regular) grid of galaxy models?
+GALAXY.GRID.DX   S32    200             # Spacing between galaxies in grid in x
+GALAXY.GRID.DY   S32    200             # Spacing between galaxies in grid in y
+
+GALAXY.MODEL     STR    PS_MODEL_SERSIC
+
+GALAXY.MODEL     STR    PS_MODEL_SERSIC
+
+BADPIX.SEED	U64	123456789	# Seed for RNG in creating deterministic bad pixels
+BADPIX.FRAC	F32	0.0001		# Fraction of bad pixels
+
+### The following options are used if not defined by the concepts (e.g., usually read from headers)
+GAIN		F32	1.0		# Default gain (e/ADU)
+READNOISE	F32	10.0		# Default read noise (e)
+OVERSCAN.SIZE	S32	32		# Default overscan columns
+SATURATION	F32	65535		# Default saturation level (ADU)
+
+PIXEL.SCALE     F32     0.26            # pixel size in arcsec
+
+# filter-dependent parameters
+ZEROPTS MULTI
+
+ZEROPTS  METADATA
+  FILTER    STR  g
+  ZERO_PT   F32  24.0
+END
+ZEROPTS  METADATA
+  FILTER    STR  r
+  ZERO_PT   F32  25.15
+END
+ZEROPTS  METADATA
+  FILTER    STR  i
+  ZERO_PT   F32  25.00
+END
+ZEROPTS  METADATA
+  FILTER    STR  z
+  ZERO_PT   F32  24.50
+END
+ZEROPTS  METADATA
+  FILTER    STR  y
+  ZERO_PT   F32  24.00
+END
+
+# the following values are not set by default, but supplied if needed from arguments
+EXPTIME     F32     NAN                 # exposure time (seconds)
+RA          F32     NAN                 # exposure time (seconds)
+DEC         F32     NAN                 # exposure time (seconds)
+PA          F32     NAN                 # exposure time (seconds)
+SEEING      F32     NAN                 # exposure time (seconds)
+SCALE       F32     NAN                 # exposure time (seconds)
+ZEROPOINT   F32     NAN                 # exposure time (seconds)
+SKY.MAGS    F32     NAN                 # exposure time (seconds)
+
+FILTER      STR     NONE                # exposure time (seconds)
+BINNING     S32     1                   # exposure time (seconds)
+
+ADDFAKES    METADATA
+ BIAS            BOOL    FALSE
+ DARK            BOOL    FALSE
+ FLAT            BOOL    FALSE
+ SKY		 BOOL	 FALSE		# Sky illumination rate (e/s)
+ STARS.REAL  	 BOOL	 FALSE		# Add stars from a catalogue?
+ STARS.FAKE  	 BOOL	 TRUE		# Add stars from a catalogue?
+END
+
+FAKEPHOT    METADATA
+ BIAS            BOOL    FALSE
+ DARK            BOOL    FALSE
+ FLAT            BOOL    FALSE
+ SKY		 BOOL	 FALSE		# Sky illumination rate (e/s)
+ STARS.REAL  	 BOOL	 FALSE		# Add stars from a catalogue?
+ STARS.FAKE  	 BOOL	 TRUE		# Add stars from a catalogue?
+ PHOTOM          BOOL    FALSE           # perform photometry on the faked stars?
+ FORCED.PHOT     BOOL    TRUE
+ FORCED.CATDIR   STR     DVO.FORCE
+END
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/ppStack.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/ppStack.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/ppStack.config	(revision 22157)
@@ -0,0 +1,31 @@
+# Recipe configuration for ppStack (image combination)
+
+ITER		S32	1		# Number of rejection iterations
+COMBINE.REJ	F32	4.0		# Rejection threshold in combination (sigma)
+MASK.BAD	STR	BLANK,SAT,BAD	# Mask value of bad pixels
+MASK.BLANK	STR	BLANK		# Mask value to give blank pixels
+THRESHOLD.MASK	F32	0.8		# Threshold for mask deconvolution (0..1)
+IMAGE.REJ	F32	0.2		# Rejected pixel fraction threshold for rejecting entire image
+ROWS		S32	64		# Number of rows to read at once
+VARIANCE	BOOL	TRUE		# Use variance in rejection?
+SAFE		BOOL	TRUE		# Play safe when combining small number of values?
+
+RENORM		BOOL	FALSE		# Renormalise variance maps?
+RENORM.MEAN	STR	ROBUST_MEDIAN	# Statistic to use for mean in renormalisation
+RENORM.STDEV	STR	ROBUST_STDEV	# Statistic to use for stdev in renormalisation
+RENORM.WIDTH	S32	300		# Size of renormalisation boxes (pixels)
+
+SOURCE.RADIUS	F32	2.0		# Radius (pixels) for matching sources
+SOURCE.MIN	S32	15		# Minimum number of sources for merging source lists
+SOURCE.ITER	S32	2		# Number of rejection iterations for magnitude difference
+SOURCE.REJ	F32	3.0		# Rejection limit (sigma) for magnitude difference
+
+PSF.INSTANCES	S32	5		# Number of instances for PSF generation
+PSF.RADIUS	F32	20.0		# Radius for PSF generation
+PSF.ORDER	S32	3		# Order of spatial variation for PSF generation
+PSF.MODEL	STR	PS_MODEL_RGAUSS	# Model for PSF generation
+
+TEMP.IMAGE	STR	conv.im.fits	# Suffix for convolved images
+TEMP.MASK	STR	conv.mk.fits	# Suffix for convolved masks
+TEMP.WEIGHT	STR	conv.wt.fits	# Suffix for convolved weight maps
+TEMP.DELETE	BOOL	FALSE		# Delete temporary files on completion?
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/ppStats.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/ppStats.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/ppStats.config	(revision 22157)
@@ -0,0 +1,133 @@
+### Default ppStats recipe
+
+# Options governing statistics
+SAMPLE          F32     0.1     # Fraction of cell to sample
+
+# Mask value to use for statistics.  this is only used for stand-alone
+# operations.  ppStats library calls pass a mask value as needed
+MASKVAL         STR     SAT,BAD 
+
+
+# Define the outputs as MULTI
+HEADER          MULTI
+CONCEPT         MULTI
+STAT            MULTI
+SUMMARY         MULTI
+
+# Add dummy values, to define the MULTI --- due to limitations in the MDC language
+HEADER		STR	_UNDEF
+CONCEPT		STR	_UNDEF
+STAT		STR	_UNDEF
+SUMMARY		STR	_UNDEF
+ANALYSIS        STR     _UNDEF
+
+# basic stats for output images
+CHIPSTATS	METADATA
+  STAT          MULTI
+  STAT		STR	ROBUST_MEDIAN
+  STAT          STR     ROBUST_STDEV   # Background statistics estimators
+
+  HEADER        MULTI
+  HEADER        STR     OVER_VAL
+  HEADER        STR     FWHM_X # major axis FWHM (in pixels)
+  HEADER        STR     FWHM_Y # minor axis FWHM (in pixels)
+  HEADER        STR     DT_DET   # elapsed time in detrend processing
+  HEADER        STR     DT_PHOT  # elapsed time in photometry processing
+  HEADER        STR     DT_ASTR  # elapsed time in astrometry processing
+  HEADER        STR     APMIFIT
+  HEADER        STR     DAPMIFIT
+  HEADER        STR     CERROR
+  HEADER        STR     NSTARS
+  HEADER        STR     NASTRO
+
+  ANALYSIS      MULTI   # metadata blocks to search in chip/cell/readout->analysis
+  ANALYSIS      STR     PSPHOT.HEADER
+  ANALYSIS      STR     PSASTRO.HEADER
+END
+
+# basic stats for residual images
+RESIDUAL	METADATA
+  STAT          MULTI
+  STAT		STR	ROBUST_MEDIAN
+  STAT          STR     ROBUST_STDEV   # Background statistics estimators
+  STAT          STR     SAMPLE_SKEWNESS
+  STAT          STR     SAMPLE_KURTOSIS
+  HEADER        MULTI
+  HEADER        STR     FRNG_00        # fringe amplitude, if measured
+  HEADER        STR     FRNG_00D       # fringe error, if measured
+  HEADER        STR     FRNG_01        # fringe amplitude, if measured
+  HEADER        STR     FRNG_01D       # fringe error, if measured
+  HEADER        STR     FRNG_02        # fringe amplitude, if measured
+  HEADER        STR     FRNG_02D       # fringe error, if measured
+END
+
+### ppStats recipe for injection
+INJECT  METADATA
+  CONCEPT       MULTI
+END
+
+### ppStats recipe for registration
+REGISTER  	METADATA
+  CONCEPT       MULTI
+  STAT          MULTI
+  SUMMARY       MULTI
+
+  CONCEPT       STR     FPA.OBJECT      # Object name
+  CONCEPT       STR     FPA.OBSTYPE     # Observation type
+  CONCEPT       STR     FPA.FILTER      # Filter
+  CONCEPT       STR     FPA.RA FPA.DEC  # Telescope pointing
+
+  CONCEPT       STR     FPA.COMMENT     # Obs Comment
+
+  CONCEPT       STR     FPA.AIRMASS     # Airmass
+  CONCEPT       STR     FPA.ALT FPA.AZ  # Telescopy alt/az
+  CONCEPT       STR     FPA.POSANGLE    # Rotator angle
+  CONCEPT       STR     FPA.TIME        # Time of exposure
+  CONCEPT       STR     FPA.TELESCOPE   # Telescope (eg, CFHT)
+  CONCEPT       STR     FPA.CAMERA      # Instrument (eg, CFH12K)
+  CONCEPT       STR     CHIP.TEMP       # Detector temperature
+  CONCEPT       STR     CELL.EXPOSURE   # Exposure time
+
+  CONCEPT       STR     FPA.M1X         # Primary x position
+  CONCEPT       STR	FPA.M1Y         # Primary y position
+  CONCEPT       STR	FPA.M1Z         # Primary z position
+  CONCEPT       STR	FPA.M1TIP       # Primary tip
+  CONCEPT       STR	FPA.M1TILT      # Primary tilt
+  CONCEPT       STR	FPA.M2X         # Secondary x position
+  CONCEPT       STR	FPA.M2Y         # Secondary y position
+  CONCEPT       STR	FPA.M2Z         # Secondary z position
+  CONCEPT       STR	FPA.M2TIP       # Secondary tip position
+  CONCEPT       STR	FPA.M2TILT      # Secondary tilt position
+  CONCEPT       STR	FPA.ENV.TEMP    # external temperature
+  CONCEPT       STR	FPA.ENV.HUMID   # external humidity
+  CONCEPT       STR	FPA.ENV.WIND    # external wind speed
+  CONCEPT       STR	FPA.ENV.DIR     # external wind direction
+
+  CONCEPT       STR     FPA.TELTEMP.M1     # Primary mirror temps (C) 
+  CONCEPT       STR     FPA.TELTEMP.M1CELL # Primary mirror support temps (C)   
+  CONCEPT       STR     FPA.TELTEMP.M2     # Secondary mirror temps (C
+  CONCEPT       STR     FPA.TELTEMP.SPIDER # Spider temperatures (C)  
+  CONCEPT       STR     FPA.TELTEMP.TRUSS  # Mid truss temperatures (C
+  CONCEPT       STR     FPA.TELTEMP.EXTRA  # Miscellaneous temperatures (C)     
+
+  CONCEPT       STR	FPA.PON.TIME    # time since last power on
+
+  STAT          STR     ROBUST_MEDIAN   # Background estimator
+  STAT          STR     ROBUST_STDEV    # Background standard deviation estimator
+  SUMMARY	STR	SAT_PIXEL_NUM
+  SUMMARY	STR	SAT_PIXEL_FRAC
+END
+
+
+# basic stats for warped images
+WARPSTATS	METADATA
+  HEADER        MULTI
+  CONCEPT       MULTI
+  STAT          MULTI
+  SUMMARY       MULTI
+
+  STAT		STR	ROBUST_MEDIAN
+  STAT          STR     ROBUST_STDEV    # Background statistics estimators
+  SUMMARY       STR     GOOD_PIXEL_NUM  # Number of good pixels
+  SUMMARY       STR     GOOD_PIXEL_FRAC # Fraction of good pixels
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/ppSub.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/ppSub.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/ppSub.config	(revision 22157)
@@ -0,0 +1,38 @@
+### Recipe file for ppSub
+
+KERNEL.TYPE	STR	RINGS		# Kernel type to use (POIS|ISIS|SPAM|FRIES|GUNK|RINGS)
+KERNEL.SIZE     S32	35		# Kernel half-size (pixels)
+SPATIAL.ORDER   S32	2		# Spatial polynomial order
+REGION.SIZE	F32	0		# Iso-kernel region size (pixels)
+STAMP.SPACING   F32	400		# Typical spacing between stamps (pixels)
+STAMP.FOOTPRINT S32	35		# Size of stamps (pixels)
+STAMP.THRESHOLD F32	0		# Flux threshold for stamps (ADU)
+ITER            S32	10		# Number of rejection iterations
+REJ             F32	2.5		# Rejection level (std dev)
+MASK.BAD        STR	BLANK,BAD,SAT	# Mask value for bad pixels
+MASK.BLANK      STR	BLANK		# Mask value to give blank pixels
+BADFRAC		F32	0.8		# Maximum fraction of bad pixels
+@ISIS.WIDTHS	F32	1 3 5		# Gaussian FWHMs for ISIS kernels
+@ISIS.ORDERS	S32	2 2 2		# Polynomial orders for ISIS kernels
+SPAM.BINNING	S32	2		# Binning in outer region for SPAM kernels
+INNER		S32	5		# Inner half-size for SPAM and FRIES kernels
+RINGS.ORDER	S32	2		# Polynomial order for RINGS kernels
+PENALTY		F32	1.0		# Penalty for wideness
+
+OPTIMUM		BOOL	FALSE		# Derive optimum parameters for ISIS and GUNK kernels
+OPTIMUM.MIN	F32	1.0		# Minimum width for optimum FWHM search
+OPTIMUM.MAX	F32	11.0		# Maximum width for optimum FWHM search
+OPTIMUM.STEP	F32	1.0		# Step in width for optimum FWHM search
+OPTIMUM.TOL	F32	3.0e-3		# Maximum difference in chi^2 between iterations to settle for
+OPTIMUM.ORDER	S32	2		# Maximum polynomial order for optimum search
+
+RENORM		BOOL	FALSE		# Renormalise weight maps?
+RENORM.WIDTH	S32	500		# Size of renormalisation boxes
+
+PHOTOMETRY	BOOL	FALSE		# Perform photometry?
+
+### Modifications to use when stacking data
+STACK	METADATA
+	KERNEL.TYPE	STR	RINGS	# Kernel type to use (POIS|ISIS|SPAM|FRIES|GUNK|RINGS)
+	KERNEL.SIZE     S32     15      # Kernel half-size (pixels)
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/psastro.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/psastro.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/psastro.config	(revision 22157)
@@ -0,0 +1,141 @@
+
+PSASTRO.SAVE.REFSTARS      BOOL FALSE
+PSASTRO.ONLY.REFSTARS      BOOL FALSE
+
+# perform single-chip astrometry?
+PSASTRO.CHIP.MODE           BOOL     TRUE
+
+# nominal plate scale (microns / pixel)
+PSASTRO.PIXEL.SCALE    F32  13.5
+
+# plotting options
+PSASTRO.PLOT.INST.MAG.MAX F32 -10.0
+PSASTRO.PLOT.INST.MAG.MIN F32 -17.0
+PSASTRO.PLOT.REF.MAG.MIN  F32 +10.0
+PSASTRO.PLOT.REF.MAG.MAX  F32 +20.0
+
+# extra field for ref stars:
+PSASTRO.FIELD.PADDING  F32 0.25
+
+# pmAstromGridMatch:
+PSASTRO.GRID.SEARCH    BOOL TRUE
+
+PSASTRO.GRID.MIN.ANGLE F32 -2.0 # start angle (degrees)
+PSASTRO.GRID.MAX.ANGLE F32 +2.0
+PSASTRO.GRID.DEL.ANGLE F32  0.5
+
+PSASTRO.GRID.MIN.SCALE F32  0.98
+PSASTRO.GRID.MAX.SCALE F32  1.04
+PSASTRO.GRID.DEL.SCALE F32  0.02
+
+PSASTRO.GRID.MIN.SIGMA F32  5.0
+
+# pmAstromGridAngle
+# max grid offset in FP units (microns)
+# use plate-scale to make this in pixels?
+PSASTRO.GRID.OFFSET    F32  10000.
+PSASTRO.GRID.SCALE     F32    500.
+PSASTRO.GRID.NSTAR.MAX S32    300 # max stars accepted for fitting
+
+# sources with these mask flags raised should be ignored
+PSASTRO.IGNORE         STR    CRLIMIT,SATURATED,DEFECT,SATSTAR,BLEND,FAIL
+PSASTRO.ROUGH.MODEL    STR    none
+
+# these tweak are in FP units (pixels, currently)
+PSASTRO.TWEAK.SCALE     F32      1
+PSASTRO.TWEAK.RANGE     F32     75
+PSASTRO.TWEAK.SMOOTH    F32      2
+PSASTRO.TWEAK.NSIGMA    F32      3
+
+# single-chip radius match in pixels
+PSASTRO.MATCH.RADIUS   F32    8
+PSASTRO.MATCH.RADIUS.N0 F32   0
+PSASTRO.MATCH.RADIUS.N1 F32   0
+PSASTRO.MATCH.RADIUS.N2 F32   0
+PSASTRO.MATCH.RADIUS.N3 F32   0
+PSASTRO.MATCH.RADIUS.N4 F32   0
+PSASTRO.MATCH.RADIUS.N5 F32   0
+PSASTRO.MATCH.RADIUS.N6 F32   0
+PSASTRO.MATCH.RADIUS.N7 F32   0
+PSASTRO.MATCH.FIT.NITER S32   1
+
+# pmAstromMatchFit
+PSASTRO.CHIP.ORDER     S32      1  # fit order
+PSASTRO.CHIP.NITER     S32      3  # fit clipping iterations
+PSASTRO.CHIP.NSIGMA    F32      3  # fit clipping sigmas
+
+PSASTRO.MAX.ERROR      F32      1.5 # max allow error for valid solution (UNITS?)
+PSASTRO.MIN.NSTAR      S32      3   # min fitted stars in solution
+PSASTRO.MAX.NSTAR      S32      300   # max fitted stars in solution
+
+PSASTRO.MAX.NRAW       S32      0   # max stars accepted for fitting (0 for all)
+PSASTRO.MAX.NREF       S32      0   # max stars accepted for fitting (0 for all)
+
+PSASTRO.MIN.INST.MAG.RAW       F32      0.0   # min instrumental magnitude for stars accepted for fitting
+PSASTRO.MAX.INST.MAG.RAW       F32      0.0   # max instrumental magnitude for stars accepted for fitting
+
+PSASTRO.MATCH.LUMFUNC  BOOL     FALSE
+
+# option may be MAX, MIN, or VALUE. for VALUE, look up 
+# plate scale for each chip in the concepts
+PSASTRO.COMMON.SCALE.OPTION	STR	MAX
+
+# mosaic-mode radius match in pixels?
+PSASTRO.MOSAIC.RADIUS.N0   F32    8
+PSASTRO.MOSAIC.RADIUS.N1   F32    6
+PSASTRO.MOSAIC.RADIUS.N2   F32    4
+PSASTRO.MOSAIC.RADIUS.N3   F32    0.0
+
+# Mosaic Astrometry options
+PSASTRO.MOSAIC.MODE           BOOL     FALSE
+PSASTRO.MOSAIC.ORDER          S32      3  # fit order
+
+PSASTRO.MOSAIC.CHIP.ORDER     S32       1 # fit order
+PSASTRO.MOSAIC.CHIP.ORDER.N0  S32      -1 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N1  S32      -1 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N2  S32      -1 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N3  S32      -1 # fit order (-1 means use default)
+
+PSASTRO.MOSAIC.CHIP.NITER     S32      3  # fit clipping iterations
+PSASTRO.MOSAIC.CHIP.NSIGMA    F32      3  # fit clipping sigmas
+
+PSASTRO.MOSAIC.MAX.ERROR.N0   F32      1.5 # max allow error for valid solution (UNITS?)
+PSASTRO.MOSAIC.MAX.ERROR.N1   F32      1.5 # max allow error for valid solution (UNITS?)
+PSASTRO.MOSAIC.MAX.ERROR.N2   F32      1.5 # max allow error for valid solution (UNITS?)
+PSASTRO.MOSAIC.MAX.ERROR.N3   F32      1.5 # max allow error for valid solution (UNITS?)
+PSASTRO.MOSAIC.MIN.NSTAR      S32      6   # min fitted stars in solution
+
+PSASTRO.MOSAIC.GRADIENT.NX    S32      2   # number of x-dir cells per chip
+PSASTRO.MOSAIC.GRADIENT.NY    S32      2   # number of y-dir cells per chip
+
+# 2MASS default configuration (use 2MASS_J as ref magnitude)
+PSASTRO.CATDIR                STR      2MASS
+DVO.GETSTAR                   STR      getstar
+DVO.GETSTAR.OUTFORMAT         STR      PS1_DEV_0
+DVO.GETSTAR.PHOTCODE          STR      2MASS_J
+DVO.GETSTAR.MAG.MAX           F32      22.0
+
+PSASTRO.2MASS                 METADATA
+  PSASTRO.CATDIR              STR      2MASS
+  DVO.GETSTAR.PHOTCODE        STR      2MASS_J
+  DVO.GETSTAR.MAG.MAX         F32      17.0
+END
+
+PSASTRO.SYNTH                 METADATA
+  PSASTRO.CATDIR              STR      SYNTH.GRIZY
+  DVO.GETSTAR.PHOTCODE        STR      r
+END
+
+LOAD.REF.ASTROM               BOOL     FALSE
+SAVE.REF.ASTROM               BOOL     FALSE
+
+PSASTRO.MODEL.REF.CHIP        STR      NONE
+
+PSASTRO.FIX.CHIPS             BOOL     FALSE
+PSASTRO.USE.MODEL             BOOL     FALSE
+
+PSASTRO.PIXEL.TOLERANCE       F32      0.1
+PSASTRO.ANGLE.TOLERANCE       F32      0.1
+
+PSASTRO.FINE METADATA
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/psphot.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/psphot.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/psphot.config	(revision 22157)
@@ -0,0 +1,247 @@
+
+# these options turn on different inputs and/or outputs
+SAVE.OUTPUT                         BOOL  TRUE
+SAVE.BACKMDL                        BOOL  FALSE
+SAVE.BACKMDL.STDEV                  BOOL  FALSE
+SAVE.BACKGND                        BOOL  FALSE
+SAVE.BACKSUB                        BOOL  FALSE
+SAVE.RESID                          BOOL  FALSE
+SAVE.PSF                            BOOL  FALSE
+LOAD.PSF                            BOOL  FALSE
+SAVE.PLOTS                          BOOL  FALSE
+
+# the zero point is used to set a basic scale for DVO
+# XXX it may not currently be read : double check this (EAM)
+ZERO_POINT                          F32   25.000          # zero point used by DVO
+ZERO_PT                             F32   25.000          # zero point used by DVO
+
+OUTPUT.FORMAT                       STR   PS1_DEV_1
+
+# these parameter govern how the background is measured
+BACKGROUND.XBIN                     S32   128             # size of background superpixels
+BACKGROUND.YBIN                     S32   128             # size of background superpixels
+IMSTATS_NPIX                        S32   10000           # number of pixels to use for sky estimate boxes:
+
+SKY_BIAS                            F32   0.0             # offset applied to measured sky (FOR TESTING)
+SKY_FIT_ORDER                       S32   0
+SKY_FIT_LINEAR                      BOOL  FALSE
+SKY_STAT                            STR   FITTED_MEAN_V4  # statistic used to measure background
+SKY_CLIP_SIGMA                      F32   2.0             # statistic used to measure background
+SKY_SIG                             F32   1.0             # optional sky error for 
+
+# allowed values for SKY_STAT: 
+# SAMPLE_MEAN, SAMPLE_MEDIAN, CLIPPED_MEAN, ROBUST_MEAN, ROBUST_QUARTILE, FITTED_MEAN
+
+# masking parameters (XXX EAM : rework this to use psRegion like ANALYSIS_REGION)
+XMIN                                F32   0               # minimum valid x-coord
+XMAX                                F32   0               # maximum valid x-coord
+YMIN                                F32   0               # minimum valid y-coord
+YMAX                                F32   0               # maximum valid y-coord
+
+# peak finding 
+PEAKS_SMOOTH_SIGMA                  F32   1.0             # smoothing kernel sigma in pixels
+PEAKS_SMOOTH_NSIGMA                 F32   2.0             # smoothing kernel width in sigmas
+PEAKS_NSIGMA_LIMIT                  F32   25.0            # peak significance threshold
+PEAKS_NSIGMA_LIMIT_2                F32   5.0             # peak significance threshold
+PEAKS_NMAX                          S32   0               # on first pass, only keep NMAX peaks (0 == all)
+
+# parameters to control the selection of the peak in the Sx,Sy plane
+MOMENTS_SCALE                       F32   0.25       
+MOMENTS_SN_MIN                      F32   100.0           # min S/N to measure moments
+MOMENTS_SX_MAX                      F32   5.0
+MOMENTS_SY_MAX                      F32   5.0
+MOMENTS_AR_MAX                      F32   1.5             # maximum axial ratio: 1 / AR < (sx / sy) < AR
+
+# basic object statistics
+SKY_INNER_RADIUS                    F32   15              # square annulus for local sky measurement
+SKY_OUTER_RADIUS                    F32   25              # square annulus for local sky measurement
+PSF_MOMENTS_RADIUS                  F32   3               # calculate initial source moments with this radius
+PSF_SN_LIM                          F32   50              # minimum S/N for stars used for PSF model
+PSF_MAX_NSTARS                      S32   200             # limit number of stars used for PSF model
+PSF_CLUMP_NSIGMA                    F32   1.5             # region of Sx,Sy plane to use for selecting PSF stars
+PSF_MIN_DS                          F32   0.01
+PSF_PARAM_WEIGHTS                   BOOL  FALSE
+
+# PSF model parameters : choose the PSF model
+# list as many PSF_MODEL options as desired
+# PSF_MODEL                         MULTI
+PSF_MODEL                           STR   PS_MODEL_GAUSS
+# PSF_MODEL                         STR   PS_MODEL_PGAUSS
+# PSF_MODEL                         STR   PS_MODEL_QGAUSS
+# PSF_MODEL                         STR   PS_MODEL_TGAUSS # not well tested, not very successful
+
+# PSF.TREND.MASK must be a 2D polynomial
+# the specified values are ignored but define the active components of the polynomial
+PSF.TREND.MASK                      METADATA  
+   NORDER_X                         S32   0               # number of x orders
+   NORDER_Y                         S32   0               # number of y orders
+   VAL_X00_Y00                      F64   1               # polynomial coefficient
+   NELEMENTS                        S32   1               # number of unmasked components
+END  # folder for 2D polynomial
+
+PSF.TREND.MODE                      STR POLY_ORD         
+PSF.TREND.NX                        S32   0 
+PSF.TREND.NY                        S32   0
+
+PSF.FAKE.ALLOW                      BOOL  FALSE           # Allow use of fake PSFs when having trouble?
+
+PSF_FIT_RADIUS                      F32   15.0            # fitting radius for test PSF model
+PSF_REF_RADIUS                      F32   25.0            # aperture magnitudes are scaled via 
+                                         # curve-of-growth to this radius
+# PSF-like source model parameters
+PSF_FIT_NSIGMA                      F32   1.0             # significance for pixel included in fit
+PSF_FIT_PADDING                     F32   2.0             # extra annulus to use for fit 
+PSF_SHAPE_NSIGMA                    F32   3.0             # max significance for shape variation
+PSF_MIN_SN                          F32   2.0             # reject objects below this significance
+PSF_MAX_CHI                         F32   50.0            # reject objects worse that this
+FULL_FIT_SN_LIM                     F32   50.0
+
+# the RESIDUALS are pixelized psf residual tables
+PSF.RESIDUALS                       BOOL  TRUE            # generate the residuals?
+PSF.RESIDUALS.XBIN                  S32   1               # Nx(residual) = Nx(input)*XBIN
+PSF.RESIDUALS.YBIN                  S32   1               # Ny(residual) = Ny(input)*YBIN
+PSF.RESIDUALS.NSIGMA                F32   3.0             # clip input stack of NSIGMA outliers
+PSF.RESIDUALS.INTERPOLATION         STR   BILINEAR        # interpolation to use when reconstructing residual
+PSF.RESIDUALS.STATISTIC             STR   ROBUST_MEDIAN   # statistic to use for generating the residual
+PSF.RESIDUALS.SPATIAL_ORDER         S32   0               # fit spatial variations of the residuals at this order (0,1)
+PSF.RESIDUALS.PIX.SN                F32   0.0             # keep this pixel if residual is more significant than this
+ 
+# this model is used for approximate subtraction in the main object analysis step
+EXT_MODEL                           STR   PS_MODEL_QGAUSS
+EXT_MIN_SN                          F32   50.0            # fit galaxies above this S/N limit
+EXT_FIT_NSIGMA                      F32   1               # significance for pixel included in fit
+EXT_FIT_PADDING                     F32   5               # extra annulus to use for fit 
+EXT_MOMENTS_RADIUS                  F32   9
+
+# extended source aperture-like measurements
+EXTENDED_SOURCE_ANALYSIS            BOOL  FALSE  # perform any of the aperture-like measurements?
+EXTENDED_SOURCE_SN_LIM              F32   20.0
+EXTENDED_SOURCE_PETROSIAN           BOOL  FALSE
+EXTENDED_SOURCE_ISOPHOTAL           BOOL  FALSE
+EXTENDED_SOURCE_ANNULI              BOOL  FALSE
+EXTENDED_SOURCE_KRON                BOOL  FALSE
+
+# Extended source fit parameters
+# these models are used for high-quality shape analysis after all sources have been removed
+EXTENDED_SOURCE_FITS                BOOL  FALSE  # perform the detailed extended sourc fits?
+
+# extended source models applied to sources
+EXTENDED_SOURCE_MODELS METADATA 
+ TYPE  	    EXTENDED_SOURCE_MODEL  MODEL            SNLIM   PSF_CONVOLVED
+ #GAUSS_RAW   EXTENDED_SOURCE_MODEL  PS_MODEL_GAUSS    20.0    FALSE
+ #PGAUSS_RAW  EXTENDED_SOURCE_MODEL  PS_MODEL_PGAUSS   20.0    FALSE
+ #QGAUSS_RAW  EXTENDED_SOURCE_MODEL   PS_MODEL_QGAUSS   20.0    FALSE
+ #SERSIC_RAW  EXTENDED_SOURCE_MODEL   PS_MODEL_SERSIC   20.0    FALSE
+ #GAUSS_PSF   EXTENDED_SOURCE_MODEL  PS_MODEL_GAUSS    20.0    TRUE
+ #PGAUSS_PSF  EXTENDED_SOURCE_MODEL  PS_MODEL_PGAUSS   20.0    TRUE
+ #QGAUSS_PSF  EXTENDED_SOURCE_MODEL  PS_MODEL_QGAUSS   20.0    TRUE
+ SERSIC_PSF  EXTENDED_SOURCE_MODEL  PS_MODEL_SERSIC   20.0    TRUE
+END
+
+# define the annuli (in pixels) for surface brightness measurements
+@RADIAL.ANNULAR.BINS.LOWER           F32     0.0   5.0  10.0  20.0  40.0  80.0
+@RADIAL.ANNULAR.BINS.UPPER           F32     5.0  10.0  20.0  40.0  80.0 160.0
+
+# Extended source fit parameters
+EXTENDED_TEST                       METADATA
+  EXTENDED_SOURCE_ANALYSIS            BOOL  TRUE  # perform any of the aperture-like measurements?
+  EXTENDED_SOURCE_SN_LIM              F32   20.0
+  EXTENDED_SOURCE_PETROSIAN           BOOL  TRUE
+  EXTENDED_SOURCE_ISOPHOTAL           BOOL  TRUE
+  EXTENDED_SOURCE_ANNULI              BOOL  TRUE
+  EXTENDED_SOURCE_KRON                BOOL  FALSE
+END
+
+ISOPHOTAL_FLUX                      F32   20.0
+PETROSIAN_R0                        F32    5.0 # reference radius in pixels
+PETROSIAN_FLUX_RATIO                F32    0.1 # we measure out to where flux = R0 * FLUX_RATIO
+
+FITMODE                             STR   BLEND  
+DEBLEND_PEAK_FRACTION               F32   0.1
+DEBLEND_SKY_NSIGMA                  F32   10.0
+
+# APTREND                           STR   NONE, CONSTANT, SKYBIAS, SKYSAT, XY_LIN, SKY_XY_LIN, SKYSAT_XY_LIN, ALL
+MEASURE.APTREND	                    BOOL  FALSE		### Gene says there's bugs in this part
+APTREND                             STR   CONSTANT
+AP_MIN_SN                           F32   25.0
+APTREND.NSTAR.MIN                   S32   5
+APTREND.ORDER.MAX                   S32   5
+
+# test options
+# BREAK_POINT may be one of (PEAKS, MOMENTS, PSFMODEL, ENSEMBLE)
+BREAK_POINT                         STR   NONE     
+PEAKS_OUTPUT_FILE                   STR   NONE
+MOMENTS_OUTPUT_FILE                 STR   NONE
+
+# ANALYSIS_REGION                   STR   [1000:1600,2800:3400]
+
+# optional parameter to limit the actual analysis to a fraction of the image
+# do not uncomment this in the master psphot.config file
+# ANALYSIS_REGION                   STR   [1000:1600,2800:3400]
+
+IGNORE_GROWTH                       BOOL  FALSE
+CONSTANT_PHOTOMETRIC_WEIGHTS        BOOL  TRUE 		  # Should the photometric code [currently only ensemblePSF] refuse to weight each pixel by it's significance?
+INTERPOLATE_AP                      BOOL  TRUE
+
+POISSON.ERRORS.PHOT.LMM             BOOL  TRUE   
+POISSON.ERRORS.PHOT.LIN             BOOL  FALSE
+POISSON.ERRORS.PARAMS               BOOL  TRUE
+
+PCM_BOX_SIZE                        S32   8
+
+NOISE.FACTOR                        F32   5.0
+NOISE.SIZE                          F32   2.0
+
+USE_FOOTPRINTS                      BOOL  F       	  # use new pmFootprint peak packaging
+FOOTPRINT_NPIXMIN                   S32   5       	  # Minimum size of a pmFootprint
+FOOTPRINT_NSIGMA_LIMIT              F32   20      	  # threshold for bright pmFootprint detection
+FOOTPRINT_NSIGMA_LIMIT_2            F32   4       	  # threshold for faint pmFootprint detection
+FOOTPRINT_GROW_RADIUS               S32   3       	  # How much to grow bright footprints
+FOOTPRINT_GROW_RADIUS_2             S32   5       	  # How much to grow faint footprints
+FOOTPRINT_CULL_NSIGMA_DELTA         F32   4       	  # Cull peaks that aren't nsigma above coll to neighbour
+FOOTPRINT_CULL_NSIGMA_MIN           F32   1       	  # Minimum height of colls in units of skyStdev
+
+# alternate PSPHOT-type recipes
+PSPHOT.FIXED.PSF METADATA
+END
+
+# alternate PSPHOT-type recipes
+PSPHOT.SUMMIT METADATA
+END
+
+# alternate PSPHOT-type recipes
+PSPHOT.SEEING METADATA
+END
+
+TEST_FIT                            BOOL  FALSE
+TEST_FIT_MODE                       STR   DEFAULT # PSF, CONV, DEFAULT
+TEST_FIT_MODEL                      STR   DEFAULT # PS_MODEL_SERSIC
+TEST_FIT_INNER_RADIUS               F32   NAN 
+TEST_FIT_OUTER_RADIUS               F32   NAN
+TEST_FIT_RADIUS                     F32   NAN
+TEST_MOMENTS_RADIUS                 F32   NAN
+TEST_FIT_X                          F32   NAN
+TEST_FIT_Y                          F32   NAN
+
+TEST_FIT_PAR0                       F32   NAN
+TEST_FIT_PAR1                       F32   NAN
+TEST_FIT_PAR2                       F32   NAN
+TEST_FIT_PAR3                       F32   NAN
+TEST_FIT_PAR4                       F32   NAN
+TEST_FIT_PAR5                       F32   NAN
+TEST_FIT_PAR6                       F32   NAN
+TEST_FIT_PAR7                       F32   NAN
+TEST_FIT_PAR8                       F32   NAN
+TEST_FIT_PAR                        F32   NAN
+
+DIAGNOSTIC.PLOTS                    METADATA
+  IMAGE.BACKGROUND.CELL.HISTOGRAM   BOOL  FALSE
+  IMAGE.BACKGROUND.CELL.HISTOGRAM.X S32   35
+  IMAGE.BACKGROUND.CELL.HISTOGRAM.Y S32   -1
+END
+
+PSF.FLUXSCALE.NX                    S32   5 		  # number cells to measure flux scale variations
+PSF.FLUXSCALE.NY                    S32   5 		  # number cells to measure flux scale variations
+
+PSPHOT.CR.NSIGMA.LIMIT              F32   3.0  # sources with crNsigma greater that this get tagged as likely cosmic rays
+PSPHOT.EXT.NSIGMA.LIMIT             F32   4.0  # sources with extNsigma greater that this get tagged as likely extended sources
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/pswarp.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/pswarp.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/pswarp.config	(revision 22157)
@@ -0,0 +1,10 @@
+### Recipe for pswarp
+
+GRID.NX			S32	128		# Iso-astrom grid size in x (pixels)
+GRID.NY			S32	128		# Iso-astrom grid size in y (pixels)
+INTERPOLATION.MODE	STR	LANCZOS3	# Interpolation mode to use
+POOR.FRAC		F32	0.01		# Max fraction of bad flux for a "poor" warped pixel
+ASTROM.SOURCE		STR	PSASTRO.OUTPUT.MEF	# Source file rule for astrometry, or NULL
+ASTROM.ACCEPT		BOOL	FALSE		# Accept astrometric solution unconditionally?
+ASTROM.DEPTH		STR	MEF             # Source file rule for astrometry, or NULL
+ACCEPT.FRAC		F32	0.1		# Minimum fraction of good pixels to accept result
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/reductionClasses.mdc
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/reductionClasses.mdc	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/reductionClasses.mdc	(revision 22157)
@@ -0,0 +1,134 @@
+# These reduction classes define recipes that go together as a group
+
+# XXX rename the jpeg recipes to the form BIAS_JPEG_BIN1_IMAGE
+# need to also modify detrend_norm_exp.pl, detrend_process_exp.pl, detrend_reject_imfile.pl
+DETREND		METADATA
+
+	BIAS_PROCESS	STR	PPIMAGE_O
+	BIAS_RESID	STR	PPIMAGE_B
+	BIAS_VERIFY	STR	PPIMAGE_OB
+        BIAS_STACK      STR     PPMERGE_BIAS
+	JPEG_BIN1_IMAGE_BIAS     STR  PPIMAGE_J1_IMAGE_B
+	JPEG_BIN2_IMAGE_BIAS     STR  PPIMAGE_J2_IMAGE_B
+	JPEG_BIN1_RESID_BIAS     STR  PPIMAGE_J1_RESID_B
+	JPEG_BIN2_RESID_BIAS     STR  PPIMAGE_J2_RESID_B
+
+	DARK_PREMASK_PROCESS	     STR  PPIMAGE_OB
+	DARK_PREMASK_RESID	     STR  PPIMAGE_D
+	DARK_PREMASK_VERIFY	     STR  PPIMAGE_OBD
+        DARK_PREMASK_STACK           STR  PPMERGE_DARK
+	JPEG_BIN1_IMAGE_DARK_PREMASK STR  PPIMAGE_J1_IMAGE_B
+	JPEG_BIN2_IMAGE_DARK_PREMASK STR  PPIMAGE_J2_IMAGE_B
+	JPEG_BIN1_RESID_DARK_PREMASK STR  PPIMAGE_J1_RESID_B
+	JPEG_BIN2_RESID_DARK_PREMASK STR  PPIMAGE_J2_RESID_B
+
+	DARK_PROCESS	STR	PPIMAGE_OB
+	DARK_RESID	STR	PPIMAGE_D
+	DARK_VERIFY	STR	PPIMAGE_OBD
+        DARK_STACK      STR     PPMERGE_DARK
+	JPEG_BIN1_IMAGE_DARK     STR  PPIMAGE_J1_IMAGE_B
+	JPEG_BIN2_IMAGE_DARK     STR  PPIMAGE_J2_IMAGE_B
+	JPEG_BIN1_RESID_DARK     STR  PPIMAGE_J1_RESID_B
+	JPEG_BIN2_RESID_DARK     STR  PPIMAGE_J2_RESID_B
+
+	SHUTTER_PROCESS	STR	PPIMAGE_OBD
+	SHUTTER_RESID	STR	PPIMAGE_S
+	SHUTTER_VERIFY	STR	PPIMAGE_OBDS
+        SHUTTER_STACK   STR     PPMERGE_SHUTTER
+	JPEG_BIN1_IMAGE_SHUTTER  STR  PPIMAGE_J1_IMAGE_F
+	JPEG_BIN2_IMAGE_SHUTTER  STR  PPIMAGE_J2_IMAGE_F
+	JPEG_BIN1_RESID_SHUTTER  STR  PPIMAGE_J1_RESID_F
+	JPEG_BIN2_RESID_SHUTTER  STR  PPIMAGE_J2_RESID_F
+
+	FLAT_PROCESS	STR	PPIMAGE_OBDS
+	FLAT_RESID	STR	PPIMAGE_F
+	FLAT_VERIFY	STR	PPIMAGE_OBDSF
+        FLAT_STACK      STR     PPMERGE_FLAT
+	JPEG_BIN1_IMAGE_FLAT     STR  PPIMAGE_J1_IMAGE_F
+	JPEG_BIN2_IMAGE_FLAT     STR  PPIMAGE_J2_IMAGE_F
+	JPEG_BIN1_RESID_FLAT     STR  PPIMAGE_J1_RESID_F
+	JPEG_BIN2_RESID_FLAT     STR  PPIMAGE_J2_RESID_F
+
+	# DOMEFLAT does not have specific processing recipes?
+	JPEG_BIN1_IMAGE_DOMEFLAT STR  PPIMAGE_J1_IMAGE_F
+	JPEG_BIN2_IMAGE_DOMEFLAT STR  PPIMAGE_J2_IMAGE_F
+	JPEG_BIN1_RESID_DOMEFLAT STR  PPIMAGE_J1_RESID_F
+	JPEG_BIN2_RESID_DOMEFLAT STR  PPIMAGE_J2_RESID_F
+
+	# SKYFLAT does not have specific processing recipes?
+	JPEG_BIN1_IMAGE_SKYFLAT  STR  PPIMAGE_J1_IMAGE_F
+	JPEG_BIN2_IMAGE_SKYFLAT  STR  PPIMAGE_J2_IMAGE_F
+	JPEG_BIN1_RESID_SKYFLAT  STR  PPIMAGE_J1_RESID_F
+	JPEG_BIN2_RESID_SKYFLAT  STR  PPIMAGE_J2_RESID_F
+
+	FLAT_PREMASK_PROCESS	STR	PPIMAGE_OBDS
+	FLAT_PREMASK_RESID	STR	PPIMAGE_F
+	FLAT_PREMASK_VERIFY	STR	PPIMAGE_OBDSF
+        FLAT_PREMASK_STACK      STR     PPMERGE_FLAT
+	JPEG_BIN1_IMAGE_FLAT_PREMASK STR  PPIMAGE_J1_IMAGE_B
+	JPEG_BIN2_IMAGE_FLAT_PREMASK STR  PPIMAGE_J2_IMAGE_B
+	JPEG_BIN1_RESID_FLAT_PREMASK STR  PPIMAGE_J1_RESID_B
+	JPEG_BIN2_RESID_FLAT_PREMASK STR  PPIMAGE_J2_RESID_B
+
+	# Generation of pixel masks from darks and flats
+	DARKMASK_PROCESS	STR	PPIMAGE_OBD
+	DARKMASK_RESID		STR	PPIMAGE_M
+	DARKMASK_VERIFY		STR	PPIMAGE_OBD
+	DARKMASK_STACK		STR	PPMERGE_DARKMASK
+	JPEG_BIN1_IMAGE_DARKMASK STR	PPIMAGE_J1_RESID_B
+	JPEG_BIN2_IMAGE_DARKMASK STR	PPIMAGE_J2_RESID_B
+	JPEG_BIN1_RESID_DARKMASK STR	PPIMAGE_J1_RESID_B
+	JPEG_BIN2_RESID_DARKMASK STR	PPIMAGE_J2_RESID_B
+
+	FLATMASK_PROCESS	STR	PPIMAGE_OBDSF
+	FLATMASK_RESID		STR	PPIMAGE_M
+	FLATMASK_VERIFY		STR	PPIMAGE_OBDSF
+	FLATMASK_STACK		STR	PPMERGE_FLATMASK
+	JPEG_BIN1_IMAGE_FLATMASK STR	PPIMAGE_J1_RESID_F
+	JPEG_BIN2_IMAGE_FLATMASK STR	PPIMAGE_J2_RESID_F
+	JPEG_BIN1_RESID_FLATMASK STR	PPIMAGE_J1_RESID_F
+	JPEG_BIN2_RESID_FLATMASK STR	PPIMAGE_J2_RESID_F
+
+	FRINGE_PROCESS	STR	PPIMAGE_OBDSF
+	FRINGE_RESID	STR	PPIMAGE_R
+	FRINGE_VERIFY	STR	PPIMAGE_OBDSFR
+        FRINGE_STACK    STR     PPMERGE_FRINGE
+	JPEG_BIN1_IMAGE_FRINGE   STR  PPIMAGE_J1_IMAGE_R
+	JPEG_BIN2_IMAGE_FRINGE   STR  PPIMAGE_J2_IMAGE_R
+	JPEG_BIN1_RESID_FRINGE   STR  PPIMAGE_J1_RESID_R
+	JPEG_BIN2_RESID_FRINGE   STR  PPIMAGE_J2_RESID_R
+
+END
+
+# basic science analysis
+DEFAULT		METADATA
+	CHIP		STR	PPIMAGE_OBDSFRP
+	JPEG_BIN1       STR     PPIMAGE_J1
+	JPEG_BIN2       STR     PPIMAGE_J2
+	FAKEPHOT	STR	FAKEPHOT
+END
+
+# photometry for pre-detrended images
+PROCESSED	METADATA
+	CHIP		STR	PPIMAGE_P
+	JPEG_BIN1       STR     PPIMAGE_J1
+	JPEG_BIN2       STR     PPIMAGE_J2
+END
+
+# Detrend Processing only for raw data
+DETREND_ONLY		METADATA
+	CHIP		STR	PPIMAGE_DET_ONLY
+	JPEG_BIN1       STR     PPIMAGE_J1
+	JPEG_BIN2       STR     PPIMAGE_J2
+END
+
+NO_PHOTOM	METADATA
+	CHIP		STR	PPIMAGE_OBDSF
+END
+
+# Detrend Processing only for raw data
+MASKPHOT		METADATA
+	CHIP		STR	PPIMAGE_MASKPHOT
+	JPEG_BIN1       STR     PPIMAGE_J1
+	JPEG_BIN2       STR     PPIMAGE_J2
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/rejections.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/rejections.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/recipes/rejections.config	(revision 22157)
@@ -0,0 +1,235 @@
+
+BIAS METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  2.0
+  IMFILE.STDEV       F32 10.0
+  IMFILE.MEANSTDEV   F32  0.5
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.5
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  1.0
+  EXP.STDEV          F32 10.0
+  EXP.MEANSTDEV      F32  0.5
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  3.0
+  ENSEMBLE.STDEV     F32  3.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+DARK METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  2.0
+  IMFILE.STDEV       F32 10.0
+  IMFILE.MEANSTDEV   F32  0.5
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.0
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  1.0
+  EXP.STDEV          F32 10.0
+  EXP.MEANSTDEV      F32  0.5
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  3.0
+  ENSEMBLE.STDEV     F32  3.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+DARK_PREMASK METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  0.0
+  IMFILE.STDEV       F32  0.0
+  IMFILE.MEANSTDEV   F32  0.0
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.0
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  0.0
+  EXP.STDEV          F32  0.0
+  EXP.MEANSTDEV      F32  0.0
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  0.0
+  ENSEMBLE.STDEV     F32  0.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+SHUTTER METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  0.0
+  IMFILE.STDEV       F32  0.0
+  IMFILE.MEANSTDEV   F32  0.0
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.0
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  0.0
+  EXP.STDEV          F32  0.0
+  EXP.MEANSTDEV      F32  0.0
+  EXP.SNR            F32 20.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  0.0
+  ENSEMBLE.STDEV     F32  0.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+FLAT METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  0.0
+  IMFILE.STDEV       F32  0.0
+  IMFILE.MEANSTDEV   F32  0.0
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.0
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  0.0
+  EXP.STDEV          F32  0.0
+  EXP.MEANSTDEV      F32  0.0
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  0.0
+  ENSEMBLE.STDEV     F32  0.0
+  ENSEMBLE.MEANSTDEV F32  3.0
+END
+
+FLAT_PREMASK METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  0.0
+  IMFILE.STDEV       F32  0.0
+  IMFILE.MEANSTDEV   F32  0.0
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.0
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  0.0
+  EXP.STDEV          F32  0.0
+  EXP.MEANSTDEV      F32  0.0
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  0.0
+  ENSEMBLE.STDEV     F32  0.0
+  ENSEMBLE.MEANSTDEV F32  3.0
+END
+
+FRINGE METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  0.0
+  IMFILE.STDEV       F32  0.0
+  IMFILE.MEANSTDEV   F32  0.0
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.0
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  0.0
+  EXP.STDEV          F32  0.0
+  EXP.MEANSTDEV      F32  0.0
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  0.0
+  ENSEMBLE.STDEV     F32  0.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+DARKMASK METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  0.0
+  IMFILE.STDEV       F32  0.0
+  IMFILE.MEANSTDEV   F32  0.0
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.0
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  0.0
+  EXP.STDEV          F32  0.0
+  EXP.MEANSTDEV      F32  0.0
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  0.0
+  ENSEMBLE.STDEV     F32  0.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+FLATMASK METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  0.0
+  IMFILE.STDEV       F32  0.0
+  IMFILE.MEANSTDEV   F32  0.0
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.0
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  0.0
+  EXP.STDEV          F32  0.0
+  EXP.MEANSTDEV      F32  0.0
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  0.0
+  ENSEMBLE.STDEV     F32  0.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+# FILTER is an additional qualifier, and may be "*" (or absent!), in which case it matches everything
+# EXPECTED is the expected mean value
+# IMFILE.MEAN is the maximum permitted mean value for an imfile, relative to the standard deviation
+# IMFILE.STDEV is the maximum permitted standard deviation for an imfile
+# EXP.MEAN is the maximum permitted mean value for an exposure, relative to the standard deviation
+# EXP.STDEV is the maximum permitted standard deviation for an exposure
+# EXP.MEANSTDEV is the maximum permitted mean standard deviation for an exposure relative to the mean
+# ENSEMBLE.MEAN is the maximum permitted mean for an ensemble of exposures
+# ENSEMBLE.STDEV is the maximum permitted standard deviation for an ensemble of exposures
+# ENSEMBLE.MEANSTDEV is the maximum permitted mean standard deviation for an ensemble of exposures
+# IMFILE.SNR is the minimum permitted signal-to-noise for an imfile
+# EXP.SNR is the minimum permitted signal-to-noise for an exposure
+# These values (all except FILTER) may be zero, in which case no clipping is applied.
+
+
+ITERATION	BOOL	TRUE		# Are iterations permitted?
+MASTER		BOOL	FALSE		# Force stacks to be accepted as a master?
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/.cvsignore	(revision 22157)
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/Makefile.am	(revision 22157)
@@ -0,0 +1,25 @@
+
+installdir = $(datadir)/ippconfig/sdss
+
+install_files = \
+	dvo.config \
+	dvo.layout \
+	camera.config \
+	format.config \
+	cmp.config \
+	cmf.config \
+	ppImage.config \
+	ppMerge.config \
+	psastro.config \
+	psphot.config \
+	pswarp.config \
+	rejections.config
+
+install_DATA = $(install_files)
+
+install-data-hook:
+	chmod 0755 $(installdir)
+
+EXTRA_DIST = $(install_files)
+
+ACLOCAL_AMFLAGS = -I m4
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/camera.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/camera.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/camera.config	(revision 22157)
@@ -0,0 +1,202 @@
+# Camera configuration file for co-adding reduced SDSS images of stripe82
+# Copied from ISP config files
+# Sebastian Jester jester at mpia.de August 7, 2007
+
+# File formats that we know about
+FORMATS         METADATA
+        SDSS     STR     sdss/format.config
+END
+ 
+# Description of camera --- all the chips and the cells that comprise them
+FPA     METADATA
+        Chip            STR     Cell
+END
+
+# valid filter names and corresponding IDs
+FILTER.ID       METADATA
+        g       STR     g
+        r       STR     r
+        i       STR     i
+        z       STR     z
+	u       STR     u
+END
+
+# Table of strings to use for the class identifier (see ippdb) according to the file level.
+CLASSID		METADATA
+	FPA	STR	fpa
+END
+
+DVO.CAMERADIR	STR	sdss		# Camera directory for DVO
+
+# convert supplied FPA.OBSTYPE values to abstract exptype names
+OBSTYPE.TABLE METADATA
+  bias 	   STR BIAS
+  zero 	   STR BIAS
+  dark 	   STR DARK
+  flat 	   STR SKYFLAT
+  skyflat  STR SKYFLAT
+  domeflat STR DOMEFLAT
+  object   STR OBJECT
+  science  STR OBJECT
+END
+
+# Recipe options
+RECIPES         METADATA
+        PSPHOT          STR     sdss/psphot.config               # psphot details
+        PSASTRO         STR     sdss/psastro.config              # psastro details
+        PPIMAGE         STR     sdss/ppImage.config              # Recipes for ppImage
+        PPMERGE         STR     sdss/ppMerge.config              # Recipes for ppMerge
+	PSWARP		STR	sdss/pswarp.config		# Recipes for pswarp
+	REJECTIONS	STR     sdss/rejections.config
+END
+
+# reduction classes (recipes which are grouped together)
+REDUCTION	STR	recipes/reductionClasses.mdc
+
+FITSTYPES       STR     recipes/fitstypes.mdc
+
+## XXX replace with appropriate common filerules?
+FILERULES METADATA
+   PSASTRO.INPUT       STR PSASTRO.INPUT.CMF
+   PSASTRO.OUTPUT      STR PSASTRO.OUTPUT.CMF
+   PSPHOT.OUTPUT       STR PSPHOT.OUTPUT.CMF
+
+   ### input file definitions
+   ### use @DETDB entries to get the detrend images from the database
+   ### replace @DETDB with @FILES if you want to require it from the 
+   ### command line, or with an explicit name to require a specific file
+   TYPE               INPUT FILENAME.RULE DATA.LEVEL FILE.TYPE 
+
+   ## files used by ppImage
+   PPIMAGE.INPUT      INPUT @FILES        FPA        IMAGE     
+   PPIMAGE.MASK       INPUT @FILES        FPA        IMAGE     
+   PPIMAGE.BIAS       INPUT @DETDB        FPA        IMAGE     
+   PPIMAGE.DARK       INPUT @DETDB        FPA        DARK
+   PPIMAGE.FLAT       INPUT @DETDB        FPA        IMAGE     
+   PPIMAGE.FRINGE     INPUT @DETDB        FPA        FRINGE
+   PPIMAGE.SHUTTER    INPUT @DETDB        FPA        IMAGE     
+
+   ## files used to build and apply the flat-field correction images
+   DVOCORR.INPUT      INPUT @FILES        FPA        IMAGE
+   DVOCORR.REFHEAD    INPUT @FILES        FPA        HEADER
+   DVOFLAT.INPUT      INPUT @FILES        FPA        IMAGE
+   DVOFLAT.CORR       INPUT @DETDB        FPA        IMAGE
+
+   ## files used by psphot 
+   PSPHOT.LOAD        INPUT @FILES        FPA        IMAGE
+   PSPHOT.INPUT       INPUT @FILES        FPA        IMAGE     
+   PSPHOT.MASK        INPUT @FILES        FPA        MASK     
+   PSPHOT.WEIGHT      INPUT @FILES        FPA        WEIGHT     
+   PSPHOT.PSF.LOAD    INPUT @FILES        FPA        PSF       
+
+   ## files used by psastro 
+   PSASTRO.INPUT.CMP  INPUT @FILES        FPA        CMP       
+   PSASTRO.INPUT.CMF  INPUT @FILES        FPA        CMF       
+
+   ## files used by pswarp
+   PSWARP.INPUT       INPUT @FILES        FPA        IMAGE
+   PSWARP.WEIGHT      INPUT @FILES        FPA        WEIGHT
+   PSWARP.MASK        INPUT @FILES        FPA        MASK
+   PSWARP.SKYCELL     INPUT @FILES        FPA        IMAGE
+   PSWARP.ASTROM      INPUT @FILES        FPA        CMF
+
+   PPSUB.INPUT        INPUT @FILES        FPA	     IMAGE
+   PPSUB.INPUT.MASK   INPUT @FILES        FPA	     MASK
+   PPSUB.INPUT.WEIGHT INPUT @FILES        FPA	     WEIGHT
+   PPSUB.REF          INPUT @FILES        FPA	     IMAGE
+   PPSUB.REF.MASK     INPUT @FILES        FPA	     MASK
+   PPSUB.REF.WEIGHT   INPUT @FILES        FPA	     WEIGHT
+
+   PPSTACK.INPUT      INPUT @FILES        FPA	     IMAGE
+   PPSTACK.INPUT.MASK INPUT @FILES        FPA	     MASK
+   PPSTACK.INPUT.WEIGHT INPUT @FILES      FPA	     WEIGHT
+
+   PPARITH.INPUT.IMAGE INPUT @FILES        CHIP       IMAGE
+   PPARITH.INPUT.MASK  INPUT @FILES        CHIP       MASK
+
+   ### output file definitions
+   TYPE                  OUTPUT  FILENAME.RULE        FILE.TYPE FITS.TYPE DATA.LEVEL FILE.SAVE FILE.FORMAT
+   PPIMAGE.OUTPUT      	 OUTPUT  {OUTPUT}.sdss.fits   IMAGE     NONE      FPA        TRUE      NONE
+   PPIMAGE.OUTPUT.MASK 	 OUTPUT  {OUTPUT}.mask.fits   MASK      NONE      FPA        TRUE      NONE
+   PPIMAGE.OUTPUT.WEIGHT OUTPUT  {OUTPUT}.wt.fits     WEIGHT    NONE      FPA        TRUE      NONE
+   PPIMAGE.CHIP 	 OUTPUT  {OUTPUT}.chip.fits   IMAGE     NONE      FPA        TRUE      NONE
+   PPIMAGE.CHIP.MASK 	 OUTPUT  {OUTPUT}.chip.mask.fits MASK   NONE      FPA        TRUE      NONE
+   PPIMAGE.CHIP.WEIGHT 	 OUTPUT  {OUTPUT}.chip.wt.fits WEIGHT   NONE      FPA        TRUE      NONE
+   PPIMAGE.OUTPUT.FPA1 	 OUTPUT  {OUTPUT}.b1.fits     IMAGE     NONE      FPA        TRUE      NONE
+   PPIMAGE.OUTPUT.FPA2 	 OUTPUT  {OUTPUT}.b2.fits     IMAGE     NONE      FPA        TRUE      NONE
+   PPIMAGE.STATS 	 OUTPUT  {OUTPUT}.stats       STATS     NONE      FPA        TRUE      NONE
+
+   PPIMAGE.BIN1        	 OUTPUT  {OUTPUT}.b1c.fits    IMAGE     NONE      FPA        TRUE      NONE
+   PPIMAGE.BIN2        	 OUTPUT  {OUTPUT}.b2c.fits    IMAGE     NONE      FPA        TRUE      NONE
+
+   PPIMAGE.JPEG1       	 OUTPUT  {OUTPUT}.b1.jpg      JPEG      NONE      FPA        TRUE      NONE
+   PPIMAGE.JPEG2       	 OUTPUT  {OUTPUT}.b2.jpg      JPEG      NONE      FPA        TRUE      NONE
+
+   PPMERGE.OUTPUT        OUTPUT   {OUTPUT}.fits       IMAGE     NONE      FPA        TRUE      NONE
+
+   DVOCORR.OUTPUT    	 OUTPUT  {OUTPUT}.fc.fits     IMAGE     NONE      FPA        TRUE      NONE
+   DVOFLAT.OUTPUT    	 OUTPUT  {OUTPUT}.co.fits     IMAGE     NONE      FPA        TRUE      NONE
+
+   PSPHOT.RESID        	 OUTPUT  {OUTPUT}.res.fits    IMAGE     NONE      FPA        TRUE      NONE
+   PSPHOT.BACKGND      	 OUTPUT  {OUTPUT}.bck.fits    IMAGE     NONE      FPA        TRUE      NONE
+   PSPHOT.BACKSUB      	 OUTPUT  {OUTPUT}.sub.fits    IMAGE     NONE      FPA        TRUE      NONE
+   PSPHOT.BACKMDL      	 OUTPUT  {OUTPUT}.mdl.fits    IMAGE     NONE      FPA        TRUE      NONE
+
+   PSPHOT.OUTPUT.RAW   	 OUTPUT  {OUTPUT}             RAW       NONE      FPA        TRUE      NONE
+   PSPHOT.OUTPUT.SX    	 OUTPUT  {OUTPUT}.sx          SX        NONE      FPA        TRUE      NONE
+   PSPHOT.OUTPUT.OBJ   	 OUTPUT  {OUTPUT}.obj         OBJ       NONE      FPA        TRUE      NONE
+   PSPHOT.OUTPUT.CMP   	 OUTPUT  {OUTPUT}.cmp         CMP       NONE      FPA        TRUE      NONE
+   PSPHOT.OUTPUT.CMF   	 OUTPUT  {OUTPUT}.cmf         CMF       NONE      FPA        TRUE      NONE
+
+   PSPHOT.PSF.SAVE     	 OUTPUT  {OUTPUT}.psf         PSF       NONE      FPA        TRUE      NONE
+
+   SOURCE.PLOT.MOMENTS   OUTPUT  {OUTPUT}.mnt.png     KAPA      NONE      FPA        TRUE      NONE
+   SOURCE.PLOT.PSFMODEL  OUTPUT  {OUTPUT}.psf.png     KAPA      NONE      FPA        TRUE      NONE
+   SOURCE.PLOT.APRESID   OUTPUT  {OUTPUT}.dap.png     KAPA      NONE      FPA        TRUE      NONE
+
+   PSASTRO.OUTPUT.CMP    OUTPUT  {OUTPUT}.smp         CMP       NONE      FPA        TRUE      NONE
+   PSASTRO.OUTPUT.CMF    OUTPUT  {OUTPUT}.smf         CMF       NONE      FPA        TRUE      NONE
+
+   PSWARP.OUTPUT         OUTPUT {OUTPUT}.fits         IMAGE     NONE      FPA        TRUE      NONE
+   PSWARP.OUTPUT.MASK    OUTPUT {OUTPUT}.mask.fits    MASK      NONE      FPA        TRUE      NONE
+   PSWARP.OUTPUT.WEIGHT  OUTPUT {OUTPUT}.wt.fits      WEIGHT    NONE      FPA        TRUE      NONE
+   PSWARP.BIN1           OUTPUT {OUTPUT}.b1.fits      IMAGE     NONE      FPA        TRUE      NONE
+   PSWARP.BIN2           OUTPUT {OUTPUT}.b2.fits      IMAGE     NONE      FPA        TRUE      NONE
+
+   SKYCELL.STATS         OUTPUT {OUTPUT}.stats        STATS     NONE      FPA        TRUE      NONE
+   SKYCELL.TEMPLATE      OUTPUT {OUTPUT}.skycell      SKYCELL   NONE      FPA        TRUE      NONE
+
+   PPSUB.OUTPUT          OUTPUT {OUTPUT}.fits         IMAGE     NONE      FPA        TRUE      NONE
+   PPSUB.OUTPUT.MASK     OUTPUT {OUTPUT}.mask.fits    MASK      NONE      FPA        TRUE      NONE
+   PPSUB.OUTPUT.WEIGHT   OUTPUT {OUTPUT}.wt.fits      WEIGHT    NONE      FPA        TRUE      NONE
+
+   PPSTACK.OUTPUT        OUTPUT {OUTPUT}.fits         IMAGE     NONE      FPA        TRUE      NONE
+   PPSTACK.OUTPUT.MASK   OUTPUT {OUTPUT}.mask.fits    MASK      NONE      FPA        TRUE      NONE
+   PPSTACK.OUTPUT.WEIGHT OUTPUT {OUTPUT}.weight.fits  WEIGHT    NONE      FPA        TRUE      NONE
+
+   PPSIM.OUTPUT          OUTPUT {OUTPUT}.fits         IMAGE     NONE      FPA        TRUE      NONE
+
+   PPARITH.OUTPUT.IMAGE  OUTPUT {OUTPUT}.fits         IMAGE     NONE      CHIP       TRUE      NONE
+   PPARITH.OUTPUT.MASK   OUTPUT {OUTPUT}.fits         MASK      NONE      CHIP       TRUE      NONE
+
+   LOG.IMFILE            OUTPUT {OUTPUT}.{CHIP.NAME}.log TEXT   NONE      CHIP       TRUE      NONE
+   LOG.EXP               OUTPUT {OUTPUT}.log          TEXT      NONE      FPA        TRUE      NONE
+END
+
+EXTNAME.RULES METADATA
+  CMF.HEAD STR cmf.hdr
+  CMF.DATA STR cmf.psf # use .PSF and .EXT?
+  CMF.XSRC STR xsrc # use .PSF and .EXT?
+  CMF.XFIT STR xfit # use .PSF and .EXT?
+
+  PSF.HEAD  STR	hdr
+  PSF.TABLE STR psf_model
+  PSF.RESID STR psf_resid
+END
+
+BLANK.HEADERS	METADATA
+	FPA.TIME	STR	TAI
+	FPA.EXPOSURE	STR	EXPTIME
+	FPA.AIRMASS	STR	AIRMASS
+END
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/cmf.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/cmf.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/cmf.config	(revision 22157)
@@ -0,0 +1,84 @@
+# Pan-STARRS Imaging Sky Probe
+
+# How to identify this type
+RULE	METADATA
+	SIMPLE		BOOL	TRUE
+	NAXIS		S32	0
+	TELESCOP	STR	ISP-1 
+	INSTRUME	STR	ISP-Apogee
+	DETECTOR	STR	ISP-Apogee-01
+	ISPCAMER	STR	Apogee U42
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	NONE	# There are no extensions
+	FPA.NAME	STR	SEQID	# A PHU keyword for unique identifier within the hierarchy level
+END
+
+# What's in the FITS file?
+CONTENTS	STR	Chip:Cell:amplifier
+
+# Specify the cell data
+CELLS	METADATA
+	amplifier	METADATA
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC		STR	TRIMSEC
+		CELL.BIASSEC		STR	BIASSEC
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.OBSTYPE	STR	OBSTYPE
+	FPA.OBJECT	STR	OBSTYPE
+	FPA.FILTER	STR	FILTNAME
+	FPA.RA		STR	RA
+	FPA.DEC		STR	DEC
+	FPA.RADECSYS	STR	RADECSYS
+	FPA.ALT		STR	ALT
+	FPA.AZ		STR	AZ
+	FPA.POSANGLE	STR	ROTANGLE
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.TIME	STR	MJD-OBS
+	CHIP.TEMP	STR	CCDTEMP
+	CELL.EXPOSURE	STR	EXPTIME
+	CELL.DARKTIME	STR	DARKTIME
+	CELL.TIME	STR	MJD-OBS
+	CELL.GAIN	STR	GAIN
+	CELL.READNOISE	STR	RDNOISE
+	CELL.XBIN	STR	XBIN
+	CELL.YBIN	STR	YBIN
+#	CELL.SATURATION	STR	SATURATE	### Currently set to 0 ???
+	CELL.BAD	STR	BADLEVEL
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	FPA.TIMESYS	STR	UTC
+	CELL.SATURATION	F32	65535
+	CELL.READDIR	S32	1
+	CELL.TIMESYS	STR	UTC
+	CHIP.XPARITY	S32	1
+	CHIP.YPARITY	S32	1
+	CHIP.X0		S32	0
+	CHIP.Y0		S32	0
+	CELL.XPARITY	S32	1
+	CELL.YPARITY	S32	1
+	CELL.X0		S32	0
+	CELL.Y0		S32	0
+END
+
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
+
+# PS Concepts to get from the database
+DATABASE	METADATA
+# None.
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/cmp.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/cmp.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/cmp.config	(revision 22157)
@@ -0,0 +1,84 @@
+# Pan-STARRS Imaging Sky Probe
+
+# How to identify this type
+RULE	METADATA
+	SIMPLE		BOOL	FALSE
+	NAXIS		S32	0
+	TELESCOP	STR	ISP-1 
+	INSTRUME	STR	ISP-Apogee
+	DETECTOR	STR	ISP-Apogee-01
+	ISPCAMER	STR	Apogee U42
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	NONE	# There are no extensions
+	FPA.NAME	STR	SEQID	# A PHU keyword for unique identifier within the hierarchy level
+END
+
+# What's in the FITS file?
+CONTENTS	STR	Chip:Cell:amplifier
+
+# Specify the cell data
+CELLS	METADATA
+	amplifier	METADATA
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC		STR	TRIMSEC
+		CELL.BIASSEC		STR	BIASSEC
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.OBSTYPE	STR	OBSTYPE
+	FPA.OBJECT	STR	OBSTYPE
+	FPA.FILTER	STR	FILTNAME
+	FPA.RA		STR	RA
+	FPA.DEC		STR	DEC
+	FPA.RADECSYS	STR	RADECSYS
+	FPA.ALT		STR	ALT
+	FPA.AZ		STR	AZ
+	FPA.POSANGLE	STR	ROTANGLE
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.TIME	STR	MJD-OBS
+	CHIP.TEMP	STR	CCDTEMP
+	CELL.EXPOSURE	STR	EXPTIME
+	CELL.DARKTIME	STR	DARKTIME
+	CELL.TIME	STR	MJD-OBS
+	CELL.GAIN	STR	GAIN
+	CELL.READNOISE	STR	RDNOISE
+	CELL.XBIN	STR	XBIN
+	CELL.YBIN	STR	YBIN
+#	CELL.SATURATION	STR	SATURATE	### Currently set to 0 ???
+	CELL.BAD	STR	BADLEVEL
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	FPA.TIMESYS	STR	UTC
+	CELL.SATURATION	F32	65535
+	CELL.READDIR	S32	1
+	CELL.TIMESYS	STR	UTC
+	CHIP.XPARITY	S32	1
+	CHIP.YPARITY	S32	1
+	CHIP.X0		S32	0
+	CHIP.Y0		S32	0
+	CELL.XPARITY	S32	1
+	CELL.YPARITY	S32	1
+	CELL.X0		S32	0
+	CELL.Y0		S32	0
+END
+
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
+
+# PS Concepts to get from the database
+DATABASE	METADATA
+# None.
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/dvo.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/dvo.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/dvo.config	(revision 22157)
@@ -0,0 +1,50 @@
+
+# location of DVO database tables
+CATDIR			/data/ipp001.0/jester/stripe82/coadd/catdir
+
+# keywords used by DVO to interpret the headers
+
+# used by parse_time to find time-related keywords
+MJD-KEYWORD		NONE
+DATE-KEYWORD		DATE-OBS
+DATE-MODE		YYYY-MM-DD
+UT-KEYWORD		TAIHMS
+JD-KEYWORD		NONE
+
+# other keyword abstractions
+EXPTIME-KEYWORD		EXPTIME
+AIRMASS-KEYWORD		AIRMASS
+CCDNUM-KEYWORD		EXTNAME
+ST-KEYWORD		NONE
+OBSERVATORY-LATITUDE	NONE
+OBSERVATORY-LONGITUDE	NONE
+SUBPIX_DATAFILE		NONE
+
+# instrumental magnitude range for calibration mode 
+CAL_INSTMAG_MAX		-7.0
+CAL_INSTMAG_MIN		-9.5
+
+# exclude overscan region from the dB image boundaries
+XOVERSCAN		0
+YOVERSCAN		0
+
+# only upload stars within region; a value of 0 means ignore the limit
+ADDSTAR_XMIN		0
+ADDSTAR_XMAX		0
+ADDSTAR_YMIN		0
+ADDSTAR_YMAX		0
+
+# exclude stars with SN > SNLIMIT (ADDSTAR_SNLIMIT overrides old name MIN_SN_FSTAT)
+ADDSTAR_SNLIMIT		0
+
+# allowed astrometry error (arcseconds)
+ADDSTAR_MAX_CERROR	5.0
+
+# correlation radius (arcseconds)
+ADDSTAR_RADIUS		5.0
+
+# scaled correlation radius 
+ADDSTAR_NSIGMA		0
+
+CATMODE			MEF
+CATFORMAT		PANSTARRS_DEV_0
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/dvo.layout
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/dvo.layout	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/dvo.layout	(revision 22157)
@@ -0,0 +1,58 @@
+# this file defines the layout of the mosaic imager:
+
+# NCCD 40  # this needs work: sometimes 36, sometimes 40
+NCCD 36
+NAXIS1 2112
+NAXIS2 4644
+
+MOSAIC_X 11
+MOSAIC_Y  4
+
+# lines need to contain:
+
+CHIPID_KEYWORD EXTNAME
+
+# ID     CHIPID xoffset yoffset xflip yflip datasec         biassec            Xo      Yo      theta
+CCD.0     ccd00       1       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.1     ccd01       2       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.2     ccd02       3       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.3     ccd03       4       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.4     ccd04       5       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.5     ccd05       6       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.6     ccd06       7       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.7     ccd07       8       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.8     ccd08       9       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.9     ccd09       1       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.10    ccd10       2       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.11    ccd11       3       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.12    ccd12       4       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.13    ccd13       5       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.14    ccd14       6       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.15    ccd15       7       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.16    ccd16       8       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.17    ccd17       9       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.18    ccd18       1       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.19    ccd19       2       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.20    ccd20       3       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.21    ccd21       4       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.22    ccd22       5       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.23    ccd23       6       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.24    ccd24       7       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.25    ccd25       8       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.26    ccd26       9       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.27    ccd27       1       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.28    ccd28       2       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.29    ccd29       3       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.30    ccd30       4       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.31    ccd31       5       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.32    ccd32       6       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.33    ccd33       7       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.34    ccd34       8       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.35    ccd35       9       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.36    ccd36       0       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.37    ccd37      10       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.38    ccd38       0       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.39    ccd39      10       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+
+# can we use header to find biassec
+USE_BIASSEC 1
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/format.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/format.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/format.config	(revision 22157)
@@ -0,0 +1,112 @@
+# SDSS camera, starting from Pan-STARRS Imaging Sky Probe
+# Sebastian Jester jester at mpia.de August 7, 2007
+
+# How to identify this type
+RULE	METADATA
+	SIMPLE		BOOL	TRUE
+#	NAXIS		S32	2
+	ORIGIN		STR	SDSS
+	TELESCOP	STR	2.5m
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	NONE	# There are no extensions
+	FPA.OBS	STR	FRAME	# A PHU keyword for unique identifier within the hierarchy level
+END
+
+# What's in the FITS file?
+CONTENTS	STR	Chip:Cell:amplifier
+
+# Specify the cell data
+CELLS	METADATA
+	amplifier	METADATA
+#		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	VALUE
+#		CELL.BIASSEC.SOURCE	STR	HEADER
+#		CELL.TRIMSEC		STR	TRIMSEC
+		CELL.TRIMSEC		STR	[1:2048,1:1361]
+#		CELL.BIASSEC		STR	BIASSEC
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.TELESCOPE	STR	TELESCOP
+	FPA.INSTRUMENT	STR	ORIGIN
+# Need the following?
+	FPA.DETECTOR	STR	CCDLOC
+#	FPA.AIRMASS	STR	AIRMASS
+	FPA.OBSTYPE	STR	FLAVOR
+	FPA.OBJECT	STR	OBJECT
+	FPA.FILTERID	STR	FILTER
+	FPA.FILTER	STR	FILTER
+	FPA.POSANGLE	STR	IPA
+	FPA.RA		STR	RA
+	FPA.DEC		STR	DEC
+	FPA.RADECSYS	STR	RADECSYS
+#	FPA.FOCUS	STR	TELFOCUS
+	FPA.TIME    	STR     DATE-OBS TAIHMS # Two values are interpreted as date and time
+	FPA.TIMESYS	STR	TIMESYS
+	FPA.ALT		STR	ALT
+	FPA.AZ		STR	AZ
+#	FPA.TEMP	STR	CCDTEMP
+	FPA.EXPOSURE	STR	EXPTIME
+	CHIP.TEMP	STR	CCDTEMP
+	CELL.EXPOSURE	STR	EXPTIME
+	CELL.DARKTIME	STR	EXPTIME
+	CELL.TIME	STR	DATE-OBS TAIHMS
+#	CELL.GAIN	STR	GAIN
+#	CELL.READNOISE	STR	RDNOISE
+#	CELL.SATURATION	STR	SATURATE	### Currently set to 0 ???
+#	CELL.TIMESYS	STR	TIMESYS
+	CELL.XBIN	STR	COLBIN
+	CELL.YBIN	STR	ROWBIN
+# these were used for some early data
+#	CELL.XBIN	STR	XBIN
+#	CELL.YBIN	STR	YBIN
+#	CELL.BAD	STR	BADLEVEL
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+# The gain is only roughly correct...
+	CELL.GAIN	F32	5.0
+# readnoise is from http://www.sdss.org/dr6/instruments/imager/index.html
+# Note that I have emails from Jim Gunn saying dark noise *is* important...
+	CELL.READNOISE	F32	5.0
+	FPA.AIRMASS	F32	1.3
+	CHIP.XSIZE	S32	2048
+	CHIP.YSIZE	S32	1361
+	CELL.XSIZE	S32	2048
+	CELL.YSIZE	S32	2048
+#	FPA.TIMESYS	STR	UTC
+	CELL.SATURATION	F32	60000
+	CELL.READDIR	S32	1
+	CELL.TIMESYS	STR	UTC
+	CHIP.XPARITY	S32	1
+	CHIP.YPARITY	S32	1
+	CHIP.X0		S32	0
+	CHIP.Y0		S32	0
+	CELL.XPARITY	S32	1
+	CELL.YPARITY	S32	1
+	CELL.X0		S32	0
+	CELL.Y0		S32	0
+#	CELL.XBIN	S32	1
+#	CELL.YBIN	S32	1
+END
+
+FORMATS		METADATA
+	FPA.RA		STR	DEGREES
+	FPA.DEC		STR	DEGREES
+	FPA.TIME    	STR     SEPARATE YEAR.FIRST
+#	FPA.TIME	STR	MJD
+	CELL.TIME    	STR     SEPARATE YEAR.FIRST
+#	CELL.TIME	STR	MJD
+END
+
+# PS Concepts to get from the database
+DATABASE	METADATA
+# None.
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/format_skycell.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/format_skycell.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/format_skycell.config	(revision 22157)
@@ -0,0 +1,110 @@
+# SDSS camera, starting from Pan-STARRS Imaging Sky Probe
+# Sebastian Jester jester at mpia.de August 7, 2007
+
+# How to identify this type
+RULE	METADATA
+	SIMPLE		BOOL	TRUE
+#	NAXIS		S32	2
+	ORIGIN		STR	SDSS
+	TELESCOP	STR	2.5m
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	NONE	# There are no extensions
+	FPA.OBS		STR	FRAME	# A PHU keyword for unique identifier within the hierarchy level
+END
+
+# What's in the FITS file?
+CONTENTS	STR	Chip:Cell:amplifier
+
+# Specify the cell data
+CELLS	METADATA
+	amplifier	METADATA
+#		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	VALUE
+#		CELL.BIASSEC.SOURCE	STR	HEADER
+#		CELL.TRIMSEC		STR	TRIMSEC
+		CELL.TRIMSEC		STR	[1:2048,1:1361]
+#		CELL.BIASSEC		STR	BIASSEC
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.TELESCOPE	STR	TELESCOP
+	FPA.INSTRUMENT	STR	ORIGIN
+# Need the following?
+	FPA.DETECTOR	STR	CCDLOC
+#	FPA.AIRMASS	STR	AIRMASS
+	FPA.OBSTYPE	STR	FLAVOR
+	FPA.OBJECT	STR	OBJECT
+	FPA.FILTERID	STR	FILTER
+	FPA.FILTER	STR	FILTER
+	FPA.POSANGLE	STR	IPA
+	FPA.RA		STR	RA
+	FPA.DEC		STR	DEC
+	FPA.RADECSYS	STR	RADECSYS
+#	FPA.FOCUS	STR	TELFOCUS
+	FPA.TIME	STR	TAI
+	FPA.TIMESYS	STR	TIMESYS
+	FPA.ALT		STR	ALT
+	FPA.AZ		STR	AZ
+#	FPA.TEMP	STR	CCDTEMP
+	FPA.EXPOSURE	STR	EXPTIME
+	CHIP.TEMP	STR	CCDTEMP
+	CELL.EXPOSURE	STR	EXPTIME
+	CELL.DARKTIME	STR	EXPTIME
+	CELL.TIME	STR	TAI
+#	CELL.GAIN	STR	GAIN
+#	CELL.READNOISE	STR	RDNOISE
+#	CELL.SATURATION	STR	SATURATE	### Currently set to 0 ???
+#	CELL.TIMESYS	STR	TIMESYS
+	CELL.XBIN	STR	COLBIN
+	CELL.YBIN	STR	ROWBIN
+# these were used for some early data
+#	CELL.XBIN	STR	XBIN
+#	CELL.YBIN	STR	YBIN
+#	CELL.BAD	STR	BADLEVEL
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+# The gain is only roughly correct...
+	CELL.GAIN	F32	5.0
+# readnoise is from http://www.sdss.org/dr6/instruments/imager/index.html
+# Note that I have emails from Jim Gunn saying dark noise *is* important...
+	CELL.READNOISE	F32	5.0
+	FPA.AIRMASS	F32	1.3
+	CHIP.XSIZE	S32	2048
+	CHIP.YSIZE	S32	1361
+	CELL.XSIZE	S32	2048
+	CELL.YSIZE	S32	2048
+#	FPA.TIMESYS	STR	UTC
+	CELL.SATURATION	F32	60000
+	CELL.READDIR	S32	1
+	CELL.TIMESYS	STR	UTC
+	CHIP.XPARITY	S32	1
+	CHIP.YPARITY	S32	1
+	CHIP.X0		S32	0
+	CHIP.Y0		S32	0
+	CELL.XPARITY	S32	1
+	CELL.YPARITY	S32	1
+	CELL.X0		S32	0
+	CELL.Y0		S32	0
+#	CELL.XBIN	S32	1
+#	CELL.YBIN	S32	1
+END
+
+FORMATS		METADATA
+	FPA.RA		STR	DEGREES
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
+
+# PS Concepts to get from the database
+DATABASE	METADATA
+# None.
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/ppImage.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/ppImage.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/ppImage.config	(revision 22157)
@@ -0,0 +1,147 @@
+### ppImage recipe configuration file
+
+# Inherit everything from the top-level recipe file, except for what's below.
+
+PHOTCODE.RULE		STR	SDSS.{FILTER.ID}.{DETECTOR}
+
+FRINGE.FILTERS  MULTI
+FRINGE.FILTERS	STR z
+FRINGE.FILTERS	STR y
+
+# apply the following constraints when selecting a detrend image
+DETREND.CONSTRAINTS  METADATA
+  BIAS METADATA
+  END
+  DARK METADATA
+  END
+  SHUTTER METADATA
+  END	
+  FLAT METADATA
+    FILTER  STR FPA.FILTERID
+#   VERSION STR RAW
+  END
+  FRINGE METADATA
+    FILTER   STR FPA.FILTERID
+#   AIRMASS  STR FPA.AIRMASS
+#   TWILIGHT STR TWILIGHT:FPA.TIME
+  END
+END
+
+## these science-image analysis recipes are placed here set MASK = FALSE
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_DET_ONLY  METADATA
+  BASE.FITS       BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL   FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL   FALSE           # Save base detrended image?
+  CHIP.FITS       BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL   TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL   TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom
+PPIMAGE_OBDSFRP   METADATA
+  BASE.FITS       BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL   FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL   FALSE           # Save base detrended image?
+  CHIP.FITS       BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL   TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL   TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM          BOOL    TRUE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_OBDSFRA   METADATA
+  BASE.FITS       BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL   FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL   FALSE           # Save base detrended image?
+  CHIP.FITS       BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL   TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL   TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM          BOOL    TRUE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
+
+# SDSS Softbias and photometry
+PPIMAGE_OP   METADATA
+  BASE.FITS       BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL   FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL   FALSE           # Save base detrended image?
+  CHIP.FITS       BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL   TRUE            # Save base detrended image?
+  CHIP.WEIGHT.FITS BOOL   TRUE            # Save base detrended image?
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  OVERSCAN.CONSTANT BOOL  TRUE
+  OVERSCAN.VALUE  F32     1000.0
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE           # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE           # Save 2nd binned chip image?
+  BIN1.JPEG       BOOL	  TRUE 		  # Save 1st binned jpeg?
+  BIN2.JPEG       BOOL	  TRUE 		  # Save 2nd binned jpeg?
+  PHOTOM          BOOL    TRUE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE            # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
+
+
+# Overscan, bias, photometry, astrometry
+PPIMAGE_OA   METADATA
+  BASE.FITS       BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL   FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL   FALSE           # Save base detrended image?
+  CHIP.FITS       BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL   FALSE           # Save base detrended image?
+  CHIP.WEIGHT.FITS BOOL   FALSE           # Save base detrended image?
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS       BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS       BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG       BOOL	  TRUE 		# Save 1st binned jpeg?
+  BIN2.JPEG       BOOL	  TRUE 		# Save 2nd binned jpeg?
+  PHOTOM          BOOL    TRUE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/ppMerge.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/ppMerge.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/ppMerge.config	(revision 22157)
@@ -0,0 +1,3 @@
+### ppImage recipe configuration file
+
+# Inherit everything from the top-level recipe file, except for what's below.
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/psastro.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/psastro.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/psastro.config	(revision 22157)
@@ -0,0 +1,38 @@
+
+# astrometry matching parameters
+
+# nominal plate scale (microns / pixel)
+PSASTRO.PIXEL.SCALE    F32  10.0
+
+# pmAstromGridMatch:
+PSASTRO.GRID.MIN.ANGLE F32 -2.0
+PSASTRO.GRID.MAX.ANGLE F32 +2.0
+PSASTRO.GRID.DEL.ANGLE F32  0.25
+PSASTRO.GRID.MIN.SIGMA F32  5.0
+
+# pmAstromGridAngle
+# max grid offset in FP units (microns)
+# use plate-scale to make this in pixels?
+PSASTRO.GRID.OFFSET    F32   10000.
+PSASTRO.GRID.SCALE     F32     500
+
+# these tweak are in FP units (pixels, currently)
+PSASTRO.TWEAK.SCALE     F32      1
+PSASTRO.TWEAK.RANGE     F32     75
+PSASTRO.TWEAK.SMOOTH    F32      2
+PSASTRO.TWEAK.NSIGMA    F32      3
+
+# match radius in pixels for CHIP astrometry
+PSASTRO.MATCH.RADIUS   F32    8
+
+# pmAstromMatchFit
+PSASTRO.CHIP.ORDER     S32      3  # fit order
+PSASTRO.CHIP.NITER     S32      3  # fit clipping iterations
+PSASTRO.CHIP.NSIGMA    F32      3  # fit clipping sigmas
+
+PSASTRO.MAX.ERROR      F32      10.0 # max error in pixels
+PSASTRO.MIN.NSTAR      S32      10   # min fitted stars in solution
+
+MAG_MAX                F32      10
+PSASTRO.CATDIR         STR      SYNTH.BRIGHT ## XXX this should probably be using SYNTH.GRIZY
+PSASTRO.MATCH.LUMFUNC  BOOL     TRUE
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/psphot.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/psphot.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/psphot.config	(revision 22157)
@@ -0,0 +1,61 @@
+
+# turn these on to see specific outputs
+SAVE.BACKMDL	BOOL 	TRUE
+#SAVE.BACKGND	BOOL 	TRUE
+#SAVE.BACKSUB	BOOL 	TRUE
+#SAVE.RESID	BOOL 	TRUE
+SAVE.PSF	BOOL 	TRUE
+SAVE.PLOTS      BOOL    TRUE
+
+# image statistics parameters
+IMSTATS_NPIX        S32  3000    	 # number of pixels to use for sky estimate boxes:
+
+PSF_SN_LIM          F32  10              # minimum S/N for stars used for PSF model
+PSF_MAX_NSTARS      S32  300             # limit number of stars used for PSF model
+
+PSF_MODEL         STR  PS_MODEL_QGAUSS
+
+MOMENTS_SN_MIN      F32   10.0
+#EXT_MIN_SN           F32  50.0           # fit galaxies above this S/N limit
+#FULL_FIT_SN_LIM      F32  50.0
+#AP_MIN_SN            F32  20.0
+PSF_CLUMP_NSIGMA   F32  2.5             # region of Sx,Sy plane to use for selecting PSF stars
+
+# PSFTREND must be a 2D polynomial
+# the specified values are ignored but define the active components of the polynomial
+PSF.TREND.MASK  METADATA  
+   NORDER_X         S32       3                # number of x orders
+   NORDER_Y         S32       3                # number of y orders
+   VAL_X00_Y00      F64       1                # polynomial coefficient
+
+   VAL_X01_Y00      F64       1                # polynomial coefficient
+   VAL_X00_Y01      F64       1                # polynomial coefficient
+
+   VAL_X02_Y00      F64       1                # polynomial coefficient
+   VAL_X01_Y01      F64       1                # polynomial coefficient
+   VAL_X00_Y02      F64       1                # polynomial coefficient
+
+   VAL_X03_Y00      F64       1                # polynomial coefficient
+   VAL_X02_Y01      F64       1                # polynomial coefficient
+   VAL_X01_Y02      F64       1                # polynomial coefficient
+   VAL_X00_Y03      F64       1                # polynomial coefficient
+   NELEMENTS        S32       10               # number of unmasked components
+END  # folder for 4D polynomial
+
+XMIN F32 15
+
+PSF.RESIDUALS       BOOL true
+PSF.RESIDUALS.SPATIAL_ORDER S32 1
+
+# BREAK_POINT         STR  ENSEMBLE
+OUTPUT.FORMAT       STR  PS1_DEV_0
+
+PSPHOT.SUMMIT METADATA
+ PEAKS_SMOOTH_SIGMA  F32  0.8 	   	 # peak significance threshold
+ PEAKS_NSIGMA_LIMIT  F32  50.0 	   	 # peak significance threshold
+ PEAKS_NMAX          S32  1000
+ SAVE.RESID	BOOL 	FALSE
+ PSF_SN_LIM          F32  25              # minimum S/N for stars used for PSF model
+ MOMENTS_SN_MIN      F32   25.0
+ PSF_MODEL         STR  PS_MODEL_PGAUSS
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/pswarp.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/pswarp.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/pswarp.config	(revision 22157)
@@ -0,0 +1,2 @@
+ASTROM.SOURCE	STR	NULL	# Source file rule for astrometry; NULL to use existing WCS
+ASTROM.ACCEPT	BOOL	TRUE	# Accept astrometric solution unconditionally?
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/rejections.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/rejections.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdss/rejections.config	(revision 22157)
@@ -0,0 +1,49 @@
+
+DARK METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  0.0
+  IMFILE.STDEV       F32  0.0
+  IMFILE.MEANSTDEV   F32  0.0
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SN          F32  0.0
+  IMFILE.BIN.STDEV   F32  0.0
+  IMFILE.BIN.SN      F32  0.0
+  IMFILE.FLUX        F32  2.0
+  EXP.MEAN           F32  0.0
+  EXP.STDEV          F32  0.0
+  EXP.MEANSTDEV      F32  0.0
+  EXP.SN             F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SN         F32  0.0
+  EXP.FLUX           F32  2.0
+  ENSEMBLE.MEAN      F32  3.0
+  ENSEMBLE.STDEV     F32  3.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+FRINGE METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  0.0
+  IMFILE.STDEV       F32  0.0
+  IMFILE.MEANSTDEV   F32  0.0
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SN          F32  0.0
+  IMFILE.BIN.STDEV   F32  0.0
+  IMFILE.BIN.SN      F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  0.0
+  EXP.STDEV          F32  0.0
+  EXP.MEANSTDEV      F32  0.0
+  EXP.SN             F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SN         F32 10.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  0.0
+  ENSEMBLE.STDEV     F32  0.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/.cvsignore	(revision 22157)
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/Makefile.am	(revision 22157)
@@ -0,0 +1,26 @@
+
+installdir = $(datadir)/ippconfig/sdssmosaic/
+
+install_files = \
+	dvo.config \
+	dvo.layout \
+	camera.config \
+	format.config \
+	cmp.config \
+	cmf.config \
+	ppImage.config \
+	ppMerge.config \
+	psastro.config \
+	psphot.config \
+	pswarp.config \
+	filerules.mdc \
+	rejections.config
+
+install_DATA = $(install_files)
+
+install-data-hook:
+	chmod 0755 $(installdir)
+
+EXTRA_DIST = $(install_files)
+
+ACLOCAL_AMFLAGS = -I m4
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/camera.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/camera.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/camera.config	(revision 22157)
@@ -0,0 +1,97 @@
+# Camera configuration file for co-adding reduced SDSS images of stripe82
+# Attempt to designate all 6 camcols as a single mosaic camera
+# Copied from SDSS camera config files, which in turn were copied 
+# from ISP config files 
+# Sebastian Jester jester at mpia.de August 27, 2007
+
+# File formats that we know about
+FORMATS         METADATA
+        SDSSmosaic	STR     sdssmosaic/format.config
+END
+ 
+# Description of camera --- all the chips and the cells that comprise them
+FPA     METADATA
+	CHIP1	STR	Cell
+	CHIP2	STR	Cell
+	CHIP3	STR	Cell
+	CHIP4	STR	Cell
+	CHIP5	STR	Cell
+	CHIP6	STR	Cell
+END
+
+# valid filter names and corresponding IDs
+FILTER.ID       METADATA
+        g       STR     g
+        r       STR     r
+        i       STR     i
+        z       STR     z
+	u       STR     u
+END
+
+# Table of strings to use for the class identifier (see ippdb) according to the file level.
+CLASSID		METADATA
+	FPA	STR	fpa
+END
+
+DVO.CAMERADIR	STR	sdss		# Camera directory for DVO
+
+# convert supplied FPA.OBSTYPE values to abstract exptype names
+OBSTYPE.TABLE METADATA
+  bias 	   STR BIAS
+  zero 	   STR BIAS
+  dark 	   STR DARK
+  flat 	   STR SKYFLAT
+  skyflat  STR SKYFLAT
+  domeflat STR DOMEFLAT
+  object   STR OBJECT
+  science  STR OBJECT
+END
+
+# Recipe options
+RECIPES         METADATA
+        PSPHOT          STR     sdssmosaic/psphot.config               # psphot details
+        PSASTRO         STR     sdssmosaic/psastro.config              # psastro details
+        PPIMAGE         STR     sdssmosaic/ppImage.config              # Recipes for ppImage
+        PPMERGE         STR     sdssmosaic/ppMerge.config              # Recipes for ppMerge
+        PSWARP          STR     sdssmosaic/pswarp.config              # Recipes for pswarp
+	REJECTIONS	STR     sdssmosaic/rejections.config
+END
+
+# Reduction classes
+REDUCTION	METADATA
+	# Detrend processing not needed for these
+        # photometry only for pre-detrended images
+	# This one used to be called PROCESSED
+	SDSSphotonly	METADATA
+		CHIP		STR	PPIMAGE_OP_SOFT
+ 		JPEG_BIN1       STR     PPIMAGE_J1
+ 		JPEG_BIN2       STR     PPIMAGE_J2
+	END
+        # photometry only for pre-detrended images
+	SDSSphotast	METADATA
+		CHIP		STR	PPIMAGE_OA_SOFT
+ 		JPEG_BIN1       STR     PPIMAGE_J1
+ 		JPEG_BIN2       STR     PPIMAGE_J2
+	END
+END
+
+FILERULES	STR	sdssmosaic/filerules.mdc
+
+EXTNAME.RULES METADATA
+  CMF.HEAD STR cmf.hdr
+  CMF.DATA STR cmf.psf
+  CMF.XSRC STR cmf.xsrc
+  CMF.XFIT STR cmf.xfit
+
+  PSF.HEAD  STR	hdr
+  PSF.TABLE STR psf_model
+  PSF.RESID STR psf_resid
+
+  REF.ASTROM STR ref_astrom
+END
+
+BLANK.HEADERS	METADATA
+	FPA.TIME	STR	TAI
+	FPA.EXPOSURE	STR	EXPTIME
+	FPA.AIRMASS	STR	AIRMASS
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/cmf.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/cmf.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/cmf.config	(revision 22157)
@@ -0,0 +1,84 @@
+# Pan-STARRS Imaging Sky Probe
+
+# How to identify this type
+RULE	METADATA
+	SIMPLE		BOOL	TRUE
+	NAXIS		S32	0
+	TELESCOP	STR	ISP-1 
+	INSTRUME	STR	ISP-Apogee
+	DETECTOR	STR	ISP-Apogee-01
+	ISPCAMER	STR	Apogee U42
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	NONE	# There are no extensions
+	FPA.NAME	STR	SEQID	# A PHU keyword for unique identifier within the hierarchy level
+END
+
+# What's in the FITS file?
+CONTENTS	STR	Chip:Cell:amplifier
+
+# Specify the cell data
+CELLS	METADATA
+	amplifier	METADATA
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC		STR	TRIMSEC
+		CELL.BIASSEC		STR	BIASSEC
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.OBSTYPE	STR	OBSTYPE
+	FPA.OBJECT	STR	OBSTYPE
+	FPA.FILTER	STR	FILTNAME
+	FPA.RA		STR	RA
+	FPA.DEC		STR	DEC
+	FPA.RADECSYS	STR	RADECSYS
+	FPA.ALT		STR	ALT
+	FPA.AZ		STR	AZ
+	FPA.POSANGLE	STR	ROTANGLE
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.TIME	STR	MJD-OBS
+	CHIP.TEMP	STR	CCDTEMP
+	CELL.EXPOSURE	STR	EXPTIME
+	CELL.DARKTIME	STR	DARKTIME
+	CELL.TIME	STR	MJD-OBS
+	CELL.GAIN	STR	GAIN
+	CELL.READNOISE	STR	RDNOISE
+	CELL.XBIN	STR	XBIN
+	CELL.YBIN	STR	YBIN
+#	CELL.SATURATION	STR	SATURATE	### Currently set to 0 ???
+	CELL.BAD	STR	BADLEVEL
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	FPA.TIMESYS	STR	UTC
+	CELL.SATURATION	F32	65535
+	CELL.READDIR	S32	1
+	CELL.TIMESYS	STR	UTC
+	CHIP.XPARITY	S32	1
+	CHIP.YPARITY	S32	1
+	CHIP.X0		S32	0
+	CHIP.Y0		S32	0
+	CELL.XPARITY	S32	1
+	CELL.YPARITY	S32	1
+	CELL.X0		S32	0
+	CELL.Y0		S32	0
+END
+
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
+
+# PS Concepts to get from the database
+DATABASE	METADATA
+# None.
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/cmp.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/cmp.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/cmp.config	(revision 22157)
@@ -0,0 +1,84 @@
+# Pan-STARRS Imaging Sky Probe
+
+# How to identify this type
+RULE	METADATA
+	SIMPLE		BOOL	FALSE
+	NAXIS		S32	0
+	TELESCOP	STR	ISP-1 
+	INSTRUME	STR	ISP-Apogee
+	DETECTOR	STR	ISP-Apogee-01
+	ISPCAMER	STR	Apogee U42
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	NONE	# There are no extensions
+	FPA.NAME	STR	SEQID	# A PHU keyword for unique identifier within the hierarchy level
+END
+
+# What's in the FITS file?
+CONTENTS	STR	Chip:Cell:amplifier
+
+# Specify the cell data
+CELLS	METADATA
+	amplifier	METADATA
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC		STR	TRIMSEC
+		CELL.BIASSEC		STR	BIASSEC
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.OBSTYPE	STR	OBSTYPE
+	FPA.OBJECT	STR	OBSTYPE
+	FPA.FILTER	STR	FILTNAME
+	FPA.RA		STR	RA
+	FPA.DEC		STR	DEC
+	FPA.RADECSYS	STR	RADECSYS
+	FPA.ALT		STR	ALT
+	FPA.AZ		STR	AZ
+	FPA.POSANGLE	STR	ROTANGLE
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.TIME	STR	MJD-OBS
+	CHIP.TEMP	STR	CCDTEMP
+	CELL.EXPOSURE	STR	EXPTIME
+	CELL.DARKTIME	STR	DARKTIME
+	CELL.TIME	STR	MJD-OBS
+	CELL.GAIN	STR	GAIN
+	CELL.READNOISE	STR	RDNOISE
+	CELL.XBIN	STR	XBIN
+	CELL.YBIN	STR	YBIN
+#	CELL.SATURATION	STR	SATURATE	### Currently set to 0 ???
+	CELL.BAD	STR	BADLEVEL
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	FPA.TIMESYS	STR	UTC
+	CELL.SATURATION	F32	65535
+	CELL.READDIR	S32	1
+	CELL.TIMESYS	STR	UTC
+	CHIP.XPARITY	S32	1
+	CHIP.YPARITY	S32	1
+	CHIP.X0		S32	0
+	CHIP.Y0		S32	0
+	CELL.XPARITY	S32	1
+	CELL.YPARITY	S32	1
+	CELL.X0		S32	0
+	CELL.Y0		S32	0
+END
+
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
+
+# PS Concepts to get from the database
+DATABASE	METADATA
+# None.
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/dvo.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/dvo.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/dvo.config	(revision 22157)
@@ -0,0 +1,71 @@
+DVO.CAMERADIR		/home/jester/IPP/ippconfig/sdssmosaic
+
+# location of DVO database tables
+CATDIR			/disk1/jester/data/SDSS/stripe82/coadd/catdir
+
+# keywords used by DVO to interpret the headers
+
+# used by parse_time to find time-related keywords
+MJD-KEYWORD		NONE
+DATE-KEYWORD		DATE-OBS
+DATE-MODE		YYYY-MM-DD
+UT-KEYWORD		TAIHMS
+JD-KEYWORD		NONE
+
+# other keyword abstractions
+EXPTIME-KEYWORD		EXPTIME
+AIRMASS-KEYWORD		AIRMASS
+CCDNUM-KEYWORD		EXTNAME
+ST-KEYWORD		NONE
+OBSERVATORY-LATITUDE	NONE
+OBSERVATORY-LONGITUDE	NONE
+SUBPIX_DATAFILE		NONE
+
+# instrumental magnitude range for calibration mode 
+CAL_INSTMAG_MAX		-7.0
+CAL_INSTMAG_MIN		-9.5
+
+# exclude overscan region from the dB image boundaries
+XOVERSCAN		0
+YOVERSCAN		0
+
+# only upload stars within region; a value of 0 means ignore the limit
+ADDSTAR_XMIN		0
+ADDSTAR_XMAX		0
+ADDSTAR_YMIN		0
+ADDSTAR_YMAX		0
+
+# exclude stars with SN > SNLIMIT (ADDSTAR_SNLIMIT overrides old name MIN_SN_FSTAT)
+ADDSTAR_SNLIMIT		0
+
+# allowed astrometry error (arcseconds)
+ADDSTAR_MAX_CERROR	2.0
+
+# correlation radius (arcseconds)
+ADDSTAR_RADIUS		2.0
+
+# scaled correlation radius 
+ADDSTAR_NSIGMA		0
+
+IMAGE_SCATTER           0.075  # mark images POOR if stdev(Mcal) > IMAGE_SCATTER
+STAR_SCATTER            0.005  # mark stars POOR if stdev(Mrel) > STAR_SCATTER
+IMAGE_OFFSET            0.100  # mark images POOR if abs(delta(Mcal)) > IMAGE_OFFSET
+STAR_CHISQ              10.0   # mark stars POOR if Xm > STAR_CHISQ
+STAR_TOOFEW              3     # mark star FEW if N(good) < STAR_TOOFEW
+IMAGE_TOOFEW            10     # mark image FEW if N(good) < IMAGE_TOOFEW
+IMAGE_GOOD_FRACTION     0.05   # mark image FEW if N(good) < IMAGE_GOOD_FRACTION * Nstars
+
+SCATTER_LIM             15.0
+MAG_LIM                 20.0   # select stars brighter than this for relphot analysis
+SIGMA_LIM               0.015  # select measurements with dM < SIGMA_LIM for relphot analysis
+# INST_MAG_MIN         -17     # optional constraints on magnitude ranges
+# INST_MAG_MAX         -13     # optional constraints on magnitude ranges
+
+RELPHOT_GRID_BINNING    256
+RELPHOT_GRID_X 6
+RELPHOT_GRID_Y 14
+
+PM_DT_MIN               0.25
+PM_TOOFEW               4
+POS_TOOFEW              1
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/dvo.layout
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/dvo.layout	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/dvo.layout	(revision 22157)
@@ -0,0 +1,59 @@
+# this file defines the layout of the mosaic imager:
+# 
+# SJ: I don't know what it does... help!
+
+NCCD 6
+NAXIS1 2048
+NAXIS2 1361
+
+MOSAIC_X  6
+MOSAIC_Y  1
+
+# lines need to contain:
+
+CHIPID_KEYWORD EXTNAME
+
+# ID     CHIPID xoffset yoffset xflip yflip datasec         biassec            Xo      Yo      theta
+CCD.0     ccd00       1       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.1     ccd01       2       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.2     ccd02       3       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.3     ccd03       4       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.4     ccd04       5       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.5     ccd05       6       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.6     ccd06       7       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.7     ccd07       8       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.8     ccd08       9       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.9     ccd09       1       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.10    ccd10       2       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.11    ccd11       3       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.12    ccd12       4       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.13    ccd13       5       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.14    ccd14       6       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.15    ccd15       7       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.16    ccd16       8       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.17    ccd17       9       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.18    ccd18       1       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.19    ccd19       2       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.20    ccd20       3       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.21    ccd21       4       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.22    ccd22       5       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.23    ccd23       6       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.24    ccd24       7       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.25    ccd25       8       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.26    ccd26       9       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.27    ccd27       1       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.28    ccd28       2       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.29    ccd29       3       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.30    ccd30       4       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.31    ccd31       5       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.32    ccd32       6       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.33    ccd33       7       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.34    ccd34       8       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.35    ccd35       9       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.36    ccd36       0       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.37    ccd37      10       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.38    ccd38       0       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.39    ccd39      10       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+
+# can we use header to find biassec
+USE_BIASSEC 1
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/filerules.mdc
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/filerules.mdc	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/filerules.mdc	(revision 22157)
@@ -0,0 +1,132 @@
+PSASTRO.INPUT       STR PSASTRO.INPUT.CMF
+PSASTRO.OUTPUT      STR PSASTRO.OUTPUT.CMF.SPL
+PSPHOT.OUTPUT       STR PSPHOT.OUTPUT.CMF.SPL
+
+### input file definitions
+### use @DETDB entries to get the detrend images from the database
+### replace @DETDB with @FILES if you want to require it from the 
+### command line, or with an explicit name to require a specific file
+TYPE               INPUT FILENAME.RULE DATA.LEVEL FILE.TYPE 
+
+## files used by ppImage
+PPIMAGE.INPUT      INPUT @FILES        CHIP       IMAGE
+PPIMAGE.MASK       INPUT @DETDB        CHIP       MASK
+PPIMAGE.BIAS       INPUT @DETDB        CHIP       IMAGE
+PPIMAGE.DARK       INPUT @DETDB        CHIP       DARK
+PPIMAGE.FLAT       INPUT @DETDB        CHIP       IMAGE
+PPIMAGE.FRINGE     INPUT @DETDB        CHIP       FRINGE     
+PPIMAGE.SHUTTER    INPUT @DETDB        CHIP       IMAGE     
+
+## Files used by ppMerge
+PPMERGE.INPUT      INPUT @FILES        CHIP       IMAGE
+PPMERGE.INPUT.MASK INPUT @FILES        CHIP       MASK
+PPMERGE.INPUT.WEIGHT INPUT @FILES      CHIP       WEIGHT
+
+## files used to build and apply the flat-field correction images
+DVOCORR.INPUT      INPUT @FILES        CHIP       IMAGE
+DVOCORR.REFHEAD    INPUT @FILES        CHIP       HEADER
+DVOFLAT.INPUT      INPUT @FILES        CHIP       IMAGE
+DVOFLAT.CORR       INPUT @DETDB        CHIP       IMAGE
+
+## files used by psphot 
+PSPHOT.LOAD        INPUT @FILES        CHIP       IMAGE
+PSPHOT.INPUT       INPUT @FILES        CHIP       IMAGE     
+PSPHOT.MASK        INPUT @FILES        CHIP       MASK     
+PSPHOT.WEIGHT      INPUT @FILES        CHIP       WEIGHT     
+PSPHOT.PSF.LOAD    INPUT @FILES        CHIP       PSF       
+PSPHOT.INPUT.CMF   INPUT @FILES        CHIP       CMF       
+
+## files used by psastro 
+PSASTRO.WCS        INPUT none.fits     CHIP       CMF
+PSASTRO.MODEL      INPUT @DETDB        FPA        ASTROM
+PSASTRO.INPUT.CMP  INPUT @FILES        CHIP       CMP       
+PSASTRO.INPUT.CMF  INPUT @FILES        CHIP       CMF       
+
+## files used by pswarp
+PSWARP.INPUT       INPUT @FILES        CHIP       IMAGE
+PSWARP.WEIGHT      INPUT @FILES        CHIP       WEIGHT
+PSWARP.MASK        INPUT @FILES        CHIP       MASK
+PSWARP.SKYCELL     INPUT @FILES        FPA        IMAGE
+PSWARP.ASTROM      INPUT @FILES        CHIP       CMF
+
+PPSUB.INPUT        INPUT @FILES        FPA	     IMAGE
+PPSUB.INPUT.MASK   INPUT @FILES        FPA	     MASK
+PPSUB.INPUT.WEIGHT INPUT @FILES        FPA	     WEIGHT
+PPSUB.REF          INPUT @FILES        FPA	     IMAGE
+PPSUB.REF.MASK     INPUT @FILES        FPA	     MASK
+PPSUB.REF.WEIGHT   INPUT @FILES        FPA	     WEIGHT
+
+PPSTACK.INPUT      INPUT @FILES        FPA	     IMAGE
+PPSTACK.INPUT.MASK INPUT @FILES        FPA	     MASK
+PPSTACK.INPUT.WEIGHT INPUT @FILES      FPA	     WEIGHT
+
+### output file definitions
+TYPE                OUTPUT   FILENAME.RULE      	       FILE.TYPE  DATA.LEVEL FILE.SAVE FILE.FORMAT
+PPIMAGE.OUTPUT      OUTPUT   {OUTPUT}.{CHIP.NAME}.fits      IMAGE      CHIP       TRUE      NONE
+PPIMAGE.OUTPUT.MASK OUTPUT   {OUTPUT}.{CHIP.NAME}.mask.fits MASK       CHIP       TRUE      NONE
+PPIMAGE.OUTPUT.WEIGHT OUTPUT {OUTPUT}.{CHIP.NAME}.wt.fits   WEIGHT     CHIP       TRUE      NONE
+PPIMAGE.CHIP        OUTPUT 	{OUTPUT}.{CHIP.NAME}.ch.fits   IMAGE      CHIP       TRUE      NONE
+PPIMAGE.CHIP.MASK   OUTPUT 	{OUTPUT}.{CHIP.NAME}.ch.mask.fits MASK    CHIP       TRUE      NONE
+PPIMAGE.CHIP.WEIGHT OUTPUT 	{OUTPUT}.{CHIP.NAME}.ch.wt.fits WEIGHT    CHIP       TRUE      NONE
+PPIMAGE.OUTPUT.FPA1 OUTPUT 	{OUTPUT}.fpa1.fits             IMAGE      FPA        TRUE      NONE
+PPIMAGE.OUTPUT.FPA2 OUTPUT 	{OUTPUT}.fpa2.fits             IMAGE      FPA        TRUE      NONE
+PPIMAGE.STATS       OUTPUT   {OUTPUT}.stats                 STATS      FPA        TRUE      NONE
+
+PPIMAGE.JPEG1       OUTPUT   {OUTPUT}.b1.jpg    	       JPEG       FPA        TRUE      NONE
+PPIMAGE.JPEG2       OUTPUT   {OUTPUT}.b2.jpg    	       JPEG       FPA        TRUE      NONE
+PPIMAGE.BIN1        OUTPUT   {OUTPUT}.{CHIP.NAME}.b1.fits   IMAGE      CHIP       TRUE      NONE
+PPIMAGE.BIN2        OUTPUT   {OUTPUT}.{CHIP.NAME}.b2.fits   IMAGE      CHIP       TRUE      NONE
+
+DVOCORR.OUTPUT      OUTPUT 	{OUTPUT}.{CHIP.NAME}.fc.fits   IMAGE      CHIP       TRUE      NONE
+DVOFLAT.OUTPUT      OUTPUT 	{OUTPUT}.{CHIP.NAME}.co.fits   IMAGE      CHIP       TRUE      NONE
+
+PSPHOT.RESID        OUTPUT   {OUTPUT}.{CHIP.NAME}.res.fits  IMAGE      CHIP       TRUE      NONE
+PSPHOT.BACKGND      OUTPUT   {OUTPUT}.{CHIP.NAME}.bck.fits  IMAGE      CHIP       TRUE      NONE
+PSPHOT.BACKSUB      OUTPUT   {OUTPUT}.{CHIP.NAME}.sub.fits  IMAGE      CHIP       TRUE      NONE
+PSPHOT.BACKMDL      OUTPUT   {OUTPUT}.{CHIP.NAME}.mdl.fits  IMAGE      CHIP       TRUE      NONE
+PSPHOT.BACKMDL.STDEV OUTPUT   {OUTPUT}.{CHIP.NAME}.mdd.fits IMAGE      CHIP       TRUE      NONE
+
+PSPHOT.OUTPUT.RAW   OUTPUT   {OUTPUT}.{CHIP.NAME}           RAW        CHIP       TRUE      NONE
+PSPHOT.OUTPUT.SX    OUTPUT   {OUTPUT}.{CHIP.NAME}.sx        SX         CHIP       TRUE      NONE
+PSPHOT.OUTPUT.OBJ   OUTPUT   {OUTPUT}.{CHIP.NAME}.obj       OBJ        CHIP       TRUE      NONE
+#   PSPHOT.OUTPUT       OUTPUT   {OUTPUT}.{CHIP.NAME}.cmf       CMF        CHIP       TRUE      NONE
+PSPHOT.OUTPUT.CMF.SPL  OUTPUT   {OUTPUT}.{CHIP.NAME}.cmf       CMF        CHIP       TRUE      NONE
+PSPHOT.OUTPUT.CMF.MEF  OUTPUT   {OUTPUT}.cmf                   CMF        FPA        TRUE      NONE
+
+PSPHOT.PSF.SAVE     OUTPUT   {OUTPUT}.{CHIP.NAME}.psf       PSF        CHIP       TRUE      NONE
+
+SOURCE.PLOT.MOMENTS  OUTPUT  {OUTPUT}.{CHIP.NAME}.mnt.png   KAPA       CHIP       TRUE      NONE
+SOURCE.PLOT.PSFMODEL OUTPUT  {OUTPUT}.{CHIP.NAME}.psf.png   KAPA       CHIP       TRUE      NONE
+SOURCE.PLOT.APRESID  OUTPUT  {OUTPUT}.{CHIP.NAME}.dap.png   KAPA       CHIP       TRUE      NONE
+
+PSASTRO.OUTPUT.CMP   OUTPUT   {OUTPUT}.{CHIP.NAME}.smp      CMP        CHIP       TRUE      NONE
+PSASTRO.OUTPUT.CMF.SPL  OUTPUT   {OUTPUT}.{CHIP.NAME}.smf   CMF        CHIP       TRUE      NONE
+PSASTRO.OUTPUT.CMF.MEF  OUTPUT   {OUTPUT}.smf	       CMF        FPA        TRUE      NONE
+#following was missing, is this the right thing to do???
+PSASTRO.OUTPUT.MEF  OUTPUT   {OUTPUT}.smf	               CMF        FPA        TRUE      NONE
+
+PSWARP.OUTPUT       OUTPUT   {OUTPUT}.fits      	       IMAGE      FPA        TRUE      NONE
+PSWARP.OUTPUT.MASK  OUTPUT   {OUTPUT}.mask.fits             MASK       FPA        TRUE      NONE
+PSWARP.OUTPUT.WEIGHT OUTPUT  {OUTPUT}.wt.fits               WEIGHT     FPA        TRUE      NONE
+PSWARP.OUTPUT.SOURCES OUTPUT {OUTPUT}.cmf                     CMF         FPA        TRUE      NONE
+PSWARP.BIN1         OUTPUT   {OUTPUT}.b1.fits   	       IMAGE      FPA        TRUE      NONE
+PSWARP.BIN2         OUTPUT   {OUTPUT}.b2.fits   	       IMAGE      FPA        TRUE      NONE
+
+SKYCELL.STATS            OUTPUT {OUTPUT}.stats                	 STATS     FPA        TRUE      NONE
+SKYCELL.TEMPLATE         OUTPUT {OUTPUT}.skycell              	 SKYCELL   FPA        TRUE      NONE
+
+PPSUB.OUTPUT        OUTPUT   {OUTPUT}.fits                  IMAGE      FPA        TRUE      NONE
+PPSUB.OUTPUT.MASK   OUTPUT   {OUTPUT}.mask.fits             MASK       FPA        TRUE      NONE
+PPSUB.OUTPUT.WEIGHT OUTPUT   {OUTPUT}.wt.fits               WEIGHT     FPA        TRUE      NONE
+
+PPSTACK.OUTPUT      OUTPUT   {OUTPUT}.fits                  IMAGE      FPA        TRUE      NONE
+PPSTACK.OUTPUT.MASK OUTPUT   {OUTPUT}.mask.fits             MASK       FPA        TRUE      NONE
+PPSTACK.OUTPUT.WEIGHT OUTPUT {OUTPUT}.weight.fits           WEIGHT     FPA        TRUE      NONE
+
+PPSIM.OUTPUT        OUTPUT   {OUTPUT}.{CHIP.NAME}.fits      IMAGE      CHIP       TRUE      NONE
+
+LOG.IMFILE            OUTPUT {OUTPUT}.{CHIP.NAME}.log       TEXT       CHIP       TRUE      NONE
+LOG.EXP               OUTPUT {OUTPUT}.log                   TEXT       FPA        TRUE      NONE
+
+TRACE.IMFILE             OUTPUT {OUTPUT}.{CHIP.NAME}.trace  TEXT      CHIP       TRUE      NONE
+TRACE.EXP                OUTPUT {OUTPUT}.trace              TEXT      FPA        TRUE      NONE
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/format.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/format.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/format.config	(revision 22157)
@@ -0,0 +1,139 @@
+# SDSSmosaic camera, starting from SDSS from Pan-STARRS Imaging Sky Probe
+# Sebastian Jester jester at mpia.de August 7, 2007
+
+# How to identify this type
+RULE	METADATA
+	SIMPLE		BOOL	TRUE
+#	NAXIS		S32	2
+	ORIGIN		STR	SDSS
+	TELESCOP	STR	2.5m
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	CHIP	# The FITS file represents one camcol = chip, 
+					# 6 of which make the FPA
+	EXTENSIONS	STR	NONE	# There are no extensions
+	FPA.OBS		STR	FRAME	# A PHU keyword for unique identifier within 
+					# the hierarchy level - just needs to be the same
+					# for all 6 files belonging to the same "exposure"
+	CHIP.NAME	STR	CAMCOL	# Distinguishes chip name
+	CONTENT		STR	CAMCOL
+	CONTENT.RULE	STR	{CHIP.NUM} # How to derive the CONTENT when writing
+END
+
+# What's in the FITS file?
+# CONTENTS	STR	Chip:Cell:amplifier
+CONTENTS	METADATA
+	# Content name, chipType
+	1		STR	CHIP1:Chip
+	2		STR	CHIP2:Chip
+	3		STR	CHIP3:Chip
+	4		STR	CHIP4:Chip
+	5		STR	CHIP5:Chip
+	6		STR	CHIP6:Chip
+END
+	
+# Specify the chips
+CHIPS		METADATA
+	# Chip type, cellName:cellType
+	Chip	STR	Cell:Cell
+END
+
+
+# Specify the cell data
+CELLS	METADATA
+	Cell	METADATA
+#		CELL.TRIMSEC.SOURCE	STR	HEADER
+#		CELL.TRIMSEC		STR	TRIMSEC
+		CELL.TRIMSEC.SOURCE	STR	VALUE
+		CELL.TRIMSEC		STR	[1:2048,1:1361]
+#		CELL.BIASSEC.SOURCE	STR	HEADER
+#		CELL.BIASSEC		STR	BIASSEC
+	END
+END
+# NB: If we could look up the gains etc. separately for both amplifiers,
+# we could split the chips into cells for each amp
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.TELESCOPE	STR	TELESCOP
+# FPA.INSTRUMENT actually needs to be identical to the CAMERA, 
+# so I'm moving it from here down to the defaults to make sure that
+# this is ture
+#	FPA.INSTRUMENT	STR	ORIGIN
+	FPA.DETECTOR	STR	CCDLOC
+#	FPA.AIRMASS	STR	AIRMASS
+	FPA.OBSTYPE	STR	FLAVOR
+	FPA.OBJECT	STR	OBJECT
+	FPA.FILTERID	STR	FILTER
+	FPA.FILTER	STR	FILTER
+	FPA.POSANGLE	STR	IPA
+	FPA.RA		STR	RA
+	FPA.DEC		STR	DEC
+	FPA.RADECSYS	STR	RADECSYS
+#	FPA.FOCUS	STR	TELFOCUS
+	FPA.TIME    	STR     DATE-OBS TAIHMS # Two values are interpreted as date and time
+	FPA.TIMESYS	STR	TIMESYS
+	FPA.ALT		STR	ALT
+	FPA.AZ		STR	AZ
+#	FPA.TEMP	STR	CCDTEMP
+	FPA.EXPOSURE	STR	EXPTIME
+	CHIP.TEMP	STR	CCDTEMP
+	CELL.EXPOSURE	STR	EXPTIME
+	CELL.DARKTIME	STR	EXPTIME
+	CELL.TIME	STR	DATE-OBS TAIHMS
+#	CELL.GAIN	STR	GAIN
+#	CELL.READNOISE	STR	RDNOISE
+#	CELL.SATURATION	STR	SATURATE	### Currently set to 0 ???
+#	CELL.TIMESYS	STR	TIMESYS
+	CELL.XBIN	STR	COLBIN
+	CELL.YBIN	STR	ROWBIN
+# these were used for some early data
+#	CELL.XBIN	STR	XBIN
+#	CELL.YBIN	STR	YBIN
+#	CELL.BAD	STR	BADLEVEL
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	FPA.INSTRUMENT	STR	SDSSmosaic
+# The gain is only roughly correct...
+	CELL.GAIN	F32	5.0
+# readnoise is from http://www.sdss.org/dr6/instruments/imager/index.html
+# Note that I have emails from Jim Gunn saying dark noise *is* important...
+	CELL.READNOISE	F32	5.0
+	FPA.AIRMASS	F32	1.3
+	CHIP.XSIZE	S32	2048
+	CHIP.YSIZE	S32	1361
+	CELL.XSIZE	S32	2048
+	CELL.YSIZE	S32	1361
+#	FPA.TIMESYS	STR	UTC
+	CELL.SATURATION	F32	60000
+	CELL.READDIR	S32	1
+	CELL.TIMESYS	STR	UTC
+	CHIP.XPARITY	S32	1
+	CHIP.YPARITY	S32	1
+	CHIP.X0		S32	0
+	CHIP.Y0		S32	0
+	CELL.XPARITY	S32	1
+	CELL.YPARITY	S32	1
+	CELL.X0		S32	0
+	CELL.Y0		S32	0
+#	CELL.XBIN	S32	1
+#	CELL.YBIN	S32	1
+END
+
+FORMATS		METADATA
+	FPA.RA		STR	DEGREES
+	FPA.DEC		STR	DEGREES
+	FPA.TIME    	STR     SEPARATE YEAR.FIRST
+#	FPA.TIME	STR	MJD
+	CELL.TIME    	STR     SEPARATE YEAR.FIRST
+#	CELL.TIME	STR	MJD
+END
+
+# PS Concepts to get from the database
+DATABASE	METADATA
+# None.
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/format_skycell.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/format_skycell.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/format_skycell.config	(revision 22157)
@@ -0,0 +1,110 @@
+# SDSS camera, starting from Pan-STARRS Imaging Sky Probe
+# Sebastian Jester jester at mpia.de August 7, 2007
+
+# How to identify this type - if the following header values match, it's accepted.
+RULE	METADATA
+	SIMPLE		BOOL	TRUE
+#	NAXIS		S32	2
+	ORIGIN		STR	SDSS
+	TELESCOP	STR	2.5m
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	NONE	# There are no extensions
+	FPA.NAME	STR	FRAME	# A PHU keyword for unique identifier within the hierarchy level
+END
+
+# What's in the FITS file?
+CONTENTS	STR	Chip:Cell:amplifier
+
+# Specify the cell data
+CELLS	METADATA
+	amplifier	METADATA
+#		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	VALUE
+#		CELL.BIASSEC.SOURCE	STR	HEADER
+#		CELL.TRIMSEC		STR	TRIMSEC
+		CELL.TRIMSEC		STR	[1:2048,1:1361]
+#		CELL.BIASSEC		STR	BIASSEC
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.TELESCOPE	STR	TELESCOP
+	FPA.INSTRUMENT	STR	ORIGIN
+# Need the following?
+	FPA.DETECTOR	STR	CCDLOC
+#	FPA.AIRMASS	STR	AIRMASS
+	FPA.OBSTYPE	STR	FLAVOR
+	FPA.OBJECT	STR	OBJECT
+	FPA.FILTERID	STR	FILTER
+	FPA.FILTER	STR	FILTER
+	FPA.POSANGLE	STR	IPA
+	FPA.RA		STR	RA
+	FPA.DEC		STR	DEC
+	FPA.RADECSYS	STR	RADECSYS
+#	FPA.FOCUS	STR	TELFOCUS
+	FPA.TIME	STR	TAI
+	FPA.TIMESYS	STR	TIMESYS
+	FPA.ALT		STR	ALT
+	FPA.AZ		STR	AZ
+#	FPA.TEMP	STR	CCDTEMP
+	FPA.EXPOSURE	STR	EXPTIME
+	CHIP.TEMP	STR	CCDTEMP
+	CELL.EXPOSURE	STR	EXPTIME
+	CELL.DARKTIME	STR	EXPTIME
+	CELL.TIME	STR	TAI
+#	CELL.GAIN	STR	GAIN
+#	CELL.READNOISE	STR	RDNOISE
+#	CELL.SATURATION	STR	SATURATE	### Currently set to 0 ???
+#	CELL.TIMESYS	STR	TIMESYS
+	CELL.XBIN	STR	COLBIN
+	CELL.YBIN	STR	ROWBIN
+# these were used for some early data
+#	CELL.XBIN	STR	XBIN
+#	CELL.YBIN	STR	YBIN
+#	CELL.BAD	STR	BADLEVEL
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+# The gain is only roughly correct...
+	CELL.GAIN	F32	5.0
+# readnoise is from http://www.sdss.org/dr6/instruments/imager/index.html
+# Note that I have emails from Jim Gunn saying dark noise *is* important...
+	CELL.READNOISE	F32	5.0
+	FPA.AIRMASS	F32	1.3
+	CHIP.XSIZE	S32	2048
+	CHIP.YSIZE	S32	1361
+	CELL.XSIZE	S32	2048
+	CELL.YSIZE	S32	2048
+#	FPA.TIMESYS	STR	UTC
+	CELL.SATURATION	F32	60000
+	CELL.READDIR	S32	1
+	CELL.TIMESYS	STR	UTC
+	CHIP.XPARITY	S32	1
+	CHIP.YPARITY	S32	1
+	CHIP.X0		S32	0
+	CHIP.Y0		S32	0
+	CELL.XPARITY	S32	1
+	CELL.YPARITY	S32	1
+	CELL.X0		S32	0
+	CELL.Y0		S32	0
+#	CELL.XBIN	S32	1
+#	CELL.YBIN	S32	1
+END
+
+FORMATS		METADATA
+	FPA.RA		STR	DEGREES
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
+
+# PS Concepts to get from the database
+DATABASE	METADATA
+# None.
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/ppImage.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/ppImage.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/ppImage.config	(revision 22157)
@@ -0,0 +1,100 @@
+### ppImage recipe configuration file
+
+# Inherit everything from the top-level recipe file, except for what's below.
+
+PHOTCODE.RULE		STR	SDSS.{FILTER.ID}.{DETECTOR}
+
+FRINGE.FILTERS  MULTI
+FRINGE.FILTERS	STR z
+# FRINGE.FILTERS	STR y
+
+# apply the following constraints when selecting a detrend image
+DETREND.CONSTRAINTS  METADATA
+  BIAS METADATA
+  END
+  DARK METADATA
+  END
+  SHUTTER METADATA
+  END	
+  FLAT METADATA
+    FILTER  STR FPA.FILTERID
+#   VERSION STR RAW
+  END
+  FRINGE METADATA
+    FILTER   STR FPA.FILTERID
+#   AIRMASS  STR FPA.AIRMASS
+#   TWILIGHT STR TWILIGHT:FPA.TIME
+  END
+  MASK METADATA
+    FILTER   STR FPA.FILTERID
+  END
+END
+
+## these science-image analysis recipes are placed here set MASK = FALSE
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_DET_ONLY  METADATA
+  BASE.FITS       BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL   FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL   FALSE           # Save base detrended image?
+  CHIP.FITS       BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL   TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL   TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom
+PPIMAGE_OBDSFRP   METADATA
+  BASE.FITS       BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL   FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL   FALSE           # Save base detrended image?
+  CHIP.FITS       BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL   TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL   TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM          BOOL    TRUE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_OBDSFRA   METADATA
+  BASE.FITS       BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL   FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL   FALSE           # Save base detrended image?
+  CHIP.FITS       BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL   TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL   TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM          BOOL    TRUE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    TRUE            # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/ppMerge.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/ppMerge.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/ppMerge.config	(revision 22157)
@@ -0,0 +1,3 @@
+### ppImage recipe configuration file
+
+# Inherit everything from the top-level recipe file, except for what's below.
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/psastro.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/psastro.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/psastro.config	(revision 22157)
@@ -0,0 +1,103 @@
+
+# astrometry matching parameters
+
+# nominal plate scale (microns / pixel)
+PSASTRO.PIXEL.SCALE    F32  10.0
+
+# pmAstromGridMatch:
+PSASTRO.GRID.MIN.ANGLE F32 -2.0
+PSASTRO.GRID.MAX.ANGLE F32 +2.0
+PSASTRO.GRID.DEL.ANGLE F32  0.25
+PSASTRO.GRID.MIN.SIGMA F32  5.0
+
+# pmAstromGridAngle
+# max grid offset in FP units (microns)
+# use plate-scale to make this in pixels?
+# PSASTRO.GRID.OFFSET    F32     10000.
+PSASTRO.GRID.OFFSET    F32     100.
+PSASTRO.GRID.SCALE     F32     500
+PSASTRO.GRID.NSTAR.MAX S32     800 # max stars accepted for fitting
+
+PSASTRO.MAX.NRAW      S32       800   # max stars accepted for fitting (0 for all)
+PSASTRO.MAX.NREF      S32      5000   # max stars accepted for fitting (0 for all)
+
+# extra field for ref stars:
+PSASTRO.FIELD.PADDING  F32     0.15
+
+# 
+PSASTRO.MIN.INST.MAG.RAW       F32     -15.0   # min instrumental magnitude for stars accepted for fitting
+PSASTRO.MAX.INST.MAG.RAW       F32       0.0   # max instrumental magnitude for stars accepted for fitting
+
+#
+PSASTRO.GRID.MIN.ANGLE F32 -2.0 # start angle (degrees)
+PSASTRO.GRID.MAX.ANGLE F32 +2.0
+PSASTRO.GRID.DEL.ANGLE F32  0.5
+
+#
+PSASTRO.GRID.MIN.SCALE F32  1.00
+PSASTRO.GRID.MAX.SCALE F32  1.00
+PSASTRO.GRID.DEL.SCALE F32  0.01
+
+# single-chip radius match in pixels
+PSASTRO.MATCH.RADIUS.N0 F32    90
+PSASTRO.MATCH.RADIUS.N1 F32    60
+PSASTRO.MATCH.RADIUS.N2 F32    30
+PSASTRO.MATCH.RADIUS.N3 F32    20
+PSASTRO.MATCH.RADIUS.N4 F32    20
+PSASTRO.MATCH.RADIUS.N5 F32    20
+PSASTRO.MATCH.RADIUS.N6 F32    10
+PSASTRO.MATCH.RADIUS.N7 F32    10
+PSASTRO.MATCH.FIT.NITER S32     8
+
+# XXX Test for taurus
+PSASTRO.GRID.MIN.SIGMA  F32  1.0
+PSASTRO.IGNORE          STR  BLEND
+
+# these tweak are in FP units (pixels, currently)
+PSASTRO.TWEAK.SCALE     F32      1
+PSASTRO.TWEAK.RANGE     F32     75
+PSASTRO.TWEAK.SMOOTH    F32      2
+PSASTRO.TWEAK.NSIGMA    F32      3
+
+# This used to be true...
+PSASTRO.USE.MODEL             BOOL     FALSE
+
+# PSASTRO.FIX.CHIPS             BOOL     TRUE
+PSASTRO.PIXEL.TOLERANCE       F32      20.0
+PSASTRO.ANGLE.TOLERANCE       F32      1.0
+
+PSASTRO.CATDIR STR /IPP/ippRefs/catdir.2mass
+
+# Mosaic Astrometry options
+PSASTRO.MOSAIC.MODE         BOOL   FALSE
+# PSASTRO.MOSAIC.MAX.ERROR.N0 F32    1.80 # max allow error for valid solution (arcsec)
+# PSASTRO.MOSAIC.MAX.ERROR.N1 F32    1.20 # max allow error for valid solution (arcsec)
+# PSASTRO.MOSAIC.MAX.ERROR.N2 F32    0.80 # max allow error for valid solution (arcsec)
+# PSASTRO.MOSAIC.MAX.ERROR.N3 F32    0.50 # max allow error for valid solution (arcsec)
+
+PSASTRO.MOSAIC.MAX.ERROR.N0 F32    5.00 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N1 F32    5.00 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N2 F32    5.00 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N3 F32    5.00 # max allow error for valid solution (arcsec)
+
+# mosaic radius match in pixels
+PSASTRO.MOSAIC.RADIUS.N0    F32    10
+PSASTRO.MOSAIC.RADIUS.N1    F32    5
+PSASTRO.MOSAIC.RADIUS.N2    F32    3
+
+PSASTRO.MOSAIC.CHIP.ORDER     S32      1  # limit chip-fit order to 1
+PSASTRO.MOSAIC.CHIP.ORDER.N0  S32      0 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N1  S32      0 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N2  S32      1 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N3  S32      1 # fit order (-1 means use default)
+
+# DVO.GETSTAR STR /home/kiawe/eugene/psconfig/dev.lin64/bin/getstar
+
+# we need to allow a loose fit if we are fitting to 2mass (50mas internal error -> 100mas limit?)
+# if we are fitting against quality digital data, we can require tighter constraints
+
+PSASTRO.MODEL.REF.CHIP        STR      XY33
+
+# Do I need to change this photcode now that I'm using 30 SDSS photcodes?
+DVO.GETSTAR.PHOTCODE        STR      g
+DVO.GETSTAR.MAG.MAX         F32      17.0
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/psphot.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/psphot.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/psphot.config	(revision 22157)
@@ -0,0 +1,72 @@
+
+# turn these on to see specific outputs
+SAVE.BACKMDL	BOOL 	TRUE
+#SAVE.BACKGND	BOOL 	TRUE
+#SAVE.BACKSUB	BOOL 	TRUE
+#SAVE.RESID	BOOL 	TRUE
+SAVE.PSF	BOOL 	TRUE
+SAVE.PLOTS      BOOL    TRUE
+
+# image statistics parameters
+IMSTATS_NPIX        S32  3000    	 # number of pixels to use for sky estimate boxes:
+
+PSF_SN_LIM          F32  10              # minimum S/N for stars used for PSF model
+PSF_MAX_NSTARS      S32  300             # limit number of stars used for PSF model
+
+PSF_MODEL         STR  PS_MODEL_QGAUSS
+
+MOMENTS_SN_MIN      F32   10.0
+#EXT_MIN_SN           F32  50.0           # fit galaxies above this S/N limit
+#FULL_FIT_SN_LIM      F32  50.0
+#AP_MIN_SN            F32  20.0
+MEASURE.APTREND	                    BOOL  FALSE
+PSF_CLUMP_NSIGMA   F32  2.5             # region of Sx,Sy plane to use for selecting PSF stars
+
+## # PSFTREND must be a 2D polynomial
+## # the specified values are ignored but define the active components of the polynomial
+## PSF.TREND.MASK  METADATA  
+##    NORDER_X         S32       3                # number of x orders
+##    NORDER_Y         S32       3                # number of y orders
+##    VAL_X00_Y00      F64       1                # polynomial coefficient
+## 
+##    VAL_X01_Y00      F64       1                # polynomial coefficient
+##    VAL_X00_Y01      F64       1                # polynomial coefficient
+## 
+##    VAL_X02_Y00      F64       1                # polynomial coefficient
+##    VAL_X01_Y01      F64       1                # polynomial coefficient
+##    VAL_X00_Y02      F64       1                # polynomial coefficient
+## 
+##    VAL_X03_Y00      F64       1                # polynomial coefficient
+##    VAL_X02_Y01      F64       1                # polynomial coefficient
+##    VAL_X01_Y02      F64       1                # polynomial coefficient
+##    VAL_X00_Y03      F64       1                # polynomial coefficient
+##    NELEMENTS        S32       10               # number of unmasked components
+## END  # folder for 4D polynomial
+
+XMIN F32 15
+
+PSF.RESIDUALS       BOOL true
+PSF.RESIDUALS.SPATIAL_ORDER S32 1
+
+# The parameter BREAK_POINT stopped the analysis after first linear
+# fit to sources.  Should be NONE or commented out.
+# BREAK_POINT         STR  ENSEMBLE
+OUTPUT.FORMAT       STR  PS1_DEV_1
+
+PSPHOT.SUMMIT METADATA
+ PEAKS_SMOOTH_SIGMA  F32  0.8 	   	 # peak significance threshold
+ PEAKS_NSIGMA_LIMIT  F32  50.0 	   	 # peak significance threshold
+ PEAKS_NMAX          S32  1000
+ SAVE.RESID	BOOL 	FALSE
+ PSF_SN_LIM          F32  25              # minimum S/N for stars used for PSF model
+ MOMENTS_SN_MIN      F32   25.0
+ PSF_MODEL         STR  PS_MODEL_PGAUSS
+END
+
+# Turn on use of footprints to avoid objectcs being found in
+# diffraction spikes etc. of bright stars
+USE_FOOTPRINTS			BOOL	T	# use new pmFootprint peak packaging
+
+# Set aperture correction radius to same 7.4" as SDSS (see EDR paper)
+PSF_REF_RADIUS      F32  18.7            # aperture magnitudes are scaled via 
+					 # curve-of-growth to this radius
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/pswarp.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/pswarp.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/pswarp.config	(revision 22157)
@@ -0,0 +1,14 @@
+### Recipe for pswarp
+
+GRID.NX			S32	128		# Iso-astrom grid size in x (pixels)
+GRID.NY			S32	128		# Iso-astrom grid size in y (pixels)
+INTERPOLATION.MODE	STR	LANCZOS3	# Interpolation mode to use
+MASK.IN			STR	BAD,SAT		# Mask for input data
+MASK.POOR		STR	SUSPECT		# Mask for "poor" warped data
+MASK.BAD		STR	BLANK		# Mask for bad warped data
+POOR.FRAC		F32	0.01		# Max fraction of bad flux for a "poor" warped pixel
+ASTROM.SOURCE		STR	PSASTRO.OUTPUT	# Source file rule for astrometry, or NULL
+#ASTROM.SOURCE		STR	NULL		# Source file rule for astrometry, or NULL
+ASTROM.ACCEPT		BOOL	TRUE		# Accept astrometric solution unconditionally?
+ASTROM.DEPTH		STR	SPLIT           # Source file rule for astrometry, or NULL
+ACCEPT.FRAC		F32	0.1		# Minimum fraction of good pixels to accept result
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/rejections.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/rejections.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/sdssmosaic/rejections.config	(revision 22157)
@@ -0,0 +1,45 @@
+
+DARK METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  0.0
+  IMFILE.STDEV       F32  0.0
+  IMFILE.MEANSTDEV   F32  0.0
+  IMFILE.SN          F32  0.0
+  IMFILE.BIN.STDEV   F32  0.0
+  IMFILE.BIN.SN      F32  0.0
+  IMFILE.FLUX        F32  2.0
+  EXP.MEAN           F32  0.0
+  EXP.STDEV          F32  0.0
+  EXP.MEANSTDEV      F32  0.0
+  EXP.SN             F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SN         F32  0.0
+  EXP.FLUX           F32  2.0
+  ENSEMBLE.MEAN      F32  3.0
+  ENSEMBLE.STDEV     F32  3.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+FRINGE METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  0.0
+  IMFILE.STDEV       F32  0.0
+  IMFILE.MEANSTDEV   F32  0.0
+  IMFILE.SN          F32  0.0
+  IMFILE.BIN.STDEV   F32  0.0
+  IMFILE.BIN.SN      F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  0.0
+  EXP.STDEV          F32  0.0
+  EXP.MEANSTDEV      F32  0.0
+  EXP.SN             F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SN         F32 10.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  0.0
+  ENSEMBLE.STDEV     F32  0.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/.cvsignore	(revision 22157)
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/Makefile.am	(revision 22157)
@@ -0,0 +1,21 @@
+
+installdir = $(datadir)/ippconfig/simmosaic
+
+install_files = \
+	camera.config \
+	format_split.config \
+	format_together.config \
+	ppImage.config \
+	psastro.config \
+	psphot.config \
+	rejections.config \
+	dvo.config
+
+install_DATA = $(install_files)
+
+install-data-hook:
+	chmod 0755 $(installdir)
+
+EXTRA_DIST = $(install_files)
+
+ACLOCAL_AMFLAGS = -I m4
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/camera.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/camera.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/camera.config	(revision 22157)
@@ -0,0 +1,84 @@
+# Camera configuration file for simulated mosaic
+
+# File formats that we know about
+FORMATS         METADATA
+	TOGETHER STR	simmosaic/format_together.config
+	SPLIT	STR	simmosaic/format_split.config
+END
+ 
+# Description of camera --- all the chips and the cells that comprise them
+FPA     METADATA
+        Chip00		STR	Cell00 Cell01 Cell10 Cell11
+        Chip01		STR	Cell00 Cell01 Cell10 Cell11
+        Chip10		STR	Cell00 Cell01 Cell10 Cell11
+        Chip11		STR	Cell00 Cell01 Cell10 Cell11
+END
+
+# move this elsewhere?  we need a lookup table to go from filter ID to abstract name
+FILTER.ID       METADATA
+	NONE	STR	NONE
+	B	STR	B
+	V	STR	V
+	R	STR	R
+	I	STR	I
+	g	STR	g
+	r	STR	r
+	i	STR	i
+	z	STR	z
+END
+
+# Table of strings to use for the class identifier (see ippdb) according to the file level.
+CLASSID		METADATA
+	FPA	STR	fpa
+	CHIP	STR	{CHIP.NAME}
+	CELL	STR	{CHIP.NAME}.{CELL.NAME}
+	fpa	STR	fpa
+	chip	STR	{CHIP.NAME}
+	cell	STR	{CHIP.NAME}.{CELL.NAME}
+END
+
+DVO.CAMERADIR	STR	simmosaic		# Camera directory for DVO
+
+# convert supplied FPA.OBSTYPE values to abstract exptype names
+OBSTYPE.TABLE METADATA
+  bias 	   STR BIAS
+  zero 	   STR BIAS
+  dark 	   STR DARK
+  flat 	   STR SKYFLAT
+  skyflat  STR SKYFLAT
+  domeflat STR DOMEFLAT
+  object   STR OBJECT
+  science  STR OBJECT
+END
+
+# Recipe options
+RECIPES		METADATA
+        PPIMAGE         STR     simmosaic/ppImage.config	# Default: all (normal) options on
+	PSPHOT		STR	simmosaic/psphot.config		# psphot details
+	PSASTRO		STR	simmosaic/psastro.config	# psastro details
+	REJECTIONS	STR	simmosaic/rejections.config	# Rejection limits
+END
+
+# reduction classes (recipes which are grouped together)
+REDUCTION	STR	recipes/reductionClasses.mdc
+
+FITSTYPES       STR     recipes/fitstypes.mdc
+
+FILERULES       STR     recipes/filerules-mef.mdc
+
+EXTNAME.RULES	METADATA
+	CMF.HEAD	STR	{CHIP.NAME}.hdr
+	CMF.DATA	STR	{CHIP.NAME}.psf
+        CMF.XSRC        STR     {CHIP.NAME}.xsrc # use .PSF and .EXT?
+        CMF.XFIT        STR     {CHIP.NAME}.xfit # use .PSF and .EXT?
+
+	PSF.HEAD	STR	{CHIP.NAME}.hdr
+	PSF.TABLE	STR	{CHIP.NAME}.psf_model
+	PSF.RESID	STR	{CHIP.NAME}.psf_resid
+END
+
+BLANK.HEADERS	METADATA
+	FPA.TIME	STR	MJD-OBS
+	FPA.EXPOSURE	STR	EXPTIME
+	FPA.AIRMASS	STR	AIRMASS
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/dvo.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/dvo.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/dvo.config	(revision 22157)
@@ -0,0 +1,58 @@
+
+# keywords used by DVO to interpret the headers
+
+# keyword abstractions:
+DATE-KEYWORD		NONE
+UT-KEYWORD		NONE
+JD-KEYWORD		NONE
+MJD-KEYWORD		MJD-OBS
+
+# other keyword abstractions
+EXPTIME-KEYWORD		EXPTIME
+AIRMASS-KEYWORD		AIRMASS
+CCDNUM-KEYWORD		EXTNAME
+ST-KEYWORD		NONE
+OBSERVATORY-LATITUDE	NONE
+OBSERVATORY-LONGITUDE	NONE
+SUBPIX_DATAFILE		NONE
+
+# instrumental magnitude range for calibration mode 
+CAL_INSTMAG_MAX		0
+CAL_INSTMAG_MIN		0
+
+# exclude overscan region from the dB image boundaries
+XOVERSCAN		0
+YOVERSCAN		0
+
+# only upload stars within region; a value of 0 means ignore the limit
+ADDSTAR_XMIN		0
+ADDSTAR_XMAX		0
+ADDSTAR_YMIN		0
+ADDSTAR_YMAX		0
+
+# exclude stars with SN > SNLIMIT (ADDSTAR_SNLIMIT overrides old name MIN_SN_FSTAT)
+ADDSTAR_SNLIMIT		0
+
+# correlation radius (arcseconds)
+ADDSTAR_RADIUS		1.0
+
+# scaled correlation radius 
+ADDSTAR_NSIGMA		0
+
+IMAGE_SCATTER           0.075  # mark images POOR if stdev(Mcal) > IMAGE_SCATTER
+STAR_SCATTER            0.005  # mark stars POOR if stdev(Mrel) > STAR_SCATTER
+IMAGE_OFFSET            0.100  # mark images POOR if abs(delta(Mcal)) > IMAGE_OFFSET
+STAR_CHISQ              10.0   # mark stars POOR if Xm > STAR_CHISQ
+STAR_TOOFEW              3     # mark star FEW if N(good) < STAR_TOOFEW
+IMAGE_TOOFEW            10     # mark image FEW if N(good) < IMAGE_TOOFEW
+IMAGE_GOOD_FRACTION     0.05   # mark image FEW if N(good) < IMAGE_GOOD_FRACTION * Nstars
+
+SCATTER_LIM             15.0
+MAG_LIM                 20.0   # select stars brighter than this for relphot analysis
+SIGMA_LIM               0.015  # select measurements with dM < SIGMA_LIM for relphot analysis
+# INST_MAG_MIN         -17     # optional constraints on magnitude ranges
+# INST_MAG_MAX         -13     # optional constraints on magnitude ranges
+
+RELPHOT_GRID_BINNING    256
+RELPHOT_GRID_X 6
+RELPHOT_GRID_Y 14
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/format_split.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/format_split.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/format_split.config	(revision 22157)
@@ -0,0 +1,124 @@
+# Simulation test camera with single chip with single cell
+
+# How to identify this type
+RULE	METADATA
+	TELESCOP	STR	SimScope
+	INSTRUME	STR	SIMMOSAIC
+	FORMAT		STR	SPLIT
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	CHIP		# The FITS file represents an entire FPA
+	EXTENSIONS	STR	CELL		# There are no extensions
+	FPA.OBS		STR	FPANAME		# A PHU keyword for unique FPA identifier
+	CONTENT	        STR     CHIPNAME	# How to determine content of FITS file
+	CONTENT.RULE	STR	{CHIP.NAME}	# How to derive the CONTENT when writing
+END
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# File identifier with corresponding file layout name
+	Chip00		STR	Chip00:SimChip
+	Chip01		STR	Chip01:SimChip
+	Chip10		STR	Chip10:SimChip
+	Chip11		STR	Chip11:SimChip
+END
+
+# Specify the layout
+CHIPS	METADATA
+	SimChip		METADATA
+		# Extension name, cellName:cellType
+		Cell00	STR	Cell00:SimCell
+		Cell01	STR	Cell01:SimCell
+		Cell10	STR	Cell10:SimCell
+		Cell11	STR	Cell11:SimCell
+	END
+END
+
+# Specify the cell data
+CELLS	METADATA
+	SimCell		METADATA
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC		STR	TRIMSEC
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BIASSEC
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.TELESCOPE	STR	TELESCOP
+	FPA.INSTRUMENT	STR	INSTRUME
+	FPA.OBSTYPE	STR	OBSTYPE
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.FILTER	STR	FILTER
+	FPA.FILTERID	STR	FILTER
+	FPA.POSANGLE	STR	POSANGLE
+	FPA.RA		STR	RA
+	FPA.DEC		STR	DEC
+	FPA.OBJECT	STR	OBJECT
+	FPA.TIME	STR	MJD-OBS
+	FPA.EXPOSURE	STR	EXPTIME
+	CELL.EXPOSURE	STR	EXPTIME
+	CELL.DARKTIME	STR	DARKTIME
+	CELL.TIME	STR	MJD-OBS
+	CELL.XBIN	STR	XBIN
+	CELL.YBIN	STR	YBIN
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	FPA.DETECTOR	STR	SIMMOSAIC
+	FPA.TIMESYS	STR	UTC
+	FPA.RADECSYS	STR	ICRS
+	CELL.SATURATION	F32	65535
+	CELL.BAD	F32	0
+	CELL.READDIR	S32	1
+	CELL.TIMESYS	STR	UTC
+	CELL.XPARITY	S32	1
+	CELL.YPARITY	S32	1
+	CHIP.XPARITY	S32	1
+	CHIP.YPARITY	S32	1
+	CELL.X0.DEPEND	STR	CELL.NAME
+	CELL.X0		METADATA
+		Cell00	S32	0
+		Cell01	S32	0
+		Cell10	S32	1024
+		Cell11	S32	1024
+	END
+	CELL.Y0.DEPEND	STR	CELL.NAME
+	CELL.Y0		METADATA
+		Cell00	S32	0
+		Cell01	S32	1024
+		Cell10	S32	0
+		Cell11	S32	1024
+	END
+	CHIP.X0.DEPEND	STR	CHIP.NAME
+	CHIP.X0		METADATA
+		Chip00	S32	0
+		Chip01	S32	0
+		Chip10	S32	2072
+		Chip11	S32	2072
+	END
+	CHIP.Y0.DEPEND	STR	CHIP.NAME
+	CHIP.Y0		METADATA
+		Chip00	S32	0
+		Chip01	S32	2072
+		Chip10	S32	0
+		Chip11	S32	2072
+	END
+	CELL.XSIZE	S32	1000
+	CELL.YSIZE	S32	1000
+	CELL.XWINDOW	S32	0
+	CELL.YWINDOW	S32	0
+	CELL.GAIN	F32	1.0
+	CELL.READNOISE	F32	10.0
+END
+
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/format_together.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/format_together.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/format_together.config	(revision 22157)
@@ -0,0 +1,119 @@
+# Simulation test camera with single chip with single cell
+
+# How to identify this type
+RULE	METADATA
+	TELESCOP	STR	SimScope
+	INSTRUME	STR	SIMMOSAIC
+	FORMAT		STR	TOGETHER
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA		# The FITS file represents an entire FPA
+	EXTENSIONS	STR	CHIP		# There are no extensions
+	FPA.OBS		STR	FPANAME		# A PHU keyword for unique FPA identifier
+	CONTENT	        STR     CHIPNAME	# How to determine content of FITS file
+END
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# Extension name ---> chip_name:chip_type
+	Chip00		STR	Chip00:SimChip
+	Chip01		STR	Chip01:SimChip
+	Chip10		STR	Chip10:SimChip
+	Chip11		STR	Chip11:SimChip
+END
+
+# Specify the layout
+CHIPS	METADATA
+	# Chip type ---> cell_name:cell_type
+	SimChip		STR	Cell00:SimCell Cell01:SimCell Cell10:SimCell Cell11:SimCell
+END
+
+
+# Specify the cell data
+CELLS	METADATA
+	SimCell		METADATA
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC		STR	TRIMSEC
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BIASSEC
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.TELESCOPE	STR	TELESCOP
+	FPA.INSTRUMENT	STR	INSTRUME
+	FPA.OBSTYPE	STR	OBSTYPE
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.FILTER	STR	FILTER
+	FPA.FILTERID	STR	FILTER
+	FPA.POSANGLE	STR	POSANGLE
+	FPA.RA		STR	RA
+	FPA.DEC		STR	DEC
+	FPA.OBJECT	STR	OBJECT
+	FPA.TIME	STR	MJD-OBS
+	FPA.EXPOSURE	STR	EXPTIME
+	CELL.EXPOSURE	STR	EXPTIME
+	CELL.DARKTIME	STR	DARKTIME
+	CELL.TIME	STR	MJD-OBS
+	CELL.XBIN	STR	XBIN
+	CELL.YBIN	STR	YBIN
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	FPA.DETECTOR	STR	SIMMOSAIC
+	FPA.TIMESYS	STR	UTC
+	FPA.RADECSYS	STR	ICRS
+	CELL.SATURATION	F32	65535
+	CELL.BAD	F32	0
+	CELL.READDIR	S32	1
+	CELL.TIMESYS	STR	UTC
+	CELL.XPARITY	S32	1
+	CELL.YPARITY	S32	1
+	CHIP.XPARITY	S32	1
+	CHIP.YPARITY	S32	1
+	CELL.X0.DEPEND	STR	CELL.NAME
+	CELL.X0		METADATA
+		Cell00	S32	0
+		Cell01	S32	0
+		Cell10	S32	1024
+		Cell11	S32	1024
+	END
+	CELL.Y0.DEPEND	STR	CELL.NAME
+	CELL.Y0		METADATA
+		Cell00	S32	0
+		Cell01	S32	1024
+		Cell10	S32	0
+		Cell11	S32	1024
+	END
+	CHIP.X0.DEPEND	STR	CHIP.NAME
+	CHIP.X0		METADATA
+		Chip00	S32	0
+		Chip01	S32	0
+		Chip10	S32	2072
+		Chip11	S32	2072
+	END
+	CHIP.Y0.DEPEND	STR	CHIP.NAME
+	CHIP.Y0		METADATA
+		Chip00	S32	0
+		Chip01	S32	2072
+		Chip10	S32	0
+		Chip11	S32	2072
+	END
+	CELL.XSIZE	S32	1000
+	CELL.YSIZE	S32	1000
+	CELL.XWINDOW	S32	0
+	CELL.YWINDOW	S32	0
+	CELL.GAIN	F32	1.0
+	CELL.READNOISE	F32	10.0
+END
+
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/ppImage.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/ppImage.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/ppImage.config	(revision 22157)
@@ -0,0 +1,222 @@
+### ppImage recipe configuration file for simulated test camera
+
+# List of tasks to perform
+
+BASE.FITS       BOOL    FALSE           # Save base detrended image?
+CHIP.FITS       BOOL    TRUE            # Save chip-mosaicked image?
+BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+
+# binned output image options
+BIN1.XBIN               S32     8
+BIN1.YBIN               S32     8
+BIN2.XBIN               S32     64
+BIN2.YBIN               S32     64
+
+# Overscan subtraction
+OVERSCAN.SINGLE         BOOL    FALSE           # Reduce overscan to a single value?
+OVERSCAN.FIT            STR     POLYNOMIAL      # NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER          S32     7               # Order of polynomial fit
+OVERSCAN.STAT           STR     MEAN            # MEAN | MEDIAN
+
+# How to select the appropriate detrend image
+DETREND.CONSTRAINTS  METADATA
+  BIAS METADATA
+  END
+  MASK METADATA
+  END
+  DARK METADATA
+  END
+  FLAT METADATA
+    FILTER  STR FPA.FILTER
+  END
+  FRINGE METADATA
+    FILTER   STR FPA.FILTER
+  END
+  SHUTTER METADATA
+  END   
+END
+
+########################################################################################
+# Need the following in order to turn off overscan and shutter for the site-level recipe
+########################################################################################
+
+# Overscan subtraction only
+PPIMAGE_O         METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Dark subtraction only
+PPIMAGE_D         METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Shutter correction only
+PPIMAGE_S         METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark
+PPIMAGE_OBD      METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter
+PPIMAGE_OBDS      METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field
+PPIMAGE_OBDSF     METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe
+PPIMAGE_OBDSFR    METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom
+PPIMAGE_OBDSFRP   METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_OBDSFRA   METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/psastro.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/psastro.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/psastro.config	(revision 22157)
@@ -0,0 +1,4 @@
+PSASTRO.CATDIR	STR	SYNTH.SIMTEST
+
+#PSASTRO.MOSAIC.GRADIENT.NX    S32     1   # number of x-dir cells per chip
+#PSASTRO.MOSAIC.GRADIENT.NY    S32     1   # number of y-dir cells per chip
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/psphot.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/psphot.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/psphot.config	(revision 22157)
@@ -0,0 +1,30 @@
+
+SAVE.BACKMDL	BOOL 	TRUE
+SAVE.BACKMDL.STDEV BOOL 	TRUE
+SAVE.BACKGND	BOOL 	TRUE
+SAVE.BACKSUB	BOOL 	TRUE
+SAVE.PLOTS      BOOL    FALSE
+SAVE.RESID	BOOL 	TRUE
+SAVE.PSF	BOOL 	TRUE
+LOAD.PSF	BOOL 	FALSE
+
+IMSTATS_NPIX        S32  3000    	 # number of pixels to use for sky estimate boxes:
+
+#PSPHOT_TEST METADATA
+# SAVE.BACKSUB	BOOL 	FALSE
+# SAVE.RESID	BOOL 	TRUE
+#END
+#
+#PEAKS_OUTPUT_FILE   STR  peaks.dat
+#MOMENTS_OUTPUT_FILE STR  moments.dat
+
+#PSF_SN_LIM          F32  20.0            # minimum S/N for stars used for PSF model
+MOMENTS_SN_MIN      F32  20.0            # min S/N to measure moments
+FULL_FIT_SN_LIM     F32  100.0
+EXT_MIN_SN          F32  100.0           # fit galaxies above this S/N limit
+
+PEAKS_NSIGMA_LIMIT  F32  10.0 	   	 # peak significance threshold
+BREAK_POINT         STR  ENSEMBLE        # limit total processing for now (for speed)
+
+
+SKY_STAT            STR  ROBUST_MEDIAN   # statistic used to measure background
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/rejections.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/rejections.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simmosaic/rejections.config	(revision 22157)
@@ -0,0 +1,49 @@
+
+DARK METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  2.0
+  IMFILE.STDEV       F32  0.0
+  IMFILE.MEANSTDEV   F32  0.0
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  1.0
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  1.0
+  EXP.STDEV          F32  0.0
+  EXP.MEANSTDEV      F32  0.5
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  3.0
+  ENSEMBLE.STDEV     F32  3.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+
+SHUTTER METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  0.0
+  IMFILE.STDEV       F32  0.0
+  IMFILE.MEANSTDEV   F32  0.0
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.0
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  0.0
+  EXP.STDEV          F32  0.0
+  EXP.MEANSTDEV      F32  0.0
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  0.0
+  ENSEMBLE.STDEV     F32  0.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simple/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simple/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simple/.cvsignore	(revision 22157)
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simple/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simple/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simple/Makefile.am	(revision 22157)
@@ -0,0 +1,18 @@
+
+installdir = $(datadir)/ippconfig/simple
+
+install_files = \
+	camera.config \
+	format.config \
+	ppImage.config \
+	psastro.config \
+	psphot.config
+
+install_DATA = $(install_files)
+
+install-data-hook:
+	chmod 0755 $(installdir)
+
+EXTRA_DIST = $(install_files)
+
+ACLOCAL_AMFLAGS = -I m4
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simple/camera.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simple/camera.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simple/camera.config	(revision 22157)
@@ -0,0 +1,64 @@
+# Camera configuration file for drop-dead boring camera, consisting of a single chip with single cell.
+
+# File formats that we know about
+FORMATS         METADATA
+	SIMPLE	STR	simple/format.config
+END
+
+# Description of camera --- all the chips and the cells that comprise them
+FPA     METADATA
+        Chip		STR	Cell
+END
+
+# move this elsewhere?  we need a lookup table to go from filter ID to abstract name
+FILTER.ID       METADATA
+	B	STR	B
+	V	STR	V
+	R	STR	R
+	I	STR	I
+	z	STR	z
+END
+
+# Table of strings to use for the class identifier (see ippdb) according to the file level.
+CLASSID		METADATA
+	FPA	STR	fpa
+END
+
+DVO.CAMERADIR	STR	megacam		# Camera directory for DVO
+
+# convert supplied FPA.OBSTYPE values to abstract exptype names
+OBSTYPE.TABLE METADATA
+  bias 	   STR BIAS
+  zero 	   STR BIAS
+  dark 	   STR DARK
+  flat 	   STR SKYFLAT
+  skyflat  STR SKYFLAT
+  domeflat STR DOMEFLAT
+  object   STR OBJECT
+  science  STR OBJECT
+END
+
+# Recipe options
+RECIPES		METADATA
+        PPIMAGE         STR     simple/ppImage.config   # Default: all (normal) options on
+	PSPHOT		STR	simple/psphot.config	# psphot details
+END
+
+# reduction classes (recipes which are grouped together)
+REDUCTION	STR	recipes/reductionClasses.mdc
+
+FITSTYPES       STR     recipes/fitstypes.mdc
+
+FILERULES       STR     recipes/filerules-simple.mdc
+
+EXTNAME.RULES METADATA
+  CMF.HEAD STR hdr
+  CMF.DATA STR psf
+  CMF.XSRC STR xsrc
+  CMF.XFIT STR xfit
+
+  PSF.HEAD  STR hdr
+  PSF.TABLE STR psf_model
+  PSF.RESID STR psf_resid
+END
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simple/format.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simple/format.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simple/format.config	(revision 22157)
@@ -0,0 +1,75 @@
+# Drop-dead simple camera with single chip with single cell
+
+# How to identify this type
+RULE	METADATA
+	SIMPLE		BOOL	TRUE
+###	NAXIS		S32	2
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	NONE	# There are no extensions
+	FPA.OBS		STR	NAXIS	# A PHU keyword for unique identifier within the hierarchy level
+END
+
+# What's in the FITS file?
+CONTENTS	STR	Chip:Cell:amplifier
+
+# Specify the cell data
+CELLS	METADATA
+	amplifier	METADATA
+		CELL.TRIMSEC.SOURCE	STR	VALUE
+		CELL.TRIMSEC		STR	[0:0,0:0]
+		# alternatives for getting these from the headers
+		# CELL.TRIMSEC.SOURCE	STR	HEADER
+		# CELL.BIASSEC.SOURCE	STR	VALUE
+		# CELL.TRIMSEC		STR	DATASEC
+		# CELL.BIASSEC		STR	BIASSEC
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.TELESCOPE	STR	TELESCOP
+	FPA.INSTRUMENT	STR	INSTRUME
+	FPA.OBSTYPE	STR	OBSTYPE
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.FILTER	STR	FILTER
+	FPA.POSANGLE	STR	POSANGLE
+	FPA.RA		STR	RA
+	FPA.DEC		STR	DEC
+	FPA.OBJECT	STR	OBJECT
+	FPA.TIME	STR	DATE-OBS UTC-OBS	# Date and time
+	FPA.EXPOSURE	STR	EXPTIME
+	CELL.EXPOSURE	STR	EXPTIME
+	CELL.DARKTIME	STR	DARKTIME
+#	CELL.TIME	STR	DATE-OBS TIME-OBS	# Date and time
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	CELL.XBIN	S32	1
+	CELL.YBIN	S32	1
+	FPA.TIMESYS	STR	UTC
+	FPA.RADECSYS	STR	ICRS
+	CELL.GAIN	F32	1.0
+	CELL.READNOISE	F32	2.0
+	CELL.BAD	F32	0
+	CELL.SATURATION	F32	65535
+#	CELL.BAD	F32	-65535
+	CELL.READDIR	S32	1
+	CELL.TIMESYS	STR	UTC
+	CELL.XPARITY	S32	1
+	CELL.YPARITY	S32	1
+	CHIP.XPARITY	S32	1
+	CHIP.YPARITY	S32	1
+	CELL.X0		S32	0
+	CELL.Y0		S32	0
+END
+
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	SEPARATE,YEAR.FIRST
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simple/ppImage.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simple/ppImage.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simple/ppImage.config	(revision 22157)
@@ -0,0 +1,120 @@
+### ppImage recipe configuration file for drop-dead simple camera
+
+# List of tasks to perform
+SHUTTER         BOOL    FALSE           # Shutter correction
+DARK		BOOL	TRUE		# Dark correction
+
+BASE.FITS       BOOL    TRUE            # Save base detrended image?
+BIN1.FITS	BOOL    TRUE            # Save 1st binned chip image?
+BIN2.FITS	BOOL    TRUE		# Save 2nd binned chip image?
+
+# binned output image options
+BIN1.XBIN               S32     8
+BIN1.YBIN               S32     8
+BIN2.XBIN               S32     64
+BIN2.YBIN               S32     64
+
+# Overscan subtraction
+OVERSCAN.SINGLE		BOOL	FALSE		# Reduce overscan to a single value?
+OVERSCAN.FIT		STR	POLYNOMIAL	# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER		S32	3		# Order of polynomial fit
+OVERSCAN.STAT		STR	MEAN		# MEAN | MEDIAN
+
+# How to select the appropriate detrend image
+DETREND.CONSTRAINTS  METADATA
+  BIAS METADATA
+  END
+  MASK METADATA
+  END
+  DARK METADATA
+  END
+  FLAT METADATA
+    FILTER  STR FPA.FILTER
+  END
+  FRINGE METADATA
+    FILTER   STR FPA.FILTER
+  END
+  SHUTTER METADATA
+  END	
+END
+
+########################################################################################
+# Need the following in order to turn off overscan and shutter for the site-level recipe
+########################################################################################
+
+# Overscan subtraction only
+PPIMAGE_O         METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+END
+
+# Dark subtraction only
+PPIMAGE_D         METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+END
+
+# Shutter correction only
+PPIMAGE_S         METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE            # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+END
+
+# Overscan, bias, dark
+PPIMAGE_OBD      METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+END
+
+# Overscan, bias, dark, shutter
+PPIMAGE_OBDS      METADATA
+  OVERSCAN        BOOL    FALSE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE            # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+END
+
+# Overscan, bias, dark, shutter, flat-field
+PPIMAGE_OBDSF     METADATA
+  OVERSCAN        BOOL    FALSE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE            # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe
+PPIMAGE_OBDSFR    METADATA
+  OVERSCAN        BOOL    FALSE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    TRUE            # Fringe subtraction
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simple/psastro.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simple/psastro.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simple/psastro.config	(revision 22157)
@@ -0,0 +1,36 @@
+
+# astrometry matching parameters
+
+# nominal plate scale (microns / pixel)
+# PSASTRO.PLATE.SCALE    F32  13.5
+PSASTRO.PLATE.SCALE    F32  1.0
+
+# pmAstromGridMatch:
+PSASTRO.GRID.MIN.ANGLE F32 -0.0851
+PSASTRO.GRID.MAX.ANGLE F32 +0.0851
+PSASTRO.GRID.DEL.ANGLE F32  0.0170
+
+# pmAstromGridAngle
+# max grid offset in FP units (microns)
+# use plate-scale to make this in pixels?
+# PSASTRO.GRID.OFFSET    F32  10000.
+# PSASTRO.GRID.SCALE     F32    500.
+PSASTRO.GRID.OFFSET    F32   1000.
+PSASTRO.GRID.SCALE     F32     50
+
+# these tweak are in FP units (pixels, currently)
+PSASTRO.TWEAK.SCALE     F32      1
+PSASTRO.TWEAK.RANGE     F32     75
+PSASTRO.TWEAK.SMOOTH    F32      2
+PSASTRO.TWEAK.NSIGMA    F32      3
+
+# pmAstromRadiusMatch
+# use plate-scale to make this in pixels?
+PSASTRO.MATCH.RADIUS   F32    8
+
+# pmAstromMatchFit
+PSASTRO.CHIP.NX        S32      1  # fit order
+PSASTRO.CHIP.NY        S32      1  # fit order
+PSASTRO.CHIP.NITER     S32      3  # fit clipping iterations
+PSASTRO.CHIP.NSIGMA    F32      3  # fit clipping sigmas
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simple/psphot.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simple/psphot.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simple/psphot.config	(revision 22157)
@@ -0,0 +1,16 @@
+
+SAVE.BACKMDL	BOOL 	FALSE
+SAVE.BACKGND	BOOL 	FALSE
+SAVE.BACKSUB	BOOL 	FALSE
+SAVE.PLOTS      BOOL    TRUE
+SAVE.RESID	BOOL 	FALSE
+SAVE.PSF	BOOL 	TRUE
+LOAD.PSF	BOOL 	FALSE
+
+IMSTATS_NPIX        S32  3000    	 # number of pixels to use for sky estimate boxes:
+
+MOMENTS_SN_MIN      F32  20.0           # min S/N to measure moments
+FULL_FIT_SN_LIM     F32  100.0
+EXT_MIN_SN          F32  100.0           # fit galaxies above this S/N limit
+
+PEAKS_NSIGMA_LIMIT  F32  10.0 	   	 # peak significance threshold
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/.cvsignore	(revision 22157)
@@ -0,0 +1,2 @@
+Makefile.in
+Makefile
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/Makefile.am	(revision 22157)
@@ -0,0 +1,22 @@
+
+installdir = $(datadir)/ippconfig/simtest
+
+install_files = \
+	camera.config \
+	format.config \
+	ppImage.config \
+	psastro.config \
+	psphot.config \
+	rejections.config \
+	dvo.config \
+	ppSim.config \
+	ppSub.config
+
+install_DATA = $(install_files)
+
+install-data-hook:
+	chmod 0755 $(installdir)
+
+EXTRA_DIST = $(install_files)
+
+ACLOCAL_AMFLAGS = -I m4
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/camera.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/camera.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/camera.config	(revision 22157)
@@ -0,0 +1,73 @@
+# Camera configuration file for simulation test
+
+# File formats that we know about
+FORMATS         METADATA
+	SIMPLE	STR	simtest/format.config
+END
+ 
+# Description of camera --- all the chips and the cells that comprise them
+FPA     METADATA
+        Chip		STR	Cell
+END
+
+# valid filter names and corresponding IDs
+FILTER.ID       METADATA
+	NONE	STR	NONE
+	g	STR	g
+	r	STR	r
+	i	STR	i
+	z	STR	z
+END
+
+# Table of strings to use for the class identifier (see ippdb) according to the file level.
+CLASSID		METADATA
+	FPA	STR	fpa
+END
+
+DVO.CAMERADIR	STR	simtest		# Camera directory for DVO
+
+# convert supplied FPA.OBSTYPE values to abstract exptype names
+OBSTYPE.TABLE METADATA
+  bias 	   STR BIAS
+  zero 	   STR BIAS
+  dark 	   STR DARK
+  flat 	   STR SKYFLAT
+  skyflat  STR SKYFLAT
+  domeflat STR DOMEFLAT
+  object   STR OBJECT
+  science  STR OBJECT
+END
+
+# Recipe options
+RECIPES		METADATA
+        PPIMAGE         STR     simtest/ppImage.config   # Default: all (normal) options on
+	PSPHOT		STR	simtest/psphot.config	# psphot details
+	PPSIM		STR	simtest/ppSim.config	# ppSim details
+	PPSUB		STR	simtest/ppSub.config	# ppSub details
+	PSASTRO		STR	simtest/psastro.config	# psastro details
+	REJECTIONS	STR	simtest/rejections.config # Rejection for detrend creation
+END
+
+# reduction classes (recipes which are grouped together)
+REDUCTION	STR	recipes/reductionClasses.mdc
+
+FITSTYPES       STR     recipes/fitstypes.mdc
+
+FILERULES	STR	recipes/filerules-simple.mdc
+
+EXTNAME.RULES METADATA
+  CMF.HEAD STR {CHIP.NAME}.hdr
+  CMF.DATA STR {CHIP.NAME}.psf # use .PSF and .EXT?
+  CMF.XSRC STR {CHIP.NAME}.xsrc # use .PSF and .EXT?
+  CMF.XFIT STR {CHIP.NAME}.xfit # use .PSF and .EXT?
+
+  PSF.HEAD  STR	{CHIP.NAME}.hdr
+  PSF.TABLE STR {CHIP.NAME}.psf_model
+  PSF.RESID STR {CHIP.NAME}.psf_resid
+END
+
+BLANK.HEADERS	METADATA
+	FPA.TIME	STR	MJD-OBS
+	FPA.EXPOSURE	STR	EXPTIME
+	FPA.AIRMASS	STR	AIRMASS
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/dvo.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/dvo.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/dvo.config	(revision 22157)
@@ -0,0 +1,59 @@
+
+# keywords used by DVO to interpret the headers
+
+# keyword abstractions:
+DATE-KEYWORD		DATE-OBS
+UT-KEYWORD		UTC-OBS
+DATE-MODE		YYYY-MM-DD
+JD-KEYWORD		NONE
+MJD-KEYWORD		NONE
+
+# other keyword abstractions
+EXPTIME-KEYWORD		EXPTIME
+AIRMASS-KEYWORD		AIRMASS
+CCDNUM-KEYWORD		EXTNAME
+ST-KEYWORD		NONE
+OBSERVATORY-LATITUDE	NONE
+OBSERVATORY-LONGITUDE	NONE
+SUBPIX_DATAFILE		NONE
+
+# instrumental magnitude range for calibration mode 
+CAL_INSTMAG_MAX		0
+CAL_INSTMAG_MIN		0
+
+# exclude overscan region from the dB image boundaries
+XOVERSCAN		0
+YOVERSCAN		0
+
+# only upload stars within region; a value of 0 means ignore the limit
+ADDSTAR_XMIN		0
+ADDSTAR_XMAX		0
+ADDSTAR_YMIN		0
+ADDSTAR_YMAX		0
+
+# exclude stars with SN > SNLIMIT (ADDSTAR_SNLIMIT overrides old name MIN_SN_FSTAT)
+ADDSTAR_SNLIMIT		0
+
+# correlation radius (arcseconds)
+ADDSTAR_RADIUS		1.0
+
+# scaled correlation radius 
+ADDSTAR_NSIGMA		0
+
+IMAGE_SCATTER           0.075  # mark images POOR if stdev(Mcal) > IMAGE_SCATTER
+STAR_SCATTER            0.005  # mark stars POOR if stdev(Mrel) > STAR_SCATTER
+IMAGE_OFFSET            0.100  # mark images POOR if abs(delta(Mcal)) > IMAGE_OFFSET
+STAR_CHISQ              10.0   # mark stars POOR if Xm > STAR_CHISQ
+STAR_TOOFEW              3     # mark star FEW if N(good) < STAR_TOOFEW
+IMAGE_TOOFEW            10     # mark image FEW if N(good) < IMAGE_TOOFEW
+IMAGE_GOOD_FRACTION     0.05   # mark image FEW if N(good) < IMAGE_GOOD_FRACTION * Nstars
+
+SCATTER_LIM             15.0
+MAG_LIM                 20.0   # select stars brighter than this for relphot analysis
+SIGMA_LIM               0.015  # select measurements with dM < SIGMA_LIM for relphot analysis
+# INST_MAG_MIN         -17     # optional constraints on magnitude ranges
+# INST_MAG_MAX         -13     # optional constraints on magnitude ranges
+
+RELPHOT_GRID_BINNING    256
+RELPHOT_GRID_X 6
+RELPHOT_GRID_Y 14
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/format.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/format.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/format.config	(revision 22157)
@@ -0,0 +1,79 @@
+# Simulation test camera with single chip with single cell
+
+# How to identify this type
+RULE	METADATA
+	INSTRUME	STR	SIMTEST
+	TELESCOP	STR	SimScope
+#	NAXIS		S32	2
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	NONE	# There are no extensions
+	FPA.OBS		STR	OBJECT	# A PHU keyword for unique identifier within the hierarchy level
+END
+
+# What's in the FITS file?
+CONTENTS	STR	Chip:Cell:amplifier
+
+# Specify the cell data
+CELLS	METADATA
+	amplifier	METADATA
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC		STR	TRIMSEC
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BIASSEC
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.TELESCOPE	STR	TELESCOP
+	FPA.INSTRUMENT	STR	INSTRUME
+	FPA.OBSTYPE	STR	OBSTYPE
+	FPA.AIRMASS	STR	AIRMASS
+        FPA.FILTERID    STR     FILTER
+	FPA.FILTER	STR	FILTER
+	FPA.POSANGLE	STR	POSANGLE
+	FPA.RA		STR	RA
+	FPA.DEC		STR	DEC
+	FPA.OBJECT	STR	OBJECT
+	FPA.TIME	STR	DATE-OBS UTC-OBS	# Date and time
+	FPA.EXPOSURE	STR	EXPTIME
+	CELL.EXPOSURE	STR	EXPTIME
+	CELL.DARKTIME	STR	DARKTIME
+	CELL.TIME	STR	DATE-OBS UTC-OBS	# Date and time
+	CELL.XBIN	STR	XBIN
+	CELL.YBIN	STR	YBIN
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	FPA.TIMESYS	STR	UTC
+	FPA.RADECSYS	STR	ICRS
+	FPA.DETECTOR	STR	SIMTEST
+	CELL.SATURATION	F32	65535
+	CELL.BAD	F32	-100 ## XXX this value is bogus, but needs to be context based so residual images do not get half-masked
+	CELL.READDIR	S32	1
+	CELL.TIMESYS	STR	UTC
+	CELL.XPARITY	S32	1
+	CELL.YPARITY	S32	1
+	CHIP.XPARITY	S32	1
+	CHIP.YPARITY	S32	1
+	CELL.X0		S32	0
+	CELL.Y0		S32	0
+	CELL.XSIZE	S32	2048
+	CELL.YSIZE	S32	2048
+	CELL.XWINDOW	S32	0
+	CELL.YWINDOW	S32	0
+	CELL.GAIN	F32	1.0
+	CELL.READNOISE	F32	10.0
+END
+
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	SEPARATE,YEAR.FIRST
+	CELL.TIME	STR	SEPARATE,YEAR.FIRST
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/ppImage.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/ppImage.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/ppImage.config	(revision 22157)
@@ -0,0 +1,225 @@
+### ppImage recipe configuration file for simulated test camera
+
+# List of tasks to perform
+
+BASE.FITS       BOOL    TRUE            # Save base detrended image?
+BIN1.FITS	BOOL    TRUE            # Save 1st binned chip image?
+BIN2.FITS	BOOL    TRUE		# Save 2nd binned chip image?
+
+# binned output image options
+BIN1.XBIN               S32     8
+BIN1.YBIN               S32     8
+BIN2.XBIN               S32     64
+BIN2.YBIN               S32     64
+
+# Overscan subtraction
+OVERSCAN.SINGLE		BOOL	FALSE		# Reduce overscan to a single value?
+OVERSCAN.FIT		STR	POLYNOMIAL	# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER		S32	7		# Order of polynomial fit
+OVERSCAN.STAT		STR	MEAN		# MEAN | MEDIAN
+
+# How to select the appropriate detrend image
+DETREND.CONSTRAINTS  METADATA
+  BIAS METADATA
+  END
+  MASK METADATA
+  END
+  DARK METADATA
+  END
+  FLAT METADATA
+    FILTER  STR FPA.FILTER
+  END
+  FRINGE METADATA
+    FILTER   STR FPA.FILTER
+  END
+  SHUTTER METADATA
+  END	
+END
+
+# Photometry code
+PHOTCODE.RULE           STR     {DETECTOR}.{FILTER.ID}
+
+########################################################################################
+# Need the following in order to turn off overscan and shutter for the site-level recipe
+########################################################################################
+
+# Overscan subtraction only
+PPIMAGE_O         METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
+
+# Dark subtraction only
+PPIMAGE_D         METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
+
+# Shutter correction only
+PPIMAGE_S         METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE            # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark
+PPIMAGE_OBD      METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter
+PPIMAGE_OBDS      METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field
+PPIMAGE_OBDSF     METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe
+PPIMAGE_OBDSFR    METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom
+PPIMAGE_OBDSFRP   METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    TRUE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_OBDSFRA   METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    TRUE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    TRUE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+  CHIP.MASK.FITS  BOOL    TRUE            # Save mask
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/ppSim.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/ppSim.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/ppSim.config	(revision 22157)
@@ -0,0 +1,2 @@
+### Recipe values for ppSim, the Pan-STARRS data simulator
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/ppSub.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/ppSub.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/ppSub.config	(revision 22157)
@@ -0,0 +1,25 @@
+### Recipe file for ppSub
+
+KERNEL.TYPE	STR	RINGS		# Kernel type to use (POIS|ISIS|SPAM|FRIES|GUNK|RINGS)
+KERNEL.SIZE     S32	15		# Kernel half-size (pixels)
+SPATIAL.ORDER   S32	1 		# Spatial polynomial order
+REGION.SIZE	F32	0		# Iso-kernel region size (pixels)
+STAMP.SPACING   F32	200		# Typical spacing between stamps (pixels)
+STAMP.FOOTPRINT S32	35		# Size of stamps (pixels)
+STAMP.THRESHOLD F32	0		# Flux threshold for stamps (ADU)
+ITER            S32	10		# Number of rejection iterations
+REJ             F32	2.5		# Rejection level (std dev)
+MASK.BAD        STR	BLANK,BAD,SAT	# Mask value for bad pixels
+MASK.BLANK      STR	BLANK		# Mask value to give blank pixels
+BADFRAC		F32	0.8		# Maximum fraction of bad pixels
+@ISIS.WIDTHS	F32	1 3 5		# Gaussian FWHMs for ISIS kernels
+@ISIS.ORDERS	S32	2 2 2		# Polynomial orders for ISIS kernels
+INNER		S32	5		# Inner half-size for SPAM and FRIES kernels
+RINGS.ORDER	S32	2		# Polynomial order for RINGS kernels
+PENALTY		F32	1.0		# Penalty for wideness
+
+### Modifications to use when stacking data
+STACK	METADATA
+	KERNEL.TYPE	STR	RINGS	# Kernel type to use (POIS|ISIS|SPAM|FRIES|GUNK|RINGS)
+	KERNEL.SIZE     S32     15      # Kernel half-size (pixels)
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/psastro.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/psastro.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/psastro.config	(revision 22157)
@@ -0,0 +1,3 @@
+PSASTRO.CATDIR		STR	SYNTH.SIMTEST
+DVO.GETSTAR.PHOTCODE	STR	r
+PSASTRO.MAX.NSTAR	S32	50	# max stars accepted for fitting
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/psphot.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/psphot.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/psphot.config	(revision 22157)
@@ -0,0 +1,30 @@
+
+SAVE.BACKMDL	BOOL 	TRUE
+SAVE.BACKGND	BOOL 	TRUE
+SAVE.BACKSUB	BOOL 	TRUE
+SAVE.PLOTS      BOOL    TRUE
+SAVE.RESID	BOOL 	TRUE
+SAVE.PSF	BOOL 	TRUE
+LOAD.PSF	BOOL 	FALSE
+
+IMSTATS_NPIX        S32  3000    	 # number of pixels to use for sky estimate boxes:
+
+#PSPHOT_TEST METADATA
+# SAVE.BACKSUB	BOOL 	FALSE
+# SAVE.RESID	BOOL 	TRUE
+#END
+#
+#PEAKS_OUTPUT_FILE   STR  peaks.dat
+#MOMENTS_OUTPUT_FILE STR  moments.dat
+
+#PSF_SN_LIM          F32  20.0            # minimum S/N for stars used for PSF model
+MOMENTS_SN_MIN      F32  10.0            # min S/N to measure moments
+FULL_FIT_SN_LIM     F32  100.0
+EXT_MIN_SN          F32  100.0           # fit galaxies above this S/N limit
+
+PEAKS_NSIGMA_LIMIT   F32  25.0 	 	 # peak significance threshold
+PEAKS_NSIGMA_LIMIT_2 F32  10.0 	   	 # peak significance threshold
+BREAK_POINT         STR  NONE            # limit total processing for now (for speed)
+
+
+SKY_STAT            STR  ROBUST_MEDIAN   # statistic used to measure background
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/rejections.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/rejections.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/simtest/rejections.config	(revision 22157)
@@ -0,0 +1,78 @@
+
+BIAS METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  2.0 # 2 sigma from expected mean
+  IMFILE.STDEV       F32 20.0 # per-image sigma of < 20.0 (supplied read-noise is 10.0)
+  IMFILE.MEANSTDEV   F32  0.0 
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.5 # binned stdev < 0.5 (binning is 64x64 : 10.0 / 64.0 = 0.15)
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  1.0 
+  EXP.STDEV          F32 20.0
+  EXP.MEANSTDEV      F32  0.0
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  5.0
+  ENSEMBLE.STDEV     F32  5.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+DARK METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  2.0
+  IMFILE.STDEV       F32  0.0
+  IMFILE.MEANSTDEV   F32  0.0
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  1.0
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  1.0
+  EXP.STDEV          F32  0.0
+  EXP.MEANSTDEV      F32  0.5
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  5.0
+  ENSEMBLE.STDEV     F32  5.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+
+SHUTTER METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  0.0
+  IMFILE.STDEV       F32  0.0
+  IMFILE.MEANSTDEV   F32  0.0
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.0
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  0.0
+  EXP.STDEV          F32  0.0
+  EXP.MEANSTDEV      F32  0.0
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  0.0
+  ENSEMBLE.STDEV     F32  0.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+
+ITERATION	BOOL	FALSE		# Are iterations permitted?
+MASTER		BOOL	TRUE		# Force stacks to be accepted as a master?
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/site.config.in
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/site.config.in	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/site.config.in	(revision 22157)
@@ -0,0 +1,62 @@
+## Site Configuration : use this as an example for your installation
+## Supply locations of interest
+
+# place your data directories here and refer to as path://PATH/remainder
+DATAPATH        METADATA
+        ALALA   STR     /data/alala.0/
+        ISP     STR     /data/ipp000.0/isp
+        ISP_V2  STR     /data/ipp000.0/isp_v2
+        ISP_V3  STR     /data/ipp000.0/isp_v3
+        ISP_TEST STR    /data/ipp000.0/isp_test
+        CFH12K  STR     /data/ipp003.0/eugene/cfh12k
+
+        SRWORK  STR     /data/ipp003.0/rodney/WORK
+        SRDATA  STR     /data/ipp003.0/rodney/DATA
+
+        SRESSWORK  STR     /data/ipp003.0/rodney/WORK
+        SRESSDATA  STR     /data/ipp003.0/rodney/DATA
+
+        SIMDATA STR     /data/ipp002.0/SIM/DATA
+        SIMWORK STR     /data/ipp002.0/SIM/WORK
+
+        MOPS    STR     /data/ipp002.0/MOPS/
+        TALCS   STR     /data/ipp003.0/TALCS/
+        GPC1    STR     /data/ipp002.0/koppenh/
+END
+
+# List of tessellations, and their DVO CATDIR
+TESSELLATIONS   METADATA
+        MOPS            STR     /data/ipp002.0/MOPS/skycells/
+        FIXNS           STR     /data/ipp002.0/FIXNS/skycells/
+END
+
+# dvo databases used for output
+DVO.CATDIRS     METADATA
+        MOPS            STR     /data/ipp002.0/MOPS/catdir/
+        FIXNS           STR     /data/ipp002.0/FIXNS/catdir/
+END
+
+# dvo databases used for psastro reference
+PSASTRO.CATDIRS METADATA
+        2MASS           STR     /data/alala.0/ipp/ippRefs/catdir.2mass
+        SYNTH.GRIZY     STR     /data/alala.0/ipp/ippRefs/catdir.synth.grizy
+        SYNTH.BRIGHT    STR     /data/alala.0/ipp/ippRefs/catdir.synth.bright
+        SYNTH.SIMTEST   STR     /data/alala.0/ipp/ippRefs/catdir.synth.simtest
+END
+
+# nebulous server
+NEB_SERVER      STR     http://alala:80/nebulous        # Nebulous server
+
+# Database configuration
+DBSERVER        STR     XXX                     # Database host name (for psDBInit)
+DBNAME          STR     XXX                     # Database name (for psDBInit)
+DBUSER          STR     XXX                     # Database user name (for psDBInit)
+DBPASSWORD      STR     XXX                     # Database password (for psDBInit)
+
+# other basic values:
+# XXX is TIME still needed / used?
+# XXX use autoconf to put this in a known location?
+# TIME          STR     psTime.config   # Time configuration file
+
+PSTAMP_DATA_STORE_ROOT STR /data/ipp000.0/datastore/dsroot # Data store root directory
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/skycell/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/skycell/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/skycell/.cvsignore	(revision 22157)
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/skycell/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/skycell/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/skycell/Makefile.am	(revision 22157)
@@ -0,0 +1,14 @@
+
+installdir = $(datadir)/ippconfig/skycell
+
+install_files = \
+	dvo.config
+
+install_DATA = $(install_files)
+
+install-data-hook:
+	chmod 0755 $(installdir)
+
+EXTRA_DIST = $(install_files)
+
+ACLOCAL_AMFLAGS = -I m4
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/skycell/dvo.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/skycell/dvo.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/skycell/dvo.config	(revision 22157)
@@ -0,0 +1,15 @@
+# DVO configuration for skycells of all cameras
+# Contains keywords used by DVO to interpret the headers
+
+# Date keyword abstractions:
+DATE-KEYWORD		NONE
+UT-KEYWORD		NONE
+DATE-MODE		YYYY-MM-DD
+JD-KEYWORD		NONE
+MJD-KEYWORD		MJD-OBS
+
+# Other important keyword abstractions:
+EXPTIME-KEYWORD		EXPTIME
+AIRMASS-KEYWORD		AIRMASS
+CCDNUM-KEYWORD		NONE
+ST-KEYWORD		NONE
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/system.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/system.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/system.config	(revision 22157)
@@ -0,0 +1,65 @@
+## system-wide options : these are concepts not specific to any camera or recipe
+
+### Setups for each camera system
+CAMERAS		METADATA
+	MEGACAM			STR	megacam/camera.config		# CFHT MegaCam
+	CFH12K			STR	cfh12k/camera.config		# CFHT 12K
+	ISP-Apogee     		STR	isp/camera.config		# Pan-STARRS Imaging sky probe
+	MOSAIC2 		STR	mosaic2/camera.config	        # CTIO MOSAIC2 camera, for ESSENCE
+	SDSSMOSAIC		STR	sdssmosaic/camera.config	# SDSS, mosaic version
+	SDSS			STR	sdss/camera.config		# Sloan Digital Sky Survey
+	GPC1			STR	gpc1/camera.config		# Pan-STARRS GPC1
+	ESOWFI			STR	esowfi/camera.config		# ESO Wide-Field Imager
+	LBCRED                  STR     lbc_red/camera.config           # Large Binocular Camera Red
+	LULIN			STR	lulin/camera.config		# Lulin Optical Telescope 1m
+	UH8K			STR	uh8k/camera.config		# WFI/8k on UH88
+	SIMMOSAIC		STR	simmosaic/camera.config		# Simulated mosaic, for testing
+	SIMTEST			STR	simtest/camera.config		# Simulation test camera
+	SIMPLE			STR	simple/camera.config		# If all else fails....
+END
+
+## directories exist for other camera, introduce if they are tested and working
+#	UCAM			STR	ucam/camera.config
+#	LRIS_BLUE		STR	lris_blue/camera.config
+#	TC3			STR	tc3/camera.config		# Pan-STARRS Test Camera III
+#	LRIS_RED		STR	lris_red/camera.config
+
+### camera names as expected by DVO
+DVO.CAMERAS		METADATA
+	MEGACAM			STR	megacam
+	CFH12K			STR	cfh12k
+	ISP-Apogee     		STR	isp
+	MOSAIC2 		STR	mosaic2
+	SDSSMOSAIC		STR	sdssmosaic
+	SDSS			STR	sdss
+	GPC1			STR	gpc1
+	ESOWFI			STR	esowfi
+	LULIN			STR	lulin
+	UH8K			STR	uh8k
+	SIMMOSAIC		STR	simmosaic
+	SIMTEST			STR	simtest
+	SIMPLE			STR	simple
+END
+
+# Header keywords for skycell concepts; required because DVO doesn't read HIERARCH
+SKYCELLS	METADATA
+	FPA.TIME	STR	MJD-OBS
+	CELL.TIME	STR	MJD-OBS
+	FPA.EXPOSURE	STR	EXPTIME
+	CELL.EXPOSURE	STR	EXPTIME
+	FPA.AIRMASS	STR	AIRMASS
+END
+
+RECIPES		METADATA		# Site-level recipes
+	MASKS		STR		recipes/masks.config	# Mask values
+	REJECTIONS	STR		recipes/rejections.config # Rejection for detrend creation
+	PPIMAGE		STR		recipes/ppImage.config  # Image reduction
+	PPMERGE		STR		recipes/ppMerge.config	# Image combination
+ 	PPSTATS		STR		recipes/ppStats.config	# Image statistics
+	PSPHOT		STR     	recipes/psphot.config	# Photometry
+	PSASTRO		STR		recipes/psastro.config	# Astrometry
+	PPSTACK		STR		recipes/ppStack.config	# Combination
+	PSWARP		STR		recipes/pswarp.config   # Warping
+	PPSIM		STR		recipes/ppSim.config	# Simulations
+	PPSUB		STR		recipes/ppSub.config	# Subtraction
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/tc3/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/tc3/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/tc3/.cvsignore	(revision 22157)
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/tc3/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/tc3/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/tc3/Makefile.am	(revision 22157)
@@ -0,0 +1,17 @@
+
+installdir = $(datadir)/ippconfig/tc3
+
+install_files = \
+	dvo.config \
+	camera.config \
+	format.config \
+	ppImage.config
+
+install_DATA = $(install_files)
+
+install-data-hook:
+	chmod 0755 $(installdir)
+
+EXTRA_DIST = $(install_files)
+
+ACLOCAL_AMFLAGS = -I m4
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/tc3/camera.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/tc3/camera.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/tc3/camera.config	(revision 22157)
@@ -0,0 +1,76 @@
+# Camera configuration file for Cfh12k: describes the camera
+
+# File formats that we know about
+FORMATS		METADATA
+	RAW	STR	tc3/format.config
+END
+
+# Description of camera --- all the chips and the cells that comprise them
+FPA	METADATA
+	CCID58-1-06b2	STR	xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+	CCID45-1-14A 	STR	xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+	CCID45-1-11A 	STR	xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+	CCID45-1-22A 	STR	xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+	CCID45-1-04C 	STR	xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+	CCID45-1-13A 	STR	xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+	CCID45-1-05A 	STR	xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+	CCID45-1-19A 	STR	xy00 xy01 xy02 xy03 xy04 xy05 xy06 xy07 xy10 xy11 xy12 xy13 xy14 xy15 xy16 xy17 xy20 xy21 xy22 xy23 xy24 xy25 xy26 xy27 xy30 xy31 xy32 xy33 xy34 xy35 xy36 xy37 xy40 xy41 xy42 xy43 xy44 xy45 xy46 xy47 xy50 xy51 xy52 xy53 xy54 xy55 xy56 xy57 xy60 xy61 xy62 xy63 xy64 xy65 xy66 xy67 xy70 xy71 xy72 xy73 xy74 xy75 xy76 xy77
+END
+
+# ccd00
+# ccd01
+# ccd10
+# ccd11
+# ccd20
+# ccd21
+# ccd30
+# ccd31
+
+# move this elsewhere?  we need a lookup table to go from filter ID to abstract name
+FILTER.ID       METADATA
+	NONE	STR	NONE
+	B	STR	B
+	V	STR	V
+END
+
+# Table of strings to use for the class identifier (see ippdb) according to the file level.
+CLASSID		METADATA
+	FPA	STR	fpa
+	CHIP	STR	{CHIP.NAME}
+	CELL	STR	{CHIP.NAME}:{CELL.NAME}
+	fpa	STR	fpa
+	chip	STR	{CHIP.NAME}
+	cell	STR	{CHIP.NAME}:{CELL.NAME}
+END
+
+# convert supplied FPA.OBSTYPE values to abstract exptype names
+OBSTYPE.TABLE METADATA
+  bias 	   STR BIAS
+  zero 	   STR BIAS
+  dark 	   STR DARK
+  flat 	   STR SKYFLAT
+  skyflat  STR SKYFLAT
+  domeflat STR DOMEFLAT
+  object   STR OBJECT
+  science  STR OBJECT
+END
+
+# Recipe options
+RECIPES		METADATA
+	# Other recipes
+#        PSPHOT          STR     tc3/psphot.config           # psphot details
+#        PSASTRO         STR     tc3/psastro.config          # psastro details
+#	PPSTATS		STR	tc3/ppStats.config		# ppStats recipe
+	PPIMAGE         STR     tc3/ppImage.config		# ppImage recipe
+END
+
+FITSTYPES       STR     recipes/fitstypes.mdc
+
+FILERULES       STR     recipes/filerules-split.mdc
+
+BLANK.HEADERS	METADATA
+	FPA.TIME	STR	MJD-OBS
+	FPA.EXPOSURE	STR	EXPTIME
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.NAME	STR	EXPNUM
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/tc3/dvo.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/tc3/dvo.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/tc3/dvo.config	(revision 22157)
@@ -0,0 +1,63 @@
+
+# location of DVO database tables
+CATDIR			/data/alala.0/eugene/isp/catdir
+
+# keywords used by DVO to interpret the headers
+
+# keyword abstractions:
+DATE-KEYWORD		DATE-OBS
+DATE-MODE		yyyy-mm-dd
+UT-KEYWORD		UTC-OBS
+JD-KEYWORD		NONE
+MJD-KEYWORD		NONE
+
+# other keyword abstractions
+EXPTIME-KEYWORD		EXPTIME
+AIRMASS-KEYWORD		AIRMASS
+CCDNUM-KEYWORD		EXTNAME
+ST-KEYWORD		NONE
+OBSERVATORY-LATITUDE	NONE
+OBSERVATORY-LONGITUDE	NONE
+SUBPIX_DATAFILE		NONE
+
+# instrumental magnitude range for calibration mode 
+CAL_INSTMAG_MAX		0
+CAL_INSTMAG_MIN		0
+
+# exclude overscan region from the dB image boundaries
+XOVERSCAN		0
+YOVERSCAN		0
+
+# only upload stars within region; a value of 0 means ignore the limit
+ADDSTAR_XMIN		0
+ADDSTAR_XMAX		0
+ADDSTAR_YMIN		0
+ADDSTAR_YMAX		0
+
+# exclude stars with SN > SNLIMIT (ADDSTAR_SNLIMIT overrides old name MIN_SN_FSTAT)
+ADDSTAR_SNLIMIT		0
+
+# correlation radius (arcseconds)
+ADDSTAR_RADIUS		1.0
+
+# scaled correlation radius 
+ADDSTAR_NSIGMA		0
+
+IMAGE_SCATTER           0.075  # mark images POOR if stdev(Mcal) > IMAGE_SCATTER
+STAR_SCATTER            0.005  # mark stars POOR if stdev(Mrel) > STAR_SCATTER
+IMAGE_OFFSET            0.100  # mark images POOR if abs(delta(Mcal)) > IMAGE_OFFSET
+STAR_CHISQ              10.0   # mark stars POOR if Xm > STAR_CHISQ
+STAR_TOOFEW              3     # mark star FEW if N(good) < STAR_TOOFEW
+IMAGE_TOOFEW            10     # mark image FEW if N(good) < IMAGE_TOOFEW
+IMAGE_GOOD_FRACTION     0.05   # mark image FEW if N(good) < IMAGE_GOOD_FRACTION * Nstars
+
+SCATTER_LIM             15.0
+MAG_LIM                 20.0   # select stars brighter than this for relphot analysis
+SIGMA_LIM               0.015  # select measurements with dM < SIGMA_LIM for relphot analysis
+# INST_MAG_MIN         -17     # optional constraints on magnitude ranges
+# INST_MAG_MAX         -13     # optional constraints on magnitude ranges
+
+RELPHOT_GRID_BINNING    256
+RELPHOT_GRID_X 6
+RELPHOT_GRID_Y 14
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/tc3/format.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/tc3/format.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/tc3/format.config	(revision 22157)
@@ -0,0 +1,361 @@
+# Camera format configuration for Pan-STARRS Test Camera 3
+
+# How to recognise this type
+RULE	METADATA
+#        IMAGESWV        STR     GPC DaqCom v2.05 (Mar 08 2007)
+	ORIGIN		STR	PS1
+	CONTROLR	STR	STARGRASP
+	EXTEND		BOOL	T
+END
+
+FILE	METADATA
+	# How to read this data
+	PHU		STR	CHIP	# The FITS file represents a chip
+	EXTENSIONS	STR	CELL	# The extensions represent cells
+	FPA.OBS	STR	OBSTYPE	# A PHU keyword for unique identifier
+	CONTENT	        STR     DETECTOR # How to determine content of FITS file
+	CONTENT.RULE	STR	{CHIP.NAME}	# How to derive the CONTENT when writing
+END
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# File identifier with corresponding file layout name
+	CCID58-1-06b2	STR	CCID58-1-06b2:CCD_10u
+	CCID45-1-14A	STR	CCID45-1-14A:CCD_12u
+	CCID45-1-11A	STR	CCID45-1-11A:CCD_12u
+	CCID45-1-22A	STR	CCID45-1-22A:CCD_12u
+	CCID45-1-04C	STR	CCID45-1-04C:CCD_10u
+	CCID45-1-13A	STR	CCID45-1-13A:CCD_12u
+	CCID45-1-05A	STR	CCID45-1-05A:CCD_12u
+	CCID45-1-19A	STR	CCID45-1-19A:CCD_12u
+END
+
+# Specify the layout
+CHIPS	METADATA
+	CCD_10u		METADATA
+		# Extension name, cellName:cellType
+		xy00	STR	xy00:pitch10u
+		xy01	STR	xy01:pitch10u
+		xy02	STR	xy02:pitch10u
+		xy03	STR	xy03:pitch10u
+		xy04	STR	xy04:pitch10u
+		xy05	STR	xy05:pitch10u
+		xy06	STR	xy06:pitch10u
+		xy07	STR	xy07:pitch10u
+		xy10	STR	xy10:pitch10u
+		xy11	STR	xy11:pitch10u
+		xy12	STR	xy12:pitch10u
+		xy13	STR	xy13:pitch10u
+		xy14	STR	xy14:pitch10u
+		xy15	STR	xy15:pitch10u
+		xy16	STR	xy16:pitch10u
+		xy17	STR	xy17:pitch10u
+		xy20	STR	xy20:pitch10u
+		xy21	STR	xy21:pitch10u
+		xy22	STR	xy22:pitch10u
+		xy23	STR	xy23:pitch10u
+		xy24	STR	xy24:pitch10u
+		xy25	STR	xy25:pitch10u
+		xy26	STR	xy26:pitch10u
+		xy27	STR	xy27:pitch10u
+		xy30	STR	xy30:pitch10u
+		xy31	STR	xy31:pitch10u
+		xy32	STR	xy32:pitch10u
+		xy33	STR	xy33:pitch10u
+		xy34	STR	xy34:pitch10u
+		xy35	STR	xy35:pitch10u
+		xy36	STR	xy36:pitch10u
+		xy37	STR	xy37:pitch10u
+		xy40	STR	xy40:pitch10u
+		xy41	STR	xy41:pitch10u
+		xy42	STR	xy42:pitch10u
+		xy43	STR	xy43:pitch10u
+		xy44	STR	xy44:pitch10u
+		xy45	STR	xy45:pitch10u
+		xy46	STR	xy46:pitch10u
+		xy47	STR	xy47:pitch10u
+		xy50	STR	xy50:pitch10u
+		xy51	STR	xy51:pitch10u
+		xy52	STR	xy52:pitch10u
+		xy53	STR	xy53:pitch10u
+		xy54	STR	xy54:pitch10u
+		xy55	STR	xy55:pitch10u
+		xy56	STR	xy56:pitch10u
+		xy57	STR	xy57:pitch10u
+		xy60	STR	xy60:pitch10u
+		xy61	STR	xy61:pitch10u
+		xy62	STR	xy62:pitch10u
+		xy63	STR	xy63:pitch10u
+		xy64	STR	xy64:pitch10u
+		xy65	STR	xy65:pitch10u
+		xy66	STR	xy66:pitch10u
+		xy67	STR	xy67:pitch10u
+		xy70	STR	xy70:pitch10u
+		xy71	STR	xy71:pitch10u
+		xy72	STR	xy72:pitch10u
+		xy73	STR	xy73:pitch10u
+		xy74	STR	xy74:pitch10u
+		xy75	STR	xy75:pitch10u
+		xy76	STR	xy76:pitch10u
+		xy77	STR	xy77:pitch10u
+	END
+	CCD_12u		METADATA
+		# Extension name, cellName:cellType
+		xy00	STR	xy00:pitch12u
+		xy01	STR	xy01:pitch12u
+		xy02	STR	xy02:pitch12u
+		xy03	STR	xy03:pitch12u
+		xy04	STR	xy04:pitch12u
+		xy05	STR	xy05:pitch12u
+		xy06	STR	xy06:pitch12u
+		xy07	STR	xy07:pitch12u
+		xy10	STR	xy10:pitch12u
+		xy11	STR	xy11:pitch12u
+		xy12	STR	xy12:pitch12u
+		xy13	STR	xy13:pitch12u
+		xy14	STR	xy14:pitch12u
+		xy15	STR	xy15:pitch12u
+		xy16	STR	xy16:pitch12u
+		xy17	STR	xy17:pitch12u
+		xy20	STR	xy20:pitch12u
+		xy21	STR	xy21:pitch12u
+		xy22	STR	xy22:pitch12u
+		xy23	STR	xy23:pitch12u
+		xy24	STR	xy24:pitch12u
+		xy25	STR	xy25:pitch12u
+		xy26	STR	xy26:pitch12u
+		xy27	STR	xy27:pitch12u
+		xy30	STR	xy30:pitch12u
+		xy31	STR	xy31:pitch12u
+		xy32	STR	xy32:pitch12u
+		xy33	STR	xy33:pitch12u
+		xy34	STR	xy34:pitch12u
+		xy35	STR	xy35:pitch12u
+		xy36	STR	xy36:pitch12u
+		xy37	STR	xy37:pitch12u
+		xy40	STR	xy40:pitch12u
+		xy41	STR	xy41:pitch12u
+		xy42	STR	xy42:pitch12u
+		xy43	STR	xy43:pitch12u
+		xy44	STR	xy44:pitch12u
+		xy45	STR	xy45:pitch12u
+		xy46	STR	xy46:pitch12u
+		xy47	STR	xy47:pitch12u
+		xy50	STR	xy50:pitch12u
+		xy51	STR	xy51:pitch12u
+		xy52	STR	xy52:pitch12u
+		xy53	STR	xy53:pitch12u
+		xy54	STR	xy54:pitch12u
+		xy55	STR	xy55:pitch12u
+		xy56	STR	xy56:pitch12u
+		xy57	STR	xy57:pitch12u
+		xy60	STR	xy60:pitch12u
+		xy61	STR	xy61:pitch12u
+		xy62	STR	xy62:pitch12u
+		xy63	STR	xy63:pitch12u
+		xy64	STR	xy64:pitch12u
+		xy65	STR	xy65:pitch12u
+		xy66	STR	xy66:pitch12u
+		xy67	STR	xy67:pitch12u
+		xy70	STR	xy70:pitch12u
+		xy71	STR	xy71:pitch12u
+		xy72	STR	xy72:pitch12u
+		xy73	STR	xy73:pitch12u
+		xy74	STR	xy74:pitch12u
+		xy75	STR	xy75:pitch12u
+		xy76	STR	xy76:pitch12u
+		xy77	STR	xy77:pitch12u
+	END
+END		
+
+# Specify the cell data
+CELLS	METADATA
+	pitch10u	METADATA
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BIASSEC
+		CELL.TRIMSEC		STR	DATASEC
+		CELL.XSIZE		S32	590
+		CELL.YSIZE		S32	598
+	END
+
+	pitch12u	METADATA
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BIASSEC
+		CELL.TRIMSEC		STR	DATASEC
+		CELL.XSIZE		S32	477
+		CELL.YSIZE		S32	496
+	END
+	pitch10u	METADATA
+		CELL.BIASSEC.SOURCE	STR	VALUE
+		CELL.TRIMSEC.SOURCE	STR	VALUE
+		CELL.BIASSEC		STR	[575:606,1:594]
+		CELL.TRIMSEC		STR	[1:574,1:594]
+	#	CELL.BIASSEC		STR	BIASSEC
+	#	CELL.TRIMSEC		STR	DATASEC
+	END
+
+	# This is just in here for fun
+	pitch12u	METADATA
+		CELL.BIASSEC.SOURCE	STR	VALUE
+		CELL.TRIMSEC.SOURCE	STR	VALUE
+		CELL.BIASSEC		STR	[1:10,1:512];[523:574,1:512]
+		CELL.TRIMSEC		STR	[11:522,1:512]
+	#	CELL.BIASSEC		STR	BIASSEC
+	#	CELL.TRIMSEC		STR	TRIMSEC
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.TELESCOPE	STR	TELESCOP
+	FPA.INSTRUMENT	STR	INSTRUME
+	FPA.DETECTOR	STR	DETECTOR
+        FPA.AIRMASS     STR     AIRMASS
+	FPA.OBSTYPE	STR	OBSTYPE
+	FPA.OBJECT	STR	OBSTYPE
+#	FPA.OBJECT	STR	OBJECT		# Not always available
+#        FPA.FILTERID    STR     FILTER		# Not available from OTIS
+#        FPA.FILTER      STR     FILTER		# Not available from OTIS
+        FPA.POSANGLE    STR     ROTANGLE
+        FPA.RA          STR     RA
+        FPA.DEC         STR     DEC
+        FPA.RADECSYS    STR     RADECSYS
+	FPA.FOCUS	STR	TELFOCUS
+	FPA.TIME	STR	MJD-OBS
+	FPA.ALT		STR	TELALT
+	FPA.AZ		STR	TELAZ
+	FPA.TEMP	STR	DETTEM
+	FPA.EXPOSURE	STR	EXPTIME
+	CHIP.TEMP	STR	DETTEM
+        CELL.EXPOSURE   STR     EXPTIME
+        CELL.DARKTIME   STR     DARKTIME
+	CELL.TIME	STR	MJD-OBS
+        CELL.SATURATION STR     SATURATE
+        CELL.XBIN       STR     CCDBIN1
+        CELL.YBIN       STR     CCDBIN2
+#	CELL.BAD	STR	BADLEVEL
+
+#### How to get the cell coordiantes from readout coordinates
+#	READOUT.X0		STR	CNPIX1
+#	READOUT.Y0		STR	CNPIX2
+
+### How to get the chip coordinates from cell coordinates
+	CELL.X0		STR	IMNPIX1
+	CELL.Y0		STR	IMNPIX2
+	CELL.XPARITY	STR	IMTM1_1
+	CELL.YPARITY	STR	IMTM2_2
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS        METADATA
+	FPA.TIMESYS		STR	UTC
+	CELL.TIMESYS		STR	UTC
+	FPA.FILTERID		STR	NONE
+	FPA.FILTER		STR	NONE
+	CELL.GAIN		F32	1.0
+	CELL.READNOISE          F32     10.0
+	CELL.READDIR		S32	1		# Cell is read in x direction
+        CELL.BAD                S32     0
+
+	CHIP.XSIZE.DEPEND	STR	CHIP.NAME
+	CHIP.XSIZE	METADATA
+		CCID58-1-06b2	S32	4837		# 10 micron pitch
+		CCID45-1-14A	S32	4033
+		CCID45-1-11A	S32	4033
+		CCID45-1-22A	S32	4033
+		CCID45-1-04C	S32	4837		# 10 micron pitch
+		CCID45-1-13A	S32	4033
+		CCID45-1-05A	S32	4033    
+		CCID45-1-19A	S32	4033 
+	END
+
+	CHIP.YSIZE.DEPEND	STR	CHIP.NAME
+	CHIP.YSIZE	METADATA
+		CCID58-1-06b2	S32	4829		# 10 micron pitch
+		CCID45-1-14A	S32	4031
+		CCID45-1-11A	S32	4031
+		CCID45-1-22A	S32	4031
+		CCID45-1-04C	S32	4829		# 10 micron pitch
+		CCID45-1-13A	S32	4031
+		CCID45-1-05A	S32	4031    
+		CCID45-1-19A	S32	4031 
+	END
+
+#		CCID58-1-06b2	0,0
+#		CCID45-1-14A	0,1
+#		CCID45-1-11A	1,0
+#		CCID45-1-22A	1,1
+#		CCID45-1-04C	2,0
+#		CCID45-1-13A	2,1
+#		CCID45-1-05A	3,0
+#		CCID45-1-19A	3,1
+
+	### How to get FPA coordinates from chip coordinates
+	CHIP.X0.DEPEND		STR	CHIP.NAME
+	CHIP.X0		METADATA
+		CCID58-1-06b2	S32	0
+		CCID45-1-14A	S32	0
+		CCID45-1-11A	S32	4900
+		CCID45-1-22A	S32	4900
+		CCID45-1-04C	S32	9800
+		CCID45-1-13A	S32	9800
+		CCID45-1-05A	S32	14700
+		CCID45-1-19A	S32	14700 
+	END
+	CHIP.Y0.DEPEND		STR	CHIP.NAME
+	CHIP.Y0		METADATA
+		CCID58-1-06b2	S32	0
+		CCID45-1-14A	S32	4900
+		CCID45-1-11A	S32	0
+		CCID45-1-22A	S32	4900
+		CCID45-1-04C	S32	0
+		CCID45-1-13A	S32	4900
+		CCID45-1-05A	S32	0
+		CCID45-1-19A	S32	4900
+	END
+	CHIP.XPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.XPARITY	METADATA
+		CCID58-1-06b2	S32	+1
+		CCID45-1-14A	S32	+1
+		CCID45-1-11A	S32	+1
+		CCID45-1-22A	S32	+1
+		CCID45-1-04C	S32	+1
+		CCID45-1-13A	S32	+1
+		CCID45-1-05A	S32	+1
+		CCID45-1-19A	S32	+1
+	END
+	CHIP.YPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.YPARITY	METADATA
+		CCID58-1-06b2	S32	+1
+		CCID45-1-14A	S32	+1
+		CCID45-1-11A	S32	+1
+		CCID45-1-22A	S32	+1
+		CCID45-1-04C	S32	+1
+		CCID45-1-13A	S32	+1
+		CCID45-1-05A	S32	+1
+		CCID45-1-19A	S32	+1
+	END
+END
+
+# How to translation PS concepts into database lookups
+DATABASE	METADATA
+	TYPE		dbEntry		TABLE		COLUMN		GIVENDBCOL	GIVENPS
+#	CELL.GAIN	dbEntry		Camera		gain		chipId,cellId	CHIP.NAME,CELL.NAME
+#	CELL.READNOISE	dbEntry		Camera		readNoise	chipId,cellId	CHIP.NAME,CELL.NAME
+
+# A database entry refers to a particular column (COLUMN) in a
+# particular table (TABLE), given certain PS concepts (GIVENPS) that
+# match certain database columns (GIVENDBCOL).
+END		
+
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
+ 
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/tc3/ppImage.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/tc3/ppImage.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/tc3/ppImage.config	(revision 22157)
@@ -0,0 +1,303 @@
+### ppImage recipe configuration file for Megacam
+
+# Overscan subtraction
+OVERSCAN.SINGLE		BOOL	FALSE		# Reduce overscan to a single value?
+OVERSCAN.FIT		STR	NONE		# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER		S32	0		# Order of polynomial fit
+OVERSCAN.STAT		STR	MEAN		# MEAN | MEDIAN
+
+OVERSCAN	BOOL	TRUE		# Overscan subtraction
+BIAS		BOOL	TRUE		# Bias subtraction
+DARK		BOOL	TRUE		# Dark subtraction
+FLAT		BOOL	TRUE		# Flat-field normalisation
+SHUTTER		BOOL	FALSE		# Shutter correction
+FRINGE		BOOL	FALSE		# Fringe subtraction
+
+# this table lists extra constraints which should be applied when
+# selecting the detrend images.
+# note: camera and time are always applied
+
+DETREND.CONSTRAINTS  METADATA
+  BIAS METADATA
+  END
+  DARK METADATA
+  END
+  FLAT METADATA
+    FILTER  STR FPA.FILTERID
+  END
+  FLAT_CORRECTION METADATA
+    FILTER   STR FPA.FILTER
+  END
+  FRINGE METADATA
+    FILTER   STR FPA.FILTER
+  END
+  SHUTTER METADATA
+  END	
+END
+
+# only apply the fringe for these filters
+FRINGE.FILTERS  MULTI
+FRINGE.FILTERS	STR r
+FRINGE.FILTERS	STR i
+FRINGE.FILTERS	STR z
+
+PHOTCODE.RULE		STR	{DETECTOR}.{FILTER.ID}.{CHIP.N}
+
+
+
+################################################################################
+# Diffferent processing options, which may be loaded symbolically
+################################################################################
+
+# No operation except potential normalisation
+PPIMAGE_N         METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan subtraction only
+PPIMAGE_O         METADATA
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Bias subtraction only
+PPIMAGE_B         METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Dark subtraction only
+PPIMAGE_D         METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Shutter correction only
+PPIMAGE_S         METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Flat-fielding only
+PPIMAGE_F         METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    TRUE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Fringe correction only
+PPIMAGE_R         METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE            # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Photometry only
+PPIMAGE_P         METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+END
+
+# Photometry & Astrometry
+PPIMAGE_A         METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Source identification and photometry
+END
+
+# Overscan, bias
+PPIMAGE_OB        METADATA
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark
+PPIMAGE_OBD      METADATA
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter
+PPIMAGE_OBDS      METADATA
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field
+PPIMAGE_OBDSF     METADATA
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE            # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe
+PPIMAGE_OBDSFR    METADATA
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE            # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom
+PPIMAGE_OBDSFRP   METADATA
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM          BOOL    FALSE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_OBDSFRA   METADATA
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    TRUE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE            # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM          BOOL    FALSE		  # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE		  # Astrometry for mosaic?
+END
+
+# Save JPEG from BIN1
+PPIMAGE_J1        METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  BASE.FITS       BOOL    FALSE           # Save base image?
+  BIN1.FITS       BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS       BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG       BOOL    TRUE		  # Save 1st binned jpeg?
+  BIN2.JPEG       BOOL    FALSE		  # Save 2nd binned jpeg?
+  BIN1.XBIN       S32     1               # Image is already binned
+  BIN1.YBIN       S32     1               # Image is already binned
+  BIN2.XBIN       S32     1               # Image is already binned
+  BIN2.YBIN       S32     1               # Image is already binned
+END
+
+# Save JPEG from BIN2
+PPIMAGE_J2        METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  BASE.FITS       BOOL    FALSE           # Save base image?
+  BIN1.FITS       BOOL    FALSE           # Save 1st binned chip image?
+  BIN2.FITS       BOOL    FALSE           # Save 2nd binned chip image?
+  BIN1.JPEG       BOOL    FALSE		  # Save 1st binned jpeg?
+  BIN2.JPEG       BOOL    TRUE		  # Save 2nd binned jpeg?
+  BIN1.XBIN       S32     1               # Image is already binned
+  BIN1.YBIN       S32     1               # Image is already binned
+  BIN2.XBIN       S32     1               # Image is already binned
+  BIN2.YBIN       S32     1               # Image is already binned
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/tek/camera.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/tek/camera.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/tek/camera.config	(revision 22157)
@@ -0,0 +1,67 @@
+# Camera configuration file for Tek
+
+# File formats that we know about
+FORMATS         METADATA
+        SIMPLE  STR     tek/format.config
+        CMP     STR     tek/cmp.config
+        CMF     STR     tek/cmf.config
+END
+ 
+ 
+# Description of camera --- all the chips and the cells that comprise them
+FPA     METADATA
+        Chip            STR     Cell
+END
+
+# Recipe options
+RECIPES         METADATA
+        PSPHOT          STR     tek/psphot.config               # psphot details
+        PSASTRO         STR     tek/psastro.config              # psastro details
+        PPIMAGE         STR     tek/ppImage.config              # Recipes for ppImage
+        PPMERGE         STR     tek/ppMerge.config              # Recipes for ppMerge
+	REJECTIONS	STR     tek/rejections.config
+END
+
+FILTER.ID       METADATA
+        g       STR     g
+        r       STR     r
+        i       STR     i
+        z       STR     z
+        y       STR     y
+	R	STR	R
+END
+
+# Table of strings to use for the class identifier (see ippdb) according to the file level.
+CLASSID		METADATA
+	FPA	STR	fpa
+END
+
+DVO.CAMERADIR	STR	tek		# Camera directory for DVO
+
+# convert supplied FPA.OBSTYPE values to abstract exptype names
+OBSTYPE.TABLE METADATA
+  bias 	   STR BIAS
+  zero 	   STR BIAS
+  dark 	   STR DARK
+  flat 	   STR SKYFLAT
+  skyflat  STR SKYFLAT
+  domeflat STR DOMEFLAT
+  object   STR OBJECT
+  science  STR OBJECT
+END
+
+FITSTYPES       STR     recipes/fitstypes.mdc
+
+FILERULES       STR     recipes/filerules-simple.mdc
+
+EXTNAME.RULES METADATA
+  CMF.HEAD STR cmf.hdr
+  CMF.DATA STR cmf.psf # use .PSF and .EXT?
+  CMF.XSRC STR {CHIP.NAME}.xsrc # use .PSF and .EXT?
+  CMF.XFIT STR {CHIP.NAME}.xfit # use .PSF and .EXT?
+
+  PSF.HEAD  STR	hdr
+  PSF.TABLE STR psf_model
+  PSF.RESID STR psf_resid
+END
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/tek/cmf.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/tek/cmf.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/tek/cmf.config	(revision 22157)
@@ -0,0 +1,82 @@
+# Tek
+
+# How to identify this type
+RULE	METADATA
+	SIMPLE		BOOL	TRUE
+	NAXIS		S32	0
+	TELESCOP	STR	2.2m-UH
+	INSTRUME	STR	TEK
+	DETECTOR	STR   	Tektronix-2048^2-CCD
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	NONE	# There are no extensions
+	FPA.NAME	STR	NAXIS	# A PHU keyword for unique identifier within the hierarchy level
+END
+
+# What's in the FITS file?
+CONTENTS	STR	Chip:Cell:amplifier
+
+# Specify the cell data
+CELLS	METADATA
+	amplifier	METADATA
+		CELL.TRIMSEC.SOURCE	STR	VALUE
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC		STR	[21:2068,1:2048]		 			CELL.BIASSEC		STR	BIASSEC
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.OBSTYPE	STR	OBSTYPE
+	FPA.OBJECT	STR	OBSTYPE
+	FPA.FILTER	STR	FILTER
+	FPA.RA		STR	RA
+	FPA.DEC		STR	DEC
+	FPA.RADECSYS	STR	RADECSYS
+	FPA.ALT		STR	ALT
+	FPA.AZ		STR	AZ
+	FPA.POSANGLE	STR	ROTANGLE
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.TIME	STR	MJD-OBS
+	CHIP.TEMP	STR	CCDTEMP
+	CELL.EXPOSURE	STR	EXPTIME
+	CELL.DARKTIME	STR	DARKTIME
+	CELL.TIME	STR	MJD-OBS
+	CELL.GAIN	STR	GAIN
+	CELL.XBIN	STR	CCDBIN1
+	CELL.YBIN	STR	CCDBIN2
+#	CELL.SATURATION	STR	SATURATE	### Currently set to 0 ???
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	CELL.READNOISE	S32	10
+	CELL.BAD	S32	0
+	FPA.TIMESYS	STR	UTC
+	CELL.SATURATION	F32	65535
+	CELL.READDIR	S32	1
+	CELL.TIMESYS	STR	UTC
+	CHIP.XPARITY	S32	1
+	CHIP.YPARITY	S32	1
+	CHIP.X0		S32	0
+	CHIP.Y0		S32	0
+	CELL.XPARITY	S32	1
+	CELL.YPARITY	S32	1
+	CELL.X0		S32	0
+	CELL.Y0		S32	0
+END
+
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
+
+# PS Concepts to get from the database
+DATABASE	METADATA
+# None.
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/tek/cmp.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/tek/cmp.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/tek/cmp.config	(revision 22157)
@@ -0,0 +1,82 @@
+# Pan-STARRS Imaging Sky Probe
+
+# How to identify this type
+RULE	METADATA
+	SIMPLE		BOOL	FALSE
+	NAXIS		S32	0
+	TELESCOP	STR	2.2m-UH
+	INSTRUME	STR	TEK
+	DETECTOR	STR	Tektronix-2048^2-CCD
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	NONE	# There are no extensions
+	FPA.NAME	STR	NAXIS	# A PHU keyword for unique identifier within the hierarchy level
+END
+
+# What's in the FITS file?
+CONTENTS	STR	Chip:Cell:amplifier
+
+# Specify the cell data
+CELLS	METADATA
+	amplifier	METADATA
+		CELL.TRIMSEC.SOURCE	STR	VALUE
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC		STR	[21:2068,1:2048]		 			CELL.BIASSEC		STR	BIASSEC
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.OBSTYPE	STR	OBSTYPE
+	FPA.OBJECT	STR	OBSTYPE
+	FPA.FILTER	STR	FILTER
+	FPA.RA		STR	RA
+	FPA.DEC		STR	DEC
+	FPA.RADECSYS	STR	RADECSYS
+	FPA.ALT		STR	ALT
+	FPA.AZ		STR	AZ
+	FPA.POSANGLE	STR	ROTANGLE
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.TIME	STR	MJD-OBS
+	CHIP.TEMP	STR	CCDTEMP
+	CELL.EXPOSURE	STR	EXPTIME
+	CELL.DARKTIME	STR	DARKTIME
+	CELL.TIME	STR	MJD-OBS
+	CELL.GAIN	STR	GAIN
+	CELL.XBIN	STR	CCDBIN1
+	CELL.YBIN	STR	CCDBIN2
+#	CELL.SATURATION	STR	SATURATE	### Currently set to 0 ???L
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	CELL.READNOISE	S32	10
+	CELL.BAD	S32	0
+	FPA.TIMESYS	STR	UTC
+	CELL.SATURATION	F32	65535
+	CELL.READDIR	S32	1
+	CELL.TIMESYS	STR	UTC
+	CHIP.XPARITY	S32	1
+	CHIP.YPARITY	S32	1
+	CHIP.X0		S32	0
+	CHIP.Y0		S32	0
+	CELL.XPARITY	S32	1
+	CELL.YPARITY	S32	1
+	CELL.X0		S32	0
+	CELL.Y0		S32	0
+END
+
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
+
+# PS Concepts to get from the database
+DATABASE	METADATA
+# None.
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/tek/dvo.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/tek/dvo.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/tek/dvo.config	(revision 22157)
@@ -0,0 +1,50 @@
+
+# location of DVO database tables
+CATDIR			/media/usbdisk/scr1/catdir.synth.grizy/
+
+# keywords used by DVO to interpret the headers
+
+# used by parse_time to find time-related keywords
+MJD-KEYWORD		MJD-OBS
+DATE-KEYWORD		DATE-OBS
+DATE-MODE		YYYY-MM-DD
+UT-KEYWORD		UT
+JD-KEYWORD		JD
+
+# other keyword abstractions
+EXPTIME-KEYWORD		EXPTIME
+AIRMASS-KEYWORD		AIRMASS
+CCDNUM-KEYWORD		EXTNAME
+ST-KEYWORD		NONE
+OBSERVATORY-LATITUDE	NONE
+OBSERVATORY-LONGITUDE	NONE
+SUBPIX_DATAFILE		NONE
+
+# instrumental magnitude range for calibration mode 
+CAL_INSTMAG_MAX		-7.0
+CAL_INSTMAG_MIN		-9.5
+
+# exclude overscan region from the dB image boundaries
+XOVERSCAN		0
+YOVERSCAN		0
+
+# only upload stars within region; a value of 0 means ignore the limit
+ADDSTAR_XMIN		0
+ADDSTAR_XMAX		0
+ADDSTAR_YMIN		0
+ADDSTAR_YMAX		0
+
+# exclude stars with SN > SNLIMIT (ADDSTAR_SNLIMIT overrides old name MIN_SN_FSTAT)
+ADDSTAR_SNLIMIT		0
+
+# allowed astrometry error (arcseconds)
+ADDSTAR_MAX_CERROR	5.0
+
+# correlation radius (arcseconds)
+ADDSTAR_RADIUS		5.0
+
+# scaled correlation radius 
+ADDSTAR_NSIGMA		0
+
+CATMODE			MEF
+CATFORMAT		PANSTARRS_DEV_0
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/tek/dvo.layout
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/tek/dvo.layout	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/tek/dvo.layout	(revision 22157)
@@ -0,0 +1,58 @@
+# this file defines the layout of the mosaic imager:
+
+# NCCD 40  # this needs work: sometimes 36, sometimes 40
+NCCD 36
+NAXIS1 2112
+NAXIS2 4644
+
+MOSAIC_X 11
+MOSAIC_Y  4
+
+# lines need to contain:
+
+CHIPID_KEYWORD EXTNAME
+
+# ID     CHIPID xoffset yoffset xflip yflip datasec         biassec            Xo      Yo      theta
+CCD.0     ccd00       1       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.1     ccd01       2       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.2     ccd02       3       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.3     ccd03       4       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.4     ccd04       5       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.5     ccd05       6       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.6     ccd06       7       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.7     ccd07       8       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.8     ccd08       9       3     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.9     ccd09       1       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.10    ccd10       2       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.11    ccd11       3       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.12    ccd12       4       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.13    ccd13       5       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.14    ccd14       6       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.15    ccd15       7       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.16    ccd16       8       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.17    ccd17       9       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.18    ccd18       1       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.19    ccd19       2       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.20    ccd20       3       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.21    ccd21       4       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.22    ccd22       5       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.23    ccd23       6       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.24    ccd24       7       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.25    ccd25       8       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.26    ccd26       9       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.27    ccd27       1       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.28    ccd28       2       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.29    ccd29       3       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.30    ccd30       4       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.31    ccd31       5       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.32    ccd32       6       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.33    ccd33       7       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.34    ccd34       8       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.35    ccd35       9       0     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.36    ccd36       0       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.37    ccd37      10       2     1     1 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.38    ccd38       0       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+CCD.39    ccd39      10       1     0     0 [6:2049,5:4100] [2085:2110,2:4400] 0       0       0
+
+# can we use header to find biassec
+USE_BIASSEC 1
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/tek/format.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/tek/format.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/tek/format.config	(revision 22157)
@@ -0,0 +1,101 @@
+# Pan-STARRS Imaging Sky Probe
+
+# How to identify this type
+RULE	METADATA
+	SIMPLE		BOOL	TRUE
+	NAXIS		S32	2
+	TELESCOP	STR	2.2m-UH 
+	INSTRUME	STR	TEK
+	DETECTOR	STR	Tektronix-2048^2-CCD
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	NONE	# There are no extensions
+	FPA.OBS	STR	NAXIS	# A PHU keyword for unique identifier within the hierarchy level
+END
+
+# What's in the FITS file?
+CONTENTS	STR	Chip:Cell:amplifier
+
+# Specify the cell data
+CELLS	METADATA
+	amplifier	METADATA
+		CELL.TRIMSEC.SOURCE	STR	VALUE
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC		STR	[21:2068,1:2048]
+		CELL.BIASSEC		STR	BIASSEC
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.TELESCOPE	STR	TELESCOP
+	FPA.INSTRUMENT	STR	INSTRUME
+	FPA.DETECTOR	STR	DETECTOR
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.OBSTYPE	STR	OBSTYPE
+	FPA.OBJECT	STR	OBSTYPE
+	FPA.FILTERID	STR	FILTER
+	FPA.FILTER	STR	FILTER
+	FPA.POSANGLE	STR	ROTANGLE
+	FPA.RA		STR	RA
+	FPA.DEC		STR	DEC
+	FPA.RADECSYS	STR	RADECSYS
+#	FPA.FOCUS	STR	TELFOCUS
+	FPA.TIME	STR	MJD-OBS
+	FPA.TIMESYS	STR	TIMESYS
+	FPA.ALT		STR	ALT
+	FPA.AZ		STR	AZ
+	FPA.TEMP	STR	CCDTEMP
+	FPA.EXPOSURE	STR	EXPTIME
+	CHIP.TEMP	STR	CCDTEMP
+	CELL.EXPOSURE	STR	EXPTIME
+	CELL.DARKTIME	STR	DARKTIME
+	CELL.TIME	STR	MJD-OBS
+	CELL.GAIN	STR	GAIN
+#	CELL.SATURATION	STR	SATURATE	### Currently set to 0 ???
+#	CELL.TIMESYS	STR	TIMESYS
+	CELL.XBIN	STR	CCDBIN1
+	CELL.YBIN	STR	CCDBIN2
+# these were used for some early data
+#	CELL.XBIN	STR	XBIN
+#	CELL.YBIN	STR	YBIN
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	CELL.READNOISE	S32	10
+	CELL.BAD	S32	0
+	CHIP.XSIZE	S32	2048
+	CHIP.YSIZE	S32	2048
+	CELL.XSIZE	S32	2048
+	CELL.YSIZE	S32	2048
+	FPA.TIMESYS	STR	UTC
+	CELL.SATURATION	F32	60000
+	CELL.READDIR	S32	1
+	CELL.TIMESYS	STR	UTC
+	CHIP.XPARITY	S32	1
+	CHIP.YPARITY	S32	1
+	CHIP.X0		S32	0
+	CHIP.Y0		S32	0
+	CELL.XPARITY	S32	1
+	CELL.YPARITY	S32	1
+	CELL.X0		S32	0
+	CELL.Y0		S32	0
+#	CELL.XBIN	S32	1
+#	CELL.YBIN	S32	1
+END
+
+FORMATS		METADATA
+	FPA.RA		STR	DEGREES
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	MJD
+	CELL.TIME	STR	MJD
+END
+
+# PS Concepts to get from the database
+DATABASE	METADATA
+# None.
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/tek/ppImage.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/tek/ppImage.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/tek/ppImage.config	(revision 22157)
@@ -0,0 +1,273 @@
+### ppImage recipe configuration file
+
+# List of tasks to perform
+#NONLIN          BOOL    FALSE           # Non-linearity correction; not implemented
+#OVERSCAN        BOOL    TRUE            # Overscan subtraction
+#BIAS            BOOL    TRUE            # Bias subtraction
+#DARK            BOOL    FALSE            # Dark subtraction
+#SHUTTER         BOOL    FALSE           # Shutter correction
+#FLAT            BOOL    TRUE            # Flat-field normalisation
+#MASK            BOOL    FALSE           # Mask bad pixels
+#MASK.VALUE      STR     SAT,BAD         # Mask pixels with these attributes
+#FRINGE          BOOL    TRUE           # Fringe subtraction
+#PHOTOM          BOOL    TRUE           # Source identification and photometry
+#ASTROM.CHIP     BOOL    FALSE          # Astrometry per chip?
+#ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+
+# Inherit everything from the top-level recipe file, except for what's below.
+
+PHOTCODE.RULE		STR	{DETECTOR}.{FILTER.ID}
+
+FRINGE.FILTERS  MULTI
+FRINGE.FILTERS	STR z
+FRINGE.FILTERS	STR y
+
+# apply the following constraints when selecting a detrend image
+DETREND.CONSTRAINTS  METADATA
+  BIAS METADATA
+  END
+  DARK METADATA
+  END
+  SHUTTER METADATA
+  END	
+  FLAT METADATA
+    FILTER  STR FPA.FILTERID
+#   VERSION STR RAW
+  END
+  FRINGE METADATA
+    FILTER   STR FPA.FILTERID
+#   AIRMASS  STR FPA.AIRMASS
+#   TWILIGHT STR TWILIGHT:FPA.TIME
+  END
+END
+
+
+# Overscan subtraction only
+PPIMAGE_O         METADATA
+  BASE.FITS       BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS       BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Bias subtraction only
+PPIMAGE_B         METADATA
+  BASE.FITS       BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS       BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Dark subtraction only
+PPIMAGE_D         METADATA
+  BASE.FITS       BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS       BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Shutter correction only
+PPIMAGE_S         METADATA
+  BASE.FITS       BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS       BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE            # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Flat-fielding only
+PPIMAGE_F         METADATA
+  BASE.FITS       BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS       BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    TRUE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Fringe correction only
+PPIMAGE_R         METADATA
+  BASE.FITS       BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS       BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Photometry only
+PPIMAGE_P         METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    TRUE           # Source identification and photometry
+  BASE.FITS       BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS       BOOL    TRUE           # Save chip-mosaic-ed image? 
+END
+
+# Photometry & Astrometry
+PPIMAGE_A         METADATA
+  OVERSCAN        BOOL    FALSE           # Overscan subtraction
+  BIAS            BOOL    FALSE           # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  PHOTOM          BOOL    TRUE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Source identification and photometry
+  BASE.FITS       BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS       BOOL    TRUE           # Save chip-mosaic-ed image? 
+END
+
+# Overscan, bias
+PPIMAGE_OB        METADATA
+  BASE.FITS       BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS       BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    FALSE           # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark
+PPIMAGE_OBD      METADATA
+  BASE.FITS       BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS       BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    FALSE            # Dark subtraction
+  SHUTTER         BOOL    FALSE           # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter
+PPIMAGE_OBDS      METADATA
+  BASE.FITS       BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS       BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    FALSE            # Dark subtraction
+  SHUTTER         BOOL    FALSE            # Shutter correction
+  FLAT            BOOL    FALSE           # Flat-field normalisation
+  MASK            BOOL    FALSE           # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field
+PPIMAGE_OBDSF     METADATA
+  BASE.FITS       BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS       BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    FALSE            # Dark subtraction
+  SHUTTER         BOOL    FALSE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE            # Mask bad pixels
+  FRINGE          BOOL    FALSE           # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe
+PPIMAGE_OBDSFR    METADATA
+  BASE.FITS       BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS       BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    FALSE            # Dark subtraction
+  SHUTTER         BOOL    FALSE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE            # Mask bad pixels
+  FRINGE          BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom
+PPIMAGE_OBDSFRP   METADATA
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    FALSE            # Dark subtraction
+  SHUTTER         BOOL    FALSE            # Shutter correction
+  FLAT            BOOL    TRUE            # Flat-field normalisation
+  MASK            BOOL    FALSE            # Mask bad pixels
+  FRINGE          BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM          BOOL    TRUE           # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+  BASE.FITS       BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS       BOOL    TRUE            # Save chip-mosaic-ed image? 
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_OBDSFRA   METADATA
+  OVERSCAN        BOOL    TRUE            # Overscan subtraction
+  BIAS            BOOL    TRUE            # Bias subtraction
+  DARK            BOOL    FALSE            # Dark subtraction
+  SHUTTER         BOOL    FALSE            # Shutter correction
+  FLAT            BOOL    FALSE            # Flat-field normalisation
+  MASK            BOOL    FALSE            # Mask bad pixels
+  FRINGE          BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS       BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS       BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM          BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP     BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC   BOOL    FALSE           # Astrometry for mosaic?
+  BASE.FITS       BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS       BOOL    TRUE           # Save chip-mosaic-ed image? 
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/tek/ppMerge.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/tek/ppMerge.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/tek/ppMerge.config	(revision 22157)
@@ -0,0 +1,3 @@
+### ppImage recipe configuration file
+
+# Inherit everything from the top-level recipe file, except for what's below.
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/tek/psastro.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/tek/psastro.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/tek/psastro.config	(revision 22157)
@@ -0,0 +1,39 @@
+
+# astrometry matching parameters
+
+# nominal plate scale (microns / pixel)
+PSASTRO.PIXEL.SCALE    F32  10.0
+
+# pmAstromGridMatch:
+PSASTRO.GRID.MIN.ANGLE F32 -300.0
+PSASTRO.GRID.MAX.ANGLE F32 +300.0
+PSASTRO.GRID.DEL.ANGLE F32  0.5
+PSASTRO.GRID.MIN.SIGMA F32  5.0
+
+# pmAstromGridAngle
+# max grid offset in FP units (microns)
+# use plate-scale to make this in pixels?
+PSASTRO.GRID.OFFSET    F32   300000.
+PSASTRO.GRID.SCALE     F32     500
+
+# these tweak are in FP units (pixels, currently)
+PSASTRO.TWEAK.SCALE     F32      1
+PSASTRO.TWEAK.RANGE     F32     75
+PSASTRO.TWEAK.SMOOTH    F32      2
+PSASTRO.TWEAK.NSIGMA    F32      3
+
+# match radius in pixels for CHIP astrometry
+PSASTRO.MATCH.RADIUS   F32    8
+
+# pmAstromMatchFit
+PSASTRO.CHIP.ORDER     S32      1  # fit order
+PSASTRO.CHIP.NITER     S32      3  # fit clipping iterations
+PSASTRO.CHIP.NSIGMA    F32      3  # fit clipping sigmas
+
+PSASTRO.MAX.ERROR      F32      10.0 # max error in pixels
+PSASTRO.MIN.NSTAR      S32      0   # min fitted stars in solution
+PSASTRO.MAX.NSTAR      S32      300 # max stars accepted for fitting
+
+MAG_MAX F32 10
+PSASTRO.CATDIR	       STR      SYNTH.GRIZY
+PSASTRO.MATCH.LUMFUNC  BOOL     TRUE
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/tek/psphot.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/tek/psphot.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/tek/psphot.config	(revision 22157)
@@ -0,0 +1,57 @@
+
+# turn these on to see specific outputs
+#SAVE.BACKMDL	BOOL 	TRUE
+#SAVE.BACKGND	BOOL 	TRUE
+#SAVE.BACKSUB	BOOL 	TRUE
+SAVE.RESID	BOOL 	TRUE
+#SAVE.PSF	BOOL 	TRUE
+
+# image statistics parameters
+IMSTATS_NPIX        S32  3000    	 # number of pixels to use for sky estimate boxes:
+
+PSF_SN_LIM          F32  10              # minimum S/N for stars used for PSF model
+PSF_MAX_NSTARS      S32  300             # limit number of stars used for PSF model
+
+PSF_MODEL         STR  PS_MODEL_QGAUSS
+
+MOMENTS_SN_MIN      F32   10.0
+#EXT_MIN_SN           F32  50.0           # fit galaxies above this S/N limit
+#FULL_FIT_SN_LIM      F32  50.0
+#AP_MIN_SN            F32  20.0
+PSF_CLUMP_NSIGMA   F32  2.5             # region of Sx,Sy plane to use for selecting PSF stars
+
+# PSFTREND must be a 2D polynomial
+# the specified values are ignored but define the active components of the polynomial
+PSF.TREND.MASK  METADATA  
+   NORDER_X         S32       3                # number of x orders
+   NORDER_Y         S32       3                # number of y orders
+   VAL_X00_Y00      F64       1                # polynomial coefficient
+
+   VAL_X01_Y00      F64       1                # polynomial coefficient
+   VAL_X00_Y01      F64       1                # polynomial coefficient
+
+   VAL_X02_Y00      F64       1                # polynomial coefficient
+   VAL_X01_Y01      F64       1                # polynomial coefficient
+   VAL_X00_Y02      F64       1                # polynomial coefficient
+
+   VAL_X03_Y00      F64       1                # polynomial coefficient
+   VAL_X02_Y01      F64       1                # polynomial coefficient
+   VAL_X01_Y02      F64       1                # polynomial coefficient
+   VAL_X00_Y03      F64       1                # polynomial coefficient
+   NELEMENTS        S32       10               # number of unmasked components
+END  # folder for 4D polynomial
+
+XMIN F32 15
+PSF.RESIDUALS       BOOL false
+BREAK_POINT         STR  ENSEMBLE
+OUTPUT.FORMAT       STR  PS1_DEV_0
+
+PSPHOT.SUMMIT METADATA
+ PEAKS_SMOOTH_SIGMA  F32  0.8 	   	 # peak significance threshold
+ PEAKS_NSIGMA_LIMIT  F32  50.0 	   	 # peak significance threshold
+ PEAKS_NMAX          S32  1000
+ SAVE.RESID	BOOL 	FALSE
+ PSF_SN_LIM          F32  25              # minimum S/N for stars used for PSF model
+ MOMENTS_SN_MIN      F32   25.0
+ PSF_MODEL         STR  PS_MODEL_PGAUSS
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/tek/rejections.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/tek/rejections.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/tek/rejections.config	(revision 22157)
@@ -0,0 +1,47 @@
+BIAS METADATA
+  FILTER             STR  *
+  EXPECTED           F32  0.0
+  IMFILE.MEAN        F32  2.0
+  IMFILE.STDEV       F32 10.0
+  IMFILE.MEANSTDEV   F32  0.5
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SN          F32  0.0
+  IMFILE.BIN.STDEV   F32  5.0
+  IMFILE.BIN.SN      F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  1.0
+  EXP.STDEV          F32 10.0
+  EXP.MEANSTDEV      F32  0.5
+  EXP.SN             F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SN         F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  3.0
+  ENSEMBLE.STDEV     F32  3.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+DARK METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  0.0
+  IMFILE.STDEV       F32  0.0
+  IMFILE.MEANSTDEV   F32  0.0
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SN          F32  0.0
+  IMFILE.BIN.STDEV   F32  0.0
+  IMFILE.BIN.SN      F32  0.0
+  IMFILE.FLUX        F32  2.0
+  EXP.MEAN           F32  0.0
+  EXP.STDEV          F32  0.0
+  EXP.MEANSTDEV      F32  0.0
+  EXP.SN             F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SN         F32  0.0
+  EXP.FLUX           F32  2.0
+  ENSEMBLE.MEAN      F32  3.0
+  ENSEMBLE.STDEV     F32  3.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/camera.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/camera.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/camera.config	(revision 22157)
@@ -0,0 +1,50 @@
+# Camera configuration file for MegaCam: describes the camera
+
+# File formats that we know about (these are relative to PATH)
+FORMATS         METADATA
+        SIMPLE  STR     ucam/format_raw.config
+END
+
+
+# Description of camera --- all the chips and the cells that comprise them
+FPA     METADATA
+	chip	STR	xy00 xy10 xy20 xy30 xy40 xy50 xy60 xy70 xy01 xy11 xy21 xy31 xy41 xy51 xy61 xy71 xy02 xy12 xy22 xy32 xy42 xy52 xy62 xy72 xy03 xy13 xy23 xy33 xy43 xy53 xy63 xy73 xy04 xy14 xy24 xy34 xy44 xy54 xy64 xy74 xy05 xy15 xy25 xy35 xy45 xy55 xy65 xy75 xy06 xy16 xy26 xy36 xy46 xy56 xy66 xy76 xy07 xy17 xy27 xy37 xy47 xy57 xy67 xy77
+END
+
+# move this elsewhere?  we need a lookup table to go from filter ID to abstract name
+FILTER.ID       METADATA
+   g        STR   g
+   r        STR   r
+   i        STR   i
+   z        STR   z
+   y        STR   y
+END
+
+# Table of strings to use for the class identifier (see ippdb) according to the file level.
+CLASSID		METADATA
+	fpa	STR	fpa
+	chip	STR	{CHIP.NAME}
+	cell	STR	{CHIP.NAME}:{CELL.NAME}
+END
+
+# convert supplied FPA.OBSTYPE values to abstract exptype names
+OBSTYPE.TABLE METADATA
+  bias 	   STR BIAS
+  zero 	   STR BIAS
+  dark 	   STR DARK
+  flat 	   STR SKYFLAT
+  skyflat  STR SKYFLAT
+  domeflat STR DOMEFLAT
+  object   STR OBJECT
+  science  STR OBJECT
+END
+
+# Recipe options
+RECIPES         METADATA
+	# Recipes for ppImage
+        # PPIMAGE         STR     ucam/ppImage.config     # Default: all (normal) options on
+END
+
+FITSTYPES       STR     recipes/fitstypes.mdc
+
+FILERULES       STR     recipes/filerules-simple.mdc
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/format_raw.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/format_raw.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/format_raw.config	(revision 22157)
@@ -0,0 +1,145 @@
+### Raw data from microCam
+
+# How to identify this type
+RULE	METADATA
+	ORIGIN		STR	PS0
+#	INSTRUME	STR	ucam1
+	EXTEND		BOOL	T
+	NEXTEND		S32	64
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	CELL	# The extensions represent cells
+	FPA.OBS		STR	OBSTYPE	# A PHU keyword for unique identifier within the hierarchy level
+END
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	xy00	STR	chip:xy00:otCell
+	xy10	STR	chip:xy10:otCell
+	xy20	STR	chip:xy20:otCell
+	xy30	STR	chip:xy30:otCell
+	xy40	STR	chip:xy40:otCell
+	xy50	STR	chip:xy50:otCell
+	xy60	STR	chip:xy60:otCell
+	xy70	STR	chip:xy70:otCell
+	xy01	STR	chip:xy01:otCell
+	xy11	STR	chip:xy11:otCell
+	xy21	STR	chip:xy21:otCell
+	xy31	STR	chip:xy31:otCell
+	xy41	STR	chip:xy41:otCell
+	xy51	STR	chip:xy51:otCell
+	xy61	STR	chip:xy61:otCell
+	xy71	STR	chip:xy71:otCell
+	xy02	STR	chip:xy02:otCell
+	xy12	STR	chip:xy12:otCell
+	xy22	STR	chip:xy22:otCell
+	xy32	STR	chip:xy32:otCell
+	xy42	STR	chip:xy42:otCell
+	xy52	STR	chip:xy52:otCell
+	xy62	STR	chip:xy62:otCell
+	xy72	STR	chip:xy72:otCell
+	xy03	STR	chip:xy03:otCell
+	xy13	STR	chip:xy13:otCell
+	xy23	STR	chip:xy23:otCell
+	xy33	STR	chip:xy33:otCell
+	xy43	STR	chip:xy43:otCell
+	xy53	STR	chip:xy53:otCell
+	xy63	STR	chip:xy63:otCell
+	xy73	STR	chip:xy73:otCell
+	xy04	STR	chip:xy04:otCell
+	xy14	STR	chip:xy14:otCell
+	xy24	STR	chip:xy24:otCell
+	xy34	STR	chip:xy34:otCell
+	xy44	STR	chip:xy44:otCell
+	xy54	STR	chip:xy54:otCell
+	xy64	STR	chip:xy64:otCell
+	xy74	STR	chip:xy74:otCell
+	xy05	STR	chip:xy05:otCell
+	xy15	STR	chip:xy15:otCell
+	xy25	STR	chip:xy25:otCell
+	xy35	STR	chip:xy35:otCell
+	xy45	STR	chip:xy45:otCell
+	xy55	STR	chip:xy55:otCell
+	xy65	STR	chip:xy65:otCell
+	xy75	STR	chip:xy75:otCell
+	xy06	STR	chip:xy06:otCell
+	xy16	STR	chip:xy16:otCell
+	xy26	STR	chip:xy26:otCell
+	xy36	STR	chip:xy36:otCell
+	xy46	STR	chip:xy46:otCell
+	xy56	STR	chip:xy56:otCell
+	xy66	STR	chip:xy66:otCell
+	xy76	STR	chip:xy76:otCell
+	xy07	STR	chip:xy07:otCell
+	xy17	STR	chip:xy17:otCell
+	xy27	STR	chip:xy27:otCell
+	xy37	STR	chip:xy37:otCell
+	xy47	STR	chip:xy47:otCell
+	xy57	STR	chip:xy57:otCell
+	xy67	STR	chip:xy67:otCell
+	xy77	STR	chip:xy77:otCell
+END
+
+# Specify the cell data
+CELLS	METADATA
+	otCell	METADATA	# Orthogonal transfer cell
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BIASSEC
+		CELL.TRIMSEC		STR	DATASEC
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.AIRMASS		STR	AIRMASS
+	FPA.FILTER		STR	FILTER
+	FPA.POSANGLE		STR	ROTANGLE
+	FPA.RA			STR	RA
+	FPA.DEC			STR	DEC
+	FPA.RADECSYS		STR	RADECSYS
+	CELL.TIME		STR	DATE-OBS
+	CELL.EXPOSURE		STR	EXPTIME
+	CELL.DARKTIME		STR	DARKTIME
+	CELL.XBIN		STR	CCDBIN1
+	CELL.YBIN		STR	CCDBIN2
+	CELL.XPARITY		STR	IMTM1_1
+	CELL.YPARITY		STR	IMTM2_2
+	CELL.X0			STR	IMNPIX1
+	CELL.Y0			STR	IMNPIX2
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	CELL.GAIN		F32	1
+	CELL.READNOISE		F32	10.0
+	CELL.SATURATION		F32	65535
+	CELL.BAD		F32	0
+	CELL.READDIR		S32	1
+	CELL.TIMESYS		STR	UTC
+	CHIP.X0			S32	0
+	CHIP.Y0			S32	0
+	CHIP.XPARITY		S32	1
+	CHIP.YPARITY		S32	1
+END
+
+# How to translation PS concepts into database lookups
+DATABASE	METADATA
+	TYPE		dbEntry		TABLE		COLUMN		GIVENDBCOL	GIVENPS
+# A database entry refers to a particular column (COLUMN) in a
+# particular table (TABLE), given certain PS concepts (GIVENPS) that
+# match certain database columns (GIVENDBCOL).
+END
+
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	CELL.TIME	STR	MJD
+	CELL.X0		STR	FORTRAN
+	CELL.Y0		STR	FORTRAN
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage.config	(revision 22157)
@@ -0,0 +1,82 @@
+### Phase 2 recipe configuration file
+
+# List of tasks to perform
+MASK		BOOL	FALSE		# Mask bad pixels
+MASK.VALUE      U8      0x10            # only mask pixels matching this bitmask
+NONLIN		BOOL	FALSE		# Non-linearity correction
+OVERSCAN	BOOL	TRUE		# Overscan subtraction
+BIAS		BOOL	TRUE		# Bias subtraction
+DARK		BOOL	TRUE		# Dark subtraction
+FLAT		BOOL	TRUE		# Flat-field normalisation
+FRINGE		BOOL	FALSE		# Fringe subtraction
+PHOTOM		BOOL	FALSE		# Source identification and photometry
+ASTROM.CHIP	BOOL	FALSE
+ASTROM.MOSAIC	BOOL	FALSE		# Astrometry
+
+BASE.FITS       BOOL    TRUE            # Save base detrended image?
+CHIP.FITS       BOOL   	TRUE            # Save chip-mosaic-ed image? 
+FPA1.FITS       BOOL   	TRUE            # Save 1st binned fpa image? 
+FPA2.FITS       BOOL   	TRUE            # Save 2nd binned fpa image? 
+BIN1.FITS	BOOL	TRUE		# Save 1st binned chip image?
+BIN2.FITS	BOOL	TRUE		# Save 2nd binned chip image?
+BIN1.JPEG	BOOL	TRUE		# Save 1st binned jpeg?
+BIN2.JPEG	BOOL	FALSE		# Save 2nd binned jpeg?
+
+# Non-linearity correction
+NONLIN.SOURCE		STR	CHIP.NAME	# How to determine the source
+#@NONLIN.DATA		F32	0.0 1.001 0.001	# A polynomial
+NONLIN.DATA		STR	nonlin.dat	# Filename for lookup table
+#NONLIN.DATA		METADATA		# Source of non-linearity data
+#	ccd00		STR	nonlin00.dat	# A lookup table 
+#	@ccd01		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd02		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd03		F32	1.2345 		# A polynomial
+#	@ccd04		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd05		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd06		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd07		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd08		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd09		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd10		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd11		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd12		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd13		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd14		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd15		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd16		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd17		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd18		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd19		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd10		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd21		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd22		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd23		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd24		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd25		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd26		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd27		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd28		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd29		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd30		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd31		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd32		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd33		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd34		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd35		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd36		F32	0.0 1.001 0.001	# A polynomial
+#END
+
+# Overscan subtraction
+OVERSCAN.SINGLE		BOOL	FALSE		# Reduce overscan to a single value?
+#OVERSCAN.FIT		STR	SPLINE		# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.FIT		STR	POLYNOMIAL	# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER		S32	5		# Order of polynomial fit
+OVERSCAN.STAT		STR	MEAN		# MEAN | MEDIAN
+
+# binned output image options
+BIN1.XBIN		S32	8
+BIN1.YBIN		S32	8
+BIN2.XBIN		S32	64
+BIN2.YBIN		S32	64
+
+PHOTCODE.RULE		STR	{CAMERA}.{FILTER.ID}.{CHIP.N}
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage_b.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage_b.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage_b.config	(revision 22157)
@@ -0,0 +1,82 @@
+### Phase 2 recipe configuration file
+
+# List of tasks to perform
+MASK		BOOL	FALSE		# Mask bad pixels
+MASK.VALUE      U8      0x10            # only mask pixels matching this bitmask
+NONLIN		BOOL	FALSE		# Non-linearity correction
+OVERSCAN	BOOL	FALSE		# Overscan subtraction
+BIAS		BOOL	TRUE		# Bias subtraction
+DARK		BOOL	FALSE		# Dark subtraction
+FLAT		BOOL	FALSE		# Flat-field normalisation
+FRINGE		BOOL	FALSE		# Fringe subtraction
+PHOTOM		BOOL	FALSE		# Source identification and photometry
+ASTROM.CHIP	BOOL	FALSE
+ASTROM.MOSAIC	BOOL	FALSE		# Astrometry
+
+BASE.FITS       BOOL    TRUE            # Save base detrended image?
+CHIP.FITS       BOOL   	FALSE           # Save chip-mosaic-ed image? 
+FPA1.FITS       BOOL   	FALSE           # Save 1st binned fpa image? 
+FPA2.FITS       BOOL   	FALSE           # Save 2nd binned fpa image? 
+BIN1.FITS	BOOL	TRUE		# Save 1st binned chip image?
+BIN2.FITS	BOOL	TRUE		# Save 2nd binned chip image?
+BIN1.JPEG	BOOL	FALSE		# Save 1st binned jpeg?
+BIN2.JPEG	BOOL	FALSE		# Save 2nd binned jpeg?
+
+# Non-linearity correction
+NONLIN.SOURCE		STR	CHIP.NAME	# How to determine the source
+#@NONLIN.DATA		F32	0.0 1.001 0.001	# A polynomial
+NONLIN.DATA		STR	nonlin.dat	# Filename for lookup table
+#NONLIN.DATA		METADATA		# Source of non-linearity data
+#	ccd00		STR	nonlin00.dat	# A lookup table 
+#	@ccd01		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd02		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd03		F32	1.2345 		# A polynomial
+#	@ccd04		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd05		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd06		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd07		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd08		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd09		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd10		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd11		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd12		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd13		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd14		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd15		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd16		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd17		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd18		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd19		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd10		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd21		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd22		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd23		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd24		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd25		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd26		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd27		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd28		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd29		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd30		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd31		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd32		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd33		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd34		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd35		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd36		F32	0.0 1.001 0.001	# A polynomial
+#END
+
+# Overscan subtraction
+OVERSCAN.SINGLE		BOOL	FALSE		# Reduce overscan to a single value?
+#OVERSCAN.FIT		STR	SPLINE		# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.FIT		STR	POLYNOMIAL	# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER		S32	5		# Order of polynomial fit
+OVERSCAN.STAT		STR	MEAN		# MEAN | MEDIAN
+
+# binned output image options
+BIN1.XBIN		S32	8
+BIN1.YBIN		S32	8
+BIN2.XBIN		S32	64
+BIN2.YBIN		S32	64
+
+PHOTCODE.RULE		STR	{CAMERA}.{FILTER.ID}.{CHIP.N}
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage_d.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage_d.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage_d.config	(revision 22157)
@@ -0,0 +1,82 @@
+### Phase 2 recipe configuration file
+
+# List of tasks to perform
+MASK		BOOL	FALSE		# Mask bad pixels
+MASK.VALUE      U8      0x10            # only mask pixels matching this bitmask
+NONLIN		BOOL	FALSE		# Non-linearity correction
+OVERSCAN	BOOL	FALSE		# Overscan subtraction
+BIAS		BOOL	FALSE		# Bias subtraction
+DARK		BOOL	TRUE		# Dark subtraction
+FLAT		BOOL	FALSE		# Flat-field normalisation
+FRINGE		BOOL	FALSE		# Fringe subtraction
+PHOTOM		BOOL	FALSE		# Source identification and photometry
+ASTROM.CHIP	BOOL	FALSE
+ASTROM.MOSAIC	BOOL	FALSE		# Astrometry
+
+BASE.FITS       BOOL    TRUE            # Save base detrended image?
+CHIP.FITS       BOOL   	FALSE           # Save chip-mosaic-ed image? 
+FPA1.FITS       BOOL   	FALSE           # Save 1st binned fpa image? 
+FPA2.FITS       BOOL   	FALSE           # Save 2nd binned fpa image? 
+BIN1.FITS	BOOL	TRUE		# Save 1st binned chip image?
+BIN2.FITS	BOOL	TRUE		# Save 2nd binned chip image?
+BIN1.JPEG	BOOL	FALSE		# Save 1st binned jpeg?
+BIN2.JPEG	BOOL	FALSE		# Save 2nd binned jpeg?
+
+# Non-linearity correction
+NONLIN.SOURCE		STR	CHIP.NAME	# How to determine the source
+#@NONLIN.DATA		F32	0.0 1.001 0.001	# A polynomial
+NONLIN.DATA		STR	nonlin.dat	# Filename for lookup table
+#NONLIN.DATA		METADATA		# Source of non-linearity data
+#	ccd00		STR	nonlin00.dat	# A lookup table 
+#	@ccd01		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd02		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd03		F32	1.2345 		# A polynomial
+#	@ccd04		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd05		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd06		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd07		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd08		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd09		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd10		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd11		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd12		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd13		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd14		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd15		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd16		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd17		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd18		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd19		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd10		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd21		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd22		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd23		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd24		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd25		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd26		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd27		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd28		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd29		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd30		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd31		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd32		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd33		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd34		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd35		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd36		F32	0.0 1.001 0.001	# A polynomial
+#END
+
+# Overscan subtraction
+OVERSCAN.SINGLE		BOOL	FALSE		# Reduce overscan to a single value?
+#OVERSCAN.FIT		STR	SPLINE		# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.FIT		STR	POLYNOMIAL	# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER		S32	5		# Order of polynomial fit
+OVERSCAN.STAT		STR	MEAN		# MEAN | MEDIAN
+
+# binned output image options
+BIN1.XBIN		S32	8
+BIN1.YBIN		S32	8
+BIN2.XBIN		S32	64
+BIN2.YBIN		S32	64
+
+PHOTCODE.RULE		STR	{CAMERA}.{FILTER.ID}.{CHIP.N}
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage_f.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage_f.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage_f.config	(revision 22157)
@@ -0,0 +1,82 @@
+### Phase 2 recipe configuration file
+
+# List of tasks to perform
+MASK		BOOL	FALSE		# Mask bad pixels
+MASK.VALUE      U8      0x10            # only mask pixels matching this bitmask
+NONLIN		BOOL	FALSE		# Non-linearity correction
+OVERSCAN	BOOL	FALSE		# Overscan subtraction
+BIAS		BOOL	FALSE		# Bias subtraction
+DARK		BOOL	FALSE		# Dark subtraction
+FLAT		BOOL	TRUE		# Flat-field normalisation
+FRINGE		BOOL	FALSE		# Fringe subtraction
+PHOTOM		BOOL	FALSE		# Source identification and photometry
+ASTROM.CHIP	BOOL	FALSE
+ASTROM.MOSAIC	BOOL	FALSE		# Astrometry
+
+BASE.FITS       BOOL    TRUE            # Save base detrended image?
+CHIP.FITS       BOOL   	FALSE           # Save chip-mosaic-ed image? 
+FPA1.FITS       BOOL   	FALSE           # Save 1st binned fpa image? 
+FPA2.FITS       BOOL   	FALSE           # Save 2nd binned fpa image? 
+BIN1.FITS	BOOL	TRUE		# Save 1st binned chip image?
+BIN2.FITS	BOOL	TRUE		# Save 2nd binned chip image?
+BIN1.JPEG	BOOL	FALSE		# Save 1st binned jpeg?
+BIN2.JPEG	BOOL	FALSE		# Save 2nd binned jpeg?
+
+# Non-linearity correction
+NONLIN.SOURCE		STR	CHIP.NAME	# How to determine the source
+#@NONLIN.DATA		F32	0.0 1.001 0.001	# A polynomial
+NONLIN.DATA		STR	nonlin.dat	# Filename for lookup table
+#NONLIN.DATA		METADATA		# Source of non-linearity data
+#	ccd00		STR	nonlin00.dat	# A lookup table 
+#	@ccd01		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd02		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd03		F32	1.2345 		# A polynomial
+#	@ccd04		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd05		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd06		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd07		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd08		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd09		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd10		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd11		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd12		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd13		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd14		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd15		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd16		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd17		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd18		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd19		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd10		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd21		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd22		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd23		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd24		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd25		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd26		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd27		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd28		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd29		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd30		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd31		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd32		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd33		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd34		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd35		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd36		F32	0.0 1.001 0.001	# A polynomial
+#END
+
+# Overscan subtraction
+OVERSCAN.SINGLE		BOOL	FALSE		# Reduce overscan to a single value?
+#OVERSCAN.FIT		STR	SPLINE		# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.FIT		STR	POLYNOMIAL	# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER		S32	5		# Order of polynomial fit
+OVERSCAN.STAT		STR	MEAN		# MEAN | MEDIAN
+
+# binned output image options
+BIN1.XBIN		S32	8
+BIN1.YBIN		S32	8
+BIN2.XBIN		S32	64
+BIN2.YBIN		S32	64
+
+PHOTCODE.RULE		STR	{CAMERA}.{FILTER.ID}.{CHIP.N}
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage_j.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage_j.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage_j.config	(revision 22157)
@@ -0,0 +1,85 @@
+### Phase 2 recipe configuration file
+
+### A little bit of a cheat to make a mosaicked binned JPEG from the binned FITS file.
+### Set binning to 1, since the input will already be binned.
+
+# List of tasks to perform
+MASK		BOOL	FALSE		# Mask bad pixels
+MASK.VALUE      U8      0xff            # only mask pixels matching this bitmask
+NONLIN		BOOL	FALSE		# Non-linearity correction
+OVERSCAN	BOOL	FALSE		# Overscan subtraction
+BIAS		BOOL	FALSE		# Bias subtraction
+DARK		BOOL	FALSE		# Dark subtraction
+FLAT		BOOL	FALSE		# Flat-field normalisation
+FRINGE		BOOL	FALSE		# Fringe subtraction
+PHOTOM		BOOL	FALSE		# Source identification and photometry
+ASTROM.CHIP	BOOL	FALSE
+ASTROM.MOSAIC	BOOL	FALSE		# Astrometry
+
+BASE.FITS       BOOL    FALSE            # Save base detrended image?
+CHIP.FITS       BOOL   	FALSE            # Save chip-mosaic-ed image? 
+FPA1.FITS       BOOL   	FALSE            # Save 1st binned fpa image? 
+FPA2.FITS       BOOL   	FALSE            # Save 2nd binned fpa image? 
+BIN1.FITS	BOOL	FALSE		# Save 1st binned chip image?
+BIN2.FITS	BOOL	FALSE		# Save 2nd binned chip image?
+BIN1.JPEG	BOOL	TRUE		# Save 1st binned jpeg?
+BIN2.JPEG	BOOL	FALSE		# Save 2nd binned jpeg?
+
+# Non-linearity correction
+NONLIN.SOURCE		STR	CHIP.NAME	# How to determine the source
+#@NONLIN.DATA		F32	0.0 1.001 0.001	# A polynomial
+NONLIN.DATA		STR	nonlin.dat	# Filename for lookup table
+#NONLIN.DATA		METADATA		# Source of non-linearity data
+#	ccd00		STR	nonlin00.dat	# A lookup table 
+#	@ccd01		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd02		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd03		F32	1.2345 		# A polynomial
+#	@ccd04		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd05		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd06		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd07		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd08		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd09		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd10		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd11		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd12		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd13		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd14		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd15		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd16		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd17		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd18		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd19		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd10		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd21		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd22		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd23		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd24		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd25		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd26		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd27		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd28		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd29		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd30		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd31		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd32		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd33		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd34		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd35		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd36		F32	0.0 1.001 0.001	# A polynomial
+#END
+
+# Overscan subtraction
+OVERSCAN.SINGLE		BOOL	FALSE		# Reduce overscan to a single value?
+#OVERSCAN.FIT		STR	SPLINE		# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.FIT		STR	POLYNOMIAL	# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER		S32	5		# Order of polynomial fit
+OVERSCAN.STAT		STR	MEAN		# MEAN | MEDIAN
+
+# binned output image options
+BIN1.XBIN		S32	1
+BIN1.YBIN		S32	1
+BIN2.XBIN		S32	64
+BIN2.YBIN		S32	64
+
+PHOTCODE.RULE		STR	{CAMERA}.{FILTER.ID}.{CHIP.N}
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage_o.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage_o.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage_o.config	(revision 22157)
@@ -0,0 +1,82 @@
+### Phase 2 recipe configuration file
+
+# List of tasks to perform
+MASK		BOOL	FALSE		# Mask bad pixels
+MASK.VALUE      U8      0x10            # only mask pixels matching this bitmask
+NONLIN		BOOL	FALSE		# Non-linearity correction
+OVERSCAN	BOOL	TRUE		# Overscan subtraction
+BIAS		BOOL	FALSE		# Bias subtraction
+DARK		BOOL	FALSE		# Dark subtraction
+FLAT		BOOL	FALSE		# Flat-field normalisation
+FRINGE		BOOL	FALSE		# Fringe subtraction
+PHOTOM		BOOL	FALSE		# Source identification and photometry
+ASTROM.CHIP	BOOL	FALSE
+ASTROM.MOSAIC	BOOL	FALSE		# Astrometry
+
+BASE.FITS       BOOL    TRUE            # Save base detrended image?
+CHIP.FITS       BOOL   	FALSE           # Save chip-mosaic-ed image? 
+FPA1.FITS       BOOL   	FALSE           # Save 1st binned fpa image? 
+FPA2.FITS       BOOL   	FALSE           # Save 2nd binned fpa image? 
+BIN1.FITS	BOOL	FALSE		# Save 1st binned chip image?
+BIN2.FITS	BOOL	FALSE		# Save 2nd binned chip image?
+BIN1.JPEG	BOOL	FALSE		# Save 1st binned jpeg?
+BIN2.JPEG	BOOL	FALSE		# Save 2nd binned jpeg?
+
+# Non-linearity correction
+NONLIN.SOURCE		STR	CHIP.NAME	# How to determine the source
+#@NONLIN.DATA		F32	0.0 1.001 0.001	# A polynomial
+NONLIN.DATA		STR	nonlin.dat	# Filename for lookup table
+#NONLIN.DATA		METADATA		# Source of non-linearity data
+#	ccd00		STR	nonlin00.dat	# A lookup table 
+#	@ccd01		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd02		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd03		F32	1.2345 		# A polynomial
+#	@ccd04		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd05		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd06		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd07		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd08		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd09		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd10		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd11		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd12		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd13		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd14		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd15		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd16		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd17		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd18		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd19		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd10		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd21		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd22		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd23		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd24		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd25		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd26		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd27		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd28		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd29		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd30		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd31		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd32		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd33		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd34		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd35		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd36		F32	0.0 1.001 0.001	# A polynomial
+#END
+
+# Overscan subtraction
+OVERSCAN.SINGLE		BOOL	FALSE		# Reduce overscan to a single value?
+#OVERSCAN.FIT		STR	SPLINE		# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.FIT		STR	POLYNOMIAL	# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER		S32	5		# Order of polynomial fit
+OVERSCAN.STAT		STR	MEAN		# MEAN | MEDIAN
+
+# binned output image options
+BIN1.XBIN		S32	8
+BIN1.YBIN		S32	8
+BIN2.XBIN		S32	64
+BIN2.YBIN		S32	64
+
+PHOTCODE.RULE		STR	{CAMERA}.{FILTER.ID}.{CHIP.N}
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage_ob.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage_ob.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage_ob.config	(revision 22157)
@@ -0,0 +1,82 @@
+### Phase 2 recipe configuration file
+
+# List of tasks to perform
+MASK		BOOL	FALSE		# Mask bad pixels
+MASK.VALUE      U8      0x10            # only mask pixels matching this bitmask
+NONLIN		BOOL	FALSE		# Non-linearity correction
+OVERSCAN	BOOL	TRUE		# Overscan subtraction
+BIAS		BOOL	TRUE		# Bias subtraction
+DARK		BOOL	FALSE		# Dark subtraction
+FLAT		BOOL	FALSE		# Flat-field normalisation
+FRINGE		BOOL	FALSE		# Fringe subtraction
+PHOTOM		BOOL	FALSE		# Source identification and photometry
+ASTROM.CHIP	BOOL	FALSE
+ASTROM.MOSAIC	BOOL	FALSE		# Astrometry
+
+BASE.FITS       BOOL    TRUE            # Save base detrended image?
+CHIP.FITS       BOOL   	FALSE           # Save chip-mosaic-ed image? 
+FPA1.FITS       BOOL   	FALSE           # Save 1st binned fpa image? 
+FPA2.FITS       BOOL   	FALSE           # Save 2nd binned fpa image? 
+BIN1.FITS	BOOL	FALSE		# Save 1st binned chip image?
+BIN2.FITS	BOOL	FALSE		# Save 2nd binned chip image?
+BIN1.JPEG	BOOL	FALSE		# Save 1st binned jpeg?
+BIN2.JPEG	BOOL	FALSE		# Save 2nd binned jpeg?
+
+# Non-linearity correction
+NONLIN.SOURCE		STR	CHIP.NAME	# How to determine the source
+#@NONLIN.DATA		F32	0.0 1.001 0.001	# A polynomial
+NONLIN.DATA		STR	nonlin.dat	# Filename for lookup table
+#NONLIN.DATA		METADATA		# Source of non-linearity data
+#	ccd00		STR	nonlin00.dat	# A lookup table 
+#	@ccd01		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd02		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd03		F32	1.2345 		# A polynomial
+#	@ccd04		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd05		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd06		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd07		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd08		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd09		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd10		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd11		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd12		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd13		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd14		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd15		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd16		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd17		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd18		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd19		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd10		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd21		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd22		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd23		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd24		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd25		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd26		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd27		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd28		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd29		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd30		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd31		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd32		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd33		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd34		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd35		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd36		F32	0.0 1.001 0.001	# A polynomial
+#END
+
+# Overscan subtraction
+OVERSCAN.SINGLE		BOOL	FALSE		# Reduce overscan to a single value?
+#OVERSCAN.FIT		STR	SPLINE		# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.FIT		STR	POLYNOMIAL	# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER		S32	5		# Order of polynomial fit
+OVERSCAN.STAT		STR	MEAN		# MEAN | MEDIAN
+
+# binned output image options
+BIN1.XBIN		S32	8
+BIN1.YBIN		S32	8
+BIN2.XBIN		S32	64
+BIN2.YBIN		S32	64
+
+PHOTCODE.RULE		STR	{CAMERA}.{FILTER.ID}.{CHIP.N}
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage_obd.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage_obd.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage_obd.config	(revision 22157)
@@ -0,0 +1,82 @@
+### Phase 2 recipe configuration file
+
+# List of tasks to perform
+MASK		BOOL	FALSE		# Mask bad pixels
+MASK.VALUE      U8      0x10            # only mask pixels matching this bitmask
+NONLIN		BOOL	FALSE		# Non-linearity correction
+OVERSCAN	BOOL	TRUE		# Overscan subtraction
+BIAS		BOOL	TRUE		# Bias subtraction
+DARK		BOOL	FALSE		# Dark subtraction
+FLAT		BOOL	FALSE		# Flat-field normalisation
+FRINGE		BOOL	FALSE		# Fringe subtraction
+PHOTOM		BOOL	FALSE		# Source identification and photometry
+ASTROM.CHIP	BOOL	FALSE
+ASTROM.MOSAIC	BOOL	FALSE		# Astrometry
+
+BASE.FITS       BOOL    TRUE            # Save base detrended image?
+CHIP.FITS       BOOL   	FALSE           # Save chip-mosaic-ed image? 
+FPA1.FITS       BOOL   	FALSE           # Save 1st binned fpa image? 
+FPA2.FITS       BOOL   	FALSE           # Save 2nd binned fpa image? 
+BIN1.FITS	BOOL	FALSE		# Save 1st binned chip image?
+BIN2.FITS	BOOL	FALSE		# Save 2nd binned chip image?
+BIN1.JPEG	BOOL	FALSE		# Save 1st binned jpeg?
+BIN2.JPEG	BOOL	FALSE		# Save 2nd binned jpeg?
+
+# Non-linearity correction
+NONLIN.SOURCE		STR	CHIP.NAME	# How to determine the source
+#@NONLIN.DATA		F32	0.0 1.001 0.001	# A polynomial
+NONLIN.DATA		STR	nonlin.dat	# Filename for lookup table
+#NONLIN.DATA		METADATA		# Source of non-linearity data
+#	ccd00		STR	nonlin00.dat	# A lookup table 
+#	@ccd01		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd02		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd03		F32	1.2345 		# A polynomial
+#	@ccd04		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd05		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd06		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd07		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd08		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd09		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd10		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd11		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd12		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd13		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd14		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd15		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd16		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd17		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd18		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd19		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd10		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd21		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd22		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd23		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd24		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd25		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd26		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd27		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd28		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd29		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd30		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd31		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd32		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd33		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd34		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd35		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd36		F32	0.0 1.001 0.001	# A polynomial
+#END
+
+# Overscan subtraction
+OVERSCAN.SINGLE		BOOL	FALSE		# Reduce overscan to a single value?
+#OVERSCAN.FIT		STR	SPLINE		# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.FIT		STR	POLYNOMIAL	# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER		S32	5		# Order of polynomial fit
+OVERSCAN.STAT		STR	MEAN		# MEAN | MEDIAN
+
+# binned output image options
+BIN1.XBIN		S32	8
+BIN1.YBIN		S32	8
+BIN2.XBIN		S32	64
+BIN2.YBIN		S32	64
+
+PHOTCODE.RULE		STR	{CAMERA}.{FILTER.ID}.{CHIP.N}
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage_obdf.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage_obdf.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppImage_obdf.config	(revision 22157)
@@ -0,0 +1,82 @@
+### Phase 2 recipe configuration file
+
+# List of tasks to perform
+MASK		BOOL	FALSE		# Mask bad pixels
+MASK.VALUE      U8      0x10            # only mask pixels matching this bitmask
+NONLIN		BOOL	FALSE		# Non-linearity correction
+OVERSCAN	BOOL	TRUE		# Overscan subtraction
+BIAS		BOOL	TRUE		# Bias subtraction
+DARK		BOOL	TRUE		# Dark subtraction
+FLAT		BOOL	TRUE		# Flat-field normalisation
+FRINGE		BOOL	FALSE		# Fringe subtraction
+PHOTOM		BOOL	FALSE		# Source identification and photometry
+ASTROM.CHIP	BOOL	FALSE
+ASTROM.MOSAIC	BOOL	FALSE		# Astrometry
+
+BASE.FITS       BOOL    TRUE            # Save base detrended image?
+CHIP.FITS       BOOL   	FALSE           # Save chip-mosaic-ed image? 
+FPA1.FITS       BOOL   	FALSE           # Save 1st binned fpa image? 
+FPA2.FITS       BOOL   	FALSE           # Save 2nd binned fpa image? 
+BIN1.FITS	BOOL	FALSE		# Save 1st binned chip image?
+BIN2.FITS	BOOL	FALSE		# Save 2nd binned chip image?
+BIN1.JPEG	BOOL	TRUE		# Save 1st binned jpeg?
+BIN2.JPEG	BOOL	TRUE		# Save 2nd binned jpeg?
+
+# Non-linearity correction
+NONLIN.SOURCE		STR	CHIP.NAME	# How to determine the source
+#@NONLIN.DATA		F32	0.0 1.001 0.001	# A polynomial
+NONLIN.DATA		STR	nonlin.dat	# Filename for lookup table
+#NONLIN.DATA		METADATA		# Source of non-linearity data
+#	ccd00		STR	nonlin00.dat	# A lookup table 
+#	@ccd01		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd02		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd03		F32	1.2345 		# A polynomial
+#	@ccd04		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd05		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd06		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd07		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd08		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd09		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd10		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd11		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd12		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd13		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd14		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd15		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd16		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd17		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd18		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd19		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd10		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd21		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd22		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd23		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd24		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd25		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd26		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd27		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd28		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd29		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd30		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd31		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd32		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd33		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd34		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd35		F32	0.0 1.001 0.001	# A polynomial
+#	@ccd36		F32	0.0 1.001 0.001	# A polynomial
+#END
+
+# Overscan subtraction
+OVERSCAN.SINGLE		BOOL	FALSE		# Reduce overscan to a single value?
+#OVERSCAN.FIT		STR	SPLINE		# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.FIT		STR	POLYNOMIAL	# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER		S32	5		# Order of polynomial fit
+OVERSCAN.STAT		STR	MEAN		# MEAN | MEDIAN
+
+# binned output image options
+BIN1.XBIN		S32	8
+BIN1.YBIN		S32	8
+BIN2.XBIN		S32	64
+BIN2.YBIN		S32	64
+
+PHOTCODE.RULE		STR	{CAMERA}.{FILTER.ID}.{CHIP.N}
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppMerge_bias.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppMerge_bias.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppMerge_bias.config	(revision 22157)
@@ -0,0 +1,14 @@
+# Recipe configuration for ppMerge
+ 
+ROWS            S32     128		# Number of rows to read at once
+ELECTRONS       F32     100.0           # Minimum number of electrons for useful signal
+SAMPLE          S32     100             # Sampling factor for measuring the background
+REJ		F32	3.0		# Rejection threshold (sigma)
+ITER		S32	1		# Number of rejection iterations
+FRACHIGH	F32	0.3		# Fraction of high pixels to reject immediately
+FRACLOW		F32	0.1		# Fraction of low pixels to reject immediately
+NKEEP		S32	5		# Minimum number of pixels in stack to keep
+MASKVAL		S32	0xff		# Mask value for input data
+### Statistics options: MEAN | MEDIAN | ROBUST | FITTED | CLIPPED
+COMBINE		STR	MEAN		# Statistic to use for combination: 
+BACKGROUND	STR	MEDIAN		# Statistic to use to measure the background
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppMerge_dark.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppMerge_dark.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppMerge_dark.config	(revision 22157)
@@ -0,0 +1,14 @@
+# Recipe configuration for ppMerge
+ 
+ROWS            S32     128		# Number of rows to read at once
+ELECTRONS       F32     100.0           # Minimum number of electrons for useful signal
+SAMPLE          S32     100             # Sampling factor for measuring the background
+REJ		F32	3.0		# Rejection threshold (sigma)
+ITER		S32	1		# Number of rejection iterations
+FRACHIGH	F32	0.3		# Fraction of high pixels to reject immediately
+FRACLOW		F32	0.1		# Fraction of low pixels to reject immediately
+NKEEP		S32	5		# Minimum number of pixels in stack to keep
+MASKVAL		S32	0xff		# Mask value for input data
+### Statistics options: MEAN | MEDIAN | ROBUST | FITTED | CLIPPED
+COMBINE		STR	MEAN		# Statistic to use for combination: 
+BACKGROUND	STR	MEDIAN		# Statistic to use to measure the background
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppMerge_flat.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppMerge_flat.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/ppMerge_flat.config	(revision 22157)
@@ -0,0 +1,14 @@
+# Recipe configuration for ppMerge
+ 
+ROWS            S32     128		# Number of rows to read at once
+ELECTRONS       F32     100.0           # Minimum number of electrons for useful signal
+SAMPLE          S32     100             # Sampling factor for measuring the background
+REJ		F32	3.0		# Rejection threshold (sigma)
+ITER		S32	1		# Number of rejection iterations
+FRACHIGH	F32	0.3		# Fraction of high pixels to reject immediately
+FRACLOW		F32	0.1		# Fraction of low pixels to reject immediately
+NKEEP		S32	5		# Minimum number of pixels in stack to keep
+MASKVAL		S32	0xff		# Mask value for input data
+### Statistics options: MEAN | MEDIAN | ROBUST | FITTED | CLIPPED
+COMBINE		STR	MEAN		# Statistic to use for combination: 
+BACKGROUND	STR	MEDIAN		# Statistic to use to measure the background
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/psastro.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/psastro.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/psastro.config	(revision 22157)
@@ -0,0 +1,36 @@
+
+# astrometry matching parameters
+
+# nominal plate scale (microns / pixel)
+# PSASTRO.PLATE.SCALE    F32  13.5
+PSASTRO.PLATE.SCALE    F32  1.0
+
+# pmAstromGridMatch:
+PSASTRO.GRID.MIN.ANGLE F32 -0.0851
+PSASTRO.GRID.MAX.ANGLE F32 +0.0851
+PSASTRO.GRID.DEL.ANGLE F32  0.0170
+
+# pmAstromGridAngle
+# max grid offset in FP units (microns)
+# use plate-scale to make this in pixels?
+# PSASTRO.GRID.OFFSET    F32  10000.
+# PSASTRO.GRID.SCALE     F32    500.
+PSASTRO.GRID.OFFSET    F32   1000.
+PSASTRO.GRID.SCALE     F32     50
+
+# these tweak are in FP units (pixels, currently)
+PSASTRO.TWEAK.SCALE     F32      1
+PSASTRO.TWEAK.RANGE     F32     75
+PSASTRO.TWEAK.SMOOTH    F32      2
+PSASTRO.TWEAK.NSIGMA    F32      3
+
+# pmAstromRadiusMatch
+# use plate-scale to make this in pixels?
+PSASTRO.MATCH.RADIUS   F32    8
+
+# pmAstromMatchFit
+PSASTRO.CHIP.NX        S32      1  # fit order
+PSASTRO.CHIP.NY        S32      1  # fit order
+PSASTRO.CHIP.NITER     S32      3  # fit clipping iterations
+PSASTRO.CHIP.NSIGMA    F32      3  # fit clipping sigmas
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/psphot.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/psphot.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/ucam/psphot.config	(revision 22157)
@@ -0,0 +1,27 @@
+
+# turn these on to see specific outputs
+#SAVE.BACKMDL	BOOL 	TRUE
+#SAVE.BACKGND	BOOL 	TRUE
+#SAVE.BACKSUB	BOOL 	TRUE
+SAVE.RESID	BOOL 	TRUE
+#SAVE.PSF	BOOL 	TRUE
+#LOAD.PSF	BOOL 	FALSE
+
+IMSTATS_NPIX        S32  1000    	 # number of pixels to use for sky estimate boxes:
+
+PSF_SN_LIM          F32  100             # minimum S/N for stars used for PSF model
+PSF_MAX_NSTARS      S32  300             # limit number of stars used for PSF model
+
+# PSF model parameters : choose the PSF model
+# list as many PSF_MODEL options as desired
+# if you want to list multiple entries, uncomment 'MULTI' here
+# PSF_MODEL           MULTI
+PSF_MODEL           STR  PS_MODEL_QGAUSS
+# PSF_MODEL           STR  PS_MODEL_GAUSS
+# PSF_MODEL           STR  PS_MODEL_PGAUSS
+# PSF_MODEL           STR  PS_MODEL_TGAUSS  ## not well tested, not very successful
+
+MOMENTS_SN_MIN      F32  20.0           # min S/N to measure moments
+EXT_MIN_SN           F32  50.0           # fit galaxies above this S/N limit
+FULL_FIT_SN_LIM      F32  50.0
+AP_MIN_SN            F32  25.0
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/.cvsignore	(revision 22157)
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/Makefile.am	(revision 22157)
@@ -0,0 +1,22 @@
+
+installdir = $(datadir)/ippconfig/uh8k
+
+install_files = \
+	dvo.config \
+	camera.config \
+	format_split.config \
+	ppImage.config \
+	ppMerge.config \
+	psastro.config \
+	psphot.config \
+	pswarp.config \
+	rejections.config
+
+install_DATA = $(install_files)
+
+install-data-hook:
+	chmod 0755 $(installdir)
+
+EXTRA_DIST = $(install_files)
+
+ACLOCAL_AMFLAGS = -I m4
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/camera.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/camera.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/camera.config	(revision 22157)
@@ -0,0 +1,85 @@
+# Camera configuration file for WFI/8k on UH88
+
+# File formats that we know about
+FORMATS         METADATA
+        SPLIT   STR     uh8k/format_split.config
+END
+
+# Description of camera --- all the chips and the cells that comprise them
+FPA     METADATA
+        chip00   STR     Cell
+        chip01   STR     Cell
+        chip02   STR     Cell
+        chip03   STR     Cell
+        chip04   STR     Cell
+        chip05   STR     Cell
+        chip06   STR     Cell
+        chip07   STR     Cell
+END
+
+# move this elsewhere?  we need a lookup table to go from filter ID to abstract name
+FILTER.ID       METADATA
+	UKNOWN	STR	UNKNOWN
+END
+
+# Table of strings to use for the class identifier (see ippdb) according to the file level.
+CLASSID         METADATA
+        FPA     STR     fpa
+        CHIP    STR     {CHIP.NAME}
+        CELL    STR     {CHIP.NAME}:{CELL.NAME}
+        fpa     STR     fpa
+        chip    STR     {CHIP.NAME}
+        cell    STR     {CHIP.NAME}:{CELL.NAME}
+END
+
+DVO.CAMERADIR	STR	uh8k		# Camera directory for DVO
+
+# convert supplied FPA.OBSTYPE values to abstract exptype names
+OBSTYPE.TABLE METADATA
+  bias     STR BIAS
+  zero     STR BIAS
+  dark     STR DARK
+  flat     STR SKYFLAT
+  skyflat  STR SKYFLAT
+  domeflat STR DOMEFLAT
+  object   STR OBJECT
+  science  STR OBJECT
+END
+
+# Recipe options
+RECIPES         METADATA
+        # Other recipes
+        PSPHOT          STR     uh8k/psphot.config           # psphot details
+        PSASTRO         STR     uh8k/psastro.config          # psastro details
+	PSWARP		STR	uh8k/pswarp.config		# pswarp details
+        PPIMAGE         STR     uh8k/ppImage.config          # ppImage recipe
+	REJECTIONS      STR     uh8k/rejections.config       # rejection recipe
+	PPMERGE		STR	uh8k/ppMerge.config		# ppMerge recipe
+END
+
+# reduction classes (recipes which are grouped together)
+REDUCTION	STR	recipes/reductionClasses.mdc
+
+FITSTYPES       STR     recipes/fitstypes.mdc
+
+FILERULES	STR	recipes/filerules-split.mdc		# File rules appropriate for MEF format
+
+EXTNAME.RULES METADATA
+  CMF.HEAD   STR {CHIP.NAME}.hdr
+  CMF.DATA   STR {CHIP.NAME}.psf # use .PSF and .EXT?
+  CMF.XSRC STR {CHIP.NAME}.xsrc # use .PSF and .EXT?
+  CMF.XFIT STR {CHIP.NAME}.xfit # use .PSF and .EXT?
+
+  PSF.HEAD   STR {CHIP.NAME}.hdr
+  PSF.TABLE  STR {CHIP.NAME}.psf_model
+  PSF.RESID  STR {CHIP.NAME}.psf_resid
+
+  REF.ASTROM STR {CHIP.NAME}.ref_astrom
+END
+
+BLANK.HEADERS	METADATA
+	FPA.TIME	STR	MJD-OBS
+	FPA.EXPOSURE	STR	EXPTIME
+	FPA.AIRMASS	STR	AIRMASS
+	FPA.NAME	STR	EXPNUM
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/dvo.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/dvo.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/dvo.config	(revision 22157)
@@ -0,0 +1,67 @@
+
+# location of DVO database tables
+CATDIR			/data/alala.0/eugene/isp/catdir
+
+# keywords used by DVO to interpret the headers
+
+# keyword abstractions:
+#DATE-KEYWORD		DATE-OBS
+#DATE-MODE		yyyy-mm-dd
+#UT-KEYWORD		UTC-OBS
+JD-KEYWORD		NONE
+MJD-KEYWORD		MJD-OBS
+
+# other keyword abstractions
+EXPTIME-KEYWORD		EXPTIME
+AIRMASS-KEYWORD		AIRMASS
+CCDNUM-KEYWORD		EXTNAME
+ST-KEYWORD		NONE
+OBSERVATORY-LATITUDE	NONE
+OBSERVATORY-LONGITUDE	NONE
+SUBPIX_DATAFILE		NONE
+
+# instrumental magnitude range for calibration mode 
+CAL_INSTMAG_MAX		0
+CAL_INSTMAG_MIN		0
+
+# exclude overscan region from the dB image boundaries
+XOVERSCAN		0
+YOVERSCAN		0
+
+# only upload stars within region; a value of 0 means ignore the limit
+ADDSTAR_XMIN		0
+ADDSTAR_XMAX		0
+ADDSTAR_YMIN		0
+ADDSTAR_YMAX		0
+
+# exclude stars with SN > SNLIMIT (ADDSTAR_SNLIMIT overrides old name MIN_SN_FSTAT)
+ADDSTAR_SNLIMIT		0
+
+# correlation radius (arcseconds)
+ADDSTAR_RADIUS		1.0
+
+# scaled correlation radius 
+ADDSTAR_NSIGMA		0
+
+IMAGE_SCATTER           0.075  # mark images POOR if stdev(Mcal) > IMAGE_SCATTER
+STAR_SCATTER            0.005  # mark stars POOR if stdev(Mrel) > STAR_SCATTER
+IMAGE_OFFSET            0.100  # mark images POOR if abs(delta(Mcal)) > IMAGE_OFFSET
+STAR_CHISQ              10.0   # mark stars POOR if Xm > STAR_CHISQ
+STAR_TOOFEW              3     # mark star FEW if N(good) < STAR_TOOFEW
+IMAGE_TOOFEW            10     # mark image FEW if N(good) < IMAGE_TOOFEW
+IMAGE_GOOD_FRACTION     0.05   # mark image FEW if N(good) < IMAGE_GOOD_FRACTION * Nstars
+
+SCATTER_LIM             15.0
+MAG_LIM                 20.0   # select stars brighter than this for relphot analysis
+SIGMA_LIM               0.015  # select measurements with dM < SIGMA_LIM for relphot analysis
+# INST_MAG_MIN         -17     # optional constraints on magnitude ranges
+# INST_MAG_MAX         -13     # optional constraints on magnitude ranges
+
+RELPHOT_GRID_BINNING    256
+RELPHOT_GRID_X 6
+RELPHOT_GRID_Y 14
+
+PM_DT_MIN               0.25
+PM_TOOFEW               4
+POS_TOOFEW              1
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/format_split.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/format_split.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/format_split.config	(revision 22157)
@@ -0,0 +1,156 @@
+# Camera format for WFI/8k on the UH88
+
+# How to recognise this type
+RULE	METADATA
+	ORIGIN		STR	UH 2.2m Telescope
+	CCD		STR	8k
+	DETECTOR	STR	8k
+	INSTRUME	STR	8k/WFI
+	IMAGESWV	STR	CFHT DetCom v3.41 (Jun 10 2008)
+	NAXIS           S32     2
+END
+
+FILE	METADATA
+	# How to read this data
+	PHU		STR	CHIP	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	NONE	# The extensions represent chips
+	FPA.OBS		STR	EXPNUM	# A PHU keyword for unique identifier
+	CONTENT		STR	EXTNAME	# Key to the CONTENTS menu
+	CONTENT.RULE	STR	{CHIP.NAME} # How to derive the CONTENT when writing
+END
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# Content name, chipType
+	chip00		STR	chip00:SingleAmp
+	chip01		STR	chip01:SingleAmp
+	chip02		STR	chip02:SingleAmp
+	chip03		STR	chip03:SingleAmp
+	chip04		STR	chip04:SingleAmp
+	chip05		STR	chip05:SingleAmp
+	chip06		STR	chip06:SingleAmp
+	chip07		STR	chip07:SingleAmp
+END
+
+# Specify the chips
+CHIPS		METADATA
+	# Chip type, cellName:cellType
+	SingleAmp	STR	Cell:amplifier
+END
+
+
+# Specify the cells
+CELLS		METADATA
+	# Cell type, concepts for this type
+	amplifier	METADATA
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BIASSEC
+		CELL.TRIMSEC		STR	DATASEC
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.INSTRUMENT	STR	INSTRUME
+	FPA.DETECTOR    STR	DETECTOR
+        FPA.AIRMASS     STR     AIRMASS
+        FPA.FILTER      STR     FILTER
+        FPA.FILTERID    STR     FILTER
+        FPA.RA          STR     RA
+        FPA.DEC         STR     DEC
+	FPA.OBSTYPE	STR	OBSTYPE
+	FPA.OBJECT	STR	OBJECT
+	FPA.FOCUS	STR	TELFOCUS
+	FPA.TIME	STR	DATE-OBS TIME-OBS
+	FPA.TIMESYS	STR	TIMESYS
+	FPA.EXPOSURE	STR	EXPTIME
+        CELL.EXPOSURE   STR     EXPTIME
+        CELL.DARKTIME   STR     DARKTIME
+	CELL.TIME	STR	DATE-OBS TIME-OBS
+	CELL.TIMESYS	STR	TIMESYS
+        CELL.XBIN       STR     CCDBIN1
+        CELL.YBIN       STR     CCDBIN2
+        CELL.SATURATION STR     MAXLIN
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS        METADATA
+        FPA.POSANGLE    	F32	0.0
+        FPA.RADECSYS    	STR     ICRS
+        CELL.READNOISE  	F32	10.0	# A guess
+	CELL.GAIN		F32	1.0	# A guess
+
+	FPA.TELESCOPE		STR	UH 88-inch
+	CHIP.XSIZE		S32	2048
+	CHIP.YSIZE		S32	4096
+	CELL.XSIZE		S32	2048
+	CELL.YSIZE		S32	4096
+	CELL.XWINDOW		S32	0
+	CELL.YWINDOW		S32	0
+	CELL.READDIR		S32	1		# Cell is read in x direction
+        CELL.BAD                F32     0
+	CELL.XPARITY		S32	1
+	CELL.YPARITY		S32	1
+	CELL.Y0			S32	0
+	CELL.X0			S32	0
+	CHIP.X0.DEPEND		STR	CHIP.NAME
+	CHIP.X0		METADATA
+		chip00	S32	0
+		chip01	S32	2048
+		chip02	S32	4096
+		chip03	S32	6144
+		chip04	S32	0   
+		chip05	S32	2048
+		chip06	S32	4096
+		chip07	S32	6144
+	END
+	CHIP.Y0.DEPEND		STR	CHIP.NAME
+	CHIP.Y0		METADATA
+		chip00	S32	8192
+		chip01	S32	8192
+		chip02	S32	8192
+		chip03	S32	8192
+		chip04	S32	0
+		chip05	S32	0
+		chip06	S32	0
+		chip07	S32	0
+	END
+	CHIP.XPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.XPARITY	METADATA
+		chip00	S32	-1
+		chip01	S32	-1
+		chip02	S32	-1
+		chip03	S32	-1
+		chip04	S32	1
+		chip05	S32	1
+		chip06	S32	1
+		chip07	S32	1
+	END
+	CHIP.YPARITY.DEPEND	STR	CHIP.NAME
+	CHIP.YPARITY	METADATA
+		chip00	S32	-1
+		chip01	S32	-1
+		chip02	S32	-1
+		chip03	S32	-1
+		chip04	S32	1
+		chip05	S32	1
+		chip06	S32	1
+		chip07	S32	1
+	END
+END
+
+
+# How to translation PS concepts into database lookups
+DATABASE	METADATA
+END		
+
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+	FPA.TIME	STR	SEPARATE YEAR.FIRST
+	CELL.TIME	STR	SEPARATE YEAR.FIRST
+END
+ 
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/ppImage.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/ppImage.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/ppImage.config	(revision 22157)
@@ -0,0 +1,172 @@
+### ppImage recipe configuration file for Megacam
+
+# Overscan subtraction
+OVERSCAN.SINGLE		BOOL	FALSE		# Reduce overscan to a single value?
+OVERSCAN.FIT		STR	NONE		# NONE | POLYNOMIAL | SPLINE
+OVERSCAN.ORDER		S32	0		# Order of polynomial fit
+OVERSCAN.STAT		STR	MEAN		# MEAN | MEDIAN
+
+# for a test, turn off selected detrend types
+OVERSCAN	BOOL	FALSE		# Overscan subtraction
+BIAS		BOOL	FALSE		# Bias subtraction
+DARK		BOOL	TRUE		# Dark subtraction
+FLAT		BOOL	TRUE		# Flat-field normalisation
+
+# binned output image options
+BIN1.XBIN               S32     18
+BIN1.YBIN               S32     18
+BIN2.XBIN               S32     144
+BIN2.YBIN               S32     144
+
+# megacam needs fringe subtraction
+FRINGE		BOOL	TRUE		# Fringe subtraction
+
+# this table lists extra constraints which should be applied when
+# selecting the detrend images.
+# note: camera and time are always applied
+
+DETREND.CONSTRAINTS  METADATA
+  BIAS METADATA
+  END
+  DARK METADATA
+#   EXPTIME STR FPA.EXPOSURE
+  END
+  FLAT METADATA
+    FILTER  STR FPA.FILTERID
+#   DETTYPE STR DOMEFLAT.RAW
+#   VERSION STR SKYFLAT.RAW
+#   VERSION STR DOMEFLAT.CORR.XX
+#   VERSION STR SKYFLAT.CORR
+  END
+  FLAT_CORRECTION METADATA
+    FILTER   STR FPA.FILTER
+  END
+  FRINGE METADATA
+    FILTER   STR FPA.FILTER
+#   AIRMASS  STR FPA.AIRMASS
+#   TWILIGHT STR FPA.TWILIGHT
+  END
+  SHUTTER METADATA
+  END
+  MASK METADATA
+  END	
+END
+
+# only apply the fringe for these filters
+FRINGE.FILTERS  MULTI
+FRINGE.FILTERS	STR i
+FRINGE.FILTERS	STR z
+
+PHOTCODE.RULE		STR	{DETECTOR}.{FILTER.ID}.{CHIP.N}
+
+
+
+# Overscan, bias, dark, shutter
+PPIMAGE_OBDS       METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    FALSE           # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field
+PPIMAGE_OBDSF      METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    FALSE           # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe
+PPIMAGE_OBDSFR     METADATA
+  BASE.FITS        BOOL    TRUE            # Save base detrended image?
+  BASE.MASK.FITS   BOOL    TRUE            # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    TRUE            # Save base detrended image?
+  CHIP.FITS        BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    FALSE           # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    FALSE           # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    FALSE           # Mask bad pixels
+  FRINGE           BOOL    TRUE            # Fringe subtraction
+  PHOTOM           BOOL    FALSE           # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom
+PPIMAGE_OBDSFRP    METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    TRUE            # Mask bad pixels
+  FRINGE           BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM           BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END
+
+# Overscan, bias, dark, shutter, flat-field, fringe, photom, astrom
+PPIMAGE_OBDSFRA    METADATA
+  BASE.FITS        BOOL    FALSE           # Save base detrended image?
+  BASE.MASK.FITS   BOOL    FALSE           # Save base detrended image?
+  BASE.WEIGHT.FITS BOOL    FALSE           # Save base detrended image?
+  CHIP.FITS        BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.MASK.FITS   BOOL    TRUE            # Save chip-mosaic-ed image? 
+  CHIP.WEIGHT.FITS BOOL    TRUE            # Save chip-mosaic-ed image? 
+  OVERSCAN         BOOL    TRUE            # Overscan subtraction
+  BIAS             BOOL    TRUE            # Bias subtraction
+  DARK             BOOL    FALSE           # Dark subtraction
+  SHUTTER          BOOL    FALSE           # Shutter correction
+  FLAT             BOOL    TRUE            # Flat-field normalisation
+  MASK             BOOL    TRUE            # Mask bad pixels
+  FRINGE           BOOL    TRUE            # Fringe subtraction
+  BIN1.FITS        BOOL    TRUE            # Save 1st binned chip image?
+  BIN2.FITS        BOOL    TRUE            # Save 2nd binned chip image?
+  PHOTOM           BOOL    TRUE            # Source identification and photometry
+  ASTROM.CHIP      BOOL    FALSE           # Astrometry per chip?
+  ASTROM.MOSAIC    BOOL    FALSE           # Astrometry for mosaic?
+END 
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/ppMerge.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/ppMerge.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/ppMerge.config	(revision 22157)
@@ -0,0 +1,40 @@
+
+# Bias combination --- don't want min/max rejection
+PPMERGE_BIAS	METADATA
+   REJ		F32	3.0		# Rejection threshold (sigma)
+   ITER		S32	2		# Number of rejection iterations
+   FRACHIGH	F32	0.0		# Fraction of high pixels to reject immediately
+   FRACLOW	F32	0.0		# Fraction of low pixels to reject immediately
+   WEIGHTS	BOOL	FALSE		# Use image weights?
+   COMBINE	STR	CLIPPED		# Statistic to use for combination: 
+END
+
+# Dark combination --- don't want min/max rejection
+# More aggressive clipping than bias, so as to remove CRs
+PPMERGE_DARK	METADATA
+  REJ		F32	2.0		# Rejection threshold (sigma)
+  ITER		S32	4		# Number of rejection iterations
+  FRACHIGH	F32	0.0		# Fraction of high pixels to reject immediately
+  FRACLOW	F32	0.0		# Fraction of low pixels to reject immediately
+  WEIGHTS	BOOL	FALSE		# Use image weights?
+  COMBINE	STR	CLIPPED		# Statistic to use for combination: 
+END
+
+# Flat combination --- use min/max rejection
+PPMERGE_FLAT	METADATA
+	REJ		F32	3.0		# Rejection threshold (sigma)
+	ITER		S32	1		# Number of rejection iterations
+	FRACHIGH	F32	0.3		# Fraction of high pixels to reject immediately
+	FRACLOW		F32	0.1		# Fraction of low pixels to reject immediately
+	NKEEP		S32	5		# Minimum number of pixels in stack to keep
+	WEIGHTS		BOOL	FALSE		# Use image weights?
+	COMBINE		STR	MEAN		# Statistic to use for combination: 
+END
+
+
+# Fringe combination --- already included in default, above
+PPMERGE_FRINGE	METADATA
+	FRACHIGH	F32	0.1		# Fraction of high pixels to reject immediately
+	WEIGHTS		BOOL	FALSE		# Use image weights?
+END
+
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/psastro.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/psastro.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/psastro.config	(revision 22157)
@@ -0,0 +1,74 @@
+
+# astrometry matching parameters
+
+# nominal plate scale (microns / pixel)
+PSASTRO.PIXEL.SCALE    F32  1.0
+
+# pmAstromGridAngle
+# max grid offset in FP units (microns)
+# use plate-scale to make this in pixels?
+PSASTRO.GRID.OFFSET    F32    2500.
+PSASTRO.GRID.SCALE     F32      50.
+
+# extra field for ref stars:
+PSASTRO.FIELD.PADDING  F32 1.0
+
+# these tweak are in FP units (pixels, currently)
+PSASTRO.TWEAK.SCALE     F32      1
+PSASTRO.TWEAK.RANGE     F32     75
+PSASTRO.TWEAK.SMOOTH    F32      2
+PSASTRO.TWEAK.NSIGMA    F32      3
+
+# single-chip radius match in pixels
+PSASTRO.MATCH.FIT.NITER S32   3
+PSASTRO.MATCH.RADIUS    F32   12.0
+PSASTRO.MATCH.RADIUS.N0 F32   15.0
+PSASTRO.MATCH.RADIUS.N1 F32   10.0
+PSASTRO.MATCH.RADIUS.N2 F32    5.0
+
+PSASTRO.MAX.NRAW       S32      1500   # max stars accepted for fitting (0 for all)
+PSASTRO.MAX.NREF       S32      1500   # max stars accepted for fitting (0 for all)
+
+PSASTRO.MIN.INST.MAG.RAW       F32      -15.5  # min instrumental magnitude for stars accepted for fitting
+PSASTRO.MAX.INST.MAG.RAW       F32       -8.0  # max instrumental magnitude for stars accepted for fitting
+
+PSASTRO.MAX.ERROR      F32      10.0 # max error in pixels
+PSASTRO.MIN.NSTAR      S32      10   # min fitted stars in solution
+
+PSASTRO.MOSAIC.MODE           BOOL     FALSE
+
+PSASTRO.MOSAIC.MAX.ERROR.N0 F32    1.50 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N1 F32    1.50 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N2 F32    0.90 # max allow error for valid solution (arcsec)
+PSASTRO.MOSAIC.MAX.ERROR.N3 F32    0.90 # max allow error for valid solution (arcsec)
+
+PSASTRO.MOSAIC.RADIUS.N0    F32    10.0
+PSASTRO.MOSAIC.RADIUS.N1    F32     8.0 # do not refine the match
+PSASTRO.MOSAIC.RADIUS.N2    F32     8.0 # do not refine the match
+PSASTRO.MOSAIC.RADIUS.N3    F32     4.0 
+
+PSASTRO.MOSAIC.CHIP.ORDER.N0  S32      0 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N1  S32      1 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N2  S32      1 # fit order (-1 means use default)
+PSASTRO.MOSAIC.CHIP.ORDER.N3  S32      3 # fit order (-1 means use default)
+
+PSASTRO.MOSAIC.GRADIENT.NX    S32      2   # number of x-dir cells per chip
+PSASTRO.MOSAIC.GRADIENT.NY    S32      4   # number of y-dir cells per chip
+
+# use this recipe to set a tight constraint
+PSASTRO.FINE METADATA
+  PSASTRO.MOSAIC.MAX.ERROR.N0 F32    1.50 # max allow error for valid solution (arcsec)
+  PSASTRO.MOSAIC.MAX.ERROR.N1 F32    1.50 # max allow error for valid solution (arcsec)
+  PSASTRO.MOSAIC.MAX.ERROR.N2 F32    0.90 # max allow error for valid solution (arcsec)
+  PSASTRO.MOSAIC.MAX.ERROR.N3 F32    0.90 # max allow error for valid solution (arcsec)
+
+  PSASTRO.MOSAIC.RADIUS.N0    F32    8.0
+  PSASTRO.MOSAIC.RADIUS.N1    F32    0.0
+  PSASTRO.MOSAIC.RADIUS.N2    F32    4.0
+  PSASTRO.MOSAIC.RADIUS.N3    F32    4.0
+
+  PSASTRO.MOSAIC.CHIP.ORDER.N0  S32      0 # fit order (-1 means use default)
+  PSASTRO.MOSAIC.CHIP.ORDER.N1  S32      1 # fit order (-1 means use default)
+  PSASTRO.MOSAIC.CHIP.ORDER.N2  S32      1 # fit order (-1 means use default)
+  PSASTRO.MOSAIC.CHIP.ORDER.N3  S32      1 # fit order (-1 means use default)
+END
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/psphot.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/psphot.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/psphot.config	(revision 22157)
@@ -0,0 +1,43 @@
+
+# turn these on to see specific outputs
+SAVE.OUTPUT	BOOL 	TRUE
+SAVE.BACKMDL	BOOL 	TRUE
+SAVE.PSF	BOOL 	TRUE
+SAVE.PLOTS     	BOOL    TRUE
+
+BACKGROUND.XBIN	    S32  128            # size of background superpixels
+BACKGROUND.YBIN	    S32  128            # size of background superpixels
+
+# image background parameters
+IMSTATS_NPIX        S32  10000    	 # number of pixels to use for sky estimate boxes:
+SKY_STAT            STR  FITTED_MEAN_V4  # statistic used to measure background
+SKY_CLIP_SIGMA      F32  2.0             # statistic used to measure background
+
+PSF_SN_LIM          F32  100             # minimum S/N for stars used for PSF model
+PSF_MAX_NSTARS      S32  300             # limit number of stars used for PSF model
+
+# PSF model parameters : choose the PSF model desired
+PSF_MODEL           STR  PS_MODEL_QGAUSS
+
+MOMENTS_SN_MIN      F32   30.0
+EXT_MIN_SN           F32  50.0           # fit galaxies above this S/N limit
+FULL_FIT_SN_LIM      F32  50.0
+AP_MIN_SN            F32  50.0
+
+# OUTPUT.FORMAT       STR SMPDATA
+OUTPUT.FORMAT       STR PS1_DEV_1
+
+PSF_SHAPE_NSIGMA     F32  3.0		 # max significance for shape variation
+PSF_MAX_CHI          F32  50.0		 # reject objects worse that this
+
+#PSF.TREND.MODE STR MAP
+#PSF.TREND.NX   S32 1
+#PSF.TREND.NY   S32 1
+
+DIAGNOSTIC.PLOTS		METADATA
+  IMAGE.BACKGROUND.CELL.HISTOGRAM   BOOL FALSE
+  IMAGE.BACKGROUND.CELL.HISTOGRAM.X S32 -1
+  IMAGE.BACKGROUND.CELL.HISTOGRAM.Y S32 12
+END
+
+USE_FOOTPRINTS                      BOOL  TRUE       	  # use new pmFootprint peak packaging
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/pswarp.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/pswarp.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/pswarp.config	(revision 22157)
@@ -0,0 +1,3 @@
+### Megacam recipe for pswarp
+
+ASTROM.SOURCE	STR	PSASTRO.OUTPUT	# Source file rule for astrometry, or NULL
Index: /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/rejections.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/rejections.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ippconfig/uh8k/rejections.config	(revision 22157)
@@ -0,0 +1,38 @@
+
+BIAS METADATA
+  FILTER      	     STR  *
+  EXPECTED    	     F32  0.0
+  IMFILE.MEAN 	     F32  2.0
+  IMFILE.STDEV       F32 10.0
+  IMFILE.MEANSTDEV   F32  0.5
+  IMFILE.SKEWNESS    F32  0.0
+  IMFILE.KURTOSIS    F32  0.0
+  IMFILE.SNR         F32  0.0
+  IMFILE.BIN.STDEV   F32  0.5
+  IMFILE.BIN.SNR     F32  0.0
+  IMFILE.FLUX        F32  0.0
+  EXP.MEAN           F32  1.0
+  EXP.STDEV          F32 10.0
+  EXP.MEANSTDEV      F32  0.5
+  EXP.SNR            F32  0.0
+  EXP.BIN.STDEV      F32  0.0
+  EXP.BIN.SNR        F32  0.0
+  EXP.FLUX           F32  0.0
+  ENSEMBLE.MEAN      F32  3.0
+  ENSEMBLE.STDEV     F32  3.0
+  ENSEMBLE.MEANSTDEV F32  0.0
+END
+
+# FILTER is an additional qualifier, and may be "*" (or absent!), in which case it matches everything
+# EXPECTED is the expected mean value
+# IMFILE.MEAN is the maximum permitted mean value for an imfile, relative to the standard deviation
+# IMFILE.STDEV is the maximum permitted standard deviation for an imfile
+# EXP.MEAN is the maximum permitted mean value for an exposure, relative to the standard deviation
+# EXP.STDEV is the maximum permitted standard deviation for an exposure
+# EXP.MEANSTDEV is the maximum permitted mean standard deviation for an exposure relative to the mean
+# ENSEMBLE.MEAN is the maximum permitted mean for an ensemble of exposures
+# ENSEMBLE.STDEV is the maximum permitted standard deviation for an ensemble of exposures
+# ENSEMBLE.MEANSTDEV is the maximum permitted mean standard deviation for an ensemble of exposures
+# IMFILE.SNR is the minimum permitted signal-to-noise for an imfile
+# EXP.SNR is the minimum permitted signal-to-noise for an exposure
+# These values (all except FILTER) may be zero, in which case no clipping is applied.
Index: /branches/eam_branches/eam_branch_20080706/ppImage/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/.cvsignore	(revision 22157)
@@ -0,0 +1,16 @@
+bin
+aclocal.m4
+autom4te.cache
+compile
+config.log
+config.status
+configure
+depcomp
+install-sh
+Makefile.in
+missing
+Makefile
+config.cache
+config.guess
+config.sub
+ltmain.sh
Index: /branches/eam_branches/eam_branch_20080706/ppImage/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/Makefile.am	(revision 22157)
@@ -0,0 +1,3 @@
+SUBDIRS = src
+
+CLEANFILES = *~ core core.*
Index: /branches/eam_branches/eam_branch_20080706/ppImage/autogen.sh
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/autogen.sh	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/autogen.sh	(revision 22157)
@@ -0,0 +1,103 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+ORIGDIR=`pwd`
+cd $srcdir
+
+PROJECT=ppImage
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL="aclocal $ACLOCAL_FLAGS"
+AUTOHEADER=autoheader
+AUTOMAKE=automake
+AUTOCONF=autoconf
+
+($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $LIBTOOlIZE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/libtool/"
+        DIE=1
+}
+
+($ACLOCAL --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $ACLOCAL installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOHEADER --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOHEADER installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOMAKE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOCONF installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+if test "$DIE" -eq 1; then
+        exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+        echo "You must run this script in the top-level $PROJECT directory"
+        exit 1
+}
+
+if test -z "$*"; then
+        echo "I am going to run ./configure with no arguments - if you wish "
+        echo "to pass any to it, please specify them on the $0 command line."
+fi
+
+$LIBTOOLIZE --copy --force || echo "$LIBTOOlIZE failed"
+$ACLOCAL || echo "$ACLOCAL failed"
+$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /branches/eam_branches/eam_branch_20080706/ppImage/configure.ac
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/configure.ac	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/configure.ac	(revision 22157)
@@ -0,0 +1,32 @@
+AC_PREREQ(2.61)
+
+AC_INIT([ppImage], [1.1.0], [ipp-support@ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([src])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_CONFIG_HEADER([src/config.h])
+AM_MAINTAINER_MODE
+
+IPP_STDCFLAGS
+
+AC_LANG(C)
+AC_GNU_SOURCE
+AC_PROG_CC_C99
+AC_PROG_INSTALL
+dnl AC_PROG_LIBTOOL
+AC_SYS_LARGEFILE
+
+PKG_CHECK_MODULES([PSLIB],    [pslib >= 1.0.0])
+PKG_CHECK_MODULES([PSMODULE], [psmodules >= 1.0.0])
+PKG_CHECK_MODULES([PSPHOT],   [psphot >= 0.8.0]) 
+PKG_CHECK_MODULES([PSASTRO],  [psastro >= 0.8.0]) 
+PKG_CHECK_MODULES([PPSTATS],  [ppStats >= 1.0.0]) 
+
+IPP_STDOPTS
+CFLAGS="${CFLAGS=} -Wall -Werror"
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+])
+AC_OUTPUT
Index: /branches/eam_branches/eam_branch_20080706/ppImage/doc/masks.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/doc/masks.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/doc/masks.txt	(revision 22157)
@@ -0,0 +1,49 @@
+
+Masks and ppImage:
+
+In most operations, ppImage uses masks to identify the good and bad
+pixels in the science image being processed.  In some cases, the
+ppImage analysis may operate explicitly on a mask.  For example, we
+may want to generate a jpeg image of the masked pixels.  
+
+There are three ways in which ppImage may set the masked pixels:
+
+1) ppImage may directly load in the mask associated with a science
+   image.  
+
+2) ppImage may load a default ('standard') mask from the command line
+   or the detrend database.  
+
+3) ppImage may set masks for pixels based on their data values.
+
+ppImage and psphot also use one special mask bit to identify pixels
+currently being used or skipped for an analysis.  
+
+Mask values used by ppImage:
+
+** intrinsic mask values **
+DARK     : the pixel has poor dark behavior : dark current is excessive or non-linear
+FLAT     : the pixel has poor flat behavior : non-linear or excessive correction
+BLANK    : pixel has no valid data (eg, outside of active detector region)
+DETECTOR : unspecified bad detector response 
+
+** extrinsic mask values **
+LOW      : pixel is below minimum allowed value
+SAT      : pixel is saturated, above max value
+RANGE    : pixel is out-of-range in an unspecified way
+
+CR       : pixel is part of a cosmic ray
+GHOST    : pixel is part of a ghost
+
+    PM_MASK_CLEAR    = 0x00,            ///< The pixel is not masked
+    PM_MASK_BLANK    = 0x01,            ///< The pixel is blank or has no (valid) data
+    PM_MASK_FLAT     = 0x02,            ///< The pixel is non-positive in the flat-field
+    PM_MASK_DETECTOR = 0x02,            ///< The detector pixel is bad (e.g., bad column, charge trap)
+    PM_MASK_SAT      = 0x04,            ///< The pixel is saturated in the image of interest
+    PM_MASK_BAD      = 0x04,            ///< The pixel is low in the image of interest
+    PM_MASK_RANGE    = 0x04,            ///< The pixel is out of range in the image of interest
+    PM_MASK_CR       = 0x08,            ///< The pixel is probably a CR
+    PM_MASK_SPARE1   = 0x10,            ///< Spare mask value
+    PM_MASK_SPARE2   = 0x20,            ///< Spare mask value
+    PM_MASK_SUSPECT  = 0x40,            ///< The pixel is suspected of being bad, but may not be
+    PM_MASK_MARK     = 0x80,            ///< The pixel is marked as temporarily ignored
Index: /branches/eam_branches/eam_branch_20080706/ppImage/notes.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/notes.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/notes.txt	(revision 22157)
@@ -0,0 +1,23 @@
+
+ppImage pmFPAfiles:
+
+    pmFPAfile *input = pmFPAfileDefineFromArgs (&status, config, "PPIMAGE.INPUT", "INPUT");
+    pmFPAfile *inputMask = pmFPAfileBindFromArgs(&status, input, config, "PPIMAGE.INPUT.MASK", "INPUT.MASK");
+    pmFPAfile *inputWeight = pmFPAfileBindFromArgs(&status, input, config, "PPIMAGE.INPUT.WEIGHT", "INPUT.WEIGHT");
+    pmFPAfile *output = pmFPAfileDefineOutput(config, input->fpa, "PPIMAGE.OUTPUT");
+    pmFPAfile *outMask = pmFPAfileDefineOutput(config, input->fpa, "PPIMAGE.OUTPUT.MASK");
+    pmFPAfile *outWeight = pmFPAfileDefineOutput(config, input->fpa, "PPIMAGE.OUTPUT.WEIGHT");
+    pmFPAfile *chipImage = pmFPAfileDefineChipMosaic(config, input->fpa, "PPIMAGE.CHIP");
+    pmFPAfile *chipMask = pmFPAfileDefineOutput(config, chipImage->fpa, "PPIMAGE.CHIP.MASK");
+    pmFPAfile *chipWeight = pmFPAfileDefineOutput(config, chipImage->fpa, "PPIMAGE.CHIP.WEIGHT");
+    pmFPAfile *byFPA1 = pmFPAfileDefineFPAMosaic(config, input->fpa, "PPIMAGE.OUTPUT.FPA1");
+    pmFPAfile *byFPA2 = pmFPAfileDefineFPAMosaic(config, input->fpa, "PPIMAGE.OUTPUT.FPA2");
+
+        pmFPAfile *psphotInput = pmFPAfileDefineFromFPA (config, chipImage->fpa, 1, 1, "PSPHOT.INPUT");
+        pmFPAfile *psphotOutput = psMetadataLookupPtr (&status, config->files, "PSPHOT.OUTPUT");
+        pmFPAfile *psastroInput = pmFPAfileDefineInput (config, psphotOutput->fpa, "PSASTRO.INPUT");
+
+    pmFPAfile *bin1 = pmFPAfileDefineFromFPA (config, chipImage->fpa, options->xBin1, options->yBin1, "PPIMAGE.BIN1");
+    pmFPAfile *bin2 = pmFPAfileDefineFromFPA (config, chipImage->fpa, options->xBin2, options->yBin2, "PPIMAGE.BIN2");
+    pmFPAfile *jpg1 = pmFPAfileDefineOutput (config, byFPA1->fpa, "PPIMAGE.JPEG1");
+    pmFPAfile *jpg2 = pmFPAfileDefineOutput (config, byFPA2->fpa, "PPIMAGE.JPEG2");
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/.cvsignore	(revision 22157)
@@ -0,0 +1,9 @@
+ppImage
+ppTest
+Makefile
+Makefile.in
+.deps
+ppFocus
+config.h
+config.h.in
+stamp-h1
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/Makefile.am	(revision 22157)
@@ -0,0 +1,77 @@
+bin_PROGRAMS = ppImage ppFocus
+
+noinst_PROGRAMS = ppTest
+
+noinst_HEADERS = \
+	ppImage.h 
+
+ppImage_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(PSPHOT_CFLAGS) $(PSASTRO_CFLAGS) $(PPSTATS_CFLAGS) $(ppImage_CFLAGS)
+ppImage_LDFLAGS = $(PSLIB_LIBS) $(PSMODULE_LIBS) $(PSPHOT_LIBS) $(PPSTATS_LIBS) $(PSASTRO_LIBS) 
+ppImage_SOURCES = \
+	ppImage.c \
+	ppImageArguments.c \
+	ppImageParseCamera.c \
+	ppImageLoop.c \
+	ppImageCleanup.c \
+	ppImageOptions.c \
+	ppImageDetrendReadout.c \
+	ppImageDetrendNonLinear.c \
+	ppImageDetrendFringe.c \
+	ppImageRebinReadout.c \
+	ppImageMosaic.c \
+	ppImagePhotom.c \
+	ppImageAstrom.c \
+	ppImageAddstar.c \
+	ppImageStats.c \
+	ppImageStatsOutput.c \
+	ppImagePixelStats.c \
+	ppImageMetadataStats.c \
+	ppImageReplaceBackground.c \
+	ppImageDefineFile.c \
+	ppImageSetMaskBits.c \
+	ppImageFileCheck.c \
+	ppImageVersion.c \
+	ppImageMemory.c
+
+ppFocus_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(PSPHOT_CFLAGS) $(PSASTRO_CFLAGS) $(PPSTATS_CFLAGS) $(ppImage_CFLAGS)
+ppFocus_LDFLAGS = $(PSLIB_LIBS) $(PSMODULE_LIBS) $(PSPHOT_LIBS) $(PPSTATS_LIBS) $(PSASTRO_LIBS)
+ppFocus_SOURCES = \
+	ppFocus.c \
+	ppFocusArguments.c \
+	ppFocusParseCamera.c \
+	ppFocusGetFWHM.c \
+	ppFocusFitFWHM.c \
+	ppImageLoop.c \
+	ppImageCleanup.c \
+	ppImageOptions.c \
+	ppImageDetrendReadout.c \
+	ppImageDetrendNonLinear.c \
+	ppImageDetrendFringe.c \
+	ppImageRebinReadout.c \
+	ppImageMosaic.c \
+	ppImagePhotom.c \
+	ppImageAstrom.c \
+	ppImageAddstar.c \
+	ppImageStats.c \
+	ppImageStatsOutput.c \
+	ppImagePixelStats.c \
+	ppImageMetadataStats.c \
+	ppImageReplaceBackground.c \
+	ppImageDefineFile.c \
+	ppImageSetMaskBits.c \
+	ppImageFileCheck.c \
+	ppImageVersion.c \
+	ppImageMemory.c
+
+ppTest_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(ppImage_CFLAGS)
+ppTest_LDFLAGS = $(PSLIB_LIBS) $(PSMODULE_LIBS)
+ppTest_SOURCES = \
+	ppTest.c
+
+CLEANFILES = *~
+
+clean-local:
+	-rm -f TAGS
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppFocus.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppFocus.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppFocus.c	(revision 22157)
@@ -0,0 +1,74 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+int main(int argc, char **argv) {
+
+    psLibInit(NULL);
+
+    psTimerStart(TIMERNAME);
+
+    // Parse the configuration and arguments
+    // Open the input image(s)
+    // Determine camera, format from header if not already defined
+    // Construct camera in preparation for reading
+    pmConfig *config = ppFocusArguments(argc, argv);
+    if (config == NULL) {
+        psErrorStackPrint(stderr, " ");
+        exit(1);
+    }
+
+    // we search the argument data for the named fileset (argname)
+    psArray *infiles = psMetadataLookupPtr(NULL, config->arguments, "INPUT");
+    if (!infiles) {
+        psTrace("pmFPAfile", 5, "Failed to find INPUT in argument list");
+        exit(1);
+    }
+
+    // allocate vectors for analysis
+    psVector *focus = psVectorAllocEmpty(infiles->n, PS_TYPE_F32);
+    psVector *fwhm = psVectorAllocEmpty(infiles->n, PS_TYPE_F32);
+
+    for (int i = 0; i < infiles->n; i++) {
+
+        // define recipe options
+        // define the active I/O files
+        ppImageOptions *options = ppFocusParseCamera(config, i);
+        if (options == NULL) {
+            psErrorStackPrint(stderr, " ");
+            exit(1);
+        }
+
+        // Image Arithmetic Loop
+        // XXX ppFocus REQUIRES photom: for it to be true?
+        //
+        if (!ppImageLoop(config, options)) {
+            psErrorStackPrint(stderr, " ");
+            exit(1);
+        }
+
+        // determine FWHM at reference location in image
+        // (also removes PPIMAGE.INPUT from config->files)
+        ppFocusGetFWHM (config, focus, fwhm);
+
+        ppFocusDropCamera (config);
+        psFree (options);
+    }
+
+    ppFocusFitFWHM (config, focus, fwhm);
+
+    psLogMsg ("ppFocus", 3, "complete ppFocus run: %f sec\n", psTimerMark (TIMERNAME));
+
+    // Cleaning up
+    psFree (focus);
+    psFree (fwhm);
+    ppImageCleanup(config, NULL);
+    return EXIT_SUCCESS;
+}
+
+// ppFocus is a lot like ppImage, but with a few important differences:
+// - the input list is a set of independent images (not multiple files for a single image)
+// - each pass to ppImageLoop performs the analysis on a different pmFPAfile
+// - after each ppImageLoop, grap the input pmFPAfile and extract the FWHM stats
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppFocusArguments.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppFocusArguments.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppFocusArguments.c	(revision 22157)
@@ -0,0 +1,72 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+static void usage (void) {
+    fprintf (stderr, "USAGE: ppFocus [-file focus.*.fits] [-list INPUT.txt] OUTPUT\n");
+    exit (2);
+}
+
+pmConfig *ppFocusArguments(int argc, char **argv) {
+
+    int N;
+    bool status;
+
+    if (argc == 1) usage ();
+
+    // load the site-wide configuration information
+    pmConfig *config = pmConfigRead(&argc, argv, RECIPE_NAME);
+    if (config == NULL) {
+        psErrorStackPrint(stderr, "Can't find site configuration!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    // save the following additional recipe values based on command-line options
+    // these options override the PPIMAGE recipe values loaded from recipe files
+    psMetadata *options = pmConfigRecipeOptions (config, RECIPE_NAME);
+
+    // save these recipe options until we have loaded the options
+    // psMetadata *options = psMetadataAlloc ();
+    // psMetadataAddPtr (config->arguments, PS_LIST_TAIL, "PPIMAGE.OPTIONS",  PS_DATA_METADATA, "", options);
+
+    // the following options override the PPIMAGE recipe options
+
+    // recipe option: -usemask : override MASK setting in phase2.recipe
+    if ((N = psArgumentGet(argc, argv, "-usemask"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddBool (options, PS_LIST_TAIL, "MASK", PS_META_REPLACE, "", true);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // XXX add other PPIMAGE recipe options here
+
+    // the input file is a required argument; if not found, we will exit
+    status = pmConfigFileSetsMD (config->arguments, &argc, argv, "INPUT", "-file", "-list");
+    if (!status) { usage ();}
+
+    // if these command-line options are supplied, load the file name lists into config->arguments
+    // override any configuration-specified source for these files
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "BIAS", "-bias", "-biaslist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "DARK", "-dark", "-darklist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "FLAT", "-flat", "-flatlist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "MASK", "-mask", "-masklist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "FRINGE", "-fringe", "-fringelist");
+
+    // chip selection is used to limit chips to be processed
+    if ((N = psArgumentGet (argc, argv, "-chip"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (config->arguments, PS_LIST_TAIL, "CHIP_SELECTIONS", PS_DATA_STRING, "",
+                          argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    if (argc != 2) usage ();
+
+    // Add the input and output images (which remain on the command-line) to the arguments list
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "Name of the output image",
+                     argv[1]);
+
+    return config;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppFocusFitFWHM.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppFocusFitFWHM.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppFocusFitFWHM.c	(revision 22157)
@@ -0,0 +1,30 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include "ppImage.h"
+
+bool ppFocusFitFWHM (pmConfig *config, psVector *focus, psVector *fwhm) {
+
+    float minFocus;
+
+    psPolynomial1D *poly = psPolynomial1DAlloc (PS_POLYNOMIAL_ORD, 2);
+
+    if (!psVectorFitPolynomial1D (poly, NULL, 0, fwhm, NULL, focus)) {
+	psError (PS_ERR_UNKNOWN, false, "failed to fit focus/fwhm trend; invalid image data?");
+	return false;
+    }
+
+    if (poly->coeff[2] <= 0.0) {
+	psLogMsg ("ppFocus", 3, "poor focus fit: zero or negative curvature\n");
+	psLogMsg ("ppFocus", 3, "fit coeffs: %f  %f  %f\n", 
+		  poly->coeff[0], poly->coeff[1], poly->coeff[2]);
+    }
+    
+    minFocus = -0.5 * poly->coeff[1] / poly->coeff[2];
+    psLogMsg ("ppFocus", 3, "best fit focus: %f\n", minFocus);
+    psLogMsg ("ppFocus", 3, "fwhm @ min: %f\n", psPolynomial1DEval (poly, minFocus));
+    
+    psFree (poly);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppFocusGetFWHM.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppFocusGetFWHM.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppFocusGetFWHM.c	(revision 22157)
@@ -0,0 +1,79 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include "ppImage.h"
+
+bool ppFocusGetFWHM (pmConfig *config, psVector *focus, psVector *fwhm) {
+
+    bool status;
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+    psMetadata *header;
+    float FOCUS, FWHM, FWHM_X, FWHM_Y, FWHMsum;
+    int FWHMnum;
+
+    pmFPAfile *input = psMetadataLookupPtr (&status, config->files, "PSPHOT.OUTPUT");
+    if (!status) {
+	psErrorStackPrint(stderr, "Can't find input data!\n");
+	exit(EXIT_FAILURE);
+    }
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    // - find readouts with measured PSFs
+    // - measure the average central FWHM for each PSF
+    FWHMsum = 0.0;
+    FWHMnum = 0;
+
+    while ((chip = pmFPAviewNextChip (view, input->fpa, 1)) != NULL) {
+        psLogMsg ("ppImageLoop", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+
+	while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+            psLogMsg ("ppImageLoop", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+
+	    // process each of the readouts
+	    while ((readout = pmFPAviewNextReadout (view, input->fpa, 1)) != NULL) {
+		if (!readout->data_exists) { continue; }
+
+		// get average FWHM
+		// psphotReadout writes the FWHM values into the PSPHOT.HEADER table
+		// the source of this value depends on the psphot options.
+		// - if breakPoint is set to PEAKS, the value will not be defined
+		// - if breakPoint is set to MOMENTS, the PSFSTAR moments are used
+		// - in all other cases, the psf model is used
+		header = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.HEADER");
+		if (header == NULL) {
+		    psError(PS_ERR_IO, false, "Missing header in ppFocus");
+		    continue;
+		}
+
+		FWHM_X = psMetadataLookupF32 (&status, header, "FWHM_X");
+		FWHM_Y = psMetadataLookupF32 (&status, header, "FWHM_Y");
+
+		FWHMsum += 0.5*(FWHM_X + FWHM_Y);
+		FWHMnum ++;
+
+		psLogMsg ("ppFocus", 4, "focus pt: %f,%f, fwhm sum: %f, fwhm num: %d\n", FWHM_X, FWHM_Y, FWHMsum, FWHMnum);
+	    }
+	}
+    }
+
+    FWHM = FWHMsum / FWHMnum;
+
+    FOCUS = psMetadataLookupF32 (&status, input->fpa->concepts, "FPA.FOCUS");
+
+    fwhm->data.F32[fwhm->n] = FWHM;
+    focus->data.F32[focus->n] = FOCUS;
+
+    psVectorExtend (fwhm, 10, 1);
+    psVectorExtend (focus, 10, 1);
+
+    psLogMsg ("ppFocus", 4, "focus: %f, fwhm: %f (%d)\n", FOCUS, FWHM, FWHMnum);
+
+    psFree (view);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppFocusParseCamera.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppFocusParseCamera.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppFocusParseCamera.c	(revision 22157)
@@ -0,0 +1,133 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include "ppImage.h"
+
+// XXX clean up error checks: return NULL, not psAbort
+ppImageOptions *ppFocusParseCamera (pmConfig *config, int entry) {
+
+    bool status = false;
+
+    // the first input image defines the camera, and all recipes and options that follow
+    // select only the first file from the INPUT array
+    pmFPAfile *input = pmFPAfileDefineSingleFromArgs (&status, config, "PPIMAGE.INPUT", "INPUT", entry);
+    if (!status) {
+        psError(PS_ERR_IO, false, "Failed to build FPA from PPIMAGE.INPUT");
+        return NULL;
+    }
+
+    // parse the options from the metadata format to the ppImageOptions structure
+    ppImageOptions *options = ppImageOptionsParse (config);
+
+    // the following are defined from the argument list, if given,
+    // otherwise they revert to the config information or detrend database if specified
+    // not all input or output images are used in a given recipe
+    if (options->doBias) {
+	if (!ppImageDefineFile (config, input->fpa, "PPIMAGE.BIAS", "BIAS", PM_FPA_FILE_IMAGE, PM_DETREND_TYPE_BIAS)) {
+	    psError (PS_ERR_IO, false, "Can't find a bias image source");
+	    return NULL;
+	}
+    }
+    if (options->doDark) {
+	if (!ppImageDefineFile (config, input->fpa, "PPIMAGE.DARK", "DARK", PM_FPA_FILE_IMAGE, PM_DETREND_TYPE_DARK)) {
+	    psError (PS_ERR_IO, false, "Can't find a dark image source");
+	    return NULL;
+	}
+    }
+    if (options->doMask) {
+	if (!ppImageDefineFile (config, input->fpa, "PPIMAGE.MASK", "MASK", PM_FPA_FILE_MASK, PM_DETREND_TYPE_MASK)) {
+	    psError (PS_ERR_IO, false, "Can't find a mask image source");
+	    return NULL;
+	}
+    }
+    if (options->doFlat) {
+	if (!ppImageDefineFile (config, input->fpa, "PPIMAGE.FLAT", "FLAT", PM_FPA_FILE_IMAGE, PM_DETREND_TYPE_FLAT)) {
+	    psError (PS_ERR_IO, false, "Can't find a shutter image source");
+	    return NULL;
+	}
+    }
+
+    // the following files are output targets
+    pmFPAfile *output = pmFPAfileDefineOutput (config, input->fpa, "PPIMAGE.OUTPUT");
+    pmFPAfile *byChip = pmFPAfileDefineNewCamera (config, "PPIMAGE.OUTPUT.CHIP");
+    pmFPAfile *byFPA1 = pmFPAfileDefineNewCamera (config, "PPIMAGE.OUTPUT.FPA1");
+    pmFPAfile *byFPA2 = pmFPAfileDefineNewCamera (config, "PPIMAGE.OUTPUT.FPA2");
+
+    // save any of these files?
+    output->save = options->BaseFITS;
+    byChip->save = options->ChipFITS;
+    byFPA1->save = options->FPA1FITS;
+    byFPA2->save = options->FPA2FITS;
+
+    // output is used as a carrier: input to byChip
+    output->freeLevel = PM_FPA_LEVEL_CHIP;
+
+    // define the binned target files (which may just be carriers for some camera configurations)
+    pmFPAfile *bin1 = pmFPAfileDefineFromFPA (config, byChip->fpa, options->xBin1, options->yBin1, "PPIMAGE.BIN1");
+    pmFPAfile *bin2 = pmFPAfileDefineFromFPA (config, byChip->fpa, options->xBin2, options->yBin2, "PPIMAGE.BIN2");
+
+    // bin1 and bin2 are used as carriers: input for byFPA1, byFPA2
+    bin1->freeLevel = PM_FPA_LEVEL_FPA;
+    bin2->freeLevel = PM_FPA_LEVEL_FPA;
+
+    pmFPAfile *jpg1 = pmFPAfileDefineOutput (config, byFPA1->fpa, "PPIMAGE.JPEG1");
+    pmFPAfile *jpg2 = pmFPAfileDefineOutput (config, byFPA2->fpa, "PPIMAGE.JPEG2");
+
+    // XXX we could potentially not define these pmFPAfiles if no output is requested...
+    bin1->save = options->Bin1FITS;
+    bin2->save = options->Bin2FITS;
+    jpg1->save = options->Bin1JPEG;
+    jpg2->save = options->Bin2JPEG;
+
+    // Chip selection: turn on only the chips specified (pass status to suppress missing-key log msg)
+    char *chipLine = psMetadataLookupStr(&status, config->arguments, "CHIP_SELECTIONS");
+    psArray *chips = psStringSplitArray (chipLine, ",", false);
+    if (chips->n > 0) {
+        pmFPASelectChip (input->fpa, -1, true); // deselect all chips
+        for (int i = 0; i < chips->n; i++) {
+            int chipNum = atoi(chips->data[i]);
+            if (! pmFPASelectChip(input->fpa, chipNum, false)) {
+                psError(PS_ERR_IO, false, "Chip number %d doesn't exist in camera.\n", chipNum);
+                return false;
+            }
+        }
+    }
+    psFree (chips);
+
+    return (options);
+}
+
+// remove from config all pmFPAfiles which could have been added (ignore missing entries)
+void ppFocusDropCamera (pmConfig *config) {
+
+    psMetadataRemoveKey (config->files, "PPIMAGE.INPUT");
+
+    psMetadataRemoveKey (config->files, "PPIMAGE.BIAS");
+    psMetadataRemoveKey (config->files, "PPIMAGE.DARK");
+    psMetadataRemoveKey (config->files, "PPIMAGE.MASK");
+    psMetadataRemoveKey (config->files, "PPIMAGE.FLAT");
+
+    psMetadataRemoveKey (config->files, "PPIMAGE.OUTPUT");
+    psMetadataRemoveKey (config->files, "PPIMAGE.OUTPUT.CHIP");
+    psMetadataRemoveKey (config->files, "PPIMAGE.OUTPUT.FPA1");
+    psMetadataRemoveKey (config->files, "PPIMAGE.OUTPUT.FPA2");
+
+    psMetadataRemoveKey (config->files, "PPIMAGE.BIN1");
+    psMetadataRemoveKey (config->files, "PPIMAGE.BIN2");
+
+    psMetadataRemoveKey (config->files, "PPIMAGE.JPEG1");
+    psMetadataRemoveKey (config->files, "PPIMAGE.JPEG2");
+
+    psMetadataRemoveKey (config->files, "PSPHOT.INPUT");
+    psMetadataRemoveKey (config->files, "PSPHOT.OUTPUT");
+    psMetadataRemoveKey (config->files, "PSPHOT.RESID");
+    psMetadataRemoveKey (config->files, "PSPHOT.BACKMDL");
+    psMetadataRemoveKey (config->files, "PSPHOT.BACKGND");
+    psMetadataRemoveKey (config->files, "PSPHOT.BACKSUB");
+    psMetadataRemoveKey (config->files, "PSPHOT.PSF.LOAD");
+    psMetadataRemoveKey (config->files, "PSPHOT.PSF.SAVE");
+
+    return;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImage.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImage.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImage.c	(revision 22157)
@@ -0,0 +1,46 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+int main(int argc, char **argv) {
+
+    psLibInit(NULL);
+
+    psTimerStart(TIMERNAME);
+
+    // Parse the configuration and arguments
+    // Open the input image(s)
+    // Determine camera, format from header if not already defined
+    // Construct camera in preparation for reading
+    pmConfig *config = ppImageArguments(argc, argv);
+    if (config == NULL) {
+        psErrorStackPrint(stderr, "Unable to parse command-line arguments.");
+        ppImageCleanup(config, NULL);
+        exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    // define recipe options
+    // define the active I/O files
+    ppImageOptions *options = ppImageParseCamera(config);
+    if (options == NULL) {
+        psErrorStackPrint(stderr, "Unable to parse camera.");
+        ppImageCleanup(config, options);
+        exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    // Image Arithmetic Loop
+    if (!ppImageLoop(config, options)) {
+        psErrorStackPrint(stderr, "Unable to loop over input");
+        ppImageCleanup(config, options);
+        exit(PS_EXIT_SYS_ERROR);
+    }
+
+    psLogMsg ("ppImage", 3, "Complete ppImage run: %f sec\n", psTimerMark(TIMERNAME));
+
+    // Cleaning up
+    ppImageCleanup(config, options);
+
+    return PS_EXIT_SUCCESS;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImage.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImage.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImage.h	(revision 22157)
@@ -0,0 +1,209 @@
+#ifndef PP_IMAGE_H
+#define PP_IMAGE_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>  // for strcasecmp
+#include <unistd.h>   // for unlink
+#include "pslib.h"
+#include "psmodules.h"
+#include "psphot.h"
+#include "psastro.h"
+#include "ppStats.h"
+
+#define RECIPE_NAME "PPIMAGE"           // Name of the recipe to use
+#define TIMERNAME "ppImage"             // Name of timer
+
+// Options for ppImage processing
+typedef struct {
+    // actions which ppImage should perform
+    bool doMaskBuild;                   // Build internal mask
+    bool doWeightBuild;                   // Build internal mask
+    bool doMask;                        // Mask bad pixels
+    bool doNonLin;                      // Non-linearity correction
+    bool doOverscan;                    // Overscan subtraction
+    bool doBias;                        // Bias subtraction
+    bool doDark;                        // Dark subtraction
+    bool doShutter;                     // Shutter correction
+    bool doFlat;                        // Flat-field normalisation
+    bool doFringe;                      // Fringe subtraction
+    bool doPhotom;                      // Source identification and photometry
+    bool doAstromChip;                  // per-chip Astrometry
+    bool doAstromMosaic;                // full-mosaic Astrometry
+    bool doStats;                       // call ppStats on the image
+    bool replaceMasked;                 // fill in masked values with background model
+
+    // output files requested
+    bool BaseFITS;
+    bool BaseMaskFITS;
+    bool BaseWeightFITS;
+
+    bool ChipFITS;
+    bool ChipMaskFITS;
+    bool ChipWeightFITS;
+
+    bool FPA1FITS;
+    bool FPA2FITS;
+    bool Bin1FITS;
+    bool Bin1JPEG;
+    bool Bin2FITS;
+    bool Bin2JPEG;
+
+    // make values for abstract concepts of masking
+    psMaskType maskValue;               // apply this bit-mask to choose masked bits
+    psMaskType markValue;               // apply this bit-mask to choose masked bits
+    psMaskType satMask;                 // Mask value to give saturated pixels
+    psMaskType badMask;                 // Mask value to give bad pixels
+    psMaskType flatMask;                // Mask value to give bad flat pixels
+    psMaskType blankMask;               // Mask value to give blank pixels
+
+    // non-linear correction parameters
+    psDataType nonLinearType;
+    psMetadataItem *nonLinearData;
+    void *nonLinearSource;
+
+    // options for the analysis
+    pmOverscanOptions *overscan;        // Overscan options
+
+    // binning parameters
+    int xBin1;                          // x-binning, scale 1
+    int yBin1;                          // y-binning, scale 1
+    int xBin2;                          // x-binning, scale 2
+    int yBin2;                          // y-binning, scale 2
+
+    // parameters used by the fringe analysis
+    float fringeRej;                    // Fringe rejection limit
+    int fringeIter;                     // Fringe iterations
+    float fringeKeep;                   // Fringe keep fraction
+
+} ppImageOptions;
+
+// Cells to be used in the detrend
+typedef struct {
+    pmCell *input;                      // The input cell, to be operated upon
+    pmCell *mask;                       // The bad pixel mask
+    pmCell *bias;                       // The bias correction
+    pmCell *dark;                       // The dark correction
+    pmCell *flat;                       // The flat-field correction
+} ppImageDetrend;
+
+ppImageOptions *ppImageOptionsAlloc(void);
+
+// Determine the processing options
+ppImageOptions *ppImageOptionsParse(pmConfig *config);
+
+// Get the configuration
+pmConfig *ppImageArguments(int argc, char **argv);
+
+// Determine what type of camera, and initialise
+ppImageOptions *ppImageParseCamera(pmConfig *config);
+bool ppImageSetMaskBits (pmConfig *config, ppImageOptions *options);
+
+// Loop over the input
+bool ppImageLoop(pmConfig *config, ppImageOptions *options);
+
+// free memory, check for leaks
+void ppImageCleanup (pmConfig *config, ppImageOptions *options);
+
+// perform the detrend analysis on the current readout
+bool ppImageDetrendReadout (pmConfig *config, ppImageOptions *options, pmFPAview *view);
+
+bool ppImageDetrendBias(pmReadout *inputReadout, pmReadout *bias, pmReadout *dark, ppImageOptions *options);
+
+bool ppImageDetrendNonLinear(pmReadout *input, ppImageOptions *options);
+bool ppImageDetrendNonLinearLookup(pmReadout *input, psMetadataItem *dataItem);
+bool ppImageDetrendNonLinearPolynomial(pmReadout *input, psMetadataItem *dataItem);
+
+bool ppImageDetrendCell(ppImageDetrend *detrend, ppImageOptions *options, pmConfig *config);
+bool ppImageDetrendBias(pmReadout *inputReadout, pmReadout *bias, pmReadout *dark, ppImageOptions *options);
+pmReadout* ppImageDetrendSelectFirst(pmCell *cell, char *name, bool doThis);
+
+bool ppImageRebinChip (pmConfig *config, pmFPAview *view, char *outName);
+bool ppImageRebinReadout (pmReadout *output, pmReadout *input, pmFPAfile *outFile);
+
+bool ppImagePhotom (pmConfig *config, pmFPAview *view);
+bool ppImageAstrom (pmConfig *config);
+bool ppImageAddstar (pmConfig *config);
+
+bool ppImageReplaceBackground (pmConfig *config, pmFPAview *view, ppImageOptions *options);
+
+bool ppImageMosaicChip (pmConfig *config, const ppImageOptions *options, const pmFPAview *view,
+                        const char *outFile, const char *inFile);
+bool ppImageMosaicFPA (pmConfig *config, const ppImageOptions *options,
+                       const char *outFile, const char *inFile);
+
+bool ppImageSetMaskBits (pmConfig *config, ppImageOptions *options);
+
+void ppImageFileCheck (pmConfig *config);
+
+// functions used by ppFocus
+pmConfig *ppFocusArguments(int argc, char **argv);
+ppImageOptions *ppFocusParseCamera (pmConfig *config, int entry);
+bool ppFocusGetFWHM (pmConfig *config, psVector *focus, psVector *fwhm);
+bool ppFocusFitFWHM (pmConfig *config, psVector *focus, psVector *fwhm);
+
+void ppFocusDropCamera (pmConfig *config);
+
+bool ppImageDefineFile (pmConfig *config, pmFPA *input, char *filerule, char *argname, pmFPAfileType fileType, pmDetrendType detrendType);
+
+// calculate stats, including MD5
+bool ppImageStats (pmConfig *config, pmChip *chip, const pmFPAview *inputView, const ppImageOptions *options);
+
+// write stats to output file
+bool ppImageStatsOutput (pmConfig *config, const ppImageOptions *options);
+
+
+// Measure fringes
+bool ppImageDetrendFringeMeasure(pmReadout *readout, // Readout to measure
+                                 pmCell *fringe, // Fringe cell (each readout is a different component)
+                                 const bool isResidual,
+                                 const ppImageOptions *options // Options
+    );
+
+// Solve the fringe system
+bool ppImageDetrendFringeSolve(pmChip *scienceChip, // Chip with science
+                               const pmChip *refChip, // Chip with reference fringes
+                               const bool isResidual,
+                               const ppImageOptions *options // Options
+    );
+
+// Generate fringe frame
+bool ppImageDetrendFringeGenerate(pmCell *science, // Science cell
+                                  pmCell *fringes, // Fringe cell, one readout per fringe component
+                                  const ppImageOptions *options // Options
+    );
+
+
+bool ppImageDetrendFringeApply (pmConfig *config, // config
+                                pmChip *chip, // science chip
+                                const pmFPAview *inputView, // current view
+                                const ppImageOptions *options // options
+    );
+
+/// Return short version information
+psString ppImageVersion(void);
+
+/// Return long version information
+psString ppImageVersionLong(void);
+
+/// Update the metadata with version information for all dependencies
+void ppImageVersionMetadata(psMetadata *metadata ///< Metadata to update with version information
+    );
+
+
+// calculate stats, including MD5
+bool ppImagePixelStats (pmConfig *config, const ppImageOptions *options, const pmFPAview *inputView);
+
+// calculate stats from headers and concepts
+bool ppImageMetadataStats (pmConfig *config, const ppImageOptions *options);
+
+void ppImageFileCheck (pmConfig *config);
+
+/// Dump memory summary to text file
+void ppImageMemoryDump(const char *description);
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageAddstar.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageAddstar.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageAddstar.c	(revision 22157)
@@ -0,0 +1,120 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include "ppImage.h"
+
+bool setFilename (pmFPAfile *file, pmFPAview *view);
+bool addstarFile (char *addstarCommand, char *filename);
+
+bool ppImageAddstar (pmConfig *config) {
+
+    bool status;
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+
+    // select recipe options supplied on command line
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, RECIPE_NAME);
+
+    // find a pmFPAfile PSASTRO.OUTPUT
+    pmFPAfile *file = psMetadataLookupPtr (&status, config->files, "PSASTRO.OUTPUT");
+    if (!status) {
+        psError (PS_ERR_IO, true, "no PSASTRO.OUTPUT file defined");
+        return false;
+    }
+
+    // find the addstar command: %s is replaced with name of output file
+    char *addstarCommand = psMetadataLookupStr (&status, recipe, "ADDSTAR.COMMAND");
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+    if (file->fileLevel == PM_FPA_LEVEL_CHIP) {
+        // call addstar on this file
+        setFilename (file, view);
+        addstarFile (addstarCommand, file->filename);
+        return true;
+    }
+
+    while ((chip = pmFPAviewNextChip (view, file->fpa, 1)) != NULL) {
+        psLogMsg ("ppImageAddstar", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+
+        if (file->fileLevel == PM_FPA_LEVEL_CHIP) {
+            // call addstar on this file
+            setFilename (file, view);
+            addstarFile (addstarCommand, file->filename);
+            continue;
+        }
+
+        while ((cell = pmFPAviewNextCell (view, file->fpa, 1)) != NULL) {
+            psLogMsg ("ppImageAddstar", 5, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (! cell->process || ! cell->file_exists) { continue; }
+
+            if (file->fileLevel == PM_FPA_LEVEL_CELL) {
+                // call addstar on this file
+                setFilename (file, view);
+                addstarFile (addstarCommand, file->filename);
+                continue;
+            }
+
+            // process each of the readouts
+            while ((readout = pmFPAviewNextReadout (view, file->fpa, 1)) != NULL) {
+                if (! readout->data_exists) { continue; }
+
+                if (file->fileLevel == PM_FPA_LEVEL_READOUT) {
+                    // call addstar on this file
+                    setFilename (file, view);
+                    addstarFile (addstarCommand, file->filename);
+                    continue;
+                } else {
+                    psError (PS_ERR_IO, true, "inconsistent fileLevel for %s: %d\n", file->name, file->fileLevel);
+                    return false;
+                }
+            }
+        }
+    }
+    return true;
+}
+
+bool setFilename (pmFPAfile *file, pmFPAview *view) {
+
+    // determine the file name
+    // free a name allocated earlier
+    psFree (file->filename);
+    file->filename = pmFPAfileNameFromRule (file->filerule, file, view);
+    if (file->filename == NULL) {
+        psError(PS_ERR_IO, true, "Filename is NULL");
+        return false;
+    }
+
+    // indirect filenames are not allowed for output
+    if (!strcasecmp (file->filename, "@FILES")) {
+        psError(PS_ERR_IO, true, "indirect filenames are not allowed for output : %s\n", file->name);
+        return false;
+    }
+    if (!strcasecmp (file->filename, "@DETDB")) {
+        psError(PS_ERR_IO, true, "detrend db filenames are not allowed for output : %s\n", file->name);
+        return false;
+    }
+    return true;
+}
+
+bool addstarFile (char *addstarCommand, char *filename) {
+
+    bool status;
+    char *addstarLine = psStringCopy(addstarCommand);
+    psStringSubstitute(&addstarCommand, filename, "%s");
+
+    // catch addstar stderr/stdout and do what?
+    psIOBuffer *buffer = psIOBufferAlloc (512);
+    psPipe *pipe = psPipeOpen (addstarLine);
+    status = psIOBufferReadEmpty (buffer, 100, pipe->fd_stdout);
+    if (!status) {
+        psError (PS_ERR_IO, false, "detselect is not responding");
+    }
+    psFree (addstarLine);
+    psFree (buffer);
+    psPipeClose (pipe);
+    psFree (pipe);
+    return status;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageArguments.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageArguments.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageArguments.c	(revision 22157)
@@ -0,0 +1,128 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+static void usage (void) {
+    fprintf(stderr, "USAGE: ppImage [-file INPUT.fits] [-list INPUT.txt] OUTPUT\n\n");
+    fprintf(stderr, "Optional arguments:\n");
+    fprintf(stderr, "\t-stats STATS.mdc: Output statistics into STATS.mdc\n");
+    fprintf(stderr, "\t-isfringe: The input image contains fringe data.\n");
+    fprintf(stderr, "\t-isdark: The input image contains dark data.\n");
+    fprintf(stderr, "\t-usemask MASKVAL: Use this mask value (override recipe).\n");
+    fprintf(stderr, "\t-chip CHIPNUM: Only process this chip number.\n");
+    fprintf(stderr, "\t-norm VALUE: Divide through by this value when done.\n");
+    fprintf(stderr, "\n");
+    fprintf(stderr, "Input options (single file / file list):\n");
+    fprintf(stderr, "\t-bias/-biaslist: Bias image.\n");
+    fprintf(stderr, "\t-dark/-darklist: Dark image.\n");
+    fprintf(stderr, "\t-shutter/-shutterlist: Shutter image.\n");
+    fprintf(stderr, "\t-flat/-flatlist: Flat image.\n");
+    fprintf(stderr, "\t-mask/-masklist: Mask image.\n");
+    fprintf(stderr, "\t-fringe/-fringelist: Fringe image and data.\n");
+    fprintf(stderr, "\n");
+    exit (2);
+}
+
+pmConfig *ppImageArguments(int argc, char **argv)
+{
+    int argnum;                         // Argument number of interest
+
+    if (argc == 1) {
+        usage();
+    }
+
+    if (psArgumentGet (argc, argv, "-version")) {
+        psString version;
+        version = ppImageVersionLong();   fprintf (stdout, "%s\n", version); psFree (version);
+        version = ppStatsVersionLong();   fprintf (stdout, "%s\n", version); psFree (version);
+        version = psphotVersionLong();    fprintf (stdout, "%s\n", version); psFree (version);
+        version = psastroVersionLong();   fprintf (stdout, "%s\n", version); psFree (version);
+        version = psModulesVersionLong(); fprintf (stdout, "%s\n", version); psFree (version);
+        version = psLibVersionLong();     fprintf (stdout, "%s\n", version); psFree (version);
+        exit (0);
+    }
+
+    // load the site-wide configuration information
+    pmConfig *config = pmConfigRead(&argc, argv, RECIPE_NAME);
+    if (config == NULL) {
+        psErrorStackPrint(stderr, "Can't find site configuration!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    // save the following additional recipe values based on command-line options
+    // these options override the PPIMAGE recipe values loaded from recipe files
+    psMetadata *options = pmConfigRecipeOptions (config, RECIPE_NAME);
+
+    // save these recipe options until we have loaded the options
+    // psMetadata *options = psMetadataAlloc ();
+    // psMetadataAddPtr (config->arguments, PS_LIST_TAIL, "PPIMAGE.OPTIONS",  PS_DATA_METADATA, "", options);
+
+    if ((argnum = psArgumentGet(argc, argv, "-stats"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "STATS", PS_META_REPLACE,
+                         "Filename for summary statistics", argv[argnum]);
+        psArgumentRemove(argnum, &argc, argv);
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-isfringe"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        psMetadataAddBool(config->arguments, PS_LIST_TAIL, "INPUT_IS_FRINGE", PS_META_REPLACE,
+                          "Input is fringe image", true);
+    }
+    if ((argnum = psArgumentGet(argc, argv, "-isdark"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        psMetadataAddBool(config->arguments, PS_LIST_TAIL, "INPUT_IS_DARK", PS_META_REPLACE,
+                          "Input is dark image", true);
+    }
+
+    // the following options override the PPIMAGE recipe options
+
+    // recipe option: -usemask : override MASK setting in recipe
+    if ((argnum = psArgumentGet(argc, argv, "-usemask"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        psMetadataAddBool(options, PS_LIST_TAIL, "MASK", PS_META_REPLACE, "", true);
+        psArgumentRemove(argnum, &argc, argv);
+    }
+
+    // XXX add other PPIMAGE recipe options here
+
+    // the input file is a required argument; if not found, we will exit
+    bool status = pmConfigFileSetsMD (config->arguments, &argc, argv, "INPUT", "-file", "-list");
+    if (!status) {
+        usage ();
+    }
+
+    // if these command-line options are supplied, load the file name lists into config->arguments
+    // override any configuration-specified source for these files
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "BIAS", "-bias", "-biaslist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "DARK", "-dark", "-darklist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "SHUTTER", "-shutter", "-shutterlist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "FLAT", "-flat", "-flatlist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "MASK", "-mask", "-masklist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "FRINGE", "-fringe", "-fringelist");
+
+    // chip selection is used to limit chips to be processed
+    if ((argnum = psArgumentGet (argc, argv, "-chip"))) {
+        psArgumentRemove (argnum, &argc, argv);
+        psMetadataAddStr (config->arguments, PS_LIST_TAIL, "CHIP_SELECTIONS", PS_DATA_STRING, "", argv[argnum]);
+        psArgumentRemove (argnum, &argc, argv);
+    }
+
+    // Optional normalisation factor
+    if ((argnum = psArgumentGet(argc, argv, "-norm"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        float norm = atof(argv[argnum]);
+        psMetadataAddF32(config->arguments, PS_LIST_TAIL, "NORMALISATION", 0,
+                         "Normalisation to apply", norm);
+        psArgumentRemove(argnum, &argc, argv);
+    }
+
+    if (argc != 2) usage ();
+
+    // Add the input and output images (which remain on the command-line) to the arguments list
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "Name of the output image", argv[1]);
+
+    return config;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageAstrom.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageAstrom.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageAstrom.c	(revision 22157)
@@ -0,0 +1,66 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include "ppImage.h"
+
+// this function is mostly equivalent to the top-level of psastro, with some
+// modifications since the data has already been loaded.
+bool ppImageAstrom (pmConfig *config) {
+
+    bool status;
+
+    // select recipe options supplied on command line
+    // XXX move these options to the "PSASTRO" recipe?
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSASTRO_RECIPE);
+
+    // find or define a pmFPAfile PSPHOT.INPUT
+    pmFPAfile *input = psMetadataLookupPtr (&status, config->files, "PSASTRO.INPUT");
+    PS_ASSERT (input, false);
+
+    ppImageMemoryDump("astrom");
+
+    // convert the output sources created by psphot into astrometry objects
+    if (!psastroConvertFPA (input->fpa, recipe)) {
+        psError (PSASTRO_ERR_UNKNOWN, false, "error reading input data\n");
+        return false;
+    }
+
+    if (!psastroAnalysis (config)) {
+        psError (PSASTRO_ERR_UNKNOWN, false, "failure in psastro analysis\n");
+        return false;
+    }
+
+    // deactivate the psastro files, reactive when needed
+    pmFPAfileActivate (config->files, false, NULL);
+    pmFPAfileActivate (config->files, true, "PSASTRO.OUTPUT");
+
+    // loop over all chips and perform IO needed
+    pmChip *chip = NULL;
+    pmFPAview *view = pmFPAviewAlloc(0);// View for level of interest
+    while ((chip = pmFPAviewNextChip(view, input->fpa, 1)) != NULL) {
+        psLogMsg ("ppImageLoop", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+
+        ppImageMemoryDump("astrom");
+
+        // Output and Close at Chip level
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+            psFree(view);
+            return false;
+        }
+    }
+
+    // Output and Close FPA
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+        psFree(view);
+        return false;
+    }
+
+    // deactivate the PSASTRO files, re-active all else
+    // XXX do we need a way to activate / deactivate other groups?
+    pmFPAfileActivate (config->files, true, NULL);
+    pmFPAfileActivate (config->files, false, "PSASTRO.OUTPUT");
+
+    psFree(view);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageCleanup.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageCleanup.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageCleanup.c	(revision 22157)
@@ -0,0 +1,28 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include "ppImage.h"
+
+void ppImageCleanup (pmConfig *config, ppImageOptions *options)
+{
+    // Free memory used by ppImage
+    psFree(options);
+    psFree(config);
+
+    // Free memory used by psModules
+    pmConceptsDone();
+    pmConfigDone();
+    pmModelClassCleanup();
+
+    // Free memory used by psLib
+    psLibFinalize();
+
+    // psMemBlock **memblocks;
+    // int Nleaks = psMemCheckLeaks (0, &memblocks, stderr, false);
+    // fprintf (stderr, "Found %d leaks at %s\n", Nleaks, "ppImage");
+
+    fprintf (stderr, "Found %d leaks at %s\n", psMemCheckLeaks (0, NULL, NULL, false), "ppImage");
+
+    return;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageDefineFile.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageDefineFile.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageDefineFile.c	(revision 22157)
@@ -0,0 +1,55 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include "ppImage.h"
+
+bool ppImageDefineFile (pmConfig *config, pmFPA *input, char *filerule, char *argname, pmFPAfileType fileType, pmDetrendType detrendType) {
+
+    bool status;
+    pmFPAfile *file;
+
+    // look for the file on the argument list
+    file = pmFPAfileDefineFromArgs  (&status, config, filerule, argname);
+    if (!status) {
+	psError (PS_ERR_UNKNOWN, false, "failed to load find definition");
+	return false;
+    }
+    if (file) {
+	if (file->type != fileType) {
+	    psError(PS_ERR_IO, true, "%s is not of type %s", filerule, pmFPAfileStringFromType (fileType));
+	    return false;
+	}
+	return true;
+    }
+
+    // look for the file in the camera config table
+    file = pmFPAfileDefineFromConf  (&status, config, filerule);
+    if (!status) {
+	psError (PS_ERR_UNKNOWN, false, "failed to load find definition");
+	return false;
+    }
+    if (file) {
+	if (file->type != fileType) {
+	    psError(PS_ERR_IO, true, "%s is not of type %s", filerule, pmFPAfileStringFromType (fileType));
+	    return false;
+	}
+	return true;
+    }
+
+    // look for the file to be loaded from the detrend database
+    file = pmFPAfileDefineFromDetDB (&status, config, filerule, input, detrendType);
+    if (!status) {
+	psError (PS_ERR_UNKNOWN, false, "failed to load file definition");
+	return false;
+    }
+    if (file) {
+	if (file->type != fileType) {
+	    psError(PS_ERR_IO, true, "%s is not of type %s", filerule, pmFPAfileStringFromType (fileType));
+	    return false;
+	}
+	return true;
+    }
+    return false;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageDetrendFringe.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageDetrendFringe.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageDetrendFringe.c	(revision 22157)
@@ -0,0 +1,338 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+bool ppImageDetrendFringeMeasure(pmReadout *readout, pmCell *fringe, const bool isResidual, const ppImageOptions *options)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(fringe, false);
+    PS_ASSERT_PTR_NON_NULL(options, false);
+
+    // Reference fringe measurements
+    psArray *references = psMemIncrRefCounter(psMetadataLookupPtr(NULL, fringe->analysis,
+                                                                  "FRINGE.MEASUREMENTS"));
+    if (!references) {
+        references = pmFringesParse(fringe); // Reference fringes
+        if (!references) {
+            psError(PS_ERR_IO, false, "Unable to find fringe references.\n");
+            return false;
+        }
+        psMetadataAdd(fringe->analysis, PS_LIST_TAIL, "FRINGE.MEASUREMENTS", PS_DATA_UNKNOWN,
+                      "Fringe measurements", references);
+    }
+
+    pmFringeStats *reference = references->data[0]; // Take the first as representative
+    pmFringeRegions *regions = reference->regions; // Regions to measure
+    pmFringeStats *measurements = pmFringeStatsMeasure(regions, readout, options->maskValue); // Fringe stats
+
+    // Normalise measurements by the exposure time
+    bool mdok;                          // Status of MD lookup
+    float expTime = psMetadataLookupF32(&mdok, readout->parent->concepts, "CELL.EXPOSURE"); // Exp. time
+    if (!mdok || !isfinite(expTime)) {
+        psError(PS_ERR_UNKNOWN, false, "CELL.EXPOSURE is not set for --- can't normalise fringes\n");
+        psFree(measurements);
+        return false;
+    }
+    if (expTime == 0) {
+        psWarning("Exposure time is zero --- are you sure you want fringe subtraction?\n");
+        expTime = 1.0;
+    }
+    // normalize by exposure time : NOTE df is 1/sigma
+    psBinaryOp(measurements->f, measurements->f, "*", psScalarAlloc(1.0 / expTime, PS_TYPE_F32));
+    psBinaryOp(measurements->df, measurements->df, "*", psScalarAlloc(expTime, PS_TYPE_F32));
+
+    char *scienceFringes = NULL;
+    if (isResidual) {
+	scienceFringes = psStringCopy ("FRINGE.RESIDUALS");
+    } else {
+	scienceFringes = psStringCopy ("FRINGE.MEASUREMENTS");
+    }
+
+    // Science fringe measurements
+    pmFringeStats *previous = psMetadataLookupPtr(NULL, readout->parent->analysis, scienceFringes);
+    if (previous) {
+        // Multiple readouts: concatenate
+        psArray *concatenate = psArrayAlloc(2); // Array to hold fringes
+
+        // Concatenate science measurements
+        concatenate->data[0] = previous;
+        concatenate->data[1] = measurements;
+        pmFringeStats *new = pmFringeStatsConcatenate(concatenate, NULL, NULL); // New measurements
+        psFree(measurements);
+        measurements = new;
+
+        // Concatenate reference measurements (duplication, so the science and reference line up)
+        for (int i = 0; i < references->n; i++) {
+            concatenate->data[0] = concatenate->data[1] = references->data[i];
+            pmFringeStats *refNew = pmFringeStatsConcatenate(concatenate, NULL, NULL);
+            psFree(references->data[i]);
+            references->data[i] = refNew;
+        }
+        concatenate->data[0] = concatenate->data[1] = NULL;
+        psFree(concatenate);
+    }
+
+    psMetadataAdd(readout->parent->analysis, PS_LIST_TAIL, scienceFringes,
+                  PS_DATA_UNKNOWN | PS_META_REPLACE, "Fringe measurements", measurements);
+    psFree(measurements);
+    psFree(scienceFringes);
+    psFree(references);
+
+    return true;
+}
+
+
+// Pull the fringes out of the cell analysis FRINGE.MEASUREMENTS for a chip
+// XXX need some error checks
+static psArray *getFringes(const pmChip *chip, const char *source)
+{
+    psArray *cells = chip->cells;       // Component cells
+    psArray *fringes = psArrayAlloc(cells->n); // Fringes, to return
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // Cell of interest
+        fringes->data[i] = psMemIncrRefCounter(psMetadataLookupPtr(NULL, cell->analysis, source));
+    }
+
+    return fringes;
+}
+
+
+// Solve the fringe system: we have science fringe measurements for each cell, and an array of reference
+// fringe measurements for each cell.  Need to concatenate these together first, and then solve.
+bool ppImageDetrendFringeSolve(pmChip *scienceChip, const pmChip *refChip, const bool isResidual, const ppImageOptions *options)
+{
+    PS_ASSERT_PTR_NON_NULL(scienceChip, NULL);
+    PS_ASSERT_PTR_NON_NULL(refChip, NULL);
+    PS_ASSERT_PTR_NON_NULL(options, NULL);
+
+    psArray *science = NULL;
+    if (isResidual) {
+	science = getFringes(scienceChip, "FRINGE.RESIDUALS"); // Fringe residuals on science chip
+    } else {
+	science = getFringes(scienceChip, "FRINGE.MEASUREMENTS"); // Fringe measurements on science chip
+    }
+
+    pmFringeStats *scienceCat = pmFringeStatsConcatenate(science, NULL, NULL); // Science fringes
+    psFree(science);
+
+    // Need to transform the array of cells each with an array of fringes --> array of fringes for the chip as
+    // a whole
+    psArray *references = getFringes(refChip, "FRINGE.MEASUREMENTS"); // Fringe measurements on reference chip
+    int numRefs = ((psArray*)references->data[0])->n; // Number of reference fringes
+    psArray *referencesCat = psArrayAlloc(numRefs); // Reference fringes
+    for (int i = 0; i < numRefs; i++) { // Iterate over fringes
+        psArray *refs = psArrayAlloc(references->n); // Array of fringes for each cell
+        for (int j = 0; j < references->n; j++) { // Iterate over cells
+            psArray *ref = references->data[j]; // Array of references for this cell
+            refs->data[j] = psMemIncrRefCounter(ref->data[i]);
+        }
+        referencesCat->data[i] = pmFringeStatsConcatenate(refs, NULL, NULL);
+        psFree(refs);
+    }
+    psFree(references);
+
+    // Now we can solve
+    psTrace("ppImage", 3, "Solving fringe system...\n");
+    pmFringeScale *solution = pmFringeScaleMeasure(scienceCat, referencesCat, options->fringeRej,
+                                                   options->fringeIter, options->fringeKeep);
+
+    if (isResidual) {
+	psMetadataAdd(scienceChip->analysis, PS_LIST_TAIL, "FRINGE.RESIDUAL.SOLUTION", PS_DATA_UNKNOWN, "Fringe solution", solution);
+    } else {
+	psMetadataAdd(scienceChip->analysis, PS_LIST_TAIL, "FRINGE.SOLUTION", PS_DATA_UNKNOWN, "Fringe solution", solution);
+    }
+
+# if (0)
+    // write the fringe amplitude or residual amplitude to the header
+    // XXX this is measured per cell, but we only have headers per chip
+    pmHDU *hdu = pmHDUFromCell(science);// HDU  of interest
+    for (int i = 0; i < solution->nFringeFrames; i++) {
+	// write metadata header value
+	psString keyword = NULL;
+	if (isResidual) {
+	    psStringAppend (&keyword, "FRES_%02dV", i);
+	} else {
+	    psStringAppend (&keyword, "FRNG_%02dV", i);
+	}
+	psMetadataAddF32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, "Fringe Amplitude", solution->coeff->data.F32[i + 1]);
+	psFree (keyword);
+
+	keyword = NULL;
+	if (isResidual) {
+	    psStringAppend (&keyword, "FRES_%02dE", i);
+	} else {
+	    psStringAppend (&keyword, "FRNG_%02dE", i);
+	}
+	psMetadataAddF32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, "Fringe Amplitude error", solution->coeffErr->data.F32[i + 1]);
+	psFree (keyword);
+    }
+# endif
+
+    psFree(solution);
+    psFree(scienceCat);
+    psFree(referencesCat);
+
+    return true;
+}
+
+
+bool ppImageDetrendFringeGenerate(pmCell *science, pmCell *fringes, const ppImageOptions *options)
+{
+    PS_ASSERT_PTR_NON_NULL(science, false);
+    PS_ASSERT_PTR_NON_NULL(fringes, false);
+
+    pmFringeScale *solution = psMetadataLookupPtr(NULL, science->parent->analysis, "FRINGE.SOLUTION");
+    if (!solution) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find fringe solution.\n");
+        return false;
+    }
+    assert(fringes->readouts->n == solution->nFringeFrames);
+
+    bool mdok;                          // Status of MD lookup
+    float expTime = psMetadataLookupF32(&mdok, science->concepts, "CELL.EXPOSURE"); // Exp. time
+    if (!mdok || !isfinite(expTime)) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.EXPOSURE is not set --- can't renormalise fringes\n");
+        return false;
+    }
+    if (expTime == 0) {
+        psWarning("Exposure time is zero --- are you sure you want fringe subtraction?\n");
+        expTime = 1.0;
+    }
+
+    pmHDU *hdu = pmHDUFromCell(science);// HDU  of interest
+
+    // Construct the fringe image from the scale
+    psTrace("ppImage", 3, "Generating fringe correction...\n");
+    psImage *sumFringe = NULL; // Sum of the fringes
+    for (int i = 0; i < solution->nFringeFrames; i++) {
+        pmReadout *fringeRO = fringes->readouts->data[i]; // Fringe readout
+        psImage *fringe = fringeRO->image; // Fringe image
+        psTrace("ppImage", 5, "Scale for fringe component %d is %f\n",
+                i, solution->coeff->data.F32[i + 1]);
+        psBinaryOp(fringe, fringe, "*", psScalarAlloc(solution->coeff->data.F32[i + 1], PS_TYPE_F32));
+        if (!sumFringe) {
+            sumFringe = psImageCopy(NULL, fringe, PS_TYPE_F32);
+        } else {
+            psBinaryOp(sumFringe, sumFringe, "+", fringe);
+        }
+
+        psVector *md5 = psImageMD5(fringe); // md5 hash
+        psString md5string = psMD5toString(md5); // String
+        psFree(md5);
+        psStringPrepend(&md5string, "Fringe image %d (scale %.3f) MD5: ",
+                        i, solution->coeff->data.F32[i + 1]);
+        psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK,
+                         md5string, "");
+        psFree(md5string);
+
+# if (0)
+	// write metadata header value
+	// XXX this is measured per cell, but we only have headers per chip
+	psString keyword = NULL;
+	psStringAppend (&keyword, "FRNG_%02dV", i);
+	psMetadataAddF32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, "Fringe Amplitude", solution->coeff->data.F32[i + 1]);
+	psFree (keyword);
+
+	keyword = NULL;
+	psStringAppend (&keyword, "FRNG_%02dE", i);
+	psMetadataAddF32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, "Fringe Amplitude error", solution->coeffErr->data.F32[i + 1]);
+	psFree (keyword);
+# endif
+
+    }
+    if (expTime != 1.0) {
+        psBinaryOp(sumFringe, sumFringe, "*", psScalarAlloc(expTime, PS_TYPE_F32));
+    }
+
+    // Apply the correction to all readouts
+    psArray *readouts = science->readouts; // Array of science readouts
+    for (int i = 0; i < readouts->n; i++) {
+        pmReadout *readout = readouts->data[i]; // Readout of interest
+        if (!readout || !readout->data_exists) {
+            continue;
+        }
+
+        // XXX: Make generic, so subregions may be subtracted as well
+        if (!psBinaryOp(readout->image, readout->image, "-", sumFringe)) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to subtract fringe.\n");
+            return false;
+        }
+
+	// measure residual fringe amplitude. results go to FRINGE.RESIDUALS
+	ppImageDetrendFringeMeasure (readout, fringes, true, options);
+    }
+    psFree(sumFringe);
+
+    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now, used for reporting
+    psString timeString = psTimeToISO(time); // String with time
+    psFree(time);
+    psStringPrepend(&timeString, "Fringe correction completed at ");
+    psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK,
+                     timeString, "");
+    psFree(timeString);
+
+    return sumFringe;
+}
+
+bool ppImageDetrendFringeApply (pmConfig *config, pmChip *chip, const pmFPAview *inputView, const ppImageOptions *options) {
+
+    pmCell *cell = NULL;
+
+    assert (options->doFringe); // do not call if not needed
+    assert (inputView->chip != -1);
+    assert (inputView->cell == -1);
+    assert (inputView->readout == -1);
+
+    pmFPAview *view = pmFPAviewAlloc(0); // View for local processing
+    *view = *inputView;
+
+    // select the reference chip
+    pmChip *fringe = pmFPAfileThisChip(config->files, view, "PPIMAGE.FRINGE");
+    if (!fringe) {
+	psError(PS_ERR_UNKNOWN, false, "missing fringe reference data.\n");
+	psFree (view);
+	return false;
+    }
+
+    // Solve the fringe system
+    if (!ppImageDetrendFringeSolve(chip, fringe, false, options)) {
+	psError(PS_ERR_UNKNOWN, false, "failed to solve the fringe system.\n");
+	psFree (view);
+	return false;
+    }
+
+    // Go back over the cells to apply the fringe correction
+    view->cell = view->readout = -1;
+    while ((cell = pmFPAviewNextCell(view, chip->parent, 1)) != NULL) {
+	if (!cell->process || !cell->file_exists) {
+	    continue;
+	}
+
+	// Apply the fringe correction
+	psTrace("ppImage", 3, "Applying fringe correction...\n");
+	pmCell *fringeCell = pmFPAfileThisCell(config->files, view, "PPIMAGE.FRINGE");
+	if (!fringeCell) {
+	    psError(PS_ERR_UNKNOWN, false, "missing fringe reference data.\n");
+	    psFree (view);
+	    return false;
+	}
+
+	if (!ppImageDetrendFringeGenerate(cell, fringeCell, options)) {
+	    psError(PS_ERR_UNKNOWN, false, "failed to apply fringe image.\n");
+	    psFree (view);
+	    return false;
+	}
+    }
+
+    // Solve the residual fringe system
+    if (!ppImageDetrendFringeSolve(chip, fringe, true, options)) {
+	psError(PS_ERR_UNKNOWN, false, "failed to solve the residual fringe system.\n");
+	psFree (view);
+	return false;
+    }
+
+    psFree (view);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageDetrendNonLinear.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageDetrendNonLinear.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageDetrendNonLinear.c	(revision 22157)
@@ -0,0 +1,114 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+bool ppImageDetrendNonLinearPolynomial(pmReadout *input, psMetadataItem *dataItem) {
+
+    // These are the polynomial coefficients
+    psVector *coeff = dataItem->data.V; // The coefficient vector
+    if (coeff->type.type != PS_TYPE_F64) {
+        psVector *temp = psVectorCopy(NULL, coeff, PS_TYPE_F64); // F64 version
+        psFree (coeff);
+        coeff = temp;
+    }
+    psPolynomial1D *correction = psPolynomial1DAlloc(coeff->n - 1, PS_POLYNOMIAL_ORD);
+    psFree(correction->coeff);
+    correction->coeff = psMemIncrRefCounter(coeff->data.F64);
+    pmNonLinearityPolynomial(input, correction);
+    psFree(coeff);
+    psFree(correction);
+    return true;
+}
+
+bool ppImageDetrendNonLinearLookup(pmReadout *input, psMetadataItem *dataItem) {
+
+    // This is a filename: lookup table
+    char *name = dataItem->data.V;       // Filename
+    psLookupTable *table = psLookupTableAlloc(name, "%f %f", 0);
+    if (psLookupTableRead(table) <= 0) {
+        psErrorStackPrint(stderr, "Unable to read non-linearity correction file "
+                          "%s --- ignored\n", name);
+        return false;
+    }
+#ifdef PRODUCTION
+    pmNonLinearityLookup(input, table);
+#else
+    psVector *influx = table->values->data[0];
+    psVector *outflux = table->values->data[1];
+    pmNonLinearityLookup(input, influx, outflux);
+#endif
+    psFree(table);
+    return true;
+}
+
+bool ppImageDetrendNonLinear(pmReadout *input, ppImageOptions *options) {
+
+    psMetadataItem *concept;
+    pmCell *cell = input->parent;
+
+    switch (options->nonLinearType) {
+      case PS_DATA_VECTOR:
+        ppImageDetrendNonLinearPolynomial (input, options->nonLinearData);
+        return true;
+
+      case PS_DATA_STRING:
+        ppImageDetrendNonLinearLookup (input, options->nonLinearData);
+        return true;
+
+      case PS_DATA_METADATA:
+        // XXX EAM: this is somewhat confusing : let's wrap in a function when i understand it
+
+        // Go looking for the value in the hierarchy
+        concept = psMetadataLookup(cell->concepts, options->nonLinearSource);
+        if (! concept) {
+            pmChip *chip = cell->parent;// Parent chip
+            concept = psMetadataLookup(chip->concepts, options->nonLinearSource);
+            if (! concept) {
+                pmFPA *fpa = chip->parent; // Parent FPA
+                concept = psMetadataLookup(fpa->concepts, options->nonLinearSource);
+                if (! concept) {
+                    psLogMsg("phase2", PS_LOG_WARN, "Unable to find value of concept %s "
+                             "for non-linearity correction --- ignored.\n", (char *)options->nonLinearSource);
+                    return false;
+                }
+            }
+        }
+
+        if (concept->type != PS_DATA_STRING) {
+            psLogMsg("phase2", PS_LOG_WARN, "Type for concept %p isn't STRING, as"
+                     " expected for non-linearity correction --- ignored.\n",
+                     concept);
+            return false;
+        }
+
+        // Get the value of the concept
+        psString conceptValue = concept->data.V;
+        psMetadata *folder = (psMetadata *)options->nonLinearData->data.V;
+        psMetadataItem *optionItem = psMetadataLookup(folder, conceptValue);
+        if (!optionItem) {
+            psLogMsg("phase2", PS_LOG_WARN, "Unable to find %s in NONLIN.DATA"
+                     " --- ignored.\n", conceptValue);
+            return false;
+        }
+
+        switch (optionItem->type) {
+          case PS_DATA_VECTOR:
+            ppImageDetrendNonLinearPolynomial (input, optionItem);
+            return true;
+          case PS_DATA_STRING:
+            ppImageDetrendNonLinearLookup (input, optionItem);
+            return true;
+          default:
+            psLogMsg("phase2", PS_LOG_WARN, "Non-linearity correction "
+                     "desired but unable to interpret NONLIN.DATA for %s"
+                     " --- ignored\n", conceptValue);
+            return false;
+        }
+      default:
+        psAbort("Invalid options->nonLinearType");
+    }
+    return true;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageDetrendReadout.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageDetrendReadout.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageDetrendReadout.c	(revision 22157)
@@ -0,0 +1,113 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+bool ppImageDetrendReadout(pmConfig *config, ppImageOptions *options, pmFPAview *view)
+{
+    // construct a view for the detrend images (which have only one readout)
+    pmFPAview *detview = pmFPAviewAlloc(0);
+    *detview = *view;
+    detview->readout = 0;
+
+    // find the currently selected readout
+    pmReadout *input = pmFPAfileThisReadout(config->files, view, "PPIMAGE.INPUT");
+
+    // Masking on the basis of pixel value needs to be done before anything else, so the values are pristine.
+    if (options->doMaskBuild) {
+        pmReadoutGenerateMask(input, options->satMask, options->badMask);
+    }
+    // apply the externally supplied mask to the input->mask pixels
+    if (options->doMask) {
+        pmReadout *mask = pmFPAfileThisReadout(config->files, detview, "PPIMAGE.MASK");
+        pmMaskBadPixels(input, mask, options->maskValue);
+    }
+
+# if 0
+    // Non-linearity correction
+    if (options->doNonLin) {
+        ppImageDetrendNonLinear(detrend->input, input, options);
+    }
+# endif
+
+    // set up the dark and bias
+    pmCell *dark = NULL;                // Multi-dark
+    pmReadout *oldDark = NULL;          // Old-fashioned dark
+    pmReadout  *bias = NULL;
+    if (options->doBias) {
+        bias = pmFPAfileThisReadout(config->files, detview, "PPIMAGE.BIAS");
+    }
+    if (options->doDark) {
+        bool mdok;                      // Status of MD lookup
+        psMetadata *recipe = psMetadataLookupPtr (&mdok, config->recipes, RECIPE_NAME);
+        assert(mdok && recipe);
+        if (psMetadataLookupBool(&mdok, recipe, "OLDDARK")) {
+            oldDark = pmFPAfileThisReadout(config->files, detview, "PPIMAGE.DARK");
+        } else {
+            dark = pmFPAfileThisCell(config->files, detview, "PPIMAGE.DARK");
+        }
+    }
+
+    // Bias, dark and overscan subtraction are all merged.
+    if (options->doBias || options->doOverscan) {
+        if (!pmBiasSubtract(input, options->overscan, bias, oldDark, view)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to subtract bias.");
+            return false;
+        }
+    }
+
+    // Weight on the basis of pixel value needs to be done after the overscan has been subtracted
+    if (options->doWeightBuild) {
+        // create the target mask and weight images
+        pmReadoutGenerateWeight(input, true);
+    }
+
+    if (options->doDark && dark) {
+        if (!pmDarkApply(input, dark, options->maskValue)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to subtract dark.");
+            return false;
+        }
+    }
+
+    // Shutter correction
+    if (options->doShutter) {
+        pmReadout *shutter = pmFPAfileThisReadout(config->files, detview, "PPIMAGE.SHUTTER");
+        if (!pmShutterCorrectionApply(input, shutter, pmConfigMaskGet("FLAT", config))) {
+            return false;
+        }
+    }
+
+    // Flat-field correction (no options used?)
+    if (options->doFlat) {
+        pmReadout *flat = pmFPAfileThisReadout(config->files, detview, "PPIMAGE.FLAT");
+        if (!pmFlatField(input, flat, options->flatMask)) {
+            return false;
+        }
+    }
+
+    // Normalisation by a (known) constant
+    bool mdok;                          // Status of MD lookup
+    float norm = psMetadataLookupF32(&mdok, config->arguments, "NORMALISATION");
+    if (mdok && isfinite(norm) && norm != 1.0) {
+        pmHDU *hdu = pmHDUFromReadout(input); // HDU of interest
+        psString comment = NULL;        // Comment to add
+        psStringAppend(&comment, "Normalization: %f", norm);
+        psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, comment, "");
+        psFree(comment);
+
+        psBinaryOp(input->image, input->image, "*", psScalarAlloc(norm, PS_TYPE_F32));
+    }
+
+    if (options->doFringe) {
+        pmCell *fringe = pmFPAfileThisCell(config->files, detview, "PPIMAGE.FRINGE");
+        if (!ppImageDetrendFringeMeasure(input, fringe, false, options)) {
+            return false;
+        }
+    }
+
+    ppImageMemoryDump("detrend");
+
+    psFree(detview);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageErrorCodes.c.in
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageErrorCodes.c.in	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageErrorCodes.c.in	(revision 22157)
@@ -0,0 +1,26 @@
+/*
+ * The line
+    { PPIMAGE_ERR_$X{ErrorCode}, "$X{ErrorDescription}"},
+ * (without the Xs)
+ * will be replaced by values from errorCodes.dat
+ */
+#include "pslib.h"
+#include "ppImageErrorCodes.h"
+
+void ppImageErrorRegister(void)
+{
+    static psErrorDescription errors[] = {
+       { PPIMAGE_ERR_BASE, "First value we use; lower values belong to psLib" },
+       { PPIMAGE_ERR_${ErrorCode}, "${ErrorDescription}"},
+    };
+    static int nerror = PPIMAGE_ERR_NERROR - PPIMAGE_ERR_BASE; // number of values in enum
+
+    for (int i = 0; i < nerror; i++) {
+       psErrorDescription *tmp = psAlloc(sizeof(psErrorDescription));
+       p_psMemSetPersistent(tmp, true);
+       *tmp = errors[i];
+       psErrorRegister(tmp, 1);
+       psFree(tmp);			/* it's on the internal list */
+    }
+    nerror = 0;			                // don't register more than once
+}
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageErrorCodes.dat
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageErrorCodes.dat	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageErrorCodes.dat	(revision 22157)
@@ -0,0 +1,10 @@
+#
+# This file is used to generate ppImageErrorClasses.h
+#
+BASE = 500		First value we use; lower values belong to psLib
+# these errors correspond to standard exit conditions
+ARGUMENTS               Incorrect arguments
+SYS                     System error
+CONFIG                  Problem in configure files
+PROG                    Programming error
+DATA                    invalid data
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageErrorCodes.h.in
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageErrorCodes.h.in	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageErrorCodes.h.in	(revision 22157)
@@ -0,0 +1,18 @@
+#if !defined(PPIMAGE_ERROR_CODES_H)
+#define PPIMAGE_ERROR_CODES_H
+/*
+ * The line
+ *  PPIMAGE_ERR_$X{ErrorCode},
+ * (without the X)
+ *
+ * will be replaced by values from errorCodes.dat
+ */
+typedef enum {
+    PPIMAGE_ERR_BASE = 512,
+    PPIMAGE_ERR_${ErrorCode},
+    PPIMAGE_ERR_NERROR
+} ppImageErrorCode;
+
+void ppImageErrorRegister(void);
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageFileCheck.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageFileCheck.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageFileCheck.c	(revision 22157)
@@ -0,0 +1,60 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+void ppImageFileCheck (pmConfig *config) {
+
+    // add the output names to the output-type files
+    psMetadataItem *item = NULL;
+    psMetadataIterator *iter = psMetadataIteratorAlloc (config->files, PS_LIST_HEAD, NULL);
+    while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
+        pmFPAfile *file = item->data.V;
+        pmFPA *fpa = file->fpa;
+        fprintf (stderr, "file %s\n", file->name);
+        if (!fpa) {
+            fprintf (stderr, "  has no FPA\n");
+            continue;
+        }
+        if (fpa->hdu) {
+            if (fpa->hdu->images) fprintf (stderr, "  (%d,%d) images\n", -1, -1);
+            if (fpa->hdu->weights) fprintf (stderr, "  (%d,%d) weights\n", -1, -1);
+            if (fpa->hdu->masks) fprintf (stderr, "  (%d,%d) masks\n", -1, -1);
+            if (fpa->hdu->header) fprintf (stderr, "  (%d,%d) header\n", -1, -1);
+        } else {
+            // fprintf (stderr, "  has no fpa data (%d,%d)\n", -1, -1);
+        }
+        for (int i = 0; i < fpa->chips->n; i++) {
+            pmChip *chip = fpa->chips->data[i];
+            if (chip->hdu) {
+                if (chip->hdu->images) fprintf (stderr, "  (%d,%d) images\n", i, -1);
+                if (chip->hdu->weights) fprintf (stderr, "  (%d,%d) weights\n", i, -1);
+                if (chip->hdu->masks) fprintf (stderr, "  (%d,%d) masks\n", i, -1);
+                if (chip->hdu->header) fprintf (stderr, "  (%d,%d) header\n", i, -1);
+            } else {
+                // fprintf (stderr, "  has no chip data (%d,%d)\n", i, -1);
+            }
+            for (int j = 0; j < chip->cells->n; j++) {
+                pmCell *cell = chip->cells->data[j];
+                if (cell->hdu) {
+                    if (cell->hdu->images) fprintf (stderr, "  (%d,%d) images\n", i, j);
+                    if (cell->hdu->weights) fprintf (stderr, "  (%d,%d) weights\n", i, j);
+                    if (cell->hdu->masks) fprintf (stderr, "  (%d,%d) masks\n", i, j);
+                    if (cell->hdu->header) fprintf (stderr, "  (%d,%d) header\n", i, j);
+                } else {
+                    // fprintf (stderr, "  has no cell data (%d,%d)\n", i, j);
+                }
+                for (int k = 0; k < cell->readouts->n; k++) {
+                    pmReadout *readout = cell->readouts->data[k];
+                    if (readout) {
+                        if (readout->image) fprintf (stderr, "  (%d,%d,%d) image\n", i, j, k);
+                        if (readout->weight) fprintf (stderr, "  (%d,%d,%d) weight\n", i, j, k);
+                        if (readout->mask) fprintf (stderr, "  (%d,%d,%d) masks\n", i, j, k);
+                    }
+                }
+            }
+        }
+    }
+    psFree (iter);
+}
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageLoop.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageLoop.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageLoop.c	(revision 22157)
@@ -0,0 +1,144 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+# define ESCAPE(MESSAGE) { \
+  psError(PS_ERR_UNKNOWN, false, MESSAGE); \
+  psFree (view); \
+  return false; \
+}
+
+bool ppImageLoop (pmConfig *config, ppImageOptions *options) {
+
+    bool status;
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+
+    // measure the total elapsed time in ppImageLoop.
+    psTimerStart ("ppImageLoop");
+
+    pmFPAfile *input = psMetadataLookupPtr(&status, config->files, "PPIMAGE.INPUT");
+    if (!status) {
+        psErrorStackPrint(stderr, "Can't find input data!\n");
+        ppImageCleanup(config, options);
+        exit(PS_EXIT_PROG_ERROR);
+    }
+
+    pmFPAview *view = pmFPAviewAlloc(0);// View for level of interest
+    pmHDU *lastHDU = NULL;              // Last HDU that was updated
+
+    // files associated with the science image
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) ESCAPE ("load failure for FPA");
+
+    while ((chip = pmFPAviewNextChip(view, input->fpa, 1)) != NULL) {
+        psLogMsg ("ppImageLoop", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) {
+            continue;
+        }
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) ESCAPE ("load failure for Chip");
+
+        while ((cell = pmFPAviewNextCell(view, input->fpa, 1)) != NULL) {
+            psLogMsg ("ppImageLoop", 5, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) {
+                continue;
+            }
+            if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) ESCAPE ("load failure for Cell");
+
+            // Put version information into the header
+            pmHDU *hdu = pmHDUFromCell(cell);
+            if (hdu && hdu != lastHDU) {
+                ppImageVersionMetadata(hdu->header);
+                lastHDU = hdu;
+            }
+
+
+            // process each of the readouts
+            while ((readout = pmFPAviewNextReadout (view, input->fpa, 1)) != NULL) {
+                if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) ESCAPE ("load failure for Readout");
+                if (!readout->data_exists) {
+                    continue;
+                }
+
+		// XXX set the options->*Mask values here (after the mask images have been loaded
+		// and before any of the value are used)
+		if (!ppImageSetMaskBits (config, options)) 
+		    ESCAPE ("Unable to set bit masks");
+
+                // perform the detrend analysis
+                if (!ppImageDetrendReadout(config, options, view)) 
+		    ESCAPE ("Unable to detrend readout");
+            }
+        }
+
+        // Apply the fringe correction
+        if (options->doFringe) {
+	    if (!ppImageDetrendFringeApply (config, chip, view, options)) 
+		ESCAPE ("Unable to defringe");
+	}
+	
+	// measure various statistics for this image
+	if (!ppImagePixelStats (config, options, view)) 
+	    ESCAPE ("Unable to measures stats for image");
+
+        if (!ppImageMosaicChip(config, options, view, "PPIMAGE.CHIP", "PPIMAGE.OUTPUT"))
+            ESCAPE ("Unable to mosaic chip");
+
+        if (!ppImageRebinChip(config, view, "PPIMAGE.BIN1"))
+            ESCAPE ("Unable to bin chip (level 1).");
+
+        if (!ppImageRebinChip(config, view, "PPIMAGE.BIN2"))
+            ESCAPE ("Unable to bin chip (level 2).");
+
+        // we perform photometry on the readouts of this chip in the output
+        if (options->doPhotom) {
+            if (!ppImagePhotom(config, view)) ESCAPE ("error running photometry.");
+        }
+
+	// replace the masked pixels with the background level
+	if (options->replaceMasked) {
+	    if (!ppImageReplaceBackground (config, view, options)) 
+		ESCAPE ("Unable to replace masked pixels with background level");
+	}
+
+        // Close cells (XXX shouldn't pmFPAfileClose iterate down as needed?)
+        view->cell = -1;
+        while ((cell = pmFPAviewNextCell(view, input->fpa, 1)) != NULL) {
+            if (!cell->process || !cell->file_exists) {
+                continue;
+            }
+            if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) ESCAPE ("save failure for Cell");
+        }
+
+        // Close chip
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) ESCAPE ("save failure for Chip");
+    }
+
+    // generate the full-scale FPA mosaic
+    if (!ppImageMosaicFPA(config, options, "PPIMAGE.OUTPUT.FPA1", "PPIMAGE.BIN1")) ESCAPE ("failure in FPA Mosaic (level 1)");
+    if (!ppImageMosaicFPA(config, options, "PPIMAGE.OUTPUT.FPA2", "PPIMAGE.BIN2")) ESCAPE ("failure in FPA Mosaic (level 2)");
+
+    // we perform astrometry on all chips after sources have been detected
+    // this also performs the psastro file IO
+    if (options->doAstromChip || options->doAstromMosaic) {
+        if (!ppImageAstrom(config)) ESCAPE ("error running astrometry.");
+    }
+
+    if (psTraceGetLevel("ppImage") >= 3) {
+	ppImageFileCheck (config);
+    }
+
+    // Write out summary statistics
+    if (!ppImageMetadataStats (config, options)) ESCAPE ("Unable to write statistics file.");
+
+    // Write out summary statistics
+    if (!ppImageStatsOutput (config, options)) ESCAPE ("Unable to write statistics file.");
+
+    // Output and Close FPA
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) ESCAPE ("save failure for FPA");
+
+    psFree(view);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageMemory.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageMemory.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageMemory.c	(revision 22157)
@@ -0,0 +1,38 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+
+#include "ppImage.h"
+
+//#define TESTING
+
+void ppImageMemoryDump(const char *description)
+{
+#ifndef TESTING
+    return;
+#else
+    psMemBlock **leaks = NULL;
+    int numLeaks = psMemCheckLeaks(0, &leaks, NULL, true);
+
+    static int num = 0;
+    psString filename = NULL;
+    psStringAppend(&filename, "%s_%d.txt", description, num++);
+    FILE *fp = fopen(filename, "w");
+    psFree(filename);
+
+    for (int i = 0; i < numLeaks; i++) {
+        psMemBlock *memBlock = leaks[i];
+        fprintf(fp, "%ld : %s at (%s:%d)  ID: %lu  Ref: %lu\n",
+                (unsigned long)memBlock->userMemorySize, memBlock->func, memBlock->file,
+                (int)memBlock->lineno, (unsigned long)memBlock->id, memBlock->refCounter);
+    }
+    psFree(leaks);
+
+    fclose(fp);
+
+    return;
+#endif
+}
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageMetadataStats.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageMetadataStats.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageMetadataStats.c	(revision 22157)
@@ -0,0 +1,60 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+// calculate stats from headers and concepts
+bool ppImageMetadataStats (pmConfig *config, const ppImageOptions *options) {
+
+    bool mdok;              // Status of MD lookup
+    
+    if (!options->doStats) {
+	return true;
+    }
+
+    // Extract statistics from the last output fpa
+    pmFPAfile *outImage  = psMetadataLookupPtr(&mdok, config->files, "PPIMAGE.OUTPUT");
+    pmFPAfile *outPhotom = psMetadataLookupPtr(&mdok, config->files, "PSPHOT.OUTPUT");
+    pmFPAfile *outAstrom = psMetadataLookupPtr(&mdok, config->files, "PSASTRO.OUTPUT");
+
+    if (!outImage && !outPhotom && !outAstrom) {
+	psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find any output file (PPIMAGE.OUTPUT, PSPHOT.OUTPUT, PSASTRO.OUTPUT).");
+	return false;
+    }
+
+    // get the latest output product available
+    pmFPAfile *output = outAstrom;
+    if (!output) {
+	output = outPhotom;
+    }
+    if (!output) {
+	output = outImage;
+    }
+
+    // select or create the fpa-level analysis stats metadata for PPIMAGE.OUTPUT:
+    psMetadata *stats = psMetadataLookupPtr (&mdok, outImage->fpa->analysis, "PPIMAGE.STATS");
+    psMemIncrRefCounter (stats);
+    if (!stats) {
+	stats = psMetadataAlloc ();
+	if (!psMetadataAdd (outImage->fpa->analysis, PS_LIST_TAIL, "PPIMAGE.STATS", PS_DATA_METADATA, "stats container", stats)) {
+	    psError(PS_ERR_UNKNOWN, false, "Unable to push stats on fpa.analysis.");
+	    psFree (stats);
+	    return false;
+	}
+    }
+
+    // extract stats for the complete fpa 
+    pmFPAview *view = pmFPAviewAlloc(0);
+
+    if (!ppStatsMetadata(stats, output->fpa, view, options->maskValue, config)) {
+	psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to generate stats for image.");
+	psFree(view);
+	psFree(stats);
+	return false;
+    }
+
+    psFree(view);
+    psFree(stats);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageMosaic.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageMosaic.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageMosaic.c	(revision 22157)
@@ -0,0 +1,77 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+bool ppImageMosaicChip(pmConfig *config, const ppImageOptions *options, const pmFPAview *view,
+                       const char *outFile, const char *inFile)
+{
+    bool status;                        // Status of MD lookup
+
+    pmFPAfile *in = psMetadataLookupPtr(&status, config->files, inFile); // Input file
+    if (!status) {
+        psErrorStackPrint(stderr, "Can't find required I/O file!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    pmFPAfile *out = psMetadataLookupPtr(&status, config->files, outFile); // Output file
+    if (!status) {
+        psErrorStackPrint(stderr, "Can't find required I/O file!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    pmChip *outChip = pmFPAviewThisChip(view, out->fpa);
+    pmChip *inChip = pmFPAviewThisChip(view, in->fpa);
+    if (!outChip->hdu && !outChip->parent->hdu) {
+        const char *name = psMetadataLookupStr(&status, in->fpa->concepts, "FPA.NAME"); // Name of FPA
+        pmFPAAddSourceFromView(out->fpa, name, view, out->format);
+    }
+
+    psTrace("pmChipMosaic", 5, "mosaic chip %s to %s (xbin,ybin: %d,%d to %d,%d)\n",
+            in->name, out->name, in->xBin, in->yBin, out->xBin, out->yBin);
+
+    // Mosaic the chip, making a deep copy.  This has the side effect of making the output
+    // image products pure trimmed images, but also increases the memory footprint.
+    status = pmChipMosaic(outChip, inChip, true, options->blankMask);
+    return status;
+}
+
+bool ppImageMosaicFPA (pmConfig *config, const ppImageOptions *options, const char *outFile,
+                       const char *inFile)
+{
+    bool status;                        // Status of MD lookup
+
+    pmFPAfile *in = psMetadataLookupPtr(&status, config->files, inFile); // Input file
+    if (!status) {
+        psErrorStackPrint(stderr, "Can't find required I/O file!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    pmFPAfile *out = psMetadataLookupPtr(&status, config->files, outFile); // Output file
+    if (!status) {
+        psErrorStackPrint(stderr, "Can't find required I/O file!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    // XXX test printing of all concepts
+    #if 0
+    for (int i = 0; i < in->fpa->chips->n; i++) {
+        pmChip *chip = in->fpa->chips->data[i];
+        for (int j = 0; j < chip->cells->n; j++) {
+            pmCell *cell = chip->cells->data[j];
+            psMetadataPrint(stdout, cell->concepts, 2);
+        }
+    }
+    #endif
+
+    const char *name = psMetadataLookupStr(&status, in->fpa->concepts, "FPA.NAME"); // Name of FPA
+
+    pmFPAview *view = pmFPAviewAlloc(0);
+    pmFPAAddSourceFromView(out->fpa, name, view, out->format);
+    psFree(view);
+
+    psTrace ("pmFPAMosaic", 5, "mosaic fpa %s to %s (xbin,ybin: %d,%d to %d,%d)\n",
+             in->name, out->name, in->xBin, in->yBin, out->xBin, out->yBin);
+    return pmFPAMosaic(out->fpa, in->fpa, false, options->blankMask);
+}
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageOptions.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageOptions.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageOptions.c	(revision 22157)
@@ -0,0 +1,272 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+static void imageOptionsFree(ppImageOptions *options)
+{
+    psFree(options->overscan);
+    psFree(options->nonLinearData);
+    psFree(options->nonLinearSource);
+}
+
+ppImageOptions *ppImageOptionsAlloc(void)
+{
+    ppImageOptions *options = psAlloc(sizeof(ppImageOptions));
+    psMemSetDeallocator(options, (psFreeFunc)imageOptionsFree);
+
+    // actions which ppImage should perform
+    options->doMaskBuild     = false;   // Build internal mask
+    options->doWeightBuild   = false;   // Build internal weight
+    options->doMask          = false;   // Mask bad pixels
+    options->doNonLin        = false;   // Non-linearity correction
+    options->doOverscan      = false;   // Overscan subtraction
+    options->doBias          = false;   // Bias subtraction
+    options->doDark          = false;   // Dark subtraction
+    options->doShutter       = false;   // Shutter correction
+    options->doFlat          = false;   // Flat-field normalisation
+    options->doFringe        = false;   // Fringe subtraction
+    options->doPhotom        = false;   // Source identification and photometry
+    options->doAstromChip    = false;   // Astrometry (per-chip)
+    options->doAstromMosaic  = false;   // Astrometry (full-mosaic)
+    options->doStats         = false;   // Measure and save image statistics
+
+    // output files requested
+    options->BaseFITS        = false;   // create output image
+    options->BaseMaskFITS    = false;   // create output mask image
+    options->BaseWeightFITS  = false;   // create output weight image
+
+    options->ChipFITS        = false;   // create output chip-mosaic image
+    options->ChipMaskFITS    = false;   // create output chip-mosaic mask image
+    options->ChipWeightFITS  = false;   // create output chip-mosaic weight image
+
+    options->FPA1FITS        = false;   // create fpa-mosaic binned image (scale 1)
+    options->FPA2FITS        = false;   // create fpa-mosaic binned image (scale 2)
+    options->Bin1FITS        = false;   // create binned image (scale 1)
+    options->Bin2FITS        = false;   // create binned image (scale 2)
+    options->Bin1JPEG        = false;   // create jpeg of binned image (scale 1)
+    options->Bin2JPEG        = false;   // create jpeg of binned image (scale 2)
+
+    // default flags for various activities
+    options->maskValue       = 0x00;    // Default mask value (used to skip / ignore pixels)
+    options->satMask         = 0x00;    // Saturated pixels (supplied to pmReadoutGenerateMask)
+    options->badMask         = 0x00;    // Bad (low) pixels (supplied to pmReadoutGenerateMask)
+    options->flatMask        = 0x00;    // Bad flat pixels (supplied to pmFlatField)
+    options->blankMask       = 0x00;    // Blank (no data, cell gap) pixels (supplied to pmChipMosaic, pmFPAMosaic)
+    options->markValue       = 0x00;    // A safe bit for internal marking
+
+    // Non-linearity default options
+    options->nonLinearType   = 0;       // Type of non-linearity data (vector, string or metadata)
+    options->nonLinearData   = NULL;    // The non-linearity data
+    options->nonLinearSource = NULL;    // If the non-linearity data is a menu, this provides the key
+
+    // Overscan defaults
+    options->overscan        = NULL;    // Overscan options
+
+    // binning parameters
+    options->xBin1           = 16;      // x-binning, scale 1
+    options->yBin1           = 16;      // y-binning, scale 1
+    options->xBin2           = 16;      // x-binning, scale 2
+    options->yBin2           = 16;      // y-binning, scale 2
+
+    // Fringe defaults
+    options->fringeRej       = NAN;     // Fringe rejection limit
+    options->fringeIter      = 0;       // Fringe iterations
+    options->fringeKeep      = 1.0;     // Fringe keep fraction
+
+    return options;
+}
+
+ppImageOptions *ppImageOptionsParse(pmConfig *config)
+{
+    bool status;
+    ppImageOptions *options = ppImageOptionsAlloc ();
+
+    // select the recipe for this analysis
+    bool mdStatus = false;              // Result of MD lookup
+    psMetadata *recipe = psMetadataLookupMetadata(&mdStatus, config->recipes, RECIPE_NAME);
+    if (! mdStatus || !recipe) {
+        psLogMsg("ppImage", PS_LOG_ERROR, "Can't find recipe %s in the RECIPES.\n", RECIPE_NAME);
+        exit(EXIT_FAILURE);
+    }
+
+    // Non-linearity recipe options
+    if (psMetadataLookupBool(NULL, recipe, "NONLIN")) {
+        psMetadataItem *dataItem = psMetadataLookup(recipe, "NONLIN.DATA");
+        if (! dataItem) {
+            psLogMsg(__func__, PS_LOG_ERROR, "Non-linearity correction desired, but unable to "
+                     "find NONLIN.DATA in recipe %s.", RECIPE_NAME);
+            exit(EXIT_FAILURE);
+        }
+
+        options->doNonLin = true;
+        options->nonLinearType = dataItem->type;
+        options->nonLinearData = dataItem;
+
+        switch (dataItem->type) {
+            // No immediate action required
+          case PS_DATA_VECTOR:
+          case PS_DATA_STRING:
+            break;
+
+            // This is a menu; we need the key
+          case PS_DATA_METADATA:
+            {
+                bool status;
+                options->nonLinearSource = psMetadataLookupStr(&status, recipe, "NONLIN.SOURCE");
+                if (! status || ! options->nonLinearSource) {
+                    psLogMsg(__func__, PS_LOG_ERROR, "Non-linearity correction desired, but unable to "
+                            "find NONLIN.SOURCE in recipe %s.", RECIPE_NAME);
+                    exit(EXIT_FAILURE);
+                }
+            }
+            break;
+          default:
+            psLogMsg(__func__, PS_LOG_ERROR, "Non-linearity correction desired, but "
+                    "NONLIN.DATA is of invalid type in recipe %s.", RECIPE_NAME);
+            exit(EXIT_FAILURE);
+        }
+    }
+
+    // XXX PAP: The overscan stuff needs to be updated following the reworked API
+
+    // Overscan recipe options
+    // XXX EAM : we should abort on invalid options. default options?
+    if (psMetadataLookupBool(NULL, recipe, "OVERSCAN")) {
+        options->doOverscan = true;
+
+        // Do the overscan as a single value?
+        bool overscanSingle = psMetadataLookupBool(NULL, recipe, "OVERSCAN.SINGLE");
+
+        // How do we fit it?
+        pmFit overscanFit = PM_FIT_NONE; // Fit type for overscan
+        int overscanOrder = 0;          // Order for overscan fit
+        psString fit = psMetadataLookupStr(NULL, recipe, "OVERSCAN.FIT");
+        if (! strcasecmp(fit, "POLYNOMIAL")) {
+            overscanFit = PM_FIT_POLY_ORD;
+            overscanOrder = psMetadataLookupS32(NULL, recipe, "OVERSCAN.ORDER");
+        } else if (! strcasecmp(fit, "CHEBYSHEV")) {
+            overscanFit = PM_FIT_POLY_CHEBY;
+            overscanOrder = psMetadataLookupS32(NULL, recipe, "OVERSCAN.ORDER");
+        } else if (! strcasecmp(fit, "SPLINE")) {
+            overscanFit = PM_FIT_SPLINE;
+        } else if (strcasecmp(fit, "NONE")) {
+            psLogMsg(__func__, PS_LOG_WARN,
+                     "OVERSCAN.FIT (%s) in recipe %s is not one of NONE, POLYNOMIAL, or SPLINE",
+                     fit, RECIPE_NAME);
+            exit(EXIT_FAILURE);
+        }
+
+        // What method do we use to measure the overscan statistics?
+        // XXX allow user to specify psStats types by name
+        psStats *overscanStats = NULL;  // Statistics for overscan
+        psString stat = psMetadataLookupStr(NULL, recipe, "OVERSCAN.STAT");
+        if (! strcasecmp(stat, "MEAN")) {
+            // overscanStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+            overscanStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+        } else if (! strcasecmp(stat, "MEDIAN")) {
+            overscanStats = psStatsAlloc(PS_STAT_SAMPLE_MEDIAN);
+        } else {
+            psErrorStackPrint(stderr, "OVERSCAN.STAT (%s) in recipe %s is not one of MEAN or MEDIAN",
+                              stat, RECIPE_NAME);
+            exit(EXIT_FAILURE);
+        }
+
+        int boxcar = psMetadataLookupS32(NULL, recipe, "OVERSCAN.BOXCAR");
+        float gauss = psMetadataLookupF32(NULL, recipe, "OVERSCAN.GAUSS");
+
+        // Fill in the options
+        options->overscan = pmOverscanOptionsAlloc(overscanSingle, overscanFit, overscanOrder,
+                                                   overscanStats, boxcar, gauss);
+
+        options->overscan->constant = psMetadataLookupBool(NULL, recipe, "OVERSCAN.CONSTANT");
+        options->overscan->value = psMetadataLookupF32(NULL, recipe, "OVERSCAN.VALUE");
+
+        psFree(overscanStats);
+    }
+
+    // for these images, even if not required otherwise
+    options->doMaskBuild = psMetadataLookupBool(NULL, recipe, "MASK.BUILD");
+    options->doWeightBuild = psMetadataLookupBool(NULL, recipe, "WEIGHT.BUILD");
+
+    // Mask recipe options (note that mask bit values are set in ppImageSetMaskBits.c)
+    options->doMask = psMetadataLookupBool(NULL, recipe, "MASK");
+
+    options->doBias = psMetadataLookupBool(NULL, recipe, "BIAS");
+    options->doDark = psMetadataLookupBool(NULL, recipe, "DARK");
+    options->doFlat = psMetadataLookupBool(NULL, recipe, "FLAT");
+    options->doFringe = psMetadataLookupBool(NULL, recipe, "FRINGE");
+    options->doShutter = psMetadataLookupBool(NULL, recipe, "SHUTTER");
+
+    options->doStats = false;
+    char *statsName = psMetadataLookupStr(&status, config->arguments, "STATS"); // Filename for statistics
+    if (statsName) {
+        options->doStats = true;
+    }
+
+    // binned image options
+    options->xBin1 = psMetadataLookupS32(&status, recipe, "BIN1.XBIN");
+    if (!status) {
+        psWarning("BIN1.XBIN not found in recipe: setting to default value.\n");
+    }
+    options->yBin1 = psMetadataLookupS32(&status, recipe, "BIN1.YBIN");
+    if (!status) {
+        psWarning("BIN1.YBIN not found in recipe: setting to default value.\n");
+        options->yBin1 = 16;
+    }
+
+    options->xBin2 = psMetadataLookupS32(&status, recipe, "BIN2.XBIN");
+    if (!status) {
+        psWarning("BIN2.XBIN not found in recipe: setting to default value.\n");
+       options->xBin1 = 16;
+    }
+    options->yBin2 = psMetadataLookupS32(&status, recipe, "BIN2.YBIN");
+    if (!status) {
+        psWarning("BIN2.YBIN not found in recipe: setting to default value.\n");
+        options->yBin1 = 16;
+    }
+
+    options->BaseFITS       = psMetadataLookupBool(NULL, recipe, "BASE.FITS");
+    options->BaseMaskFITS   = psMetadataLookupBool(NULL, recipe, "BASE.MASK.FITS");
+    options->BaseWeightFITS = psMetadataLookupBool(NULL, recipe, "BASE.WEIGHT.FITS");
+
+    options->ChipFITS       = psMetadataLookupBool(NULL, recipe, "CHIP.FITS");
+    options->ChipMaskFITS   = psMetadataLookupBool(NULL, recipe, "CHIP.MASK.FITS");
+    options->ChipWeightFITS = psMetadataLookupBool(NULL, recipe, "CHIP.WEIGHT.FITS");
+
+    options->FPA1FITS       = psMetadataLookupBool(NULL, recipe, "FPA1.FITS");
+    options->FPA2FITS       = psMetadataLookupBool(NULL, recipe, "FPA2.FITS");
+
+    options->Bin1FITS       = psMetadataLookupBool(NULL, recipe, "BIN1.FITS");
+    options->Bin1JPEG       = psMetadataLookupBool(NULL, recipe, "BIN1.JPEG");
+    options->Bin2FITS       = psMetadataLookupBool(NULL, recipe, "BIN2.FITS");
+    options->Bin2JPEG       = psMetadataLookupBool(NULL, recipe, "BIN2.JPEG");
+
+    options->doPhotom       = psMetadataLookupBool(NULL, recipe, "PHOTOM");
+    options->doAstromChip   = psMetadataLookupBool(NULL, recipe, "ASTROM.CHIP");
+    options->doAstromMosaic = psMetadataLookupBool(NULL, recipe, "ASTROM.MOSAIC");
+
+    // even if not requested explicitly, if any of these are set, build an internal mask and weight:
+    if (options->doBias || options->doOverscan || options->doDark || options->doShutter || options->doFlat ||
+        options->doPhotom) {
+        options->doMaskBuild = true;
+        options->doWeightBuild = true;
+    } else if (options->doMask) {
+        options->doMaskBuild = true;
+    }
+
+    if ((options->doAstromChip || options->doAstromMosaic) && !options->doPhotom) {
+        psLogMsg(__func__, PS_LOG_ERROR, "Invalid PPIMAGE options: cannot do ASTROMetry without PHOTOMetry");
+        exit(EXIT_FAILURE);
+    }
+
+    // Fringe options
+    options->fringeRej = psMetadataLookupF32(NULL, recipe, "FRINGE.REJ");
+    options->fringeIter = psMetadataLookupS32(NULL, recipe, "FRINGE.ITER");
+    options->fringeKeep = psMetadataLookupF32(NULL, recipe, "FRINGE.KEEP");
+
+    options->replaceMasked  = psMetadataLookupBool(NULL, recipe, "REPLACE.MASKED");
+
+    return options;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageParseCamera.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageParseCamera.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageParseCamera.c	(revision 22157)
@@ -0,0 +1,472 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include "ppImage.h"
+
+ppImageOptions *ppImageParseCamera(pmConfig *config)
+{
+    bool status = false;
+
+    // the input image defines the camera, and all recipes and options the follow
+    pmFPAfile *input = pmFPAfileDefineFromArgs (&status, config, "PPIMAGE.INPUT", "INPUT");
+    if (!status || !input) {
+        psError(PS_ERR_IO, false, "Failed to build FPA from PPIMAGE.INPUT");
+        return NULL;
+    }
+    if (input->type != PM_FPA_FILE_IMAGE) {
+        psError(PS_ERR_IO, true, "PPIMAGE.INPUT is not of type IMAGE");
+        return NULL;
+    }
+
+    // if MASK or WEIGHT was supplied on command line, bind files to 'input'.
+    // the mask and weight will be mosaicked with the image
+    pmFPAfile *inputMask = pmFPAfileBindFromArgs(&status, input, config, "PPIMAGE.INPUT.MASK", "PPIMAGE.INPUT.MASK");
+    if (!status) {
+        psError (PS_ERR_UNKNOWN, false, "failed to load find definition");
+        return NULL;
+    }
+    if (inputMask) {
+      if (inputMask->type != PM_FPA_FILE_MASK) {
+        psError(PS_ERR_IO, true, "PPIMAGE.INPUT.MASK is not of type MASK");
+        return NULL;
+      }
+    }
+
+    pmFPAfile *inputWeight = pmFPAfileBindFromArgs(&status, input, config, "PPIMAGE.INPUT.WEIGHT", "PPIMAGE.INPUT.WEIGHT");
+    if (!status) {
+        psError (PS_ERR_UNKNOWN, false, "failed to load find definition");
+        return NULL;
+    }
+    if (inputWeight && inputWeight->type != PM_FPA_FILE_WEIGHT) {
+        psError(PS_ERR_IO, true, "PPIMAGE.INPUT.WEIGHT is not of type WEIGHT");
+        return NULL;
+    }
+
+    // add recipe options supplied on command line
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, RECIPE_NAME);
+
+    // parse the options from the metadata format to the ppImageOptions structure
+    ppImageOptions *options = ppImageOptionsParse (config);
+
+    // the following are defined from the argument list, if given,
+    // otherwise they revert to the config information
+    // not all input or output images are used in a given recipe
+    if (options->doBias) {
+        if (!ppImageDefineFile (config, input->fpa, "PPIMAGE.BIAS", "BIAS", PM_FPA_FILE_IMAGE, PM_DETREND_TYPE_BIAS)) {
+            psError (PS_ERR_IO, false, "Can't find a bias image source");
+            psFree (options);
+            return NULL;
+        }
+    }
+    if (options->doDark) {
+        if (!ppImageDefineFile (config, input->fpa, "PPIMAGE.DARK", "DARK", PM_FPA_FILE_DARK, PM_DETREND_TYPE_DARK)) {
+            psError (PS_ERR_IO, false, "Can't find a dark image source");
+            psFree (options);
+            return NULL;
+        }
+    }
+    if (options->doMask) {
+
+        if (!ppImageDefineFile (config, input->fpa, "PPIMAGE.MASK", "MASK", PM_FPA_FILE_MASK, PM_DETREND_TYPE_MASK)) {
+            psError (PS_ERR_IO, false, "Can't find a mask image source");
+            psFree (options);
+            return NULL;
+        }
+	// XXX have ppImageDefineFile return the pmFPAfile?
+	pmFPAfile *mask = psMetadataLookupPtr(&status, config->files, "PPIMAGE.MASK");
+	psAssert (mask, "mask not defined?  not possible!");
+
+	// Need to read the names of bit masks from the mask header and set them in the
+	// recipe.  If we are loading this from the detrend db, this action will happen
+	// when the file is resolved.
+	if (!mask->detrend) {
+	    // XXX need to load the mask bit names from one of the headers
+	    // this grabs the first available hdu : no guarantee that it will be valid, though
+	    pmHDU *hdu = pmHDUGetFirst (mask->fpa);
+	    if (!hdu) {
+		psError(PS_ERR_IO, true, "no valid HDU for PPIMAGE.INPUT.MASK");
+		return NULL;
+	    }
+	    // XXX is this consistent with the pmConfigMaskReadHeader call above?
+	    if (!pmConfigMaskReadHeader (config, hdu->header)) {
+		psError(PS_ERR_IO, false, "error in mask bits");
+		return NULL;
+	    }
+	}
+    }
+    if (options->doShutter) {
+        if (!ppImageDefineFile (config, input->fpa, "PPIMAGE.SHUTTER", "SHUTTER", PM_FPA_FILE_IMAGE, PM_DETREND_TYPE_SHUTTER)) {
+            psError (PS_ERR_IO, false, "Can't find a shutter image source");
+            psFree (options);
+            return NULL;
+        }
+    }
+
+    if (options->doFlat) {
+        if (!ppImageDefineFile (config, input->fpa, "PPIMAGE.FLAT", "FLAT", PM_FPA_FILE_IMAGE, PM_DETREND_TYPE_FLAT)) {
+            psError (PS_ERR_IO, false, "Can't find a flat image source");
+            psFree (options);
+            return NULL;
+        }
+    }
+
+    // fringe frame are only applied for a subset of the filters.
+    // if the filter is one of those identified by a FRINGE.FILTERS metadata entry
+    // in ppImage.config, apply the fringe frame
+    if (options->doFringe) {
+        // determine filter from the concepts
+        const char *filter = psMetadataLookupStr(&status, input->fpa->concepts, "FPA.FILTER");
+        if (!status || !filter || strlen(filter) == 0) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find FPA.FILTER.\n");
+            psFree(options);
+            return NULL;
+        }
+
+        // select FRINGE.FILTERS from the recipe and test if the filter matches
+        // is the mdi saved with psList or string?
+        psMetadataItem *mdi = psMetadataLookup (recipe, "FRINGE.FILTERS");
+        if (mdi == NULL) {
+            // no valid filters for fringe data for this camera
+            options->doFringe = false;
+            goto skip_fringe;
+        }
+        // place entry on a list regardless of type
+        psList *filters = NULL;
+        if (mdi->type == PS_DATA_STRING) {
+            filters = psListAlloc(NULL);
+            psListAdd (filters, PS_LIST_HEAD, mdi);
+        } else if (mdi->type == PS_DATA_METADATA_MULTI) {
+            filters = psMemIncrRefCounter(mdi->data.list);
+        } else {
+            psError(PS_ERR_BAD_PARAMETER_TYPE, true,
+                    "FRINGE.FILTERS in recipe %s is not of type METADATA", RECIPE_NAME);
+            psFree(options);
+            return NULL;
+        }
+
+        // search through list to find the current filter
+        psListIterator *iter = psListIteratorAlloc (filters, PS_LIST_HEAD, FALSE);
+        options->doFringe = false;
+        for (int i = 0; !options->doFringe && (i < filters->n); i++) {
+            psMetadataItem *item = psListGetAndIncrement (iter);
+            char *validFilter = item->data.V;
+            if (strcmp (validFilter, filter)) continue;
+            options->doFringe = true;
+        }
+        psFree(iter);
+        psFree(filters);
+    }
+skip_fringe:
+    if (options->doFringe) {
+        if (!ppImageDefineFile (config, input->fpa, "PPIMAGE.FRINGE", "FRINGE", PM_FPA_FILE_FRINGE, PM_DETREND_TYPE_FRINGE)) {
+            psError (PS_ERR_IO, false, "Can't find a fringe image source");
+            return NULL;
+        }
+    }
+
+    // the following files are output targets
+    pmFPAfile *outImage = pmFPAfileDefineOutput(config, input->fpa, "PPIMAGE.OUTPUT");
+    if (!outImage) {
+        psError(PS_ERR_IO, false, _("Unable to generate output file from PPIMAGE.OUTPUT"));
+        psFree(options);
+        return NULL;
+    }
+    if (outImage->type != PM_FPA_FILE_IMAGE) {
+        psError(PS_ERR_IO, true, "PPIMAGE.OUTPUT is not of type IMAGE");
+        psFree(options);
+        return NULL;
+    }
+    pmFPAfile *outMask = pmFPAfileDefineOutput(config, input->fpa, "PPIMAGE.OUTPUT.MASK");
+    if (!outMask) {
+        psError(PS_ERR_IO, false, _("Unable to generate output file from PPIMAGE.OUTPUT.MASK"));
+        psFree(options);
+        return NULL;
+    }
+    if (outMask->type != PM_FPA_FILE_MASK) {
+        psError(PS_ERR_IO, true, "PPIMAGE.OUTPUT.MASK is not of type MASK");
+        psFree(options);
+        return NULL;
+    }
+    pmFPAfile *outWeight = pmFPAfileDefineOutput(config, input->fpa, "PPIMAGE.OUTPUT.WEIGHT");
+    if (!outWeight) {
+        psError(PS_ERR_IO, false, _("Unable to generate output file from PPIMAGE.OUTPUT.WEIGHT"));
+        psFree(options);
+        return NULL;
+    }
+    if (outWeight->type != PM_FPA_FILE_WEIGHT) {
+        psError(PS_ERR_IO, true, "PPIMAGE.OUTPUT.WEIGHT is not of type WEIGHT");
+        psFree(options);
+        return NULL;
+    }
+
+    // XXX should these be bound explicitly to the outImage->fpa rather than the input->fpa?
+    pmFPAfile *chipImage = pmFPAfileDefineChipMosaic(config, input->fpa, "PPIMAGE.CHIP");
+    if (!chipImage) {
+        psError(PS_ERR_IO, false, _("Unable to generate new file from PPIMAGE.CHIP"));
+        psFree(options);
+        return NULL;
+    }
+    if (chipImage->type != PM_FPA_FILE_IMAGE) {
+        psError(PS_ERR_IO, true, "PPIMAGE.CHIP is not of type IMAGE");
+        psFree(options);
+        return NULL;
+    }
+    pmFPAfile *chipMask = pmFPAfileDefineOutput(config, chipImage->fpa, "PPIMAGE.CHIP.MASK");
+    if (!chipMask) {
+        psError(PS_ERR_IO, false, _("Unable to generate output file from PPIMAGE.CHIP.MASK"));
+        psFree(options);
+        return NULL;
+    }
+    if (chipMask->type != PM_FPA_FILE_MASK) {
+        psError(PS_ERR_IO, true, "PPIMAGE.CHIP.MASK is not of type MASK");
+        psFree(options);
+        return NULL;
+    }
+    pmFPAfile *chipWeight = pmFPAfileDefineOutput(config, chipImage->fpa, "PPIMAGE.CHIP.WEIGHT");
+    if (!chipWeight) {
+        psError(PS_ERR_IO, false, _("Unable to generate output file from PPIMAGE.CHIP.WEIGHT"));
+        psFree(options);
+        return NULL;
+    }
+    if (chipWeight->type != PM_FPA_FILE_WEIGHT) {
+        psError(PS_ERR_IO, true, "PPIMAGE.CHIP.WEIGHT is not of type WEIGHT");
+        psFree(options);
+        return NULL;
+    }
+
+    pmFPAfile *byFPA1 = pmFPAfileDefineFPAMosaic(config, input->fpa, "PPIMAGE.OUTPUT.FPA1");
+    if (!byFPA1) {
+        psError(PS_ERR_IO, false, _("Unable to generate new file from PPIMAGE.OUTPUT.FPA1"));
+        psFree(options);
+        return NULL;
+    }
+    if (byFPA1->type != PM_FPA_FILE_IMAGE) {
+        psError(PS_ERR_IO, true, "PPIMAGE.OUTPUT.FPA1 is not of type IMAGE");
+        psFree(options);
+        return NULL;
+    }
+    pmFPAfile *byFPA2 = pmFPAfileDefineFPAMosaic(config, input->fpa, "PPIMAGE.OUTPUT.FPA2");
+    if (!byFPA2) {
+        psError(PS_ERR_IO, false, _("Unable to generate new file from PPIMAGE.OUTPUT.FPA2"));
+        psFree(options);
+        return NULL;
+    }
+    if (byFPA2->type != PM_FPA_FILE_IMAGE) {
+        psError(PS_ERR_IO, true, "PPIMAGE.OUTPUT.FPA2 is not of type IMAGE");
+        psFree(options);
+        return NULL;
+    }
+
+    // chipImage    -> psphotInput  (pmFPAfileDefineFromFile)       : fpa is constructed
+    // psphotInput  -> psphotOutput (pmFPAfileDefineOutputFromFile) : fpa is equated
+    // psphotOutput -> psastroInput (pmFPAfileDefineInput)          : fpa is ref-copied
+    // psastroInput -> psastroModel (pmFPAfileDefineFromArgs        : fpa is ref-copied
+    // psastroInput -> psastroModel (pmFPAfileDefineFromConf        : fpa is constructed
+    // psastroInput -> psastroModel (pmFPAfileDefineFromDetDB       : fpa is constructed (pmDetrendSelect uses input concepts )
+
+    // For photometry, we operate on the chip-mosaicked image
+    // we create a copy of the mosaicked image for psphot so we can write out a clean image
+    if (options->doPhotom) {
+
+        // this file is just used as a carrier; output files (eg, PSPHOT.RESID) are defined by
+        // psphotDefineFiles
+        pmFPAfile *psphotInput = pmFPAfileDefineFromFile (config, chipImage, 1, 1, "PSPHOT.INPUT");
+        PS_ASSERT (psphotInput, false);
+
+        // define associated psphot input/output files
+        if (!psphotDefineFiles (config, psphotInput)) {
+            psError(PSPHOT_ERR_CONFIG, false, "Trouble defining the additional input/output files for psphot");
+            return false;
+        }
+    }
+
+    // For photometry, we operate on the chip-mosaicked image
+    if (options->doAstromChip || options->doAstromMosaic) {
+        if (!options->doPhotom) {
+            psError (PSASTRO_ERR_CONFIG, false, "photometry mode is not selected; it is required for astrometry");
+            return false;
+        }
+
+        pmFPAfile *psphotOutput = psMetadataLookupPtr (&status, config->files, "PSPHOT.OUTPUT");
+        PS_ASSERT (psphotOutput, false);
+
+        pmFPAfile *psastroInput = pmFPAfileDefineInput (config, psphotOutput->fpa, "PSASTRO.INPUT");
+        PS_ASSERT (psastroInput, false);
+        psastroInput->mode = PM_FPA_MODE_REFERENCE;
+
+        // define associated psphot input/output files
+        if (!psastroDefineFiles (config, psastroInput)) {
+            psError(PSPHOT_ERR_CONFIG, false, "Trouble defining the additional input/output files for psastro");
+            return false;
+        }
+
+        // deactivate the psastro files, reactive when needed
+        pmFPAfileActivate (config->files, false, "PSASTRO.OUTPUT");
+    }
+
+    // save any of these files?
+    outImage->save   = options->BaseFITS;
+    outMask->save    = options->BaseMaskFITS;
+    outWeight->save  = options->BaseWeightFITS;
+
+    chipImage->save  = options->ChipFITS;
+    chipMask->save   = options->ChipMaskFITS;
+    chipWeight->save = options->ChipWeightFITS;
+
+    byFPA1->save     = options->FPA1FITS;
+    byFPA2->save     = options->FPA2FITS;
+
+    // outImage is used as a carrier: input to chipImage -> require the data to remain at the CHIP level
+    outImage->freeLevel = PS_MIN (outImage->freeLevel, PM_FPA_LEVEL_CHIP);
+    outImage->dataLevel = outImage->freeLevel;
+    outImage->fileLevel = PS_MIN (outImage->fileLevel, outImage->dataLevel);
+
+    // outMask and outWeight must be freed at the same level as outImage (all freed by pmFPAFreeData)
+    outMask->freeLevel   = outImage->freeLevel;
+    outWeight->freeLevel = outImage->freeLevel;
+    outMask->dataLevel   = outImage->dataLevel;
+    outWeight->dataLevel = outImage->dataLevel;
+
+    // Ditto for the chip-mosaicked version
+    chipMask->freeLevel   = chipImage->freeLevel;
+    chipWeight->freeLevel = chipImage->freeLevel;
+    chipMask->dataLevel   = chipImage->dataLevel;
+    chipWeight->dataLevel = chipImage->dataLevel;
+
+    // the input data is the same as the outImage data : force the free levels to match
+    input->freeLevel = PS_MIN (outImage->freeLevel, input->freeLevel);
+
+    // define the binned target files (which may just be carriers for some camera configurations)
+    pmFPAfile *bin1 = pmFPAfileDefineFromFPA (config, chipImage->fpa, options->xBin1, options->yBin1, "PPIMAGE.BIN1");
+    if (!bin1) {
+        psError(PS_ERR_IO, false, _("Unable to generate new file from PPIMAGE.BIN1"));
+        psFree(options);
+        return NULL;
+    }
+    if (bin1->type != PM_FPA_FILE_IMAGE) {
+        psError(PS_ERR_IO, true, "PPIMAGE.OUTPUT.BIN1 is not of type IMAGE");
+        psFree(options);
+        return NULL;
+    }
+
+    pmFPAfile *bin2 = pmFPAfileDefineFromFPA (config, chipImage->fpa, options->xBin2, options->yBin2, "PPIMAGE.BIN2");
+    if (!bin2) {
+        psError(PS_ERR_IO, false, _("Unable to generate new file from PPIMAGE.BIN2"));
+        psFree(options);
+        return NULL;
+    }
+    if (bin2->type != PM_FPA_FILE_IMAGE) {
+        psError(PS_ERR_IO, true, "PPIMAGE.OUTPUT.BIN2 is not of type IMAGE");
+        psFree(options);
+        return NULL;
+    }
+
+    // bin1 and bin2 are used as carriers: input for byFPA1, byFPA2
+    bin1->freeLevel = PM_FPA_LEVEL_FPA;
+    bin2->freeLevel = PM_FPA_LEVEL_FPA;
+
+    pmFPAfile *jpg1 = pmFPAfileDefineOutput (config, byFPA1->fpa, "PPIMAGE.JPEG1");
+    if (!jpg1) {
+        psError(PS_ERR_IO, false, _("Unable to generate new file from PPIMAGE.JPEG1"));
+        psFree(options);
+        return NULL;
+    }
+    if (jpg1->type != PM_FPA_FILE_JPEG) {
+        psError(PS_ERR_IO, true, "PPIMAGE.OUTPUT.JPEG1 is not of type JPEG");
+        psFree(options);
+        return NULL;
+    }
+    pmFPAfile *jpg2 = pmFPAfileDefineOutput (config, byFPA2->fpa, "PPIMAGE.JPEG2");
+    if (!jpg2) {
+        psError(PS_ERR_IO, false, _("Unable to generate new file from PPIMAGE.JPEG2"));
+        psFree(options);
+        return NULL;
+    }
+    if (jpg2->type != PM_FPA_FILE_JPEG) {
+        psError(PS_ERR_IO, true, "PPIMAGE.OUTPUT.JPEG2 is not of type JPEG");
+        psFree(options);
+        return NULL;
+    }
+
+    // XXX we could potentially not define these pmFPAfiles if no output is requested...
+    bin1->save = options->Bin1FITS;
+    bin2->save = options->Bin2FITS;
+    jpg1->save = options->Bin1JPEG;
+    jpg2->save = options->Bin2JPEG;
+
+    // Chip selection: turn on only the chips specified (pass status to suppress missing-key log msg)
+    char *chipLine = psMetadataLookupStr(&status, config->arguments, "CHIP_SELECTIONS");
+    psArray *chips = psStringSplitArray (chipLine, ",", false);
+    if (chips->n > 0) {
+        pmFPASelectChip (input->fpa, -1, true); // deselect all chips
+        for (int i = 0; i < chips->n; i++) {
+            int chipNum = atoi(chips->data[i]);
+            if (! pmFPASelectChip(input->fpa, chipNum, false)) {
+                psError(PS_ERR_IO, false, "Chip number %d doesn't exist in camera.\n", chipNum);
+                psFree(options);
+                return false;
+            }
+        }
+    }
+    psFree (chips);
+
+    if (psMetadataLookupBool(NULL, config->arguments, "INPUT_IS_FRINGE")) {
+        // It's a fringe file, so change the file type
+        input->type = PM_FPA_FILE_FRINGE;
+        outImage->type = PM_FPA_FILE_FRINGE;
+    }
+    if (psMetadataLookupBool(NULL, config->arguments, "INPUT_IS_DARK")) {
+        // It's a dark file, so change the file type
+        input->type = PM_FPA_FILE_DARK;
+        outImage->type = PM_FPA_FILE_DARK;
+        // Turn off compression --- there are just too many nasties that can happen
+        psFree(outImage->compression);
+        outImage->compression = NULL;
+        psFree(outImage->options);
+        outImage->options = NULL;
+    }
+
+    // Turn off mask and weight output if we're not doing anything interesting
+    if (!options->doMaskBuild && outMask->save) {
+        psWarning("output mask image (BASE.MASK.FITS) requested, but not generated: skipping.\n");
+        outMask->save = false;
+    }
+    if (!options->doWeightBuild && outWeight->save) {
+        psWarning("output weight image (BASE.WEIGHT.FITS) requested, but not generated: skipping.\n");
+        outWeight->save = false;
+    }
+    if (!options->doMaskBuild && chipMask->save) {
+        psWarning("output mask image (CHIP.MASK.FITS) requested, but not generated: skipping.\n");
+        chipMask->save = false;
+    }
+    if (!options->doWeightBuild && chipWeight->save) {
+        psWarning("output weight image (CHIP.WEIGHT.FITS) requested, but not generated: skipping.\n");
+        chipWeight->save = false;
+    }
+
+    if (psTraceGetLevel("ppImage.config") > 0) {
+        // Get a look inside all the files.
+        psMetadataIterator *filesIter = psMetadataIteratorAlloc(config->files, PS_LIST_HEAD, NULL); // Iterator
+        psMetadataItem *item;               // Item from iteration
+        fprintf(stderr, "Files:\n");
+        while ((item = psMetadataGetAndIncrement(filesIter))) {
+            pmFPAfile *file = item->data.V; // File of interest
+            fprintf(stderr, "%s: %p %p %p (%p) %p\n", file->name,
+                    file->src, file->fpa,
+                    file->camera, file->fpa->camera, file->format);
+        }
+        psFree(filesIter);
+    }
+
+    // Change the input dark type between the old (IMAGE) and new (multi-DARK).
+    // Hopefully this is a temporary change until we all move over to using the new dark types
+    bool mdok;                          // Status of MD lookup
+    if (options->doDark && psMetadataLookupBool(&mdok, recipe, "OLDDARK")) {
+        pmFPAfile *dark = psMetadataLookupPtr(NULL, config->files, "PPIMAGE.DARK");
+        assert(dark);
+        dark->type = PM_FPA_FILE_IMAGE;
+    }
+
+    return (options);
+}
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImagePhotom.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImagePhotom.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImagePhotom.c	(revision 22157)
@@ -0,0 +1,54 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include "ppImage.h"
+
+// In this function, we perform the psphot analysis routine for the chip-mosaicked images
+bool ppImagePhotom (pmConfig *config, pmFPAview *view) {
+
+    bool status;
+    pmCell *cell;
+    pmReadout *readout;
+
+    psphotModelClassInit ();
+
+    // find or define a pmFPAfile PSPHOT.INPUT
+    pmFPAfile *input = psMetadataLookupPtr (&status, config->files, "PSPHOT.INPUT");
+    if (!status) {
+        psError(PSPHOT_ERR_CONFIG, false, "PSPHOT.INPUT I/O file is not defined");
+        return false;
+    }
+
+    // we make a new copy of the output chip to keep psphot from modifying the output image
+    pmChip *oldChip = pmFPAviewThisChip (view, input->src);
+    pmChip *newChip = pmFPAviewThisChip (view, input->fpa);
+    pmChipCopy (newChip, oldChip);
+
+    // iterate over the cells and readout for this chip
+    while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+        psLogMsg ("ppImagePhotom", 5, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+        if (! cell->process || ! cell->file_exists) { continue; }
+
+        // process each of the readouts
+        while ((readout = pmFPAviewNextReadout (view, input->fpa, 1)) != NULL) {
+            if (! readout->data_exists) { continue; }
+
+            // run the actual photometry analysis
+            if (!psphotReadout (config, view)) {
+                psError(psErrorCodeLast(), false, "failure in psphotReadout for chip %d, cell %d, readout %d\n", view->chip, view->cell, view->readout);
+                return false;
+            }
+        }
+    }
+
+    ppImageMemoryDump("photom");
+
+    // the PSPHOT.INPUT file is a temporary file used to carry PPIMAGE.CHIP to psphotReadout
+    // XXX not sure that this is needed...
+//    pmFPAfileActivate (config->files, false, "PSPHOT.INPUT");
+
+    return true;
+}
+
+// XXX do we need to deactivate all files and activate the psphot ones explicitly?
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImagePixelStats.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImagePixelStats.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImagePixelStats.c	(revision 22157)
@@ -0,0 +1,104 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+// calculate stats, including MD5
+bool ppImagePixelStats (pmConfig *config, const ppImageOptions *options, const pmFPAview *inputView) {
+
+    bool mdok;              // Status of MD lookup
+
+    // loop over the cells/readouts for this chip
+    pmFPAview *view = pmFPAviewAlloc(0); // View for local processing
+    *view = *inputView;
+    
+    // perform the analysis of for this FPA
+    pmFPAfile *output = psMetadataLookupPtr(&mdok, config->files, "PPIMAGE.OUTPUT");
+    if (!output) {
+	psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find output file (PPIMAGE.OUTPUT).");
+	psFree (view);
+	return false;
+    }
+
+    // select the corresponding chip
+    pmChip *chip = pmFPAviewThisChip (view, output->fpa);
+    if (!chip) {
+	psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find requested chip.");
+	psFree (view);
+	return false;
+    }
+    
+    // Perform statistics for this chip
+    if (options->doStats) {
+
+	// select or create the fpa-level analysis stats metadata (save on PPIMAGE.OUPUT:PPIMAGE.STATS)
+	psMetadata *stats = psMetadataLookupPtr (&mdok, output->fpa->analysis, "PPIMAGE.STATS");
+	psMemIncrRefCounter (stats);
+	if (!stats) {
+	    stats = psMetadataAlloc ();
+	    if (!psMetadataAdd (output->fpa->analysis, PS_LIST_TAIL, "PPIMAGE.STATS", PS_DATA_METADATA, "stats container", stats)) {
+		psError(PS_ERR_UNKNOWN, false, "Unable to push stats on fpa.analysis.");
+		psFree (view);
+		psFree (stats);
+		return false;
+	    }
+	}
+
+	if (!ppStatsPixels(stats, output->fpa, view, options->maskValue, config)) {
+	    psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to generate stats for image.");
+	    psFree (view);
+	    psFree (stats);
+	    return false;
+	}
+
+	// extract the fringe amplitude from the analysis
+	if (options->doFringe && !ppStatsFringe(stats, chip, "FRINGE", "FRINGE.SOLUTION")) {
+	    psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to extract fringe solution for image.");
+	    psFree (view);
+	    psFree (stats);
+	    return false;
+	}
+
+	// extract the fringe residual amplitude from the analysis
+	if (options->doFringe && !ppStatsFringe(stats, chip, "FRINGE_RESID", "FRINGE.RESIDUAL.SOLUTION")) {
+	    psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to extract fringe solution for image.");
+	    psFree (view);
+	    psFree (stats);
+	    return false;
+	}
+	
+	psFree (stats);
+    }
+
+    pmCell *cell = NULL;
+    pmReadout *readout = NULL;
+
+    view->cell = view->readout = -1;
+    while ((cell = pmFPAviewNextCell(view, output->fpa, 1)) != NULL) {
+	if (!cell->process || !cell->file_exists) {
+	    continue;
+	}
+
+	// Add MD5 information for cell
+	pmHDU *hdu = pmHDUFromCell(cell); // HDU that owns the cell
+	while ((readout = pmFPAviewNextReadout(view, output->fpa, 1)) != NULL) {
+	    const char *chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME");
+	    const char *cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME");
+
+	    psString headerName = NULL; // Header name for MD5
+	    psStringAppend(&headerName, "MD5_%s_%s_%d", chipName, cellName, view->readout);
+
+	    psVector *md5 = psImageMD5(readout->image); // md5 hash
+	    psString md5string = psMD5toString(md5); // String
+	    psFree (md5);
+	    psMetadataAddStr(hdu->header, PS_LIST_TAIL, headerName, PS_META_REPLACE,
+			     "Image MD5", md5string);
+	    psFree (md5string);
+	    psFree (headerName);
+	}
+    }
+
+    psFree (view);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageRebinReadout.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageRebinReadout.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageRebinReadout.c	(revision 22157)
@@ -0,0 +1,99 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+bool ppImageRebinChip (pmConfig *config, pmFPAview *view, char *outName) {
+
+    pmCell *cell;
+    pmReadout *inReadout, *outReadout;
+
+    pmFPAfile *outFile = psMetadataLookupPtr (NULL, config->files, outName);
+    if (outFile == NULL) return false;
+
+    // XXX double check that chip != -1?
+
+    pmChip *inChip  = pmFPAviewThisChip (view, outFile->src);
+    pmChip *outChip = pmFPAviewThisChip (view, outFile->fpa);
+    if (!pmChipCopyStructure (outChip, inChip, outFile->xBin, outFile->yBin)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to copy chip structure.");
+        return false;
+    }
+
+    while ((cell = pmFPAviewNextCell (view, outFile->src, 1)) != NULL) {
+        psLogMsg ("ppImageRebinChip", 5, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+        if (! cell->process || ! cell->file_exists) { continue; }
+
+        // process each of the readouts
+        while ((inReadout = pmFPAviewNextReadout (view, outFile->src, 1)) != NULL) {
+            if (! inReadout->data_exists) { continue; }
+
+            outReadout = pmFPAviewThisReadout (view, outFile->fpa);
+
+            // run the rebin code
+            ppImageRebinReadout (outReadout, inReadout, outFile);
+        }
+
+        // Get the original values for the binning
+        bool mdok = true;               // Status of MD lookup
+        int xBin = psMetadataLookupS32(&mdok, cell->concepts, "CELL.XBIN"); // The binning in x
+        if (!mdok || xBin <= 0) {
+            psLogMsg(__func__, PS_LOG_WARN, "CELL.XBIN is not set --- assuming 1.\n");
+            xBin = 1;
+        }
+        int yBin = psMetadataLookupS32(&mdok, cell->concepts, "CELL.YBIN"); // The binning in y
+        if (!mdok || yBin <= 0) {
+            psLogMsg(__func__, PS_LOG_WARN, "CELL.YBIN is not set --- assuming 1.\n");
+            yBin = 1;
+        }
+
+        // Update the concepts with the new values for the binning
+        pmCell *outCell = pmFPAviewThisCell(view, outFile->fpa); // The output cell
+        psMetadataItem *binItem = psMetadataLookup(outCell->concepts, "CELL.XBIN");
+        binItem->data.S32 = xBin * outFile->xBin;
+        binItem = psMetadataLookup(outCell->concepts, "CELL.YBIN");
+        binItem->data.S32 = yBin * outFile->yBin;
+    }
+
+    return true;
+}
+
+// XXX this should be made consistent with psImageBinning
+bool ppImageRebinReadout (pmReadout *output, pmReadout *input, pmFPAfile *outFile)
+{
+    PS_ASSERT_PTR_NON_NULL(output, false);
+    PS_ASSERT_IMAGE_NON_NULL(output->image, false);
+    PS_ASSERT_PTR_NON_NULL(input, false);
+    PS_ASSERT_IMAGE_NON_NULL(input->image, false);
+    PS_ASSERT_PTR_NON_NULL(outFile, false);
+
+    // the binning process must not change the size of the output image...
+    // psStats *stats = psStatsAlloc (PS_STATS_SAMPLE_MEAN);
+    // psImageRebin (output->image, input->image, NULL, 0, scale, stats);
+    // psFree (stats);
+
+    int dX = outFile->xBin;
+    int dY = outFile->yBin;
+
+    int nX = input->image->numCols;
+    int nY = input->image->numRows;
+
+    // do the rebinning by hand, mean only for test
+    for (int yOut = 0; yOut < output->image->numRows; yOut++) {
+        for (int xOut = 0; xOut < output->image->numCols; xOut++) {
+            float value = 0;
+            int nPix = 0;
+            for (int yIn = yOut * dY; (yIn < yOut * dY + dY) && (yIn < nY); yIn ++) {
+                for (int xIn = xOut * dX; (xIn < xOut * dX + dX) && (xIn < nX); xIn ++) {
+		    if (input->mask && input->mask->data.U8[yIn][xIn]) continue;
+                    value += input->image->data.F32[yIn][xIn];
+                    nPix ++;
+                }
+            }
+            output->image->data.F32[yOut][xOut] = value / nPix;
+        }
+    }
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageReplaceBackground.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageReplaceBackground.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageReplaceBackground.c	(revision 22157)
@@ -0,0 +1,111 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include "ppImage.h"
+
+enum {MODE_NONE, MODE_MODEL, MODE_VALUE};
+
+// In this function, we perform the psphot analysis routine for the chip-mosaicked images
+bool ppImageReplaceBackground (pmConfig *config, pmFPAview *view, ppImageOptions *options) {
+
+    bool status;
+    pmCell *cell;
+    pmReadout *readout;
+
+    // find the reference source image
+    pmFPAfile *input = psMetadataLookupPtr (&status, config->files, "PPIMAGE.CHIP");
+    if (!status) {
+        psError(PSPHOT_ERR_CONFIG, false, "PSPHOT.INPUT I/O file is not defined");
+        return false;
+    }
+
+    // select the appropriate recipe information
+    psMetadata *ppImageRecipe  = psMetadataLookupPtr (&status, config->recipes, "PPIMAGE");
+
+    // select the appropriate recipe information (needed by psphotModelBackground)
+    psMetadata *psphotRecipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+
+    char *replaceSkyModeWord = psMetadataLookupStr (&status, ppImageRecipe, "REPLACE.MODE");
+    float replaceSkyValue = 0.0;
+
+    int replaceSkyMode = MODE_NONE;
+    if (!strcasecmp (replaceSkyModeWord, "MODEL")) {
+	replaceSkyMode = MODE_MODEL;
+    }
+    if (!strcasecmp (replaceSkyModeWord, "VALUE")) {
+	replaceSkyMode = MODE_VALUE;
+	replaceSkyValue = psMetadataLookupF32 (&status, ppImageRecipe, "REPLACE.VALUE");
+    }
+    psAssert (replaceSkyMode != MODE_NONE, "replace sky mode not defined");
+
+    // XXX Should this be options->maskValue or & ~options->satMask? the latter will leave saturated pixels high
+    // psMaskType maskVal  = options->maskValue & ~options->satMask;
+    psMaskType maskVal  = options->maskValue;
+
+    pmFPAfile *modelFile = NULL;
+    if (replaceSkyMode == MODE_MODEL) {
+	// find the model data, if it exists yet (if not, we build it below)
+	modelFile = psMetadataLookupPtr(&status, config->files, "PSPHOT.BACKMDL");
+
+	// user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+	psMetadataAddU8 (psphotRecipe, PS_LIST_TAIL, "MASK.PSPHOT", PS_META_REPLACE, "user-defined mask", maskVal);
+    }
+
+    // iterate over the cells and readout for this chip
+    while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+	psLogMsg ("ppImagePhotom", 5, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+	if (! cell->process || ! cell->file_exists) { continue; }
+
+	// process each of the readouts
+	while ((readout = pmFPAviewNextReadout (view, input->fpa, 1)) != NULL) {
+	    if (! readout->data_exists) { continue; }
+	    if (! readout->mask) { continue; }
+
+	    // replace masked pixels with values from model (unbinning not needed)
+	    pmReadout *modelReadout = NULL;
+	    psImageBinning *binning = NULL;
+	    psImage *model = NULL;
+	    if (replaceSkyMode == MODE_MODEL) {
+		// we are using this pmFPAfile as an I/O file: select readout or create
+		if (modelFile) {
+		    modelReadout = pmFPAviewThisReadout (view, modelFile->fpa);
+		}
+		if (!modelReadout) {
+		    psphotModelBackground (config, view, "PPIMAGE.CHIP");
+		    modelFile = psMetadataLookupPtr(&status, config->files, "PSPHOT.BACKMDL");
+		    assert (modelFile);
+		    if (modelFile->mode == PM_FPA_MODE_INTERNAL) {
+			modelReadout = modelFile->readout;
+		    } else {
+			modelReadout = pmFPAviewThisReadout (view, modelFile->fpa);
+		    }
+		    assert (modelReadout);
+		}
+		binning = psMetadataLookupPtr(&status, psphotRecipe, "PSPHOT.BACKGROUND.BINNING");
+		model = modelReadout->image;
+	    }
+
+	    psImage *mask = readout->mask;
+	    psImage *image = readout->image;
+
+	    for (int iy = 0; iy < image->numRows; iy++) {
+		for (int ix = 0; ix < image->numCols; ix++) {
+		    if (!(mask->data.U8[iy][ix] && maskVal)) continue;
+		    if (replaceSkyMode == MODE_MODEL) {
+			image->data.F32[iy][ix] = psImageUnbinPixel_V2(ix, iy, model, binning);
+		    } else {
+			image->data.F32[iy][ix] = replaceSkyValue;
+		    }
+		}
+	    }
+	}
+    }
+
+    if (replaceSkyMode == MODE_MODEL) {
+	pmFPAfileDropInternal (config->files, "PSPHOT.BACKMDL");
+	pmFPAfileDropInternal (config->files, "PSPHOT.BACKMDL.STDEV");
+    }
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageSetMaskBits.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageSetMaskBits.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageSetMaskBits.c	(revision 22157)
@@ -0,0 +1,44 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+bool ppImageSetMaskBits (pmConfig *config, ppImageOptions *options) {
+
+    if (!pmConfigMaskSetBits (&options->maskValue, &options->markValue, config)) {
+	psError (PS_ERR_UNKNOWN, true, "Unable to define the mask bit values");
+	return false;
+    }
+
+    // at this point we know we have valid values for required entries SAT, BAD, FLAT, BLANK:
+
+    // mask for non-linear flat corrections
+    options->flatMask = pmConfigMaskGet("FLAT", config); 
+    psAssert (options->flatMask, "flat mask not set");
+
+    // mask for non-existent data  (default to DETECTOR if not defined)
+    options->blankMask = pmConfigMaskGet("BLANK", config); 
+    psAssert (options->blankMask, "blank mask not set");
+
+    // mask for saturated data  (default to RANGE if not defined)
+    options->satMask = pmConfigMaskGet("SAT", config);
+    psAssert (options->satMask, "sat mask not set");
+    
+    // mask for below-range data  (default to RANGE if not defined)
+    options->badMask = pmConfigMaskGet("BAD", config);
+    psAssert (options->badMask, "bad mask not set");
+
+    // save MASK and MARK on the PSPHOT recipe
+    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSPHOT_RECIPE);
+    if (!recipe) {
+        psError(PSPHOT_ERR_CONFIG, false, "missing recipe %s", PSPHOT_RECIPE);
+        return false;
+    }
+
+    // set maskValue and markValue in the psphot recipe
+    psMetadataAddU8 (recipe, PS_LIST_TAIL, "MASK.PSPHOT", PS_META_REPLACE, "user-defined mask", options->maskValue);
+    psMetadataAddU8 (recipe, PS_LIST_TAIL, "MARK.PSPHOT", PS_META_REPLACE, "user-defined mask", options->markValue);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageStats.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageStats.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageStats.c	(revision 22157)
@@ -0,0 +1,104 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+// calculate stats, including MD5
+bool ppImageStats (pmConfig *config, pmChip *chip, const pmFPAview *inputView, const ppImageOptions *options) {
+
+    bool mdok;              // Status of MD lookup
+    pmCell *cell = NULL;
+    pmReadout *readout = NULL;
+
+    pmFPAview *view = pmFPAviewAlloc(0); // View for local processing
+    *view = *inputView;
+    
+    // select or create the fpa-level analysis stats metadata:
+    psMetadata *stats = psMetadataLookupPtr (&mdok, chip->parent->analysis, "PPIMAGE.STATS");
+    psMemIncrRefCounter (stats);
+    if (!stats) {
+	stats = psMetadataAlloc ();
+	if (!psMetadataAdd (chip->parent->analysis, PS_LIST_TAIL, "PPIMAGE.STATS", PS_DATA_METADATA, "stats container", stats)) {
+	    psError(PS_ERR_UNKNOWN, false, "Unable to push stats on fpa.analysis.");
+	    psFree (view);
+	    psFree (stats);
+	    return false;
+	}
+    }
+
+    view->cell = view->readout = -1;
+    while ((cell = pmFPAviewNextCell(view, chip->parent, 1)) != NULL) {
+	if (!cell->process || !cell->file_exists) {
+	    continue;
+	}
+
+	// Perform statistics on the detrended cell
+	if (options->doStats) {
+
+	    pmFPAfile *outImage = psMetadataLookupPtr(&mdok, config->files, "PPIMAGE.OUTPUT");
+	    pmFPAfile *outPhotom = psMetadataLookupPtr(&mdok, config->files, "PSPHOT.OUTPUT");
+	    pmFPAfile *outAstrom = psMetadataLookupPtr(&mdok, config->files, "PSASTRO.OUTPUT");
+
+	    if (!outImage && !outPhotom && !outAstrom) {
+		psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find any output file (PPIMAGE.OUTPUT, PSPHOT.OUTPUT, PSASTRO.OUTPUT).");
+		psFree (view);
+		psFree(stats);
+		return false;
+	    }
+
+	    // get the latest output product available
+	    pmFPAfile *output = outAstrom;
+	    if (!output) {
+		output = outPhotom;
+	    }
+	    if (!output) {
+		output = outImage;
+	    }
+
+	    if (!ppStatsFPA(stats, output->fpa, view, options->maskValue, config)) {
+		psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to generate stats for image.");
+		psFree(view);
+		psFree(stats);
+		return false;
+	    }
+
+	    // extract the fringe amplitude from the analysis
+	    if (options->doFringe && !ppStatsFringe(stats, chip, "FRINGE", "FRINGE.SOLUTION")) {
+		psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to extract fringe solution for image.");
+		psFree(view);
+		psFree(stats);
+		return false;
+	    }
+
+	    // extract the fringe residual amplitude from the analysis
+	    if (options->doFringe && !ppStatsFringe(stats, chip, "FRINGE_RESID", "FRINGE.RESIDUAL.SOLUTION")) {
+		psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to extract fringe solution for image.");
+		psFree(view);
+		psFree(stats);
+		return false;
+	    }
+	}
+
+	// Add MD5 information for cell
+	pmHDU *hdu = pmHDUFromCell(cell); // HDU that owns the cell
+	while ((readout = pmFPAviewNextReadout(view, chip->parent, 1)) != NULL) {
+	    const char *chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME");
+	    const char *cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME");
+
+	    psString headerName = NULL; // Header name for MD5
+	    psStringAppend(&headerName, "MD5_%s_%s_%d", chipName, cellName, view->readout);
+
+	    psVector *md5 = psImageMD5(readout->image); // md5 hash
+	    psString md5string = psMD5toString(md5); // String
+	    psFree(md5);
+	    psMetadataAddStr(hdu->header, PS_LIST_TAIL, headerName, PS_META_REPLACE,
+			     "Image MD5", md5string);
+	    psFree(md5string);
+	    psFree(headerName);
+	}
+    }
+    psFree(view);
+    psFree(stats);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageStatsOutput.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageStatsOutput.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageStatsOutput.c	(revision 22157)
@@ -0,0 +1,62 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+// write stats to output file
+bool ppImageStatsOutput (pmConfig *config, const ppImageOptions *options) {
+
+    bool mdok;
+
+    // measure statistics, or ignore?
+    if (!options->doStats) return true;
+
+    // PPIMAGE.STATS is stored on PPIMAGE.OUTPUT
+    pmFPAfile *output = psMetadataLookupPtr(&mdok, config->files, "PPIMAGE.OUTPUT");
+    if (!output) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find PPIMAGE.OUTPUT entry in config file.\n");
+        return false;
+    }
+
+    // select the fpa-level analysis stats metadata:
+    psMetadata *stats = psMetadataLookupPtr (&mdok, output->fpa->analysis, "PPIMAGE.STATS");
+    if (!stats) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find PPIMAGE.STATS entry in output fpa analysis.\n");
+        return false;
+    }
+
+    // convert the stats MDC to a string
+    char *statsMDC = psMetadataConfigFormat(stats);
+    if (!statsMDC || strlen(statsMDC) == 0) {
+        psError(PS_ERR_IO, false, "Unable to serialize stats metadata.\n");
+        return false;
+    }
+
+    // get the output stats filename
+    const char *statsName = psMetadataLookupStr(&mdok, config->arguments, "STATS"); // Filename for statistics
+    if (!statsName && !strlen(statsName)) {
+        psError (PS_ERR_UNEXPECTED_NULL, false, "missing STATS entry in arguments list.");
+        psFree(statsMDC);
+        return false;
+    }
+
+    // convert to a real UNIX filename
+    psString resolved = pmConfigConvertFilename(statsName, config, true, true); // Resolved filename
+    FILE *statsFile = fopen (resolved, "w");
+    if (!statsFile) {
+        psError(PS_ERR_IO, true, "Unable to open statistics file %s for writing.\n", resolved);
+        psFree(statsMDC);
+        psFree(resolved);
+        return false;
+    }
+    psFree(resolved);
+
+    // write the stats MDC to a file
+    // XXX why does this not call psMetadataConfigPrint?
+    fprintf(statsFile, "%s", statsMDC);
+
+    psFree(statsMDC);
+    fclose(statsFile);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageVersion.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageVersion.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppImageVersion.c	(revision 22157)
@@ -0,0 +1,61 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+static const char *cvsTag = "$Name: not supported by cvs2svn $";// CVS tag name
+
+psString ppImageVersion(void)
+{
+    psString version = NULL;            // Version, to return
+    psStringAppend(&version, "%s-%s",PACKAGE_NAME,PACKAGE_VERSION);
+    return version;
+}
+
+psString ppImageVersionLong(void)
+{
+    psString version = ppImageVersion(); // Version, to return
+    psString tag = psStringStripCVS(cvsTag, "Name"); // CVS tag
+    psStringAppend(&version, " (cvs tag %s) %s, %s", tag, __DATE__, __TIME__);
+    psFree(tag);
+    return version;
+}
+
+
+void ppImageVersionMetadata(psMetadata *metadata)
+{
+    PS_ASSERT_METADATA_NON_NULL(metadata,);
+
+    psString pslib = psLibVersionLong();// psLib version
+    psString psmodules = psModulesVersionLong(); // psModules version
+    psString psphot = psphotVersionLong(); // psphot version
+    psString psastro = psastroVersionLong(); // psastro version
+    psString ppStats = ppStatsVersionLong(); // ppStats version
+    psString ppImage = ppImageVersionLong(); // ppImage version
+
+    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now
+    psString timeString = psTimeToISO(time); // The time in an ISO string
+    psFree(time);
+    psString head = NULL;               // Head string
+    psStringAppend(&head, "ppImage processing at %s. Component information:", timeString);
+    psFree(timeString);
+
+    psMetadataAddStr(metadata, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, head, "");
+    psMetadataAddStr(metadata, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, pslib, "");
+    psMetadataAddStr(metadata, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, psmodules, "");
+    psMetadataAddStr(metadata, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, psphot, "");
+    psMetadataAddStr(metadata, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, psastro, "");
+    psMetadataAddStr(metadata, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, ppStats, "");
+    psMetadataAddStr(metadata, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, ppImage, "");
+
+    psFree(head);
+    psFree(pslib);
+    psFree(psmodules);
+    psFree(psphot);
+    psFree(psastro);
+    psFree(ppStats);
+    psFree(ppImage);
+
+    return;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppImage/src/ppTest.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/src/ppTest.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/src/ppTest.c	(revision 22157)
@@ -0,0 +1,190 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include "pslib.h"
+#include "psmodules.h"
+
+int main(int argc, char *argv[])
+{
+    psLibInit(NULL);
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// ppImageConfig.c
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    pmConfig *config = pmConfigRead(&argc, argv, "PPIMAGE");
+    if (! config) {
+        psErrorStackPrint(stderr, "Can't find site configuration!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    // Parse other command-line arguments
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-key", 0, "exposure ID", "");
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-bias", 0, "Name of the bias image", "");
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-dark", 0, "Name of the dark image", "");
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-flat", 0, "Name of the flat-field image", "");
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-mask", 0, "Name of the mask image", "");
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-fringe", 0, "Name of the fringe image", "");
+    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "-chip", 0, "Chip number to process (if positive)", -1);
+
+    if (! psArgumentParse(config->arguments, &argc, argv) || argc != 3) {
+        printf("\nPan-STARRS Phase 2 processing\n\n");
+        printf("Usage: %s INPUT.fits OUTPUT.fits\n\n", argv[0]);
+        psArgumentHelp(config->arguments);
+        psFree(config->arguments);
+        exit(EXIT_FAILURE);
+    }
+
+    // Add the input and output images (which remain on the command-line) to the arguments list
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-input",  0, "Name of the input image", argv[1]);
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-output", 0, "Name of the output image", argv[2]);
+
+    // Define database handle, if used
+#if 0
+    config->database = pmConfigDB(config->site);
+#endif
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// ppImageParseCamera.c extract with some alterations
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    const char *inName = psMetadataLookupStr(NULL, config->arguments, "-input");
+    psLogMsg("ppImage", PS_LOG_INFO, "Opening input image: %s\n", inName);
+    psFits *inFile = psFitsOpen(inName, "r"); // File handle for FITS file
+    if (! inFile) {
+        // There's no point in continuing if we can't open the input
+        psErrorStackPrint(stderr, "Can't open input image: %s\n", inName);
+        exit(EXIT_FAILURE);
+    }
+    psMetadata *phu = psFitsReadHeader(NULL, inFile); // FITS primary header
+
+    psMetadata *cameraFormat = pmConfigCameraFormatFromHeader(NULL, NULL, config, phu, true);
+    if (! config->camera) {
+        cameraFormat = pmConfigCameraFormatFromHeader(NULL, NULL, config, phu, true);
+        if (! config->camera) {
+             // There's no point in continuing if we can't recognise what we've got
+            psErrorStackPrint(stderr, "Can't find camera configuration!\n");
+            exit(EXIT_FAILURE);
+        }
+    }
+    // Determine the correct recipe to use
+    if (! config->recipes && !pmConfigReadRecipes(config, PM_RECIPE_SOURCE_CAMERA | PM_RECIPE_SOURCE_CL)) {
+        // There's no point in continuing if we can't work out what recipes to use
+        psErrorStackPrint(stderr, "Can't find recipe configuration!\n");
+        exit(EXIT_FAILURE);
+    }
+
+#if 1
+    const char *outName = psMetadataLookupStr(NULL, config->arguments, "-output");
+    psLogMsg("ppImage", PS_LOG_INFO, "Opening output image: %s\n", outName);
+    psFits *outFile = psFitsOpen(outName, "w");
+    if (!outFile) {
+        // There's no point in continuing if we can't open the output
+        psErrorStackPrint(stderr, "Can't open output image: %s\n", outName);
+        exit(EXIT_FAILURE);
+    }
+#endif
+
+    // Construct camera in preparation for reading
+    pmFPA *fpa = pmFPAConstruct(config->camera, config->cameraName);
+    pmFPAview *view = pmFPAAddSourceFromHeader(fpa, phu, cameraFormat);
+    printf("View chip: %d\n", view->chip);
+    printf("View cell: %d\n", view->cell);
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// The action happens here
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if 0
+    pmFPAPrint(stdout, fpa, false, false);
+    exit(0);
+#endif
+
+#if 0
+    // A mosaic
+    psMetadata *mosaicCamera = psMetadataConfigRead(NULL, NULL, "/home/mithrandir/price/ipp/config/mcshort_mosaic/camera.config", true);
+    pmFPA *mosaicFPA = pmFPAConstruct(mosaicCamera);
+    psMetadata *mosaicFormat = psMetadataConfigRead(NULL, NULL, "/home/mithrandir/price/ipp/config/mcshort_mosaic/format_mosaic.config", true);
+    pmFPAview *mosaicView = pmFPAviewAlloc(0);
+    pmFPAAddSourceFromView(mosaicFPA, mosaicView, mosaicFormat);
+    psFree(mosaicView);
+#endif
+
+    // Read the FPA
+    pmFPARead(fpa, inFile, NULL);
+
+#if 1
+    psArray *chips = fpa->chips;
+    pmFPAWrite(fpa, outFile, NULL, true, false);
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];
+#if 1
+        //pmChipRead(chip, inFile, NULL);
+        pmChipWrite(chip, outFile, NULL, true, false);
+        psArray *cells = chip->cells;
+        for (int j = 0; j < cells->n; j++) {
+            pmCell *cell = cells->data[j];
+            pmCellWrite(cell, outFile, NULL, true);
+#if 0 // Read bit by bit
+            pmReadout *readout = pmReadoutAlloc(cell);
+            for (int z = 0; pmReadoutReadNext(readout, inFile, z, 512); z++) {
+                do {
+                    printf("Chip %d, Cell %d, Plane %d, row0 = %d\n", i, j, z, readout->row0);
+                    pmReadoutWriteNext(readout, outFile, z);
+                    //pmFPAPrint(stdout, fpa, false, true);
+                } while (pmReadoutReadNext(readout, inFile, z, 512));
+            }
+            psFree(readout);
+#else // Read the whole cell at once
+            //            pmCellRead(cell, inFile, NULL);
+            //            pmCellWrite(cell, outFile, NULL, false);
+            //            pmCellFreeData(cell);
+#endif
+        }
+        pmChipWrite(chip, outFile, NULL, false, false);
+#endif
+
+#if 0
+        pmChipMosaic(mosaicFPA->chips->data[i], chip);
+        pmChipWrite(mosaicFPA->chips->data[i], outFile, NULL, true, true);
+        pmChipFreeData(chip);
+        pmChipFreeData(mosaicFPA->chips->data[i]);
+#endif
+    }
+    pmFPAWrite(fpa, outFile, NULL, false, false);
+#endif
+
+#if 0
+    pmFPAMosaic(mosaicFPA, fpa);
+    pmFPAWrite(mosaicFPA, outFile, NULL, true, true);
+    //pmFPAPrint(stdout, mosaicFPA, true, true);
+    psFree(mosaicCamera);
+    psFree(mosaicFormat);
+    psFree(mosaicView);
+    //psFree(mosaicFPA);
+#endif
+
+#if 1
+    pmFPAPrint(stdout, fpa, false, false);
+#endif
+
+#if 1
+    psFitsClose(outFile);
+#endif
+
+    psFree(view);
+    psFree(phu);
+    psFree(fpa);
+    psFree(config);
+    psFitsClose(inFile);
+    psFree(cameraFormat);
+
+    pmConceptsDone();
+    pmConfigDone();
+    psLibFinalize();
+
+    // Pau.
+}
Index: /branches/eam_branches/eam_branch_20080706/ppImage/test/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppImage/test/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppImage/test/.cvsignore	(revision 22157)
@@ -0,0 +1,1 @@
+*
Index: /branches/eam_branches/eam_branch_20080706/ppMerge/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppMerge/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppMerge/.cvsignore	(revision 22157)
@@ -0,0 +1,12 @@
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+compile
+config.log
+config.status
+configure
+depcomp
+install-sh
+missing
+test
Index: /branches/eam_branches/eam_branch_20080706/ppMerge/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppMerge/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppMerge/Makefile.am	(revision 22157)
@@ -0,0 +1,3 @@
+SUBDIRS = src
+
+CLEANFILES = *~ core core.*
Index: /branches/eam_branches/eam_branch_20080706/ppMerge/autogen.sh
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppMerge/autogen.sh	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppMerge/autogen.sh	(revision 22157)
@@ -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=ppMerge
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL="aclocal $ACLOCAL_FLAGS"
+AUTOHEADER=autoheader
+AUTOMAKE=automake
+AUTOCONF=autoconf
+
+#($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+#        echo
+#        echo "You must have $LIBTOOlIZE installed to compile $PROJECT."
+#        echo "Download the appropriate package for your distribution,"
+#        echo "or get the source tarball at http://ftp.gnu.org/gnu/libtool/"
+#        DIE=1
+#}
+
+($ACLOCAL --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $ACLOCAL installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOHEADER --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOHEADER installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOMAKE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOCONF installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+if test "$DIE" -eq 1; then
+        exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+        echo "You must run this script in the top-level $PROJECT directory"
+        exit 1
+}
+
+if test -z "$*"; then
+        echo "I am going to run ./configure with no arguments - if you wish "
+        echo "to pass any to it, please specify them on the $0 command line."
+fi
+
+#$LIBTOOLIZE --copy --force || echo "$LIBTOOlIZE failed"
+$ACLOCAL || echo "$ACLOCAL failed"
+$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /branches/eam_branches/eam_branch_20080706/ppMerge/configure.ac
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppMerge/configure.ac	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppMerge/configure.ac	(revision 22157)
@@ -0,0 +1,30 @@
+AC_PREREQ(2.61)
+
+AC_INIT([ppMerge], [1.1.0], [ipp-support@ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([src])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_CONFIG_HEADER([src/config.h])
+AM_MAINTAINER_MODE
+
+IPP_STDCFLAGS
+
+AC_LANG(C)
+AC_GNU_SOURCE
+AC_PROG_CC_C99
+AC_PROG_INSTALL
+dnl AC_PROG_LIBTOOL
+AC_SYS_LARGEFILE
+
+PKG_CHECK_MODULES([PSLIB], [pslib >= 1.0.0])
+PKG_CHECK_MODULES([PSMODULE], [psmodules >= 1.0.0])
+PKG_CHECK_MODULES([PPSTATS], [ppStats >= 1.0.0]) 
+
+IPP_STDOPTS
+CFLAGS="${CFLAGS=} -Wall -Werror"
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+])
+AC_OUTPUT
Index: /branches/eam_branches/eam_branch_20080706/ppMerge/src/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppMerge/src/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppMerge/src/.cvsignore	(revision 22157)
@@ -0,0 +1,7 @@
+.deps
+Makefile
+Makefile.in
+ppMerge
+config.h
+config.h.in
+stamp-h1
Index: /branches/eam_branches/eam_branch_20080706/ppMerge/src/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppMerge/src/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppMerge/src/Makefile.am	(revision 22157)
@@ -0,0 +1,25 @@
+bin_PROGRAMS = ppMerge
+
+ppMerge_CFLAGS = $(PPSTATS_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
+ppMerge_LDFLAGS = $(PPSTATS_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
+
+ppMerge_SOURCES =		\
+	ppMerge.c		\
+	ppMergeArguments.c	\
+	ppMergeCamera.c		\
+	ppMergeFiles.c		\
+	ppMergeScaleZero.c	\
+	ppMergeLoop.c		\
+	ppMergeMask.c
+
+
+noinst_HEADERS =		\
+	ppMerge.h
+
+CLEANFILES = *~
+
+clean-local:
+	-rm -f TAGS
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
Index: /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMerge.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMerge.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMerge.c	(revision 22157)
@@ -0,0 +1,155 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppMerge.h"
+
+//#include "ppMem.h"
+
+// Yet to do:
+//
+// 1. Mask pixels with less than the minimum number of electrons
+// 2. Sampling factor for background measurement
+// 3. On/off pairs
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+    psMemSetThreadSafety(false);
+    psTimerStart(TIMERNAME);
+
+    psExit exitValue = PS_EXIT_SUCCESS; // Exit value for program
+
+    pmConfig *config = pmConfigRead(&argc, argv, PPMERGE_RECIPE); // Configuration
+    if (!config) {
+        psErrorStackPrint(stderr, "Error reading configuration.");
+        exitValue = PS_EXIT_CONFIG_ERROR;
+        goto die;
+    }
+
+    if (!ppMergeArguments(argc, argv, config)) {
+        psErrorStackPrint(stderr, "Error reading arguments.");
+        exitValue = PS_EXIT_CONFIG_ERROR;
+        goto die;
+    }
+
+    ppMergeType type = psMetadataLookupS32(NULL, config->arguments, "TYPE"); // Type of frame
+    switch (type) {
+      case PPMERGE_TYPE_MASK:
+        if (!ppMergeMask(config)) {
+            psErrorStackPrint(stderr, "Error generating mask.");
+            exitValue = PS_EXIT_DATA_ERROR;
+            goto die;
+        }
+        break;
+      case PPMERGE_TYPE_BIAS:
+      case PPMERGE_TYPE_DARK:
+      case PPMERGE_TYPE_SHUTTER:
+      case PPMERGE_TYPE_FLAT:
+      case PPMERGE_TYPE_FRINGE:
+        if (!ppMergeScaleZero(config)) {
+            psErrorStackPrint(stderr, "Error getting scale and zero-points.");
+            exitValue = PS_EXIT_DATA_ERROR;
+            goto die;
+        }
+        if (!ppMergeLoop(config)) {
+            psErrorStackPrint(stderr, "Error performing merge.");
+            exitValue = PS_EXIT_PROG_ERROR;
+            goto die;
+        }
+        break;
+      default:
+        psAbort("Invalid frame type: %x", type);
+    }
+
+
+    // Output the statistics
+    bool mdok;                          // Status of MD lookup
+    psString statsName = psMetadataLookupStr(&mdok, config->arguments, "STATS.NAME"); // Statistics file name
+    if (mdok && statsName && strlen(statsName) > 0) {
+        psString resolved = pmConfigConvertFilename(statsName, config, true, true); // Resolved filename
+        FILE *statsFile = fopen(resolved, "w"); // Output statistics file
+        if (!statsFile) {
+            psError(PS_ERR_IO, true, "Unable to open statistics file %s for writing.", resolved);
+            psFree(resolved);
+            exitValue = PS_EXIT_CONFIG_ERROR;
+            goto die;
+        }
+        psFree(resolved);
+        psMetadata *stats = psMetadataLookupMetadata(&mdok, config->arguments, "STATS.DATA"); // Statistics
+        if (!stats) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find statistics");
+            exitValue = PS_EXIT_PROG_ERROR;
+            goto die;
+        }
+        psString statsOut = psMetadataConfigFormat(stats); // String to write out
+        fprintf(statsFile, "%s", statsOut);
+        psFree(statsOut);
+        fclose(statsFile);
+    }
+
+
+
+
+#if 0
+    // Set various tasks (define optional operations)
+    ppMergeOptions *options = ppMergeOptionsParse(config);
+    if (!options) {
+        psErrorStackPrint(stderr, "Unable to parse options.");
+        exit(EXIT_FAILURE);
+    }
+
+    // Check the inputs
+    ppMergeData *data = ppMergeCheckInputs(options, config);
+    if (!data) {
+        psErrorStackPrint(stderr, "Not enough valid input files.");
+        exit(EXIT_FAILURE);
+    }
+
+    if (options->mask) {
+        // Generate a mask
+        ppMergeMask(data, options, config);
+    } else {
+
+        psImage *scale = NULL;              // The scalings
+        psImage *zero = NULL;               // The zeros
+        psArray *shutters = NULL;           // The shutter correction data
+
+        // Measure the background in each image
+        ppMergeScaleZero(&scale, &zero, &shutters, data, options, config);
+
+        // Do the combination and write
+        ppMergeCombine(scale, zero, shutters, data, options, config);
+
+        psFree(scale);
+        psFree(zero);
+        psFree(shutters);
+    }
+
+    // Output the statistics
+    if (data->statsFile && data->stats) {
+        psString statsOut = psMetadataConfigFormat(data->stats); // String to write out
+        fprintf(data->statsFile, "%s", statsOut);
+        psFree(statsOut);
+    }
+
+    // Cleaning up
+    psFree(data);
+    psFree(options);
+#endif
+
+ die:
+    psTrace("ppSub", 1, "Finished at %f sec\n", psTimerMark("ppSub"));
+    psTimerStop();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exitValue);
+}
Index: /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMerge.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMerge.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMerge.h	(revision 22157)
@@ -0,0 +1,82 @@
+#ifndef PP_MERGE_H
+#define PP_MERGE_H
+
+#define TIMERNAME "ppMerge"             // Name for timer
+#define PPMERGE_RECIPE "PPMERGE"        // Recipe name
+
+// Type of frame to merge
+typedef enum {
+    PPMERGE_TYPE_UNKNOWN,               // Unknown type
+    PPMERGE_TYPE_BIAS,                  // Bias frame
+    PPMERGE_TYPE_DARK,                  // (Multi-)Dark frame
+    PPMERGE_TYPE_MASK,                  // Mask frame
+    PPMERGE_TYPE_SHUTTER,               // Shutter frame
+    PPMERGE_TYPE_FLAT,                  // Flat-field frame (dome or sky)
+    PPMERGE_TYPE_FRINGE,                // Fringe frame
+} ppMergeType;
+
+// Files, for activation
+typedef enum {
+    PPMERGE_FILES_ALL,                  // All files
+    PPMERGE_FILES_INPUT,                // Input files
+    PPMERGE_FILES_OUTPUT                // Output files
+} ppMergeFiles;
+
+// Parse command-line arguments and recipe
+bool ppMergeArguments(int argc, char *argv[], // Command-line arguments
+                      pmConfig *config  // Configuration
+    );
+
+// Set up camera files
+bool ppMergeCamera(pmConfig *config     // Configuration
+    );
+
+// Measure scale and zero-points
+bool ppMergeScaleZero(pmConfig *config  // Configuration
+    );
+
+// Main loop to do the merging
+bool ppMergeLoop(pmConfig *config       // Configuration
+    );
+
+// Main loop for masks
+bool ppMergeMask(pmConfig *config       // Configuration
+    );
+
+// Read nominated input file
+bool ppMergeFileReadInput(pmConfig *config, // Configuration
+                          pmReadout *readout, // Readout into which to read
+                          int num,      // Number of file in sequence
+                          int rows      // Number of rows to read at once
+    );
+
+// Open nominated input file
+bool ppMergeFileOpenInput(pmConfig *config, // Configuration
+                          const pmFPAview *view, // View to open
+                          int num       // Number of file in sequence
+    );
+
+// Set the data level for files specified by name; return an array of the files
+psArray *ppMergeFileDataLevel(const pmConfig *config, // Configuration
+                              const char *name, // Name of files
+                              pmFPALevel level // Level for file data level
+    );
+
+// Activate/deactivate a list of files
+bool ppMergeFileActivate(const pmConfig *config, // Configuration
+                         ppMergeFiles files, // Files to turn on/off
+                         bool state     // Activation state
+    );
+
+// Activate/deactivate a single element for a list; return array of files
+psArray *ppMergeFileActivateSingle(const pmConfig *config, // Configuration
+                                   ppMergeFiles files, // Files to turn on/off
+                                   bool state,   // Activation state
+                                   int num // Number of file in sequence
+    );
+
+// Return name of output pmFPAfile
+psString ppMergeOutputFile(const pmConfig *config // Configuration
+    );
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeArguments.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeArguments.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeArguments.c	(revision 22157)
@@ -0,0 +1,384 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppMerge.h"
+
+// Print usage information and die
+static void usage(const char *program,  // Name of the program
+                  psMetadata *arguments // Command-line arguments
+    )
+{
+    fprintf(stderr, "\nPan-STARRS Detrend Merging\n\n");
+    fprintf(stderr, "Usage: %s INPUT.mdc OUTPUT_ROOT\n"
+            "where INPUTS.mdc contains various METADATAs, each with:\n"
+            "\tIMAGE(STR):     Image filename\n"
+            "\tMASK(STR):      Mask filename\n"
+            "\tWEIGHT(STR)     Weight map filename\n"
+            "where MASK and WEIGHT are optional.",
+            program);
+    fprintf(stderr, "\n");
+    psArgumentHelp(arguments);
+}
+
+// Get a float-point value from the command-line or recipe, and add it to the arguments
+#define VALUE_ARG_RECIPE_FLOAT(ARGNAME, RECIPENAME, TYPE) { \
+    ps##TYPE value = psMetadataLookup##TYPE(NULL, arguments, ARGNAME); \
+    if (isnan(value)) { \
+        bool mdok; \
+        value = psMetadataLookup##TYPE(&mdok, recipe, RECIPENAME); \
+        if (!mdok) { \
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to find %s in recipe %s", \
+                RECIPENAME, PPMERGE_RECIPE); \
+            goto ERROR; \
+        } \
+    } \
+    psMetadataAdd##TYPE(config->arguments, PS_LIST_TAIL, RECIPENAME, 0, NULL, value); \
+}
+
+// Get an integer value from the command-line or recipe, and add it to the arguments
+#define VALUE_ARG_RECIPE_INT(ARGNAME, RECIPENAME, TYPE, UNSET) { \
+    ps##TYPE value = psMetadataLookup##TYPE(NULL, arguments, ARGNAME); \
+    if (value == UNSET) { \
+        bool mdok; \
+        value = psMetadataLookup##TYPE(&mdok, recipe, RECIPENAME); \
+        if (!mdok) { \
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to find %s in recipe %s", \
+                RECIPENAME, PPMERGE_RECIPE); \
+            goto ERROR; \
+        } \
+    } \
+    psMetadataAdd##TYPE(config->arguments, PS_LIST_TAIL, RECIPENAME, 0, NULL, value); \
+}
+
+// Get a boolean from the command-line or recipe, and add it to the arguments if either is set
+#define VALUE_ARG_RECIPE_BOOL(ARGNAME, RECIPENAME) { \
+    bool value = (psMetadataLookupBool(NULL, arguments, ARGNAME) || \
+                  psMetadataLookupBool(NULL, recipe, RECIPENAME)); \
+    psMetadataAddBool(config->arguments, PS_LIST_TAIL, RECIPENAME, 0, NULL, value); \
+}
+
+// Get a statistic name from the command-line or recipe, and add the enum to the arguments
+#define VALUE_ARG_RECIPE_STAT(ARGNAME, RECIPENAME) { \
+    const char *stat = psMetadataLookupStr(NULL, arguments, ARGNAME); \
+    if (!stat) { \
+        stat = psMetadataLookupStr(NULL, recipe, RECIPENAME); \
+        if (!stat) { \
+            psError(PS_ERR_BAD_PARAMETER_VALUE, false, "Unable to find %s in recipe %s", \
+                    RECIPENAME, PPMERGE_RECIPE); \
+            goto ERROR; \
+        } \
+    } \
+    psMetadataAddS32(config->arguments, PS_LIST_TAIL, RECIPENAME, 0, NULL, psStatsOptionFromString(stat)); \
+}
+
+// Get a string from the command-line or recipe, and add to the arguments
+#define VALUE_ARG_RECIPE_STR(ARGNAME, RECIPENAME) { \
+    const char *str = psMetadataLookupStr(NULL, arguments, ARGNAME); \
+    if (!str) { \
+        str = psMetadataLookupStr(NULL, recipe, RECIPENAME); \
+        if (!str) { \
+            psError(PS_ERR_BAD_PARAMETER_VALUE, false, "Unable to find %s in recipe %s", \
+                    RECIPENAME, PPMERGE_RECIPE); \
+            goto ERROR; \
+        } \
+    } \
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, RECIPENAME, 0, NULL, str); \
+}
+
+// Get a string from the command-line or recipe, and add the appropriate mask value to the arguments
+#define VALUE_ARG_RECIPE_MASK(ARGNAME, RECIPENAME) { \
+    const char *str = psMetadataLookupStr(NULL, arguments, ARGNAME); \
+    if (!str) { \
+        str = psMetadataLookupStr(NULL, recipe, RECIPENAME); \
+        if (!str) { \
+            psError(PS_ERR_BAD_PARAMETER_VALUE, false, "Unable to find %s in recipe %s", \
+                    RECIPENAME, PPMERGE_RECIPE); \
+            goto ERROR; \
+        } \
+    } \
+    psMaskType mask = pmConfigMask(str, config); \
+    psMetadataAddU8(config->arguments, PS_LIST_TAIL, RECIPENAME, 0, NULL, mask); \
+}
+
+// Get a string value from the command-line and add it to the target
+static bool valueArgStr(psMetadata *arguments, // Command-line arguments
+                        const char *argName, // Argument name in the command-line arguments
+                        const char *mdName, // Name for value in the metadata
+                        psMetadata *target // Target metadata to which to add value
+                        )
+{
+    psString value = psMetadataLookupStr(NULL, arguments, argName); // Value of interest
+    if (value && strlen(value) > 0) {
+        return psMetadataAddStr(target, PS_LIST_TAIL, mdName, 0, NULL, value);
+    }
+    return false;
+}
+
+bool ppMergeArguments(int argc, char *argv[], pmConfig *config)
+{
+    assert(config);
+
+    psMetadata *arguments = psMetadataAlloc(); // Command-line arguments
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-type", 0, "Type of calibration frame", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-stats", 0, "MDC file to hold statistics ", NULL);
+    // Standard combination parameters
+    psMetadataAddS32(arguments, PS_LIST_TAIL, "-rows",     0, "Rows to read per scan", 0);
+    psMetadataAddS32(arguments, PS_LIST_TAIL, "-sample",   0, "Sampling factor for background", 0);
+    psMetadataAddS32(arguments, PS_LIST_TAIL, "-iter",     0, "Number of rejection iterations", 0);
+    psMetadataAddF32(arguments, PS_LIST_TAIL, "-rej",      0, "Rejection threshold (sigma)", NAN);
+    psMetadataAddF32(arguments, PS_LIST_TAIL, "-fraclow",  0, "Fraction of low pixels to discard", NAN);
+    psMetadataAddF32(arguments, PS_LIST_TAIL, "-frachigh", 0, "Fraction of low pixels to discard", NAN);
+    psMetadataAddS32(arguments, PS_LIST_TAIL, "-nkeep",    0, "Minimum number of pixels in stack to keep", 0);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-weights", 0, "Use image weights in combination?", false);
+
+    // XXX EAM : not clear this should be allowed on the command line.
+    // psMetadataAddStr(arguments, PS_LIST_TAIL, "-maskval",  0, "Mask value for input data", NULL);
+
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-combine",  0, "Statistic to use for combination", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-mean",     0, "Statistic to use to measure the mean", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-stdev",    0, "Statistic to use to measure the stdev", NULL);
+
+    // Fringe construction parameters
+    psMetadataAddS32(arguments, PS_LIST_TAIL, "-fringe-num",     0, "Number of fringe regions", 0);
+    psMetadataAddS32(arguments, PS_LIST_TAIL, "-fringe-size",    0, "Half-size of fringe regions", 0);
+    psMetadataAddS32(arguments, PS_LIST_TAIL, "-fringe-xsmooth", 0, "Number of smoothing regions in x", 0);
+    psMetadataAddS32(arguments, PS_LIST_TAIL, "-fringe-ysmooth", 0, "Number of smoothing regions in y", 0);
+
+    // Shutter construction parameters
+    psMetadataAddS32(arguments, PS_LIST_TAIL, "-shutter-size", 0, "Size for shutter measurement regions", 0);
+    psMetadataAddS32(arguments, PS_LIST_TAIL, "-shutter-iter", 0, "Number of iterations for shutter", 0);
+    psMetadataAddF32(arguments, PS_LIST_TAIL, "-shutter-rej",  0, "Rejection limit for shutter", NAN);
+
+    // Mask construction parameters
+    psMetadataAddF32(arguments, PS_LIST_TAIL, "-mask-suspect",  0, "Threshold for suspect pixels (sigma)", NAN);
+    psMetadataAddF32(arguments, PS_LIST_TAIL, "-mask-bad",      0, "Threshold for bad pixels (sigma)", NAN);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-mask-mode",     0, "Mode to identify bad pixels", NULL);
+    psMetadataAddS32(arguments, PS_LIST_TAIL, "-mask-grow",     0, "Number of pixels to grow final mask", 0);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-mask-set-valud",0, "Value to set for output mask pixels", NULL);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-mask-chip",    0, "Measure mask statistics by chip?", false);
+
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-mask-smooth-suspect",  0, "smooth suspect pixels?", false);
+    psMetadataAddF32(arguments, PS_LIST_TAIL, "-mask-smooth-scale",  0, "smoothing sigma for suspect pixels", NAN);
+
+    // chip & cell selections are used to limit chips to be processed
+    int argnum;
+    if ((argnum = psArgumentGet (argc, argv, "-chip"))) {
+        psArgumentRemove (argnum, &argc, argv);
+        psMetadataAddStr (config->arguments, PS_LIST_TAIL, "CHIP_SELECTIONS", PS_DATA_STRING, "", argv[argnum]);
+        psArgumentRemove (argnum, &argc, argv);
+    }
+    if ((argnum = psArgumentGet (argc, argv, "-cell"))) {
+        psArgumentRemove (argnum, &argc, argv);
+        psMetadataAddStr (config->arguments, PS_LIST_TAIL, "CELL_SELECTIONS", PS_DATA_STRING, "", argv[argnum]);
+        psArgumentRemove (argnum, &argc, argv);
+    }
+
+    if (argc == 1 || !psArgumentParse(arguments, &argc, argv) || argc != 3) {
+        usage(argv[0], arguments);
+        goto ERROR;
+    }
+
+    unsigned int numBad = 0;                     // Number of bad lines
+    psMetadata *inputs = psMetadataConfigRead(NULL, &numBad, argv[1], false); // Information about inputs
+    if (!inputs || numBad > 0) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, false, "Unable to cleanly read MDC file with inputs.");
+        goto ERROR;
+    }
+    psMetadataAddMetadata(config->arguments, PS_LIST_TAIL, "INPUTS", 0,
+                          "Metadata with input details", inputs);
+    psFree(inputs);
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "OUTPUT", 0,
+                     "Root name of the output image list", argv[2]);
+
+    valueArgStr(arguments, "-stats", "STATS.NAME", config->arguments);
+
+
+    // Set the type of calibration frame
+    const char *typeStr = psMetadataLookupStr(NULL, arguments, "-type"); // Type of calibration
+    if (strlen(typeStr) <= 0) {
+      psError(PS_ERR_UNKNOWN, false, "No -type specified.");
+      goto ERROR;
+    }
+    ppMergeType type = PPMERGE_TYPE_UNKNOWN; // Enumerated type for frame type
+    if (strcasecmp(typeStr, "BIAS") == 0) {
+      type = PPMERGE_TYPE_BIAS;
+      goto VALID;
+    } 
+    if (strcasecmp(typeStr, "DARK") == 0 ||
+	strcasecmp(typeStr, "DARK_PREMASK") == 0) {
+      type = PPMERGE_TYPE_DARK;
+      goto VALID;
+    } 
+    if (strcasecmp(typeStr, "FLAT") == 0 || 
+	strcasecmp(typeStr, "DOMEFLAT") == 0 ||
+	strcasecmp(typeStr, "SKYFLAT") == 0 ||
+	strcasecmp(typeStr, "FLAT_PREMASK") == 0 ||
+	strcasecmp(typeStr, "SKYFLAT_PREMASK") == 0 ||
+	strcasecmp(typeStr, "DOMEFLAT_PREMASK") == 0) {
+      type = PPMERGE_TYPE_FLAT;
+      goto VALID;
+    } 
+    if (strcasecmp(typeStr, "FRINGE") == 0) {
+      type = PPMERGE_TYPE_FRINGE;
+      goto VALID;
+    } 
+    if (strcasecmp(typeStr, "SHUTTER") == 0) {
+      type = PPMERGE_TYPE_SHUTTER;
+      goto VALID;
+    } 
+    if (strcasecmp(typeStr, "MASK") == 0 ||
+	strcasecmp(typeStr, "DARKMASK") == 0 ||
+	strcasecmp(typeStr, "FLATMASK") == 0) {
+      type = PPMERGE_TYPE_MASK;
+      goto VALID;
+    } 
+    psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unrecognised image type: %s", typeStr);
+    goto ERROR;
+
+ VALID:
+    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "TYPE", 0, "Type of calibration frame", type);
+
+    // Need to set the camera before the recipes can be read
+    if (!ppMergeCamera(config)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to setup cameras.");
+        goto ERROR;
+    }
+
+    psMetadata *recipe = psMetadataLookupMetadata(NULL, config->recipes, PPMERGE_RECIPE); // Recipe for ppSim
+    if (!recipe) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find recipe %s", PPMERGE_RECIPE);
+        goto ERROR;
+    }
+
+    // Standard combination parameters
+    VALUE_ARG_RECIPE_INT("-rows",       "ROWS",     S32, 0);
+    VALUE_ARG_RECIPE_INT("-sample",     "SAMPLE",   S32, 0);
+    VALUE_ARG_RECIPE_INT("-iter",       "ITER",     S32, 0);
+    VALUE_ARG_RECIPE_FLOAT("-rej",      "REJ",      F32);
+    VALUE_ARG_RECIPE_FLOAT("-fraclow",  "FRACLOW",  F32);
+    VALUE_ARG_RECIPE_FLOAT("-frachigh", "FRACHIGH", F32);
+    VALUE_ARG_RECIPE_INT("-nkeep",      "NKEEP",    S32, 0);
+    VALUE_ARG_RECIPE_BOOL("-weights",   "WEIGHTS");
+
+    // XXX we do not supply this on the command line
+    // VALUE_ARG_RECIPE_MASK("-maskval",   "MASKVAL");
+
+    VALUE_ARG_RECIPE_STAT("-combine",   "COMBINE");
+    VALUE_ARG_RECIPE_STAT("-mean",      "MEAN");
+    VALUE_ARG_RECIPE_STAT("-stdev",     "STDEV");
+
+    // Fringe construction parameters
+    VALUE_ARG_RECIPE_INT("-fringe-num",     "FRINGE.NUM",     S32, 0);
+    VALUE_ARG_RECIPE_INT("-fringe-size",    "FRINGE.SIZE",    S32, 0);
+    VALUE_ARG_RECIPE_INT("-fringe-xsmooth", "FRINGE.XSMOOTH", S32, 0);
+    VALUE_ARG_RECIPE_INT("-fringe-ysmooth", "FRINGE.YSMOOTH", S32, 0);
+
+    // Shutter construction parameters
+    VALUE_ARG_RECIPE_INT("-shutter-size",  "SHUTTER.SIZE", S32, 0);
+
+    // Mask construction parameters
+    VALUE_ARG_RECIPE_FLOAT("-mask-suspect", "MASK.SUSPECT", F32);
+    VALUE_ARG_RECIPE_FLOAT("-mask-bad",     "MASK.BAD",     F32);
+    VALUE_ARG_RECIPE_INT("-mask-grow",      "MASK.GROW",    S32, 0);
+    VALUE_ARG_RECIPE_STR("-mask-set-value", "MASK.SET.VALUE");
+    VALUE_ARG_RECIPE_BOOL("-mask-chip",     "MASK.CHIPSTATS");
+
+    VALUE_ARG_RECIPE_BOOL("-mask-smooth-suspect", "MASK.SMOOTH.SUSPECT");
+    VALUE_ARG_RECIPE_FLOAT("-mask-smooth-scale", "MASK.SMOOTH.SCALE", F32);
+
+    const char *maskModeStr = psMetadataLookupStr(NULL, arguments, "-mask-mode"); // Mode to identify bad pix
+    if (!maskModeStr) {
+        maskModeStr = psMetadataLookupStr(NULL, recipe, "MASK.MODE");
+        if (!maskModeStr) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, false, "No mask mode specified in recipe.");
+            goto ERROR;
+        }
+    }
+    pmMaskIdentifyMode maskMode = pmMaskIdentifyModeFromString(maskModeStr);
+    if (maskMode == PM_MASK_ID_NONE) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, false, "Invalid mask mode %s", maskModeStr);
+        goto ERROR;
+    }
+    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "MASK.MODE", 0, "Mode for mask identification",
+                     maskMode);
+
+
+    if (type == PPMERGE_TYPE_DARK) {
+        psMetadata *ordinates = psMetadataLookupMetadata(NULL, recipe, "DARK.ORDINATES"); // Ordinates info
+        psArray *translated = psArrayAllocEmpty(psListLength(ordinates->list)); // Translated version
+
+        psMetadataIterator *iter = psMetadataIteratorAlloc(ordinates, PS_LIST_HEAD, NULL); // Iterator
+        psMetadataItem *item;           // Item from iteration
+        while ((item = psMetadataGetAndIncrement(iter))) {
+            int order = 0;              // Polynomial order
+            bool scale = false;         // Scale values?
+            float min = NAN, max = NAN; // Minimum and maximum values for scaling
+            switch (item->type) {
+              case PS_TYPE_S32:
+                order = item->data.S32;
+                break;
+              case PS_DATA_METADATA:
+                order = psMetadataLookupS32(NULL, item->data.md, "ORDER");
+                bool mdok;                  // Status of MD lookup
+                scale = psMetadataLookupBool(&mdok, item->data.md, "SCALE");
+                min = psMetadataLookupF32(&mdok, item->data.md, "MIN");
+                max = psMetadataLookupF32(&mdok, item->data.md, "MAX");
+                break;
+              default:
+                psError(PS_ERR_BAD_PARAMETER_TYPE, true,
+                        "Type of DARK.ORDINATES entry %s (%x) is not METADATA or S32",
+                        item->name, item->type);
+                return false;
+            }
+            if (order <= 0) {
+                psError(PS_ERR_BAD_PARAMETER_VALUE, true, "ORDER not positive (%d) for DARK.ORDINATES %s",
+                        order, item->name);
+                return false;
+            }
+
+            pmDarkOrdinate *ord = pmDarkOrdinateAlloc(item->name, order);
+            ord->scale = scale;
+            ord->min = min;
+            ord->max = max;
+            psArrayAdd(translated, translated->n, ord);
+            psFree(ord);
+        }
+        psFree(iter);
+
+        psMetadataAddArray(config->arguments, PS_LIST_TAIL, "DARK.ORDINATES", 0,
+                           "Ordinates to fit for dark", translated);
+        psFree(translated);             // Drop reference
+
+        psString darkNorm = psMetadataLookupStr(NULL, recipe, "DARK.NORM"); // Normalisation concept
+        if (darkNorm && strcmp(darkNorm, "NONE") != 0) {
+            psMetadataAddStr(config->arguments, PS_LIST_TAIL, "DARK.NORM", 0,
+                             "Normalisation concept for dark", darkNorm);
+        }
+    }
+
+
+#if 0
+    // Add concepts for scale and zero
+    // XXX These have never been used
+    psMetadataItem *scaleItem = psMetadataItemAllocF32("PPMERGE.SCALE", "Scaling for ppMerge", NAN);
+    psMetadataItem *zeroItem = psMetadataItemAllocF32("PPMERGE.ZERO", "Zero offset for ppMerge", NAN);
+    pmConceptRegister(scaleItem, NULL, NULL, false, PM_FPA_LEVEL_CELL);
+    pmConceptRegister(zeroItem, NULL, NULL, false, PM_FPA_LEVEL_CELL);
+    psFree(scaleItem);
+    psFree(zeroItem);
+#endif
+
+    return true;
+
+ERROR:
+    psFree(arguments);
+    return false;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeCamera.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeCamera.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeCamera.c	(revision 22157)
@@ -0,0 +1,363 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppMerge.h"
+
+// Define an output file, with its own FPA
+bool outputFile(pmConfig *config,       // Configuration
+                const char *name,       // Name of output file
+                pmFPAfileType type,     // Type of file
+                const char *description, // Description of file
+                psMetadata *format,     // Camera format
+                pmFPAview *view         // View for PHU
+    )
+{
+    assert(config);
+    assert(name && strlen(name) > 0);
+    assert(view);
+
+    // Output image
+    pmFPA *fpa = pmFPAConstruct(config->camera, config->cameraName); // FPA to contain the output
+    if (!fpa) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to construct an FPA from camera configuration.");
+        return false;
+    }
+
+    // Chip selection: turn on only the chips specified.
+    bool status;
+    char *chipLine = psMetadataLookupStr(&status, config->arguments, "CHIP_SELECTIONS");
+    psArray *chips = psStringSplitArray (chipLine, ",", false);
+    if (chips->n > 0) {
+        pmFPASelectChip (fpa, -1, true); // deselect all chips
+        for (int i = 0; i < chips->n; i++) {
+            int chipNum = atoi(chips->data[i]);
+            if (! pmFPASelectChip(fpa, chipNum, false)) {
+                psError(PS_ERR_IO, false, "Chip number %d doesn't exist in camera.\n", chipNum);
+		psFree (chips);
+                return false;
+            }
+        }
+    }
+    psFree (chips);
+
+    // Cell selection: turn on only the cells specified.  Note that this affects the same cell
+    // for all chips
+    char *cellLine = psMetadataLookupStr(&status, config->arguments, "CELL_SELECTIONS");
+    psArray *cells = psStringSplitArray (cellLine, ",", false);
+    if (cells->n > 0) {
+	for (int i = 0; i < fpa->chips->n; i++) {
+	    pmChip *chip = fpa->chips->data[i];
+	    pmChipSelectCell (chip, -1, true); // deselect all cells
+	    for (int j = 0; j < cells->n; j++) {
+		int cellNum = atoi(cells->data[j]);
+		if (! pmChipSelectCell(chip, cellNum, false)) {
+		    psError(PS_ERR_IO, false, "Cell number %d doesn't exist in camera.\n", cellNum);
+		    psFree (cells);
+		    return false;
+		}
+	    }
+	}
+    }
+    psFree (cells);
+
+    pmFPAfile *output = pmFPAfileDefineOutput(config, fpa, name);
+    psFree(fpa);                        // Drop reference
+    if (!output) {
+        psError(PS_ERR_IO, false, "Unable to generate output file from %s", name);
+        return false;
+    }
+    if (output->type != type) {
+        psError(PS_ERR_IO, true, "%s is not of type %s", name, pmFPAfileStringFromType(type));
+        return false;
+    }
+    output->save = true;
+
+    if (!pmFPAAddSourceFromView(fpa, description, view, format)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to generate output FPA.");
+        return false;
+    }
+
+    return true;
+}
+
+
+bool ppMergeCamera(pmConfig *config)
+{
+    bool haveMasks = false;             // Do we have masks?
+    bool haveWeights = false;           // Do we have weight maps?
+
+    ppMergeType type = psMetadataLookupS32(NULL, config->arguments, "TYPE"); // Type of frame
+    psMetadata *inputs = psMetadataLookupMetadata(NULL, config->arguments, "INPUTS"); // The inputs info
+    psMetadataIterator *iter = psMetadataIteratorAlloc(inputs, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *item;               // Item from iteration
+    int numFiles = 0;                   // Number of files
+    while ((item = psMetadataGetAndIncrement(iter))) {
+        if (item->type != PS_DATA_METADATA) {
+            psError(PS_ERR_BAD_PARAMETER_TYPE, true,
+                    "Component %s of the input metadata is not of type METADATA", item->name);
+            psFree(iter);
+            return false;
+        }
+
+        psMetadata *input = item->data.md; // The input metadata of interest
+
+        psString image = psMetadataLookupStr(NULL, input, "IMAGE"); // Name of image
+        if (!image || strlen(image) == 0) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Component %s lacks IMAGE of type STR", item->name);
+            psFree(iter);
+            return false;
+        }
+
+        bool mdok;
+        psString mask = psMetadataLookupStr(&mdok, input, "MASK"); // Name of mask
+        psString weight = psMetadataLookupStr(&mdok, input, "WEIGHT"); // Name of weight map
+
+        // Add the image file
+        psArray *imageFiles = psArrayAlloc(1); // Array of filenames for this FPA
+        imageFiles->data[0] = psMemIncrRefCounter(image);
+        psMetadataAddArray(config->arguments, PS_LIST_TAIL, "IMAGE.FILENAMES", PS_META_REPLACE,
+                           "Filenames of image files", imageFiles);
+        psFree(imageFiles);
+
+        bool found = false;             // Found the file?
+        pmFPAfile *imageFile = pmFPAfileDefineFromArgs(&found, config, "PPMERGE.INPUT", "IMAGE.FILENAMES");
+        if (!imageFile || !found) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to define file from image %d (%s)", numFiles, image);
+            return false;
+        }
+        if (imageFile->type != PM_FPA_FILE_IMAGE) {
+            psError(PS_ERR_IO, true, "PPMERGE.INPUT is not of type IMAGE");
+            return false;
+        }
+
+        // Optionally add the mask file
+        if (mask && strlen(mask) > 0) {
+            psArray *maskFiles = psArrayAlloc(1); // Array of filenames for this FPA
+            maskFiles->data[0] = psMemIncrRefCounter(mask);
+            psMetadataAddArray(config->arguments, PS_LIST_TAIL, "MASK.FILENAMES", PS_META_REPLACE,
+                               "Filenames of mask files", maskFiles);
+            psFree(maskFiles);
+
+            bool status;
+            pmFPAfile *maskFile = pmFPAfileBindFromArgs(&status, imageFile, config, "PPMERGE.INPUT.MASK",
+                                                        "MASK.FILENAMES");
+            if (!status) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to define file from mask %d (%s)", numFiles, mask);
+                return false;
+            }
+            if (maskFile->type != PM_FPA_FILE_MASK) {
+                psError(PS_ERR_IO, true, "PPMERGE.INPUT.MASK is not of type MASK");
+                return false;
+            }
+            haveMasks = true;
+        }
+
+        // Optionally add the weight file
+        if (weight && strlen(weight) > 0) {
+            haveWeights = true;
+            psArray *weightFiles = psArrayAlloc(1); // Array of filenames for this FPA
+            weightFiles->data[0] = psMemIncrRefCounter(weight);
+            psMetadataAddArray(config->arguments, PS_LIST_TAIL, "WEIGHT.FILENAMES", PS_META_REPLACE,
+                               "Filenames of weight files", weightFiles);
+            psFree(weightFiles);
+
+            bool status;
+            pmFPAfile *weightFile = pmFPAfileBindFromArgs(&status, imageFile, config, "PPMERGE.INPUT.WEIGHT",
+                                                          "WEIGHT.FILENAMES");
+            if (!status) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to define file from weight %d (%s)", numFiles, weight);
+                return false;
+            }
+            if (weightFile->type != PM_FPA_FILE_WEIGHT) {
+                psError(PS_ERR_IO, true, "PPMERGE.INPUT.WEIGHT is not of type WEIGHT");
+                return false;
+            }
+            haveWeights = true;
+        }
+
+        numFiles++;
+    }
+    psFree(iter);
+    psMetadataRemoveKey(config->arguments, "IMAGE.FILENAMES");
+    if (psMetadataLookup(config->arguments, "MASK.FILENAMES")) {
+        psMetadataRemoveKey(config->arguments, "MASK.FILENAMES");
+    }
+    if (psMetadataLookup(config->arguments, "WEIGHT.FILENAMES")) {
+        psMetadataRemoveKey(config->arguments, "WEIGHT.FILENAMES");
+    }
+    if (psMetadataLookup(config->arguments, "PSF.FILENAMES")) {
+        psMetadataRemoveKey(config->arguments, "PSF.FILENAMES");
+    }
+
+    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "INPUTS.NUM", 0, "Number of input files", numFiles);
+    psMetadataAddBool(config->arguments, PS_LIST_TAIL, "INPUTS.MASKS", 0, "Got input masks?", haveMasks);
+    psMetadataAddBool(config->arguments, PS_LIST_TAIL, "INPUTS.WEIGHTS", 0,
+                      "Got input weights?", haveWeights);
+
+
+    // Check that all the inputs are consistent
+
+// Check an FPA level to ensure the camera format and PHU are consistent across all input files
+#define CHECK_LEVEL(HDU, CHIP, CELL) { \
+    if (HDU) { \
+        if (!phuView) { \
+            phuView = pmFPAviewAlloc(0); \
+            phuView->chip = CHIP; \
+            phuView->cell = CELL; \
+        } else if ((phuView->chip != (CHIP)) && (phuView->cell != (CELL))) { \
+            psError(PS_ERR_UNKNOWN, true, "Differing PHU for input %d", i); \
+            psFree(phuView); \
+            return false; \
+        } \
+        if (!format) { \
+            format = (HDU)->format; \
+        } else if (format != (HDU)->format) { \
+            psError(PS_ERR_UNKNOWN, true, "Camera format %d doesn't match: %p vs %p", \
+                    i, format, (HDU)->format); \
+            psFree(phuView); \
+            return false; \
+        } \
+        continue; \
+    } \
+}
+
+    psMetadata *format = NULL;          // Camera format
+    pmFPAview *phuView = NULL;          // View to PHU
+    for (int i = 0; i < numFiles; i++) {
+        pmFPAfile *input = pmFPAfileSelectSingle(config->files, "PPMERGE.INPUT", i); // File of interest
+        pmFPA *fpa = input->fpa;        // FPA of interest
+        CHECK_LEVEL(fpa->hdu, -1, -1);
+        psArray *chips = fpa->chips;    // Array of chips
+        for (int j = 0; j < chips->n; j++) {
+            pmChip *chip = chips->data[j]; // Chip of interest
+            CHECK_LEVEL(chip->hdu, j, -1);
+            psArray *cells = chip->cells;   // Array of cells
+            for (int k = 0; k < cells->n; k++) {
+                pmCell *cell = cells->data[k]; // Cell of interest
+                CHECK_LEVEL(cell->hdu, j, k);
+            }
+        }
+    }
+    if (!phuView || !format) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find PHU for input files.");
+        psFree(phuView);
+        return false;
+    }
+
+    // Cull chips and cells that don't have data
+    // Otherwise the abundance of metadata in the concepts (esp. for GPC) can overload the memory
+    for (int i = 0; i < numFiles; i++) {
+        pmFPAfile *input = pmFPAfileSelectSingle(config->files, "PPMERGE.INPUT", i); // File of interest
+        pmFPA *fpa = input->fpa;        // FPA of interest
+        psArray *chips = fpa->chips; // Array of chips in output
+        for (int i = 0; i < chips->n; i++) {
+            pmChip *chip = chips->data[i]; // Chip of interest
+            psArray *cells = chip->cells; // Array of cells
+            int culled = 0;             // Number of culled cells
+            for (int j = 0; j < cells->n; j++) {
+                pmCell *cell = cells->data[j];
+                pmHDU *hdu = pmHDUGetLowest(fpa, chip, cell); // HDU for cell
+                if (!hdu || hdu->blankPHU) {
+                    cell->data_exists = false;
+                    cell->file_exists = false;
+                    culled++;
+		    if (cell->concepts) {
+			psFree(cell->concepts);
+			cell->concepts = NULL;
+		    }
+                }
+            }
+            if (culled == cells->n) {
+                chip->data_exists = false;
+                chip->file_exists = false;
+		if (chip->concepts) {
+		    psFree(chip->concepts);
+		    chip->concepts = NULL;
+		}
+            }
+        }
+    }
+
+    // Count the cells
+    {
+        int numCells = 0;               // Number of cells
+        pmFPAfile *input = pmFPAfileSelectSingle(config->files, "PPMERGE.INPUT", 0); // Representative file
+        pmFPA *fpa = input->fpa;        // FPA for file
+        psArray *chips = fpa->chips; // Array of chips
+        for (int i = 0; i < chips->n; i++) {
+            pmChip *chip = chips->data[i]; // Chip of interest
+            psArray *cells = chip->cells; // Array of cells
+            for (int j = 0; j < cells->n; j++) {
+                pmCell *cell = cells->data[j]; // Cell of interest
+                pmHDU *hdu = pmHDUGetLowest(fpa, chip, cell); // HDU that would have data
+                if (hdu && !hdu->blankPHU) {
+                    numCells++;
+                }
+            }
+        }
+
+        psMetadataAddS32(config->arguments, PS_LIST_TAIL, "INPUTS.CELLS", 0, "Number of cells in input",
+                         numCells);
+    }
+
+    // Output image
+    pmFPA *fpa = pmFPAConstruct(config->camera, config->cameraName); // FPA to contain the output
+    if (!fpa) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to construct an FPA from camera configuration.");
+        psFree(phuView);
+        return false;
+    }
+
+    psString outName = ppMergeOutputFile(config); // Name of output file
+
+    pmFPAfileType fileType = PM_FPA_FILE_NONE; // Type of output file
+    switch (type) {
+      case PPMERGE_TYPE_BIAS:
+        fileType = PM_FPA_FILE_IMAGE;
+        break;
+      case PPMERGE_TYPE_DARK:
+        fileType = PM_FPA_FILE_DARK;
+        break;
+      case PPMERGE_TYPE_MASK:
+        fileType = PM_FPA_FILE_MASK;
+        break;
+      case PPMERGE_TYPE_SHUTTER:
+        fileType = PM_FPA_FILE_IMAGE;
+        break;
+      case PPMERGE_TYPE_FLAT:
+        fileType = PM_FPA_FILE_IMAGE;
+        break;
+      case PPMERGE_TYPE_FRINGE:
+        fileType = PM_FPA_FILE_FRINGE;
+      default:
+        psAbort("Unknown frame type: %x", type);
+    }
+
+    if (!outputFile(config, outName, fileType, "Merged detrend", format, phuView)) {
+        psFree(outName);
+        psFree(phuView);
+        return false;
+    }
+    psFree(outName);
+
+    if (!outputFile(config, "PPMERGE.OUTPUT.SIGMA", PM_FPA_FILE_IMAGE, "Merge sigma", format, phuView)) {
+        psFree(phuView);
+        return false;
+    }
+
+    if (!outputFile(config, "PPMERGE.OUTPUT.COUNT", PM_FPA_FILE_IMAGE, "Merged count", format, phuView)) {
+        psFree(phuView);
+        return false;
+    }
+
+    psFree(phuView);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeErrorCodes.c.in
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeErrorCodes.c.in	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeErrorCodes.c.in	(revision 22157)
@@ -0,0 +1,26 @@
+/*
+ * The line
+    { PPMERGE_ERR_$X{ErrorCode}, "$X{ErrorDescription}"},
+ * (without the Xs)
+ * will be replaced by values from errorCodes.dat
+ */
+#include "pslib.h"
+#include "ppMergeErrorCodes.h"
+
+void ppMergeErrorRegister(void)
+{
+    static psErrorDescription errors[] = {
+       { PPMERGE_ERR_BASE, "First value we use; lower values belong to psLib" },
+       { PPMERGE_ERR_${ErrorCode}, "${ErrorDescription}"},
+    };
+    static int nerror = PPMERGE_ERR_NERROR - PPMERGE_ERR_BASE; // number of values in enum
+
+    for (int i = 0; i < nerror; i++) {
+       psErrorDescription *tmp = psAlloc(sizeof(psErrorDescription));
+       p_psMemSetPersistent(tmp, true);
+       *tmp = errors[i];
+       psErrorRegister(tmp, 1);
+       psFree(tmp);			/* it's on the internal list */
+    }
+    nerror = 0;			                // don't register more than once
+}
Index: /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeErrorCodes.dat
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeErrorCodes.dat	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeErrorCodes.dat	(revision 22157)
@@ -0,0 +1,10 @@
+#
+# This file is used to generate ppMergeErrorClasses.h
+#
+BASE = 700		First value we use; lower values belong to psLib
+# these errors correspond to standard exit conditions
+ARGUMENTS               Incorrect arguments
+SYS                     System error
+CONFIG                  Problem in configure files
+PROG                    Programming error
+DATA                    invalid data
Index: /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeErrorCodes.h.in
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeErrorCodes.h.in	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeErrorCodes.h.in	(revision 22157)
@@ -0,0 +1,18 @@
+#if !defined(PPMERGE_ERROR_CODES_H)
+#define PPMERGE_ERROR_CODES_H
+/*
+ * The line
+ *  PPMERGE_ERR_$X{ErrorCode},
+ * (without the X)
+ *
+ * will be replaced by values from errorCodes.dat
+ */
+typedef enum {
+    PPMERGE_ERR_BASE = 512,
+    PPMERGE_ERR_${ErrorCode},
+    PPMERGE_ERR_NERROR
+} ppMergeErrorCode;
+
+void ppMergeErrorRegister(void);
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeFiles.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeFiles.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeFiles.c	(revision 22157)
@@ -0,0 +1,214 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppMerge.h"
+
+const char *allFiles[] = { "PPMERGE.INPUT", "PPMERGE.INPUT.MASK", "PPMERGE.INPUT.WEIGHT",
+                           "PPMERGE.OUTPUT", "PPMERGE.OUTPUT.COUNT", "PPMERGE.OUTPUT.SIGMA",
+                           NULL };      // All files
+const char *inputFiles[] = { "PPMERGE.INPUT", "PPMERGE.INPUT.MASK", "PPMERGE.INPUT.WEIGHT",
+                             NULL };    // Input files
+const char *outputFiles[] = { "PPMERGE.OUTPUT", "PPMERGE.OUTPUT.COUNT", "PPMERGE.OUTPUT.SIGMA",
+                              NULL };   // Output files
+
+// Select file list based on enum
+static const char **selectFiles(ppMergeFiles files)
+{
+    switch (files) {
+      case PPMERGE_FILES_ALL:    return allFiles;
+      case PPMERGE_FILES_INPUT:  return inputFiles;
+      case PPMERGE_FILES_OUTPUT: return outputFiles;
+      default:
+        psAbort("Invalid file option");
+    }
+    return NULL;
+}
+
+bool ppMergeFileReadInput(pmConfig *config, pmReadout *readout, int num, int rows)
+{
+    pmFPAfile *file = pmFPAfileSelectSingle(config->files, "PPMERGE.INPUT", num);
+    if (!pmReadoutReadChunk(readout, file->fits, 0, rows, 0, config)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read readout.");
+        return false;
+    }
+    bool mdok;          // Status of MD lookup
+    if (psMetadataLookupBool(&mdok, config->arguments, "INPUTS.MASKS")) {
+        pmFPAfile *file = pmFPAfileSelectSingle(config->files, "PPMERGE.INPUT.MASK", num);
+        if (!pmReadoutReadChunkMask(readout, file->fits, 0, rows, 0, config)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to read readout mask.");
+            return false;
+        }
+    }
+    if (psMetadataLookupBool(&mdok, config->arguments, "INPUTS.WEIGHTS")) {
+        pmFPAfile *file = pmFPAfileSelectSingle(config->files, "PPMERGE.INPUT.WEIGHT", num);
+        if (!pmReadoutReadChunkWeight(readout, file->fits, 0, rows, 0, config)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to read readout weight.");
+            return false;
+        }
+    }
+    return true;
+}
+
+bool ppMergeFileOpenInput(pmConfig *config, const pmFPAview *view, int num)
+{
+    {
+        pmFPAfile *input = pmFPAfileSelectSingle(config->files, "PPMERGE.INPUT", num);
+        pmFPAview *fileView = pmFPAviewForLevel(input->fileLevel, view);
+        if (!pmFPAfileOpen(input, fileView, config)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to open image file %d", num);
+            psFree(fileView);
+            return false;
+        }
+        psFree(fileView);
+    }
+    bool mdok;          // Status of MD lookup
+    if (psMetadataLookupBool(&mdok, config->arguments, "INPUTS.MASKS")) {
+        pmFPAfile *mask = pmFPAfileSelectSingle(config->files, "PPMERGE.INPUT.MASK", num); // Mask file
+        pmFPAview *fileView = pmFPAviewForLevel(mask->fileLevel, view);
+        if (!pmFPAfileOpen(mask, fileView, config)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to open mask file %d", num);
+            psFree(fileView);
+            return false;
+        }
+        psFree(fileView);
+    }
+    if (psMetadataLookupBool(&mdok, config->arguments, "INPUTS.WEIGHTS")) {
+        pmFPAfile *weight = pmFPAfileSelectSingle(config->files, "PPMERGE.INPUT.WEIGHT", num); // Weight file
+        pmFPAview *fileView = pmFPAviewForLevel(weight->fileLevel, view);
+        if (!pmFPAfileOpen(weight, fileView, config)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to open weight file %d", num);
+            psFree(fileView);
+            return false;
+        }
+        psFree(fileView);
+    }
+    return true;
+}
+
+psArray *ppMergeFileDataLevel(const pmConfig *config, const char *name, pmFPALevel level)
+{
+    assert(config);
+    assert(name);
+
+    int numFiles = psMetadataLookupS32(NULL, config->arguments, "INPUTS.NUM"); // Number of input files
+    assert(numFiles > 0);
+    psArray *files = psArrayAlloc(numFiles); // Files of interest
+    for (int i = 0; i < numFiles; i++) {
+        pmFPAfile *file = pmFPAfileSelectSingle(config->files, name, i); // Image file
+        file->dataLevel = level;
+        file->freeLevel = level;
+        files->data[i] = psMemIncrRefCounter(file);
+    }
+    return files;
+}
+
+bool ppMergeFileActivate(const pmConfig *config, ppMergeFiles files, bool state)
+{
+    assert(config);
+
+    bool mdok;                          // Status of MD lookup
+    bool haveMasks = psMetadataLookupBool(&mdok, config->arguments, "INPUTS.MASKS"); // Do we have masks?
+    bool haveWeights = psMetadataLookupBool(&mdok, config->arguments, "INPUTS.WEIGHTS"); // Got weights?
+
+    const char **fileList = selectFiles(files); // Files to activate
+    for (int i = 0; fileList[i] != NULL; i++) {
+        if (!haveMasks && strcmp(fileList[i], "PPMERGE.INPUT.MASK") == 0) {
+            continue;
+        }
+        if (!haveWeights && strcmp(fileList[i], "PPMERGE.INPUT.WEIGHT") == 0) {
+            continue;
+        }
+        psString name = NULL;           // Name of file
+        if (strcmp(fileList[i], "PPMERGE.OUTPUT") == 0) {
+            name = ppMergeOutputFile(config);
+        } else {
+            name = psStringCopy(fileList[i]);
+        }
+
+        if (!pmFPAfileActivate(config->files, state, name)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to activate file %s", name);
+            psFree(name);
+            return false;
+        }
+        psFree(name);
+    }
+
+    return true;
+}
+
+
+
+psArray *ppMergeFileActivateSingle(const pmConfig *config, ppMergeFiles files, bool state, int num)
+{
+    assert(config);
+
+    bool mdok;                          // Status of MD lookup
+    bool haveMasks = psMetadataLookupBool(&mdok, config->arguments, "INPUTS.MASKS"); // Do we have masks?
+    bool haveWeights = psMetadataLookupBool(&mdok, config->arguments, "INPUTS.WEIGHTS"); // Got weights?
+
+    psList *list = psListAlloc(NULL);   // List of files
+    const char **fileList = selectFiles(files); // Files to activate
+    for (int i = 0; fileList[i] != NULL; i++) {
+        if (!haveMasks && strcmp(fileList[i], "PPMERGE.INPUT.MASK") == 0) {
+            continue;
+        }
+        if (!haveWeights && strcmp(fileList[i], "PPMERGE.INPUT.WEIGHT") == 0) {
+            continue;
+        }
+
+        psString name = NULL;           // Name of file
+        if (strcmp(fileList[i], "PPMERGE.OUTPUT") == 0) {
+            name = ppMergeOutputFile(config);
+        } else {
+            name = psStringCopy(fileList[i]);
+        }
+
+        pmFPAfile *file = pmFPAfileActivateSingle(config->files, state, name, num); // Activated file
+        psFree(name);
+        psListAdd(list, PS_LIST_TAIL, file);
+    }
+
+    psArray *array = psListToArray(list);
+    psFree(list);
+
+    return array;
+}
+
+
+psString ppMergeOutputFile(const pmConfig *config)
+{
+    ppMergeType type = psMetadataLookupS32(NULL, config->arguments, "TYPE"); // Type of frame
+    const char *outSuffix = NULL;         // Suffix for output file
+    switch (type) {
+      case PPMERGE_TYPE_BIAS:
+        outSuffix = "BIAS";
+        break;
+      case PPMERGE_TYPE_DARK:
+        outSuffix = "DARK";
+        break;
+      case PPMERGE_TYPE_MASK:
+        outSuffix = "MASK";
+        break;
+      case PPMERGE_TYPE_SHUTTER:
+        outSuffix = "SHUTTER";
+        break;
+      case PPMERGE_TYPE_FLAT:
+        outSuffix = "FLAT";
+        break;
+      case PPMERGE_TYPE_FRINGE:
+        outSuffix = "FRINGE";
+      default:
+        psAbort("Unknown frame type: %x", type);
+    }
+
+    psString outName = NULL;            // Name of output file
+    psStringAppend(&outName, "PPMERGE.OUTPUT.%s", outSuffix);
+
+    return outName;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeLoop.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeLoop.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeLoop.c	(revision 22157)
@@ -0,0 +1,369 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+#include <pslib.h>
+#include <psmodules.h>
+#include <ppStats.h>
+
+#include "ppMerge.h"
+
+bool ppMergeLoop(pmConfig *config)
+{
+    assert(config);
+
+    psMetadata *arguments = config->arguments; // Arguments
+    ppMergeType type = psMetadataLookupS32(NULL, config->arguments, "TYPE"); // Type of frame
+    int numFiles = psMetadataLookupS32(NULL, arguments, "INPUTS.NUM"); // Number of input files
+    bool mdok;                          // Status of MD lookup
+    bool haveMasks = psMetadataLookupBool(&mdok, arguments, "INPUTS.MASKS"); // Do we have masks?
+    bool haveWeights = psMetadataLookupBool(&mdok, arguments, "INPUTS.WEIGHTS"); // Do we have weights?
+
+    psArray *inputs = ppMergeFileDataLevel(config, "PPMERGE.INPUT", PM_FPA_LEVEL_READOUT); // Input images
+    psArray *masks = NULL, *weights = NULL; // Input masks and weights
+    if (haveMasks) {
+        masks = ppMergeFileDataLevel(config, "PPMERGE.INPUT.MASK", PM_FPA_LEVEL_READOUT);
+    }
+    if (haveWeights) {
+        weights = ppMergeFileDataLevel(config, "PPMERGE.INPUT.WEIGHT", PM_FPA_LEVEL_READOUT);
+    }
+
+    // General combination parameters
+    int rows = psMetadataLookupS32(NULL, arguments, "ROWS"); // Number of rows to read per chunk
+    int iter = psMetadataLookupS32(NULL, arguments, "ITER"); // Number of rejection iterations
+    float rej = psMetadataLookupF32(NULL, arguments, "REJ"); // Rejection level
+    float fraclow = psMetadataLookupF32(NULL, arguments, "FRACLOW"); // Reject fraction of low pixels
+    float frachigh = psMetadataLookupF32(NULL, arguments, "FRACHIGH"); // Reject fraction of hi pixels
+    int nKeep = psMetadataLookupS32(NULL, arguments, "NKEEP"); // Minimum number of values to keep
+    psStatsOptions combineStat = psMetadataLookupS32(NULL, arguments, "COMBINE"); // Combination statistic
+    bool useWeights = psMetadataLookupBool(NULL, arguments, "WEIGHTS"); // Use weights?
+
+    // Fringe parameters
+    int fringeNum = psMetadataLookupS32(NULL, arguments, "FRINGE.NUM"); // Number of fringe points
+    int fringeSize = psMetadataLookupS32(NULL, arguments, "FRINGE.SIZE"); // Size of fringe regions
+    int fringeSmoothX = psMetadataLookupS32(NULL, arguments, "FRINGE.XSMOOTH"); // Smoothing regions in x
+    int fringeSmoothY = psMetadataLookupS32(NULL, arguments, "FRINGE.YSMOOTH"); // Smoothing regions in y
+
+    // set the mask and mark bit values based on the named masks
+    psMaskType maskVal;
+    psMaskType markVal;
+    if (!pmConfigMaskSetBits (&maskVal, &markVal, config)) {
+	psError (PS_ERR_UNKNOWN, true, "Unable to define the mask bit values");
+	return false;
+    }
+
+    pmCombineParams *combination = pmCombineParamsAlloc(combineStat); // Combination parameters
+    combination->maskVal = maskVal;
+    combination->blank = pmConfigMaskGet("BLANK", config);
+    combination->nKeep = nKeep;
+    combination->fracHigh = frachigh;
+    combination->fracLow = fraclow;
+    combination->iter = iter;
+    combination->rej = rej;
+    combination->weights = useWeights;
+
+    psMetadata *stats = NULL;           // Statistics for output
+    if (psMetadataLookup(config->arguments, "STATS.NAME")) {
+        stats = psMetadataAlloc();
+        psMetadataAddMetadata(config->arguments, PS_LIST_TAIL, "STATS.DATA", 0, "Statistics output", stats);
+    }
+
+    pmFPAview *view = pmFPAviewAlloc(0); // View to component of interest
+
+    // Retrieve data placed on analysis
+    psVector *scales = NULL, *zeros = NULL; // Scale and zeroes for combination
+    psArray *shutters = NULL;           // Shutter correction data
+    switch (type) {
+      case PPMERGE_TYPE_FRINGE:
+        zeros = psMetadataLookupPtr(NULL, arguments, "ZEROS");
+        if (!zeros) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find ZEROS");
+            goto ERROR;
+        }
+        // Flow through
+      case PPMERGE_TYPE_FLAT:
+        scales = psMetadataLookupPtr(NULL, arguments, "SCALES");
+        if (!scales) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find SCALES");
+            goto ERROR;
+        }
+        break;
+      case PPMERGE_TYPE_SHUTTER:
+        shutters = psMetadataLookupPtr(NULL, arguments, "SHUTTER");
+        if (!shutters) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find SHUTTER");
+            goto ERROR;
+        }
+        break;
+      case PPMERGE_TYPE_MASK:
+      case PPMERGE_TYPE_BIAS:
+      case PPMERGE_TYPE_DARK:
+        break;
+      default:
+        psAbort("Should never get here.");
+    }
+
+    // Dark parameters
+    psArray *darkOrdinates = psMetadataLookupPtr(NULL, arguments, "DARK.ORDINATES"); // Dark info
+    psString darkNorm = psMetadataLookupStr(&mdok, arguments, "DARK.NORM"); // Dark normalisation
+
+
+    if (!ppMergeFileActivate(config, PPMERGE_FILES_ALL, true)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to activate files.");
+        goto ERROR;
+    }
+    psString outName = ppMergeOutputFile(config); // Name of output file
+    pmFPAfile *output = psMetadataLookupPtr(NULL, config->files, outName); // Output file
+    psFree(outName);
+    assert(output && output->fpa);
+    pmFPA *outFPA = output->fpa;        // Output FPA
+    int cellNum = 0;                    // Index of cell
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+        goto ERROR;
+    }
+    pmChip *outChip;                    // Chip of interest
+    while ((outChip = pmFPAviewNextChip(view, outFPA, 1))) {
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+            goto ERROR;
+        }
+        pmCell *outCell;                // Cell of interest
+        while ((outCell = pmFPAviewNextCell(view, outFPA, 1))) {
+            if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+                goto ERROR;
+            }
+
+            pmHDU *hdu = pmHDUGetLowest(outFPA, outChip, outCell); // HDU for cell
+            if (!hdu || hdu->blankPHU) {
+                // No data here
+                continue;
+            }
+
+            pmReadout *outRO = pmReadoutAlloc(outCell);
+
+            psArray *readouts = psArrayAlloc(numFiles); // Input readouts
+            for (int i = 0; i < numFiles; i++) {
+                // We need to do some of the opening ourselves
+                if (!ppMergeFileOpenInput(config, view, i)) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to open file %d", i);
+                    goto ERROR;
+                }
+
+                pmFPAfile *input = pmFPAfileSelectSingle(config->files, "PPMERGE.INPUT", i);
+                pmCell *inCell = pmFPAviewThisCell(view, input->fpa); // Input cell
+                readouts->data[i] = pmReadoutAlloc(inCell);
+            }
+
+            float shutterRef = NAN;     // Reference shutter correction
+            if (type == PPMERGE_TYPE_SHUTTER) {
+                shutterRef = pmShutterCorrectionReference(shutters->data[cellNum]);
+            }
+
+            // Read convolutions by chunks
+            bool more = true;               // More to read?
+            for (int numChunk = 0; more; numChunk++) {
+                psTrace("ppStack", 2, "Initial stack of chunk %d....\n", numChunk);
+                for (int i = 0; i < numFiles; i++) {
+                    pmReadout *inRO = readouts->data[i]; // Input readout
+
+                    // Read a chunk from a file
+                    #define READ_CHUNK(NAME,TYPE) { \
+                        pmFPAfile *file = pmFPAfileSelectSingle(config->files, NAME, i); \
+                        if (!pmReadoutReadChunk##TYPE(inRO, file->fits, 0, rows, 0, config)) { \
+                            psError(PS_ERR_IO, false, "Unable to read chunk %d for file %s %d", \
+                                    numChunk, NAME, i); \
+                            psFree(readouts); \
+                            psFree(outRO); \
+                            goto ERROR; \
+                        } \
+                    }
+
+                    READ_CHUNK("PPMERGE.INPUT", /* Blank */);
+                    if (haveMasks) {
+                        READ_CHUNK("PPMERGE.INPUT.MASK", Mask);
+                    }
+                    if (haveWeights) {
+                        READ_CHUNK("PPMERGE.INPUT.WEIGHT", Weight);
+                    }
+                }
+
+                switch (type) {
+                  case PPMERGE_TYPE_SHUTTER:
+                    if (!pmShutterCorrectionGenerate(outRO, NULL, readouts, shutterRef,
+                                                     shutters->data[cellNum], iter, rej, maskVal)) {
+                        psFree(readouts);
+                        psFree(outRO);
+                        goto ERROR;
+                    }
+                    break;
+                  case PPMERGE_TYPE_DARK:
+                    if (!pmDarkCombine(outCell, readouts, darkOrdinates, darkNorm, iter, rej, maskVal)) {
+                        psFree(readouts);
+                        psFree(outRO);
+                        goto ERROR;
+                    }
+                    break;
+                  case PPMERGE_TYPE_BIAS:
+                  case PPMERGE_TYPE_FLAT:
+                  case PPMERGE_TYPE_FRINGE:
+                    if (!pmReadoutCombine(outRO, readouts, zeros, scales, combination)) {
+                        psFree(readouts);
+                        psFree(outRO);
+                        goto ERROR;
+                    }
+                    break;
+                  default:
+                    psAbort("Should never get here.");
+                }
+
+
+                for (int i = 0; i < numFiles && more; i++) {
+                    pmReadout *inRO = readouts->data[i];
+
+                    // Check to see if there's more chunks to read
+                    #define MORE_CHUNK(NAME,TYPE) { \
+                        pmFPAfile *file = pmFPAfileSelectSingle(config->files, NAME, i); \
+                        more &= pmReadoutMore##TYPE(inRO, file->fits, 0, rows, config); \
+                    }
+
+                    MORE_CHUNK("PPMERGE.INPUT", /* Blank */);
+                    if (haveMasks) {
+                        MORE_CHUNK("PPMERGE.INPUT.MASK", Mask);
+                    }
+                    if (haveWeights) {
+                        MORE_CHUNK("PPMERGE.INPUT.WEIGHT", Weight);
+                    }
+                }
+            }
+
+            // Get list of cells for concepts averaging
+            psList *inCells = psListAlloc(NULL); // List of cells
+            for (int i = 0; i < numFiles; i++) {
+                pmReadout *readout = readouts->data[i]; // Readout of interest
+                psListAdd(inCells, PS_LIST_TAIL, readout->parent);
+            }
+            if (!pmConceptsAverageCells(outCell, inCells, NULL, NULL, true)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to average cell concepts.");
+                psFree(inCells);
+                psFree(outRO);
+                goto ERROR;
+            }
+            psFree(inCells);
+
+            psFree(readouts);
+
+            // Plug supplementary images into their own FPAs
+            {
+                pmCell *countsCell = pmFPAfileThisCell(config->files, view, "PPMERGE.OUTPUT.COUNT");
+                pmReadout *countsRO = pmReadoutAlloc(countsCell); // Readout with count of inputs per pixel
+                psImage *counts = psMetadataLookupPtr(NULL, outRO->analysis, PM_READOUT_STACK_ANALYSIS_COUNT);
+                countsRO->image = psImageCopy(countsRO->image, counts, PS_TYPE_F32);
+                psMetadataRemoveKey(outRO->analysis, PM_READOUT_STACK_ANALYSIS_COUNT);
+                countsRO->data_exists = countsCell->data_exists = countsCell->parent->data_exists = true;
+                psFree(countsRO);
+
+                pmCell *sigmaCell = pmFPAfileThisCell(config->files, view, "PPMERGE.OUTPUT.SIGMA");
+                pmReadout *sigmaRO = pmReadoutAlloc(sigmaCell); // Readout with stdev per pixel
+                psImage *sigma = psMetadataLookupPtr(NULL, outRO->analysis, PM_READOUT_STACK_ANALYSIS_SIGMA);
+                sigmaRO->image = psImageCopy(sigmaRO->image, sigma, PS_TYPE_F32);
+                psMetadataRemoveKey(outRO->analysis, PM_READOUT_STACK_ANALYSIS_SIGMA);
+                sigmaRO->data_exists = sigmaCell->data_exists = sigmaCell->parent->data_exists = true;
+                psFree(sigmaRO);
+            }
+
+            // Measure the fringes for this cell
+            //
+            // XXX Need to deal with multiple components: we will do this by building up the components
+            // Read the existing fringe measurements
+            // Use existing regions to measure fringe statistics
+            // Add the new fringe measurements to the existing fringe measurements.
+            // Write the appended fringe measurements.
+            // Read in the "output" file to get the existing components.
+            // Put the new readout into the cell after the existing readouts.
+            if (type == PPMERGE_TYPE_FRINGE && outRO) {
+                pmFringeRegions *regions = pmFringeRegionsAlloc(fringeNum, fringeSize, fringeSize,
+                                                                fringeSmoothX, fringeSmoothY);
+                pmFringeStats *fringe = pmFringeStatsMeasure(regions, outRO, maskVal);
+                psFree(regions);
+                if (!fringe) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to measure fringe statistics.\n");
+                    psFree(outRO);
+                    goto ERROR;
+                }
+
+                psArray *fringes = psArrayAlloc(1); // Array of fringes
+                fringes->data[0] = fringe;
+
+                pmFringesFormat(outCell, NULL, fringes);
+                psFree(fringes);        // Drop reference
+            }
+
+            if (!ppStatsFPA(stats, outFPA, view, maskVal, config)) {
+                psError(PS_ERR_UNKNOWN, true, "Unable to generate stats for image.");
+                goto ERROR;
+            }
+
+            psFree(outRO);
+            cellNum++;
+
+            if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+                goto ERROR;
+            }
+        }
+
+        if (outChip->data_exists) {
+            psList *inChips = psListAlloc(NULL);
+            for (int i=0; i < numFiles; i++) {
+                pmChip *chip = pmFPAviewThisChip(view, ((pmFPAfile *)inputs->data[i])->fpa);
+                psListAdd(inChips, PS_LIST_TAIL, chip);
+            }
+            if (!pmConceptsAverageChips(outChip, inChips, true)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to average Chip concepts.");
+                psFree(inChips);
+                goto ERROR;
+            }
+            psFree(inChips);
+        }
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+            goto ERROR;
+        }
+    }
+
+    // Get list of FPAs for concepts averaging
+    psList *inFPAs = psListAlloc(NULL); // List of FPAs
+    for (int i = 0; i < numFiles; i++) {
+        pmFPAfile *input = inputs->data[i]; // Input file
+        psListAdd(inFPAs, PS_LIST_TAIL, input->fpa);
+    }
+    if (!pmConceptsAverageFPAs(output->fpa, inFPAs)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to average FPA concepts.");
+        psFree(inFPAs);
+        goto ERROR;
+    }
+    psFree(inFPAs);
+
+
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+        goto ERROR;
+    }
+
+    psFree(view);
+    psFree(combination);
+    psFree(inputs);
+    psFree(masks);
+    psFree(weights);
+    psFree(stats);
+    return true;
+
+ERROR:
+    psFree(view);
+    psFree(combination);
+    psFree(inputs);
+    psFree(masks);
+    psFree(weights);
+    psFree(stats);
+    return false;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeMask.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeMask.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeMask.c	(revision 22157)
@@ -0,0 +1,469 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include <pslib.h>
+#include <psmodules.h>
+#include <ppStats.h>
+
+#include "ppMerge.h"
+
+static bool mergeMask(pmConfig *config, // Configuration
+                      const pmFPAview *view, // View to chip
+                      bool writeOut,     // Write output?
+                      psRandom *rng,    // Random number generator
+                      psMetadata *stats // Statistics output
+    )
+{
+    assert(config);
+    assert(view);
+    assert(view->chip != -1 && view->cell == -1 && view->readout == -1);
+
+    bool mdok;                          // Status of MD lookup
+    int numFiles = psMetadataLookupS32(NULL, config->arguments, "INPUTS.NUM"); // Number of input files
+    psStatsOptions meanStat = psMetadataLookupS32(NULL, config->arguments, "MEAN"); // Statistic for mean
+    psStatsOptions stdevStat = psMetadataLookupS32(NULL, config->arguments, "STDEV"); // Statistic for stdev
+    int sample = psMetadataLookupS32(NULL, config->arguments, "SAMPLE"); // Size of sample for statistics
+    bool chipStats = psMetadataLookupBool(&mdok, config->arguments, "MASK.CHIPSTATS"); // Statistics on chip?
+    float maskSuspect = psMetadataLookupF32(NULL, config->arguments, "MASK.SUSPECT"); // Threshold for suspect pixels
+    float maskBad = psMetadataLookupF32(NULL, config->arguments, "MASK.BAD"); // Threshold for bad pixels
+    pmMaskIdentifyMode maskMode = psMetadataLookupS32(NULL, config->arguments, "MASK.MODE"); // Mode for identifying bad pixels
+    int maskGrow = psMetadataLookupS32(NULL, config->arguments, "MASK.GROW"); // Radius to grow mask
+
+    bool smoothSuspect = psMetadataLookupBool(&mdok, config->arguments, "MASK.SMOOTH.SUSPECT"); // Radius to grow mask
+    float smoothScale = psMetadataLookupF32(&mdok, config->arguments, "MASK.SMOOTH.SCALE"); // Radius to grow mask
+
+    psMaskType markVal;
+    psMaskType maskValRaw;
+    if (!pmConfigMaskSetBits (&maskValRaw, &markVal, config)) {
+	psError (PS_ERR_UNKNOWN, true, "Unable to define the mask bit values");
+	return false;
+    }
+
+    char *maskOutName = psMetadataLookupStr (&mdok, config->arguments, "MASK.SET.VALUE");
+    psMaskType maskValOut = pmConfigMaskGet (maskOutName, config);
+    if (!maskValOut) {
+	psError (PS_ERR_UNKNOWN, true, "Undefined output mask bit value");
+	return false;
+    }
+    
+    psStats *statistics = psStatsAlloc(meanStat | stdevStat); // Statistics for background
+
+    psString outName = ppMergeOutputFile(config); // Name of output file
+    pmChip *outChip = pmFPAfileThisChip(config->files, view, outName); // Output chip
+    psFree(outName);
+    
+    int numCells = 1;
+    if (chipStats) {
+	// count the number of active cells for this chip:
+	numCells = 0;
+	for (int i = 0; i < outChip->cells->n; i++) {
+	    pmCell *cell = outChip->cells->data[i];
+	    if (!cell->process) continue;
+	    numCells ++;
+	}
+    }
+
+    // For each input file, get the statistics, which can be calculated at the chip or cell levels
+    psVector *values = psVectorAlloc(sample, PS_TYPE_F32); // Pixel values for statistics
+    pmFPAview *inView = pmFPAviewAlloc(0); // View for input
+    for (int i = 0; i < numFiles; i++) {
+        pmFPAfileActivate(config->files, false, NULL);
+        psArray *files = ppMergeFileActivateSingle(config, PPMERGE_FILES_INPUT, true, i); // Input files
+        pmFPAfile *input = files->data[0]; // Input file
+        psFree(files);
+        pmFPA *inFPA = input->fpa;  // Input FPA
+        *inView = *view;
+
+        int valueIndex = 0;             // Index for vector of pixel values
+
+        pmCell *inCell;                 // Input cell
+        while ((inCell = pmFPAviewNextCell(inView, inFPA, 1))) {
+
+	    // the output FPA structure carries the information about which cells to process
+            pmCell *outCell = pmFPAfileThisCell(config->files, inView, "PPMERGE.OUTPUT.MASK"); // Output cell
+	    if (!outCell->process) continue;
+
+            pmHDU *hdu = pmHDUFromCell(inCell); // HDU for cell
+            if (!hdu || hdu->blankPHU) {
+                // No data here
+                continue;
+            }
+            psTrace("ppMerge", 1, "Getting suspect pixels for file %d chip %d cell %d",
+                    i, inView->chip, inView->cell);
+
+            if (!pmFPAfileIOChecks(config, inView, PM_FPA_BEFORE)) {
+                psFree(inView);
+                goto MERGE_MASK_ERROR;
+            }
+
+            if (!ppMergeFileOpenInput(config, view, i)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to open file %d", i);
+                psFree(inView);
+                goto MERGE_MASK_ERROR;
+            }
+
+            if (inCell->readouts->n > 1) {
+                psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                        "File %d chip %d cell %d contains more than one readout (%ld)",
+                        i, inView->chip, inView->cell, inCell->readouts->n);
+                psFree(inView);
+                goto MERGE_MASK_ERROR;
+            }
+
+            pmReadout *readout;
+            if (inCell->readouts && inCell->readouts->n == 1) {
+                readout = psMemIncrRefCounter(inCell->readouts->data[0]); // Input readout
+            } else {
+                readout = pmReadoutAlloc(inCell);
+            }
+
+            if (!ppMergeFileReadInput(config, readout, i, 0)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to read readout %d", i);
+                psFree(inView);
+                psFree(readout);
+                goto MERGE_MASK_ERROR;
+            }
+
+            pmReadout *outRO = NULL;    // Output readout
+            if (outCell->readouts && outCell->readouts->n == 1) {
+                outRO = psMemIncrRefCounter(outCell->readouts->data[0]);
+            } else {
+                outRO = pmReadoutAlloc(outCell);
+            }
+            psImage *outMask = outRO->mask;    // Output mask image (for iterative generation of mask)
+
+            psImage *image = readout->image, *mask = readout->mask; // Image and mask
+            int numCols = readout->image->numCols, numRows = readout->image->numRows; // Image size
+            int numPix = numCols * numRows; // Number of pixels
+            int num = PS_MIN(numPix, sample / numCells); // Number of values to add
+            if (!chipStats) {
+                valueIndex = 0;
+            }
+            for (int i = 0; i < num; i++) {
+                int pixel = numPix * psRandomUniform(rng);
+                int x = pixel % numCols;
+                int y = pixel / numCols;
+                if (mask && (mask->data.PS_TYPE_MASK_DATA[y][x] & maskValRaw)) continue;
+		if (outMask && (outMask->data.PS_TYPE_MASK_DATA[y][x] & maskValOut)) continue;
+		if (!isfinite(image->data.F32[y][x])) continue;
+
+                values->data.F32[valueIndex++] = image->data.F32[y][x];
+            }
+
+            if (!chipStats) {
+                values->n = valueIndex;
+                if (!psVectorStats(statistics, values, NULL, NULL, 0)) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to do statistics on readout.");
+                    psFree(inView);
+                    psFree(readout);
+                    goto MERGE_MASK_ERROR;
+                }
+
+		float mean = psStatsGetValue(statistics, meanStat);
+		float stdev = psStatsGetValue(statistics, stdevStat);
+
+		// this function increments the count for each suspect pixel in each input plane
+		// maskValRaw is used to test for valid input pixels
+                if (!pmMaskFlagSuspectPixels(outRO, readout, mean, stdev, maskSuspect, maskValRaw)) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to find suspect values in file %d", i);
+                    psFree(inView);
+                    psFree(readout);
+                    goto MERGE_MASK_ERROR;
+                }
+                pmCellFreeData(inCell);
+
+                if (!pmFPAfileIOChecks(config, inView, PM_FPA_AFTER)) {
+                    psFree(inView);
+                    psFree(readout);
+                    goto MERGE_MASK_ERROR;
+                }
+            }
+            psFree(readout);
+            psFree(outRO);
+        }
+
+        // Additional run through cells if we want chip-level statistics
+        if (chipStats && valueIndex > 0) {
+            values->n = valueIndex;
+            if (!psVectorStats(statistics, values, NULL, NULL, 0)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to do statistics on chip.");
+                goto MERGE_MASK_ERROR;
+            }
+            inView->cell = -1;
+            while ((inCell = pmFPAviewNextCell(inView, inFPA, 1))) {
+
+		// the output FPA structure carries the information about which cells to process
+		pmCell *outCell = pmFPAfileThisCell(config->files, inView, "PPMERGE.OUTPUT.MASK"); // Output cell
+		if (!outCell->process) continue;
+
+                pmHDU *hdu = pmHDUFromCell(inCell); // HDU for cell
+                if (!hdu || hdu->blankPHU) continue;
+
+                pmReadout *readout = inCell->readouts->data[0]; // Readout of interest
+
+                inView->readout = 0;
+                pmReadout *outRO = pmFPAfileThisReadout(config->files, inView, "PPMERGE.OUTPUT.MASK");
+
+		float mean = psStatsGetValue(statistics, meanStat);
+		float stdev = psStatsGetValue(statistics, stdevStat);
+
+		if (!pmMaskFlagSuspectPixels(outRO, readout, mean, stdev, maskSuspect, maskValRaw)) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to find suspect values in file %d", i);
+                    goto MERGE_MASK_ERROR;
+                }
+                pmCellFreeData(inCell);
+
+                inView->readout = -1;
+                if (!pmFPAfileIOChecks(config, inView, PM_FPA_AFTER)) {
+                    psFree(inView);
+                    goto MERGE_MASK_ERROR;
+                }
+            }
+        }
+    }
+    psFree(inView);
+    psFree(statistics); statistics = NULL;
+    psFree(values); values = NULL;
+
+
+    // Another run through the chip to threshold on the suspects
+    pmFPAfileActivate(config->files, false, NULL);
+    if (writeOut) {
+        ppMergeFileActivate(config, PPMERGE_FILES_OUTPUT, true);
+    }
+    pmFPA *outFPA = outChip->parent;    // Output FPA
+    pmCell *outCell;                    // Output cell
+    pmFPAview *outView = pmFPAviewAlloc(0); // View into output FPA
+    *outView = *view;
+    while ((outCell = pmFPAviewNextCell(outView, outFPA, 1))) {
+
+	// skip inactive cells
+	if (!outCell->process) continue;
+
+        pmHDU *hdu = pmHDUFromCell(outCell); // HDU for cell
+        if (!hdu || hdu->blankPHU) continue;
+
+        psTrace("ppMerge", 1, "Getting bad pixels for chip %d cell %d", outView->chip, outView->cell);
+
+        assert(outCell->readouts && outCell->readouts->n == 1);
+        pmReadout *outRO = outCell->readouts->data[0]; // Output readout
+
+	if (smoothSuspect) {
+	    // XXX test output of suspect pixel image
+	    psImage *suspects = psMetadataLookupPtr(NULL, outRO->analysis, PM_MASK_ANALYSIS_SUSPECT); // Suspect img
+	    assert (suspects);
+	    psImageSmooth (suspects, smoothScale, 3); // extend smoothing region to 3-sigma
+	}
+
+	// set the bad pixels to the value 'maskVal'
+        if (!pmMaskIdentifyBadPixels(outRO, maskValOut, maskBad, maskMode)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to mask bad pixels");
+            goto MERGE_MASK_ERROR;
+        }
+
+        // Supplementary outputs
+        {
+            // The counts image is fairly useless, but it preserves the model
+            pmCell *countsCell = pmFPAfileThisCell(config->files, outView, "PPMERGE.OUTPUT.COUNT");
+            pmReadout *countsRO = pmReadoutAlloc(countsCell); // Readout with count of inputs per pixel
+            countsRO->image = psImageAlloc(outRO->mask->numCols, outRO->mask->numRows, PS_TYPE_F32);
+            psImageInit(countsRO->image, numFiles);
+            countsRO->data_exists = countsCell->data_exists = countsCell->parent->data_exists = true;
+            psFree(countsRO);
+
+            pmCell *sigmaCell = pmFPAfileThisCell(config->files, outView, "PPMERGE.OUTPUT.SIGMA");
+            pmReadout *sigmaRO = pmReadoutAlloc(sigmaCell); // Readout with suspect image
+            psImage *suspect = psMetadataLookupPtr(NULL, outRO->analysis, PM_MASK_ANALYSIS_SUSPECT);
+            sigmaRO->image = psImageCopy(sigmaRO->image, suspect, PS_TYPE_F32);
+            psMetadataRemoveKey(outRO->analysis, PM_MASK_ANALYSIS_SUSPECT);
+            sigmaRO->data_exists = sigmaCell->data_exists = sigmaCell->parent->data_exists = true;
+            psFree(sigmaRO);
+        }
+
+        if (maskGrow > 0) {
+            psImage *grown = psImageGrowMask(NULL, outRO->mask, maskValOut, maskGrow, maskValOut); // Grown mask
+            psFree(outRO->mask);
+            outRO->mask = grown;
+        }
+
+        if (writeOut) {
+            if (!pmFPAfileIOChecks(config, outView, PM_FPA_BEFORE)) {
+                psFree(outView);
+                goto MERGE_MASK_ERROR;
+            }
+
+            outRO->data_exists = outCell->data_exists = outChip->data_exists = true;
+
+            // Average concepts
+            psList *cells = psListAlloc(NULL); // List of cells, for concept averaging
+            for (int i = 0; i < numFiles; i++) {
+                pmFPAfile *inFile = pmFPAfileSelectSingle(config->files, "PPMERGE.INPUT", i); // Input file
+                pmCell *inCell = pmFPAviewThisCell(outView, inFile->fpa); // Input cell
+                psListAdd(cells, PS_LIST_TAIL, inCell);
+            }
+            if (!pmConceptsAverageCells(outCell, cells, NULL, NULL, true)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to average cell concepts.");
+                psFree(cells);
+                psFree(outRO);
+                psFree(outView);
+                return false;
+            }
+            psFree(cells);
+
+            // Statistics on the merged cell using a fake image
+            outRO->image = psImageAlloc(outRO->mask->numCols, outRO->mask->numRows, PS_TYPE_F32);
+            psImageInit(outRO->image, 1.0);
+            if (!ppStatsFPA(stats, outRO->parent->parent->parent, outView, maskValOut, config)) {
+                psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to generate stats for image.");
+                psFree(outRO);
+                psFree(outView);
+                return false;
+            }
+            psFree(outRO->image);
+            outRO->image = NULL;
+
+            // Write
+            if (!pmFPAfileIOChecks(config, outView, PM_FPA_AFTER)) {
+                psFree(outView);
+                goto MERGE_MASK_ERROR;
+            }
+        }
+    }
+    psFree(outView);
+
+    ppMergeFileActivate(config, PPMERGE_FILES_ALL, true);
+
+    return true;
+
+
+MERGE_MASK_ERROR:
+    psFree(statistics);
+    psFree(values);
+    return false;
+}
+
+bool ppMergeMask(pmConfig *config)
+{
+    assert(config);
+
+    bool mdok;                          // Status of MD lookup
+    int numFiles = psMetadataLookupS32(NULL, config->arguments, "INPUTS.NUM"); // Number of inputs
+    bool haveMasks = psMetadataLookupBool(&mdok, config->arguments, "INPUTS.MASKS"); // Do we have masks?
+    bool haveWeights = psMetadataLookupBool(&mdok, config->arguments, "INPUTS.WEIGHTS"); // Do we have weights?
+    int iter = psMetadataLookupS32(NULL, config->arguments, "ITER"); // Number of rejection iterations
+
+    PS_ASSERT_INT_POSITIVE(iter, false);
+
+    pmFPAview *view = pmFPAviewAlloc(0); // View to component of interest
+    psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, 0); // Random number generator
+
+    psMetadata *stats = NULL;           // Statistics for output
+    if (psMetadataLookup(config->arguments, "STATS.NAME")) {
+        stats = psMetadataAlloc();
+        psMetadataAddMetadata(config->arguments, PS_LIST_TAIL, "STATS.DATA", 0, "Statistics output", stats);
+    }
+
+    psArray *inputs = ppMergeFileDataLevel(config, "PPMERGE.INPUT", PM_FPA_LEVEL_READOUT); // Input images
+    psFree(inputs);
+    if (haveMasks) {
+        psArray *masks = ppMergeFileDataLevel(config, "PPMERGE.INPUT.MASK", PM_FPA_LEVEL_READOUT);
+        psFree(masks);
+    }
+    if (haveWeights) {
+        psArray *weights = ppMergeFileDataLevel(config, "PPMERGE.INPUT.WEIGHT", PM_FPA_LEVEL_READOUT);
+        psFree(weights);
+    }
+
+    if (!ppMergeFileActivate(config, PPMERGE_FILES_INPUT, true)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to activate files.");
+        goto PPMERGE_MASK_ERROR;
+    }
+    if (!ppMergeFileActivate(config, PPMERGE_FILES_OUTPUT, true)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to activate files.");
+        goto PPMERGE_MASK_ERROR;
+    }
+
+    // XXX this function should use pmConfigMaskReadHeader () to get the named values defined
+    // for the input masks.
+
+    psString outName = ppMergeOutputFile(config); // Name of output file
+    pmFPAfile *output = psMetadataLookupPtr(NULL, config->files, outName); // Output file
+    psFree(outName);
+    assert(output && output->fpa);
+    pmFPA *outFPA = output->fpa;        // Output FPA
+
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+        goto PPMERGE_MASK_ERROR;
+    }
+    pmChip *outChip;                    // Chip of interest
+    while ((outChip = pmFPAviewNextChip(view, outFPA, 1))) {
+
+        if (!outChip->process) continue;
+
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+            goto PPMERGE_MASK_ERROR;
+        }
+
+        for (int i = 0; i < iter; i++) {
+            if (!mergeMask(config, view, (i == iter - 1), rng, stats)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to merge chip %d", view->chip);
+                goto PPMERGE_MASK_ERROR;
+            }
+        }
+
+	if (outChip->data_exists) {
+	    psList *inChips = psListAlloc(NULL);
+	    for (int i=0; i < numFiles; i++) {
+		pmFPAfile *file = pmFPAfileSelectSingle(config->files, "PPMERGE.INPUT", i); // Input file
+		pmChip *chip = pmFPAviewThisChip(view, file->fpa);
+		psListAdd(inChips, PS_LIST_TAIL, chip);
+	    }
+
+	    // XXX I need to call pmConfigMaskWriteHeader for the PHU somewhere, after it is created!
+
+	    if (!pmConceptsAverageChips(outChip, inChips, true)) {
+		psError(PS_ERR_UNKNOWN, false, "Unable to average Chip concepts.");
+		psFree(inChips);
+		goto PPMERGE_MASK_ERROR;
+	    }
+	    psFree(inChips);
+	}
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+            goto PPMERGE_MASK_ERROR;
+        }
+    }
+
+    psList *fpaList = psListAlloc(NULL);// List of FPAs for concept averaging
+    for (int i = 0; i < numFiles; i++) {
+        pmFPAfile *file = pmFPAfileSelectSingle(config->files, "PPMERGE.INPUT", i); // Input file
+        psListAdd(fpaList, PS_LIST_TAIL, file->fpa);
+    }
+    if (!pmConceptsAverageFPAs(outFPA, fpaList)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to average FPA concepts.");
+        psFree(fpaList);
+        goto PPMERGE_MASK_ERROR;
+    }
+    psFree(fpaList);
+
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+        goto PPMERGE_MASK_ERROR;
+    }
+
+    psFree(stats);
+    psFree(view);
+    psFree(rng);
+
+    return true;
+
+PPMERGE_MASK_ERROR:
+    psFree(stats);
+    psFree(view);
+    psFree(rng);
+    return false;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeScaleZero.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeScaleZero.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeScaleZero.c	(revision 22157)
@@ -0,0 +1,221 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppMerge.h"
+
+// Get the scale and zero for each chip of each input
+bool ppMergeScaleZero(pmConfig *config)
+{
+    assert(config);
+
+    ppMergeType type = psMetadataLookupS32(NULL, config->arguments, "TYPE"); // Type of frame
+    int numInputs = psMetadataLookupS32(NULL, config->arguments, "INPUTS.NUM"); // Number of inputs
+    int numCells = psMetadataLookupS32(NULL, config->arguments, "INPUTS.CELLS"); // Number of cells
+    psStatsOptions meanStat = psMetadataLookupS32(NULL, config->arguments, "MEAN"); // Statistic for mean
+    psStatsOptions stdevStat = psMetadataLookupS32(NULL, config->arguments, "STDEV"); // Statistic for stdev
+    int shutterSize = psMetadataLookupS32(NULL, config->arguments, "SHUTTER.SIZE"); // Size of shutter region
+
+    psMaskType maskVal = pmConfigMaskGet("MASK.VALUE", config); // Value to mask
+
+    psVector *gains = NULL;             // Gains for each cell
+    psArray *shutters = NULL;           // Shutter data for each cell
+    psStats *stats = NULL;              // Statistics for background
+    psImage *background = NULL;         // Background measurements per cell per file
+
+    switch (type) {
+      case PPMERGE_TYPE_BIAS:
+      case PPMERGE_TYPE_DARK:
+        // Nothing to measure
+        return true;
+      case PPMERGE_TYPE_FLAT:
+      case PPMERGE_TYPE_FRINGE:
+        gains = psVectorAlloc(numCells, PS_TYPE_F32);
+        background = psImageAlloc(numCells, numInputs, PS_TYPE_F32);
+        psImageInit(background, NAN);
+        stats = psStatsAlloc(meanStat);
+        break;
+      case PPMERGE_TYPE_SHUTTER:
+        shutters = psArrayAlloc(numCells);
+        break;
+      case PPMERGE_TYPE_MASK:
+      default:
+        break;
+    }
+    psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, 0); // Random number generator
+    pmFPAview *view = NULL;             // View into FPA
+
+    for (int i = 0; i < numInputs; i++) {
+        pmFPAfileActivate(config->files, false, NULL);
+        psArray *files = ppMergeFileActivateSingle(config, PPMERGE_FILES_INPUT, true, i); // Activated files
+        pmFPAfile *input = files->data[0]; // Representative file; should be the image (not mask or weight)
+        pmFPA *fpa = input->fpa;        // FPA of interest
+        view = pmFPAviewAlloc(0);       // View to component of interest
+        int cellNum = 0;                // Index for cell
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+            goto ERROR;
+        }
+        pmChip *chip;                   // Chip of interest
+        while ((chip = pmFPAviewNextChip(view, fpa, 1))) {
+            if (!chip->file_exists) {
+                continue;
+            }
+            if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+                goto ERROR;
+            }
+
+            pmCell *cell;               // Cell of interest
+            while ((cell = pmFPAviewNextCell(view, fpa, 1))) {
+                if (!cell->file_exists) {
+                    continue;
+                }
+                if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+                    goto ERROR;
+                }
+
+                if (!cell->data_exists) {
+                    continue;
+                }
+
+                if (cell->readouts->n > 1) {
+                    psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                            "File %d chip %d cell %d contains more than one readout (%ld)",
+                            i, view->chip, view->cell, cell->readouts->n);
+                    goto ERROR;
+                }
+                pmReadout *readout = cell->readouts->data[0]; // Readout of interest
+
+                switch (type) {
+                  case PPMERGE_TYPE_FLAT:
+                  case PPMERGE_TYPE_FRINGE: {
+                      // Extract the gain
+                      float gain = psMetadataLookupF32(NULL, cell->concepts, "CELL.GAIN"); // Cell gain
+                      if (!isfinite(gain)) {
+			// psError(PS_ERR_BAD_PARAMETER_VALUE, false,
+			// "CELL.GAIN for file %d chip %d cell %d is not set.",
+			// i, view->chip, view->cell);
+			// goto ERROR;
+			psWarning ("CELL.GAIN for file %d chip %d cell %d is NaN", i, view->chip, view->cell);
+                      }
+                      gains->data.F32[cellNum] = gain;
+
+                      // Measure the background
+                      if (!psImageBackground(stats, NULL, readout->image, readout->mask, maskVal, rng)) {
+			// psError(PS_ERR_UNKNOWN, false,
+			// "Unable to get statistics for file %d chip %d cell %d",
+			// i, view->chip, view->cell);
+			// goto ERROR;
+			psWarning ("Unable to get statistics for file %d chip %d cell %d", i, view->chip, view->cell);
+			background->data.F32[i][cellNum] = NAN;
+                      } else {
+			background->data.F32[i][cellNum] = psStatsGetValue(stats, meanStat);
+		      }
+                      break;
+                  }
+                  case PPMERGE_TYPE_SHUTTER: {
+                      pmShutterCorrectionData *shutter = shutters->data[cellNum]; // Shutter correction data
+                      if (!shutter) {
+                          shutter = pmShutterCorrectionDataAlloc(readout->image->numCols,
+                                                                 readout->image->numRows,
+                                                                 shutterSize);
+                          shutters->data[cellNum] = shutter;
+                      }
+                      if (!pmShutterCorrectionAddReadout(shutter, readout, meanStat, stdevStat,
+                                                         maskVal, rng)) {
+                          psError(PS_ERR_UNKNOWN, false,
+                                  "Can't add file %d chip %d cell %d to shutter correction.",
+                                  i, view->chip, view->cell);
+                          goto ERROR;
+                      }
+                      break;
+                  }
+                  case PPMERGE_TYPE_MASK:
+                  default:
+                    psAbort("Should never get here.");
+                }
+
+                cellNum++;
+
+                if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+                    goto ERROR;
+                }
+            }
+
+            if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+                goto ERROR;
+            }
+        }
+
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+            goto ERROR;
+        }
+
+        psFree(view);
+
+#if 0
+        // Reset files for reading again
+        for (int i = 0; i < files->n; i++) {
+            pmFPAfile *file = files->data[i]; // File of interest
+        }
+#endif
+        psFree(files);
+    }
+
+    psFree(rng); rng = NULL;
+    psFree(stats); stats = NULL;
+
+    // Store results
+    switch (type) {
+      case PPMERGE_TYPE_FRINGE:
+        psMetadataAddImage(config->arguments, PS_LIST_TAIL, "ZEROS", 0,
+                           "Zero to subtract from each input cell", background);
+        // Flow through
+      case PPMERGE_TYPE_FLAT: {
+          // Need to normalize over the focal plane
+          if (psTraceGetLevel("ppMerge") > 9) {
+              for (int i = 0; i < gains->n; i++) {
+                  psTrace("ppMerge", 10, "Gain for cell %d is %f\n", i, gains->data.F32[i]);
+              }
+          }
+          psVector *fluxes = NULL;        // Solution to fluxes
+          if (!pmFlatNormalize(&fluxes, &gains, background)) {
+              psError(PS_ERR_UNKNOWN, false, "Normalisation failed to converge --- continuing anyway.");
+              psFree(fluxes);
+              goto ERROR;
+          }
+
+          psMetadataAddVector(config->arguments, PS_LIST_TAIL, "SCALES", 0,
+                              "Scale to divide into each input file", fluxes);
+          psFree(fluxes);               // Drop reference
+          break;
+      }
+      case PPMERGE_TYPE_SHUTTER:
+        psMetadataAddArray(config->arguments, PS_LIST_TAIL, "SHUTTER", 0,
+                           "Shutter data", shutters);
+        break;
+      case PPMERGE_TYPE_MASK:
+      default:
+        psAbort("Should never get here.");
+    }
+
+    psFree(background);
+    psFree(shutters);
+    return true;
+
+ERROR:
+    // Common path for errors
+    psFree(gains);
+    psFree(background);
+    psFree(shutters);
+    psFree(rng);
+    psFree(stats);
+    psFree(view);
+    return false;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeScaleZero.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeScaleZero.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeScaleZero.h	(revision 22157)
@@ -0,0 +1,19 @@
+#ifndef PP_MERGE_SCALE_ZERO_H
+#define PP_MERGE_SCALE_ZERO_H
+
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppMergeData.h"
+#include "ppMergeOptions.h"
+
+// Get the scale and zero for each chip of each input
+bool ppMergeScaleZero(psImage **scales, // The scales for each integration/cell
+                      psImage **zeros, // The zeroes for each integration/cell
+                      psArray **shutter,// The shutter correction data for each cell
+                      ppMergeData *data,// The data
+                      const ppMergeOptions *options, // The options
+                      const pmConfig *config // The configuration
+    );
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeVersion.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeVersion.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeVersion.c	(revision 22157)
@@ -0,0 +1,60 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include <ppStats.h>
+
+#include "ppMergeVersion.h"
+
+static const char *cvsTag = "$Name: not supported by cvs2svn $";// CVS tag name
+
+psString ppMergeVersion(void)
+{
+    psString version = NULL;            // Version, to return
+    psStringAppend(&version, "%s-%s",PACKAGE_NAME,PACKAGE_VERSION);
+    return version;
+}
+
+psString ppMergeVersionLong(void)
+{
+    psString version = ppMergeVersion(); // Version, to return
+    psString tag = psStringStripCVS(cvsTag, "Name"); // CVS tag
+    psStringAppend(&version, " (cvs tag %s) %s, %s", tag, __DATE__, __TIME__);
+    psFree(tag);
+    return version;
+}
+
+
+void ppMergeVersionMetadata(psMetadata *metadata)
+{
+    PS_ASSERT_METADATA_NON_NULL(metadata,);
+
+    psString pslib = psLibVersionLong();// psLib version
+    psString psmodules = psModulesVersionLong(); // psModules version
+    psString ppStats = ppStatsVersionLong(); // ppStats version
+    psString ppMerge = ppMergeVersionLong(); // ppMerge version
+
+    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now
+    psString timeString = psTimeToISO(time); // The time in an ISO string
+    psFree(time);
+    psString head = NULL;               // Head string
+    psStringAppend(&head, "ppMerge processing at %s. Component information:", timeString);
+    psFree(timeString);
+
+    psMetadataAddStr(metadata, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, head, "");
+    psMetadataAddStr(metadata, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, pslib, "");
+    psMetadataAddStr(metadata, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, psmodules, "");
+    psMetadataAddStr(metadata, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, ppStats, "");
+    psMetadataAddStr(metadata, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, ppMerge, "");
+
+    psFree(head);
+    psFree(pslib);
+    psFree(psmodules);
+    psFree(ppStats);
+    psFree(ppMerge);
+
+    return;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeVersion.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeVersion.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppMerge/src/ppMergeVersion.h	(revision 22157)
@@ -0,0 +1,14 @@
+#ifndef PP_MERGE_VERSION_H
+#define PP_MERGE_VERSION_H
+
+/// Return short version information
+psString ppMergeVersion(void);
+
+/// Return long version information
+psString ppMergeVersionLong(void);
+
+/// Update the metadata with version information for all dependencies
+void ppMergeVersionMetadata(psMetadata *metadata ///< Metadata to update with version information
+    );
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/ppStats/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/.cvsignore	(revision 22157)
@@ -0,0 +1,16 @@
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+compile
+config.log
+config.status
+configure
+depcomp
+install-sh
+missing
+config.guess
+config.sub
+libtool
+ltmain.sh
+ppStats.pc
Index: /branches/eam_branches/eam_branch_20080706/ppStats/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/Makefile.am	(revision 22157)
@@ -0,0 +1,9 @@
+SUBDIRS = src
+
+CLEANFILES = *~ core core.*
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA= ppStats.pc
+
+EXTRA_DIST = \
+	ppStats.pc.in
Index: /branches/eam_branches/eam_branch_20080706/ppStats/autogen.sh
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/autogen.sh	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/autogen.sh	(revision 22157)
@@ -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=ppStats
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL="aclocal $ACLOCAL_FLAGS"
+AUTOHEADER=autoheader
+AUTOMAKE=automake
+AUTOCONF=autoconf
+
+($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $LIBTOOlIZE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/libtool/"
+        DIE=1
+}
+
+($ACLOCAL --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $ACLOCAL installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOHEADER --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOHEADER installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOMAKE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOCONF installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+if test "$DIE" -eq 1; then
+        exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+        echo "You must run this script in the top-level $PROJECT directory"
+        exit 1
+}
+
+if test -z "$*"; then
+        echo "I am going to run ./configure with no arguments - if you wish "
+        echo "to pass any to it, please specify them on the $0 command line."
+fi
+
+$LIBTOOLIZE --copy --force || echo "$LIBTOOlIZE failed"
+$ACLOCAL || echo "$ACLOCAL failed"
+$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /branches/eam_branches/eam_branch_20080706/ppStats/configure.ac
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/configure.ac	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/configure.ac	(revision 22157)
@@ -0,0 +1,30 @@
+AC_PREREQ(2.61)
+
+AC_INIT([ppStats], [1.1.1], [ipp-support@ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([src])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_CONFIG_HEADER([src/config.h])
+AM_MAINTAINER_MODE
+
+IPP_STDCFLAGS
+
+AC_LANG(C)
+AC_GNU_SOURCE
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+AC_SYS_LARGEFILE 
+
+PKG_CHECK_MODULES([PSLIB], [pslib >= 1.0.0])
+PKG_CHECK_MODULES([PSMODULE], [psmodules >= 1.0.0])
+
+IPP_STDOPTS
+CFLAGS="${CFLAGS=} -Wall -Werror -std=c99"
+   
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+  ppStats.pc
+])
+AC_OUTPUT
Index: /branches/eam_branches/eam_branch_20080706/ppStats/ppStats.pc.in
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/ppStats.pc.in	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/ppStats.pc.in	(revision 22157)
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libppStats
+Description: Pan-STARRS Image Statistics
+Version: @VERSION@
+Requires: pslib psmodules
+Libs: -L${libdir} -lppStats
+Cflags: -I${includedir}
Index: /branches/eam_branches/eam_branch_20080706/ppStats/src/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/src/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/src/.cvsignore	(revision 22157)
@@ -0,0 +1,10 @@
+.deps
+Makefile
+Makefile.in
+.libs
+*.lo
+*.la
+ppStats
+config.h
+config.h.in
+stamp-h1
Index: /branches/eam_branches/eam_branch_20080706/ppStats/src/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/src/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/src/Makefile.am	(revision 22157)
@@ -0,0 +1,41 @@
+
+lib_LTLIBRARIES = libppStats.la
+libppStats_la_CPPFLAGS = $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
+
+bin_PROGRAMS = ppStats
+
+ppStats_CFLAGS 	= $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
+ppStats_LDFLAGS = $(PSMODULE_LIBS) $(PSLIB_LIBS)
+ppStats_LDADD 	= libppStats.la
+
+ppStats_SOURCES =		\
+	ppStats.c	        
+
+libppStats_la_SOURCES = 		\
+	ppStatsFPA.c			\
+	ppStatsData.c			\
+	ppStatsFringe.c			\
+	ppStatsLoop.c			\
+	ppStatsChip.c			\
+	ppStatsCell.c			\
+	ppStatsReadout.c		\
+	ppStatsUtils.c  		\
+	ppStatsPixels.c			\
+	ppStatsMetadata.c		\
+	ppStatsSetupFromRecipe.c	\
+	ppStatsSetupFromArgs.c          \
+	ppStatsVersion.c
+
+include_HEADERS = 			\
+	ppStats.h
+
+noinst_HEADERS = 		\
+	ppStatsInternal.h
+
+CLEANFILES = *~
+
+clean-local:
+	-rm -f TAGS
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
Index: /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStats.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStats.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStats.c	(revision 22157)
@@ -0,0 +1,122 @@
+# include "ppStatsInternal.h"
+
+int main(int argc, char **argv) {
+
+    psExit status = PS_EXIT_SUCCESS;
+
+    psLibInit(NULL);
+    psTimerStart("PPSTATS");
+
+    // Parse the configuration and arguments
+    pmConfig *config = pmConfigRead(&argc, argv, PPSTATS_RECIPE);
+    if (!config) {
+        psErrorStackPrint(stderr, "Unable to read configuration.\n");
+        exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    // Get the options, open the files
+    ppStatsData *data = ppStatsSetupFromArgs(&argc, argv, config);
+    if (!data) {
+        psErrorStackPrint(stderr, "Unable to parse command-line arguments.\n");
+        exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    // Output filename is optional
+    const char *outName = NULL;         // Output file name
+    FILE *outFile = stdout;             // Output file
+    if (argc == 2) {
+        outName = argv[1];
+        psString resolved = pmConfigConvertFilename(outName, config, true, true); // Resolved filename
+
+        if (resolved && strlen(resolved) > 0) {
+            outFile = fopen(resolved, "w");
+            if (!outFile) {
+                psLogMsg("ppStats", PS_LOG_ERROR, "Unable to open output file %s\n", resolved);
+                psFree(resolved);
+                // XXX this could be a system or config error, but not a data error
+                status = PS_EXIT_CONFIG_ERROR;
+                goto die;
+            }
+        } else {
+            psErrorStackPrint(stderr, "Unable to open output file %s.\n", resolved);
+            exit(PS_EXIT_CONFIG_ERROR);
+        }
+        psFree(resolved);
+    }
+
+    // Go through the FPA and do the hard work
+    psMetadata *results = ppStatsLoop(&status, data, config);
+    if (status != PS_EXIT_SUCCESS) {
+        psErrorStackPrint(stderr, "Error in stats loop.\n");
+        exit (status);
+    }
+
+    // report on the file disposition
+    if (data->fileLevel) {
+        pmFPALevel level = pmFPAPHULevel(config->format);
+
+        const char *levelName = pmFPALevelToName(level); // Level for file
+        psMetadataAddStr(results, PS_LIST_HEAD, "FILE.LEVEL", 0, "File level", levelName);
+
+        char *classID = NULL;
+        switch (level) {
+          case PM_FPA_LEVEL_FPA:
+            assert (data->fpa != NULL);
+            assert (data->fileView->chip == -1);
+            classID = pmFPANameFromRule ("{FPA.NAME}", data->fpa, data->fileView);
+            break;
+          case PM_FPA_LEVEL_CHIP:
+            assert (data->fpa != NULL);
+            assert (data->fileView->chip != -1);
+            assert (data->fileView->cell == -1);
+            classID = pmFPANameFromRule ("{CHIP.NAME}", data->fpa, data->fileView);
+            break;
+          case PM_FPA_LEVEL_CELL:
+            assert (data->fpa != NULL);
+            assert (data->fileView->chip != -1);
+            assert (data->fileView->cell != -1);
+            classID = pmFPANameFromRule ("{CELL.NAME}", data->fpa, data->fileView);
+            break;
+          default:
+            psErrorStackPrint(stderr, "Error in file level.\n");
+            exit (PS_EXIT_CONFIG_ERROR);
+        }
+        psMetadataAddStr(results, PS_LIST_HEAD, "CLASS.ID", 0, "name for element at file level", classID);
+        psFree (classID);
+    }
+
+    // did we actually request any data?
+    if (psListLength(results->list) == 0) {
+        psErrorStackPrint(stderr, "No output.\n");
+        exit (status);
+    }
+
+    // Format and print the output
+    psString output = psMetadataConfigFormat(results);
+    if (!output) {
+        psErrorStackPrint(stderr, "Unable to generate configuration file with result.\n");
+        psFree(results);
+        exit(PS_EXIT_CONFIG_ERROR);
+    }
+    fprintf(outFile, "%s", output);
+    psFree(output);
+
+    // Clean up
+    psFree(results);
+    if (outName) {
+        fclose(outFile);
+    }
+
+    // Common code for the death.
+die:
+    if (status) {
+        psErrorStackPrint (stderr, "failure in %s", __func__);
+    }
+    psFree(data);
+    psFree(config);
+    pmConceptsDone();
+    pmConfigDone();
+    psLibFinalize();
+
+    return status;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStats.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStats.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStats.config	(revision 22157)
@@ -0,0 +1,25 @@
+### Example ppStats recipe
+
+MULTI	HEADERS		# May specify multiple HEADERS
+HEADER		STR	HEADER1,HEADER2			# Read these headers
+HEADER		STR	HEADER3				# Read these headers too
+
+MULTI	STATS		# May specify multiple STATS
+STAT		STR	SAMPLE_MEAN,SAMPLE_STDEV	# Calculate these statistics
+STAT		STR	ROBUST_MEDIAN			# Calculate these statistics too
+
+MULTI   ANALYSIS        # metadata blocks to search in chip/cell/readout->analysis
+ANALYSIS        STR     PSPHOT.HEADER
+ANALYSIS        STR     PSASTRO.HEADER
+
+SAMPLE		F32	0.1				# Fraction of cell to sample
+
+MULTI	CHIPS		# May specify multiple CHIPS
+CHIPS		STR	chip00, chip01			# Look at these chips
+CHIPS		STR	chip22				# Look at this chip too
+
+MULTI	CELLS		# May specify multiple CELLS
+CELLS		STR	LeftCell,RightCell		# Look at these cells
+CELLS		STR	UpsideDownCell			# Look at this cell too
+
+MASKVAL		U8	0xff
Index: /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStats.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStats.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStats.h	(revision 22157)
@@ -0,0 +1,130 @@
+
+#ifndef PP_STATS_H
+#define PP_STATS_H
+
+#define PPSTATS_RECIPE "PPSTATS"
+
+typedef struct {
+    // Inputs
+    psFits *fits;                       // Input file handle
+    pmFPA *fpa;                         // FPA to analyse
+    pmFPAview *view;                    // View to analyse
+    // Stuff to output
+    psStats *stats;                     // pixel Statistics to calculate
+    bool doStats;                       // Do pixel statistics?
+    bool fileLevel;                     // Output file level?
+    pmFPAview *fileView;                // View to analyse
+
+    psList *headers;                    // Headers to read
+    psList *concepts;                   // Concepts to read
+    psList *analysis;                   // Analysis entries to read
+    psList *summary;                    // Summary statistics to calculate
+    // Options for input data
+    float sample;                       // Fraction of cell to sample for statistics
+    psMaskType maskVal;                 // Mask value for images
+    psList *chips;                      // Chips to look at
+    psList *cells;                      // Cells to look at
+} ppStatsData;
+
+// Allocator
+ppStatsData *ppStatsDataAlloc(void);
+
+/// Perform the ppStats steps on the given FPA (optionally for specified view)
+psMetadata *ppStatsFPA(psMetadata *out,
+                       pmFPA *fpa,         // FPA for which to get statistics
+                       pmFPAview *view,    // View for analysis
+                       psMaskType maskVal, // Value to mask
+                       pmConfig *config    // Configuration
+    );
+
+psExit ppStatsChip(psMetadata *fpaResults, // Metadata holding the fpa results
+                   pmChip *chip,     // Chip for which to get statistics
+                   psFits *fits,     // FITS file handle
+                   pmFPAview *view,  // View for analysis
+                   ppStatsData *data,// The data
+                   pmConfig *config // Configuration
+    );
+
+psExit ppStatsCell(psMetadata *chipResults, // Metadata holding the chip results
+                   pmCell *cell,     // Cell for which to get statistics
+                   psFits *fits,     // FITS file handle
+                   pmFPAview *view,  // View for analysis
+                   ppStatsData *data,// The data
+                   pmConfig *config  // Configuration
+    );
+
+/// Supplement the statistics with the fringe solution
+bool ppStatsFringe(psMetadata *stats,     ///< Statistics metadata to supplement
+                   const pmChip *chip,    ///< The chip containing the solution
+                   const char *root,      ///< Name of output entry
+                   const char *fringeName ///< Name of the solution in the chip->analysis
+    );
+
+
+// measure only the pixel-related statistics (stats, summary)
+psMetadata *ppStatsPixels(psMetadata *out,
+                          pmFPA *fpa,         // FPA for which to get statistics
+                          pmFPAview *view,    // View for analysis
+                          psMaskType maskVal, // Value to mask
+                          pmConfig *config    // Configuration
+    );
+
+// measure only the non-pixel-related statistics (headers, concepts)
+psMetadata *ppStatsMetadata(psMetadata *out,
+                            pmFPA *fpa,         // FPA for which to get statistics
+                            pmFPAview *view,    // View for analysis
+                            psMaskType maskVal, // Value to mask
+                            pmConfig *config    // Configuration
+    );
+
+/// Loop over the input image and do all the hard work
+psMetadata *ppStatsLoop(psExit *result,
+                        ppStatsData *data, // The data
+                        pmConfig *config // Configuration
+    );
+
+/// Set up the options and input/output files
+ppStatsData *ppStatsSetupFromArgs(int *argc, char *argv[], // Command-line arguments
+                                  pmConfig *config // Configuration
+    );
+
+bool ppStatsSetupFromRecipe(ppStatsData *data, // Data for running ppStats
+                            pmConfig *config // Configuration
+    );
+
+
+/// Return short version information
+psString ppStatsVersion(void);
+
+/// Return long version information
+psString ppStatsVersionLong(void);
+
+void p_ppStatsGetMetadata(psMetadata *target, // Target for metadata
+                          psMetadata *source, // Source for metadata
+                          psList *list    // List containing keywords
+    );
+
+void p_ppStatsGetAnalysis(psMetadata *target, // Output Target for metadata
+                          psList *headers,    // List containing desired keywords
+                          psMetadata *source, // Input Source for metadata
+                          psList *list        // List containing analysis blocks
+    );
+
+bool p_ppStatsDoThis(psList *toDoList,    // List of things to do
+                     const char *this     // The name of "this"
+    );
+
+void p_ppStatsAddToHierarchy(psMetadata *source, // Source to add
+                             psMetadata *target, // Target to which to add
+                             const char *name, // Name of source
+                             const char *comment // Comment for source
+    );
+
+psExit ppStatsReadout(psMetadata *cellResults, // Metadata holding the chip results
+                      pmReadout *readout,       // Cell for which to get statistics
+                      int nReadout,     // readout number
+                      ppStatsData *data,        // The data
+                      const pmConfig *config // Configuration
+    );
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsCell.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsCell.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsCell.c	(revision 22157)
@@ -0,0 +1,119 @@
+# include "ppStatsInternal.h"
+
+psExit ppStatsCell(psMetadata *chipResults, // Metadata holding the chip results
+                   pmCell *cell,        // Cell for which to get statistics
+                   psFits *fits,        // FITS file handle
+                   pmFPAview *view,  // View for analysis
+                   ppStatsData *data,   // The data
+                   pmConfig *config // Configuration
+    )
+{
+    assert(chipResults);
+    assert(cell);
+    assert(data);
+    assert(config);
+
+    const char *cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME"); // Name of cell
+
+    // Check to see if this is a cell of interest
+    if (!p_ppStatsDoThis(data->cells, cellName)) {
+        return PS_EXIT_SUCCESS;
+    }
+
+#if 0
+    // select the header unit for this cell
+    // XXX why is this needed for Cell but not Chip?
+    pmHDU *hdu = pmHDUFromCell(cell); // HDU for cell
+    if (!hdu || hdu->blankPHU) {
+        if (fits) {
+            // No HDU means there's no data in this cell
+            return PS_EXIT_SUCCESS;
+        } else {
+            psError(PS_ERR_UNKNOWN, false, "Can't find HDU for cell\n");
+            return PS_EXIT_CONFIG_ERROR;
+        }
+    }
+#endif
+
+    // Extract Header and Concept values from the Cell and Readout->analysis level
+
+    // find or generate a metadata to hold the results
+    bool mdok;                          // Status of MD lookup
+    psMetadata *cellResults = psMemIncrRefCounter(psMetadataLookupMetadata(&mdok, chipResults, cellName));
+    if (!mdok || !cellResults) {
+        cellResults = psMetadataAlloc();
+    }
+
+    // Extract Header values
+    if (psListLength(data->headers)) {
+        // extract from existing headers
+        if (cell->hdu) {
+            if (fits && !pmCellReadHeader(cell, fits, config)) {
+                psError (PS_ERR_IO, false, "trouble reading cell header\n");
+                psFree(cellResults);
+                return PS_EXIT_DATA_ERROR;
+            }
+            pmHDU *hdu = cell->hdu;     // HDU for headers
+            p_ppStatsGetMetadata(cellResults, hdu->header, data->headers);
+        }
+        // extract from data->analysis output MD entries
+        if (psListLength(data->analysis)) {
+            p_ppStatsGetAnalysis (cellResults, data->headers, cell->analysis, data->analysis);
+        }
+    }
+
+    // Extract Concept values
+    if (psListLength(data->concepts) > 0) {
+        if (fits && cell->hdu && !pmCellReadHeader(cell, fits, config)) {
+            psError (PS_ERR_IO, false, "trouble reading cell header\n");
+            psFree(cellResults);
+            return PS_EXIT_DATA_ERROR;
+        }
+        pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_ALL, false, config);
+        p_ppStatsGetMetadata(cellResults, cell->concepts, data->concepts);
+    }
+
+    // If we want to measure pixel statistics, we must read (or have) the pixel data
+    if (data->doStats || psListLength(data->summary)) {
+        // Read the image pixel data
+        if (fits && !pmCellRead(cell, fits, config)) {
+            psError (PS_ERR_IO, false, "trouble reading cell data\n");
+            psFree(cellResults);
+            return PS_EXIT_DATA_ERROR;
+        }
+    }
+
+    // check if we can legitimately iterate to the readout level
+    psArray *readouts = cell->readouts; // Array of component readouts
+    if (view->readout >= readouts->n) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Desired readout view (%d) doesn't match "
+                "number of readouts (%ld)\n", view->readout, readouts->n);
+        return PS_EXIT_CONFIG_ERROR;
+    }
+    if ((view->readout == -1) && (readouts->n > 1)) {
+        psWarning("Multiple readouts (%ld) present in cell %s\n", readouts->n, cellName);
+    }
+
+    // Iterate over readouts
+    for (int i = 0; i < readouts->n; i++) {
+        if ((view->readout >= 0) && (i != view->readout)) continue;
+        pmReadout *readout = readouts->data[i];  // Cell of interest
+        psExit result = ppStatsReadout(cellResults, readout, i, data, config);
+        if (result != PS_EXIT_SUCCESS) {
+            psError(PS_ERR_UNKNOWN, false, "trouble with readout stats for %d\n", i);
+            if (fits) {
+                pmCellFreeData(cell);
+            }
+            psFree(cellResults);
+            return result;
+        }
+    }
+
+    // Add the cell results to the chip
+    p_ppStatsAddToHierarchy(cellResults, chipResults, cellName, "Results for cell");
+    psFree (cellResults);
+    if (fits) {
+        pmCellFreeData(cell);
+    }
+    return PS_EXIT_SUCCESS;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsChip.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsChip.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsChip.c	(revision 22157)
@@ -0,0 +1,96 @@
+# include "ppStatsInternal.h"
+
+psExit ppStatsChip(psMetadata *fpaResults, // Metadata holding the fpa results
+                   pmChip *chip,     // Chip for which to get statistics
+                   psFits *fits,     // FITS file handle
+                   pmFPAview *view,  // View for analysis
+                   ppStatsData *data,// The data
+                   pmConfig *config // Configuration
+    )
+{
+    assert(fpaResults);
+    assert(chip);
+    assert(view);
+    assert(data);
+    assert(config);
+
+    const char *chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME"); // Name of chip
+
+    // Check to see if this is a chip of interest
+    if (!p_ppStatsDoThis(data->chips, chipName)) {
+        return PS_EXIT_SUCCESS;
+    }
+    if (!chip->file_exists && !chip->data_exists) {
+        return PS_EXIT_SUCCESS;
+    }
+
+    // Extract Header and Concept values from the Chip level
+
+    // find or generate a metadata to hold the results
+    bool mdok;                          // Status of MD lookup
+    psMetadata *chipResults = psMemIncrRefCounter(psMetadataLookupMetadata(&mdok, fpaResults, chipName));
+    if (!mdok || !chipResults) {
+        chipResults = psMetadataAlloc();
+    }
+
+    // Extract Header values
+    if (psListLength(data->headers)) {
+        // extract from existing headers
+        if (chip->hdu) {
+            if (fits && !pmChipReadHeader(chip, fits, config)) {
+                psError (PS_ERR_IO, false, "trouble reading chip header\n");
+                psFree(chipResults);
+                return PS_EXIT_DATA_ERROR;
+            }
+            pmHDU *hdu = chip->hdu;     // HDU for headers
+            p_ppStatsGetMetadata(chipResults, hdu->header, data->headers);
+        }
+        // extract from data->analysis output MD entries
+        if (psListLength(data->analysis)) {
+            p_ppStatsGetAnalysis (chipResults, data->headers, chip->analysis, data->analysis);
+        }
+    }
+
+    // Extract Concept values
+    if (psListLength(data->concepts) > 0) {
+        if (fits && chip->hdu && !pmChipReadHeader(chip, fits, config)) {
+            psError (PS_ERR_IO, false, "trouble reading chip header\n");
+            psFree(chipResults);
+            return PS_EXIT_DATA_ERROR;
+        }
+        pmConceptsReadChip(chip, PM_CONCEPT_SOURCE_ALL, false, false, config);
+        p_ppStatsGetMetadata(chipResults, chip->concepts, data->concepts);
+    }
+
+    // check if we can legitimately iterate to the cell level
+    psArray *cells = chip->cells;       // Array of cells
+    if (view->cell >= cells->n) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Desired cell view (%d) doesn't match "
+                "number of cells (%ld)\n", view->cell, cells->n);
+        return PS_EXIT_CONFIG_ERROR;
+    }
+
+    // Iterate over cells (if view->cell is set, skip all others)
+    for (int i = 0; i < cells->n; i++) {
+        if ((view->cell >= 0) && (i != view->cell)) continue;
+        pmCell *cell = cells->data[i];  // Cell of interest
+        psExit result = ppStatsCell(chipResults, cell, fits, view, data, config);
+        if (result != PS_EXIT_SUCCESS) {
+            psError(PS_ERR_UNKNOWN, false, "trouble with cell stats for %d\n", i);
+            if (fits) {
+                pmChipFreeData(chip);
+            }
+            psFree(chipResults);
+            return result;
+        }
+    }
+
+    if (fits) {
+        pmChipFreeData(chip);
+    }
+
+    p_ppStatsAddToHierarchy(chipResults, fpaResults, chipName, "Results for chip");
+
+    psFree (chipResults);
+    return PS_EXIT_SUCCESS;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsData.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsData.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsData.c	(revision 22157)
@@ -0,0 +1,52 @@
+#include "ppStatsInternal.h"
+
+static void statsDataFree(ppStatsData *data // Data to free
+    )
+{
+    if (data->fits) {
+        psFitsClose(data->fits);
+        data->fits = NULL;
+    }
+    psFree(data->fpa);
+    psFree(data->view);
+    psFree(data->headers);
+    psFree(data->concepts);
+    psFree(data->analysis);
+    psFree(data->summary);
+
+    psFree(data->stats);
+    psFree(data->fileView);
+
+    psFree(data->chips);
+    psFree(data->cells);
+
+    return;
+}
+
+
+ppStatsData *ppStatsDataAlloc(void)
+{
+    ppStatsData *data = psAlloc(sizeof(ppStatsData)); // Newly allocated data
+    psMemSetDeallocator(data, (psFreeFunc)statsDataFree);
+
+    data->fits = NULL;
+    data->fpa = NULL;
+    data->view = NULL;
+
+    data->headers = psListAlloc(NULL);
+    data->concepts = psListAlloc(NULL);
+    data->analysis = psListAlloc(NULL);
+    data->summary = psListAlloc(NULL);
+    data->stats = psStatsAlloc(0);
+    data->doStats = false;
+    data->fileLevel = false;
+    data->fileView = NULL;
+
+    data->sample = 0;
+    data->maskVal = 0;
+    data->chips = psListAlloc(NULL);
+    data->cells = psListAlloc(NULL);
+
+    return data;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsErrorCodes.c.in
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsErrorCodes.c.in	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsErrorCodes.c.in	(revision 22157)
@@ -0,0 +1,26 @@
+/*
+ * The line
+    { PPSTATS_ERR_$X{ErrorCode}, "$X{ErrorDescription}"},
+ * (without the Xs)
+ * will be replaced by values from errorCodes.dat
+ */
+#include "pslib.h"
+#include "ppStatsErrorCodes.h"
+
+void ppStatsErrorRegister(void)
+{
+    static psErrorDescription errors[] = {
+       { PPSTATS_ERR_BASE, "First value we use; lower values belong to psLib" },
+       { PPSTATS_ERR_${ErrorCode}, "${ErrorDescription}"},
+    };
+    static int nerror = PPSTATS_ERR_NERROR - PPSTATS_ERR_BASE; // number of values in enum
+
+    for (int i = 0; i < nerror; i++) {
+       psErrorDescription *tmp = psAlloc(sizeof(psErrorDescription));
+       p_psMemSetPersistent(tmp, true);
+       *tmp = errors[i];
+       psErrorRegister(tmp, 1);
+       psFree(tmp);			/* it's on the internal list */
+    }
+    nerror = 0;			                // don't register more than once
+}
Index: /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsErrorCodes.dat
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsErrorCodes.dat	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsErrorCodes.dat	(revision 22157)
@@ -0,0 +1,10 @@
+#
+# This file is used to generate ppStatsErrorClasses.h
+#
+BASE = 400		First value we use; lower values belong to psLib
+# these errors correspond to standard exit conditions
+ARGUMENTS               Incorrect arguments
+SYS                     System error
+CONFIG                  Problem in configure files
+PROG                    Programming error
+DATA                    invalid data
Index: /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsErrorCodes.h.in
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsErrorCodes.h.in	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsErrorCodes.h.in	(revision 22157)
@@ -0,0 +1,18 @@
+#if !defined(PPSTATS_ERROR_CODES_H)
+#define PPSTATS_ERROR_CODES_H
+/*
+ * The line
+ *  PPSTATS_ERR_$X{ErrorCode},
+ * (without the X)
+ *
+ * will be replaced by values from errorCodes.dat
+ */
+typedef enum {
+    PPSTATS_ERR_BASE = 512,
+    PPSTATS_ERR_${ErrorCode},
+    PPSTATS_ERR_NERROR
+} ppStatsErrorCode;
+
+void ppStatsErrorRegister(void);
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsFPA.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsFPA.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsFPA.c	(revision 22157)
@@ -0,0 +1,55 @@
+#include "ppStatsInternal.h"
+
+psMetadata *ppStatsFPA(psMetadata *out,
+		       pmFPA *fpa,         // FPA for which to get statistics
+		       pmFPAview *view,    // View for analysis
+		       psMaskType maskVal, // Value to mask
+		       pmConfig *config    // Configuration
+    )
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL)
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    ppStatsData *data = ppStatsDataAlloc(); // All the input data
+
+    // Get the options, open the files
+    if (!ppStatsSetupFromRecipe(data, config)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to get ppStats options from recipe.");
+        psFree(data);
+        return NULL;
+    }
+
+    // Override recipe mask value
+    data->maskVal = maskVal;
+
+    if (data->fpa) {
+        psFree(data->fpa);
+    }
+    data->fpa = psMemIncrRefCounter(fpa);
+
+    if (data->view) {
+        psFree(data->view);
+    }
+    data->view = psMemIncrRefCounter(view);
+
+    // Go through the FPA and do the hard work
+    psExit status;                      // Status of statistics loop
+    psMetadata *result = ppStatsLoop(&status, data, config);
+    if (status != PS_EXIT_SUCCESS) {
+        psError (PS_ERR_UNKNOWN, false, "Not able to measure FPA statistics.\n");
+        psFree(result);
+        psFree(data);
+        return (NULL);
+    }
+
+    if (out != NULL) {
+	psMetadataOverlay (out, result);
+	psFree(result);
+	psFree(data);
+	return out;
+    }
+
+    psFree(data);
+    return result;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsFringe.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsFringe.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsFringe.c	(revision 22157)
@@ -0,0 +1,37 @@
+#include "ppStatsInternal.h"
+
+bool ppStatsFringe(psMetadata *stats, const pmChip *chip, const char *root, const char *fringeName)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_STRING_NON_EMPTY(fringeName, false);
+
+    bool mdok;                          // Status of MD lookup
+    const char *chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME"); // Name of chip
+
+    psMetadata *chipStats = psMetadataLookupMetadata(&mdok, stats, chipName); // Chip statistics
+    if (!mdok || !chipStats) {
+        chipStats = psMetadataAlloc();
+        psMetadataAddMetadata(stats, PS_LIST_TAIL, chipName, 0, NULL, chipStats);
+    } else {
+        psMemIncrRefCounter(chipStats);
+    }
+
+    pmFringeScale *fringes = psMetadataLookupPtr(NULL, chip->analysis, fringeName); // Solution for fringes
+    for (int i = 0; i < fringes->nFringeFrames; i++) {
+        psString name = NULL; // Name of statistic
+        psStringAppend(&name, "%s_%d", root, i);
+        psMetadataAddF32(chipStats, PS_LIST_TAIL, name, 0, "Fringe amplitude",
+                         fringes->coeff->data.F32[i + 1]);
+        psFree(name);
+        name = NULL;
+        psStringAppend(&name, "%s_ERR_%d", root, i);
+        psMetadataAddF32(chipStats, PS_LIST_TAIL, name, 0, "Fringe amplitude error",
+                         fringes->coeffErr->data.F32[i + 1]);
+        psFree(name);
+    }
+
+    psFree(chipStats);
+
+    return true;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsInternal.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsInternal.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsInternal.h	(revision 22157)
@@ -0,0 +1,17 @@
+
+# ifdef HAVE_CONFIG_H
+# include <config.h>
+# endif
+
+#ifndef PP_STATS_STAND_ALONE_H
+#define PP_STATS_STAND_ALONE_H
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "ppStats.h"
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsLoop.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsLoop.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsLoop.c	(revision 22157)
@@ -0,0 +1,72 @@
+# include "ppStatsInternal.h"
+
+psMetadata *ppStatsLoop(psExit *result,
+                        ppStatsData *data, // The data
+                        pmConfig *config // Configuration
+    )
+{
+    PS_ASSERT_PTR_NON_NULL(data, NULL);
+    pmFPA *fpa = data->fpa;             // FPA to analyse
+    psFits *fits = data->fits;          // FITS file handle
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    *result = PS_EXIT_SUCCESS;
+
+    // allocate or bump the ref counter (so we can just free below)
+    pmFPAview *view = (data->view) ? psMemIncrRefCounter(data->view) : pmFPAviewAlloc(0);
+
+    // allocate a new one if needed
+    psMetadata *newResults = psMetadataAlloc();
+
+    // Iterate through the FPA
+    if (psListLength(data->headers) > 0 && fpa->hdu) {
+        if (fits && !pmFPAReadHeader(fpa, fits, config)) {
+            psError(PS_ERR_IO, false, "Unable to read header for FPA.");
+            psFree(view);
+            psFree(newResults);
+            *result = PS_EXIT_DATA_ERROR;
+            return NULL;
+        }
+        pmHDU *hdu = fpa->hdu;          // HDU for headers
+        p_ppStatsGetMetadata(newResults, hdu->header, data->headers);
+    }
+    if (psListLength(data->concepts) > 0) {
+        if (fits && fpa->hdu && !pmFPAReadHeader(fpa, fits, config)) {
+            psError(PS_ERR_IO, false, "Unable to read header for FPA.");
+            psFree(view);
+            psFree(newResults);
+            *result = PS_EXIT_DATA_ERROR;
+            return NULL;
+        }
+        pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_ALL, false, config);
+        p_ppStatsGetMetadata(newResults, fpa->concepts, data->concepts);
+    }
+
+    // check if we can legitimately iterate to the chip level
+    psArray *chips = fpa->chips;        // Array of chips
+    if (view->chip >= chips->n) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Desired chip view (%d) doesn't match "
+                "number of chips (%ld)\n", view->chip, chips->n);
+        return NULL;
+    }
+
+    // Iterate over chips (if view->chip is set, skip all others)
+    for (int i = 0; i < chips->n; i++) {
+        if ((view->chip >= 0) && (i != view->chip)) continue;
+        pmChip *chip = chips->data[i];  // Chip of interest
+        *result = ppStatsChip(newResults, chip, fits, view, data, config);
+        if (*result != PS_EXIT_SUCCESS) {
+            psError(PS_ERR_UNKNOWN, false, "trouble with stats for chip %d\n", i);
+            psFree (view);
+            psFree (newResults);
+            return NULL;
+        }
+    }
+
+    if (fits) {
+        pmFPAFreeData(fpa);
+    }
+
+    psFree(view);
+    return newResults;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsMetadata.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsMetadata.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsMetadata.c	(revision 22157)
@@ -0,0 +1,63 @@
+#include "ppStatsInternal.h"
+
+// measure only the non-pixel-related statistics (headers, concepts)
+psMetadata *ppStatsMetadata(psMetadata *out,
+			    pmFPA *fpa,         // FPA for which to get statistics
+			    pmFPAview *view,    // View for analysis
+			    psMaskType maskVal, // Value to mask
+			    pmConfig *config    // Configuration
+    )
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL)
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    ppStatsData *data = ppStatsDataAlloc(); // All the input data
+
+    // Get the options, open the files
+    if (!ppStatsSetupFromRecipe(data, config)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to get ppStats options from recipe.");
+        psFree(data);
+        return NULL;
+    }
+
+    // drop the 'concept' and 'header' elements of data:
+    psFree (data->summary);    
+    psFree (data->stats);
+    data->summary = psListAlloc(NULL);
+    data->stats = psStatsAlloc(0);
+    data->doStats = false;
+
+    // Override recipe mask value
+    data->maskVal = maskVal;
+
+    if (data->fpa) {
+        psFree(data->fpa);
+    }
+    data->fpa = psMemIncrRefCounter(fpa);
+
+    if (data->view) {
+        psFree(data->view);
+    }
+    data->view = psMemIncrRefCounter(view);
+
+    // Go through the FPA and do the hard work
+    psExit status;                      // Status of statistics loop
+    psMetadata *result = ppStatsLoop(&status, data, config);
+    if (status != PS_EXIT_SUCCESS) {
+        psError (PS_ERR_UNKNOWN, false, "Not able to measure FPA statistics.\n");
+        psFree(result);
+        psFree(data);
+        return (NULL);
+    }
+
+    if (out != NULL) {
+	psMetadataOverlay (out, result);
+	psFree(result);
+	psFree(data);
+	return out;
+    }
+
+    psFree(data);
+    return result;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsPixels.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsPixels.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsPixels.c	(revision 22157)
@@ -0,0 +1,62 @@
+#include "ppStatsInternal.h"
+
+// measure only the pixel-related statistics (stats, summary)
+psMetadata *ppStatsPixels(psMetadata *out,
+			  pmFPA *fpa,         // FPA for which to get statistics
+			  pmFPAview *view,    // View for analysis
+			  psMaskType maskVal, // Value to mask
+			  pmConfig *config    // Configuration
+    )
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL)
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    ppStatsData *data = ppStatsDataAlloc(); // All the input data
+
+    // Get the options, open the files
+    if (!ppStatsSetupFromRecipe(data, config)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to get ppStats options from recipe.");
+        psFree(data);
+        return NULL;
+    }
+
+    // drop the 'concept' and 'header' elements of data:
+    psFree (data->headers);    
+    psFree (data->concepts);
+    data->headers = psListAlloc(NULL);
+    data->concepts = psListAlloc(NULL);
+
+    // Override recipe mask value 
+    data->maskVal = maskVal;
+
+    if (data->fpa) {
+        psFree(data->fpa);
+    }
+    data->fpa = psMemIncrRefCounter(fpa);
+
+    if (data->view) {
+        psFree(data->view);
+    }
+    data->view = psMemIncrRefCounter(view);
+
+    // Go through the FPA and do the hard work
+    psExit status;                      // Status of statistics loop
+    psMetadata *result = ppStatsLoop(&status, data, config);
+    if (status != PS_EXIT_SUCCESS) {
+        psError (PS_ERR_UNKNOWN, false, "Not able to measure FPA statistics.\n");
+        psFree(result);
+        psFree(data);
+        return (NULL);
+    }
+
+    if (out != NULL) {
+	psMetadataOverlay (out, result);
+	psFree(result);
+	psFree(data);
+	return out;
+    }
+
+    psFree(data);
+    return result;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsReadout.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsReadout.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsReadout.c	(revision 22157)
@@ -0,0 +1,207 @@
+# include "ppStatsInternal.h"
+
+static bool warnNonFinite = false;     // Have we warned about non-finite values?
+
+psExit ppStatsReadout(psMetadata *cellResults, // Metadata holding the chip results
+                      pmReadout *readout,       // Cell for which to get statistics
+                      int nReadout,     // readout number
+                      ppStatsData *data,        // The data
+                      const pmConfig *config // Configuration
+    )
+{
+    assert(cellResults);
+    assert(readout);
+    assert(data);
+    assert(config);
+
+    /*** psphot and psastro put their results on the readout->analysis metadata (PSPHOT.HEADER,
+         PSASTRO.HEADER).  we need to pull quantities of interest from those locations. to do
+         this, we need to select the appropriate readout.  ***/
+
+    // Extract Header and Concept values from the Cell and Readout->analysis level
+
+    psString readoutName = NULL;
+    psStringAppend (&readoutName, "READOUT.%02d", nReadout);
+
+    // find or generate a metadata to hold the results
+    bool mdok;                          // Status of MD lookup
+    psMetadata *readoutResults = psMemIncrRefCounter(psMetadataLookupMetadata(&mdok, cellResults, readoutName));
+    if (!mdok || !readoutResults) {
+        readoutResults = psMetadataAlloc();
+    }
+
+    // Extract Header values
+    if (psListLength(data->headers)) {
+        // extract from data->analysis output MD entries
+        if (psListLength(data->analysis)) {
+            p_ppStatsGetAnalysis (readoutResults, data->headers, readout->analysis, data->analysis);
+        }
+    }
+
+    // Do we want to measure pixel statistics?
+    if (!data->doStats && !psListLength(data->summary)) {
+        // Nothing further to do --- don't want to waste our time reading the data
+        goto readoutDone;
+    }
+
+    if (!readout->image) {
+        psLogMsg(__func__, PS_LOG_WARN, "No image associated with readout --- ignored.\n");
+        goto readoutDone;
+    }
+
+    // Measure basic image statistics (means, stdevs, etc).
+    if (data->sample <= 0.0) {
+        if (!psImageStats(data->stats, readout->image, readout->mask, data->maskVal)) {
+            psWarning("Unable to perform statistics on readout %s --- ignored.\n", readoutName);
+            goto statsDone;
+        }
+    } else {
+        // Apply sampling
+        psImage *image = readout->image; // The image of interest
+        psImage *mask = readout->mask; // The mask image
+        int numSamples = data->sample * image->numCols * image->numRows; // Number of samples
+        int sampleSpace = 1.0 / data->sample; // Space between samples
+        psVector *sampleValues = psVectorAlloc(numSamples, PS_TYPE_F32); // Vector of samples
+        psVector *sampleMask = psVectorAlloc(numSamples, PS_TYPE_MASK);  // Corresponding mask
+        if (!mask) {
+            psVectorInit(sampleMask, 0);
+        }
+        for (int i = 0; i < numSamples; i++) {
+            int j = i * sampleSpace;
+            int y = j / image->numCols;
+            int x = j % image->numCols;
+            sampleValues->data.F32[i] = image->data.F32[y][x];
+            if ((!mask || !(mask->data.PS_TYPE_MASK_DATA[y][x] & data->maskVal)) &&
+                !isfinite(sampleValues->data.F32[i])) {
+                if (!warnNonFinite) {
+                    psWarning("Unmasked non-finite value detected at %d,%d; suppressing further warnings",
+                              x, y);
+                    warnNonFinite = true;
+                }
+                sampleMask->data.PS_TYPE_MASK_DATA[i] = data->maskVal;
+            } else if (mask) {
+                sampleMask->data.PS_TYPE_MASK_DATA[i] = mask->data.PS_TYPE_MASK_DATA[y][x];
+            }
+        }
+        if (!psVectorStats(data->stats, sampleValues, NULL, sampleMask, data->maskVal)) {
+            psWarning("Unable to perform statistics on readout %s.\n", readoutName);
+            psErrorClear();
+        }
+        psFree(sampleValues);
+        psFree(sampleMask);
+    }
+
+#define WRITE_STAT(SYMBOL, NAME, SOURCE) \
+    if (data->stats->options & SYMBOL) { \
+        psMetadataAddF32(readoutResults, PS_LIST_TAIL, NAME, 0, NULL, data->stats->SOURCE); \
+    }
+
+    WRITE_STAT(PS_STAT_SAMPLE_MEAN,     "SAMPLE_MEAN",     sampleMean);
+    WRITE_STAT(PS_STAT_SAMPLE_MEDIAN,   "SAMPLE_MEDIAN",   sampleMedian);
+    WRITE_STAT(PS_STAT_SAMPLE_STDEV,    "SAMPLE_STDEV",    sampleStdev);
+    WRITE_STAT(PS_STAT_SAMPLE_SKEWNESS, "SAMPLE_SKEWNESS", sampleSkewness);
+    WRITE_STAT(PS_STAT_SAMPLE_KURTOSIS, "SAMPLE_KURTOSIS", sampleKurtosis);
+    WRITE_STAT(PS_STAT_SAMPLE_QUARTILE, "SAMPLE_LQ",       sampleLQ);
+    WRITE_STAT(PS_STAT_SAMPLE_QUARTILE, "SAMPLE_UQ",       sampleUQ);
+    WRITE_STAT(PS_STAT_ROBUST_MEDIAN,   "ROBUST_MEDIAN",   robustMedian);
+    WRITE_STAT(PS_STAT_ROBUST_STDEV,    "ROBUST_STDEV",    robustStdev);
+    WRITE_STAT(PS_STAT_ROBUST_QUARTILE, "ROBUST_LQ",       robustLQ);
+    WRITE_STAT(PS_STAT_ROBUST_QUARTILE, "ROBUST_UQ",       robustUQ);
+    WRITE_STAT(PS_STAT_ROBUST_QUARTILE, "ROBUST_N50",      robustN50);
+    WRITE_STAT(PS_STAT_FITTED_MEAN,     "FITTED_MEAN",     fittedMean);
+    WRITE_STAT(PS_STAT_FITTED_STDEV,    "FITTED_STDEV",    fittedStdev);
+    WRITE_STAT(PS_STAT_CLIPPED_MEAN,    "CLIPPED_MEAN",    clippedMean);
+    WRITE_STAT(PS_STAT_CLIPPED_STDEV,   "CLIPPED_STDEV",   clippedStdev);
+
+    // measure other types of statistics tests
+
+statsDone:
+    // count saturated pixels
+    if (psListLength(data->summary) > 0) {
+        bool get_nSatPixels = false;
+        bool get_fSatPixels = false;
+        bool findNumGood = false;       // Return the number of good pixels?
+        bool findFracGood = false;      // Return the fraction of good pixels?
+
+        psListIterator *iterator = psListIteratorAlloc(data->summary, PS_LIST_HEAD, false);
+        psString choice;
+        while ((choice = psListGetAndIncrement(iterator))) {
+            if (!strcasecmp(choice, "SAT_PIXEL_NUM"))  get_nSatPixels = true;
+            if (!strcasecmp(choice, "SAT_PIXEL_FRAC")) get_fSatPixels = true;
+            if (!strcasecmp(choice, "GOOD_PIXEL_NUM")) findNumGood = true;
+            if (!strcasecmp(choice, "GOOD_PIXEL_NUM")) findFracGood = true;
+        }
+
+        if (get_nSatPixels || get_fSatPixels) {
+            // Find the saturation point
+            float saturation = psMetadataLookupF32(&mdok, readout->parent->concepts,
+                                                   "CELL.SATURATION"); // Saturation level
+            if (!mdok || isnan(saturation)) {
+                psWarning("CELL.SATURATION is not set --- unable to measure N_SAT_PIXELS.\n");
+                if (get_nSatPixels) {
+                    psMetadataAddS32(readoutResults, PS_LIST_TAIL, "SAT_PIXEL_NUM", 0, NULL, 0);
+                }
+                if (get_fSatPixels) {
+                    psMetadataAddF32(readoutResults, PS_LIST_TAIL, "SAT_PIXEL_FRAC", 0, NULL, NAN);
+                }
+            } else {
+                int nSatPixels = 0;
+                for (int j = 0; j < readout->image->numRows; j++) {
+                    for (int i = 0; i < readout->image->numCols; i++) {
+                        if (readout->image->data.F32[j][i] >= saturation) {
+                            nSatPixels ++;
+                        }
+                    }
+                }
+                if (get_nSatPixels) {
+                    psMetadataAddS32(readoutResults, PS_LIST_TAIL, "SAT_PIXEL_NUM", 0,
+                                     "Number of saturated pixels", nSatPixels);
+                }
+                if (get_fSatPixels) {
+                    psMetadataAddF32(readoutResults, PS_LIST_TAIL, "SAT_PIXEL_FRAC", 0,
+                                     "Fraction of saturated pixels",
+                                     nSatPixels / (float)(readout->image->numRows * readout->image->numCols));
+                }
+            }
+        }
+
+        if (findNumGood || findFracGood) {
+            if (!readout->mask || data->maskVal == 0) {
+                psWarning("Number or fraction of good pixels requested, but no mask provided");
+                if (findNumGood) {
+                    psMetadataAddS32(readoutResults, PS_LIST_TAIL, "GOOD_PIXEL_NUM", 0, NULL, 0);
+                }
+                if (findFracGood) {
+                    psMetadataAddF32(readoutResults, PS_LIST_TAIL, "GOOD_PIXEL_FRAC", 0, NULL, NAN);
+                }
+            } else {
+                int numBad = 0;            // Number of bad pixels
+                for (int j = 0; j < readout->mask->numRows; j++) {
+                    for (int i = 0; i < readout->mask->numCols; i++) {
+                        if (readout->mask->data.PS_TYPE_MASK_DATA[j][i] & data->maskVal) {
+                            numBad++;
+                        }
+                    }
+                }
+
+                int numTotal = readout->mask->numRows * readout->mask->numCols; // Total number of pixels
+                int numGood = numTotal - numBad; // Number of good pixels
+                if (findNumGood) {
+                    psMetadataAddS32(readoutResults, PS_LIST_TAIL, "GOOD_PIXEL_NUM", 0,
+                                     "Number of good pixels", numGood);
+                }
+                if (findFracGood) {
+                    psMetadataAddF32(readoutResults, PS_LIST_TAIL, "GOOD_PIXEL_FRAC", 0,
+                                     "Fraction of good pixels", numGood / (float)numTotal);
+                }
+            }
+        }
+    }
+
+readoutDone:
+    // Add the readout results to the cell
+    p_ppStatsAddToHierarchy(readoutResults, cellResults, readoutName, "Results for readout");
+    psFree (readoutResults);
+    psFree (readoutName);
+    return PS_EXIT_SUCCESS;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsSetupFromArgs.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsSetupFromArgs.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsSetupFromArgs.c	(revision 22157)
@@ -0,0 +1,224 @@
+#include "ppStatsInternal.h"
+
+// This file is for setting up the required inputs from the command-line
+
+// Print usage information and die
+static void usageAndDie(char *argv[],   // Command-line arguments: only need the first which is always present
+                        pmConfig *config // Configuration
+    )
+{
+    printf("Return headers, concepts and/or image statistics.\n\n"
+           "Usage:\n"
+           "\t%s INPUT.fits [OUTPUT_NAME]\n"
+           "\n", argv[0]);
+    psArgumentHelp(config->arguments);
+    psFree(config);
+    psLibFinalize();
+    pmConceptsDone();
+    pmConfigDone();
+    exit(EXIT_FAILURE);
+}
+
+// Generate a list from the arguments
+static void listFromArguments(psMetadata *arguments, // Arguments to parse
+                              const char *name, // Name of the item, for error message
+                              const char *flag, // The flag for the argument of interest
+                              psList *target // The target list
+    )
+{
+    psString regex = NULL;              // Regular expression for the flag
+    psStringAppend(&regex, "^%s$", flag);
+    psMetadataIterator *iterator = psMetadataIteratorAlloc(arguments, PS_LIST_HEAD, regex); // Iterator
+    psFree(regex);
+    psMetadataItem *item;               // Item from iteration
+    while ((item = psMetadataGetAndIncrement(iterator))) {
+        if (item->type != PS_DATA_STRING) {
+            psLogMsg(__func__, PS_LOG_WARN, "%s name is not of type STRING (%x) --- ignored.\n",
+                     name, item->type);
+            continue;
+        }
+        if (item->data.V && strlen(item->data.V) > 0) {
+            psListAdd(target, PS_LIST_TAIL, item->data.V);
+        }
+    }
+    psFree(iterator);
+
+    return;
+}
+
+// Set the statistics option; for arguments
+static inline void statsOptionArguments(psMetadata *arguments, // Arguments to parse
+                                        const char *name, // Name for option
+                                        ppStatsData *data, // Configuration data
+                                        psStatsOptions option // Option to check for
+    )
+{
+    if (psMetadataLookupBool(NULL, arguments, name)) {
+        data->stats->options |= option;
+        data->doStats = true;
+    }
+    return;
+}
+
+// Print out what we're going to do, from the list
+static void checkList(psList *list,     // List
+                      const char *name  // Name of list
+    )
+{
+    psString value;
+    psListIterator *iterator = psListIteratorAlloc(list, PS_LIST_HEAD, false);
+    while ((value = psListGetAndIncrement(iterator))) {
+        printf("%s: %s\n", name, value);
+    }
+    psFree(iterator);
+    return;
+}
+
+
+ppStatsData *ppStatsSetupFromArgs(int *argc, char *argv[], // Command-line arguments
+                                  pmConfig *config // Configuration
+    )
+{
+    // Setup and parse command-line arguments
+    psMetadata *arguments = config->arguments; // Arguments
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-chip", PS_META_DUPLICATE_OK, "Chip to inspect", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-cell", PS_META_DUPLICATE_OK, "Cell to inspect", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-header", PS_META_DUPLICATE_OK, "Header to look up", NULL);
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-concept", PS_META_DUPLICATE_OK, "Concept to look up", NULL);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-mean", 0, "Calculate sample mean", false);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-stdev", 0, "Calculate sample standard deviation", false);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-median", 0, "Calculate sample median", false);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-quartile", 0, "Calculate sample quartiles", false);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-skewness", 0, "Calculate sample skewness", false);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-kurtosis", 0, "Calculate sample kurtosis", false);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-robust-median", 0, "Calculate robust median", false);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-robust-stdev", 0, "Calculate robust standard deviation", false);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-robust-quartile", 0, "Calculate robust quartile range", false);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-fitted-mean", 0, "Calculate fitted mean", false);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-fitted-stdev", 0, "Calculate fitted standard deviation", false);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-clipped-mean", 0, "Calculate clipped median", false);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-clipped-stdev", 0, "Calculate clipped standard deviation", false);
+    psMetadataAddS32(arguments, PS_LIST_TAIL, "-iter", 0, "Clipping iterations", 0);
+    psMetadataAddF32(arguments, PS_LIST_TAIL, "-rej", 0, "Clipping level", 0.0);
+    psMetadataAddF32(arguments, PS_LIST_TAIL, "-sample", 0, "Sampling fraction", 0.0);
+    psMetadataAddBool(arguments, PS_LIST_TAIL, "-level", 0, "File level", 0);
+
+    if (*argc == 1) {
+        // No command-line arguments: print the help
+        usageAndDie(argv, config);
+    }
+    if (!psArgumentParse(arguments, argc, argv) ||
+        (*argc != 2 && *argc != 3)) {
+        printf("Unable to parse command-line arguments.\n\n");
+        usageAndDie(argv, config);
+    }
+
+    // Parse the command-line options
+    ppStatsData *data = ppStatsDataAlloc(); // The data
+    const char *inName = argv[1]; // Input file name
+    psArgumentRemove(1, argc, argv);
+
+    listFromArguments(arguments, "Chip", "-chip", data->chips);
+    listFromArguments(arguments, "Cell", "-cell", data->cells);
+    listFromArguments(arguments, "Header", "-header", data->headers);
+    listFromArguments(arguments, "Concept", "-concept", data->concepts);
+
+    // Set the statistics options
+    statsOptionArguments(arguments, "-mean", data,     PS_STAT_SAMPLE_MEAN);
+    statsOptionArguments(arguments, "-stdev", data,    PS_STAT_SAMPLE_STDEV);
+    statsOptionArguments(arguments, "-median", data,   PS_STAT_SAMPLE_MEDIAN);
+    statsOptionArguments(arguments, "-quartile", data, PS_STAT_SAMPLE_QUARTILE);
+    statsOptionArguments(arguments, "-skewness", data, PS_STAT_SAMPLE_SKEWNESS);
+    statsOptionArguments(arguments, "-kurtosis", data, PS_STAT_SAMPLE_KURTOSIS);
+    statsOptionArguments(arguments, "-robust-median", data,   PS_STAT_ROBUST_MEDIAN);
+    statsOptionArguments(arguments, "-robust-stdev", data,    PS_STAT_ROBUST_STDEV);
+    statsOptionArguments(arguments, "-robust-quartile", data, PS_STAT_ROBUST_QUARTILE);
+    statsOptionArguments(arguments, "-fitted-mean", data,   PS_STAT_FITTED_MEAN);
+    statsOptionArguments(arguments, "-fitted-stdev", data,  PS_STAT_FITTED_STDEV);
+    statsOptionArguments(arguments, "-clipped-mean", data,  PS_STAT_CLIPPED_MEAN);
+    statsOptionArguments(arguments, "-clipped-stdev", data, PS_STAT_CLIPPED_STDEV);
+    data->stats->clipSigma = psMetadataLookupF32(NULL, arguments, "-rej");
+    data->stats->clipIter = psMetadataLookupS32(NULL, arguments, "-iter");
+    data->sample = psMetadataLookupF32(NULL, arguments, "-sample");
+    data->fileLevel = psMetadataLookupBool(NULL, arguments, "-level");
+    data->fileView = NULL;
+
+    // Open the input file, determine the camera
+    int result = PS_EXIT_UNKNOWN_ERROR;
+    {
+        psString resolved = pmConfigConvertFilename(inName, config, false, false); // Resolved filename
+        data->fits = psFitsOpen(resolved, "r");
+        if (!data->fits) {
+            psError(PS_ERR_IO, false, "Unable to open input file %s\n", resolved);
+            psFree(resolved);
+            result = PS_EXIT_DATA_ERROR;
+            goto die;
+        }
+        psFree(resolved);
+
+        psMetadata *header = psFitsReadHeader(NULL, data->fits); // The FITS (primary) header
+        psMetadata *format = pmConfigCameraFormatFromHeader(NULL, NULL, config, header, true);
+        if (!format) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to determine camera format for %s\n", inName);
+            psFree(header);
+            result = PS_EXIT_DATA_ERROR;
+            goto die;
+        }
+
+        // need to handle optional alternate EXTWORD value
+        psMetadata *fileMenu = psMetadataLookupMetadata (NULL, format, "FILE");
+        if (!fileMenu) {
+            psError (PS_ERR_IO, true, "FILE METADATA missing from camera format\n");
+            return false;
+        }
+        bool status = false;
+        char *extword = psMetadataLookupStr (&status, fileMenu, "EXTWORD");
+        if (status) {
+            psFitsSetExtnameWord (data->fits, extword);
+        }
+
+        data->fpa = pmFPAConstruct(config->camera, config->cameraName);
+        if (!data->fpa) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to construct FPA for %s\n", resolved);
+            psFree(header);
+            psFree(format);
+            result = PS_EXIT_CONFIG_ERROR;
+            goto die;
+        }
+        data->fileView = pmFPAAddSourceFromHeader(data->fpa, header, format);
+        psFree(header);
+        psFree(format);
+        if (!data->fileView) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to add input file %s to FPA.\n", inName);
+            result = PS_EXIT_CONFIG_ERROR;
+            goto die;
+        }
+    }
+
+    // Get the rest from the recipe
+    if (!ppStatsSetupFromRecipe(data, config)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read ppStats options from recipe.");
+        psFree(data);
+        return NULL;
+    }
+
+    // Print out what we're going to do
+    if (psTraceGetLevel("ppStats") > 9) {
+        checkList(data->chips, "CHIP");
+        checkList(data->cells, "CELL");
+        checkList(data->headers, "HEADER");
+        checkList(data->concepts, "CONCEPT");
+    }
+
+    return data;
+
+    // Common path for error conditions: clean up and exit.
+die:
+    psErrorStackPrint(stderr, "Unable to set ppStats parameters from command-line arguments");
+    psFree(config);
+    psFree(data);
+    pmConceptsDone();
+    pmConfigDone();
+    psLibFinalize();
+    exit(result);
+}
Index: /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsSetupFromRecipe.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsSetupFromRecipe.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsSetupFromRecipe.c	(revision 22157)
@@ -0,0 +1,140 @@
+#include "ppStatsInternal.h"
+
+// Strings in a recipe may be defined multiply (with MULTI) or listed on a single line
+static void listFromRecipe(psList *target, // The target list
+                           const psMetadata *recipe, // Recipe to search
+                           const char *name // Name for item within recipe
+    )
+{
+    // If the list already has entries, don't read anything else
+    if (psListLength(target) > 0) {
+        return;
+    }
+
+    // First check that at least one item of interest exists
+    psMetadataItem *checkItem = psMetadataLookup(recipe, name);
+    if (!checkItem) {
+        // Nothing to see here
+        return;
+    }
+
+    psString regex = NULL;              // Regular expression for the flag
+    psStringAppend(&regex, "^%s$", name);
+    psMetadataIterator *iterator = psMetadataIteratorAlloc(recipe, PS_LIST_HEAD, regex);
+    psFree(regex);
+    psMetadataItem *item;
+    int numItem = 0; // Occurrence of the item in the recipe; to help the user in case of trouble
+    while ((item = psMetadataGetAndIncrement(iterator))) {
+        numItem++;
+        if (item->type != PS_DATA_STRING) {
+            psLogMsg(__func__, PS_LOG_WARN, "Occurrence %d of %s in the recipe is "
+                     "not of type STRING (%x) --- ignored.\n", numItem, name, item->type);
+            continue;
+        }
+        // Parse into a list of independent values
+        psList *values = psStringSplit(item->data.V, " ,;", false);
+        // Copy into the target
+        psListIterator *valuesIter = psListIteratorAlloc(values, PS_LIST_HEAD, false);
+        psString valueString;
+        while ((valueString = psListGetAndIncrement(valuesIter))) {
+            // Remove the default _UNDEF entries in the recipe, which come from the limitation of the metadata
+            // config language to have an empty MULTI, combined with the constraint that higher-level recipes
+            // aren't allowed to add new values but only change what already exists.
+            if (strcmp(valueString, "_UNDEF") != 0) {
+                psListAdd(target, PS_LIST_TAIL, valueString);
+            }
+        }
+        psFree(valuesIter);
+        psFree(values);
+    }
+    psFree(iterator);
+
+    return;
+}
+
+
+bool ppStatsSetupFromRecipe(ppStatsData *data, // Data for running ppStats
+                            pmConfig *config // Configuration
+    )
+{
+    PS_ASSERT_PTR_NON_NULL(data, false);
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // Determine recipe parameters
+    bool mdok;                          // Status of MD lookup
+    psMetadata *recipe = psMetadataLookupMetadata(&mdok, config->recipes, PPSTATS_RECIPE);
+    if (!mdok || !recipe) {
+        psLogMsg(__func__, PS_LOG_WARN, "Unable to find recipe %s.\n", PPSTATS_RECIPE);
+        return data;
+    }
+
+    listFromRecipe(data->chips,    recipe, "CHIP");
+    listFromRecipe(data->cells,    recipe, "CELL");
+    listFromRecipe(data->headers,  recipe, "HEADER");
+    listFromRecipe(data->concepts, recipe, "CONCEPT");
+    listFromRecipe(data->analysis, recipe, "ANALYSIS");
+
+    // Parse SUMMARY statistics information
+    listFromRecipe(data->summary, recipe, "SUMMARY");
+
+    // Parse the statistics options
+    psList *recipeStats = psListAlloc(NULL); // List of statistics options
+    listFromRecipe(recipeStats, recipe, "STAT");
+
+    // validate STATs choices
+    if (psListLength(recipeStats) > 0) {
+        psListIterator *iterator = psListIteratorAlloc(recipeStats, PS_LIST_HEAD, false);
+        psString statString;            // Statistic string, from iteration
+        while ((statString = psListGetAndIncrement(iterator))) {
+            psStatsOptions stat = psStatsOptionFromString(statString);
+            if (stat == 0) {
+                psLogMsg(__func__, PS_LOG_WARN, "Can't interpret STATS entry in recipe: "
+                         "%s --- ignored.\n", statString);
+                continue;
+            }
+            data->stats->options |= stat;
+            data->doStats = true;
+        }
+    }
+    psFree(recipeStats);
+
+    // Clipping options
+    if (data->stats->clipIter == 0 && isnan(data->stats->clipSigma)) {
+        int iter = psMetadataLookupS32(&mdok, recipe, "ITER"); // Number of clipping iterations
+        if (mdok && iter > 0) {
+            data->stats->clipIter = iter;
+        } else {
+            psLogMsg(__func__, PS_LOG_WARN, "ITER in recipe is not of type S32 and positive --- "
+                     "retaining default.\n");
+        }
+        float rej = psMetadataLookupF32(&mdok, recipe, "REJ"); // Clipping level
+        if (mdok && rej > 0) {
+            data->stats->clipSigma = rej;
+        } else {
+            psLogMsg(__func__, PS_LOG_WARN, "REJ in recipe is not of type F32 and positive --- "
+                     "retaining default.\n");
+        }
+    }
+
+    if (data->sample == 0) {
+        float sample = psMetadataLookupF32(&mdok, recipe, "SAMPLE"); // Sample fraction
+        if (mdok && sample > 0) {
+            data->sample = sample;
+        } else {
+            psLogMsg(__func__, PS_LOG_WARN, "SAMPLE in recipe is not of type F32 and positive --- "
+                     "retaining default.\n");
+        }
+    }
+
+    // set the mask value used for stand-alone analyses.
+    if (data->maskVal == 0) {
+        const char *names = psMetadataLookupStr(&mdok, recipe, "MASKVAL"); // Names for mask value
+        if (mdok) {
+            data->maskVal = pmConfigMaskGet(names, config);
+        } else {
+            psWarning("MASKVAL in recipe is not of type STR --- retaining default.\n");
+        }
+    }
+
+    return data;
+}
Index: /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsUtils.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsUtils.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsUtils.c	(revision 22157)
@@ -0,0 +1,83 @@
+# include "ppStatsInternal.h"
+
+void p_ppStatsGetMetadata(psMetadata *target, // Target for metadata
+                          psMetadata *source, // Source for metadata
+                          psList *list    // List containing keywords
+    )
+{
+    assert(target);
+    assert(list);
+    if (!source) {
+        // Nothing to get from!
+        return;
+    }
+
+    psListIterator *iterator = psListIteratorAlloc(list, PS_LIST_HEAD, false); // Iterator
+    psString name;                      // Name from iteration
+    while ((name = psListGetAndIncrement(iterator))) {
+        psMetadataItem *item = psMetadataLookup(source, name); // Item of interest, or NULL
+        if (item) {
+            psMetadataAddItem(target, item, PS_LIST_TAIL, PS_META_REPLACE);
+        }
+    }
+    psFree(iterator);
+    return;
+}
+
+void p_ppStatsGetAnalysis(psMetadata *target, // Output Target for metadata
+                          psList *headers,    // List containing desired keywords
+                          psMetadata *source, // Input Source for metadata
+                          psList *list        // List containing analysis blocks
+    )
+{
+    bool status;
+
+    psListIterator *iterator = psListIteratorAlloc(list, PS_LIST_HEAD, false); // Iterator
+    psString name;                      // Name from iteration
+    while ((name = psListGetAndIncrement(iterator))) {
+        psMetadata *folder = psMetadataLookupMetadata(&status, source, name); // Item of interest, or NULL
+        if (folder) {
+            p_ppStatsGetMetadata (target, folder, headers);
+        }
+    }
+    psFree(iterator);
+    return;
+}
+
+bool p_ppStatsDoThis(psList *toDoList,    // List of things to do
+                     const char *this     // The name of "this"
+    )
+{
+    if (psListLength(toDoList) == 0) {
+        // No list --- do everything
+        return true;
+    }
+
+    psListIterator *iterator = psListIteratorAlloc(toDoList, PS_LIST_HEAD, false); // Iterator
+    psString test;                      // Test string, from iteration
+    while ((test = psListGetAndIncrement(iterator))) {
+        if (strcmp(this, test) == 0) {
+            // It's in the list --- do it
+            psFree(iterator);
+            return true;
+        }
+    }
+    psFree(iterator);
+    // Couldn't find it --- don't do it
+    return false;
+}
+
+void p_ppStatsAddToHierarchy(psMetadata *source, // Source to add
+                             psMetadata *target, // Target to which to add
+                             const char *name, // Name of source
+                             const char *comment // Comment for source
+    )
+{
+    if (psListLength(source->list) > 0 && !psMetadataLookup(target, name)) {
+        psMetadataAdd(target, PS_LIST_TAIL, name, PS_DATA_METADATA | PS_META_REPLACE,
+                      comment, source);
+    }
+    return;
+}
+
+
Index: /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsVersion.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsVersion.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/ppStats/src/ppStatsVersion.c	(revision 22157)
@@ -0,0 +1,19 @@
+#include "ppStatsInternal.h"
+
+static const char *cvsTag = "$Name: not supported by cvs2svn $";// CVS tag name
+
+psString ppStatsVersion(void)
+{
+    psString version = NULL;            // Version, to return
+    psStringAppend(&version, "%s-%s",PACKAGE_NAME,PACKAGE_VERSION);
+    return version;
+}
+
+psString ppStatsVersionLong(void)
+{
+    psString version = ppStatsVersion(); // Version, to return
+    psString tag = psStringStripCVS(cvsTag, "Name"); // CVS tag
+    psStringAppend(&version, " (cvs tag %s) %s, %s", tag, __DATE__, __TIME__);
+    psFree(tag);
+    return version;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/.cvsignore	(revision 22157)
@@ -0,0 +1,29 @@
+autom4te.cache
+Makefile
+config.log
+config.status
+libtool
+Makefile.in
+aclocal.m4
+configure
+Doxyfile
+DoxygenLog
+docs
+man
+psmodules-config
+psmodules.pc
+bin
+include
+lib
+config.guess
+config.sub
+depcomp
+install-sh
+ltmain.sh
+missing
+psmodule.kdevelop.pcs
+psmodule-*.tar.gz
+psmodule-*.tar.bz2
+compile
+ChangeLog
+psmodules-*.tar.*
Index: /branches/eam_branches/eam_branch_20080706/psModules/AUTHORS
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/AUTHORS	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/AUTHORS	(revision 22157)
@@ -0,0 +1,1 @@
+Maui High Performance Computing Center, University of Hawai'i
Index: /branches/eam_branches/eam_branch_20080706/psModules/COPYING
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/COPYING	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/COPYING	(revision 22157)
@@ -0,0 +1,6 @@
+(C) 2004 University of Hawai'i
+
+Permission to copy/distribute to be governed by:
+    Institute for Astronomy
+    2680 Woodlawn Drive
+    Honolulu, HI 96822-1897
Index: /branches/eam_branches/eam_branch_20080706/psModules/Doxyfile.in
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/Doxyfile.in	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/Doxyfile.in	(revision 22157)
@@ -0,0 +1,1079 @@
+# Doxyfile 1.3.4
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME           = "Pan-STARRS Module Library"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER         = @VERSION@
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = @builddir@/docs
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch,
+# Finnish, French, German, Greek, Hungarian, Italian, Japanese, Japanese-en
+# (Japanese with English messages), Korean, Norwegian, Polish, Portuguese,
+# Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish, and Ukrainian.
+
+OUTPUT_LANGUAGE        = English
+
+# This tag can be used to specify the encoding used in the generated output.
+# The encoding is not always determined by the language that is chosen,
+# but also whether or not the output is meant for Windows or non-Windows users.
+# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES
+# forces the Windows encoding (this is the default for the Windows binary),
+# whereas setting the tag to NO uses a Unix-style encoding (the default for
+# all platforms other than Windows).
+
+USE_WINDOWS_ENCODING   = NO
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC    = YES
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all inherited
+# members of a class in the documentation of that class as if those members were
+# ordinary class members. Constructors, destructors and assignment operators of
+# the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. It is allowed to use relative paths in the argument list.
+
+STRIP_FROM_PATH        =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful is your file systems
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like the Qt-style comments (thus requiring an
+# explict @brief command for a brief description.
+
+JAVADOC_AUTOBRIEF      = YES
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the DETAILS_AT_TOP tag is set to YES then Doxygen
+# will output the detailed description near the top, like JavaDoc.
+# If set to NO, the detailed description appears after the member
+# documentation.
+
+DETAILS_AT_TOP         = YES
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# reimplements.
+
+INHERIT_DOCS           = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 4
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java sources
+# only. Doxygen will then generate output that is more tailored for Java.
+# For instance, namespaces will be presented as packages, qualified scopes
+# will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING            = NO
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any 
+# documentation blocks found inside the body of a function. 
+# If set to NO (the default) these blocks will be appended to the 
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation 
+# that is typed after a \internal command is included. If the tag is set 
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate 
+# file names in lower-case letters. If set to YES upper-case letters are also 
+# allowed. This is useful if you have classes or files whose names only differ 
+# in case and if your file system supports case sensitive file names. Windows 
+# users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen 
+# will show members with their full class and namespace scopes in the 
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen 
+# will put a list of the files that are included by a file in the documentation 
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] 
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen 
+# will sort the (detailed) documentation of file and class members 
+# alphabetically by member name. If set to NO the members will appear in 
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or 
+# disable (NO) the todo list. This list is created by putting \todo 
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or 
+# disable (NO) the test list. This list is created by putting \test 
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or 
+# disable (NO) the bug list. This list is created by putting \bug 
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or 
+# disable (NO) the deprecated list. This list is created by putting 
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional 
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines 
+# the initial value of a variable or define consists of for it to appear in 
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely. 
+# The appearance of the initializer of individual variables and defines in the 
+# documentation can be controlled using \showinitializer or \hideinitializer 
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated 
+# at the bottom of the documentation of classes and structs. If set to YES the 
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated 
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are 
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings 
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will 
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for 
+# potential errors in the documentation, such as not documenting some 
+# parameters in a documented function, or documenting parameters that 
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# The WARN_FORMAT tag determines the format of the warning messages that 
+# doxygen can produce. The string should contain the $file, $line, and $text 
+# tags, which will be replaced by the file and line number from which the 
+# warning originated and the warning text.
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning 
+# and error messages should be written. If left blank the output is written 
+# to stderr.
+
+WARN_LOGFILE           = DoxygenLog
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain 
+# documented source files. You may enter file names like "myfile.cpp" or 
+# directories like "/usr/src/myproject". Separate the files or directories 
+# with spaces.
+
+INPUT                  = src
+
+# If the value of the INPUT tag contains directories, you can use the 
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp
+# *.h++ *.idl *.odl *.cs *.php *.php3 *.inc
+
+FILE_PATTERNS          = *.h *.dox
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a 
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or directories 
+# that are symbolic links (a Unix filesystem feature) are excluded from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the 
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude 
+# certain files from those directories.
+
+EXCLUDE_PATTERNS       = *_wrap.c
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see 
+# the \include command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the 
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank all files are included.
+
+EXAMPLE_PATTERNS       = 
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be 
+# searched for input files to be used with the \include or \dontinclude 
+# commands irrespective of the value of the RECURSIVE tag. 
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or 
+# directories that contain image that are included in the documentation (see 
+# the \image command).
+
+IMAGE_PATH             = 
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should 
+# invoke to filter for each input file. Doxygen will invoke the filter program 
+# by executing (via popen()) the command <filter> <input-file>, where <filter> 
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an 
+# input file. Doxygen will then use the output that the filter program writes 
+# to standard output.
+
+INPUT_FILTER           = 
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using 
+# INPUT_FILTER) will be used to filter the input files when producing source 
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will 
+# be generated. Documented entities will be cross-referenced with these sources.
+
+SOURCE_BROWSER         = YES
+
+# Setting the INLINE_SOURCES tag to YES will include the body 
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct 
+# doxygen to hide any special comment blocks from generated source code 
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = NO
+
+# If the REFERENCED_BY_RELATION tag is set to YES (the default) 
+# then for each documented function all documented 
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES (the default) 
+# then for each documented function all documented entities 
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = YES
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen 
+# will generate a verbatim copy of the header file for each class for 
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index 
+# of all compounds will be generated. Enable this if the project 
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = YES
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then 
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns 
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all 
+# classes will be put under the same header in the alphabetical index. 
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that 
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will 
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for 
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank 
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard header.
+
+HTML_HEADER            = 
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard footer.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading 
+# style sheet that is used by each HTML page. It can be used to 
+# fine-tune the look of the HTML output. If the tag is left blank doxygen 
+# will generate a default style sheet
+
+HTML_STYLESHEET        = 
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, 
+# files or namespaces will be aligned in HTML using tables. If set to 
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS     = YES
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files 
+# will be generated that can be used as input for tools like the 
+# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) 
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can 
+# be used to specify the file name of the resulting .chm file. You 
+# can add a path in front of the file if the result should not be 
+# written to the html output dir.
+
+CHM_FILE               = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can 
+# be used to specify the location (absolute path including file name) of 
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run 
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag 
+# controls if a separate .chi index file is generated (YES) or that 
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag 
+# controls whether a binary table of contents is generated (YES) or a 
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members 
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = YES
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at 
+# top of each HTML page. The value NO (the default) enables the index and 
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# This tag can be used to set the number of enum values (range [1..20]) 
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE   = 10
+
+# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
+# generated containing a tree-like index structure (just like the one that 
+# is generated for HTML Help). For this to work a browser that supports 
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, 
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are 
+# probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW      = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be 
+# used to set the initial width (in pixels) of the frame in which the tree 
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will 
+# generate Latex output.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be 
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to 
+# generate index for LaTeX. If left blank `makeindex' will be used as the 
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact 
+# LaTeX documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_LATEX          = YES
+
+# The PAPER_TYPE tag can be used to set the paper type that is used 
+# by the printer. Possible values are: a4, a4wide, letter, legal and 
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = letter
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX 
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         = times
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for 
+# the generated latex document. The header should contain everything until 
+# the first chapter. If it is left blank doxygen will generate a 
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           = 
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated 
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will 
+# contain links (just like the HTML output) instead of page references 
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of 
+# plain latex in the generated Makefile. Set this option to YES to get a 
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. 
+# command to the generated LaTeX files. This will instruct LaTeX to keep 
+# running if errors occur, instead of asking the user for help. 
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not 
+# include the index chapters (such as File Index, Compound Index, etc.) 
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output 
+# The RTF output is optimised for Word 97 and may not look very pretty with 
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact 
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated 
+# will contain hyperlink fields. The RTF file will 
+# contain links (just like the HTML output) instead of page references. 
+# This makes the output suitable for online browsing using WORD or other 
+# programs which support those fields. 
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's 
+# config file, i.e. a series of assigments. You only have to provide 
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    = 
+
+# Set optional variables used in the generation of an rtf document. 
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will 
+# generate man pages
+
+GENERATE_MAN           = YES
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output, 
+# then it will generate one additional man file for each entity 
+# documented in the real man page(s). These additional files 
+# only source the real man page, but without them the man command 
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will 
+# generate an XML file that captures the structure of 
+# the code including all documentation. Note that this 
+# feature is still experimental and incomplete at the 
+# moment.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_SCHEMA             = 
+
+# The XML_DTD tag can be used to specify an XML DTD, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_DTD                = 
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will 
+# generate an AutoGen Definitions (see autogen.sf.net) file 
+# that captures the structure of the code including all 
+# documentation. Note that this feature is still experimental 
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will 
+# generate a Perl module file that captures the structure of 
+# the code including all documentation. Note that this 
+# feature is still experimental and incomplete at the 
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate 
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able 
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be 
+# nicely formatted so it can be parsed by a human reader.  This is useful 
+# if you want to understand what is going on.  On the other hand, if this 
+# tag is set to NO the size of the Perl module output will be much smaller 
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file 
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. 
+# This is useful so different doxyrules.make files included by the same 
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX = 
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro 
+# names in the source code. If set to NO (the default) only conditional 
+# compilation will be performed. Macro expansion can be done in a controlled 
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES 
+# then the macro expansion is limited to the macros specified with the 
+# PREDEFINED and EXPAND_AS_PREDEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files 
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that 
+# contain include files that are not input files but should be processed by 
+# the preprocessor.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard 
+# patterns (like *.h and *.hpp) to filter out the header-files in the 
+# directories. If left blank, the patterns specified with FILE_PATTERNS will 
+# be used.
+
+INCLUDE_FILE_PATTERNS  = 
+
+# The PREDEFINED tag can be used to specify one or more macro names that 
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed.
+
+PREDEFINED             = DOXYGEN
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used. 
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED      = 
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then 
+# doxygen's preprocessor will remove all function-like macros that are alone 
+# on a line, have an all uppercase name, and do not end with a semicolon. Such 
+# function macros are typically used for boiler-plate code, and will confuse the 
+# parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::addtions related to external references   
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles. 
+# Optionally an initial location of the external documentation 
+# can be added for each tagfile. The format of a tag file without 
+# this location is as follows: 
+#   TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows: 
+#   TAGFILES = file1=loc1 "file2 = loc2" ... 
+# where "loc1" and "loc2" can be relative or absolute paths or 
+# URLs. If a location is present for each tag, the installdox tool 
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen 
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create 
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed 
+# in the class index. If set to NO only the inherited external classes 
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed 
+# in the modules index. If set to NO, only the current project's groups will 
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script 
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = @PERL@
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool   
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will 
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base or
+# super classes. Setting the tag to NO turns the diagrams off. Note that this 
+# option is superceded by the HAVE_DOT option below. This is only a fallback. It is 
+# recommended to install and use dot, since it yields more powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# If set to YES, the inheritance and collaboration graphs will hide 
+# inheritance and usage relations if the target is undocumented 
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is 
+# available from the path. This tool is part of Graphviz, a graph visualization 
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section 
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = YES
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect inheritance relations. Setting this tag to YES will force the 
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect implementation dependencies (inheritance, containment, and 
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and 
+# collaboration diagrams in a style similiar to the OMG's Unified Modeling 
+# Language.
+
+UML_LOOK               = YES
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT 
+# tags are set to YES then doxygen will generate a graph for each documented 
+# file showing the direct and indirect include dependencies of the file with 
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and 
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each 
+# documented header file showing the documented files that directly or 
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will 
+# generate a call dependency graph for every global function or class method. 
+# Note that enabling this option will significantly increase the time of a run. 
+# So in most cases it will be better to enable call graphs for selected 
+# functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen 
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images 
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be 
+# found. If left blank, it is assumed the dot tool can be found on the path.
+
+DOT_PATH               = 
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that 
+# contain dot files that are included in the documentation (see the 
+# \dotfile command).
+
+DOTFILE_DIRS           = 
+
+# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width 
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than 
+# this value, doxygen will try to truncate the graph, so that it fits within 
+# the specified constraint. Beware that most browsers cannot cope with very 
+# large images.
+
+MAX_DOT_GRAPH_WIDTH    = 1024
+
+# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height 
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than 
+# this value, doxygen will try to truncate the graph, so that it fits within 
+# the specified constraint. Beware that most browsers cannot cope with very 
+# large images.
+
+MAX_DOT_GRAPH_HEIGHT   = 1024
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the 
+# graphs generated by dot. A depth value of 3 means that only nodes reachable 
+# from the root by following a path via at most 3 edges will be shown. Nodes that 
+# lay further from the root node will be omitted. Note that setting this option to 
+# 1 or 2 may greatly reduce the computation time needed for large code bases. Also 
+# note that a graph may be further truncated if the graph's image dimensions are 
+# not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH and MAX_DOT_GRAPH_HEIGHT). 
+# If 0 is used for the depth value (the default), the graph is not depth-constrained.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will 
+# generate a legend page explaining the meaning of the various boxes and 
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will 
+# remove the intermediate dot files that are used to generate 
+# the various graphs.
+
+DOT_CLEANUP            = YES
+
+#---------------------------------------------------------------------------
+# Configuration::addtions related to the search engine   
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be 
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE           = NO
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/INSTALL
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/INSTALL	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/INSTALL	(revision 22157)
@@ -0,0 +1,167 @@
+Basic Installation
+==================
+
+   These are generic installation instructions.
+
+   The `configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation.  It uses
+those values to create a `Makefile' in each directory of the package.
+It may also create one or more `.h' files containing system-dependent
+definitions.  Finally, it creates a shell script `config.status' that
+you can run in the future to recreate the current configuration, a file
+`config.cache' that saves the results of its tests to speed up
+reconfiguring, and a file `config.log' containing compiler output
+(useful mainly for debugging `configure').
+
+   If you need to do unusual things to compile the package, please try
+to figure out how `configure' could check whether to do them, and mail
+diffs or instructions to the address given in the `README' so they can
+be considered for the next release.  If at some point `config.cache'
+contains results you don't want to keep, you may remove or edit it.
+
+   The file `configure.in' is used to create `configure' by a program
+called `autoconf'.  You only need `configure.in' if you want to change
+it or regenerate `configure' using a newer version of `autoconf'.
+
+The simplest way to compile this package is:
+
+  1. `cd' to the directory containing the package's source code and type
+     `./configure' to configure the package for your system.  If you're
+     using `csh' on an old version of System V, you might need to type
+     `sh ./configure' instead to prevent `csh' from trying to execute
+     `configure' itself.
+
+     Running `configure' takes a while.  While running, it prints some
+     messages telling which features it is checking for.
+
+  2. Type `make' to compile the package.
+
+  3. Type `make install' to install the programs and any data files and
+     documentation.
+
+  4. You can remove the program binaries and object files from the
+     source code directory by typing `make clean'.  
+
+Compilers and Options
+=====================
+
+   Some systems require unusual options for compilation or linking that
+the `configure' script does not know about.  You can give `configure'
+initial values for variables by setting them in the environment.  Using
+a Bourne-compatible shell, you can do that on the command line like
+this:
+     CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure
+
+Or on systems that have the `env' program, you can do it like this:
+     env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure
+
+Compiling For Multiple Architectures
+====================================
+
+   You can compile the package for more than one kind of computer at the
+same time, by placing the object files for each architecture in their
+own directory.  To do this, you must use a version of `make' that
+supports the `VPATH' variable, such as GNU `make'.  `cd' to the
+directory where you want the object files and executables to go and run
+the `configure' script.  `configure' automatically checks for the
+source code in the directory that `configure' is in and in `..'.
+
+   If you have to use a `make' that does not supports the `VPATH'
+variable, you have to compile the package for one architecture at a time
+in the source code directory.  After you have installed the package for
+one architecture, use `make distclean' before reconfiguring for another
+architecture.
+
+Installation Names
+==================
+
+   By default, `make install' will install the package's files in
+`/usr/local/bin', `/usr/local/man', etc.  You can specify an
+installation prefix other than `/usr/local' by giving `configure' the
+option `--prefix=PATH'.
+
+   You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files.  If you
+give `configure' the option `--exec-prefix=PATH', the package will use
+PATH as the prefix for installing programs and libraries.
+Documentation and other data files will still use the regular prefix.
+
+   If the package supports it, you can cause programs to be installed
+with an extra prefix or suffix on their names by giving `configure' the
+option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
+
+Optional Features
+=================
+
+   Some packages pay attention to `--enable-FEATURE' options to
+`configure', where FEATURE indicates an optional part of the package.
+They may also pay attention to `--with-PACKAGE' options, where PACKAGE
+is something like `gnu-as' or `x' (for the X Window System).  The
+`README' should mention any `--enable-' and `--with-' options that the
+package recognizes.
+
+   For packages that use the X Window System, `configure' can usually
+find the X include and library files automatically, but if it doesn't,
+you can use the `configure' options `--x-includes=DIR' and
+`--x-libraries=DIR' to specify their locations.
+
+Specifying the System Type
+==========================
+
+   There may be some features `configure' can not figure out
+automatically, but needs to determine by the type of host the package
+will run on.  Usually `configure' can figure that out, but if it prints
+a message saying it can not guess the host type, give it the
+`--host=TYPE' option.  TYPE can either be a short name for the system
+type, such as `sun4', or a canonical name with three fields:
+     CPU-COMPANY-SYSTEM
+
+See the file `config.sub' for the possible values of each field.  If
+`config.sub' isn't included in this package, then this package doesn't
+need to know the host type.
+
+   If you are building compiler tools for cross-compiling, you can also
+use the `--target=TYPE' option to select the type of system they will
+produce code for and the `--build=TYPE' option to select the type of
+system on which you are compiling the package.
+
+Sharing Defaults
+================
+
+   If you want to set default values for `configure' scripts to share,
+you can create a site shell script called `config.site' that gives
+default values for variables like `CC', `cache_file', and `prefix'.
+`configure' looks for `PREFIX/share/config.site' if it exists, then
+`PREFIX/etc/config.site' if it exists.  Or, you can set the
+`CONFIG_SITE' environment variable to the location of the site script.
+A warning: not all `configure' scripts look for a site script.
+
+Operation Controls
+==================
+
+   `configure' recognizes the following options to control how it
+operates.
+
+`--cache-file=FILE'
+     Use and save the results of the tests in FILE instead of
+     `./config.cache'.  Set FILE to `/dev/null' to disable caching, for
+     debugging `configure'.
+
+`--help'
+     Print a summary of the options to `configure', and exit.
+
+`--quiet'
+`--silent'
+`-q'
+     Do not print messages saying which checks are being made.
+
+`--srcdir=DIR'
+     Look for the package's source code in directory DIR.  Usually
+     `configure' can determine that directory automatically.
+
+`--version'
+     Print the version of Autoconf used to generate the `configure'
+     script, and exit.
+
+`configure' also accepts some other, not widely useful, options.
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/Makefile.am	(revision 22157)
@@ -0,0 +1,26 @@
+SUBDIRS = src test
+
+bin_SCRIPTS = psmodules-config
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA= psmodules.pc
+
+EXTRA_DIST = \
+	Doxyfile.in \
+	psmodules-config.in \
+	psmodules.pc.in \
+	autogen.sh
+
+if HAVE_DOXYGEN
+install-data-hook: doxygen
+	-$(mkdir_p) $(mandir)/man3
+	chmod 0755 $(mandir)/man3
+	-cp $(top_builddir)/docs/man/man3/* $(mandir)/man3
+
+doxygen:
+	$(DOXYGEN)
+endif
+
+CLEANFILES = $(prefix)/docs/psmodules/* *~
+
+test: check
Index: /branches/eam_branches/eam_branch_20080706/psModules/NEWS
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/NEWS	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/NEWS	(revision 22157)
@@ -0,0 +1,1 @@
+ 
Index: /branches/eam_branches/eam_branch_20080706/psModules/README
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/README	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/README	(revision 22157)
@@ -0,0 +1,1 @@
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/TODO
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/TODO	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/TODO	(revision 22157)
@@ -0,0 +1,1 @@
+ 
Index: /branches/eam_branches/eam_branch_20080706/psModules/autogen.sh
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/autogen.sh	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/autogen.sh	(revision 22157)
@@ -0,0 +1,114 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+ORIGDIR=`pwd`
+cd $srcdir
+
+PROJECT=psmodules
+TEST_TYPE=-f
+FILE=psmodules.pc.in
+
+DIE=0
+
+if [ "`which glibtoolize 2> /dev/null`" != "" ]
+ then LIBTOOLIZE=glibtoolize
+ else LIBTOOLIZE=libtoolize
+fi
+
+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"
+
+# bypass taps bootstrap.sh
+cd ./test/tap
+$LIBTOOLIZE --copy --force || echo "$LIBTOOlIZE failed"
+$ACLOCAL || echo "$ACLOCAL failed"
+$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /branches/eam_branches/eam_branch_20080706/psModules/configure.ac
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/configure.ac	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/configure.ac	(revision 22157)
@@ -0,0 +1,348 @@
+AC_PREREQ(2.61)
+
+AC_INIT([psmodules],[1.1.0],[http://pan-starrs.ifa.hawaii.edu/bugzilla])
+AC_CONFIG_SRCDIR([psmodules.pc.in])
+
+dnl this enables the building of libtap
+AC_CONFIG_SUBDIRS([test/tap])
+
+AM_INIT_AUTOMAKE([1.7 foreign dist-bzip2])
+AM_CONFIG_HEADER([src/config.h])
+AM_MAINTAINER_MODE
+
+PSMODULES_LT_VERSION="1:1:0"
+AC_SUBST(PSMODULES_LT_VERSION,$PSMODULES_LT_VERSION)
+
+IPP_STDCFLAGS
+
+AC_LANG(C)
+AC_PROG_CC_C99
+AC_GNU_SOURCE
+AC_C_INLINE
+AC_C_CONST
+AC_PROG_INSTALL
+AM_PROG_LIBTOOL
+AC_SYS_LARGEFILE
+AC_FUNC_FSEEKO
+
+AC_PREFIX_DEFAULT([`pwd`])
+
+dnl build tests at the same time as the source code
+AC_ARG_ENABLE(tests,
+  [AS_HELP_STRING(--enable-tests,build tests at same time as source)],
+  [AC_MSG_RESULT(test building enabled)
+   tests=true],
+   [tests=false])
+AM_CONDITIONAL(BUILD_TESTS, test x$tests = xtrue)
+
+dnl ------------------- PERL options ---------------------
+  AC_ARG_WITH(perl,
+    [AS_HELP_STRING(--with-perl=FILE,Specify location of PERL executable.)],
+    [AC_CHECK_PROG(PERL, $withval, $withval)],
+    [AC_CHECK_PROG(PERL, perl, `which perl`)])
+    if test "$PERL" == ""
+    then
+      AC_MSG_ERROR([PERL is required.  Use --with-perl to specify its install location.])
+    fi
+    AC_SUBST(PERL,$PERL)
+
+SRCPATH='${top_srcdir}/src'
+SRCDIRS="extras config concepts camera astrom detrend imcombine objects"
+# escape two escapes at this level so \\ gets passed to the shell and \ to perl
+SRCINC=`echo "${SRCDIRS=}" | ${PERL} -pe "s|(\w+)|-I\\\\${SRCPATH=}/\1|g"`
+SRCINC="-I${SRCPATH=} ${SRCINC=}"
+SRCSUBLIBS=`echo "${SRCDIRS=}" | ${PERL} -pe "s|(\w+)|\1/libpsmodules\1.la|g"`
+AC_SUBST(SRCSUBLIBS,${SRCSUBLIBS=})
+AC_SUBST(SRCINC,${SRCINC=})
+AC_SUBST([SRCDIRS],${SRCDIRS=})
+
+dnl doxygen -------------------------------------------------------------------
+dnl doxygen doc generation is very, very slow so we're turing it off by default
+
+dnl save LIBS/CFLAGS/LDFLAGS
+TMP_LIBS=${LIBS}
+TMP_CFLAGS=${CFLAGS}
+TMP_LDFLAGS=${LDFLAGS}
+TMP_CPPFLAGS=${CPPFLAGS}
+
+AC_ARG_ENABLE(doxygen,
+  [AS_HELP_STRING(--enable-doxygen ,enable manpage generation)],
+  [AC_MSG_RESULT(doxygen enabled)
+    AC_PATH_PROG([DOXYGEN], [doxygen], [])
+  ],
+  [AC_MSG_RESULT([doxygen disabled])
+    doxygen=off
+  ]
+)
+AM_CONDITIONAL([HAVE_DOXYGEN], test -n "$DOXYGEN" -a "x$doxygen" != "xoff")
+
+dnl restore the CFLAGS/LDFLAGS
+LIBS=${TMP_LIBS}
+CFLAGS=${TMP_CFLAGS}
+LDFLAGS=${TMP_LDFLAGS}
+CPPFLAGS=${TMP_CPPFLAGS}
+
+dnl ------------------------------------------------------------
+
+AC_PATH_PROG([ERRORCODES], [psParseErrorCodes], [missing])
+if test "$ERRORCODES" = "missing" ; then
+  AC_MSG_ERROR([psParseErrorCodes is required])
+fi
+
+dnl ------------------ kapa,libkapa options -------------------------
+dnl -- libkapa implies the requirement for libpng, libjpeg as well --
+
+dnl save LIBS/CFLAGS/LDFLAGS
+TMP_LIBS=${LIBS}
+TMP_CFLAGS=${CFLAGS}
+TMP_LDFLAGS=${LDFLAGS}
+TMP_CPPFLAGS=${CPPFLAGS}
+
+dnl test for command-line options: use ohana-config if not supplied
+KAPA_CFLAGS_CONFIG="true"
+KAPA_LIBS_CONFIG="true"
+AC_ARG_WITH(kapa,
+[AS_HELP_STRING(--with-kapa=DIR,Specify location of libkapa)],
+[KAPA_CFLAGS="-I$withval/include" KAPA_LIBS="-L$withval/lib" 
+ KAPA_CFLAGS_CONFIG="false"       KAPA_LIBS_CONFIG="false"])
+AC_ARG_WITH(kapa-include,
+[AS_HELP_STRING(--with-kapa-include=DIR,Specify libkapa include directory.)],
+[KAPA_CFLAGS="-I$withval" KAPA_CFLAGS_CONFIG="false"])
+AC_ARG_WITH(kapa-lib,
+[AS_HELP_STRING(--with-kapa-lib=DIR,Specify libkapa library directory.)],
+[KAPA_LIBS="-L$withval" KAPA_LIBS_CONFIG="false"])
+
+echo "KAPA_CFLAGS_CONFIG: $KAPA_CFLAGS_CONFIG"
+echo "KAPA_LIBS_CONFIG: $KAPA_LIBS_CONFIG"
+echo "KAPA_CFLAGS: $KAPA_CFLAGS"
+echo "KAPA_LIBS: $KAPA_LIBS"
+
+dnl HAVE_KAPA is set to false if any of the tests fail
+HAVE_KAPA="true"
+AC_MSG_NOTICE([checking for libkapa])
+if test "$KAPA_CFLAGS_CONFIG" = "true" -o "$KAPA_LIBS_CONFIG" = "true"; then
+  AC_MSG_NOTICE([kapa info supplied by ohana-config])
+  KAPA_CONFIG=`which ohana-config`
+  AC_CHECK_FILE($KAPA_CONFIG,[],
+    [HAVE_KAPA="false"; AC_MSG_WARN([libkapa is not found: output plots disabled.  Obtain libkapa at http://kiawe.ifa.hawaii.edu/Elixir/Ohana or use --with-kapa to specify location])])
+  
+  echo "HAVE_KAPA: $HAVE_KAPA"
+  echo "KAPA_CFLAGS_CONFIG: $KAPA_CFLAGS_CONFIG"
+
+  if test "$HAVE_KAPA" = "true" -a "$KAPA_CFLAGS_CONFIG" = "true" ; then
+   AC_MSG_NOTICE([libkapa cflags info supplied by ohana-config])
+   AC_MSG_CHECKING([libkapa cflags])
+   KAPA_CFLAGS="`${KAPA_CONFIG} --cflags`"
+   AC_MSG_RESULT([${KAPA_CFLAGS}])
+  fi
+
+  if test "$HAVE_KAPA" = "true" -a "$KAPA_LIBS_CONFIG" = "true" ; then
+   AC_PATH_X
+   if test "$no_x" = "yes" ; then
+      AC_MSG_WARN([X11 not found: output plots using kapa disabled.  Use --x-includes and --x-libraries if required.])
+      HAVE_KAPA="false"
+   else
+      AC_MSG_NOTICE([libkapa ldflags info supplied by ohana-config])
+      AC_MSG_CHECKING([libkapa ldflags])
+      if test -n "$x_libraries" ; then
+            KAPA_LIBS="`${KAPA_CONFIG} --libs` -L$x_libraries -lX11"
+      else
+            KAPA_LIBS="`${KAPA_CONFIG} --libs` -lX11"
+      fi
+      if test -n "$x_includes" ; then
+            KAPA_CFLAGS="${KAPA_CFLAGS} -I$x_includes"
+      else
+            KAPA_CFLAGS="${KAPA_CFLAGS}"
+      fi
+      AC_MSG_RESULT([${KAPA_LIBS}])
+   fi
+  fi
+fi
+
+if test "$HAVE_KAPA" = "true" ; then
+ AC_MSG_NOTICE([libkapa supplied])
+ PSMODULES_CFLAGS="${PSMODULES_CFLAGS} ${KAPA_CFLAGS}"
+ PSMODULES_LIBS="${PSMODULES_LIBS} ${KAPA_LIBS}"
+else
+ AC_MSG_NOTICE([libkapa ignored])
+fi
+
+dnl HAVE_KAPA is set to false if any of the tests fail
+dnl HAVE_KAPA=true
+dnl AC_CHECK_HEADERS([kapa.h],
+dnl  [PSMODULES_CFLAGS="$PSMODULES_CFLAGS $KAPA_CFLAGS" AC_SUBST(KAPA_CFLAGS)],
+dnl  [HAVE_KAPA=false; AC_MSG_WARN([libkapa headers not found: output plots disabled.  Obtain libkapa at http://kiawe.ifa.hawaii.edu/Elixir/Ohana or use --with-kapa to specify location.])]
+dnl )
+dnl AC_CHECK_LIB(kapa,KapaInitGraph,
+dnl  [PSMODULES_LIBS="$PSMODULES_LIBS $JPEG_LDFLAGS -ljpeg"],  
+dnl  [HAVE_KAPA=false; AC_MSG_WARN([libkapa headers not found: output plots disabled.  Obtain libkapa at http://kiawe.ifa.hawaii.edu/Elixir/Ohana or use --with-kapa to specify location.])],[-lm]
+dnl )
+
+dnl restore the CFLAGS/LDFLAGS
+LIBS=${TMP_LIBS}
+CFLAGS=${TMP_CFLAGS}
+LDFLAGS=${TMP_LDFLAGS}
+CPPFLAGS=${TMP_CPPFLAGS}
+
+dnl ------------------ libjpeg options ---------------------
+
+dnl save LIBS/CFLAGS/LDFLAGS
+TMP_LIBS=${LIBS}
+TMP_CFLAGS=${CFLAGS}
+TMP_LDFLAGS=${LDFLAGS}
+TMP_CPPFLAGS=${CPPFLAGS}
+
+AC_ARG_WITH(jpeg,
+[AS_HELP_STRING(--with-jpeg=DIR,Specify location of libjpeg.)],
+[JPEG_CFLAGS="-I$withval/include"
+ JPEG_LDFLAGS="-L$withval/lib"])
+AC_ARG_WITH(jpeg-include,
+[AS_HELP_STRING(--with-jpeg-include=DIR,Specify libjpeg include directory.)],
+[JPEG_CFLAGS="-I$withval"])
+AC_ARG_WITH(jpeg-lib,
+[AS_HELP_STRING(--with-jpeg-lib=DIR,Specify libjpeg library directory.)],
+[JPEG_LDFLAGS="-L$withval"])
+
+CFLAGS="${CFLAGS} ${JPEG_CFLAGS}"
+CPPFLAGS=${CFLAGS}
+LDFLAGS="${LDFLAGS} ${JPEG_LDFLAGS}"
+
+AC_CHECK_HEADERS([jpeglib.h],
+  [PSMODULES_CFLAGS="$PSMODULES_CFLAGS $JPEG_CFLAGS" AC_SUBST(JPEG_CFLAGS)],
+  [HAVE_KAPA=false; AC_MSG_WARN([libjpeg headers not found: output plots disabled.  Obtain libjpeg from http://www.ijg.org/ or use --with-jpeg to specify location.])]
+)
+
+AC_CHECK_LIB(jpeg,jpeg_CreateCompress,
+  [PSMODULES_LIBS="$PSMODULES_LIBS $JPEG_LDFLAGS -ljpeg"],
+  [HAVE_KAPA=false; AC_MSG_WARN([libjpeg library not found: output plots disabled.  Obtain libjpeg from http://www.ijg.org/ or use --with-jpeg to specify location.])]
+)
+
+dnl restore the CFLAGS/LDFLAGS
+LIBS=${TMP_LIBS}
+CFLAGS=${TMP_CFLAGS}
+LDFLAGS=${TMP_LDFLAGS}
+CPPFLAGS=${TMP_CPPFLAGS}
+
+dnl ------------------ libpng options ---------------------
+
+dnl save LIBS/CFLAGS/LDFLAGS
+TMP_LIBS=${LIBS}
+TMP_CFLAGS=${CFLAGS}
+TMP_LDFLAGS=${LDFLAGS}
+TMP_CPPFLAGS=${CPPFLAGS}
+
+AC_ARG_WITH(png,
+[AS_HELP_STRING(--with-png=DIR,Specify location of libpng.)],
+[PNG_CFLAGS="-I$withval/include"
+ PNG_LDFLAGS="-L$withval/lib"])
+AC_ARG_WITH(png-include,
+[AS_HELP_STRING(--with-png-include=DIR,Specify libpng include directory.)],
+[PNG_CFLAGS="-I$withval"])
+AC_ARG_WITH(png-lib,
+[AS_HELP_STRING(--with-png-lib=DIR,Specify libpng library directory.)],
+[PNG_LDFLAGS="-L$withval"])
+
+CFLAGS="${CFLAGS} ${PNG_CFLAGS}"
+CPPFLAGS=${CFLAGS}
+LDFLAGS="${LDFLAGS} ${PNG_LDFLAGS}"
+
+AC_CHECK_HEADERS([png.h],
+  [PSMODULES_CFLAGS="$PSMODULES_CFLAGS $PNG_CFLAGS" AC_SUBST(PNG_CFLAGS)],
+  [HAVE_KAPA=false; AC_MSG_WARN([libpng headers not found: output plots disabled.  Obtain libpng from http://www.ijg.org/ or use --with-png to specify location.])]
+)
+
+AC_CHECK_LIB(png,png_init_io,
+  [PSMODULES_LIBS="$PSMODULES_LIBS $PNG_LDFLAGS -lpng"],
+  [HAVE_KAPA=false; AC_MSG_WARN([libpng library not found: output plots disabled.  Obtain libpng from http://www.ijg.org/ or use --with-png to specify location.])]
+)
+
+dnl restore the CFLAGS/LDFLAGS
+LIBS=${TMP_LIBS}
+CFLAGS=${TMP_CFLAGS}
+LDFLAGS=${TMP_LDFLAGS}
+CPPFLAGS=${TMP_CPPFLAGS}
+
+dnl ------------------ use kapa or not? ---------------------
+
+if test "$HAVE_KAPA" == "true" ; then
+  AC_MSG_RESULT([including plotting functions])
+  AC_DEFINE([HAVE_KAPA],[1],[enable use of libkapa])
+else
+  AC_MSG_RESULT([skipping plotting functions])
+  AC_DEFINE([HAVE_KAPA],[0],[disable use of libkapa])
+fi
+
+dnl pslib ---------------------------------------------------------------------
+AC_ARG_WITH(pslib-config,
+[  --with-pslib-config=FILE  Specify location of psLib-config script],
+[PSLIB_CONFIG=$withval])
+
+if test -z ${PSLIB_CONFIG} ; then
+  PKG_CHECK_MODULES([PSLIB], [pslib >= 0.12.0])
+else
+  AC_CHECK_FILE($PSLIB_CONFIG,[],
+    [AC_MSG_ERROR([psLib is required.  If not in path, use --with-pslib-config to specify pslib-config script location.])])
+  AC_MSG_CHECKING([PSLIB_CFLAGS])
+  PSLIB_CFLAGS="`${PSLIB_CONFIG} --cflags`"
+  AC_MSG_RESULT([${PSLIB_CFLAGS}])
+  AC_MSG_CHECKING([PSLIB_LIBS])
+  PSLIB_LIBS="`${PSLIB_CONFIG} --libs`"
+  AC_MSG_RESULT([${PSLIB_LIBS}])
+fi
+
+PSMODULES_CFLAGS="${PSMODULES_CFLAGS=} ${PSLIB_CFLAGS}"
+PSMODULES_LIBS="${PSMODULES_LIBS=} ${PSLIB_LIBS}"
+
+dnl nebclient -----------------------------------------------------------------
+
+PKG_CHECK_MODULES([NEBCLIENT], [nebclient >= 0.0.2],
+    [AC_DEFINE([HAVE_NEBCLIENT], 1, [Define to 1 if libnebclient is avaiable])],    [AC_MSG_RESULT([no])]
+)
+
+PSMODULES_CFLAGS="${PSMODULES_CFLAGS=} ${NEBCLIENT_CFLAGS}"
+PSMODULES_LIBS="${PSMODULES_LIBS=} ${NEBCLIENT_LIBS}"
+
+echo "PSMODULES_CFLAGS: $PSMODULES_CFLAGS"
+echo "PSMODULE_LIBS: $PSMODULES_LIBS"
+
+dnl ------- enable -Werror after all of the probes have run ---------
+IPP_STDOPTS
+
+CFLAGS="${CFLAGS} -Wall -Werror"
+dnl enable POSIX/XSI and C99 compliance
+CFLAGS="${CFLAGS=} -D_XOPEN_SOURCE=600 -D_POSIX_C_SOURCE=200112L"
+
+dnl ---------------- export variables --------------------
+
+
+AC_SUBST([PSMODULES_CFLAGS])
+AC_SUBST([PSMODULES_LIBS])
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+  src/astrom/Makefile
+  src/camera/Makefile
+  src/config/Makefile
+  src/concepts/Makefile
+  src/detrend/Makefile
+  src/imcombine/Makefile
+  src/objects/Makefile
+  src/extras/Makefile
+  test/Makefile
+  test/astrom/Makefile
+  test/config/Makefile
+  test/camera/Makefile
+  test/concepts/Makefile
+  test/detrend/Makefile
+  test/extras/Makefile
+  test/imcombine/Makefile
+  test/objects/Makefile
+  test/pstap/Makefile
+  test/pstap/src/Makefile
+  Doxyfile
+  psmodules-config
+  psmodules.pc
+])
+
+AC_OUTPUT
Index: /branches/eam_branches/eam_branch_20080706/psModules/psmodules-config.in
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/psmodules-config.in	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/psmodules-config.in	(revision 22157)
@@ -0,0 +1,76 @@
+#! /bin/sh
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@/@PACKAGE_NAME@
+
+usage()
+{
+    cat <<EOF
+Usage: pslib-config [OPTION]
+
+Known values for OPTION are:
+
+  --prefix		print psLib installation prefix
+  --libs		print library linking information
+  --cflags		print pre-processor and compiler flags
+  --help		display this help and exit
+  --version		output version information
+
+EOF
+
+    exit $1
+}
+
+if test $# -eq 0; then
+    usage 1
+fi
+
+cflags=false
+libs=false
+
+while test $# -gt 0; do
+    case "$1" in
+    -*=*) optarg=`echo "$1" | sed 's/[-_a-zA-Z0-9]*=//'` ;;
+    *) optarg= ;;
+    esac
+
+    case "$1" in
+    --prefix=*)
+	prefix=$optarg
+	;;
+
+    --prefix)
+	echo $prefix
+	;;
+
+    --version)
+	echo @VERSION@
+	exit 0
+	;;
+
+    --help)
+	usage 0
+	;;
+
+    --cflags)
+       	echo -I${includedir} @PSMODULES_CFLAGS@
+       	;;
+
+    --libs)
+       	echo -L${libdir} -lpsmodules @PSMODULES_LIBS@
+       	;;
+
+    --deps)
+       	echo @LDFLAGS@
+       	;;
+    *)
+	usage
+	exit 1
+	;;
+    esac
+    shift
+done
+
+exit 0
Index: /branches/eam_branches/eam_branch_20080706/psModules/psmodules.pc.in
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/psmodules.pc.in	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/psmodules.pc.in	(revision 22157)
@@ -0,0 +1,12 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@/@PACKAGE_NAME@
+
+Name: @PACKAGE_NAME@
+Description: Pan-STARRS Module Library
+Version: @VERSION@
+Requires: pslib
+Libs: -L${libdir} -lpsmodules
+Libs.private: @PSMODULES_LIBS@
+Cflags: -I${includedir} @PSMODULES_CFLAGS@
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/.cvsignore	(revision 22157)
@@ -0,0 +1,9 @@
+.deps
+.libs
+Makefile
+config.h
+stamp-h1
+Makefile.in
+config.h.in
+*.la
+*.lo
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/Makefile.am	(revision 22157)
@@ -0,0 +1,13 @@
+SUBDIRS = $(SRCDIRS)
+lib_LTLIBRARIES = libpsmodules.la
+
+libpsmodules_la_CPPFLAGS = $(SRCINC)
+libpsmodules_la_LIBADD = $(SRCSUBLIBS) $(PSMODULES_LIBS)
+libpsmodules_la_DEPENDENCIES = $(SRCSUBLIBS)
+libpsmodules_la_SOURCES = 
+libpsmodules_la_LDFLAGS = -version-info $(PSMODULES_LT_VERSION)
+
+pkginclude_HEADERS = \
+	psmodules.h
+
+CLEANFILES = *~
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/.cvsignore	(revision 22157)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.la
+*.lo
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/Makefile.am	(revision 22157)
@@ -0,0 +1,23 @@
+noinst_LTLIBRARIES = libpsmodulesastrom.la
+
+libpsmodulesastrom_la_CPPFLAGS = $(SRCINC) $(PSMODULES_CFLAGS) -I../pslib/
+libpsmodulesastrom_la_LDFLAGS  = -release $(PACKAGE_VERSION)
+libpsmodulesastrom_la_SOURCES  = \
+	pmAstrometryObjects.c \
+	pmAstrometryRegions.c \
+	pmAstrometryDistortion.c \
+	pmAstrometryUtils.c \
+	pmAstrometryModel.c \
+	pmAstrometryRefstars.c \
+	pmAstrometryWCS.c
+
+pkginclude_HEADERS = \
+	pmAstrometryObjects.h \
+	pmAstrometryRegions.h \
+	pmAstrometryDistortion.h \
+	pmAstrometryUtils.h \
+	pmAstrometryModel.h \
+	pmAstrometryRefstars.h \
+	pmAstrometryWCS.h
+
+CLEANFILES = *~
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryDistortion.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryDistortion.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryDistortion.c	(revision 22157)
@@ -0,0 +1,348 @@
+/** @file  pmAstrometryDistortion.c
+*
+*  @brief This file defines the basic types for measuring the focal-plane distortion.
+*
+*  @ingroup AstroImage
+*
+*  @author EAM, IfA
+*
+*  @version $Revision: 1.21 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2007-11-12 21:58:17 $
+*
+*  Copyright 2006 Institute for Astronomy, University of Hawaii
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/******************************************************************************/
+/*  INCLUDE FILES                                                             */
+/******************************************************************************/
+
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAExtent.h"
+#include "pmAstrometryObjects.h"
+#include "pmAstrometryRegions.h"
+#include "pmAstrometryDistortion.h"
+#include "pmKapaPlots.h"
+
+static void pmAstromGradientFree (pmAstromGradient *grad)
+{
+
+    if (grad == NULL)
+        return;
+
+    return;
+}
+
+pmAstromGradient *pmAstromGradientAlloc ()
+{
+
+    pmAstromGradient *gradient = psAlloc (sizeof(pmAstromGradient));
+    psMemSetDeallocator(gradient, (psFreeFunc) pmAstromGradientFree);
+
+    return (gradient);
+}
+
+psArray *pmAstromMeasureGradients(psArray *gradients, psArray *rawstars, psArray *refstars, psArray *matches, psRegion *region, int Nx, int Ny)
+{
+
+    if (gradients == NULL) {
+        gradients = psArrayAllocEmpty (100);
+    }
+
+    // NOTE: region specifies the FP region in pixels covered by the chip (NOT in FP units)
+    // determine range
+    int DX = (region->x1 - region->x0) / Nx;
+    int DY = (region->y1 - region->y0) / Ny;
+
+    psPolynomial2D *local = psPolynomial2DAlloc (PS_POLYNOMIAL_ORD, 1, 1);
+    local->coeffMask[1][1] = PS_POLY_MASK_SET;
+
+    // measure gradient for fractional chip regions
+    for (int nx = 0; nx < Nx; nx++) {
+        for (int ny = 0; ny < Ny; ny++) {
+            int Xmin = nx*DX;
+            int Xmax = Xmin + DX;
+            int Ymin = ny*DY;
+            int Ymax = Ymin + DY;
+
+            psStats *stats = NULL;
+            psVector *mask = NULL;
+            pmAstromGradient *grad = NULL;
+
+            psVector *L  = psVectorAllocEmpty (100, PS_TYPE_F32);
+            psVector *M  = psVectorAllocEmpty (100, PS_TYPE_F32);
+            psVector *dP = psVectorAllocEmpty (100, PS_TYPE_F32);
+            psVector *dQ = psVectorAllocEmpty (100, PS_TYPE_F32);
+
+            // XXX this is a bit inefficient: first sorting by X or Y could speed this up.
+            // XXX or assigning to a segment in a single pass first
+            // select the stars within this chip region
+            int Npts = 0;
+            for (int i = 0; i < matches->n; i++) {
+
+                pmAstromMatch *match = matches->data[i];
+
+                pmAstromObj *raw = rawstars->data[match->raw];
+
+                if (raw->chip->x < Xmin) continue;
+                if (raw->chip->x > Xmax) continue;
+                if (raw->chip->y < Ymin) continue;
+                if (raw->chip->y > Ymax) continue;
+
+                pmAstromObj *ref = refstars->data[match->ref];
+
+                L->data.F32[Npts] = ref->FP->x;
+                M->data.F32[Npts] = ref->FP->y;
+
+                // P,Q = L,M + terms of order epsilon.
+                // measuring the gradient constrains thos terms
+                dP->data.F32[Npts] = ref->TP->x - raw->FP->x;
+                dQ->data.F32[Npts] = ref->TP->y - raw->FP->y;
+
+                psVectorExtend (L, 100, 1);
+                psVectorExtend (M, 100, 1);
+                psVectorExtend (dP, 100, 1);
+                psVectorExtend (dQ, 100, 1);
+                Npts++;
+            }
+
+            psTrace ("psModules.astrom", 4, "Npts: %d (%d,%d) : (%d - %d),(%d - %d)\n", Npts, nx, ny, Xmin, Xmax, Ymin, Ymax);
+
+            if (Npts < 5)
+                goto skip;
+
+            // stats structure and mask for use in measuring the clipping statistic
+            // this analysis has too few data points to use the robust median method
+            stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+            mask = psVectorAlloc (Npts, PS_TYPE_MASK);
+            psVectorInit (mask, 0);
+
+            grad = pmAstromGradientAlloc ();
+
+            // fit the collection of positions and offsets with a local 1st order gradient
+            // apply 3hi/3lo sigma clipping to the fitted data values
+            // the mask is used to mark the points which pass / fail the fit
+            if (!psVectorClipFitPolynomial2D (local, stats, mask, 0xff, dP, NULL, L, M)) {
+                goto skip;
+            }
+
+            grad->dTPdL.x = local->coeff[1][0];
+            grad->dTPdM.x = local->coeff[0][1];
+
+            // fit the collection of positions and offsets with a local 1st order gradient
+            // apply 3hi/3lo sigma clipping to the fitted data values
+            // the mask is used to mark the points which pass / fail the fit
+            if (!psVectorClipFitPolynomial2D (local, stats, mask, 0xff, dQ, NULL, L, M)) {
+                goto skip;
+            }
+
+            grad->dTPdL.y = local->coeff[1][0];
+            grad->dTPdM.y = local->coeff[0][1];
+
+            // also measure the L and M median positions as a representative coordinate
+            psVectorStats (stats, L, NULL, NULL, 0);
+            grad->FP.x = stats->sampleMedian;
+
+            psVectorStats (stats, M, NULL, NULL, 0);
+            grad->FP.y = stats->sampleMedian;
+
+            psArrayAdd (gradients, 100, grad);
+
+skip:
+            psFree (grad);
+            psFree (stats);
+            psFree (mask);
+            psFree (L);
+            psFree (M);
+            psFree (dP);
+            psFree (dQ);
+        }
+    }
+    psFree (local);
+    return gradients;
+}
+
+bool pmAstromFitDistortion(pmFPA *fpa, psArray *gradients, double pixelScale)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_ARRAY_NON_NULL(gradients, false);
+
+    psPolynomial2D *localX = NULL;
+    psPolynomial2D *localY = NULL;
+
+    // assign the gradient elements to psVectors for fitting
+    psVector *dPdL = psVectorAlloc (gradients->n, PS_TYPE_F32);
+    psVector *dQdL = psVectorAlloc (gradients->n, PS_TYPE_F32);
+    psVector *dPdM = psVectorAlloc (gradients->n, PS_TYPE_F32);
+    psVector *dQdM = psVectorAlloc (gradients->n, PS_TYPE_F32);
+    psVector *L = psVectorAlloc (gradients->n, PS_TYPE_F32);
+    psVector *M = psVectorAlloc (gradients->n, PS_TYPE_F32);
+
+    for (int i = 0; i < gradients->n; i++) {
+
+        pmAstromGradient *grad = gradients->data[i];
+
+        dPdL->data.F32[i] = grad->dTPdL.x;
+        dQdL->data.F32[i] = grad->dTPdL.y;
+
+        dPdM->data.F32[i] = grad->dTPdM.x;
+        dQdM->data.F32[i] = grad->dTPdM.y;
+
+        L->data.F32[i] = grad->FP.x;
+        M->data.F32[i] = grad->FP.y;
+    }
+
+    // mask and stats structure for measuring the clipping statistic
+    // this analysis has too few data points to use the robust median method
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+    psVector *mask = psVectorAlloc (gradients->n, PS_TYPE_MASK);
+    psVectorInit (mask, 0);
+
+    // the order of the gradient fits need to be 1 less than the distortion term
+    // determine the gradient order(s) from the fpa->toTP structure
+    localX = psPolynomial2DAlloc (PS_POLYNOMIAL_ORD, fpa->toTPA->x->nX-1, fpa->toTPA->x->nY);
+    localY = psPolynomial2DAlloc (PS_POLYNOMIAL_ORD, fpa->toTPA->x->nX,   fpa->toTPA->x->nY-1);
+
+    // set masks based on fpa->toTPA
+    for (int i = 0; i <= fpa->toTPA->x->nX; i++) {
+        for (int j = 0; j <= fpa->toTPA->x->nY; j++) {
+            if ((i > 0) && (i <= fpa->toTPA->x->nX)) {
+                localX->coeffMask[i-1][j] = fpa->toTPA->x->coeffMask[i][j];
+            }
+            if ((j > 0) && (j <= fpa->toTPA->x->nY)) {
+                localY->coeffMask[i][j-1] = fpa->toTPA->x->coeffMask[i][j];
+            }
+        }
+    }
+
+    // fit the local gradients in each direction
+    if (!psVectorClipFitPolynomial2D (localX, stats, mask, 0xff, dPdL, NULL, L, M)) {
+        psLogMsg ("psastro", 3, "failed to fit x-dir gradient\n");
+        psFree (localX);
+        psFree (localY);
+        goto escape;
+    }
+
+    if (!psVectorClipFitPolynomial2D (localY, stats, mask, 0xff, dPdM, NULL, L, M)) {
+        psLogMsg ("psastro", 3, "failed to fit y-dir gradient\n");
+        psFree (localX);
+        psFree (localY);
+        goto escape;
+    }
+
+    // update fpa->toTP distortion terms
+    fpa->toTPA->x->coeff[0][0] = 0;
+    for (int i = 1; i <= fpa->toTPA->x->nX; i++) {
+        if (fpa->toTPA->x->coeffMask[i][0] & PS_POLY_MASK_SET) {
+            continue;
+        }
+        fpa->toTPA->x->coeff[i][0] = localX->coeff[i-1][0] / i;
+    }
+    for (int j = 1; j <= fpa->toTPA->x->nY; j++) {
+        if (fpa->toTPA->x->coeffMask[0][j] & PS_POLY_MASK_SET) {
+            continue;
+        }
+        fpa->toTPA->x->coeff[0][j] = localY->coeff[0][j-1] / j;
+    }
+    for (int i = 1; i <= fpa->toTPA->x->nX; i++) {
+        for (int j = 1; j <= fpa->toTPA->x->nY; j++) {
+            if (fpa->toTPA->x->coeffMask[i][j] & PS_POLY_MASK_SET) {
+                continue;
+            }
+            fpa->toTPA->x->coeff[i][j] = 0.5*(localX->coeff[i-1][j] / i + localY->coeff[i][j-1] / j);
+        }
+    }
+    fpa->toTPA->x->coeff[1][0] += 1.0;
+    psFree (localX);
+    psFree (localY);
+
+    // the order of the gradient fits need to be 1 less than the distortion term
+    // determine the gradient order(s) from the fpa->toTP structure
+    localX = psPolynomial2DAlloc (PS_POLYNOMIAL_ORD, fpa->toTPA->y->nX-1, fpa->toTPA->y->nY);
+    localY = psPolynomial2DAlloc (PS_POLYNOMIAL_ORD, fpa->toTPA->y->nX,   fpa->toTPA->y->nY-1);
+
+    // set masks based on fpa->toTP
+    for (int i = 0; i < fpa->toTPA->y->nX; i++) {
+        for (int j = 0; j < fpa->toTPA->y->nY; j++) {
+            if ((i > 0) && (i <= fpa->toTPA->y->nX)) {
+                localX->coeffMask[i-1][j] = fpa->toTPA->y->coeffMask[i][j];
+            }
+            if ((j > 0) && (j <= fpa->toTPA->y->nY)) {
+                localY->coeffMask[i][j-1] = fpa->toTPA->y->coeffMask[i][j];
+            }
+        }
+    }
+
+    // fit the local gradients in each direction
+    psVectorClipFitPolynomial2D (localX, stats, mask, 0xff, dQdL, NULL, L, M);
+    psVectorClipFitPolynomial2D (localY, stats, mask, 0xff, dQdM, NULL, L, M);
+
+    // update fpa->toTP distortion terms
+    fpa->toTPA->y->coeff[0][0] = 0;
+    for (int i = 1; i <= fpa->toTPA->y->nX; i++) {
+        if (fpa->toTPA->y->coeffMask[i][0] & PS_POLY_MASK_SET) {
+            continue;
+        }
+        fpa->toTPA->y->coeff[i][0] = localX->coeff[i-1][0] / i;
+    }
+    for (int j = 1; j <= fpa->toTPA->y->nY; j++) {
+        if (fpa->toTPA->y->coeffMask[0][j] & PS_POLY_MASK_SET) {
+            continue;
+        }
+        fpa->toTPA->y->coeff[0][j] = localY->coeff[0][j-1] / j;
+    }
+    for (int i = 1; i <= fpa->toTPA->y->nX; i++) {
+        for (int j = 1; j <= fpa->toTPA->y->nY; j++) {
+            if (fpa->toTPA->y->coeffMask[i][j] & PS_POLY_MASK_SET) {
+                continue;
+            }
+            fpa->toTPA->y->coeff[i][j] = 0.5*(localX->coeff[i-1][j] / i + localY->coeff[i][j-1] / j);
+        }
+    }
+    fpa->toTPA->y->coeff[0][1] += 1.0;
+    psFree (localX);
+    psFree (localY);
+
+    // free unneeded structures
+    psFree (dPdL);
+    psFree (dPdM);
+    psFree (dQdL);
+    psFree (dQdM);
+    psFree (L);
+    psFree (M);
+    psFree (stats);
+    psFree (mask);
+
+    // reset the fromTPA terms here. choose an appropriate region based on the dimensions of
+    // the complete FPA
+    psRegion *region = pmAstromFPAExtent (fpa);
+
+    psFree (fpa->fromTPA);
+    fpa->fromTPA = psPlaneTransformInvert(NULL, fpa->toTPA, *region, 50);
+    psFree (region);
+
+    if (fpa->fromTPA == NULL) {
+        psError (PS_ERR_UNKNOWN, false, "failed to invert fpa->toTPA\n");
+        return false;
+    }
+
+    return true;
+
+escape:
+    // free unneeded structures
+    psFree (dPdL);
+    psFree (dPdM);
+    psFree (dQdL);
+    psFree (dQdM);
+    psFree (L);
+    psFree (M);
+    psFree (stats);
+    psFree (mask);
+    return false;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryDistortion.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryDistortion.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryDistortion.h	(revision 22157)
@@ -0,0 +1,59 @@
+/* @file  pmAstrometryDistortion.h
+ * @brief This file defines the basic types for measuring and fitting the focal-plane distortion.
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-03-18 22:07:17 $
+ * Copyright 2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_ASTROMETRY_DISTORTION_H
+#define PM_ASTROMETRY_DISTORTION_H
+
+/// @addtogroup Astrometry
+/// @{
+
+/* The following data structure carries the information about the residual
+ * gradient of source positions in the tangent plane (pmAstromObj.TP) as a
+ * function of position in the focal plane (pmAstromObj.FP).
+ */
+typedef struct
+{
+    psPlane FP;
+    psPlane dTPdL;
+    psPlane dTPdM;
+}
+pmAstromGradient;
+
+pmAstromGradient *pmAstromGradientAlloc ();
+
+/* The following function determines the position residual, in the tangent
+ * plane, as a function of position in the focal plane, for a collection of raw
+ * measurements and matched reference stars. The configuration data must include
+ * the bin size over which the gradient is measured (keyword: ASTROM.GRAD.BOX).
+ * The function returns an array of pmAstromGradient structures, defined below.
+ */
+psArray *pmAstromMeasureGradients(
+    psArray *gradients,
+    psArray *rawstars,
+    psArray *refstars,
+    psArray *matches,
+    psRegion *region,
+    int Nx, int Ny
+);
+
+/* The gradient set measured above can be fitted with a pair of 2D
+ * polynomials. The resulting fits can then be related back to the implied
+ * polynomials which represent the distortion. The following function performs
+ * the fit and applies the result to the distortion transformation of the
+ * supplied pmFPA structure. The configuration variable supplies the polynomial
+ * order (keyword: ASTROM.DISTORT.ORDER).
+ */
+bool pmAstromFitDistortion(
+    pmFPA *fpa,
+    psArray *gradients,
+    double pixelScale);
+
+/// @}
+#endif // PM_ASTROMETRY_DISTORTION_H
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryModel.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryModel.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryModel.c	(revision 22157)
@@ -0,0 +1,767 @@
+/** @file  pmAstrometryModel.c
+ *
+ *  @brief Functions to read and write astrometric model
+ * 
+ *  The generic model does not specify the location of the boresite on the sky, and it includes
+ *  a model for the rotator and motion of the boresite.
+ *
+ *  @ingroup AstroImage
+ *
+ *  @author EAM, IfA
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-06-09 01:53:31 $
+ *
+ *  Copyright 2007 Institute for Astronomy, University of Hawaii
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/******************************************************************************/
+/*  INCLUDE FILES                                                             */
+/******************************************************************************/
+#include <stdio.h>
+#include <strings.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+#include <unistd.h>   // for unlink
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+#include "pmFPAExtent.h"
+#include "pmAstrometryWCS.h"
+#include "pmAstrometryUtils.h"
+#include "pmAstrometryRegions.h"
+#include "pmAstrometryModel.h"
+
+# define REQUIRE(TEST,MESSAGE){ if (!(TEST)) { psAbort (MESSAGE); }}
+
+/********************* CheckDataStatus functions *****************************/
+
+bool pmAstromModelCheckDataStatusForView (const pmFPAview *view, pmFPAfile *file) {
+
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        bool exists = pmAstromModelCheckDataStatusForFPA (fpa);
+        return exists;
+    }
+    if (view->chip >= fpa->chips->n) {
+        psError(PS_ERR_IO, true, "Requested chip == %d >= fpa->chips->n == %ld", view->chip, fpa->chips->n);
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        bool exists = pmAstromModelCheckDataStatusForChip (chip);
+        return exists;
+    }
+    if (view->cell >= chip->cells->n) {
+        psError(PS_ERR_IO, true, "Requested cell == %d >= chip->cells->n == %ld", view->cell, chip->cells->n);
+        return false;
+    }
+    psError(PS_ERR_IO, false, "Astrometry only valid at the chip level");
+    return false;
+}
+
+bool pmAstromModelCheckDataStatusForFPA (const pmFPA *fpa) {
+
+    if (!fpa->toTPA) return false;
+    if (!fpa->fromTPA) return false;
+    if (!fpa->toSky) return false;
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        if (!chip) continue;
+        if (pmAstromModelCheckDataStatusForChip (chip)) return true;
+    }
+    return false;
+}
+
+bool pmAstromModelCheckDataStatusForChip (const pmChip *chip) {
+
+    if (!chip->toFPA) return false;
+    if (!chip->fromFPA) return false;  // XXX not strictly needed?
+    return true;
+}
+
+/********************* Write Data functions *****************************/
+
+bool pmAstromModelWriteForView (const pmFPAview *view, pmFPAfile *file, const pmConfig *config) {
+
+    // write the full model in one pass: require the level to be FPA
+    if (view->chip != -1) {
+	psError(PS_ERR_IO, false, "Astrometry must be written at the FPA level");
+	return false;
+    }
+
+    if (!pmAstromModelWriteFPA (file)) {
+	psError(PS_ERR_IO, false, "Failed to write Astrometry for fpa");
+	return false;
+    }
+    return true;
+}
+
+// write out all chip-level Astrometry data for this FPA
+bool pmAstromModelWriteFPA (pmFPAfile *file) {
+
+    if (!pmAstromModelWritePHU (file)) {
+	psError(PS_ERR_IO, false, "Failed to write PHU for Astrometry model");
+	return false;
+    }
+
+    if (!pmAstromModelWriteChips (file)) {
+	psError(PS_ERR_IO, false, "Failed to write Astrometry for chips");
+	return false;
+    }
+
+    if (!pmAstromModelWriteFP (file)) {
+	psError(PS_ERR_IO, false, "Failed to write Sky for Astrometry model");
+	return false;
+    }
+
+    if (!pmAstromModelWriteTP (file)) {
+	psError(PS_ERR_IO, false, "Failed to write Sky for Astrometry model");
+	return false;
+    }
+
+    if (!pmAstromModelWriteSky (file)) {
+	psError(PS_ERR_IO, false, "Failed to write Sky for Astrometry model");
+	return false;
+    }
+
+    return true;
+}
+
+bool pmAstromModelWritePHU (pmFPAfile *file) { 
+
+    // output header data 
+    psMetadata *outhead = psMetadataAlloc();
+
+    // use the FPA phu to generate the PHU header
+    pmHDU *phu = file->fpa->hdu;
+
+    // if there is no FPA PHU, this is a single header+image (extension-less) file. This could be
+    // the case for an input SPLIT set of files being written out as a MEF.  if there is a PHU,
+    // write it out as a 'blank'
+    if (phu) {
+	psMetadataCopy (outhead, phu->header);
+    } else {
+	pmConfigConformHeader (outhead, file->format);
+
+# if (0)
+	// XXX this is no longer needed : FPA.OBS is now correctly copied in pmConfigConformHeader
+	// XXX probably need other info to conform the header
+	psMetadata *fileData = psMetadataLookupMetadata(NULL, file->format, "FILE"); // File information
+	const char *fpaNameHdr = psMetadataLookupStr(NULL, fileData, "FPA.NAME");
+	if (fpaNameHdr && strlen(fpaNameHdr) > 0) {
+	    const char *fpaName = psMetadataLookupStr(NULL, file->fpa->concepts, "FPA.NAME");
+	    psMetadataAddStr(outhead, PS_LIST_TAIL, fpaNameHdr, PS_META_REPLACE, "FPA name", fpaName);
+	}
+# endif
+    }
+
+    psMetadataAddBool (outhead, PS_LIST_TAIL, "EXTEND", PS_META_REPLACE, "this file has extensions", true);
+    psFitsWriteBlank (file->fits, outhead, "");
+    file->wrote_phu = true;
+
+    psTrace ("pmFPAfile", 5, "wrote phu %s (type: %d)\n", file->filename, file->type);
+    psFree (outhead);
+
+    return true;
+}
+
+// fourth layer holds the chips
+bool pmAstromModelWriteChips (pmFPAfile *file) {
+
+    psMetadata *header = psMetadataAlloc();
+    psMetadataAddStr(header, PS_LIST_TAIL, "COORD",    PS_META_REPLACE, "name of this layer",	"CHIPS");
+    psMetadataAddStr(header, PS_LIST_TAIL, "PARENT",   PS_META_REPLACE, "next layer up",     	"FOCAL_PLANE");
+    psMetadataAddStr(header, PS_LIST_TAIL, "BOUNDARY", PS_META_REPLACE, "validity region",   	"RECTANGLE");
+    psMetadataAddStr(header, PS_LIST_TAIL, "TRANSFRM", PS_META_REPLACE, "mapping to parent", 	"POLYNOMIAL");
+
+    psArray *model = psArrayAllocEmpty (1);
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    pmChip *chip = NULL;
+    while ((chip = pmFPAviewNextChip (view, file->fpa, 1)) != NULL) {
+
+	if (!chip->toFPA) continue;
+	assert (chip->toFPA->x);
+	assert (chip->toFPA->y);
+      
+	psRegion *region = pmChipPixels (chip);
+
+	// set the chip name
+	char *chiprule = psStringCopy ("{CHIP.NAME}"); 
+        char *chipname = pmFPAfileNameFromRule (chiprule, file, view);
+
+	for (int i = 0; i <= chip->toFPA->x->nX; i++) {
+	    for (int j = 0; j <= chip->toFPA->x->nY; j++) {
+		psMetadata *row = psMetadataAlloc ();
+		
+		psMetadataAddStr(row,    PS_LIST_TAIL, "SEGMENT",  PS_META_REPLACE, "name of this segment", chipname);
+		psMetadataAddStr(row,    PS_LIST_TAIL, "PARENT",   PS_META_REPLACE, "next layer up",        "FOCAL_PLANE");
+		psMetadataAddF32(row,    PS_LIST_TAIL, "MINX",     PS_META_REPLACE, "range", region->x0);
+		psMetadataAddF32(row,    PS_LIST_TAIL, "MAXX",     PS_META_REPLACE, "range", region->x1);
+		psMetadataAddF32(row,    PS_LIST_TAIL, "MINY",     PS_META_REPLACE, "range", region->y0);
+		psMetadataAddF32(row,    PS_LIST_TAIL, "MAXY",     PS_META_REPLACE, "range", region->y1);
+    
+		psMetadataAddS32(row,    PS_LIST_TAIL, "XORDER",   PS_META_REPLACE, "", i);
+		psMetadataAddS32(row,    PS_LIST_TAIL, "YORDER",   PS_META_REPLACE, "", j);
+		psMetadataAddS32(row,    PS_LIST_TAIL, "NXORDER",  PS_META_REPLACE, "", chip->toFPA->x->nX);
+		psMetadataAddS32(row,    PS_LIST_TAIL, "NYORDER",  PS_META_REPLACE, "", chip->toFPA->x->nY);
+		psMetadataAddF32(row,    PS_LIST_TAIL, "POLY_X",   PS_META_REPLACE, "", chip->toFPA->x->coeff[i][j]);
+		psMetadataAddF32(row,    PS_LIST_TAIL, "POLY_Y",   PS_META_REPLACE, "", chip->toFPA->y->coeff[i][j]);
+		psMetadataAddF32(row,    PS_LIST_TAIL, "ERROR_X",  PS_META_REPLACE, "", chip->toFPA->x->coeffErr[i][j]);
+		psMetadataAddF32(row,    PS_LIST_TAIL, "ERROR_Y",  PS_META_REPLACE, "", chip->toFPA->y->coeffErr[i][j]);
+		psMetadataAddU8 (row,    PS_LIST_TAIL, "MASK_X",   PS_META_REPLACE, "", chip->toFPA->x->coeffMask[i][j]);
+		psMetadataAddU8 (row,    PS_LIST_TAIL, "MASK_Y",   PS_META_REPLACE, "", chip->toFPA->y->coeffMask[i][j]);
+		psArrayAdd (model, 100, row);
+		psFree (row);
+	    }
+	}
+	psFree (chiprule);
+	psFree (chipname);
+	psFree (region);
+    }
+
+    if (!psFitsWriteTable (file->fits, header, model, "CHIPS")) {
+        psError(PS_ERR_IO, false, "writing sky data\n");
+        psFree(model);
+        return false;
+    }
+
+    psFree (view);
+    psFree (model);
+    psFree (header);
+    return true;
+}
+
+// third layer is the focal plane
+bool pmAstromModelWriteFP (pmFPAfile *file) {
+
+    bool status;
+
+    psMetadata *header = psMetadataAlloc();
+    psMetadataAddStr(header, PS_LIST_TAIL, "COORD",    PS_META_REPLACE, "name of this layer",	"FOCAL_PLANE");
+    psMetadataAddStr(header, PS_LIST_TAIL, "PARENT",   PS_META_REPLACE, "next layer up",     	"TANGENT_PLANE");
+    psMetadataAddStr(header, PS_LIST_TAIL, "BOUNDARY", PS_META_REPLACE, "validity region",   	"RECTANGLE");
+    psMetadataAddStr(header, PS_LIST_TAIL, "TRANSFRM", PS_META_REPLACE, "mapping to parent", 	"POLYNOMIAL");
+
+    psArray *model = psArrayAllocEmpty (1);
+
+    // XXX is this or the tpa region correct?
+    psRegion *region = pmAstromFPAExtent (file->fpa);
+
+    // rotate the toTPA to have 0.0 posangle
+    float posangle = psMetadataLookupF32 (&status, file->fpa->concepts, "FPA.POSANGLE"); 
+
+    // the to/from TPA transform currently has rotation of posangle; remove it & create the toTPA version
+    psPlaneTransform *fromTPA = psPlaneTransformRotate (NULL, file->fpa->fromTPA, -posangle);
+    psPlaneTransform *toTPA   = psPlaneTransformInvert(NULL, fromTPA, *region, 50);
+
+    for (int i = 0; i <= toTPA->x->nX; i++) {
+	for (int j = 0; j <= toTPA->x->nY; j++) {
+	    psMetadata *row = psMetadataAlloc ();
+	    psMetadataAddStr(row,    PS_LIST_TAIL, "SEGMENT",  PS_META_REPLACE, "name of this segment", "FOCAL_PLANE");
+	    psMetadataAddStr(row,    PS_LIST_TAIL, "PARENT",   PS_META_REPLACE, "next layer up",        "TANGENT_PLANE");
+	    psMetadataAddF32(row,    PS_LIST_TAIL, "MINX",     PS_META_REPLACE, "range", region->x0);
+	    psMetadataAddF32(row,    PS_LIST_TAIL, "MAXX",     PS_META_REPLACE, "range", region->x1);
+	    psMetadataAddF32(row,    PS_LIST_TAIL, "MINY",     PS_META_REPLACE, "range", region->y0);
+	    psMetadataAddF32(row,    PS_LIST_TAIL, "MAXY",     PS_META_REPLACE, "range", region->y1);
+    
+	    psMetadataAddS32(row,    PS_LIST_TAIL, "XORDER",   PS_META_REPLACE, "", i);
+	    psMetadataAddS32(row,    PS_LIST_TAIL, "YORDER",   PS_META_REPLACE, "", j);
+	    psMetadataAddS32(row,    PS_LIST_TAIL, "NXORDER",  PS_META_REPLACE, "", toTPA->x->nX);
+	    psMetadataAddS32(row,    PS_LIST_TAIL, "NYORDER",  PS_META_REPLACE, "", toTPA->x->nY);
+	    psMetadataAddF32(row,    PS_LIST_TAIL, "POLY_X",   PS_META_REPLACE, "", toTPA->x->coeff[i][j]);
+	    psMetadataAddF32(row,    PS_LIST_TAIL, "POLY_Y",   PS_META_REPLACE, "", toTPA->y->coeff[i][j]);
+	    psMetadataAddF32(row,    PS_LIST_TAIL, "ERROR_X",  PS_META_REPLACE, "", toTPA->x->coeffErr[i][j]);
+	    psMetadataAddF32(row,    PS_LIST_TAIL, "ERROR_Y",  PS_META_REPLACE, "", toTPA->y->coeffErr[i][j]);
+	    psMetadataAddU8 (row,    PS_LIST_TAIL, "MASK_X",   PS_META_REPLACE, "", toTPA->x->coeffMask[i][j]);
+	    psMetadataAddU8 (row,    PS_LIST_TAIL, "MASK_Y",   PS_META_REPLACE, "", toTPA->y->coeffMask[i][j]);
+
+	    psArrayAdd (model, 100, row);
+	    psFree (row);
+	}
+    }
+
+    if (!psFitsWriteTable (file->fits, header, model, "FP")) {
+        psError(PS_ERR_IO, false, "writing sky data\n");
+        psFree(model);
+	psFree (header);
+	psFree (region);
+        return false;
+    }
+
+    psFree (model);
+    psFree (header);
+    psFree (region);
+    return true;
+}
+
+// second layer is the tangent plane
+bool pmAstromModelWriteTP (pmFPAfile *file) {
+
+    bool status;
+
+    // get the boresite model parameters
+    float Xo = psMetadataLookupF32 (&status, file->fpa->concepts, "FPA.BORE.X0"); 
+    float Yo = psMetadataLookupF32 (&status, file->fpa->concepts, "FPA.BORE.Y0"); 
+    float RX = psMetadataLookupF32 (&status, file->fpa->concepts, "FPA.BORE.RX"); 
+    float RY = psMetadataLookupF32 (&status, file->fpa->concepts, "FPA.BORE.RY"); 
+    float To = psMetadataLookupF32 (&status, file->fpa->concepts, "FPA.BORE.T0"); 
+    float Po = psMetadataLookupF32 (&status, file->fpa->concepts, "FPA.BORE.P0"); 
+    float PosZero = psMetadataLookupF32 (&status, file->fpa->concepts, "FPA.POS_ZERO");  /// XXX be consistent with degrees v radians
+    char *refChip = psMetadataLookupStr (&status, file->fpa->concepts, "FPA.REF.CHIP");
+
+    psMetadata *header = psMetadataAlloc();
+    psMetadataAddStr(header, PS_LIST_TAIL, "COORD",    PS_META_REPLACE, "name of this layer",	"TANGENT_PLANE");
+    psMetadataAddStr(header, PS_LIST_TAIL, "PARENT",   PS_META_REPLACE, "next layer up",     	"SKY");
+    psMetadataAddStr(header, PS_LIST_TAIL, "BOUNDARY", PS_META_REPLACE, "validity region",   	"RECTANGLE");
+    psMetadataAddStr(header, PS_LIST_TAIL, "TRANSFRM", PS_META_REPLACE, "mapping to parent", 	"PROJECTION");
+
+    psArray *model = psArrayAllocEmpty (1);
+    psMetadata *row = psMetadataAlloc ();
+    psMetadataAddStr(row,    PS_LIST_TAIL, "SEGMENT",  PS_META_REPLACE, "name of this segment", "TANGENT_PLANE");
+    psMetadataAddStr(row,    PS_LIST_TAIL, "PARENT",   PS_META_REPLACE, "next layer up",        "SKY");
+
+    psRegion *region = pmAstromFPAExtent (file->fpa);
+    psMetadataAddF32(row,    PS_LIST_TAIL, "MINX",     PS_META_REPLACE, "range", region->x0);
+    psMetadataAddF32(row,    PS_LIST_TAIL, "MAXX",     PS_META_REPLACE, "range", region->x1);
+    psMetadataAddF32(row,    PS_LIST_TAIL, "MINY",     PS_META_REPLACE, "range", region->y0);
+    psMetadataAddF32(row,    PS_LIST_TAIL, "MAXY",     PS_META_REPLACE, "range", region->y1);
+    
+    // psMetadataAddF32(row,    PS_LIST_TAIL, "XREF",     PS_META_REPLACE, "", file->fpa->toSky->R  * PS_DEG_RAD);
+    // psMetadataAddF32(row,    PS_LIST_TAIL, "YREF",     PS_META_REPLACE, "", file->fpa->toSky->D  * PS_DEG_RAD);
+
+    psMetadataAddF32(row,    PS_LIST_TAIL, "XSCALE",   PS_META_REPLACE, "", file->fpa->toSky->Xs * PS_DEG_RAD);
+    psMetadataAddF32(row,    PS_LIST_TAIL, "YSCALE",   PS_META_REPLACE, "", file->fpa->toSky->Ys * PS_DEG_RAD);
+    psMetadataAddF32(row,    PS_LIST_TAIL, "BORE_X0",  PS_META_REPLACE, "boresite parameter", Xo);
+    psMetadataAddF32(row,    PS_LIST_TAIL, "BORE_Y0",  PS_META_REPLACE, "boresite parameter", Yo);
+    psMetadataAddF32(row,    PS_LIST_TAIL, "BORE_RX",  PS_META_REPLACE, "boresite parameter", RX);
+    psMetadataAddF32(row,    PS_LIST_TAIL, "BORE_RY",  PS_META_REPLACE, "boresite parameter", RY);
+    psMetadataAddF32(row,    PS_LIST_TAIL, "BORE_T0",  PS_META_REPLACE, "boresite parameter", To);
+    psMetadataAddF32(row,    PS_LIST_TAIL, "BORE_P0",  PS_META_REPLACE, "boresite parameter", Po);
+    psMetadataAddF32(row,    PS_LIST_TAIL, "POS_ZERO", PS_META_REPLACE, "boresite parameter", PosZero);
+    psMetadataAddStr(row,    PS_LIST_TAIL, "REF_CHIP", PS_META_REPLACE, "boresite parameter", refChip);
+
+    psArrayAdd (model, 100, row);
+    psFree (row);
+
+    if (!psFitsWriteTable (file->fits, header, model, "TP")) {
+        psError(PS_ERR_IO, false, "writing sky data\n");
+	psFree (region);
+        psFree (model);
+	psFree (header);
+        return false;
+    }
+
+    psFree (region);
+    psFree (model);
+    psFree (header);
+    return (true);
+}
+
+// first layer is the sky
+bool pmAstromModelWriteSky (pmFPAfile *file) {
+
+    psMetadata *header = psMetadataAlloc();
+    psMetadataAddStr(header, PS_LIST_TAIL, "COORD",    PS_META_REPLACE, "name of this layer", 	"SKY");
+    psMetadataAddStr(header, PS_LIST_TAIL, "PARENT",   PS_META_REPLACE, "next layer up",      	"NONE");
+    psMetadataAddStr(header, PS_LIST_TAIL, "BOUNDARY", PS_META_REPLACE, "validity region",    	"NONE");
+    psMetadataAddStr(header, PS_LIST_TAIL, "TRANSFRM", PS_META_REPLACE, "mapping to parent",  	"NONE");
+
+    psArray *model = psArrayAllocEmpty (1);
+    psMetadata *row = psMetadataAlloc ();
+    psMetadataAddStr(row,    PS_LIST_TAIL, "SEGMENT",  PS_META_REPLACE, "name of this segment", "SKY");
+    psMetadataAddStr(row,    PS_LIST_TAIL, "PARENT",   PS_META_REPLACE, "next layer up",        "NONE");
+    
+    psArrayAdd (model, 100, row);
+    psFree (row);
+
+    if (!psFitsWriteTable (file->fits, header, model, "SKY")) {
+        psError(PS_ERR_IO, false, "writing sky data\n");
+        psFree(model);
+        return false;
+    }
+
+    psFree (model);
+    psFree (header);
+    return (true);
+}
+
+/********************* Read Data functions *****************************/
+
+bool pmAstromModelReadForView (const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+    {
+
+	// write the full model in one pass: require the level to be FPA
+	if (view->chip != -1) {
+	    psError(PS_ERR_IO, false, "Astrometry must be read at the FPA level");
+	    return false;
+	}
+
+	if (!pmAstromModelReadFPA (file)) {
+	    psError(PS_ERR_IO, false, "Failed to read Astrometry for fpa");
+	    return false;
+	}
+	return true;
+    }
+
+// read out all chip-level Astrometry data for this FPA
+bool pmAstromModelReadFPA (pmFPAfile *file) {
+
+    if (!pmAstromModelReadPHU (file)) {
+	psError(PS_ERR_IO, false, "Failed to read PHU for Astrometry model");
+	return false;
+    }
+
+    if (!pmAstromModelReadChips (file)) {
+	psError(PS_ERR_IO, false, "Failed to read Astrometry for chips");
+	return false;
+    }
+
+    if (!pmAstromModelReadFP (file)) {
+	psError(PS_ERR_IO, false, "Failed to read Sky for Astrometry model");
+	return false;
+    }
+
+    // NOTE : TP must come after FP as it applies the POS, ROT boresite corrections to the
+    // transformation determined in FP
+    if (!pmAstromModelReadTP (file)) {
+	psError(PS_ERR_IO, false, "Failed to read Sky for Astrometry model");
+	return false;
+    }
+
+    if (!pmAstromModelReadSky (file)) {
+	psError(PS_ERR_IO, false, "Failed to read Sky for Astrometry model");
+	return false;
+    }
+
+    return true;
+}
+
+bool pmAstromModelReadPHU (pmFPAfile *file) { 
+
+    fprintf (stderr, "not sure we need to read PHU\n");
+    return true;
+}
+
+int pmConceptsChipNumberFromName (pmFPA *fpa, char *name) {
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+	pmChip *chip = fpa->chips->data[i];
+	if (!chip) continue;
+	char *thisone = psMetadataLookupStr (NULL, chip->concepts, "CHIP.NAME");
+	if (!thisone) continue;
+	if (!strcmp (name, thisone)) return (i);
+    }
+    return -1;
+}
+
+pmChip *pmConceptsChipFromName (pmFPA *fpa, char *name) {
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+	pmChip *chip = fpa->chips->data[i];
+	if (!chip) continue;
+	char *thisone = psMetadataLookupStr (NULL, chip->concepts, "CHIP.NAME");
+	if (!thisone) continue;
+	if (!strcmp (name, thisone)) return (chip);
+    }
+    return NULL;
+}
+
+// first layer converts Chip to Focal Plane
+bool pmAstromModelReadChips (pmFPAfile *file) {
+
+    bool status;
+
+    // set FITS cursor
+    if (!psFitsMoveExtName (file->fits, "CHIPS")) {
+        psError(PS_ERR_IO, false, "missing CHIPS extension in astrometry model\n");
+        return false;
+    }
+
+    // free exising tranformations in prep for new alloc below
+    for (int i = 0; i < file->fpa->chips->n; i++) {
+	pmChip *chip = file->fpa->chips->data[i];
+	psFree (chip->toFPA);
+	chip->toFPA = NULL;
+    }
+
+    // XXX do I need anything from the header?
+    psMetadata *header = psFitsReadHeader(NULL, file->fits); // The FITS header
+    if (!header) psAbort("cannot read model header");
+
+    // load the full model in one shot
+    psArray *model = psFitsReadTable (file->fits);
+    if (!model) psAbort("cannot read model");
+    fprintf (stderr, "read %ld rows from FP\n", model->n);
+
+    // parse the model entries
+    for (int i = 0; i < model->n; i++) {
+	psMetadata *row = model->data[i];
+
+	// name of the chip for this row. 
+	char *chipname = psMetadataLookupStr (&status, row, "SEGMENT");
+
+	// get chip from name
+	pmChip *chip = pmConceptsChipFromName (file->fpa, chipname);
+	REQUIRE (chip, "invalid chip name");
+
+	// define the toFPA transform if not already defined
+	int nX = psMetadataLookupS32(&status, row, "NXORDER"); REQUIRE (status, "missing NXORDER");
+	int nY = psMetadataLookupS32(&status, row, "NYORDER"); REQUIRE (status, "missing NYORDER");
+	if (chip->toFPA == NULL) {
+	    chip->toFPA = psPlaneTransformAlloc(nX, nY);
+	} else {
+	    REQUIRE (chip->toFPA->x->nX == nX, "mismatch in chip order");
+	    REQUIRE (chip->toFPA->x->nY == nY, "mismatch in chip order");
+	    REQUIRE (chip->toFPA->y->nX == nX, "mismatch in chip order");
+	    REQUIRE (chip->toFPA->y->nY == nY, "mismatch in chip order");
+	}
+
+	int ix = psMetadataLookupS32(&status, row, "XORDER");  REQUIRE (status, "missing XORDER");
+	int iy = psMetadataLookupS32(&status, row, "YORDER");  REQUIRE (status, "missing YORDER");
+
+	// XXX do I need to use this or can i rely on the camera concepts?
+	// minX = psMetadataLookupF32(&status, row, "MINX");
+	// maxX = psMetadataLookupF32(&status, row, "MAXX");
+	// minY = psMetadataLookupF32(&status, row, "MINY");
+	// maxY = psMetadataLookupF32(&status, row, "MAXY");
+
+	// XXX need to include mask values
+	chip->toFPA->x->coeff[ix][iy]    = psMetadataLookupF32(&status, row, "POLY_X");
+	chip->toFPA->y->coeff[ix][iy]    = psMetadataLookupF32(&status, row, "POLY_Y");
+	chip->toFPA->x->coeffErr[ix][iy] = psMetadataLookupF32(&status, row, "ERROR_X");
+	chip->toFPA->y->coeffErr[ix][iy] = psMetadataLookupF32(&status, row, "ERROR_Y");
+	chip->toFPA->x->coeffMask[ix][iy] = psMetadataLookupU8(&status, row, "MASK_X");
+	chip->toFPA->y->coeffMask[ix][iy] = psMetadataLookupU8(&status, row, "MASK_Y");
+    }
+
+    // convert the toFPA transfomations to fromFPA transformations
+    for (int i = 0; i < file->fpa->chips->n; i++) {
+	pmChip *chip = file->fpa->chips->data[i];
+	if (!chip->toFPA) continue;
+	psRegion *region = pmChipPixels (chip);
+	psFree (chip->fromFPA);
+	chip->fromFPA = psPlaneTransformInvert(NULL, chip->toFPA, *region, 50);
+	psFree (region);
+    }
+
+    psFree (model);
+    psFree (header);
+    return true;
+}
+
+// second layer converts Focal Plane to Tangent Plane (unrotated)
+bool pmAstromModelReadFP (pmFPAfile *file) {
+
+    bool status;
+
+    if (!psFitsMoveExtName (file->fits, "FP")) {
+        psError(PS_ERR_IO, false, "missing FP extension in astrometry model\n");
+        return false;
+    }
+
+    psMetadata *header = psFitsReadHeader(NULL, file->fits); // The FITS header
+    if (!header) psAbort("cannot read model header");
+
+    // free the old
+    psFree (file->fpa->toTPA);
+    file->fpa->toTPA = NULL;
+
+    // read the complete model data at one shot
+    psArray *model = psFitsReadTable (file->fits);
+    fprintf (stderr, "read %ld rows from FP\n", model->n);
+
+    // parse the model
+    for (int i = 0; i < model->n; i++) {
+	psMetadata *row = model->data[i];
+
+	// there is only one transformation in this model; the order is defined in the header
+	int nX = psMetadataLookupS32(&status, row, "NXORDER"); REQUIRE (status, "missing NXORDER");
+	int nY = psMetadataLookupS32(&status, row, "NYORDER"); REQUIRE (status, "missing NYORDER");
+	if (file->fpa->toTPA == NULL) {
+	    // allocate the new transformation
+	    file->fpa->toTPA = psPlaneTransformAlloc(nX, nY);
+	} else {
+	    REQUIRE (file->fpa->toTPA->x->nX == nX, "mismatch in chip order");
+	    REQUIRE (file->fpa->toTPA->x->nY == nY, "mismatch in chip order");
+	    REQUIRE (file->fpa->toTPA->y->nX == nX, "mismatch in chip order");
+	    REQUIRE (file->fpa->toTPA->y->nY == nY, "mismatch in chip order");
+	}
+
+	int ix = psMetadataLookupS32(&status, row, "XORDER"); REQUIRE (status, "missing XORDER");
+	int iy = psMetadataLookupS32(&status, row, "YORDER"); REQUIRE (status, "missing YORDER");
+	file->fpa->toTPA->x->coeff[ix][iy]     = psMetadataLookupF32(&status, row, "POLY_X");  REQUIRE (status, "missing POLY_X");
+	file->fpa->toTPA->y->coeff[ix][iy]     = psMetadataLookupF32(&status, row, "POLY_Y");  REQUIRE (status, "missing POLY_Y");
+	file->fpa->toTPA->x->coeffErr[ix][iy]  = psMetadataLookupF32(&status, row, "ERROR_X"); REQUIRE (status, "missing ERROR_X");
+	file->fpa->toTPA->y->coeffErr[ix][iy]  = psMetadataLookupF32(&status, row, "ERROR_Y"); REQUIRE (status, "missing ERROR_Y");
+	file->fpa->toTPA->x->coeffMask[ix][iy] = psMetadataLookupU8 (&status, row, "MASK_X");  REQUIRE (status, "missing MASK_X");
+	file->fpa->toTPA->y->coeffMask[ix][iy] = psMetadataLookupU8 (&status, row, "MASK_Y");  REQUIRE (status, "missing MASK_Y");
+    }
+
+    psRegion *region = pmAstromFPAExtent (file->fpa);
+    psFree (file->fpa->fromTPA);
+    file->fpa->fromTPA = psPlaneTransformInvert(NULL, file->fpa->toTPA, *region, 50);
+
+    psFree (model);
+    psFree (header);
+    psFree (region);
+    return true;
+}
+
+# define TRANSFER(TO,FROM,NAME) { \
+    psMetadataItem *item = psMetadataLookup(FROM,NAME); \
+    if (!item) psAbort ("cannot find %s", NAME); \
+    psMetadataItem *newItem = psMetadataItemCopy(item); \
+    if (!psMetadataAddItem(TO, newItem, PS_LIST_TAIL, PS_META_REPLACE)) { \
+	psAbort ("cannot copy %s", NAME); \
+    } \
+    psFree (newItem); }
+
+// third layer applies boresite corrections and converts tangent plane to sky
+bool pmAstromModelReadTP (pmFPAfile *file) {
+    
+    if (!psFitsMoveExtName (file->fits, "TP")) {
+        psError(PS_ERR_IO, false, "missing TP extension in astrometry model\n");
+        return false;
+    }
+
+    psMetadata *header = psFitsReadHeader(NULL, file->fits); // The FITS header
+    if (!header) psAbort("cannot read model header");
+
+    psArray *model = psFitsReadTable (file->fits);
+    fprintf (stderr, "read %ld rows from TP\n", model->n);
+    if (model->n != 1) psAbort("invalid number of rows in TP model (%ld)", model->n);
+
+    psMetadata *row = model->data[0];
+
+    // move needed items to the concepts
+    TRANSFER (file->fpa->concepts, row, "XSCALE");
+    TRANSFER (file->fpa->concepts, row, "XSCALE");
+    TRANSFER (file->fpa->concepts, row, "YSCALE");
+    TRANSFER (file->fpa->concepts, row, "BORE_X0");
+    TRANSFER (file->fpa->concepts, row, "BORE_Y0");
+    TRANSFER (file->fpa->concepts, row, "BORE_RX");
+    TRANSFER (file->fpa->concepts, row, "BORE_RY");
+    TRANSFER (file->fpa->concepts, row, "BORE_T0");
+    TRANSFER (file->fpa->concepts, row, "BORE_P0");
+    TRANSFER (file->fpa->concepts, row, "POS_ZERO");
+    TRANSFER (file->fpa->concepts, row, "REF_CHIP");
+
+    psFree (model);
+    psFree (header);
+    return (true);
+}
+
+// first layer is the sky
+bool pmAstromModelReadSky (pmFPAfile *file) {
+
+    if (!psFitsMoveExtName (file->fits, "SKY")) {
+        psError(PS_ERR_IO, false, "missing SKY extension in astrometry model\n");
+        return false;
+    }
+
+    psMetadata *header = psFitsReadHeader(NULL, file->fits); // The FITS header
+    if (!header) psAbort("cannot read model header");
+
+    psArray *model = psFitsReadTable (file->fits);
+    fprintf (stderr, "read %ld rows from SKY\n", model->n);
+    if (model->n != 1) psAbort("invalid number of rows in SKY model (%ld)", model->n);
+
+    // XXX not much information of interest in this table...
+
+    psFree (model);
+    psFree (header);
+    return (true);
+}
+
+// third layer applies boresite corrections and converts tangent plane to sky
+bool pmAstromModelSetTP (pmFPAfile *file, psMetadata *concepts) {
+    
+    bool status;
+
+    // these externally supplied values are used to set the final transformation terms
+    double RA  = psMetadataLookupF64 (&status, concepts, "FPA.RA"); REQUIRE (status, "missing FPA.RA");
+    double DEC = psMetadataLookupF64 (&status, concepts, "FPA.DEC"); REQUIRE (status, "missing FPA.DEC");
+    double POS = PM_RAD_DEG * psMetadataLookupF64 (&status, concepts, "FPA.POSANGLE"); REQUIRE (status, "missing FPA.POSANGLE");
+    // double ROT = psMetadataLookupF64 (&status, concepts, "FPA.ROTANGLE"); REQUIRE (status, "missing FPA.ROTANGLE");
+
+    // get projection scale; center is supplied
+    float Xs = psMetadataLookupF32(&status, file->fpa->concepts, "XSCALE") * PM_RAD_DEG; REQUIRE (status, "missing XSCALE");
+    float Ys = psMetadataLookupF32(&status, file->fpa->concepts, "YSCALE") * PM_RAD_DEG; REQUIRE (status, "missing YSCALE");
+
+    // allocate a new toSky projection using the reported position
+    psFree (file->fpa->toSky);
+    file->fpa->toSky = psProjectionAlloc (RA, DEC, Xs, Ys, PS_PROJ_DIS);
+
+    // get boresite correction terms.  RX,RY,To,Po define an ellipse along which the boresite travels
+    double Xo = psMetadataLookupF32(&status, file->fpa->concepts, "BORE_X0"); REQUIRE (status, "missing ");
+    double Yo = psMetadataLookupF32(&status, file->fpa->concepts, "BORE_Y0"); REQUIRE (status, "missing ");
+    double RX = psMetadataLookupF32(&status, file->fpa->concepts, "BORE_RX"); REQUIRE (status, "missing ");
+    double RY = psMetadataLookupF32(&status, file->fpa->concepts, "BORE_RY"); REQUIRE (status, "missing ");
+    double To = psMetadataLookupF32(&status, file->fpa->concepts, "BORE_T0"); REQUIRE (status, "missing ");
+    double Po = psMetadataLookupF32(&status, file->fpa->concepts, "BORE_P0"); REQUIRE (status, "missing ");
+
+    // the true rotation of the instrument is POSANGLE - POS_ZERO
+    double PosZero = PM_RAD_DEG * psMetadataLookupF32(&status, file->fpa->concepts, "POS_ZERO"); REQUIRE (status, "missing POS_ZERO");
+    char *refChip  = psMetadataLookupStr(&status, file->fpa->concepts, "REF_CHIP"); REQUIRE (status, "missing REF_CHIP");
+
+    // apply true posangle = -(POS - POS_ZERO)
+    psPlaneTransform *fromTPA = psPlaneTransformRotate (NULL, file->fpa->fromTPA, (POS - PosZero));
+    psFree (file->fpa->fromTPA);
+    file->fpa->fromTPA = fromTPA;
+
+    psRegion *region = pmAstromFPAExtent (file->fpa);
+    psFree (file->fpa->toTPA);
+    file->fpa->toTPA = psPlaneTransformInvert(NULL, file->fpa->fromTPA, *region, 50);
+
+    // current position of the nominal boresite in refChip coordinates
+    double X = Xo + RX*cos(POS - To)*cos(Po) + RY*sin(POS - To)*sin(Po);
+    double Y = Yo + RY*sin(POS - To)*cos(Po) - RX*cos(POS - To)*sin(Po);
+
+    // get reference chip from name
+    pmChip *chip = pmConceptsChipFromName (file->fpa, refChip);
+    if (!chip) psAbort ("invalid chip name for reference");
+
+    psPlane *boreCH = psPlaneAlloc();
+    psPlane *boreFP = psPlaneAlloc();
+    psPlane *boreTP = psPlaneAlloc();
+    psSphere *boreSky = psSphereAlloc();
+
+    // find the true RA,DEC coord of the mirror of the reported boresite
+    boreCH->x = -X;
+    boreCH->y = -Y;
+    psPlaneTransformApply (boreFP, chip->toFPA, boreCH);
+    psPlaneTransformApply (boreTP, file->fpa->toTPA, boreFP);
+    psDeproject (boreSky, boreTP, file->fpa->toSky); 
+
+    // modify the projection to account for offset between true and reported boresite
+    file->fpa->toSky->R = boreSky->r;
+    file->fpa->toSky->D = boreSky->d;
+
+    psTrace ("psModules.astrom", 5, "actual boresite coordinates: %lf, %lf\n", file->fpa->toSky->R*PS_DEG_RAD, file->fpa->toSky->D*PS_DEG_RAD);
+    psTrace ("psModules.astrom", 5, "plate scale used: %lf, %lf\n", file->fpa->toSky->Xs*PS_DEG_RAD*3600.0, file->fpa->toSky->Ys*PS_DEG_RAD*3600.0);
+
+    psFree (boreCH);
+    psFree (boreFP);
+    psFree (boreTP);
+    psFree (boreSky);
+
+    psFree (region);
+    return (true);
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryModel.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryModel.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryModel.h	(revision 22157)
@@ -0,0 +1,43 @@
+/* @file  pmAstrometryModel.h
+ * @brief Astrometry model I/O functions
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-03-18 18:24:29 $
+ * Copyright 2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_ASTROMETRY_MODEL_H
+#define PM_ASTROMETRY_MODEL_H
+
+/// @addtogroup Astrometry
+/// @{
+
+bool pmAstromModelCheckDataStatusForView (const pmFPAview *view, pmFPAfile *file);
+bool pmAstromModelCheckDataStatusForFPA (const pmFPA *fpa);
+bool pmAstromModelCheckDataStatusForChip (const pmChip *chip);
+
+bool pmAstromModelWriteForView (const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmAstromModelWriteFPA (pmFPAfile *file);
+bool pmAstromModelWritePHU (pmFPAfile *file);
+bool pmAstromModelWriteSky (pmFPAfile *file);
+bool pmAstromModelWriteTP (pmFPAfile *file);
+bool pmAstromModelWriteFP (pmFPAfile *file);
+bool pmAstromModelWriteChips (pmFPAfile *file);
+
+int pmConceptsChipNumberFromName (pmFPA *fpa, char *name);
+pmChip *pmConceptsChipFromName (pmFPA *fpa, char *name);
+
+bool pmAstromModelReadForView (const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmAstromModelReadFPA (pmFPAfile *file);
+bool pmAstromModelReadPHU (pmFPAfile *file);
+bool pmAstromModelReadChips (pmFPAfile *file);
+bool pmAstromModelReadFP (pmFPAfile *file);
+bool pmAstromModelReadTP (pmFPAfile *file);
+bool pmAstromModelReadSky (pmFPAfile *file);
+
+bool pmAstromModelSetTP (pmFPAfile *file, psMetadata *concepts);
+
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryObjects.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryObjects.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryObjects.c	(revision 22157)
@@ -0,0 +1,929 @@
+/** @file  pmAstrometryObjects.c
+*
+*  @brief This file defines the basic types for matching objects
+*  based on their astrometry.
+*
+*  @ingroup AstroImage
+*
+*  @author EAM, IfA
+*
+*  @version $Revision: 1.39 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2008-04-11 07:42:05 $
+*
+*  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/******************************************************************************/
+/*  INCLUDE FILES                                                             */
+/******************************************************************************/
+#include <stdio.h>
+#include <strings.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+#include <unistd.h>   // for unlink
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmAstrometryObjects.h"
+
+#define PM_ASTROMETRYOBJECTS_DEBUG 1
+/******************************************************************************
+pmAstromObjSortByMag(**a, **b): sort by mag (descending)
+
+Is this a private routine?
+Should we do the early asserts?
+ ******************************************************************************/
+int pmAstromObjSortByMag(
+    const void **a,
+    const void **b)
+{
+    if (PM_ASTROMETRYOBJECTS_DEBUG) {
+        PS_ASSERT_PTR_NON_NULL(a, 0);
+        PS_ASSERT_PTR_NON_NULL(*a, 0);
+        PS_ASSERT_PTR_NON_NULL(b, 0);
+        PS_ASSERT_PTR_NON_NULL(*b, 0);
+    }
+
+    pmAstromObj *A = *(pmAstromObj **)a;
+    pmAstromObj *B = *(pmAstromObj **)b;
+
+    psF32 diff = A->Mag - B->Mag;
+    if (diff > FLT_EPSILON) {
+        return (-1);
+    }
+
+    if (diff < FLT_EPSILON) {
+        return (+1);
+    }
+
+    return (0);
+}
+
+/************************************************************************************************************/
+/*
+ * Working routine to match two lists (where x[12] are sorted), given psVectors of their coordinates and the
+ * permutation used to sort in x
+ */
+static psArray *match_lists(const psVector *x1, const psVector *y1, // x/y coordinates of first set of objects
+                            const psVector *x2, const psVector *y2, // x/y   "    "    "  second "   "  "   "
+                            const psVector *sorted1, const psVector *sorted2, // mapping to original order
+                            const double RADIUS) // matching radius
+{
+    psArray *matches = psArrayAllocEmpty(x1->n);
+
+    const double RADIUS_SQR = PS_SQR(RADIUS);
+    double dX, dY, dR;
+
+    int jStart;
+    int i = 0, j = 0;
+    while (i < x1->n && j < x2->n) {
+        dX = x1->data.F64[i] - x2->data.F64[j];
+        if (dX <= -RADIUS) {
+            i++;
+            continue;
+        }
+        if (dX >= +RADIUS) {
+            j++;
+            continue;
+        }
+
+        jStart = j;
+        while (fabs(dX) < RADIUS && j < x2->n) {
+
+            dX = x1->data.F64[i] - x2->data.F64[j];
+            dY = y1->data.F64[i] - y2->data.F64[j];
+            dR = dX*dX + dY*dY;
+
+            if (dR > RADIUS_SQR) {
+                j++;
+                continue;
+            }
+
+            // got a match; add to output list
+            pmAstromMatch *match = pmAstromMatchAlloc (sorted1->data.S32[i], sorted2->data.S32[j]);
+            psArrayAdd (matches, 100, match);
+            psFree (match);
+
+            j++;
+        }
+        j = jStart;
+        i++;
+    }
+
+    return (matches);
+}
+
+/************************************************************************************************************/
+// macro to generate code for radius match function based on desired member
+// radius is in units of matching member (eg, pixels for chip, microns for FP, etc)
+#define MAKE_ASTROM_RADIUS(FUNC, MEMBER) \
+psArray *FUNC( \
+               const psArray *st1, \
+               const psArray *st2, \
+               double RADIUS) \
+{ \
+    PS_ASSERT_PTR_NON_NULL(st1, NULL); \
+    PS_ASSERT_PTR_NON_NULL(st2, NULL); \
+    \
+    assert(st1->n == 0 || pmAstromObjTest(st1->data[0])); \
+    assert(st2->n == 0 || pmAstromObjTest(st2->data[0])); \
+    \
+    /* sort both lists by X coord; st1 first */ \
+    psVector *x1 = psVectorAlloc(st1->n, PS_TYPE_F64); \
+    for (int i = 0; i < st1->n; i++) { \
+        x1->data.F64[i] = ((pmAstromObj *)st1->data[i])->MEMBER->x; \
+    } \
+    const psVector *sorted1 = psVectorSortIndex(NULL, x1); \
+    assert (sorted1->type.type == PS_TYPE_S32); \
+    \
+    psVector *y1 = psVectorAlloc(st1->n, PS_TYPE_F64); \
+    for (int i = 0; i < st1->n; i++) { \
+        x1->data.F64[i] = ((pmAstromObj *)st1->data[sorted1->data.S32[i]])->MEMBER->x; \
+        y1->data.F64[i] = ((pmAstromObj *)st1->data[sorted1->data.S32[i]])->MEMBER->y; \
+    } \
+    \
+    /* now st2 */ \
+    psVector *x2 = psVectorAlloc(st2->n, PS_TYPE_F64); \
+    for (int i = 0; i < st2->n; i++) { \
+        x2->data.F64[i] = ((pmAstromObj *)st2->data[i])->MEMBER->x; \
+    } \
+    const psVector *sorted2 = psVectorSortIndex(NULL, x2); \
+    \
+    psVector *y2 = psVectorAlloc(st2->n, PS_TYPE_F64); \
+    for (int i = 0; i < st2->n; i++) { \
+        x2->data.F64[i] = ((pmAstromObj *)st2->data[sorted2->data.S32[i]])->MEMBER->x; \
+        y2->data.F64[i] = ((pmAstromObj *)st2->data[sorted2->data.S32[i]])->MEMBER->y; \
+    } \
+    /* Do the work */ \
+    psArray *matches = match_lists(x1, y1, x2, y2, sorted1, sorted2, RADIUS); \
+    \
+    psFree((void *)sorted1); \
+    psFree((void *)sorted2); \
+    psFree(x1); \
+    psFree(y1); \
+    psFree(x2); \
+    psFree(y2); \
+    \
+    psLogMsg (__func__, 3, "radius match: %ld pairs (radius: %f)\n", matches->n, RADIUS); \
+    return (matches); \
+}
+
+/******************************************************************************/
+/*
+ * Match two lists of pmAstromObjs, based on the FP, TP, or chip coordinates
+ */
+MAKE_ASTROM_RADIUS(pmAstromRadiusMatch, FP)
+MAKE_ASTROM_RADIUS(pmAstromRadiusMatchFP, FP)
+MAKE_ASTROM_RADIUS(pmAstromRadiusMatchTP, TP)
+MAKE_ASTROM_RADIUS(pmAstromRadiusMatchChip, chip)
+
+/******************************************************************************
+pmAstromMatchFit(map, raw, ref, match, stats): take two matched star lists
+and fit a psPlaneTransform between them
+ ******************************************************************************/
+pmAstromFitResults *pmAstromMatchFit(
+    psPlaneTransform *map,
+    psArray *raw,
+    psArray *ref,
+    psArray *match,
+    psStats *stats)
+{
+    PS_ASSERT_PTR_NON_NULL(map, NULL);
+    PS_ASSERT_PTR_NON_NULL(raw, NULL);
+    PS_ASSERT_PTR_NON_NULL(ref, NULL);
+    PS_ASSERT_PTR_NON_NULL(match, NULL);
+    PS_ASSERT_PTR_NON_NULL(stats, NULL);
+
+    // reassign values for clip fit
+    // XXX set wt based on mag error?
+    psVector *X = psVectorAlloc (match->n, PS_TYPE_F32);
+    psVector *Y = psVectorAlloc (match->n, PS_TYPE_F32);
+    psVector *x = psVectorAlloc (match->n, PS_TYPE_F32);
+    psVector *y = psVectorAlloc (match->n, PS_TYPE_F32);
+    psVector *wt = psVectorAlloc (match->n, PS_TYPE_F32);
+    // take the matched stars, first fit
+    for (int i = 0; i < match->n; i++) {
+        pmAstromMatch *pair = match->data[i];
+        pmAstromObj *rawStar = raw->data[pair->raw];
+        pmAstromObj *refStar = ref->data[pair->ref];
+
+        X->data.F32[i] = rawStar->chip->x;
+        Y->data.F32[i] = rawStar->chip->y;
+
+        x->data.F32[i] = refStar->FP->x;
+        y->data.F32[i] = refStar->FP->y;
+
+        wt->data.F32[i] = 1.0;
+    }
+
+    // constant errors
+    psVector *mask = psVectorAlloc (match->n, PS_TYPE_U8);
+    psVectorInit (mask, 0);
+
+    // the stats options supplied are used to perform the clip fitting
+    pmAstromFitResults *results = pmAstromFitResultsAlloc();
+    results->xStats = psStatsAlloc (PS_STAT_NONE);
+    results->yStats = psStatsAlloc (PS_STAT_NONE);
+    *results->xStats = *stats;
+    *results->yStats = *stats;
+
+    int nIter = stats->clipIter;
+
+    results->xStats->clipIter = 1;
+    results->yStats->clipIter = 1;
+
+    // fit chip-to-FPA transformation
+    // we run 'clipIter' cycles clipping in each of x and y, with only one iteration each
+    // need to use the stats lookups functions to get the width and center
+    for (int i = 0; i < nIter; i++) {
+	if (!psVectorClipFitPolynomial2D (map->x, results->xStats, mask, 0xff, x, wt, X, Y)) {
+            psError(PS_ERR_UNKNOWN, false, "failure in clip-fitting for x\n");
+	    psFree (x);
+	    psFree (y);
+	    psFree (X);
+	    psFree (Y);
+	    psFree (wt);
+	    psFree (mask);
+
+	    return results;
+	}
+        psTrace ("psModules.astrom", 3, "x resid: %f +/- %f (%ld of %ld)\n", results->xStats->clippedMean, results->xStats->clippedStdev, results->xStats->clippedNvalues, x->n);
+
+        if (!psVectorClipFitPolynomial2D (map->y, results->yStats, mask, 0xff, y, wt, X, Y)) {
+            psError(PS_ERR_UNKNOWN, false, "failure in clip-fitting for y\n");
+	    psFree (x);
+	    psFree (y);
+	    psFree (X);
+	    psFree (Y);
+	    psFree (wt);
+	    psFree (mask);
+
+	    return results;
+	}
+        psTrace ("psModules.astrom", 3, "y resid: %f +/- %f (%ld of %ld)\n", results->yStats->clippedMean, results->yStats->clippedStdev, results->yStats->clippedNvalues, y->n);
+    }
+    results->xStats->clipIter = stats->clipIter;
+    results->yStats->clipIter = stats->clipIter;
+
+    psFree (x);
+    psFree (y);
+    psFree (X);
+    psFree (Y);
+    psFree (wt);
+    psFree (mask);
+
+    return (results);
+}
+
+
+/******************************************************************************
+pmAstromRotateObj(old, center, angle, angle): rotate & scale the focal-plane coordinates
+about the center coordinate angle specified in radians
+ ******************************************************************************/
+psArray *pmAstromRotateObj(
+    const psArray *old,
+    psPlane center,
+    double angle,
+    double scale)
+{
+    PS_ASSERT_PTR_NON_NULL(old, NULL);
+
+    double X, Y;
+    pmAstromObj *newObj;
+    const pmAstromObj *oldObj;
+
+    psArray *new = psArrayAlloc (old->n);
+    double cs = scale*cos(angle);
+    double sn = scale*sin(angle);
+    double xCenter = center.x;
+    double yCenter = center.y;
+
+    for (int i = 0; i < old->n; i++) {
+
+        oldObj = (pmAstromObj *)old->data[i];
+        newObj = pmAstromObjCopy (oldObj);
+
+        X = oldObj->FP->x - xCenter;
+        Y = oldObj->FP->y - yCenter;
+
+        newObj->FP->x = X*cs + Y*sn + xCenter;
+        newObj->FP->y = Y*cs - X*sn + yCenter;
+
+        new->data[i] = newObj;
+    }
+    return (new);
+}
+
+/******************************************************************************
+pmAstromStatsFree(stats)
+ ******************************************************************************/
+static void pmAstromStatsFree(pmAstromStats *stats)
+{
+    if (stats == NULL)
+        return;
+    return;
+}
+
+/******************************************************************************
+pmAstromStatsAlloc()
+ ******************************************************************************/
+pmAstromStats *pmAstromStatsAlloc(void)
+{
+    pmAstromStats *stats = psAlloc (sizeof(pmAstromStats));
+    psMemSetDeallocator (stats, (psFreeFunc)pmAstromStatsFree);
+
+    //    stats->center = {0, 0, 0, 0};
+    //    stats->offset = {0, 0, 0, 0};
+    stats->angle     = 0.0;
+    stats->scale     = 1.0;
+    stats->minMetric = 0.0;
+    stats->minVar    = 0.0;
+    stats->nMatch    = 0;
+    stats->nTest     = 0;
+    stats->nSigma    = 0;
+
+    return (stats);
+}
+
+/******************************************************************************
+pmAstromFitResultsFree(stats)
+ ******************************************************************************/
+static void pmAstromFitResultsFree(pmAstromFitResults *results)
+{
+    if (results == NULL)
+        return;
+    psFree (results->xStats);
+    psFree (results->yStats);
+    return;
+}
+
+/******************************************************************************
+pmAstromFitResultsAlloc()
+ ******************************************************************************/
+pmAstromFitResults *pmAstromFitResultsAlloc(void)
+{
+    pmAstromFitResults *results = psAlloc (sizeof(pmAstromFitResults));
+    psMemSetDeallocator (results, (psFreeFunc)pmAstromFitResultsFree);
+
+    results->xStats    = NULL;
+    results->yStats    = NULL;
+    results->nMatch    = 0;
+    results->nSigma    = 0;
+
+    return (results);
+}
+
+/******************************************************************************
+astromObjFree(obj)
+ ******************************************************************************/
+static void astromObjFree(pmAstromObj *obj)
+{
+    if (obj == NULL) {
+        return;
+    }
+
+    psFree(obj->pix);
+    psFree(obj->cell);
+    psFree(obj->chip);
+    psFree(obj->FP);
+    psFree(obj->TP);
+    psFree(obj->sky);
+
+    return;
+}
+
+
+/******************************************************************************
+pmAstromObjAlloc()
+ ******************************************************************************/
+pmAstromObj *pmAstromObjAlloc(void)
+{
+    pmAstromObj *obj = psAlloc (sizeof(pmAstromObj));
+    psMemSetDeallocator (obj, (psFreeFunc)astromObjFree);
+
+    obj->pix  = psPlaneAlloc();
+    obj->cell = psPlaneAlloc();
+    obj->chip = psPlaneAlloc();
+    obj->FP   = psPlaneAlloc();
+    obj->TP   = psPlaneAlloc();
+    obj->sky  = psSphereAlloc();
+    obj->Mag  = 0;
+    obj->dMag = 0;
+
+    return (obj);
+}
+
+bool pmAstromObjTest(const psPtr ptr)
+{
+    return (psMemGetDeallocator(ptr) == (psFreeFunc)astromObjFree);
+}
+
+
+
+/******************************************************************************
+pmAstromObjCopy(old)
+ ******************************************************************************/
+pmAstromObj *pmAstromObjCopy(const pmAstromObj *old)
+{
+    PS_ASSERT_PTR_NON_NULL(old, NULL);
+    pmAstromObj *obj = pmAstromObjAlloc();
+
+    *obj->pix  = *old->pix;
+    *obj->cell = *old->cell;
+    *obj->chip = *old->chip;
+    *obj->FP   = *old->FP;
+    *obj->TP   = *old->TP;
+    *obj->sky  = *old->sky;
+    obj->Mag   =  old->Mag;
+    obj->dMag  =  old->dMag;
+
+    return(obj);
+}
+
+
+/******************************************************************************
+ ******************************************************************************/
+static void pmAstromMatchFree (pmAstromMatch *match)
+{
+    if (match == NULL)
+        return;
+    return;
+}
+
+
+/******************************************************************************
+ ******************************************************************************/
+pmAstromMatch *pmAstromMatchAlloc(
+    int raw,
+    int ref)
+{
+    pmAstromMatch *match = psAlloc (sizeof(pmAstromMatch));
+    psMemSetDeallocator(match, (psFreeFunc) pmAstromMatchFree);
+
+    match->raw = raw;
+    match->ref = ref;
+
+    return (match);
+}
+
+
+static double maxOffpix;                // maximum allowed offset between lists, in raw pixels
+static double Scale;                    // grid pixel scale static
+double Offset;                          // deltas to pixels
+/******************************************************************************
+AstromGridBin(*dx, *dy, dX, dY): local function to convert x,y coords to grid
+bins it requires the globals defined above.
+
+ ******************************************************************************/
+static bool AstromGridBin(
+    int *dx,
+    int *dy,
+    double dX,
+    double dY)
+{
+    if (PM_ASTROMETRYOBJECTS_DEBUG) {
+        PS_ASSERT_PTR_NON_NULL(dx, false);
+        PS_ASSERT_PTR_NON_NULL(dy, false);
+    }
+
+    if (!isfinite(dX)) return false;
+    if (!isfinite(dY)) return false;
+
+    if (fabs(dX) > maxOffpix) return false;
+    if (fabs(dY) > maxOffpix) return false;
+
+    *dx = dX / Scale + Offset;
+    *dy = dY / Scale + Offset;
+    return true;
+}
+
+
+/******************************************************************************
+pmAstromGridAngle(raw, ref, config): match the two lists using the binned
+delta-delta max.
+ ******************************************************************************/
+pmAstromStats *pmAstromGridAngle(
+    const psArray *raw,
+    const psArray *ref,
+    const psMetadata *config)
+{
+    PS_ASSERT_PTR_NON_NULL(raw, NULL);
+    PS_ASSERT_PTR_NON_NULL(ref, NULL);
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    bool status;
+    int nPix;       // size of matching grid
+    int nPixHalf;   // half-size of matching grid
+    double dX, dY;  // offset between a possible matched pair
+    int iX, iY;     // corresponding grid bin
+
+    const pmAstromObj *ob1, *ob2; // short-cut pointers to the objects
+
+    pmAstromStats *stats = pmAstromStatsAlloc();    // output match statistics
+
+    // max allowed offset in either X or Y directions
+    double gridOffset = psMetadataLookupF32 (&status, config, "PSASTRO.GRID.OFFSET");
+
+    // sampling scale of the grid
+    double gridScale  = psMetadataLookupF32 (&status, config, "PSASTRO.GRID.SCALE");
+
+    // set the static scaling factors
+    nPixHalf = (int)(gridOffset / gridScale + 0.5);  // half-grid
+    nPix = 2*nPixHalf + 1;                           // full grid width
+
+    // these are globals used by p_pmAstromGridBin
+    maxOffpix = gridScale * (nPixHalf + 0.5);            // max offset from true center
+    Offset    = maxOffpix / gridScale;
+    Scale     = gridScale;
+
+    // images used as accumulators for the loop below
+    psImage *gridNP = psImageAlloc (nPix, nPix, PS_TYPE_U32);
+    psImage *gridDX = psImageAlloc (nPix, nPix, PS_TYPE_F32);
+    psImage *gridDY = psImageAlloc (nPix, nPix, PS_TYPE_F32);
+    psImage *gridD2 = psImageAlloc (nPix, nPix, PS_TYPE_F32);
+    psImageInit (gridNP, 0);
+    psImageInit (gridDX, 0);
+    psImageInit (gridDY, 0);
+    psImageInit (gridD2, 0);
+
+    // short-cut names for grid images
+    psU32 **NP = gridNP->data.U32;
+    psF32 **DX = gridDX->data.F32;
+    psF32 **DY = gridDY->data.F32;
+    psF32 **D2 = gridD2->data.F32;
+
+    // accumulate grids for focal plane (L,M) matches
+    for (int i = 0; i < raw->n; i++) {
+        ob1 = (pmAstromObj *)raw->data[i];
+        for (int j = 0; j < ref->n; j++) {
+            ob2 = (pmAstromObj *)ref->data[j];
+            dX = ob1->FP->x - ob2->FP->x;
+            dY = ob1->FP->y - ob2->FP->y;
+
+            // fprintf (f, "dX,dY: %8.2f %8.2f : %8.2f %8.2f : %8.2f %8.2f\n", dX, dY, ob1->FP->x, ob2->FP->x, ob1->FP->y, ob2->FP->y);
+            // find bin coordinates for this delta-delta
+            if (!AstromGridBin (&iX, &iY, dX, dY)) {
+                continue; // matched pair is too far offset
+            }
+
+            // accumulate bin stats
+            NP[iY][iX] ++;
+            DX[iY][iX] += dX;
+            DY[iY][iX] += dY;
+            D2[iY][iX] += PS_SQR(dX) + PS_SQR(dY);
+        }
+    }
+
+    // now assess the grid images
+    {
+        double minMetric = 1e10;
+        double minVar = 1e10;
+        int minX = -1;
+        int minY = -1;
+        double metric, var;
+
+        // find the max pixel
+        psStats *imStats = psStatsAlloc (PS_STAT_MAX | PS_STAT_MAX | PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+        if (!psImageStats(imStats, gridNP, NULL, 0)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to get image statistics.\n");
+            psFree(imStats);
+            psFree(gridNP);
+            psFree(gridDX);
+            psFree(gridDY);
+            psFree(gridD2);
+            psFree(stats);
+            return NULL;
+        }
+
+        # if 0
+        char line[16];
+        psFits *fits = psFitsOpen ("grid.image.fits", "w");
+        psFitsWriteImage (fits, NULL, gridNP, 0, NULL);
+        psFitsClose (fits);
+        fprintf (stderr, "wrote grid image, press return to continue\n");
+        fgets (line, 15, stdin);
+        # endif
+
+        // only check bins with at least 1/2 of max bin
+        // XXX requiring at least 3 matches in bin
+        int minNpts = PS_MAX (0.5*imStats->max, 5);
+        psTrace("psModule.astrom", 5, "minNpts: %d, min: %d, max: %d, median: %f, stdev: %f", minNpts, (int)(imStats->min), (int)(imStats->max), imStats->sampleMedian, imStats->sampleStdev);
+
+        // find the 'best' bin
+        for (int j = 0; j < gridNP->numRows; j++) {
+            for (int i = 0; i < gridNP->numCols; i++) {
+                if (NP[j][i] < minNpts) continue;
+
+                // this metric emphasizes a narrow peak with lots of sources over one with few.
+                var = fabs((D2[j][i]/NP[j][i]) - PS_SQR(DX[j][i]/NP[j][i]) - PS_SQR(DY[j][i]/NP[j][i]));
+                metric = var / PS_SQR(NP[j][i]) / PS_SQR(NP[j][i]);
+
+                // fprintf (stderr, "try : %f %f (%d pts, %f var, %f met)\n", DX[j][i]/NP[j][i], DY[j][i]/NP[j][i], NP[j][i], var, metric);
+
+                if (metric < minMetric) {
+                    minMetric = metric;
+                    minVar    = var;
+                    minX      = i;
+                    minY      = j;
+                }
+            }
+        }
+
+        // convert the bin to delta-delta
+        if ((minX < 0) || (minY < 0))
+        {
+            // no valid matches found
+            stats->offset.x   = 0;
+            stats->offset.y   = 0;
+            stats->minMetric  = minMetric;
+            stats->minVar     = minVar;
+            stats->nMatch     = 0;
+        } else
+        {
+            stats->offset.x  = DX[minY][minX] / NP[minY][minX];
+            stats->offset.y  = DY[minY][minX] / NP[minY][minX];
+            stats->minMetric = minMetric;
+            stats->minVar    = minVar;
+            stats->nMatch    = NP[minY][minX];
+        }
+
+        psFree (imStats);
+        // XXX EAM : This routine, and pmAstromGridMatch, need to handle failure cases better
+    }
+
+    // sort the NP values and choose
+    psVector *listNP = psVectorAlloc (nPix*nPix, PS_TYPE_U32);
+    int n = 0;
+    for (int i = 0; i < nPix; i++) {
+        for (int j = 0; j < nPix; j++) {
+            listNP->data.U32[n] = gridNP->data.U32[j][i];
+            n++;
+        }
+    }
+    psVector *sort = psVectorSort (NULL, listNP);
+    stats->nTest = sort->data.U32[sort->n - 5];
+    stats->nSigma = (stats->nMatch - stats->nTest) / sqrt(stats->nTest);
+    // XXX this needs a better analysis of the image histogram..
+    // fprintf (stderr, "sigma: nMatch: %d, nTest: %d, nTen: %d\n", stats->nMatch, stats->nTest, sort->data.U32[sort->n - 10]);
+
+    psFree (sort);
+    psFree (listNP);
+    psFree (gridNP);
+    psFree (gridDX);
+    psFree (gridDY);
+    psFree (gridD2);
+    return (stats);
+}
+
+
+
+/******************************************************************************
+pmAstromGridMatch(*raw, *ref, *config): match two star lists.
+ ******************************************************************************/
+
+pmAstromStats *pmAstromGridMatch(const psArray *raw,
+                                 const psArray *ref,
+                                 const psMetadata *config)
+{
+    PS_ASSERT_PTR_NON_NULL(raw, NULL);
+    PS_ASSERT_PTR_NON_NULL(ref, NULL);
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    bool status;
+    double xMin, xMax, yMin, yMax;
+    const pmAstromObj *obj;
+    psArray *rot;
+
+    pmAstromStats *minStat = pmAstromStatsAlloc ();
+    pmAstromStats *newStat = NULL;
+
+    psPlane center;
+
+    // find center of the raw field (focal-plane coords)
+    xMin = yMin = +1e10;
+    xMax = yMax = -1e10;
+    for (int i = 0; i < raw->n; i++) {
+        obj = (pmAstromObj *)raw->data[i];
+        xMin = PS_MIN (obj->FP->x, xMin);
+        xMax = PS_MAX (obj->FP->x, xMax);
+        yMin = PS_MIN (obj->FP->y, yMin);
+        yMax = PS_MAX (obj->FP->y, yMax);
+    }
+    center.x = 0.5*(xMin + xMax);
+    center.y = 0.5*(yMin + yMax);
+
+    double minScale = psMetadataLookupF32 (&status, config, "PSASTRO.GRID.MIN.SCALE");
+    double maxScale = psMetadataLookupF32 (&status, config, "PSASTRO.GRID.MAX.SCALE");
+    double delScale = psMetadataLookupF32 (&status, config, "PSASTRO.GRID.DEL.SCALE");
+
+    double minAngle = PS_RAD_DEG*psMetadataLookupF32 (&status, config, "PSASTRO.GRID.MIN.ANGLE");
+    double maxAngle = PS_RAD_DEG*psMetadataLookupF32 (&status, config, "PSASTRO.GRID.MAX.ANGLE");
+    double delAngle = PS_RAD_DEG*psMetadataLookupF32 (&status, config, "PSASTRO.GRID.DEL.ANGLE");
+    double minSigma = psMetadataLookupF32 (&status, config, "PSASTRO.GRID.MIN.SIGMA");
+
+    minStat->minMetric = 1e10;
+    for (double scale = minScale; scale <= maxScale; scale += delScale) {
+	for (double angle = minAngle; angle <= maxAngle; angle += delAngle) {
+	    rot = pmAstromRotateObj (raw, center, angle, scale);
+
+# if 0
+	    FILE *f1 = fopen ("raw.dat", "w");
+	    for (int i = 0; i < rot->n; i++) {
+		pmAstromObj *obj = rot->data[i];
+		fprintf (f1, "%8.2f %8.2f   %6.2f\n", obj->FP->x, obj->FP->y, obj->Mag);
+	    }
+	    fclose (f1);
+	    FILE *f2 = fopen ("ref.dat", "w");
+	    for (int i = 0; i < ref->n; i++) {
+		pmAstromObj *obj = ref->data[i];
+		fprintf (f2, "%8.2f %8.2f   %6.2f\n", obj->FP->x, obj->FP->y, obj->Mag);
+	    }
+	    fclose (f2);
+	    fprintf (stderr, "type return");
+	    char c;
+	    fscanf (stdin, "%c", &c);
+# endif
+
+	    newStat = pmAstromGridAngle (rot, ref, config);
+	    newStat->angle  = angle;
+	    newStat->scale  = scale;
+	    newStat->center = center;
+
+	    if (isfinite(newStat->minMetric) && (newStat->minMetric > 0.0) && (newStat->minMetric < minStat->minMetric)) {
+		*minStat = *newStat;
+		psLogMsg ("psModule.astrom", 4, "grid test - offset: %7.2f,%7.2f @ %6.1f deg x %7.3f (%4d pts, %5.1f sig, %5.1f var, %6.3f log metric) *",
+			  minStat->offset.x, minStat->offset.y, PS_DEG_RAD*minStat->angle, minStat->scale, minStat->nMatch, minStat->nSigma, minStat->minVar, log10(minStat->minMetric));
+	    } else {
+		psLogMsg ("psModule.astrom", 4, "grid test - offset: %7.2f,%7.2f @ %6.1f deg x %7.3f (%4d pts, %5.1f sig, %5.1f var, %6.3f log metric)",
+			  newStat->offset.x, newStat->offset.y, PS_DEG_RAD*newStat->angle, newStat->scale, newStat->nMatch, newStat->nSigma, newStat->minVar, log10(newStat->minMetric));
+
+	    }
+	    psFree (newStat);
+	    psFree (rot);
+	}
+    }
+    psLogMsg ("psModule.astrom.grid.match", 4, "grid best - offset: %7.2f,%7.2f @ %6.1f deg x %7.3f (%4d pts, %5.1f sig, %5.1f var, %6.3f log metric)",
+              minStat->offset.x, minStat->offset.y, PS_DEG_RAD*minStat->angle, minStat->scale, minStat->nMatch, minStat->nSigma, minStat->minVar, log10(minStat->minMetric));
+
+    // I need to decide if a solution is likely to be a good solution or just a mis-match
+    // one posibility: how significant is the peak relative to the 4th or 5th most significant pixel?
+
+    if (minStat->nSigma < minSigma) {
+        psError(PS_ERR_UNKNOWN, true, "Failed to find a valid match (%f sigma for best)", minStat->nSigma);
+        psFree (minStat);
+        return NULL;
+    }
+    return (minStat);
+}
+
+/******************************************************************************
+pmAstromGridTweak(*raw, *ref, *recipe, stats): improve match for two star lists.
+ ******************************************************************************/
+pmAstromStats *pmAstromGridTweak(
+    psArray *raw,
+    psArray *ref,
+    psMetadata *recipe,
+    pmAstromStats *stats)
+{
+    bool status;
+    pmAstromObj *ob1, *ob2;  // short-cut pointers to the objects
+    double dX, dY;   // offset between a possible matched pair
+    psArray *rot;
+    int nBin, xBin, yBin;
+
+    rot = pmAstromRotateObj (raw, stats->center, stats->angle, stats->scale);
+
+    // sampling scale of the grid
+    double tweakScale  = psMetadataLookupF32 (&status, recipe, "PSASTRO.TWEAK.SCALE");
+    double tweakRange  = psMetadataLookupF32 (&status, recipe, "PSASTRO.TWEAK.RANGE");
+    double tweakSmooth = psMetadataLookupF32 (&status, recipe, "PSASTRO.TWEAK.SMOOTH");
+    double tweakNsigma = psMetadataLookupF32 (&status, recipe, "PSASTRO.TWEAK.NSIGMA");
+
+    nBin = 2*tweakRange / tweakScale;
+    psVector *xHist = psVectorAlloc (nBin, PS_TYPE_F32);
+    psVector *yHist = psVectorAlloc (nBin, PS_TYPE_F32);
+    psVectorInit (xHist, 0);
+    psVectorInit (yHist, 0);
+
+    // accumulate grids for focal plane (L,M) matches
+    for (int i = 0; i < rot->n; i++) {
+        ob1 = (pmAstromObj *)rot->data[i];
+        for (int j = 0; j < ref->n; j++) {
+            ob2 = (pmAstromObj *)ref->data[j];
+            dX = ob1->FP->x - ob2->FP->x - stats->offset.x;
+            dY = ob1->FP->y - ob2->FP->y - stats->offset.y;
+
+            xBin = (dX + tweakRange) / tweakScale;
+            yBin = (dY + tweakRange) / tweakScale;
+
+            if (xBin < 0)
+                continue;
+            if (yBin < 0)
+                continue;
+            if (xBin >= nBin)
+                continue;
+            if (yBin >= nBin)
+                continue;
+
+            xHist->data.F32[xBin] += 1.0;
+            yHist->data.F32[yBin] += 1.0;
+        }
+    }
+
+    // smooth histgram vector with gaussian of 1sigma = radius
+    psVector *xHistNew = psVectorSmooth(NULL, xHist, tweakSmooth, tweakNsigma);
+    psVector *yHistNew = psVectorSmooth(NULL, yHist, tweakSmooth, tweakNsigma);
+    psFree(xHist);
+    psFree(yHist);
+    xHist = xHistNew;
+    yHist = yHistNew;
+
+    // select peak in x and in y
+    xBin = yBin = 0;
+    double xMax = 0;
+    double yMax = 0;
+    for (int i = 0; i < nBin; i++) {
+        if (xHist->data.F32[i] > xMax) {
+            xBin = i;
+            xMax = xHist->data.F32[i];
+        }
+        if (yHist->data.F32[i] > yMax) {
+            yBin = i;
+            yMax = yHist->data.F32[i];
+        }
+    }
+    double xPeak = xBin*tweakScale - tweakRange;
+    double yPeak = yBin*tweakScale - tweakRange;
+    psLogMsg (__func__, 3, "tweak peak by %f,%f\n", xPeak, yPeak);
+
+    // adjust offset by peak center
+    pmAstromStats *tweak = pmAstromStatsAlloc();
+    *tweak = *stats;
+    tweak->offset.x += xPeak;
+    tweak->offset.y += yPeak;
+
+    psFree (rot);
+    psFree (xHist);
+    psFree (yHist);
+
+    return tweak;
+}
+
+/******************************************************************************
+pmAstromGridApply(*map, stat): apply the measured FPA offset and rotation
+(stat) to the fpa astrom structures.
+ ******************************************************************************/
+psPlaneTransform *pmAstromGridApply(
+    psPlaneTransform *map,
+    pmAstromStats *stat)
+{
+    PS_ASSERT_PTR_NON_NULL(map, NULL);
+    PS_ASSERT_POLY_NON_NULL(map->x, NULL);
+    PS_ASSERT_POLY_NON_NULL(map->y, NULL);
+
+    double cs = stat->scale * cos (stat->angle);
+    double sn = stat->scale * sin (stat->angle);
+
+    double dx = (map->x->coeff[0][0] - stat->center.x);
+    double dy = (map->y->coeff[0][0] - stat->center.y);
+
+    // new offset
+    map->x->coeff[0][0] =  cs*dx + sn*dy - stat->offset.x + stat->center.x;
+    map->y->coeff[0][0] = -sn*dx + cs*dy - stat->offset.y + stat->center.y;
+
+    // original rotation matrix
+    double pc1_1 = map->x->coeff[1][0];
+    double pc1_2 = map->x->coeff[0][1];
+    double pc2_1 = map->y->coeff[1][0];
+    double pc2_2 = map->y->coeff[0][1];
+
+    // new rotation matrix
+    map->x->coeff[1][0] = +cs*pc1_1 + sn*pc2_1;
+    map->x->coeff[0][1] = +cs*pc1_2 + sn*pc2_2;
+    map->y->coeff[1][0] = -sn*pc1_1 + cs*pc2_1;
+    map->y->coeff[0][1] = -sn*pc1_2 + cs*pc2_2;
+
+    return (map);
+}
+
+/* Illustration of the grid bins
+   dX        bin
+   -35:-25 -> 0     bin = dX / Scale + Offset
+   -25:-15 -> 1     Scale = 10
+   -15:-05 -> 2     Offset = 3.5
+   -05:+05 -> 3     nPix = 3 (maxOffset = 35 = (nPix + 0.5)*dXix
+   +05:+15 -> 4     dPix = 10
+   +15:+25 -> 5
+   +25:+35 -> 6
+
+   maxOffsetRequest = 30
+   nPix = (int) (maxOffset / dPix + 0.5);
+   maxOffset = (nPix + 0.5)*Scale;
+*/
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryObjects.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryObjects.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryObjects.h	(revision 22157)
@@ -0,0 +1,348 @@
+/* @file  pmAstrometryObjects.h
+ * @brief basic matching of objects based on their astrometry.
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.17 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-11-21 07:02:55 $
+ * Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+ */
+
+#ifndef PM_ASTROMETRY_OBJECTS_H
+#define PM_ASTROMETRY_OBJECTS_H
+
+/// @addtogroup Astrometry
+/// @{
+
+/*
+ *
+ * This structure specifies the coordinate of the detection in each of the
+ * four necessary coordinate frames: pix defines the position in the psReadout
+ * frame, FP defines the position in the Focal Plane frame, TP defines the
+ * position in the Tangent Plane frame, sky defines the position on the Celestial
+ * Sphere. In addition, a measurement of the brightness is given by the element
+ * Mag. Such a data structure should be used for both the raw and the reference
+ * stars. In astrometric processing, the raw detections will be projected using
+ * the best available information to each of these coordinate frames from the pix
+ * coordinates, while the reference detections will be projected to the other
+ * frames from the sky coordinates.
+ *
+ * XXX: There are more members here than in the SDRS.
+ *
+ */
+typedef struct
+{
+    psPlane *pix;   ///< the position in the pmReadout frame
+    psPlane *cell;   ///< the position in the pmCell frame
+    psPlane *chip;   ///< the position in the pmChip frame
+    psPlane *FP;   ///< the position in the pmFPA frame
+    psPlane *TP;   ///< the position in the tangent plane
+    psSphere *sky;        ///< the position on the Celestial Sphere.
+    double Mag;                         ///< object magnitude XXX what filter?
+    double dMag;                        ///< error on object magnitude
+}
+pmAstromObj;
+
+/*
+ *
+ * The pmAstromMatch structure defines the cross-correlation between two
+ * arrays. A single such data item specifies that item number pmAstromMatch.idx1
+ * in the first list corresponds to pmAstromMatch.idx2 in the second list.
+ *
+ */
+typedef struct
+{
+    int raw;                             ///< What is this?
+    int ref;                             ///< What is this?
+}
+pmAstromMatch;
+
+
+/*
+ *
+ * XXX: Not in SDRS.
+ *
+ */
+typedef struct
+{
+    psPlane center;                     ///<
+    psPlane offset;                     ///<
+    double  scale;                      ///<
+    double  angle;                      ///<
+    double  minMetric;                  ///<
+    double  minVar;                     ///<
+    int     nMatch;                     ///<
+    int     nTest;                      ///<
+    double  nSigma;                     ///<
+}
+pmAstromStats;
+
+typedef struct
+{
+    psStats *xStats;
+    psStats *yStats;
+    int     nMatch;                     ///<
+    double  nSigma;                     ///<
+}
+pmAstromFitResults;
+
+/*
+ *
+ * If the two sets of coordinates are expected to agree very well (ie, the current best-guess
+ * astrometric solution is quite close to reality), perform a match based on a simple radius
+ * test. The following functions accept two sets of pmAstromObj sources and determines the
+ * matched objects between the two lists using coordinates of the desired depth (depending on
+ * the function). The input and reference sources must have been projected to the desired depth
+ * (eg, for Focal Plane coordinates, to pmAstromObj.FP).  The specified radius must be in the
+ * units for the matching depth (chip: pixels, focal plane: microns, tangent plane:
+ * degrees. The output consists an array of pmAstromMatch values, defined above.
+ *
+ */
+psArray *pmAstromRadiusMatch(
+    const psArray *st1,
+    const psArray *st2,
+    double RADIUS
+);
+psArray *pmAstromRadiusMatchFP(
+    const psArray *st1,
+    const psArray *st2,
+    double RADIUS
+);
+psArray *pmAstromRadiusMatchTP(
+    const psArray *st1,
+    const psArray *st2,
+    double RADIUS
+);
+psArray *pmAstromRadiusMatchChip(
+    const psArray *st1,
+    const psArray *st2,
+    double RADIUS
+);
+
+
+pmAstromStats *pmAstromStatsAlloc(void);
+
+/*
+ *
+ * This function accepts an array of pmAstromObj objects and rotates them by
+ * the given angle about the given center coordinate pCenter,qCenter in the Focal
+ * Plane Array coordinates.
+ *
+ * XXX: This differs from the SDRS
+ *
+ */
+/* SDRS
+psArray *pmAstromRotateObj(
+    psArray *old,
+    double angle,
+    double pCenter,
+    double qCenter
+);
+*/
+psArray *pmAstromRotateObj(
+    const psArray *old,
+    psPlane center,
+    double angle,
+    double scale
+);
+
+
+/*
+ *
+ * If the two sets of coordinates are not known to agree well, but the
+ * relative scale and approximate relative rotation is known, then a much faster
+ * match can be found using pair-pair displacements. In such a case, the two
+ * lists can be considered as having the same coordinate system, with an unknown
+ * relative displacement. In this algorithm, all possible pair-wise differences
+ * between the source positions in the two lists are constructed and accumulated
+ * in a grid of possible offset values. The resulting grid is searched for a
+ * cluster representing the offset between the two input lists. This algorithm
+ * can only tolerate a small error in the relative scale or the relative rotation
+ * of the two coordinate lists. However, this process is naturally O(N2), and is
+ * thus advantageous over triangle matching in some circumstances. This process
+ * can be extended to allow a larger uncertainty in the relative rotation by
+ * allowing the procedure to scan over a range of rotations. We define the
+ * following function to apply this matching algorithm:
+ *
+ * XXX: In the SDRS, this function is a pointer.
+ *
+ */
+pmAstromStats *pmAstromGridMatch(
+    const psArray *st1,
+    const psArray *st2,
+    const psMetadata *config
+);
+
+/******************************************************************************
+pmAstromGridTweak(*raw, *ref, *recipe, stats): improve match for two star lists.
+ ******************************************************************************/
+pmAstromStats *pmAstromGridTweak(
+    psArray *raw,
+    psArray *ref,
+    psMetadata *recipe,
+    pmAstromStats *stats);
+
+/*
+ *
+ * The result of a pmAstromGridMatch may be used to modify the astrometry
+ * transformation information for a pmFPA image hierarchy structure. The result
+ * of pmAstromGridMatch defines the adjustments which should be made to the
+ * reference coordinate of the projection (pmFPA.projection.R,D) and the
+ * effective rotation of the Focal Plane.  The rotation implies modification of
+ * the linear terms of the pmFPA.toTangentPlane transformation. These two
+ * adjustments are made using the function:
+ *
+ * XXX: This function name is different in the SDRS.
+ *
+ */
+psPlaneTransform *pmAstromGridApply(
+    psPlaneTransform *map,
+    pmAstromStats *stat
+);
+
+
+/*
+ *
+ * This function is identical to pmAstromGridMatch, but is valid for only a
+ * single relative rotation. The input config information need not contain any of
+ * the GRID.*.ANGLE entries (they will be ignored).
+ *
+ * XXX: This function name is different in the SDRS.
+ *
+ */
+/* in pmAstromGrid.c */
+pmAstromStats *pmAstromGridAngle(
+    const psArray *st1,
+    const psArray *st2,
+    const psMetadata *config);
+
+
+
+/*
+ *
+ * This function accepts the raw and reference source lists and the list of
+ * matched entries. It uses the matched list to determine a polynomial
+ * transformation between the two coordinate systems. The fitting uses clipping
+ * to exclude outliers, likely representing poor matches. The config element must
+ * contain the information ASTROM.NSIGMA (specifying the number of sigma used in
+ * the clipping) and ASTROM.NCLIP (specifying the number of clipping iterations
+ * must be performed). The config element must also specify the order of the
+ * polynomial fit (keyword: ASTROM.ORDER). The result of this fit is a set of
+ * modifications of the components of the pmFPA.toTangentPlane transformation,
+ * and the modifications of the reference coordinate of the projection
+ * (pmFPA.projection.R,D) and the projection scale (pmFPA.projection.Xs,Ys). The
+ * modifications to pmFPA.toTangentPlane incorporate the rotation component of
+ * the linear terms and the higher-order terms of the polynomial fits.
+ *
+ * XXX: No prototype code.
+ *
+ */
+bool pmAstromFitFPA(
+    pmFPA *fpa,
+    psArray *st1,
+    psArray *st2,
+    psArray *match,
+    psMetadata *config
+);
+
+
+
+/*
+ *
+ * This function accepts the raw and reference source lists for a single chip
+ * and the list of matched entries. It uses the matched list to determine a
+ * polynomial transformation between the two coordinate systems. The fitting
+ * uses clipping to exclude outliers, likely representing poor matches. The
+ * config element must contain the information ASTROM.NSIGMA
+ *(specifying the number of sigma used in the clipping) and ASTROM.NCLIP
+ *(specifying the number of clipping iterations must be performed). The config
+ *element must also specify the order of the polynomial fit (keyword:
+ *ASTROM.ORDER).  The result of this fit is a set of modifications of the
+ *components of the pmChip.toFPA transformation.
+ *
+ * XXX: No prototype code.
+ *
+ */
+bool pmAstromFitChip(
+    pmFPA *fpa,
+    psArray *st1,
+    psArray *st2,
+    psArray *match,
+    psMetadata *config
+);
+
+
+/*******************************************************************************
+ The following functions and structs were in the prototype code, but not the
+ SDRS.
+ ******************************************************************************/
+/*
+ *
+ *
+ *
+ *
+ */
+
+pmAstromFitResults *pmAstromFitResultsAlloc(void);
+
+/*
+ *
+ * Allocates a pmAstromObj struct.
+ *
+ */
+pmAstromObj *pmAstromObjAlloc (void);
+/*
+ * Is a given pointer a pmAstromObj?
+ */
+bool pmAstromObjTest(const psPtr ptr);
+
+
+/*
+ *
+ * Copies a pmAstromObj struct.
+ *
+ */
+pmAstromObj *pmAstromObjCopy(
+    const pmAstromObj *old
+);
+
+
+
+/*
+ *
+ *
+ *
+ */
+pmAstromMatch *pmAstromMatchAlloc(
+    int i1,
+    int i2
+);
+
+
+
+
+/*
+ *
+ *
+ *
+ */
+pmAstromFitResults *pmAstromMatchFit(
+    psPlaneTransform *map,
+    psArray *raw,
+    psArray *ref,
+    psArray *match,
+    psStats *stats
+);
+
+/*
+ *
+ *
+ *
+ */
+int pmAstromObjSortByMag(
+    const void **a,
+    const void **b
+);
+
+/// @}
+#endif // PM_ASTROMETRY_OBJECTS_H
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryRefstars.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryRefstars.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryRefstars.c	(revision 22157)
@@ -0,0 +1,295 @@
+/* @file  pmAstrometryRefstars.c
+ * @brief Functions to write (and read?) astrometric reference stars
+ * 
+ * @ingroup AstroImage
+ *
+ * @author EAM, IfA
+ * @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-03-20 00:27:00 $
+ * Copyright 2008 Institute for Astronomy, University of Hawaii
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/******************************************************************************/
+/*  INCLUDE FILES                                                             */
+/******************************************************************************/
+#include <stdio.h>
+#include <strings.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+#include <unistd.h>   // for unlink
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+#include "pmAstrometryObjects.h"
+#include "pmAstrometryRefstars.h"
+
+/********************* CheckDataStatus functions *****************************/
+
+bool pmAstromRefstarsCheckDataStatusForView (const pmFPAview *view, pmFPAfile *file) {
+
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        bool exists = pmAstromRefstarsCheckDataStatusForFPA (fpa);
+        return exists;
+    }
+    if (view->chip >= fpa->chips->n) {
+        psError(PS_ERR_IO, true, "Requested chip == %d >= fpa->chips->n == %ld", view->chip, fpa->chips->n);
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        bool exists = pmAstromRefstarsCheckDataStatusForChip (chip);
+        return exists;
+    }
+    if (view->cell >= chip->cells->n) {
+        psError(PS_ERR_IO, true, "Requested cell == %d >= chip->cells->n == %ld", view->cell, chip->cells->n);
+        return false;
+    }
+    psError(PS_ERR_IO, false, "Astrometry only valid at the chip level");
+    return false;
+}
+
+// return true if data exists for any chip
+bool pmAstromRefstarsCheckDataStatusForFPA (const pmFPA *fpa) {
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        if (!chip) continue;
+        if (pmAstromRefstarsCheckDataStatusForChip (chip)) return true;
+    }
+    return false;
+}
+
+// return true if data exists for any cell
+bool pmAstromRefstarsCheckDataStatusForChip (const pmChip *chip) {
+
+    for (int i = 0; i < chip->cells->n; i++) {
+      pmCell *cell = chip->cells->data[i];
+        if (!cell) continue;
+        if (pmAstromRefstarsCheckDataStatusForCell (cell)) return true;
+    }
+    return false;
+}
+
+// return true if data exists for any readout
+bool pmAstromRefstarsCheckDataStatusForCell (const pmCell *cell) {
+
+    for (int i = 0; i < cell->readouts->n; i++) {
+      pmReadout *readout = cell->readouts->data[i];
+        if (!readout) continue;
+        if (pmAstromRefstarsCheckDataStatusForReadout (readout)) return true;
+    }
+    return false;
+}
+
+// check if refstars array exists
+bool pmAstromRefstarsCheckDataStatusForReadout (const pmReadout *readout) {
+
+  if (!readout->analysis) return false;
+
+  // select the raw objects for this readout
+  psArray *refstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.REFSTARS");
+  if (refstars == NULL) return false;
+
+  return true;
+}
+
+/********************* Write Data functions *****************************/
+
+bool pmAstromRefstarsWriteForView (const pmFPAview *view, pmFPAfile *file, const pmConfig *config) {
+
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(file->fpa, false);
+
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        if (!pmAstromRefstarsWriteFPA (fpa, view, file, config)) {
+            psError(PS_ERR_IO, false, "Failed to write refstars for fpa");
+            return false;
+        }
+        return true;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        psError(PS_ERR_UNKNOWN, false, "Writing chip == %d (>= chips->n == %ld)", view->chip, fpa->chips->n);
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        if (!pmAstromRefstarsWriteChip (chip, view, file, config)) {
+            psError(PS_ERR_IO, false, "Failed to write refstars for chip");
+            return false;
+        }
+        return true;
+    }
+
+    psError(PS_ERR_IO, false, "Astrometry must be written at the FPA level");
+    return false;
+}
+
+bool pmAstromRefstarsWritePHU (const pmFPAview *view, pmFPAfile *file, const pmConfig *config) { 
+
+    // output header data 
+    psMetadata *outhead = psMetadataAlloc();
+
+    // use the FPA phu to generate the PHU header
+    pmHDU *phu = file->fpa->hdu;
+
+    // if there is no FPA PHU, this is a single header+image (extension-less) file. This could be
+    // the case for an input SPLIT set of files being written out as a MEF.  if there is a PHU,
+    // write it out as a 'blank'
+    if (phu) {
+	psMetadataCopy (outhead, phu->header);
+    } else {
+	pmConfigConformHeader (outhead, file->format);
+    }
+
+    psMetadataAddBool (outhead, PS_LIST_TAIL, "EXTEND", PS_META_REPLACE, "this file has extensions", true);
+    psFitsWriteBlank (file->fits, outhead, "");
+    file->wrote_phu = true;
+
+    psTrace ("pmFPAfile", 5, "wrote phu %s (type: %d)\n", file->filename, file->type);
+    psFree (outhead);
+
+    return true;
+}
+
+// write out all chip-level Astrometry data for this FPA
+bool pmAstromRefstarsWriteFPA (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file, const pmConfig *config) {
+
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(fpa->chips, false);
+
+    pmFPAview *thisView = pmFPAviewAlloc (view->nRows);
+    *thisView = *view;
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        thisView->chip = i;
+        if (!pmAstromRefstarsWriteChip (chip, thisView, file, config)) {
+            psError(PS_ERR_IO, false, "Failed to write %dth chip", i);
+            psFree (thisView);
+            return false;
+        }
+    }
+    psFree (thisView);
+    return true;
+}
+
+bool pmAstromRefstarsWriteChip (pmChip *chip, const pmFPAview *view, pmFPAfile *file, const pmConfig *config) {
+
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(chip->cells, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    pmFPAview *thisView = pmFPAviewAlloc (view->nRows);
+    *thisView = *view;
+
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCell *cell = chip->cells->data[i];
+        thisView->cell = i;
+        if (!pmAstromRefstarsWriteCell (cell, thisView, file, config)) {
+            psError(PS_ERR_IO, false, "Failed to write %dth cell", i);
+            psFree (thisView);
+            return false;
+        }
+    }
+    psFree (thisView);
+    return true;
+}
+
+// read in all readout-level Objects files for this cell
+bool pmAstromRefstarsWriteCell (pmCell *cell, const pmFPAview *view, pmFPAfile *file, const pmConfig *config) {
+
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(cell->readouts, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    pmFPAview *thisView = pmFPAviewAlloc (view->nRows);
+    *thisView = *view;
+
+    for (int i = 0; i < cell->readouts->n; i++) {
+        pmReadout *readout = cell->readouts->data[i];
+        thisView->readout = i;
+        if (!pmAstromRefstarsWriteReadout (readout, thisView, file, config)) {
+            psError(PS_ERR_IO, false, "Failed to write %dth readout", i);
+            psFree (thisView);
+            return false;
+        }
+    }
+    psFree (thisView);
+    return true;
+}
+
+// write out all refstars files for this readout
+bool pmAstromRefstarsWriteReadout (pmReadout *readout, const pmFPAview *view, pmFPAfile *file, const pmConfig *config) {
+
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(readout->analysis, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    // select the raw objects for this readout
+    psArray *refstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.REFSTARS");
+    if (refstars == NULL) { return TRUE; }
+
+    psMetadata *header = psMetadataAlloc();
+
+    psArray *table = psArrayAllocEmpty (1);
+
+    // set the extname : we are really only allowed one entry per chip; check this here?
+    char *chiprule = psStringCopy ("{CHIP.NAME}"); 
+    char *chipname = pmFPAfileNameFromRule (chiprule, file, view);
+
+    for (int i = 0; i < refstars->n; i++) {
+      psMetadata *row = psMetadataAlloc ();
+		
+      pmAstromObj *ref = refstars->data[i];
+
+      psMetadataAddF64(row,    PS_LIST_TAIL, "RA",     	PS_META_REPLACE, "degrees", PS_DEG_RAD*ref->sky->r);
+      psMetadataAddF64(row,    PS_LIST_TAIL, "DEC",    	PS_META_REPLACE, "degrees", PS_DEG_RAD*ref->sky->d);
+      psMetadataAddF32(row,    PS_LIST_TAIL, "TP_X",   	PS_META_REPLACE, "microns", ref->TP->x);
+      psMetadataAddF32(row,    PS_LIST_TAIL, "TP_Y",   	PS_META_REPLACE, "microns", ref->TP->y);
+      psMetadataAddF32(row,    PS_LIST_TAIL, "FP_X",   	PS_META_REPLACE, "microns", ref->FP->x);
+      psMetadataAddF32(row,    PS_LIST_TAIL, "FP_Y",   	PS_META_REPLACE, "microns", ref->FP->y);
+      psMetadataAddF32(row,    PS_LIST_TAIL, "CHIP_X", 	PS_META_REPLACE, "microns", ref->chip->x);
+      psMetadataAddF32(row,    PS_LIST_TAIL, "CHIP_Y", 	PS_META_REPLACE, "microns", ref->chip->y);
+      psMetadataAddF32(row,    PS_LIST_TAIL, "MAG",    	PS_META_REPLACE, "microns", ref->Mag);
+      psMetadataAddF32(row,    PS_LIST_TAIL, "MAG_ERR", PS_META_REPLACE, "microns", ref->dMag);
+
+      psArrayAdd (table, 100, row);
+      psFree (row);
+    }
+    if (!psFitsWriteTable (file->fits, header, table, chipname)) {
+        psError(PS_ERR_IO, false, "writing refstars\n");
+        psFree (table);
+	psFree (header);
+	psFree (chiprule);
+	psFree (chipname);
+        return false;
+    }
+
+    psFree (chiprule);
+    psFree (chipname);
+    psFree (table);
+    psFree (header);
+    return true;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryRefstars.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryRefstars.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryRefstars.h	(revision 22157)
@@ -0,0 +1,34 @@
+/* @file  pmAstrometryRefstars.h
+ * @brief Functions to write (and read?) astrometric reference stars
+ *
+ * @ingroup AstroImage
+ *
+ * @author EAM, IfA
+ * @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-03-18 18:24:29 $
+ * Copyright 2008 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_ASTROMETRY_REFSTARS_H
+#define PM_ASTROMETRY_REFSTARS_H
+
+/// @addtogroup Astrometry
+/// @{
+
+bool pmAstromRefstarsCheckDataStatusForView (const pmFPAview *view, pmFPAfile *file);
+bool pmAstromRefstarsCheckDataStatusForFPA (const pmFPA *fpa);
+bool pmAstromRefstarsCheckDataStatusForChip (const pmChip *chip);
+bool pmAstromRefstarsCheckDataStatusForCell (const pmCell *cell);
+bool pmAstromRefstarsCheckDataStatusForReadout (const pmReadout *readout);
+
+bool pmAstromRefstarsWriteForView (const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmAstromRefstarsWritePHU (const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+
+bool pmAstromRefstarsWriteFPA (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmAstromRefstarsWriteChip (pmChip *chip, const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmAstromRefstarsWriteCell (pmCell *cell, const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmAstromRefstarsWriteReadout (pmReadout *readout, const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+
+
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryRegions.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryRegions.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryRegions.c	(revision 22157)
@@ -0,0 +1,170 @@
+/** @file  pmAstrometryRegions.c
+ *  @brief functions to define astrometry regions on FPA images
+ *  @ingroup Astrometry
+ *
+ *  @author EAM, IfA
+ *  @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-12-22 17:51:48 $
+ *
+ *  Copyright 2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAExtent.h"
+#include "pmAstrometryRegions.h"
+
+// cell pixels corresponding to readout boundary
+psRegion *pmAstromReadoutInCell (pmReadout *readout) {
+
+    psRegion *region;
+    region = pmReadoutExtent (readout);
+    return (region);
+}
+
+// chip pixels corresponding to cell boundary
+psRegion *pmAstromCellInChip (pmCell *cell) {
+
+    psRegion *region;
+    region = pmCellExtent (cell);
+    return (region);
+}
+
+// FP pixels corresponding to chip boundary
+// since the chip may be rotated in the fpa, this region does not correspond 
+// exactly to the pixel grid of the chip
+psRegion *pmAstromChipInFP (pmChip *chip) {
+
+    PS_ASSERT_PTR_NON_NULL(chip, NULL);
+
+    // if we have not astrometry for this chip, just skip it silently (no error)
+    if (!chip->toFPA) return NULL;
+
+    // determine the bounding box of this chip in chip pixels
+    psRegion *chipExtent = pmChipPixels (chip);
+    if (!chipExtent) return NULL;
+
+    // apply chip-to-fpa astrometry to determine fpa coordinates 
+    psPlane *chPix = psPlaneAlloc ();
+    psPlane *fpPix = psPlaneAlloc ();
+    psRegion *chipRegion = psRegionAlloc(INFINITY, 0, INFINITY, 0); // Extent of chip
+
+    // determine the outer bounding box -- does not correspond to the same square region!
+    chPix->x = chipExtent->x0;
+    chPix->y = chipExtent->y0;
+    psPlaneTransformApply(fpPix, chip->toFPA, chPix); 
+    chipRegion->x0 = PS_MIN (chipRegion->x0, fpPix->x);
+    chipRegion->x1 = PS_MAX (chipRegion->x1, fpPix->x);
+    chipRegion->y0 = PS_MIN (chipRegion->y0, fpPix->y);
+    chipRegion->y1 = PS_MAX (chipRegion->y1, fpPix->y);
+
+    chPix->x = chipExtent->x1;
+    chPix->y = chipExtent->y0;
+    psPlaneTransformApply(fpPix, chip->toFPA, chPix); 
+    chipRegion->x0 = PS_MIN (chipRegion->x0, fpPix->x);
+    chipRegion->x1 = PS_MAX (chipRegion->x1, fpPix->x);
+    chipRegion->y0 = PS_MIN (chipRegion->y0, fpPix->y);
+    chipRegion->y1 = PS_MAX (chipRegion->y1, fpPix->y);
+
+    chPix->x = chipExtent->x1;
+    chPix->y = chipExtent->y1;
+    psPlaneTransformApply(fpPix, chip->toFPA, chPix); 
+    chipRegion->x0 = PS_MIN (chipRegion->x0, fpPix->x);
+    chipRegion->x1 = PS_MAX (chipRegion->x1, fpPix->x);
+    chipRegion->y0 = PS_MIN (chipRegion->y0, fpPix->y);
+    chipRegion->y1 = PS_MAX (chipRegion->y1, fpPix->y);
+
+    chPix->x = chipExtent->x0;
+    chPix->y = chipExtent->y1;
+    psPlaneTransformApply(fpPix, chip->toFPA, chPix); 
+    chipRegion->x0 = PS_MIN (chipRegion->x0, fpPix->x);
+    chipRegion->x1 = PS_MAX (chipRegion->x1, fpPix->x);
+    chipRegion->y0 = PS_MIN (chipRegion->y0, fpPix->y);
+    chipRegion->y1 = PS_MAX (chipRegion->y1, fpPix->y);
+
+    psFree (chPix);
+    psFree (fpPix);
+    psFree (chipExtent);
+    return (chipRegion);
+}
+
+// return FPA pixels included in all chips
+// this FPA grid has 0,0 at the mosaic center and is used for astrometric reference.
+psRegion *pmAstromFPAExtent(const pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    psArray *chips = fpa->chips;       // Array of component chips
+    psRegion *fpaExtent = psRegionAlloc(INFINITY, 0, INFINITY, 0); // Extent of fpa
+    for (long i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // Chip of interest
+        psRegion *chipExtent = pmAstromChipInFP(chip); // Extent of chip
+	if (!chipExtent) { continue; }
+        fpaExtent->x0 = PS_MIN(fpaExtent->x0, chipExtent->x0);
+        fpaExtent->x1 = PS_MAX(fpaExtent->x1, chipExtent->x1);
+        fpaExtent->y0 = PS_MIN(fpaExtent->y0, chipExtent->y0);
+        fpaExtent->y1 = PS_MAX(fpaExtent->y1, chipExtent->y1);
+        psFree(chipExtent);
+    }
+
+    return fpaExtent;
+}
+
+// TPA pixels corresponding to FPA boundary
+psRegion *pmAstromFPInTP (pmFPA *fpa) {
+
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa->toTPA, NULL);
+
+    psRegion *fpaExtent = pmAstromFPAExtent (fpa);
+    if (!fpaExtent) return NULL;
+
+    // apply fpa-to-tpa astrometry to determine tpa coordinates 
+    psPlane *fpPix = psPlaneAlloc ();
+    psPlane *tpPix = psPlaneAlloc ();
+    psRegion *fpaRegion = psRegionAlloc(INFINITY, 0, INFINITY, 0); // Extent of fpa
+
+    // determine the outer bounding box -- does not correspond to the same square region!
+    fpPix->x = fpaExtent->x0;
+    fpPix->y = fpaExtent->y0;
+    psPlaneTransformApply(tpPix, fpa->toTPA, fpPix); 
+    fpaRegion->x0 = PS_MIN (fpaRegion->x0, tpPix->x);
+    fpaRegion->x1 = PS_MAX (fpaRegion->x1, tpPix->x);
+    fpaRegion->y0 = PS_MIN (fpaRegion->y0, tpPix->y);
+    fpaRegion->y1 = PS_MAX (fpaRegion->y1, tpPix->y);
+
+    fpPix->x = fpaExtent->x1;
+    fpPix->y = fpaExtent->y0;
+    psPlaneTransformApply(tpPix, fpa->toTPA, fpPix); 
+    fpaRegion->x0 = PS_MIN (fpaRegion->x0, tpPix->x);
+    fpaRegion->x1 = PS_MAX (fpaRegion->x1, tpPix->x);
+    fpaRegion->y0 = PS_MIN (fpaRegion->y0, tpPix->y);
+    fpaRegion->y1 = PS_MAX (fpaRegion->y1, tpPix->y);
+
+    fpPix->x = fpaExtent->x1;
+    fpPix->y = fpaExtent->y1;
+    psPlaneTransformApply(tpPix, fpa->toTPA, fpPix); 
+    fpaRegion->x0 = PS_MIN (fpaRegion->x0, tpPix->x);
+    fpaRegion->x1 = PS_MAX (fpaRegion->x1, tpPix->x);
+    fpaRegion->y0 = PS_MIN (fpaRegion->y0, tpPix->y);
+    fpaRegion->y1 = PS_MAX (fpaRegion->y1, tpPix->y);
+
+    fpPix->x = fpaExtent->x0;
+    fpPix->y = fpaExtent->y1;
+    psPlaneTransformApply(tpPix, fpa->toTPA, fpPix); 
+    fpaRegion->x0 = PS_MIN (fpaRegion->x0, tpPix->x);
+    fpaRegion->x1 = PS_MAX (fpaRegion->x1, tpPix->x);
+    fpaRegion->y0 = PS_MIN (fpaRegion->y0, tpPix->y);
+    fpaRegion->y1 = PS_MAX (fpaRegion->y1, tpPix->y);
+
+    psFree (fpPix);
+    psFree (tpPix);
+    psFree (fpaExtent);
+    return (fpaRegion);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryRegions.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryRegions.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryRegions.h	(revision 22157)
@@ -0,0 +1,35 @@
+/* @file  pmAstrometryRegion.h
+ * @brief functions to detemine fpa,chip,etc boundaries from astrometry
+ *
+ * @author EAM, IfA
+ * @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-03-21 21:59:57 $
+ * Copyright 2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_ASTROMETRY_REGIONS_H
+#define PM_ASTROMETRY_REGIONS_H
+
+/// @addtogroup Astrometry
+/// @{
+
+// cell pixels corresponding to readout boundary
+psRegion *pmAstromReadoutInCell (pmReadout *readout);
+
+// chip pixels corresponding to cell boundary
+psRegion *pmAstromCellInChip (pmCell *cell);
+
+// FP pixels corresponding to chip boundary
+// since the chip may be rotated in the fpa, this region does not correspond 
+// exactly to the pixel grid of the chip
+psRegion *pmAstromChipInFP (pmChip *chip);
+
+// return FPA pixels included in all chips
+// this FPA grid has 0,0 at the mosaic center and is used for astrometric reference.
+psRegion *pmAstromFPAExtent(const pmFPA *fpa);
+
+// chip pixels corresponding to cell boundary
+psRegion *pmAstromFPInTP (pmFPA *fpa);
+
+/// @}
+#endif // PM_ASTROMETRY_DISTORTION_H
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryTable.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryTable.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryTable.c	(revision 22157)
@@ -0,0 +1,765 @@
+/** @file  pmAstrometryTable.c
+ *
+ *  @brief Functions to read and write astrometric information using the generic astrometry
+ *  model.
+ * 
+ *  The generic model does not specify the location of the boresite on the sky, and it includes
+ *  a model for the rotator and motion of the boresite.
+ *
+ *  @ingroup AstroImage
+ *
+ *  @author EAM, IfA
+ *  @version $Revision: 1.9 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-06-09 01:53:46 $
+ *
+ *  Copyright 2007 Institute for Astronomy, University of Hawaii
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/******************************************************************************/
+/*  INCLUDE FILES                                                             */
+/******************************************************************************/
+#include <stdio.h>
+#include <strings.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+#include <unistd.h>   // for unlink
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+#include "pmFPAExtent.h"
+#include "pmAstrometryWCS.h"
+#include "pmAstrometryUtils.h"
+#include "pmAstrometryRegions.h"
+#include "pmAstrometryTable.h"
+
+# define REQUIRE(TEST,MESSAGE){ if (!(TEST)) { psAbort (MESSAGE); }}
+
+/********************* CheckDataStatus functions *****************************/
+
+bool pmAstromCheckDataStatusForView (const pmFPAview *view, pmFPAfile *file) {
+
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        bool exists = pmAstromCheckDataStatusForFPA (fpa);
+        return exists;
+    }
+    if (view->chip >= fpa->chips->n) {
+        psError(PS_ERR_IO, true, "Requested chip == %d >= fpa->chips->n == %ld", view->chip, fpa->chips->n);
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        bool exists = pmAstromCheckDataStatusForChip (chip);
+        return exists;
+    }
+    if (view->cell >= chip->cells->n) {
+        psError(PS_ERR_IO, true, "Requested cell == %d >= chip->cells->n == %ld", view->cell, chip->cells->n);
+        return false;
+    }
+    psError(PS_ERR_IO, false, "Astrometry only valid at the chip level");
+    return false;
+}
+
+bool pmAstromCheckDataStatusForFPA (const pmFPA *fpa) {
+
+    if (!fpa->toTPA) return false;
+    if (!fpa->fromTPA) return false;
+    if (!fpa->toSky) return false;
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        if (!chip) continue;
+        if (pmAstromCheckDataStatusForChip (chip)) return true;
+    }
+    return false;
+}
+
+bool pmAstromCheckDataStatusForChip (const pmChip *chip) {
+
+    if (!chip->toFPA) return false;
+    if (!chip->fromFPA) return false;  // XXX not strictly needed?
+    return true;
+}
+
+/********************* Write Data functions *****************************/
+
+bool pmAstromWriteForView (const pmFPAview *view, pmFPAfile *file, const pmConfig *config) {
+
+    // write the full table in one pass: require the level to be FPA
+    if (view->chip != -1) {
+	psError(PS_ERR_IO, false, "Astrometry must be written at the FPA level");
+	return false;
+    }
+
+    if (!pmAstromWriteFPA (file)) {
+	psError(PS_ERR_IO, false, "Failed to write Astrometry for fpa");
+	return false;
+    }
+    return true;
+}
+
+// write out all chip-level Astrometry data for this FPA
+bool pmAstromWriteFPA (pmFPAfile *file) {
+
+    if (!pmAstromWritePHU (file)) {
+	psError(PS_ERR_IO, false, "Failed to write PHU for Astrometry table");
+	return false;
+    }
+
+    if (!pmAstromWriteChips (file)) {
+	psError(PS_ERR_IO, false, "Failed to write Astrometry for chips");
+	return false;
+    }
+
+    if (!pmAstromWriteFP (file)) {
+	psError(PS_ERR_IO, false, "Failed to write Sky for Astrometry table");
+	return false;
+    }
+
+    if (!pmAstromWriteTP (file)) {
+	psError(PS_ERR_IO, false, "Failed to write Sky for Astrometry table");
+	return false;
+    }
+
+    if (!pmAstromWriteSky (file)) {
+	psError(PS_ERR_IO, false, "Failed to write Sky for Astrometry table");
+	return false;
+    }
+
+    return true;
+}
+
+bool pmAstromWritePHU (pmFPAfile *file) { 
+
+    // output header data 
+    psMetadata *outhead = psMetadataAlloc();
+
+    // use the FPA phu to generate the PHU header
+    pmHDU *phu = file->fpa->hdu;
+
+    // if there is no FPA PHU, this is a single header+image (extension-less) file. This could be
+    // the case for an input SPLIT set of files being written out as a MEF.  if there is a PHU,
+    // write it out as a 'blank'
+    if (phu) {
+	psMetadataCopy (outhead, phu->header);
+    } else {
+	pmConfigConformHeader (outhead, file->format);
+
+# if (0)
+	// XXX this is no longer needed : FPA.OBS is now correctly copied in pmConfigConformHeader
+	// XXX probably need other info to conform the header
+	psMetadata *fileData = psMetadataLookupMetadata(NULL, file->format, "FILE"); // File information
+	const char *fpaNameHdr = psMetadataLookupStr(NULL, fileData, "FPA.NAME");
+	if (fpaNameHdr && strlen(fpaNameHdr) > 0) {
+	    const char *fpaName = psMetadataLookupStr(NULL, file->fpa->concepts, "FPA.NAME");
+	    psMetadataAddStr(outhead, PS_LIST_TAIL, fpaNameHdr, PS_META_REPLACE, "FPA name", fpaName);
+	}
+# endif
+    }
+
+    psMetadataAddBool (outhead, PS_LIST_TAIL, "EXTEND", PS_META_REPLACE, "this file has extensions", true);
+    psFitsWriteBlank (file->fits, outhead, "");
+    file->wrote_phu = true;
+
+    psTrace ("pmFPAfile", 5, "wrote phu %s (type: %d)\n", file->filename, file->type);
+    psFree (outhead);
+
+    return true;
+}
+
+// fourth layer holds the chips
+bool pmAstromWriteChips (pmFPAfile *file) {
+
+    psMetadata *header = psMetadataAlloc();
+    psMetadataAddStr(header, PS_LIST_TAIL, "COORD",    PS_META_REPLACE, "name of this layer",	"CHIPS");
+    psMetadataAddStr(header, PS_LIST_TAIL, "PARENT",   PS_META_REPLACE, "next layer up",     	"FOCAL_PLANE");
+    psMetadataAddStr(header, PS_LIST_TAIL, "BOUNDARY", PS_META_REPLACE, "validity region",   	"RECTANGLE");
+    psMetadataAddStr(header, PS_LIST_TAIL, "TRANSFRM", PS_META_REPLACE, "mapping to parent", 	"POLYNOMIAL");
+
+    psArray *table = psArrayAllocEmpty (1);
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    pmChip *chip = NULL;
+    while ((chip = pmFPAviewNextChip (view, file->fpa, 1)) != NULL) {
+
+	if (!chip->toFPA) continue;
+	assert (chip->toFPA->x);
+	assert (chip->toFPA->y);
+      
+	psRegion *region = pmChipPixels (chip);
+
+	// set the chip name
+	char *chiprule = psStringCopy ("{CHIP.NAME}"); 
+        char *chipname = pmFPAfileNameFromRule (chiprule, file, view);
+
+	for (int i = 0; i <= chip->toFPA->x->nX; i++) {
+	    for (int j = 0; j <= chip->toFPA->x->nY; j++) {
+		psMetadata *row = psMetadataAlloc ();
+		
+		psMetadataAddStr(row,    PS_LIST_TAIL, "SEGMENT",  PS_META_REPLACE, "name of this segment", chipname);
+		psMetadataAddStr(row,    PS_LIST_TAIL, "PARENT",   PS_META_REPLACE, "next layer up",        "FOCAL_PLANE");
+		psMetadataAddF32(row,    PS_LIST_TAIL, "MINX",     PS_META_REPLACE, "range", region->x0);
+		psMetadataAddF32(row,    PS_LIST_TAIL, "MAXX",     PS_META_REPLACE, "range", region->x1);
+		psMetadataAddF32(row,    PS_LIST_TAIL, "MINY",     PS_META_REPLACE, "range", region->y0);
+		psMetadataAddF32(row,    PS_LIST_TAIL, "MAXY",     PS_META_REPLACE, "range", region->y1);
+    
+		psMetadataAddS32(row,    PS_LIST_TAIL, "XORDER",   PS_META_REPLACE, "", i);
+		psMetadataAddS32(row,    PS_LIST_TAIL, "YORDER",   PS_META_REPLACE, "", j);
+		psMetadataAddS32(row,    PS_LIST_TAIL, "NXORDER",  PS_META_REPLACE, "", chip->toFPA->x->nX);
+		psMetadataAddS32(row,    PS_LIST_TAIL, "NYORDER",  PS_META_REPLACE, "", chip->toFPA->x->nY);
+		psMetadataAddF32(row,    PS_LIST_TAIL, "POLY_X",   PS_META_REPLACE, "", chip->toFPA->x->coeff[i][j]);
+		psMetadataAddF32(row,    PS_LIST_TAIL, "POLY_Y",   PS_META_REPLACE, "", chip->toFPA->y->coeff[i][j]);
+		psMetadataAddF32(row,    PS_LIST_TAIL, "ERROR_X",  PS_META_REPLACE, "", chip->toFPA->x->coeffErr[i][j]);
+		psMetadataAddF32(row,    PS_LIST_TAIL, "ERROR_Y",  PS_META_REPLACE, "", chip->toFPA->y->coeffErr[i][j]);
+		psMetadataAddU8 (row,    PS_LIST_TAIL, "MASK_X",   PS_META_REPLACE, "", chip->toFPA->x->coeffMask[i][j]);
+		psMetadataAddU8 (row,    PS_LIST_TAIL, "MASK_Y",   PS_META_REPLACE, "", chip->toFPA->y->coeffMask[i][j]);
+		psArrayAdd (table, 100, row);
+		psFree (row);
+	    }
+	}
+	psFree (chiprule);
+	psFree (chipname);
+	psFree (region);
+    }
+
+    if (!psFitsWriteTable (file->fits, header, table, "CHIPS")) {
+        psError(PS_ERR_IO, false, "writing sky data\n");
+        psFree(table);
+        return false;
+    }
+
+    psFree (view);
+    psFree (table);
+    psFree (header);
+    return true;
+}
+
+// third layer is the focal plane
+bool pmAstromWriteFP (pmFPAfile *file) {
+
+    bool status;
+
+    psMetadata *header = psMetadataAlloc();
+    psMetadataAddStr(header, PS_LIST_TAIL, "COORD",    PS_META_REPLACE, "name of this layer",	"FOCAL_PLANE");
+    psMetadataAddStr(header, PS_LIST_TAIL, "PARENT",   PS_META_REPLACE, "next layer up",     	"TANGENT_PLANE");
+    psMetadataAddStr(header, PS_LIST_TAIL, "BOUNDARY", PS_META_REPLACE, "validity region",   	"RECTANGLE");
+    psMetadataAddStr(header, PS_LIST_TAIL, "TRANSFRM", PS_META_REPLACE, "mapping to parent", 	"POLYNOMIAL");
+
+    psArray *table = psArrayAllocEmpty (1);
+
+    // XXX is this or the tpa region correct?
+    psRegion *region = pmAstromFPAExtent (file->fpa);
+
+    // rotate the toTPA to have 0.0 posangle
+    float posangle = psMetadataLookupF32 (&status, file->fpa->concepts, "FPA.POSANGLE"); 
+
+    // the to/from TPA transform currently has rotation of posangle; remove it & create the toTPA version
+    psPlaneTransform *fromTPA = psPlaneTransformRotate (NULL, file->fpa->fromTPA, -posangle);
+    psPlaneTransform *toTPA   = psPlaneTransformInvert(NULL, fromTPA, *region, 50);
+
+    for (int i = 0; i <= toTPA->x->nX; i++) {
+	for (int j = 0; j <= toTPA->x->nY; j++) {
+	    psMetadata *row = psMetadataAlloc ();
+	    psMetadataAddStr(row,    PS_LIST_TAIL, "SEGMENT",  PS_META_REPLACE, "name of this segment", "FOCAL_PLANE");
+	    psMetadataAddStr(row,    PS_LIST_TAIL, "PARENT",   PS_META_REPLACE, "next layer up",        "TANGENT_PLANE");
+	    psMetadataAddF32(row,    PS_LIST_TAIL, "MINX",     PS_META_REPLACE, "range", region->x0);
+	    psMetadataAddF32(row,    PS_LIST_TAIL, "MAXX",     PS_META_REPLACE, "range", region->x1);
+	    psMetadataAddF32(row,    PS_LIST_TAIL, "MINY",     PS_META_REPLACE, "range", region->y0);
+	    psMetadataAddF32(row,    PS_LIST_TAIL, "MAXY",     PS_META_REPLACE, "range", region->y1);
+    
+	    psMetadataAddS32(row,    PS_LIST_TAIL, "XORDER",   PS_META_REPLACE, "", i);
+	    psMetadataAddS32(row,    PS_LIST_TAIL, "YORDER",   PS_META_REPLACE, "", j);
+	    psMetadataAddS32(row,    PS_LIST_TAIL, "NXORDER",  PS_META_REPLACE, "", toTPA->x->nX);
+	    psMetadataAddS32(row,    PS_LIST_TAIL, "NYORDER",  PS_META_REPLACE, "", toTPA->x->nY);
+	    psMetadataAddF32(row,    PS_LIST_TAIL, "POLY_X",   PS_META_REPLACE, "", toTPA->x->coeff[i][j]);
+	    psMetadataAddF32(row,    PS_LIST_TAIL, "POLY_Y",   PS_META_REPLACE, "", toTPA->y->coeff[i][j]);
+	    psMetadataAddF32(row,    PS_LIST_TAIL, "ERROR_X",  PS_META_REPLACE, "", toTPA->x->coeffErr[i][j]);
+	    psMetadataAddF32(row,    PS_LIST_TAIL, "ERROR_Y",  PS_META_REPLACE, "", toTPA->y->coeffErr[i][j]);
+	    psMetadataAddU8 (row,    PS_LIST_TAIL, "MASK_X",   PS_META_REPLACE, "", toTPA->x->coeffMask[i][j]);
+	    psMetadataAddU8 (row,    PS_LIST_TAIL, "MASK_Y",   PS_META_REPLACE, "", toTPA->y->coeffMask[i][j]);
+
+	    psArrayAdd (table, 100, row);
+	    psFree (row);
+	}
+    }
+
+    if (!psFitsWriteTable (file->fits, header, table, "FP")) {
+        psError(PS_ERR_IO, false, "writing sky data\n");
+        psFree(table);
+	psFree (header);
+	psFree (region);
+        return false;
+    }
+
+    psFree (table);
+    psFree (header);
+    psFree (region);
+    return true;
+}
+
+// second layer is the tangent plane
+bool pmAstromWriteTP (pmFPAfile *file) {
+
+    bool status;
+
+    // get the boresite model parameters
+    float Xo = psMetadataLookupF32 (&status, file->fpa->concepts, "FPA.BORE.X0"); 
+    float Yo = psMetadataLookupF32 (&status, file->fpa->concepts, "FPA.BORE.Y0"); 
+    float RX = psMetadataLookupF32 (&status, file->fpa->concepts, "FPA.BORE.RX"); 
+    float RY = psMetadataLookupF32 (&status, file->fpa->concepts, "FPA.BORE.RY"); 
+    float To = psMetadataLookupF32 (&status, file->fpa->concepts, "FPA.BORE.T0"); 
+    float Po = psMetadataLookupF32 (&status, file->fpa->concepts, "FPA.BORE.P0"); 
+    float PosZero = psMetadataLookupF32 (&status, file->fpa->concepts, "FPA.POS_ZERO");  /// XXX be consistent with degrees v radians
+    char *refChip = psMetadataLookupStr (&status, file->fpa->concepts, "FPA.REF.CHIP");
+
+    psMetadata *header = psMetadataAlloc();
+    psMetadataAddStr(header, PS_LIST_TAIL, "COORD",    PS_META_REPLACE, "name of this layer",	"TANGENT_PLANE");
+    psMetadataAddStr(header, PS_LIST_TAIL, "PARENT",   PS_META_REPLACE, "next layer up",     	"SKY");
+    psMetadataAddStr(header, PS_LIST_TAIL, "BOUNDARY", PS_META_REPLACE, "validity region",   	"RECTANGLE");
+    psMetadataAddStr(header, PS_LIST_TAIL, "TRANSFRM", PS_META_REPLACE, "mapping to parent", 	"PROJECTION");
+
+    psArray *table = psArrayAllocEmpty (1);
+    psMetadata *row = psMetadataAlloc ();
+    psMetadataAddStr(row,    PS_LIST_TAIL, "SEGMENT",  PS_META_REPLACE, "name of this segment", "TANGENT_PLANE");
+    psMetadataAddStr(row,    PS_LIST_TAIL, "PARENT",   PS_META_REPLACE, "next layer up",        "SKY");
+
+    psRegion *region = pmAstromFPAExtent (file->fpa);
+    psMetadataAddF32(row,    PS_LIST_TAIL, "MINX",     PS_META_REPLACE, "range", region->x0);
+    psMetadataAddF32(row,    PS_LIST_TAIL, "MAXX",     PS_META_REPLACE, "range", region->x1);
+    psMetadataAddF32(row,    PS_LIST_TAIL, "MINY",     PS_META_REPLACE, "range", region->y0);
+    psMetadataAddF32(row,    PS_LIST_TAIL, "MAXY",     PS_META_REPLACE, "range", region->y1);
+    
+    // psMetadataAddF32(row,    PS_LIST_TAIL, "XREF",     PS_META_REPLACE, "", file->fpa->toSky->R  * PM_DEG_RAD);
+    // psMetadataAddF32(row,    PS_LIST_TAIL, "YREF",     PS_META_REPLACE, "", file->fpa->toSky->D  * PM_DEG_RAD);
+
+    psMetadataAddF32(row,    PS_LIST_TAIL, "XSCALE",   PS_META_REPLACE, "", file->fpa->toSky->Xs * PM_DEG_RAD);
+    psMetadataAddF32(row,    PS_LIST_TAIL, "YSCALE",   PS_META_REPLACE, "", file->fpa->toSky->Ys * PM_DEG_RAD);
+    psMetadataAddF32(row,    PS_LIST_TAIL, "BORE_X0",  PS_META_REPLACE, "boresite parameter", Xo);
+    psMetadataAddF32(row,    PS_LIST_TAIL, "BORE_Y0",  PS_META_REPLACE, "boresite parameter", Yo);
+    psMetadataAddF32(row,    PS_LIST_TAIL, "BORE_RX",  PS_META_REPLACE, "boresite parameter", RX);
+    psMetadataAddF32(row,    PS_LIST_TAIL, "BORE_RY",  PS_META_REPLACE, "boresite parameter", RY);
+    psMetadataAddF32(row,    PS_LIST_TAIL, "BORE_T0",  PS_META_REPLACE, "boresite parameter", To);
+    psMetadataAddF32(row,    PS_LIST_TAIL, "BORE_P0",  PS_META_REPLACE, "boresite parameter", Po);
+    psMetadataAddF32(row,    PS_LIST_TAIL, "POS_ZERO", PS_META_REPLACE, "boresite parameter", PosZero);
+    psMetadataAddStr(row,    PS_LIST_TAIL, "REF_CHIP", PS_META_REPLACE, "boresite parameter", refChip);
+
+    psArrayAdd (table, 100, row);
+    psFree (row);
+
+    if (!psFitsWriteTable (file->fits, header, table, "TP")) {
+        psError(PS_ERR_IO, false, "writing sky data\n");
+	psFree (region);
+        psFree (table);
+	psFree (header);
+        return false;
+    }
+
+    psFree (region);
+    psFree (table);
+    psFree (header);
+    return (true);
+}
+
+// first layer is the sky
+bool pmAstromWriteSky (pmFPAfile *file) {
+
+    psMetadata *header = psMetadataAlloc();
+    psMetadataAddStr(header, PS_LIST_TAIL, "COORD",    PS_META_REPLACE, "name of this layer", 	"SKY");
+    psMetadataAddStr(header, PS_LIST_TAIL, "PARENT",   PS_META_REPLACE, "next layer up",      	"NONE");
+    psMetadataAddStr(header, PS_LIST_TAIL, "BOUNDARY", PS_META_REPLACE, "validity region",    	"NONE");
+    psMetadataAddStr(header, PS_LIST_TAIL, "TRANSFRM", PS_META_REPLACE, "mapping to parent",  	"NONE");
+
+    psArray *table = psArrayAllocEmpty (1);
+    psMetadata *row = psMetadataAlloc ();
+    psMetadataAddStr(row,    PS_LIST_TAIL, "SEGMENT",  PS_META_REPLACE, "name of this segment", "SKY");
+    psMetadataAddStr(row,    PS_LIST_TAIL, "PARENT",   PS_META_REPLACE, "next layer up",        "NONE");
+    
+    psArrayAdd (table, 100, row);
+    psFree (row);
+
+    if (!psFitsWriteTable (file->fits, header, table, "SKY")) {
+        psError(PS_ERR_IO, false, "writing sky data\n");
+        psFree(table);
+        return false;
+    }
+
+    psFree (table);
+    psFree (header);
+    return (true);
+}
+
+/********************* Read Data functions *****************************/
+
+bool pmAstromReadForView (const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+    {
+
+	// write the full table in one pass: require the level to be FPA
+	if (view->chip != -1) {
+	    psError(PS_ERR_IO, false, "Astrometry must be read at the FPA level");
+	    return false;
+	}
+
+	if (!pmAstromReadFPA (file)) {
+	    psError(PS_ERR_IO, false, "Failed to read Astrometry for fpa");
+	    return false;
+	}
+	return true;
+    }
+
+// read out all chip-level Astrometry data for this FPA
+bool pmAstromReadFPA (pmFPAfile *file) {
+
+    if (!pmAstromReadPHU (file)) {
+	psError(PS_ERR_IO, false, "Failed to read PHU for Astrometry table");
+	return false;
+    }
+
+    if (!pmAstromReadChips (file)) {
+	psError(PS_ERR_IO, false, "Failed to read Astrometry for chips");
+	return false;
+    }
+
+    if (!pmAstromReadFP (file)) {
+	psError(PS_ERR_IO, false, "Failed to read Sky for Astrometry table");
+	return false;
+    }
+
+    // NOTE : TP must come after FP as it applies the POS, ROT boresite corrections to the
+    // transformation determined in FP
+    if (!pmAstromReadTP (file)) {
+	psError(PS_ERR_IO, false, "Failed to read Sky for Astrometry table");
+	return false;
+    }
+
+    if (!pmAstromReadSky (file)) {
+	psError(PS_ERR_IO, false, "Failed to read Sky for Astrometry table");
+	return false;
+    }
+
+    return true;
+}
+
+bool pmAstromReadPHU (pmFPAfile *file) { 
+
+    fprintf (stderr, "not sure we need to read PHU\n");
+    return true;
+}
+
+int pmConceptsChipNumberFromName (pmFPA *fpa, char *name) {
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+	pmChip *chip = fpa->chips->data[i];
+	if (!chip) continue;
+	char *thisone = psMetadataLookupStr (NULL, chip->concepts, "CHIP.NAME");
+	if (!thisone) continue;
+	if (!strcmp (name, thisone)) return (i);
+    }
+    return -1;
+}
+
+pmChip *pmConceptsChipFromName (pmFPA *fpa, char *name) {
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+	pmChip *chip = fpa->chips->data[i];
+	if (!chip) continue;
+	char *thisone = psMetadataLookupStr (NULL, chip->concepts, "CHIP.NAME");
+	if (!thisone) continue;
+	if (!strcmp (name, thisone)) return (chip);
+    }
+    return NULL;
+}
+
+// first layer converts Chip to Focal Plane
+bool pmAstromReadChips (pmFPAfile *file) {
+
+    bool status;
+
+    // set FITS cursor
+    if (!psFitsMoveExtName (file->fits, "CHIPS")) {
+        psError(PS_ERR_IO, false, "missing CHIPS extension in astrometry table\n");
+        return false;
+    }
+
+    // free exising tranformations in prep for new alloc below
+    for (int i = 0; i < file->fpa->chips->n; i++) {
+	pmChip *chip = file->fpa->chips->data[i];
+	psFree (chip->toFPA);
+	chip->toFPA = NULL;
+    }
+
+    // XXX do I need anything from the header?
+    psMetadata *header = psFitsReadHeader(NULL, file->fits); // The FITS header
+    if (!header) psAbort("cannot read table header");
+
+    // load the full table in one shot
+    psArray *table = psFitsReadTable (file->fits);
+    if (!table) psAbort("cannot read table");
+    fprintf (stderr, "read %ld rows from FP\n", table->n);
+
+    // parse the table entries
+    for (int i = 0; i < table->n; i++) {
+	psMetadata *row = table->data[i];
+
+	// name of the chip for this row. 
+	char *chipname = psMetadataLookupStr (&status, row, "SEGMENT");
+
+	// get chip from name
+	pmChip *chip = pmConceptsChipFromName (file->fpa, chipname);
+	REQUIRE (chip, "invalid chip name");
+
+	// define the toFPA transform if not already defined
+	int nX = psMetadataLookupS32(&status, row, "NXORDER"); REQUIRE (status, "missing NXORDER");
+	int nY = psMetadataLookupS32(&status, row, "NYORDER"); REQUIRE (status, "missing NYORDER");
+	if (chip->toFPA == NULL) {
+	    chip->toFPA = psPlaneTransformAlloc(nX, nY);
+	} else {
+	    REQUIRE (chip->toFPA->x->nX == nX, "mismatch in chip order");
+	    REQUIRE (chip->toFPA->x->nY == nY, "mismatch in chip order");
+	    REQUIRE (chip->toFPA->y->nX == nX, "mismatch in chip order");
+	    REQUIRE (chip->toFPA->y->nY == nY, "mismatch in chip order");
+	}
+
+	int ix = psMetadataLookupS32(&status, row, "XORDER");  REQUIRE (status, "missing XORDER");
+	int iy = psMetadataLookupS32(&status, row, "YORDER");  REQUIRE (status, "missing YORDER");
+
+	// XXX do I need to use this or can i rely on the camera concepts?
+	// minX = psMetadataLookupF32(&status, row, "MINX");
+	// maxX = psMetadataLookupF32(&status, row, "MAXX");
+	// minY = psMetadataLookupF32(&status, row, "MINY");
+	// maxY = psMetadataLookupF32(&status, row, "MAXY");
+
+	// XXX need to include mask values
+	chip->toFPA->x->coeff[ix][iy]    = psMetadataLookupF32(&status, row, "POLY_X");
+	chip->toFPA->y->coeff[ix][iy]    = psMetadataLookupF32(&status, row, "POLY_Y");
+	chip->toFPA->x->coeffErr[ix][iy] = psMetadataLookupF32(&status, row, "ERROR_X");
+	chip->toFPA->y->coeffErr[ix][iy] = psMetadataLookupF32(&status, row, "ERROR_Y");
+	chip->toFPA->x->coeffMask[ix][iy] = psMetadataLookupU8(&status, row, "MASK_X");
+	chip->toFPA->y->coeffMask[ix][iy] = psMetadataLookupU8(&status, row, "MASK_Y");
+    }
+
+    // convert the toFPA transfomations to fromFPA transformations
+    for (int i = 0; i < file->fpa->chips->n; i++) {
+	pmChip *chip = file->fpa->chips->data[i];
+	if (!chip->toFPA) continue;
+	psRegion *region = pmChipPixels (chip);
+	psFree (chip->fromFPA);
+	chip->fromFPA = psPlaneTransformInvert(NULL, chip->toFPA, *region, 50);
+	psFree (region);
+    }
+
+    psFree (table);
+    psFree (header);
+    return true;
+}
+
+// second layer converts Focal Plane to Tangent Plane (unrotated)
+bool pmAstromReadFP (pmFPAfile *file) {
+
+    bool status;
+
+    if (!psFitsMoveExtName (file->fits, "FP")) {
+        psError(PS_ERR_IO, false, "missing FP extension in astrometry table\n");
+        return false;
+    }
+
+    psMetadata *header = psFitsReadHeader(NULL, file->fits); // The FITS header
+    if (!header) psAbort("cannot read table header");
+
+    // free the old
+    psFree (file->fpa->toTPA);
+    file->fpa->toTPA = NULL;
+
+    // read the complete table data at one shot
+    psArray *table = psFitsReadTable (file->fits);
+    fprintf (stderr, "read %ld rows from FP\n", table->n);
+
+    // parse the table
+    for (int i = 0; i < table->n; i++) {
+	psMetadata *row = table->data[i];
+
+	// there is only one transformation in this table; the order is defined in the header
+	int nX = psMetadataLookupS32(&status, row, "NXORDER"); REQUIRE (status, "missing NXORDER");
+	int nY = psMetadataLookupS32(&status, row, "NYORDER"); REQUIRE (status, "missing NYORDER");
+	if (file->fpa->toTPA == NULL) {
+	    // allocate the new transformation
+	    file->fpa->toTPA = psPlaneTransformAlloc(nX, nY);
+	} else {
+	    REQUIRE (file->fpa->toTPA->x->nX == nX, "mismatch in chip order");
+	    REQUIRE (file->fpa->toTPA->x->nY == nY, "mismatch in chip order");
+	    REQUIRE (file->fpa->toTPA->y->nX == nX, "mismatch in chip order");
+	    REQUIRE (file->fpa->toTPA->y->nY == nY, "mismatch in chip order");
+	}
+
+	int ix = psMetadataLookupS32(&status, row, "XORDER"); REQUIRE (status, "missing XORDER");
+	int iy = psMetadataLookupS32(&status, row, "YORDER"); REQUIRE (status, "missing YORDER");
+	file->fpa->toTPA->x->coeff[ix][iy]     = psMetadataLookupF32(&status, row, "POLY_X");  REQUIRE (status, "missing POLY_X");
+	file->fpa->toTPA->y->coeff[ix][iy]     = psMetadataLookupF32(&status, row, "POLY_Y");  REQUIRE (status, "missing POLY_Y");
+	file->fpa->toTPA->x->coeffErr[ix][iy]  = psMetadataLookupF32(&status, row, "ERROR_X"); REQUIRE (status, "missing ERROR_X");
+	file->fpa->toTPA->y->coeffErr[ix][iy]  = psMetadataLookupF32(&status, row, "ERROR_Y"); REQUIRE (status, "missing ERROR_Y");
+	file->fpa->toTPA->x->coeffMask[ix][iy] = psMetadataLookupU8 (&status, row, "MASK_X");  REQUIRE (status, "missing MASK_X");
+	file->fpa->toTPA->y->coeffMask[ix][iy] = psMetadataLookupU8 (&status, row, "MASK_Y");  REQUIRE (status, "missing MASK_Y");
+    }
+
+    psRegion *region = pmAstromFPAExtent (file->fpa);
+    psFree (file->fpa->fromTPA);
+    file->fpa->fromTPA = psPlaneTransformInvert(NULL, file->fpa->toTPA, *region, 50);
+
+    psFree (table);
+    psFree (header);
+    psFree (region);
+    return true;
+}
+
+# define TRANSFER(TO,FROM,NAME) { \
+    psMetadataItem *item = psMetadataLookup(FROM,NAME); \
+    if (!item) psAbort ("cannot find %s", NAME); \
+    psMetadataItem *newItem = psMetadataItemCopy(item); \
+    if (!psMetadataAddItem(TO, newItem, PS_LIST_TAIL, PS_META_REPLACE)) { \
+	psAbort ("cannot copy %s", NAME); \
+    } \
+    psFree (newItem); }
+
+// third layer applies boresite corrections and converts tangent plane to sky
+bool pmAstromReadTP (pmFPAfile *file) {
+    
+    if (!psFitsMoveExtName (file->fits, "TP")) {
+        psError(PS_ERR_IO, false, "missing TP extension in astrometry table\n");
+        return false;
+    }
+
+    psMetadata *header = psFitsReadHeader(NULL, file->fits); // The FITS header
+    if (!header) psAbort("cannot read table header");
+
+    psArray *table = psFitsReadTable (file->fits);
+    fprintf (stderr, "read %ld rows from TP\n", table->n);
+    if (table->n != 1) psAbort("invalid number of rows in TP table (%ld)", table->n);
+
+    psMetadata *row = table->data[0];
+
+    // move needed items to the concepts
+    TRANSFER (file->fpa->concepts, row, "XSCALE");
+    TRANSFER (file->fpa->concepts, row, "XSCALE");
+    TRANSFER (file->fpa->concepts, row, "YSCALE");
+    TRANSFER (file->fpa->concepts, row, "BORE_X0");
+    TRANSFER (file->fpa->concepts, row, "BORE_Y0");
+    TRANSFER (file->fpa->concepts, row, "BORE_RX");
+    TRANSFER (file->fpa->concepts, row, "BORE_RY");
+    TRANSFER (file->fpa->concepts, row, "BORE_T0");
+    TRANSFER (file->fpa->concepts, row, "BORE_P0");
+    TRANSFER (file->fpa->concepts, row, "POS_ZERO");
+    TRANSFER (file->fpa->concepts, row, "REF_CHIP");
+
+    psFree (table);
+    psFree (header);
+    return (true);
+}
+
+// first layer is the sky
+bool pmAstromReadSky (pmFPAfile *file) {
+
+    if (!psFitsMoveExtName (file->fits, "SKY")) {
+        psError(PS_ERR_IO, false, "missing SKY extension in astrometry table\n");
+        return false;
+    }
+
+    psMetadata *header = psFitsReadHeader(NULL, file->fits); // The FITS header
+    if (!header) psAbort("cannot read table header");
+
+    psArray *table = psFitsReadTable (file->fits);
+    fprintf (stderr, "read %ld rows from SKY\n", table->n);
+    if (table->n != 1) psAbort("invalid number of rows in SKY table (%ld)", table->n);
+
+    // XXX not much information of interest in this table...
+
+    psFree (table);
+    psFree (header);
+    return (true);
+}
+
+// third layer applies boresite corrections and converts tangent plane to sky
+bool pmAstromSetTP (pmFPAfile *file, psMetadata *concepts) {
+    
+    bool status;
+
+    // these externally supplied values are used to set the final transformation terms
+    double RA  = psMetadataLookupF64 (&status, concepts, "FPA.RA"); REQUIRE (status, "missing FPA.RA");
+    double DEC = psMetadataLookupF64 (&status, concepts, "FPA.DEC"); REQUIRE (status, "missing FPA.DEC");
+    double POS = PM_RAD_DEG * psMetadataLookupF64 (&status, concepts, "FPA.POSANGLE"); REQUIRE (status, "missing FPA.POSANGLE");
+    // double ROT = psMetadataLookupF64 (&status, concepts, "FPA.ROTANGLE"); REQUIRE (status, "missing FPA.ROTANGLE");
+
+    // get projection scale; center is supplied
+    float Xs = psMetadataLookupF32(&status, file->fpa->concepts, "XSCALE") * PM_RAD_DEG; REQUIRE (status, "missing XSCALE");
+    float Ys = psMetadataLookupF32(&status, file->fpa->concepts, "YSCALE") * PM_RAD_DEG; REQUIRE (status, "missing YSCALE");
+
+    // allocate a new toSky projection using the reported position
+    psFree (file->fpa->toSky);
+    file->fpa->toSky = psProjectionAlloc (RA, DEC, Xs, Ys, PS_PROJ_DIS);
+
+    // get boresite correction terms.  RX,RY,To,Po define an ellipse along which the boresite travels
+    double Xo = psMetadataLookupF32(&status, file->fpa->concepts, "BORE_X0"); REQUIRE (status, "missing ");
+    double Yo = psMetadataLookupF32(&status, file->fpa->concepts, "BORE_Y0"); REQUIRE (status, "missing ");
+    double RX = psMetadataLookupF32(&status, file->fpa->concepts, "BORE_RX"); REQUIRE (status, "missing ");
+    double RY = psMetadataLookupF32(&status, file->fpa->concepts, "BORE_RY"); REQUIRE (status, "missing ");
+    double To = psMetadataLookupF32(&status, file->fpa->concepts, "BORE_T0"); REQUIRE (status, "missing ");
+    double Po = psMetadataLookupF32(&status, file->fpa->concepts, "BORE_P0"); REQUIRE (status, "missing ");
+
+    // the true rotation of the instrument is POSANGLE - POS_ZERO
+    double PosZero = PM_RAD_DEG * psMetadataLookupF32(&status, file->fpa->concepts, "POS_ZERO"); REQUIRE (status, "missing POS_ZERO");
+    char *refChip  = psMetadataLookupStr(&status, file->fpa->concepts, "REF_CHIP"); REQUIRE (status, "missing REF_CHIP");
+
+    // apply true posangle = -(POS - POS_ZERO)
+    psPlaneTransform *fromTPA = psPlaneTransformRotate (NULL, file->fpa->fromTPA, (POS - PosZero));
+    psFree (file->fpa->fromTPA);
+    file->fpa->fromTPA = fromTPA;
+
+    psRegion *region = pmAstromFPAExtent (file->fpa);
+    psFree (file->fpa->toTPA);
+    file->fpa->toTPA = psPlaneTransformInvert(NULL, file->fpa->fromTPA, *region, 50);
+
+    // current position of the nominal boresite in refChip coordinates
+    double X = Xo + RX*cos(POS - To)*cos(Po) + RY*sin(POS - To)*sin(Po);
+    double Y = Yo + RY*sin(POS - To)*cos(Po) - RX*cos(POS - To)*sin(Po);
+
+    // get reference chip from name
+    pmChip *chip = pmConceptsChipFromName (file->fpa, refChip);
+    if (!chip) psAbort ("invalid chip name for reference");
+
+    psPlane *boreCH = psPlaneAlloc();
+    psPlane *boreFP = psPlaneAlloc();
+    psPlane *boreTP = psPlaneAlloc();
+    psSphere *boreSky = psSphereAlloc();
+
+    // find the true RA,DEC coord of the mirror of the reported boresite
+    boreCH->x = -X;
+    boreCH->y = -Y;
+    psPlaneTransformApply (boreFP, chip->toFPA, boreCH);
+    psPlaneTransformApply (boreTP, file->fpa->toTPA, boreFP);
+    psDeproject (boreSky, boreTP, file->fpa->toSky); 
+
+    // modify the projection to account for offset between true and reported boresite
+    file->fpa->toSky->R = boreSky->r;
+    file->fpa->toSky->D = boreSky->d;
+
+    psFree (boreCH);
+    psFree (boreFP);
+    psFree (boreTP);
+    psFree (boreSky);
+
+    psFree (region);
+    return (true);
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryTable.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryTable.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryTable.h	(revision 22157)
@@ -0,0 +1,43 @@
+/* @file  pmAstrometryTable.h
+ * @brief Astrometry table I/O functions
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-12-22 17:53:27 $
+ * Copyright 2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_ASTROMETRY_TABLE_H
+#define PM_ASTROMETRY_TABLE_H
+
+/// @addtogroup Astrometry
+/// @{
+
+bool pmAstromCheckDataStatusForView (const pmFPAview *view, pmFPAfile *file);
+bool pmAstromCheckDataStatusForFPA (const pmFPA *fpa);
+bool pmAstromCheckDataStatusForChip (const pmChip *chip);
+
+bool pmAstromWriteForView (const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmAstromWriteFPA (pmFPAfile *file);
+bool pmAstromWritePHU (pmFPAfile *file);
+bool pmAstromWriteSky (pmFPAfile *file);
+bool pmAstromWriteTP (pmFPAfile *file);
+bool pmAstromWriteFP (pmFPAfile *file);
+bool pmAstromWriteChips (pmFPAfile *file);
+
+int pmConceptsChipNumberFromName (pmFPA *fpa, char *name);
+pmChip *pmConceptsChipFromName (pmFPA *fpa, char *name);
+
+bool pmAstromReadForView (const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmAstromReadFPA (pmFPAfile *file);
+bool pmAstromReadPHU (pmFPAfile *file);
+bool pmAstromReadChips (pmFPAfile *file);
+bool pmAstromReadFP (pmFPAfile *file);
+bool pmAstromReadTP (pmFPAfile *file);
+bool pmAstromReadSky (pmFPAfile *file);
+
+bool pmAstromSetTP (pmFPAfile *file, psMetadata *concepts);
+
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryUtils.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryUtils.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryUtils.c	(revision 22157)
@@ -0,0 +1,377 @@
+/** @file  pmAstrometryUtils.c
+ *
+ *  @brief utility functions for transform and distort functions
+ *
+ *  @ingroup Astrometry
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.7 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-12-22 17:52:21 $
+ *
+ *  Copyright 2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmAstrometryUtils.h"
+
+// given a 2D transformation -- L(x,y),M(x,y) -- find the coordinates x,y
+// for which L,M = 0,0. tol is the allowed error on x,y.
+psPlane *psPlaneTransformGetCenter (psPlaneTransform *trans, double tol)
+{
+
+    // crpix1,2 = X,Y(crval1,2)
+    // start with linear solution for Xo,Yo:
+    double R  = (trans->x->coeff[1][0]*trans->y->coeff[0][1] - trans->x->coeff[0][1]*trans->y->coeff[1][0]);
+    double Xo = (trans->y->coeff[0][0]*trans->x->coeff[0][1] - trans->x->coeff[0][0]*trans->y->coeff[0][1])/R;
+    double Yo = (trans->x->coeff[0][0]*trans->y->coeff[1][0] - trans->y->coeff[0][0]*trans->x->coeff[1][0])/R;
+
+    // iterate to actual solution: requires small non-linear terms
+    if (trans->x->nX > 1) {
+        psPolynomial2D *XdX = psPolynomial2D_dX(NULL, trans->x);
+        psPolynomial2D *XdY = psPolynomial2D_dY(NULL, trans->x);
+
+        psPolynomial2D *YdX = psPolynomial2D_dX(NULL, trans->y);
+        psPolynomial2D *YdY = psPolynomial2D_dY(NULL, trans->y);
+
+        psImage *Alpha = psImageAlloc (2, 2, PS_DATA_F32);
+        psVector *Beta = psVectorAlloc (2, PS_DATA_F32);
+
+        /* this loop uses the Newton-Raphson method to solve for Xo,Yo
+        * it needs the high order terms to be small 
+        * Xo,Yo are in pixels;
+        */
+        double dPos = tol + 1;
+        for (int i = 0; (dPos > tol) && (i < 20); i++) {
+            // NOTE: order for Alpha is: [y][x]
+            Alpha->data.F32[0][0] = psPolynomial2DEval (XdX, Xo, Yo);
+            Alpha->data.F32[1][0] = psPolynomial2DEval (XdY, Xo, Yo);
+            Alpha->data.F32[0][1] = psPolynomial2DEval (YdX, Xo, Yo);
+            Alpha->data.F32[1][1] = psPolynomial2DEval (YdY, Xo, Yo);
+
+            Beta->data.F32[0] = psPolynomial2DEval (trans->x, Xo, Yo);
+            Beta->data.F32[1] = psPolynomial2DEval (trans->y, Xo, Yo);
+
+            psMatrixGJSolveF32 (Alpha, Beta);
+
+            Xo -= Beta->data.F32[0];
+            Yo -= Beta->data.F32[1];
+            dPos = hypot(Beta->data.F32[0], Beta->data.F32[1]);
+        }
+        psFree (Alpha);
+        psFree (Beta);
+        psFree (XdX);
+        psFree (XdY);
+        psFree (YdX);
+        psFree (YdY);
+    }
+    psPlane *center = psPlaneAlloc ();
+    center->x = Xo;
+    center->y = Yo;
+
+    return center;
+}
+
+// convert a transformation L(x,y) to L'(x-xo,y-yo)
+psPlaneTransform *psPlaneTransformSetCenter (psPlaneTransform *output, psPlaneTransform *input, double Xo, double Yo)
+{
+
+    // validate fit order
+
+    if (output == NULL) {
+        output = psPlaneTransformAlloc(input->x->nX, input->x->nY);
+    }
+
+    /* given two equivalent polynomial representations L(x,y) = \sum_i \sum_j A_{i,j} x^i y^j
+     * we can transform L(x,y) into L'(x-xo,y-yo) by taking the derivatives of both sides and 
+     * noting that the constant term in each is the coefficient in the case of L(x,y) and is the 
+     * value of L'(-xo,-yo) in the second case.
+     */
+
+    psPolynomial2D *tmp;
+
+    psPolynomial2D *xPx = psPolynomial2DCopy (NULL, input->x);
+    psPolynomial2D *yPx = psPolynomial2DCopy (NULL, input->y);
+
+    for (int i = 0; i <= input->x->nX; i++) {
+        psPolynomial2D *xPy = psPolynomial2DCopy (NULL, xPx);
+        psPolynomial2D *yPy = psPolynomial2DCopy (NULL, yPx);
+        for (int j = 0; j <= input->x->nY; j++) {
+            output->x->coeffMask[i][j] = input->x->coeffMask[i][j];
+            output->y->coeffMask[i][j] = input->y->coeffMask[i][j];
+            output->x->coeff[i][j] = (output->x->coeffMask[i][j] & PS_POLY_MASK_SET) ? 0 : psPolynomial2DEval (xPy, Xo, Yo) / tgamma(i+1) / tgamma(j+1);
+            output->y->coeff[i][j] = (output->y->coeffMask[i][j] & PS_POLY_MASK_SET) ? 0 : psPolynomial2DEval (yPy, Xo, Yo) / tgamma(i+1) / tgamma(j+1);
+
+            // take the next derivative wrt y, catch output (is NULL on last pass)
+            tmp = psPolynomial2D_dY(NULL, xPy);
+            psFree (xPy);
+            xPy = tmp;
+            tmp = psPolynomial2D_dY(NULL, yPy);
+            psFree (yPy);
+            yPy = tmp;
+        }
+        // take the next derivative wrt x, catch output (is NULL on last pass)
+        tmp = psPolynomial2D_dX(NULL, xPx);
+        psFree (xPx);
+        xPx = tmp;
+        tmp = psPolynomial2D_dX(NULL, yPx);
+        psFree (yPx);
+        yPx = tmp;
+    }
+    return output;
+}
+
+// rotate a transformation L(x,y) by theta
+psPlaneTransform *psPlaneTransformRotate (psPlaneTransform *output, psPlaneTransform *input, double theta)
+{
+    /* given the polynomial transformations:
+     *  L(x,y) = \sum_i \sum_j A_{i,j} x^i y^j and 
+     *  M(x,y) = \sum_i \sum_j B_{i,j} x^i y^j 
+     * we can rotate L,M to L',M' by applying the rotation matrix (c,s),(-s,c).
+     * the resulting terms of L and M are:
+     * A'_{i,j} = c A_{i,j} + s B_{i,j}
+     * B'_{i,j} = c B_{i,j} - s A_{i,j}
+     */
+
+    if (output == NULL) {
+        output = psPlaneTransformAlloc(input->x->nX, input->x->nY);
+    }
+
+    float cs = cos(theta);
+    float sn = sin(theta);
+
+    for (int i = 0; i <= input->x->nX; i++) {
+        for (int j = 0; j <= input->x->nY; j++) {
+	    // XXX what about inconsistent x and y masking?
+            output->x->coeffMask[i][j] = input->x->coeffMask[i][j];
+	    output->y->coeffMask[i][j] = input->y->coeffMask[i][j];
+	    if (output->x->coeffMask[i][j]) {
+		output->x->coeff[i][j] = 0.0;
+		output->y->coeff[i][j] = 0.0;
+	    } else {
+		output->x->coeff[i][j] = cs*input->x->coeff[i][j] + sn*input->y->coeff[i][j];
+		output->y->coeff[i][j] = cs*input->y->coeff[i][j] - sn*input->x->coeff[i][j];
+	    }
+        }
+    }
+    return output;
+}
+
+// construct a psPlaneTransform which is the identify transformation
+psPlaneTransform *psPlaneTransformIdentity (int order)
+{
+
+    psPlaneTransform *transform;
+
+    if (order < 1)
+        psAbort("invalid order");
+    if (order > 3)
+        psAbort("invalid order");
+
+    // all coeffs and masks initially set to 0
+    transform = psPlaneTransformAlloc (order, order);
+
+    for (int i = 0; i <= order; i++) {
+        for (int j = 0; j <= order; j++) {
+            if (i + j > order) {
+                transform->x->coeffMask [i][j] = PS_POLY_MASK_SET;
+                transform->y->coeffMask [i][j] = PS_POLY_MASK_SET;
+            }
+        }
+    }
+    transform->x->coeff[1][0] = 1;
+    transform->y->coeff[0][1] = 1;
+    transform->x->coeffMask[1][0] = PS_POLY_MASK_NONE;
+    transform->y->coeffMask[0][1] = PS_POLY_MASK_NONE;
+
+    return transform;
+}
+
+// check that the given psPlaneTransform is the identity * (Xs,Ys)
+bool psPlaneTransformIsDiagonal (psPlaneTransform *transform)
+{
+
+    int order;
+    bool status;
+
+    // we currently only support up to 3rd order polynomials
+    if (transform->x->nX < 1)
+        return false;
+    if (transform->x->nY < 1)
+        return false;
+    if (transform->y->nX < 1)
+        return false;
+    if (transform->y->nY < 1)
+        return false;
+
+    if (transform->x->nX != transform->x->nY)
+        return false;
+    if (transform->y->nX != transform->y->nY)
+        return false;
+
+    // these are not actually valid tests
+    if (transform->x->nX > 3)
+        return false;
+    if (transform->x->nY > 3)
+        return false;
+    if (transform->y->nX > 3)
+        return false;
+    if (transform->y->nY > 3)
+        return false;
+
+    status = true;
+    order = transform->x->nX;
+    for (int i = 0; i <= order; i++) {
+        for (int j = 0; j <= order; j++) {
+            if (i + j > order) {
+                // high-order cross terms must be masked (eg, x^3 y^2)
+                status &= (transform->x->coeffMask[i][j] & PS_POLY_MASK_SET);
+            } else {
+                status &= !(transform->x->coeffMask[i][j] & PS_POLY_MASK_SET);
+                if ((i == 1) && (i + j == 1)) {
+                    // linear, diagonal terms must be non-zero
+                    status &= (fabs(transform->x->coeff[i][j]) > FLT_EPSILON);
+                } else {
+                    // non-linear and off-diagonal terms must be 0 (eg, x^2, x y)
+                    status &= (fabs(transform->x->coeff[i][j]) < FLT_EPSILON);
+                }
+            }
+        }
+    }
+
+    order = transform->y->nX;
+    for (int i = 0; i <= order; i++) {
+        for (int j = 0; j <= order; j++) {
+            if (i + j > order) {
+                // high-order cross terms must be masked (eg, x^3 y^2)
+                status &= (transform->y->coeffMask[i][j] & PS_POLY_MASK_SET);
+            } else {
+                status &= !(transform->y->coeffMask[i][j] & PS_POLY_MASK_SET);
+                if ((j == 1) && (i + j == 1)) {
+                    // linear, diagonal terms must be 1.0
+                    status &= (fabs(transform->y->coeff[i][j]) > FLT_EPSILON);
+                } else {
+                    // non-linear and off-diagonal terms must be 0 (eg, x^2, x y)
+                    status &= (fabs(transform->y->coeff[i][j]) < FLT_EPSILON);
+                }
+            }
+        }
+    }
+    return status;
+}
+
+// construct a psPlaneDistort which is the identify transformation
+psPlaneDistort *psPlaneDistortIdentity (int order)
+{
+
+    psPlaneDistort *distort;
+
+    if (order < 1)
+        psAbort("invalid order");
+    if (order > 3)
+        psAbort("invalid order");
+
+    // all coeffs and masks initially set to 0
+    distort = psPlaneDistortAlloc (order, order, 0, 0);
+
+    for (int i = 0; i <= order; i++) {
+        for (int j = 0; j <= order; j++) {
+            if (i + j > order) {
+                distort->x->coeffMask [i][j][0][0] = PS_POLY_MASK_SET;
+                distort->y->coeffMask [i][j][0][0] = PS_POLY_MASK_SET;
+            }
+        }
+    }
+    distort->x->coeff[1][0][0][0] = 1;
+    distort->y->coeff[0][1][0][0] = 1;
+
+    return distort;
+}
+
+// check that the given psPlaneDistort is the identity * (Xs,Ys)
+bool psPlaneDistortIsDiagonal (psPlaneDistort *distort)
+{
+
+    int order;
+    bool status;
+
+    // we currently only support up to 3rd order polynomials
+    if (distort->x->nX < 1)
+        return false;
+    if (distort->x->nY < 1)
+        return false;
+    if (distort->y->nX < 1)
+        return false;
+    if (distort->y->nY < 1)
+        return false;
+
+    if (distort->x->nX > 3)
+        return false;
+    if (distort->x->nY > 3)
+        return false;
+    if (distort->y->nX > 3)
+        return false;
+    if (distort->y->nY > 3)
+        return false;
+
+    if (distort->x->nZ > 0)
+        return false;
+    if (distort->x->nT > 0)
+        return false;
+    if (distort->y->nZ > 0)
+        return false;
+    if (distort->y->nT > 0)
+        return false;
+
+    if (distort->x->nX != distort->x->nY)
+        return false;
+    if (distort->y->nX != distort->y->nY)
+        return false;
+
+    status = true;
+    order = distort->x->nX;
+    for (int i = 0; i <= order; i++) {
+        for (int j = 0; j <= order; j++) {
+            if (i + j > order) {
+                // high-order cross terms must be masked (eg, x^3 y^2)
+                status &= (distort->x->coeffMask[i][j][0][0] & PS_POLY_MASK_SET);
+            } else {
+                status &= !(distort->x->coeffMask[i][j][0][0] & PS_POLY_MASK_SET);
+                if ((i == 1) && (i + j == 1)) {
+                    // linear, diagonal terms must be 1.0
+                    status &= (fabs(distort->x->coeff[i][j][0][0]) > FLT_EPSILON);
+                } else {
+                    // non-linear and off-diagonal terms must be 0 (eg, x^2, x y)
+                    status &= (fabs(distort->x->coeff[i][j][0][0]) < FLT_EPSILON);
+                }
+            }
+        }
+    }
+
+    order = distort->y->nX;
+    for (int i = 0; i <= order; i++) {
+        for (int j = 0; j <= order; j++) {
+            if (i + j > order) {
+                // high-order cross terms must be masked (eg, x^3 y^2)
+                status &= (distort->y->coeffMask[i][j][0][0] & PS_POLY_MASK_SET);
+            } else {
+                status &= !(distort->y->coeffMask[i][j][0][0] & PS_POLY_MASK_SET);
+                if ((j == 1) && (i + j == 1)) {
+                    // linear, diagonal terms must be 1.0
+                    status &= (fabs(distort->y->coeff[i][j][0][0]) > FLT_EPSILON);
+                } else {
+                    // non-linear and off-diagonal terms must be 0 (eg, x^2, x y)
+                    status &= (fabs(distort->y->coeff[i][j][0][0]) < FLT_EPSILON);
+                }
+            }
+        }
+    }
+    return status;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryUtils.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryUtils.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryUtils.h	(revision 22157)
@@ -0,0 +1,28 @@
+/* @file  pmAstrometryUtils.h
+ * @brief utility functions for transform and distort functions
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-12-19 18:57:05 $
+ * Copyright 2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_ASTROMETRY_UTILS_H
+#define PM_ASTROMETRY_UTILS_H
+
+/// @addtogroup Astrometry
+/// @{
+
+psPlane *psPlaneTransformGetCenter (psPlaneTransform *trans, double tol);
+psPlaneTransform *psPlaneTransformSetCenter (psPlaneTransform *output, psPlaneTransform *input, double Xo, double Yo);
+psPlaneTransform *psPlaneTransformRotate (psPlaneTransform *output, psPlaneTransform *input, double theta);
+
+psPlaneTransform *psPlaneTransformIdentity (int order);
+psPlaneDistort *psPlaneDistortIdentity (int order);
+
+bool psPlaneTransformIsDiagonal (psPlaneTransform *transform);
+bool psPlaneDistortIsDiagonal (psPlaneDistort *distort);
+
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryWCS.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryWCS.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryWCS.c	(revision 22157)
@@ -0,0 +1,798 @@
+/** @file  pmAstrometryWCS.c
+ *
+ *  @brief functions to convert FITS WCS keywords to / from pmFPA structures
+ *
+ *  @ingroup Astrometry
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.27 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-03-18 18:38:14 $
+ *
+ *  Copyright 2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <strings.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAExtent.h"
+#include "pmAstrometryWCS.h"
+#include "pmAstrometryUtils.h"
+#include "pmAstrometryRegions.h"
+
+// the following functions support coordinate transformations direcly related to the FITS WCS
+// keywords.  The FITS WCS allows for only a single level of transformation, thus it is not
+// appropriate for mosaic astrometry consisting of telescope distortion plus chip terms.
+// Below, we support the Elixir convention of using two connected FITS headers to define two
+// levels of coordinate transformation.  In the pmFPA structure, the projection, distortion,
+// and FPA-to-Chip transformations are carried independently.  NOTE: The FITS WCS keywords do
+// not represent a simple polynomial.  Instead, they have no constant term, and the coordinates
+// are corrected to a reference pixel before the polynomial transformation is applied.
+
+// interpret header WCS (only handles traditional WCS for the moment)
+// pixelScale is microns per pixel
+bool pmAstromReadWCS (pmFPA *fpa, pmChip *chip, const psMetadata *header, double pixelScale)
+{
+    pmAstromWCS *wcs = pmAstromWCSfromHeader (header);
+    if (!wcs) {
+        return false;
+    }
+
+    bool status = pmAstromWCStoFPA (fpa, chip, wcs, pixelScale);
+
+    psFree (wcs);
+    return status;
+}
+
+// convert toFPA / toSky components to pmAstromWCS
+// tolerance is convergence for inversion of non-linear terms in pixels
+bool pmAstromWriteWCS (psMetadata *header, const pmFPA *fpa, const pmChip *chip, double tol)
+{
+    pmAstromWCS *wcs = pmAstromWCSfromFPA(fpa, chip, tol);
+    if (!wcs) return false;
+
+    pmAstromWCStoHeader (header, wcs);
+
+    psFree (wcs);
+    return true;
+}
+
+// interpret chip header WCS as bilevel chip components
+bool pmAstromReadBilevelChip (pmChip *chip, const psMetadata *header)
+{
+    pmAstromWCS *wcs = pmAstromWCSfromHeader (header);
+    if (!wcs) {
+        return false;
+    }
+
+    bool status = pmAstromWCSBileveltoChip (chip, wcs);
+
+    psFree (wcs);
+    return status;
+}
+
+// convert toFPA / toSky components to traditional WCS
+// we require the header to have NAXIS1,NAXIS2, the field of the FPA 
+// the center of the TPA/Sky projection is 0.5*(NAXIS1,NAXIS2)
+bool pmAstromReadBilevelMosaic (pmFPA *fpa, const psMetadata *header)
+{
+    pmAstromWCS *wcs = pmAstromWCSfromHeader (header);
+    if (!wcs) {
+        psError(PS_ERR_UNKNOWN, false, "failure to determine WCS terms from header");
+        return false;
+    }
+
+    bool status1 = false;
+    bool status2 = false;
+    int Nx = psMetadataLookupS32 (&status1, header, "NAXIS1");
+    int Ny = psMetadataLookupS32 (&status2, header, "NAXIS2");
+    if (!status1 || !status2) {
+	psFree (wcs);
+        psError(PS_ERR_UNKNOWN, false, "missing required FPA size in header");
+        return false;
+    }
+
+    psRegion region = psRegionSet (-0.5*Nx, +0.5*Nx, -0.5*Ny, +0.5*Ny);
+    bool status = pmAstromWCSBileveltoFPA (fpa, wcs, region);
+
+    psFree (wcs);
+    return status;
+}
+
+// convert chip->toFPA components to bilevel WCS
+bool pmAstromWriteBilevelChip (psMetadata *header, const pmChip *chip, double tol)
+{
+    pmAstromWCS *wcs = pmAstromWCSBilevelChipFromFPA (chip, tol);
+    pmAstromWCStoHeader (header, wcs);
+
+    psFree (wcs);
+    return true;
+}
+
+
+// convert fpa->toTPA, fpa->toSky components to bilevel WCS
+bool pmAstromWriteBilevelMosaic (psMetadata *header, const pmFPA *fpa, double tol)
+{
+    pmAstromWCS *wcs = pmAstromWCSBilevelMosaicFromFPA (fpa, tol);
+
+    // we need to specify the dimensions of the FPA
+    // if we have chips defined, we can do
+    psRegion *region = pmAstromFPAExtent (fpa);
+    int Nx = region->x1 - region->x0;
+    int Ny = region->y1 - region->y0;
+    psMetadataAddS32 (header, PS_LIST_TAIL, "NAXIS1", PS_META_REPLACE, "Mosaic Dimensions", Nx);
+    psMetadataAddS32 (header, PS_LIST_TAIL, "NAXIS2", PS_META_REPLACE, "Mosaic Dimensions", Ny);
+
+    pmAstromWCStoHeader (header, wcs);
+
+    psFree (region);
+    psFree (wcs);
+    return true;
+}
+
+// convert coordinates from chip to sky using a pmAstromWCS structure
+bool pmAstromWCStoSky (psSphere *sky, pmAstromWCS *wcs, psPlane *chip)
+{
+
+    if (chip == NULL)
+        return false;
+    if (sky == NULL)
+        return false;
+    if (wcs == NULL)
+        return false;
+
+    psPlane *Chip = psPlaneAlloc();
+    psPlane *FP = psPlaneAlloc();
+
+    Chip->x = chip->x - wcs->crpix1;
+    Chip->y = chip->y - wcs->crpix2;
+
+    psPlaneTransformApply (FP, wcs->trans, Chip);
+    psDeproject (sky, FP, wcs->toSky); // find the RA,DEC coord of the focal-plane coordinate
+
+    psFree (Chip);
+    psFree (FP);
+    return true;
+}
+
+// convert coordinates from sky to chip using a pmAstromWCS structure
+bool pmAstromWCStoChip (psPlane *chip, pmAstromWCS *wcs, psSphere *sky)
+{
+
+    if (chip == NULL)
+        return false;
+    if (sky == NULL)
+        return false;
+    if (wcs == NULL)
+        return false;
+
+    psError(PS_ERR_UNKNOWN, true, "not yet implemented: needs to invert the transformation");
+    return false;
+
+    psPlane *Chip = psPlaneAlloc();
+    psPlane *FP = psPlaneAlloc();
+
+    psProject (FP, sky, wcs->toSky); // find the RA,DEC coord of the focal-plane coordinate
+
+    // XXX I actually need the inverse of wcs->transform at this point
+    psPlaneTransformApply (Chip, wcs->trans, FP);
+
+    chip->x = Chip->x + wcs->crpix1;
+    chip->y = Chip->y + wcs->crpix2;
+
+    psFree (Chip);
+    psFree (FP);
+    return true;
+}
+
+// interpret header WCS keywords (valid for bilevel and traditional WCS)
+pmAstromWCS *pmAstromWCSfromHeader (const psMetadata *header)
+{
+    psProjectionType type;
+    bool status, pcKeys, cdKeys, isPoly;
+    char name[16]; // used to store FITS keyword below (always < 8, so 16 should be safe!)
+
+    // interpret header data, convert to crval(i), etc
+    char *ctype = psMetadataLookupPtr (&status, header, "CTYPE2");
+    if (!status) {
+        psLogMsg ("psastro", 5, "warning: no WCS metadata in header\n");
+        return NULL;
+    }
+
+    // determine projection type
+    // XXX there are two indications for higher-order terms: the type (DIS,WRP,PLY,ZPL) and
+    // the value of NPLYTERM.
+    type = psProjectTypeFromString (ctype);
+    if (type == PS_PROJ_NTYPE) {
+        psLogMsg ("psastro", 2, "warning: unknown projection type %s\n", ctype);
+        return NULL;
+    }
+
+    // what type of WCS keywords are available?
+    // XXX add check for CROTA2
+    int fitOrder = psMetadataLookupS32 (&isPoly, header, "NPLYTERM");
+    psMetadataLookupF64 (&pcKeys, header, "PC001001");
+    psMetadataLookupF64 (&cdKeys, header, "CD1_1");
+
+    if (cdKeys && pcKeys) {
+        // XXX make this an option
+        psLogMsg ("psastro", 2, "warning: both CDi_j and PC00i00j defined in headers, using PC00i00j terms\n");
+    }
+    if (!cdKeys && !pcKeys) {
+        psError(PS_ERR_UNKNOWN, true, "missing both CDi_j and PC00i00j WCS terms");
+        // XXX we could default here to RA, DEC, ROTANGLE
+        return NULL;
+    }
+    if (isPoly) {
+        if (!pcKeys) {
+            psError(PS_ERR_UNKNOWN, true, "polynomial terms defined, but missing PC00i00j WCS terms");
+            return NULL;
+        }
+        if (fitOrder == 0)
+            fitOrder = 1;
+        if ((fitOrder > 3) || (fitOrder < 1)) {
+            psError(PS_ERR_UNKNOWN, true, "NPLYTERM value undefined: %d", fitOrder);
+            return NULL;
+        }
+    } else {
+        fitOrder = 1;
+    }
+
+    pmAstromWCS *wcs = pmAstromWCSAlloc (fitOrder, fitOrder);
+
+    // construct a transformation from X,Y in pixels to L,M in pixels
+    // NOTE that the WCS keywords convert X,Y to degrees first (using cdelt1,2)
+    // and then define a transformation from degrees to degrees
+
+    wcs->crval1 = psMetadataLookupF64 (&status, header, "CRVAL1");
+    wcs->crval2 = psMetadataLookupF64 (&status, header, "CRVAL2");
+    wcs->crpix1 = psMetadataLookupF64 (&status, header, "CRPIX1");
+    wcs->crpix2 = psMetadataLookupF64 (&status, header, "CRPIX2");
+    wcs->toSky = psProjectionAlloc (wcs->crval1*PM_RAD_DEG, wcs->crval2*PM_RAD_DEG, PM_RAD_DEG, PM_RAD_DEG, type);
+    // XXX I think this is wrong for linear proj
+
+    // test the CDELTi varient
+    if (pcKeys) {
+        wcs->cdelt1 = psMetadataLookupF64 (&status, header, "CDELT1");
+        wcs->cdelt2 = psMetadataLookupF64 (&status, header, "CDELT2");
+
+        // test the CROTAi varient:
+        // XXX double check lambda..
+        double rotate = psMetadataLookupF64 (&status, header, "CROTA2");
+        if (status) {
+            wcs->trans->x->coeff[1][0] = +wcs->cdelt1 * cos(rotate*PM_RAD_DEG); // == PC1_1
+            wcs->trans->x->coeff[0][1] = -wcs->cdelt2 * sin(rotate*PM_RAD_DEG); // == PC1_2
+            wcs->trans->y->coeff[1][0] = +wcs->cdelt1 * sin(rotate*PM_RAD_DEG); // == PC2_1
+            wcs->trans->y->coeff[0][1] = +wcs->cdelt2 * cos(rotate*PM_RAD_DEG); // == PC2_2
+            return wcs;
+        }
+
+        // FITS WCS PCi,j has units of unity
+        // wcs->trans has units of degrees/pixel
+        wcs->trans->x->coeff[1][0] = wcs->cdelt1 * psMetadataLookupF64 (&status, header, "PC001001"); // == PC1_1
+        wcs->trans->x->coeff[0][1] = wcs->cdelt2 * psMetadataLookupF64 (&status, header, "PC001002"); // == PC1_2
+        wcs->trans->y->coeff[1][0] = wcs->cdelt1 * psMetadataLookupF64 (&status, header, "PC002001"); // == PC2_1
+        wcs->trans->y->coeff[0][1] = wcs->cdelt2 * psMetadataLookupF64 (&status, header, "PC002002"); // == PC2_2
+
+        if (isPoly) {
+            // Elixir-style polynomial terms
+            // XXX currently, Elixir/DVO cannot accept mixed orders
+            for (int i = 0; i <= fitOrder; i++) {
+                for (int j = 0; j <= fitOrder; j++) {
+                    if (i + j < 2)
+                        continue;
+                    if (i + j > fitOrder) {
+                        wcs->trans->x->coeffMask[i][j] = PS_POLY_MASK_SET;
+                        wcs->trans->y->coeffMask[i][j] = PS_POLY_MASK_SET;
+                        continue;
+                    }
+                    sprintf (name, "PCA1X%1dY%1d", i, j);
+                    wcs->trans->x->coeff[i][j] = pow(wcs->cdelt1, i) * pow(wcs->cdelt2, j) * psMetadataLookupF64 (&status, header, name);
+                    sprintf (name, "PCA2X%1dY%1d", i, j);
+                    wcs->trans->y->coeff[i][j] = pow(wcs->cdelt1, i) * pow(wcs->cdelt2, j) * psMetadataLookupF64 (&status, header, name);
+                }
+            }
+        }
+        return wcs;
+    }
+
+    // test the CDi_j varient
+    if (cdKeys) {
+        wcs->trans->x->coeff[1][0] = psMetadataLookupF64 (&status, header, "CD1_1"); // == PC1_1
+        wcs->trans->x->coeff[0][1] = psMetadataLookupF64 (&status, header, "CD1_2"); // == PC1_2
+        wcs->trans->y->coeff[1][0] = psMetadataLookupF64 (&status, header, "CD2_1"); // == PC2_1
+        wcs->trans->y->coeff[0][1] = psMetadataLookupF64 (&status, header, "CD2_2"); // == PC2_2
+        wcs->cdelt1 = hypot (wcs->trans->x->coeff[1][0], wcs->trans->x->coeff[0][1]);
+        wcs->cdelt2 = hypot (wcs->trans->y->coeff[1][0], wcs->trans->y->coeff[0][1]);
+        return wcs;
+    }
+    psLogMsg ("psastro", 2, "warning: missing rotation matrix?\n");
+    psFree (wcs);
+    return NULL;
+}
+
+// convert wcs transformations into header WCS keywords (only handles traditional WCS for the moment)
+// wcs->trans defines the transformation from pixels to degrees.
+// wcs->cdelt1,2 carries the original pixels scale.
+// XXX force PC00i00j to be normalized, or use cdelt1,2 to set the scale?
+// here I've chosen to force the rotation matrix to be normalized
+bool pmAstromWCStoHeader (psMetadata *header, const pmAstromWCS *wcs)
+{
+    char name[16]; // used to store FITS keyword below (always < 8, so 16 should be safe!)
+    char *type;
+
+    if (!wcs) return false;
+
+    type = psProjectTypeToString (wcs->toSky->type, "RA--");
+    psMetadataAddStr (header, PS_LIST_TAIL, "CTYPE1", PS_META_REPLACE, "", type);
+    psFree (type);
+
+    type = psProjectTypeToString (wcs->toSky->type, "DEC-");
+    psMetadataAddStr (header, PS_LIST_TAIL, "CTYPE2", PS_META_REPLACE, "", type);
+    psFree (type);
+
+    psMetadataAddF64 (header, PS_LIST_TAIL, "CRVAL1", PS_META_REPLACE, "", wcs->toSky->R*PS_DEG_RAD);
+    psMetadataAddF64 (header, PS_LIST_TAIL, "CRVAL2", PS_META_REPLACE, "", wcs->toSky->D*PS_DEG_RAD);
+
+    psMetadataAddF64 (header, PS_LIST_TAIL, "CRPIX1", PS_META_REPLACE, "", wcs->crpix1);
+    psMetadataAddF64 (header, PS_LIST_TAIL, "CRPIX2", PS_META_REPLACE, "", wcs->crpix2);
+
+    // XXX make it optional to write out CDi_j terms, or other versions
+    // apply CDELT1,2 (degrees / pixel) to yield PCi,j terms of order unity
+    double cdelt1 = wcs->cdelt1;
+    double cdelt2 = wcs->cdelt2;
+    psMetadataAddF64 (header, PS_LIST_TAIL, "CDELT1", PS_META_REPLACE, "", cdelt1);
+    psMetadataAddF64 (header, PS_LIST_TAIL, "CDELT2", PS_META_REPLACE, "", cdelt2);
+
+    // test the PC00i00j varient:
+    psMetadataAddF64 (header, PS_LIST_TAIL, "PC001001", PS_META_REPLACE, "", wcs->trans->x->coeff[1][0] / cdelt1); // == PC1_1
+    psMetadataAddF64 (header, PS_LIST_TAIL, "PC001002", PS_META_REPLACE, "", wcs->trans->x->coeff[0][1] / cdelt2); // == PC1_2
+    psMetadataAddF64 (header, PS_LIST_TAIL, "PC002001", PS_META_REPLACE, "", wcs->trans->y->coeff[1][0] / cdelt1); // == PC2_1
+    psMetadataAddF64 (header, PS_LIST_TAIL, "PC002002", PS_META_REPLACE, "", wcs->trans->y->coeff[0][1] / cdelt2); // == PC2_2
+
+    // Elixir-style polynomial terms
+    // XXX currently, Elixir/DVO cannot accept mixed orders
+    // XXX need to respect the masks
+    // XXX is wcs->cdelt1,2 always consistent?
+    int fitOrder = wcs->trans->x->nX;
+    if (fitOrder > 1) {
+        for (int i = 0; i <= fitOrder; i++) {
+            for (int j = 0; j <= fitOrder; j++) {
+                if (i + j < 2)
+                    continue;
+                if (i + j > fitOrder)
+                    continue;
+                sprintf (name, "PCA1X%1dY%1d", i, j);
+                psMetadataAddF64 (header, PS_LIST_TAIL, name, PS_META_REPLACE, "", wcs->trans->x->coeff[i][j] / pow(cdelt1, i) / pow(cdelt2, j));
+                sprintf (name, "PCA2X%1dY%1d", i, j);
+                psMetadataAddF64 (header, PS_LIST_TAIL, name, PS_META_REPLACE, "", wcs->trans->y->coeff[i][j] / pow(cdelt1, i) / pow(cdelt2, j));
+            }
+        }
+        psMetadataAddS32 (header, PS_LIST_TAIL, "NPLYTERM", PS_META_REPLACE, "", fitOrder);
+    }
+
+    return true;
+}
+
+// interpret header WCS (only handles traditional WCS for the moment)
+// pixelScale is the pixel size in microns/pixel
+bool pmAstromWCStoFPA (pmFPA *fpa, pmChip *chip, const pmAstromWCS *wcs, double pixelScale)
+{
+    psPlaneTransform *toFPA;
+
+    // create transformation with 0,0 reference pixel and units of degrees/pixel
+    toFPA = psPlaneTransformSetCenter (NULL, wcs->trans, -wcs->crpix1, -wcs->crpix2);
+
+    // modify scale of toFPA to have units of microns/pixel
+    // cdelt1,2 has units of degree/pixel
+    for (int i = 0; i <= toFPA->x->nX; i++) {
+        for (int j = 0; j <= toFPA->x->nX; j++) {
+            toFPA->x->coeff[i][j] *= pixelScale/wcs->cdelt1;
+            toFPA->y->coeff[i][j] *= pixelScale/wcs->cdelt2;
+        }
+    }
+
+    // pdelt1,2 has units of degree/micron
+    double pdelt1 = wcs->cdelt1 / pixelScale;
+    double pdelt2 = wcs->cdelt2 / pixelScale;
+
+    // projection from TPA (linear microns) to SKY (radians)
+    psProjection *toSky = psProjectionAlloc (wcs->toSky->R, wcs->toSky->D, PM_RAD_DEG*pdelt1, PM_RAD_DEG*pdelt2, wcs->toSky->type);
+
+    if (fpa->toSky == NULL) {
+        fpa->toTPA = psPlaneTransformIdentity (1);
+        fpa->fromTPA = psPlaneTransformIdentity (1);
+        fpa->toSky = toSky;
+    } else {
+
+        // this section allows the loaded chip to be included in an fpa structure in which
+        // other chips have already been loaded (ie, the fpa->toTPA, fpa->toSky components have
+        // already been defined).  we have to adjust to match the existing transformation.
+
+        if (fpa->toTPA == NULL)
+            psAbort("projection defined, tangent-plane not defined");
+        if (fpa->fromTPA == NULL)
+            psAbort("projection defined, tangent-plane not defined");
+
+        // convert from pixels on this chip to pixels on reference chip
+        // rX has units of refpixels / pixel
+        double rX = toSky->Xs / fpa->toSky->Xs;
+        double rY = toSky->Ys / fpa->toSky->Ys;
+
+        for (int i = 0; i <= toFPA->x->nX; i++) {
+            for (int j = 0; j <= toFPA->x->nY; j++) {
+                toFPA->x->coeff[i][j] *= rX;
+                toFPA->y->coeff[i][j] *= rY;
+            }
+        }
+
+	// apply the exiting fromTPA transformation to make the new toFPA consistent with the toTPA layter
+	// XXX this only works if toTPA is at most a linear transformation
+        psPlaneTransform *toFPAnew = psPlaneTransformAlloc(toFPA->x->nX, toFPA->x->nY);
+	for (int i = 0; i <= toFPA->x->nX; i++) {
+	  for (int j = 0; j <= toFPA->x->nY; j++) {
+	    double f1 = toFPA->x->coeffMask[i][j] ? 0.0 : fpa->fromTPA->x->coeff[1][0]*toFPA->x->coeff[i][j];
+	    double f2 = toFPA->y->coeffMask[i][j] ? 0.0 : fpa->fromTPA->x->coeff[0][1]*toFPA->y->coeff[i][j];
+	    toFPAnew->x->coeff[i][j] = f1 + f2;
+
+	    double g1 = toFPA->x->coeffMask[i][j] ? 0.0 : fpa->fromTPA->y->coeff[1][0]*toFPA->x->coeff[i][j];
+	    double g2 = toFPA->y->coeffMask[i][j] ? 0.0 : fpa->fromTPA->y->coeff[0][1]*toFPA->y->coeff[i][j];
+	    toFPAnew->y->coeff[i][j] = g1 + g2;
+	  }
+	}
+	toFPAnew->x->coeff[0][0] += fpa->fromTPA->x->coeff[0][0];
+	toFPAnew->y->coeff[0][0] += fpa->fromTPA->y->coeff[0][0];
+
+	psFree (toFPA);
+	toFPA = toFPAnew;
+
+        // adjust reference pixel for new toSky reference coordinate
+        // find the FPA coordinate of 0,0 for this chip.
+        psPlane *fpOld = psPlaneAlloc();
+        psPlane *fpNew = psPlaneAlloc();
+        psPlane *tp = psPlaneAlloc();
+        psSphere *sky = psSphereAlloc();
+
+	sky->r = toSky->R;
+	sky->d = toSky->D;
+        psProject (tp, sky, fpa->toSky); // find the focal-plane coord of this RA,DEC coord using the ref chip projection
+        psPlaneTransformApply (fpOld, fpa->fromTPA, tp);
+
+	sky->r = fpa->toSky->R;
+	sky->d = fpa->toSky->D;
+        psProject (tp, sky, fpa->toSky); // find the focal-plane coord of this RA,DEC coord using the ref chip projection
+        psPlaneTransformApply (fpNew, fpa->fromTPA, tp);
+
+        toFPA->x->coeff[0][0] -= fpNew->x - fpOld->x;
+        toFPA->y->coeff[0][0] -= fpNew->y - fpOld->y;
+
+        psFree (sky);
+        psFree (tp);
+        psFree (fpOld);
+        psFree (fpNew);
+
+        psFree (toSky);
+    }
+
+    // free an existing toFPA structure
+    psFree (chip->toFPA);
+    chip->toFPA = toFPA;
+
+    // determine the inverse transformation: we need the chip pixels covered by this transform
+    psRegion *region = pmChipPixels (chip);
+
+    psFree (chip->fromFPA);
+    chip->fromFPA = psPlaneTransformInvert(NULL, chip->toFPA, *region, 50);
+    psFree (region);
+
+    // this can take a very long time...
+    while (fpa->toSky->R < 0)
+        fpa->toSky->R += 2.0*M_PI;
+    while (fpa->toSky->R > 2.0*M_PI)
+        fpa->toSky->R -= 2.0*M_PI;
+
+    psTrace ("psastro", 5, "toFPA: %f %f  (%f,%f),(%f,%f)\n",
+             chip->toFPA->x->coeff[0][0], chip->toFPA->y->coeff[0][0],
+             chip->toFPA->x->coeff[1][0], chip->toFPA->x->coeff[0][1],
+             chip->toFPA->y->coeff[1][0], chip->toFPA->y->coeff[0][1]);
+
+    psTrace ("psastro", 5, "frFPA: %f %f  (%f,%f),(%f,%f)\n",
+             chip->fromFPA->x->coeff[0][0], chip->fromFPA->y->coeff[0][0],
+             chip->fromFPA->x->coeff[1][0], chip->fromFPA->x->coeff[0][1],
+             chip->fromFPA->y->coeff[1][0], chip->fromFPA->y->coeff[0][1]);
+
+    return true;
+}
+
+// convert a pmAstromWCS structure representing a bilevel chip into corresponding chip elements
+bool pmAstromWCSBileveltoChip (pmChip *chip, const pmAstromWCS *wcs)
+{
+    /* we convert wcs->trans to toFPA, which is different from wcs->trans in 3 important ways:
+     * 1) the output is in pixel (not degrees): divide by cdelt1,2 raised to an appropriate power
+     * 2) X,Y are applied directly, without an applied Xo,Yo offset
+     * 3) there is an allowed Lo,Mo term ([0][0] coefficients)
+     */
+
+    // create transformation with 0,0 reference pixel and units of microns/pixel
+    psFree (chip->toFPA);
+    chip->toFPA = psPlaneTransformSetCenter (NULL, wcs->trans, -wcs->crpix1, -wcs->crpix2);
+
+    // determine the inverse transformation: we need the chip pixels covered by this transform
+    psRegion *region = pmChipPixels (chip);
+
+    psFree (chip->fromFPA);
+    chip->fromFPA = psPlaneTransformInvert(NULL, chip->toFPA, *region, 50);
+    psFree (region);
+
+    return true;
+}
+
+// convert a pmAstromWCS structure representing a bilevel mosaic into corresponding fpa elements
+bool pmAstromWCSBileveltoFPA (pmFPA *fpa, const pmAstromWCS *wcs, psRegion region)
+{
+    // projection from TPA (microns) to SKY (radians)
+    // cdelt1,2 has units of degrees/micron
+    fpa->toSky = psProjectionAlloc (wcs->toSky->R, wcs->toSky->D, wcs->cdelt1*PM_RAD_DEG, wcs->cdelt2*PM_RAD_DEG, wcs->toSky->type);
+
+    // create transformation with 0,0 reference pixel
+    fpa->toTPA = psPlaneTransformSetCenter (NULL, wcs->trans, -wcs->crpix1, -wcs->crpix2);
+
+    // convert fpa->toTPA to units of unity (microns/micron)
+    for (int i = 0; i <= fpa->toTPA->x->nX; i++) {
+        for (int j = 0; j <= fpa->toTPA->x->nY; j++) {
+            fpa->toTPA->x->coeff[i][j] /= wcs->cdelt1;
+            fpa->toTPA->y->coeff[i][j] /= wcs->cdelt2;
+        }
+    }
+
+    // the transformation used the region to define the inversion grid
+    // the region defines the FPA pixels covered by the tranformation
+    psFree (fpa->fromTPA);
+    fpa->fromTPA = psPlaneTransformInvert(NULL, fpa->toTPA, region, 50);
+    return true;
+}
+
+// convert toFPA / toSky components to pmAstromWCS
+// tolerance is allowed error in center solution in pixels
+pmAstromWCS *pmAstromWCSfromFPA (const pmFPA *fpa, const pmChip *chip, double tol)
+{
+    // XXX require chip->toFPA->x->nX == chip->toFPA->x->nY
+    // XXX require chip->toFPA->y->nX == chip->toFPA->y->nY
+    // XXX require chip->toFPA->x->nX == chip->toFPA->y->nX
+    // XXX require chip->toFPA->nX == 1,2,3
+
+    // technically, we can have a plate scale here (fpa->toTPA:dx,dy != 1)
+    // XXX not really: toTPA needs to have unity scale for distortion fitting function
+    // if (!psPlaneTransformIsDiagonal (fpa->toTPA))
+    // psAbort("invalid TPA transformation");
+
+    // create a temporary transform which combines toTPA and toFPA.  allow toTPA to have 0th
+    // and 1st order terms
+    
+    // XXX require fpa->toTPA->x->nX == fpa->toTPA->x->nY
+    // XXX require fpa->toTPA->y->nX == fpa->toTPA->y->nY
+    // XXX require fpa->toTPA->x->nX == fpa->toTPA->y->nX
+    // XXX require fpa->toTPA->x->coeffMask[1][1]
+    // XXX require fpa->toTPA->y->coeffMask[1][1]
+    // XXX require fpa->toTPA->nX == 1
+
+    psPlaneTransform *toTPA = psPlaneTransformAlloc(chip->toFPA->x->nX, chip->toFPA->x->nY);
+
+    for (int i = 0; i <= toTPA->x->nX; i++) {
+	for (int j = 0; j <= toTPA->x->nY; j++) {
+	    double f1 = chip->toFPA->x->coeffMask[i][j] ? 0.0 : fpa->toTPA->x->coeff[1][0]*chip->toFPA->x->coeff[i][j];
+	    double f2 = chip->toFPA->y->coeffMask[i][j] ? 0.0 : fpa->toTPA->x->coeff[0][1]*chip->toFPA->y->coeff[i][j];
+	    toTPA->x->coeff[i][j] = f1 + f2;
+
+	    double g1 = chip->toFPA->x->coeffMask[i][j] ? 0.0 : fpa->toTPA->y->coeff[1][0]*chip->toFPA->x->coeff[i][j];
+	    double g2 = chip->toFPA->y->coeffMask[i][j] ? 0.0 : fpa->toTPA->y->coeff[0][1]*chip->toFPA->y->coeff[i][j];
+	    toTPA->y->coeff[i][j] = g1 + g2;
+	}
+    }
+    toTPA->x->coeff[0][0] += fpa->toTPA->x->coeff[0][0];
+    toTPA->y->coeff[0][0] += fpa->toTPA->y->coeff[0][0];
+
+    pmAstromWCS *wcs = pmAstromWCSAlloc(toTPA->x->nX, toTPA->x->nY);
+
+    // convert projection from FPA to SKY into wcs projection (degrees to radians)
+    wcs->toSky = psProjectionAlloc (fpa->toSky->R, fpa->toSky->D, PM_RAD_DEG, PM_RAD_DEG, fpa->toSky->type);
+    wcs->crval1 = fpa->toSky->R*PS_DEG_RAD;
+    wcs->crval2 = fpa->toSky->D*PS_DEG_RAD;
+
+    // given transformation, solve for coordinates which yields output coordinates of 0,0
+    psPlane *center = psPlaneTransformGetCenter (toTPA, tol);
+
+    // create wcs transform from toFPA, resulting transformation has units of microns/pixel
+    // adjust wcs transform to use center as reference coordinate
+    psPlaneTransformSetCenter (wcs->trans, toTPA, center->x, center->y);
+
+    // calculated center is crpix1,2
+    wcs->crpix1 = center->x;
+    wcs->crpix2 = center->y;
+    psFree (center);
+
+    // pdelt1,2 has units of degrees/micron
+    double pdelt1 = fpa->toSky->Xs * PS_DEG_RAD;
+    double pdelt2 = fpa->toSky->Ys * PS_DEG_RAD;
+
+    // convert wcs->trans to a matrix with units of degrees/pixel
+    for (int i = 0; i <= wcs->trans->x->nX; i++) {
+        for (int j = 0; j <= wcs->trans->x->nY; j++) {
+            wcs->trans->x->coeff[i][j] *= pdelt1;
+            wcs->trans->y->coeff[i][j] *= pdelt2;
+        }
+    }
+
+    // cdelt1,2 has units of degrees/pixel
+    wcs->cdelt1 = hypot (wcs->trans->x->coeff[1][0], wcs->trans->x->coeff[0][1]);
+    wcs->cdelt2 = hypot (wcs->trans->y->coeff[1][0], wcs->trans->y->coeff[0][1]);
+
+    psFree (toTPA);
+
+    return wcs;
+}
+
+/* the bilevel astrometry description consists of a polynomial warping from
+   chip coordinates to FPA coordinates (coords->ctype = LIN---WRP), followed
+   by a polynomial representation of the telescope distortion + the projection
+   (coords->ctype = RA---DIS).
+*/
+
+// convert the chip-level toFPA to a wcs polynomial transformation
+pmAstromWCS *pmAstromWCSBilevelChipFromFPA (const pmChip *chip, double tol)
+{
+    // XXX require chip->toFPA->x->nX == chip->toFPA->x->nY
+    // XXX require chip->toFPA->y->nX == chip->toFPA->y->nY
+    // XXX require chip->toFPA->x->nX == chip->toFPA->y->nX
+    // XXX require chip->toFPA->nX == 1,2,3
+
+    // convert chip->toFPA to wcs format (WRP)
+    pmAstromWCS *wcs = pmAstromWCSAlloc(chip->toFPA->x->nX, chip->toFPA->x->nY);
+
+    // Chip to FPA transformation is a Cartesian 'projection'
+    // reference pixel for FPA is 0.0, 0.0
+    wcs->toSky = psProjectionAlloc (0.0, 0.0, 1.0, 1.0, PS_PROJ_WRP);
+    wcs->crval1 = 0.0;
+    wcs->crval2 = 0.0;
+
+    // given transformation, solve for coordinates which yields output coordinates of 0,0
+    psPlane *center = psPlaneTransformGetCenter (chip->toFPA, tol);
+
+    // adjust wcs transform to use center as reference coordinate
+    // resulting transformation has units of microns/pixel
+    psPlaneTransformSetCenter (wcs->trans, chip->toFPA, center->x, center->y);
+
+    // calculated center is crpix1,2
+    wcs->crpix1 = center->x;
+    wcs->crpix2 = center->y;
+    psFree (center);
+
+    // output coordinates are in microns : CDELT1,2 has units of microns/pixel
+    wcs->cdelt1 = hypot (wcs->trans->x->coeff[1][0], wcs->trans->x->coeff[0][1]);
+    wcs->cdelt2 = hypot (wcs->trans->y->coeff[1][0], wcs->trans->y->coeff[0][1]);
+
+    return wcs;
+}
+
+// convert the fpa-level toTPA, toSky to a wcs polynomial transformation
+pmAstromWCS *pmAstromWCSBilevelMosaicFromFPA (const pmFPA *fpa, double tol)
+{
+    // XXX require fpa->toTPA->x->nX == fpa->toTPA->x->nY
+    // XXX require fpa->toTPA->y->nX == fpa->toTPA->y->nY
+    // XXX require fpa->toTPA->x->nX == fpa->toTPA->y->nX
+    // XXX require fpa->toTPA->nX == 1,2,3
+    // XXX require fpa->toSky->type == PS_PROJ_TAN
+
+    // convert fpa->toTPA + fpa->toSky to wcs format (DIS)
+    pmAstromWCS *wcs = pmAstromWCSAlloc(fpa->toTPA->x->nX, fpa->toTPA->x->nY);
+
+    // convert projection from TPA to SKY into wcs projection (degrees to radians)
+    wcs->toSky = psProjectionAlloc (fpa->toSky->R, fpa->toSky->D, PM_RAD_DEG, PM_RAD_DEG, PS_PROJ_DIS);
+    wcs->crval1 = fpa->toSky->R*PS_DEG_RAD;
+    wcs->crval2 = fpa->toSky->D*PS_DEG_RAD;
+
+    // given transformation, solve for coordinates which yields output coordinates of 0,0
+    psPlane *center = psPlaneTransformGetCenter (fpa->toTPA, tol);
+
+    // adjust wcs transform to use center as reference coordinate
+    // resulting transformation has units of unity (microns/micron)
+    psPlaneTransformSetCenter (wcs->trans, fpa->toTPA, center->x, center->y);
+
+    // calculated center is crpix1,2
+    wcs->crpix1 = center->x;
+    wcs->crpix2 = center->y;
+    psFree (center);
+
+    // pdelt1,2 has units of degrees/micron
+    double pdelt1 = fpa->toSky->Xs * PS_DEG_RAD;
+    double pdelt2 = fpa->toSky->Ys * PS_DEG_RAD;
+
+    // convert wcs->trans to units of degree/micron
+    for (int i = 0; i <= wcs->trans->x->nX; i++) {
+        for (int j = 0; j <= wcs->trans->x->nY; j++) {
+            wcs->trans->x->coeff[i][j] *= pdelt1;
+            wcs->trans->y->coeff[i][j] *= pdelt2;
+        }
+    }
+
+    // cdelt1,2 has units of degrees/micron
+    wcs->cdelt1 = hypot (wcs->trans->x->coeff[1][0], wcs->trans->x->coeff[0][1]);
+    wcs->cdelt2 = hypot (wcs->trans->y->coeff[1][0], wcs->trans->y->coeff[0][1]);
+
+    return wcs;
+}
+
+static void pmAstromWCSFree (pmAstromWCS *wcs)
+{
+
+    if (!wcs)
+        return;
+    psFree (wcs->trans);
+    psFree (wcs->toSky);
+}
+
+pmAstromWCS *pmAstromWCSAlloc (int nXorder, int nYorder)
+{
+
+    pmAstromWCS *wcs = (pmAstromWCS *) psAlloc(sizeof(pmAstromWCS));
+    psMemSetDeallocator(wcs, (psFreeFunc) pmAstromWCSFree);
+
+    wcs->trans = psPlaneTransformAlloc (nXorder, nYorder);
+    wcs->toSky = NULL;
+
+    memset (wcs->ctype1, 0, PM_ASTROM_WCS_TYPE_SIZE);
+    memset (wcs->ctype2, 0, PM_ASTROM_WCS_TYPE_SIZE);
+    return wcs;
+}
+
+/*****
+ 
+For mosaic astrometry, we need to have a starting set of projection terms in which the
+chip-to-FPA terms result in a fixed physical unit on the focal plane (eg, pixels or
+microns).  This set of projections, coupled with an identity toTPA (ie, no distortion) will
+result in substantial errors between the observed and predicted star positions on the focal
+plane: this is the measurement of the optical distortion in the camera.  At the same time,
+we need to carry around the transformations which allow us to make an accurate calculation
+of the position of the stars based on the input (per-chip) astrometry.  These
+transformations will allow us to match the raw and ref stars robustly.  To convert the
+per-chip astrometry (which may have been calculated with a different plate scale for each
+chip) to a collection of astrometry terms for chips in a single mosaic, we need to adjust
+the chip-to-FPA scaling (eg, pc11) to match the variations in the effective plate scale for
+each chip (eg, cdelt1).  Thus, we need to carry around both the
+ 
+*****/
+
+/* discussion of the coord transformations:
+   X,Y: coord on a chip in pixels
+   L,M: coord on the focal plane (pixels)
+   P,Q: coord in the tangent plane (microns or mm?)
+   R,D: coord on the sky 
+ 
+   this function creates WCS terms which convert directly from chip to sky.
+   this function requires a linear, unrotated toTPA distortion term
+   toTPA->x,y->coeff[1][0],[0][1] defines the detector scale (microns / pixel)
+   tpSky->Xs,Ys defines the plate scale (radians / micron)
+*/
+
+/* at this point, we have extracted from the header the WCS terms in the form of a polynomial,
+ * wcs->trans, which will convert X,Y in pixels to L,M in degrees.  we also have the following 
+ * elements defined:
+ * type (projection type)
+ * crval1,2 (in RA,DEC degrees)
+ * crpix1,2 
+ * cdelt1,2 (in degrees / pixel)
+ * pixelScale (microns / pixel)
+ * 
+ * now we convert wcs->trans to toFPA, which is different from wcs->trans in 3 important ways:
+ * 1) the output is in microns (not degrees): divide by cdelt1,2
+ * 2) X,Y are applied directly, without an applied Xo,Yo offset
+ * 3) there is an allowed Lo,Mo term ([0][0] coefficients)
+ */
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryWCS.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryWCS.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/astrom/pmAstrometryWCS.h	(revision 22157)
@@ -0,0 +1,77 @@
+/* @file  pmAstrometryDistortion.h
+ * @brief functions to convert FITS WCS keywords to / from pmFPA structures
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.11 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-03-21 22:00:49 $
+ * Copyright 2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_ASTROMETRY_WCS_H
+#define PM_ASTROMETRY_WCS_H
+
+/// @addtogroup Astrometry
+/// @{
+
+#define PM_ASTROM_WCS_TYPE_SIZE 80
+typedef struct
+{
+    char ctype1[PM_ASTROM_WCS_TYPE_SIZE];
+    char ctype2[PM_ASTROM_WCS_TYPE_SIZE];
+    double crval1, crval2;
+    double crpix1, crpix2;
+    double cdelt1, cdelt2;
+    psProjection *toSky;
+    psPlaneTransform *trans;
+}
+pmAstromWCS;
+
+// support function for the pmAstromWCS representation
+pmAstromWCS *pmAstromWCSAlloc (int nXorder, int nYorder);
+bool pmAstromWCStoSky (psSphere *sky, pmAstromWCS *wcs, psPlane *chip);
+bool pmAstromWCStoChip (psPlane *chip, pmAstromWCS *wcs, psSphere *sky);
+
+// read and write the pmAstromWCS representation to the header
+bool pmAstromWCStoHeader (psMetadata *header, const pmAstromWCS *wcs);
+pmAstromWCS *pmAstromWCSfromHeader (const psMetadata *header);
+
+// convert from wcs terms to chip->toFPA, fpa->toSky,toTPA terms
+bool pmAstromWCSBileveltoChip (pmChip *chip, const pmAstromWCS *wcs);
+bool pmAstromWCSBileveltoFPA (pmFPA *fpa, const pmAstromWCS *wcs, psRegion region);
+
+// convert from chip->toFPA, fpa->toSky,toTPA terms to wcs terms
+pmAstromWCS *pmAstromWCSBilevelChipFromFPA (const pmChip *chip, double tol);
+pmAstromWCS *pmAstromWCSBilevelMosaicFromFPA (const pmFPA *fpa, double tol);
+
+// convert the pmAstromWCS representation to the FPA representation
+bool pmAstromWCStoFPA (pmFPA *fpa, pmChip *chip, const pmAstromWCS *wcs, double plateScale);
+pmAstromWCS *pmAstromWCSfromFPA (const pmFPA *fpa, const pmChip *chip, double tol);
+
+// read wcs terms from the supplied header into the fpa hierarchy components
+bool pmAstromReadWCS (pmFPA *fpa, pmChip *chip, const psMetadata *header, double plateScale);
+
+// write the wcs terms from the fpa hierarchy components into the supplied header
+// tol is the convergence tolerance for the non-linear solution to the reference pixel
+bool pmAstromWriteWCS (psMetadata *header, const pmFPA *fpa, const pmChip *chip, double tol);
+
+bool pmAstromReadBilevelChip (pmChip *chip, const psMetadata *header);
+bool pmAstromReadBilevelMosaic (pmFPA *fpa, const psMetadata *header);
+
+bool pmAstromWriteBilevelChip (psMetadata *header, const pmChip *chip, double tol);
+bool pmAstromWriteBilevelMosaic (psMetadata *header, const pmFPA *fpa, double tol);
+
+// move to pslib
+psPlaneDistort *psPlaneDistortIdentity (int order);
+bool psPlaneDistortIsDiagonal (psPlaneDistort *distort);
+
+# define PM_DEG_RAD 57.295779513082322
+# define PM_RAD_DEG  0.017453292519943
+
+/// @}
+#endif // PM_ASTROMETRY_WCS_H
+
+/*
+ * the wcs->trans component defines a polynomial which converts (x-crpix1),(y-crpix2) to
+ * L,M in degrees
+ */
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/.cvsignore	(revision 22157)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.la
+*.lo
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/Makefile.am	(revision 22157)
@@ -0,0 +1,59 @@
+noinst_LTLIBRARIES = libpsmodulescamera.la
+
+libpsmodulescamera_la_CPPFLAGS = $(SRCINC) $(PSMODULES_CFLAGS)
+libpsmodulescamera_la_LDFLAGS  = -release $(PACKAGE_VERSION)
+libpsmodulescamera_la_SOURCES  = \
+	pmFPA.c \
+	pmFPACalibration.c \
+	pmFPAConstruct.c \
+	pmFPACopy.c \
+	pmFPAHeader.c \
+	pmFPAMaskWeight.c \
+	pmFPAMosaic.c \
+	pmFPARead.c \
+	pmFPAUtils.c \
+	pmFPAWrite.c \
+	pmHDU.c \
+	pmHDUUtils.c \
+	pmHDUGenerate.c \
+	pmFPA_JPEG.c \
+	pmFPAview.c \
+	pmFPAfile.c \
+	pmFPAfileDefine.c \
+	pmFPAfileIO.c \
+	pmFPAfileFitsIO.c \
+	pmFPAFlags.c \
+	pmFPALevel.c \
+	pmFPAExtent.c \
+	pmCellSquish.c \
+	pmReadoutStack.c \
+	pmReadoutFake.c
+
+pkginclude_HEADERS = \
+	pmFPA.h \
+	pmFPACalibration.h \
+	pmFPAConstruct.h \
+	pmFPACopy.h \
+	pmFPAHeader.h \
+	pmFPAMaskWeight.h \
+	pmFPAMosaic.h \
+	pmFPARead.h \
+	pmFPAUtils.h \
+	pmFPAWrite.h \
+	pmHDU.h \
+	pmHDUUtils.h \
+	pmHDUGenerate.h \
+	pmFPA_JPEG.h \
+	pmFPAview.h \
+	pmFPAfile.h \
+	pmFPAfileDefine.h \
+	pmFPAfileIO.h \
+	pmFPAfileFitsIO.h \
+	pmFPAFlags.h \
+	pmFPALevel.h \
+	pmFPAExtent.h \
+	pmCellSquish.h \
+	pmReadoutStack.h \
+	pmReadoutFake.h
+
+CLEANFILES = *~
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmCellSquish.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmCellSquish.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmCellSquish.c	(revision 22157)
@@ -0,0 +1,176 @@
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmShifts.h"
+#include "pmCellSquish.h"
+
+// Comparing values to get ranges
+#define COMPARE_SMALLER(TARGET, SOURCE) if ((SOURCE) < (TARGET)) (TARGET) = (SOURCE);
+#define COMPARE_BIGGER(TARGET, SOURCE) if ((SOURCE) > (TARGET)) (TARGET) = (SOURCE);
+
+
+bool pmCellSquish(pmCell *cell, psMaskType maskVal, bool useShifts)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_ARRAY_NON_NULL(cell->readouts, false);
+    psArray *readouts = cell->readouts; // Array of readouts
+    long numReadouts = readouts->n; // Number of readouts
+
+    if (numReadouts <= 1) {
+        // We squished everything there was to squish
+        return true;
+    }
+
+    pmShifts *shifts = NULL;                   // Orthogonal transfer shifts
+    if (useShifts) {
+        bool mdok;                      // Status of MD lookup
+        shifts = psMetadataLookupPtr(&mdok, cell->analysis, PM_SHIFTS_TABLE_NAME);
+        if (!mdok || !shifts) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "Squishing with shifts requested, but no shifts found.");
+            return false;
+        }
+        if (shifts->num != numReadouts) {
+            psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                    "Number of shifts (%ld) does not match number of readouts (%ld).",
+                    shifts->num, numReadouts);
+            return false;
+        }
+    }
+
+    // First pass to get the bounds, make sure everything is legit.
+    int xMin = 0, xMax = 0, yMin = 0, yMax = 0; // Bounds of the squish
+    bool valid = false;                 // Do we have a valid readout?
+    int col0 = 0, row0 = 0, numCols = 0, numRows = 0;// Window parameters for the readouts
+    int xShift = 0, yShift = 0;         // Shift due to orthogonal transfer, to be applied
+    if (useShifts && shifts->xyRelative) {
+        // Correct for final shift, to put in correct frame for the image that is read out
+        xShift = - shifts->x->data.S32[shifts->num - 1];
+        yShift = - shifts->y->data.S32[shifts->num - 1];
+    }
+    for (long i = 0; i < numReadouts; i++) {
+        // Add in the shift
+        if (useShifts) {
+            if (shifts->xyRelative) {
+                // Need to accumulate shift
+                xShift += shifts->x->data.S32[i];
+                yShift += shifts->y->data.S32[i];
+            } else {
+                // Correct for final shift, to put in correct frame for the image that is read out
+                xShift = shifts->x->data.S32[i] - shifts->x->data.S32[shifts->num - 1];
+                yShift = shifts->y->data.S32[i] - shifts->y->data.S32[shifts->num - 1];
+            }
+        }
+
+        pmReadout *readout = readouts->data[i]; // Readout of interest
+        if (!readout || !readout->image) {
+            continue;
+        }
+
+        if (!valid) {
+            valid = true;
+
+            col0 = readout->col0;
+            row0 = readout->row0;
+            numCols = readout->image->numCols;
+            numRows = readout->image->numRows;
+
+            if (useShifts) {
+                xMin = col0;
+                xMax = col0 + numCols;
+                yMin = row0;
+                yMax = row0 + numRows;
+            }
+        } else {
+            if (readout->col0 != col0 || readout->row0 != row0 ||
+                readout->image->numCols != numCols || readout->image->numRows != numRows) {
+                // Everything should have the same window because we've read it in from an image cube
+                psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                        "Readout window [%d:%d,%d:%d] doesn't match canonical window [%d:%d,%d:%d]",
+                        readout->col0, readout->col0 + readout->image->numCols,
+                        readout->row0, readout->row0 + readout->image->numRows,
+                        col0, col0 + numCols, row0, row0 + numRows);
+                return false;
+            }
+
+            if (useShifts) {
+                // If there is shifting, the actual window may change
+                int xMinTest = readout->col0 + xShift; // Minimum x value
+                int xMaxTest = readout->col0 + readout->image->numCols + xShift; // Maximum x value
+                int yMinTest = readout->row0 + yShift; // Minimum y value
+                int yMaxTest = readout->row0 + readout->image->numRows + yShift; // Maximum y value
+                COMPARE_SMALLER(xMin, xMinTest);
+                COMPARE_BIGGER(xMax, xMaxTest);
+                COMPARE_SMALLER(yMin, yMinTest);
+                COMPARE_BIGGER(yMax, yMaxTest);
+            }
+        }
+    }
+
+    if (useShifts) {
+        // Size of combined image, after shifts applied
+        numCols = xMax - xMin + 1;
+        numRows = yMax - yMin + 1;
+    }
+    psImage *squishImage = psImageAlloc(numCols, numRows, PS_TYPE_F32); // Squished image
+    psImageInit(squishImage, 0.0);
+    psImage *squishMask = psImageAlloc(numCols, numRows, PS_TYPE_MASK); // Squished mask
+    psImageInit(squishMask, 0);
+
+    // Second pass to do the squish
+    xShift = yShift = 0;                // Reset the accumulated shifts
+    if (useShifts && shifts->xyRelative) {
+        // Correct for final shift, to put in correct frame for the image that is read out
+        xShift = - shifts->x->data.S32[shifts->num - 1];
+        yShift = - shifts->y->data.S32[shifts->num - 1];
+    }
+    for (long i = 0; i < numReadouts; i++) {
+        if (useShifts) {
+            if (shifts->xyRelative) {
+                // Need to accumulate shift
+                xShift += shifts->x->data.S32[i];
+                yShift += shifts->y->data.S32[i];
+            } else {
+                // Correct for final shift, to put in correct frame for the image that is read out
+                xShift = shifts->x->data.S32[i] - shifts->x->data.S32[shifts->num - 1];
+                yShift = shifts->y->data.S32[i] - shifts->y->data.S32[shifts->num - 1];
+            }
+        }
+
+        pmReadout *readout = readouts->data[i]; // Readout of interest
+        if (!readout || !readout->image) {
+            continue;
+        }
+
+        int xOffset = xMin - readout->col0; // Offset to squished readout in x
+        int yOffset = yMin - readout->row0; // Offset to squished readout in y
+        if (useShifts) {
+            xOffset += xShift;
+            yOffset += yShift;
+        }
+
+        psImage *image = readout->image; // The image of interest
+        psImage *mask = readout->mask; // The mask of interest
+        for (int y = 0; y < readout->image->numRows; y++) {
+            int ySquish = y + yOffset; // Position on squished readout in y
+            for (int x = 0; x < readout->image->numCols; x++) {
+                int xSquish = x + xOffset; // Position on squished readout in x
+                squishImage->data.F32[ySquish][xSquish] += image->data.F32[y][x];
+                if (mask) {
+                    squishMask->data.PS_TYPE_MASK_DATA[ySquish][xSquish] |= mask->data.U8[y][x] & maskVal;
+                }
+            }
+        }
+    }
+
+    pmCellFreeReadouts(cell);
+    pmReadout *squishRO = pmReadoutAlloc(cell); // New readout to hold squished image
+    squishRO->image = squishImage;
+    squishRO->mask = squishMask;
+    squishRO->row0 = yMin;
+    squishRO->col0 = xMin;
+    psFree(squishRO);               // Drop reference
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmCellSquish.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmCellSquish.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmCellSquish.h	(revision 22157)
@@ -0,0 +1,13 @@
+#ifndef PM_CELL_SQUISH_H
+#define PM_CELL_SQUISH_H
+
+/// Squish (combine all component readouts of) a cell
+///
+/// The component readouts are combined, optionally taking into account orthogonal transfer shifts (assumed to
+/// already have been read) and masks.
+bool pmCellSquish(pmCell *cell,         ///< Cell to have readouts combined
+                  psMaskType maskVal,   ///< Value to be masked
+                  bool useShifts        ///< Use the shifts when squishing?
+    );
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPA.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPA.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPA.c	(revision 22157)
@@ -0,0 +1,459 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <strings.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmConcepts.h"
+#include "pmMaskBadPixels.h"
+
+static void readoutFree(pmReadout *readout)
+{
+    // if this readout has a parent, drop that instance
+    if (readout->parent) {
+        psTrace("psModules.camera", 9, "Removing readout %zd from cell %zd...\n",
+                (size_t)readout, (size_t)readout->parent);
+        psArray *readouts = readout->parent->readouts;
+        for (int i = 0; i < readouts->n; i++) {
+            if (readouts->data[i] == readout) {
+                readouts->data[i] = NULL;
+            }
+        }
+    }
+    psTrace("psModules.camera", 9, "Freeing readout %zd\n", (size_t) readout);
+
+    psFree(readout->image);
+    psFree(readout->mask);
+    psFree(readout->weight);
+    psFree(readout->analysis);
+    psFree(readout->bias);
+}
+
+static void cellFree(pmCell *cell)
+{
+
+    // if this cell has a parent, drop that instance
+    if (cell->parent) {
+        psTrace("psModules.camera", 9, "Removing cell %zd from chip %zd...\n",
+                (size_t)cell, (size_t)cell->parent);
+        psArray *cells = cell->parent->cells;
+        for (int i = 0; i < cells->n; i++) {
+            if (cells->data[i] == cell) {
+                cells->data[i] = NULL;
+            }
+        }
+    }
+    psTrace("psModules.camera", 9, "Freeing cell %zd\n", (size_t)cell);
+    pmCellFreeReadouts(cell);
+    psFree(cell->readouts);
+
+    psFree(cell->concepts);
+    psFree(cell->analysis);
+    psFree(cell->config);
+    psFree(cell->hdu);
+}
+
+static void chipFree(pmChip* chip)
+{
+    // if this chip has a parent, drop that instance
+    if (chip->parent) {
+        psTrace("psModules.camera", 9, "Removing chip %zd from fpa %zd...\n",
+                (size_t)chip, (size_t)chip->parent);
+        psArray *chips = chip->parent->chips;
+        for (int i = 0; i < chips->n; i++) {
+            if (chips->data[i] == chip) {
+                chips->data[i] = NULL;
+            }
+        }
+    }
+
+    psTrace("psModules.camera", 9, "Freeing chip %zd\n", (size_t)chip);
+    pmChipFreeCells(chip);
+    psFree(chip->cells);
+
+    psFree(chip->concepts);
+    psFree(chip->analysis);
+    psFree(chip->hdu);
+
+    psFree(chip->toFPA);
+    psFree(chip->fromFPA);
+}
+
+
+static void FPAFree(pmFPA *fpa)
+{
+    psTrace("psModules.camera", 9, "Freeing fpa %zd\n", (size_t)fpa);
+
+    // NULL the parent pointers
+    psArray *chips = fpa->chips;
+    for (int i = 0 ; i < chips->n ; i++) {
+        pmChip *tmpChip = chips->data[i];
+        if (! tmpChip) {
+            continue;
+        }
+        tmpChip->parent = NULL;
+    }
+    psFree(fpa->chips);
+    psFree(fpa->concepts);
+    psFree(fpa->analysis);
+    psFree((void *)fpa->camera);
+    psFree(fpa->hdu);
+
+    psFree(fpa->fromTPA);
+    psFree(fpa->toTPA);
+    psFree(fpa->toSky);
+}
+
+void pmCellFreeReadouts(pmCell *cell)
+{
+    PS_ASSERT_PTR_NON_NULL(cell,);
+
+    //
+    // Set the parent to NULL in all cell->readouts before psFree(cell->readouts)
+    // in order to avoid memory reference counter problems.
+    //
+    psArray *readouts = cell->readouts;
+    for (psS32 i = 0 ; i < readouts->n ; i++) {
+        pmReadout *tmpReadout = readouts->data[i];
+        if (! tmpReadout) {
+            continue;
+        }
+        tmpReadout->parent = NULL;
+        psTrace("psModules.camera", 9, "Will now free readout %zd...\n", (size_t)tmpReadout);
+    }
+    cell->readouts = psArrayRealloc(cell->readouts, 0);
+    cell->readouts->n = 0;
+}
+
+
+void pmChipFreeCells(pmChip *chip)
+{
+    PS_ASSERT_PTR_NON_NULL(chip,);
+
+    //
+    // Set the parent to NULL in all chip->cells before psFree(chip->cells)
+    // in order to avoid memory reference counter problems.
+    //
+    psArray *cells = chip->cells;
+    for (int i = 0 ; i < cells->n ; i++) {
+        pmCell *tmpCell = cells->data[i];
+        if (! tmpCell) {
+            continue;
+        }
+        tmpCell->parent = NULL;
+        pmCellFreeReadouts(tmpCell);// Drop all readouts the cell holds
+    }
+    chip->cells = psArrayRealloc(chip->cells, 0);
+    chip->cells->n = 0;
+}
+
+void pmReadoutFreeData (pmReadout *readout)
+{
+    if (!readout) {
+        return;
+    }
+
+    psFree(readout->image);
+    psFree(readout->mask);
+    psFree(readout->weight);
+    psFree(readout->bias);
+
+    psTrace("psModules.camera", 3, "Freeing readout data for %zd\n", (size_t) readout);
+
+    readout->image = NULL;
+    readout->weight = NULL;
+    readout->mask = NULL;
+
+    readout->bias = psListAlloc(NULL);
+
+    readout->col0 = 0;
+    readout->row0 = 0;
+    readout->imageScan = 0;
+    readout->maskScan = 0;
+    readout->weightScan = 0;
+}
+
+void pmCellFreeData(pmCell *cell)
+{
+    if (!cell) {
+        return;
+    }
+
+    for (int i = 0; i < cell->readouts->n; i++) {
+        pmReadoutFreeData(cell->readouts->data[i]);
+    }
+    if (cell->hdu) {
+        psFree(cell->hdu->images);
+        psFree(cell->hdu->weights);
+        psFree(cell->hdu->masks);
+        // psFree(cell->hdu->header);
+
+        cell->hdu->images = NULL;
+        cell->hdu->weights = NULL;
+        cell->hdu->masks = NULL;
+        // cell->hdu->header = NULL;
+    }
+}
+
+void pmChipFreeData(pmChip *chip)
+{
+    if (!chip) {
+        return;
+    }
+
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCellFreeData(chip->cells->data[i]);
+    }
+    if (chip->hdu) {
+        psFree(chip->hdu->images);
+        psFree(chip->hdu->weights);
+        psFree(chip->hdu->masks);
+        // psFree(chip->hdu->header);
+
+        chip->hdu->images = NULL;
+        chip->hdu->weights = NULL;
+        chip->hdu->masks = NULL;
+        // chip->hdu->header = NULL;
+    }
+}
+
+void pmFPAFreeData(pmFPA *fpa)
+{
+    if (!fpa) {
+        return;
+    }
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChipFreeData(fpa->chips->data[i]);
+    }
+    if (fpa->hdu) {
+        psFree(fpa->hdu->images);
+        psFree(fpa->hdu->weights);
+        psFree(fpa->hdu->masks);
+        // psFree(fpa->hdu->header);
+
+        fpa->hdu->images = NULL;
+        fpa->hdu->weights = NULL;
+        fpa->hdu->masks = NULL;
+        // fpa->hdu->header = NULL;
+    }
+}
+
+pmReadout *pmReadoutAlloc(pmCell *cell)
+{
+    pmReadout *tmpReadout = (pmReadout *)psAlloc(sizeof(pmReadout));
+    psMemSetDeallocator(tmpReadout, (psFreeFunc) readoutFree);
+
+    tmpReadout->image = NULL;
+    tmpReadout->mask = NULL;
+    tmpReadout->weight = NULL;
+    tmpReadout->bias = psListAlloc(NULL);
+    tmpReadout->analysis = psMetadataAlloc();
+    tmpReadout->parent = cell;
+    if (cell) {
+        cell->readouts = psArrayAdd(cell->readouts, 1, (psPtr) tmpReadout);
+    }
+
+    tmpReadout->process = true;            // All cells are processed by default
+    tmpReadout->file_exists = false;       // file not yet identified
+    tmpReadout->data_exists = false;       // data yet read in
+
+    tmpReadout->row0 = 0;
+    tmpReadout->col0 = 0;
+
+    tmpReadout->imageScan = 0;
+    tmpReadout->maskScan = 0;
+    tmpReadout->weightScan = 0;
+
+    return(tmpReadout);
+}
+
+bool psMemCheckReadout(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) readoutFree);
+}
+
+
+pmCell *pmCellAlloc(
+    pmChip *chip,
+    const char *name)
+{
+    pmCell *tmpCell = (pmCell *) psAlloc(sizeof(pmCell));
+    psMemSetDeallocator(tmpCell, (psFreeFunc) cellFree);
+
+    tmpCell->config = NULL;
+    tmpCell->analysis = psMetadataAlloc();
+    tmpCell->readouts = psArrayAlloc(0);
+    tmpCell->parent = chip;
+    if (chip) {
+        chip->cells = psArrayAdd(chip->cells, 1, (psPtr) tmpCell);
+    }
+    tmpCell->hdu = NULL;
+    tmpCell->process = true;            // All cells are processed by default
+    tmpCell->file_exists = false;       // Not yet identified
+    tmpCell->data_exists = false;       // Not yet read in
+
+    tmpCell->concepts = psMetadataAlloc();
+    if (!psMetadataAddStr(tmpCell->concepts, PS_LIST_HEAD, "CELL.NAME", 0, NULL, name)) {
+        psErrorClear();
+        psWarning("Could not add CELL.NAME to metadata.");
+    }
+    tmpCell->conceptsRead = PM_CONCEPT_SOURCE_NONE;
+    // XXX does this work?  moved to conceptsRead... pmConceptsBlankCell(tmpCell);
+
+    return tmpCell;
+}
+
+bool psMemCheckCell(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) cellFree);
+}
+
+
+pmChip *pmChipAlloc(
+    pmFPA *fpa,
+    const char *name)
+{
+    pmChip *tmpChip = (pmChip*)psAlloc(sizeof(pmChip));
+    psMemSetDeallocator(tmpChip, (psFreeFunc) chipFree);
+
+    tmpChip->toFPA = NULL;
+    tmpChip->fromFPA = NULL;
+
+    tmpChip->analysis = psMetadataAlloc();
+    tmpChip->cells = psArrayAlloc(0);
+    tmpChip->parent = fpa;
+    if (fpa) {
+        psArrayAdd(fpa->chips, 1, (psPtr)tmpChip);
+    }
+    tmpChip->hdu = NULL;
+    tmpChip->process = true;            // Work on all chips, by default
+    tmpChip->file_exists = false;       // Not yet identified
+    tmpChip->data_exists = false;       // Not yet read in
+
+    tmpChip->concepts = psMetadataAlloc();
+    if (!psMetadataAddStr(tmpChip->concepts, PS_LIST_HEAD, "CHIP.NAME", 0, NULL, name)) {
+        psErrorClear();
+        psWarning("Could not add CHIP.NAME %s to concepts.", name);
+    }
+    tmpChip->conceptsRead = PM_CONCEPT_SOURCE_NONE;
+    // XXX does this work?  moved to conceptsRead... pmConceptsBlankChip(tmpChip);
+    return tmpChip;
+}
+
+bool psMemCheckChip(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) chipFree);
+}
+
+pmFPA *pmFPAAlloc(const psMetadata *camera, const char *cameraName)
+{
+    pmFPA *tmpFPA = (pmFPA *) psAlloc(sizeof(pmFPA));
+    psMemSetDeallocator(tmpFPA, (psFreeFunc) FPAFree);
+
+    tmpFPA->fromTPA = NULL;
+    tmpFPA->toTPA = NULL;
+    tmpFPA->toSky = NULL;
+
+    tmpFPA->analysis = psMetadataAlloc();
+    tmpFPA->camera = psMemIncrRefCounter((psPtr) camera);
+    tmpFPA->chips = psArrayAlloc(0);
+    tmpFPA->hdu = NULL;
+
+    tmpFPA->concepts = psMetadataAlloc();
+    if (!psMetadataAddStr(tmpFPA->concepts, PS_LIST_TAIL, "FPA.CAMERA", PS_META_REPLACE,
+                          "Camera name (according to configuration)", cameraName)) {
+        psErrorClear();
+        psWarning("Could not add FPA.CAMERA %s to concepts.", cameraName);
+    }
+    tmpFPA->conceptsRead = PM_CONCEPT_SOURCE_NONE;
+    // XXX does this work?  moved to conceptsRead... pmConceptsBlankFPA(tmpFPA);
+
+    // this may be somewhat pedantic, but it makes these things consistent
+    if (!psMetadataAddStr(tmpFPA->concepts, PS_LIST_TAIL, "FPA.NAME", PS_META_REPLACE,
+                          "name of FPA level", "fpa")) {
+        psErrorClear();
+        psWarning("Could not add FPA.NAME to concepts.");
+    }
+
+    return tmpFPA;
+}
+
+bool psMemCheckFPA(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) FPAFree);
+}
+
+
+// Check a cell to ensure that all component readouts have the parent pointer set correctly
+static bool cellCheckParents(pmCell *cell // Cell to check
+                            )
+{
+    PS_ASSERT_PTR_NON_NULL(cell, true);
+
+    bool flag = true;
+    for (long i = 0; i < cell->readouts->n ; i++) {
+        pmReadout *tmpReadout = (pmReadout *) cell->readouts->data[i];
+        if (!tmpReadout) {
+            continue;
+        }
+        if (tmpReadout->parent != cell) {
+            tmpReadout->parent = cell;
+            flag = false;
+        }
+    }
+    return flag;
+}
+
+// Check a chip to ensure that all component cells have the parent pointer set correctly
+static bool chipCheckParents(pmChip *chip // Chip to check
+                            )
+{
+    PS_ASSERT_PTR_NON_NULL(chip, true);
+
+    bool flag = true;
+    for (long i = 0; i < chip->cells->n ; i++) {
+        pmCell *tmpCell = (pmCell*)chip->cells->data[i];
+        if (!tmpCell) {
+            continue;
+        }
+        if (tmpCell->parent != chip) {
+            tmpCell->parent = chip;
+            flag = false;
+        }
+
+        flag &= cellCheckParents(tmpCell);
+    }
+    return flag;
+}
+
+psBool pmFPACheckParents(pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+
+    bool flag = true;
+    for (long i = 0; i < fpa->chips->n ; i++) {
+        pmChip *tmpChip = (pmChip*)fpa->chips->data[i];
+        if (!tmpChip) {
+            continue;
+        }
+        if (tmpChip->parent != fpa) {
+            tmpChip->parent = fpa;
+            flag = false;
+        }
+
+        flag &= chipCheckParents(tmpChip);
+    }
+    return flag;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPA.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPA.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPA.h	(revision 22157)
@@ -0,0 +1,234 @@
+/* @file pmFPA.h
+ * @brief Defines the focal plane hierarchy, along with functions for interacting with it
+ *
+ * @author George Gusciora, MHPCC
+ * @author Paul Price, IfA
+ * @author Eugene Magnier, IfA
+ *
+ * @version $Revision: 1.22 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-06-05 01:31:33 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_H
+#define PM_FPA_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+// Return chip position, given FPA position; calculations are all done in pixel units
+#define PM_FPA_TO_CHIP(pos, chip0, chipParity) \
+    (((pos) - (chip0))*(chipParity))
+
+// Return cell position, given chip position; calculations are all done in pixel units
+#define PM_CHIP_TO_CELL(pos, cell0, cellParity, binning) \
+    (((pos) - (cell0))*(cellParity)/(binning))
+
+// Return chip position, given a cell position; calculations are all done in pixel units
+#define PM_CELL_TO_CHIP(pos, cell0, cellParity, binning) \
+    ((pos)*(binning)*(cellParity) + (cell0))
+
+// Return FPA position, given a chip position; calculations are all done in pixel units
+#define PM_CHIP_TO_FPA(pos, chip0, chipParity) \
+    ((pos)*(chipParity) + (chip0))
+
+/// Focal plane array (the entirety of the camera)
+///
+/// The FPA is the top-level camera structure, and consists of one or more chips.  It also contains the
+/// concepts metadata appropriate to this level, a summary of analysis tasks that have been performed, the
+/// camera configuration information, any HDU that corresponds to this level for the file of interest, and
+/// astrometric transformations.  The astrometric transformations encode how to transform from the tangent
+/// plane to the sky, and back.
+typedef struct {
+    // Astrometric transformations
+    psPlaneTransform *fromTPA;  ///< Transformation from tangent plane to focal plane, or NULL
+    psPlaneTransform *toTPA;  ///< Transformation from focal plane to tangent plane, or NULL
+    psProjection *toSky;         ///< Projection from tangent plane to sky, or NULL
+    // Information
+    psMetadata *concepts;               ///< FPA-level concepts
+    unsigned int conceptsRead;          ///< Which concepts have been read; see pmConceptsSource
+    psMetadata *analysis;               ///< FPA-level analysis metadata
+    const psMetadata *camera;           ///< Camera configuration
+    psArray *chips;                     ///< The component chips
+    pmHDU *hdu;                         ///< FITS header data unit of interest, or NULL
+} pmFPA;
+
+/// A chip (contiguous detector element)
+///
+/// The chip is the mid-level camera structure, being part of an FPA, and consisting of one or more cells
+/// (e.g., a CCD).  It also contains the concepts metadata appropriate to this level, a summary of analysis
+/// tasks that have been performed, status flags, any HDU that corresponds to this level for the file of
+/// interest, and astrometric transformations.  The astrometric transformations provide transforms between the
+/// chip and FPA coordinates and back.
+typedef struct {
+    // Astrometric transformations
+    psPlaneTransform *toFPA;            ///< Transformation from chip to FPA coordinates, or NULL
+    psPlaneTransform *fromFPA;          ///< Transformation from FPA to chip coordinates, or NULL
+    // Information
+    psMetadata *concepts;               ///< Chip-level concepts
+    unsigned int conceptsRead;          ///< Which concepts have been read; see pmConceptsSource
+    psMetadata *analysis;               ///< Chip-level analysis metadata
+    psArray *cells;                     ///< The component cells
+    pmFPA *parent;                      ///< Parent FPA
+    bool process;                       ///< Do we bother about reading and working with this chip?
+    bool file_exists;                   ///< Does the file for this chip exist (read case only)?
+    bool data_exists;                   ///< Does the data for this chip exist (read case only)?
+    pmHDU *hdu;                         ///< FITS header data unit of interest,
+} pmChip;
+
+/// A cell (smallest logical unit)
+///
+/// A cell is the lowest-level camera structure, being part of a chip (e.g., an amplifier).  It may consist of
+/// one or more readouts, which are individual reads of the cell.  It also contains the concepts metadata
+/// appropriate to this level, the cell configuration information (for convenience) from the camera
+/// configuration, a summary of analysis tasks that have been performed, status flags, and any HDU that
+/// corresponds to this level for the file of interest
+
+/** Cell data structure
+ *
+ *  A cell consists of one or more readouts.  It also contains a pointer to the
+ *  cell's metadata, and its parent chip.  On the astrometry side, it also
+ *  contains coordinate transforms from the cell to chip, from the cell to
+ *  focal-plane, as well as a "quick and dirty" tranform from the cell to
+ *  sky coordinates.
+ *
+ */
+typedef struct {
+    psMetadata *concepts;               ///< Cell-level concepts
+    unsigned int conceptsRead;          ///< Which concepts have been read; see pmConceptsSource
+    psMetadata *config;                 ///< Cell configuration information (from CELLS in the camera config)
+    psMetadata *analysis;               ///< Cell-level analysis metadata
+    psArray *readouts;                  ///< The component readouts
+    pmChip *parent;                     ///< Parent chip
+    bool process;                       ///< Do we bother about reading and working with this cell?
+    bool file_exists;                   ///< Does the file for this cell exist (read case only)?
+    bool data_exists;                   ///< Does the data for this cell exist (read case only)?
+    pmHDU *hdu;                         ///< FITS header data unit of interest
+} pmCell;
+
+/// A readout (individual read of a cell)
+///
+/// A readout corresponds to an individual read of a cell (e.g., a single image as part of a video sequence,
+/// or one of multiple coadds).  It contains the actual pixels used in analysis (along with mask and weight
+/// maps).  When reading from a FITS file, the images are subimages (from CELL.TRIMSEC) of the pixels read
+/// from the appropriate HDU (at the FPA, chip or cell level).  The readout also contains a list of bias
+/// sections (prescans or overscans, or otherwise), a summary of analysis tasks that have been performed,
+/// status flags, and the offsets used for reading a FITS file incrementally.
+typedef struct {
+    int col0;                           ///< Column offset; non-zero if reading in columns incrementally
+    int row0;                           ///< Row offset; non-zero if reading in rows incrementally
+    psImage *image;                     ///< Imaging area of readout (corresponds to CELL.TRIMSEC region)
+    psImage *mask;                      ///< Mask of input image (corresponds to CELL.TRIMSEC region)
+    psImage *weight;                    ///< Weight of input image (corresponds to CELL.TRIMSEC region)
+    psList *bias;                       ///< List of bias (prescan/overscan) images
+    psMetadata *analysis;               ///< Readout-level analysis metadata
+    pmCell *parent;                     ///< Parent cell
+    bool process;                       ///< Do we bother about reading and working with this readout?
+    bool file_exists;                   ///< Does the file for this readout exist (read case only)?
+    bool data_exists;                   ///< Does the data for this readout exist (read case only)?
+    int imageScan, maskScan, weightScan;///< Separate tracking numbers for reading images incrementally
+} pmReadout;
+
+/// Free all readouts within a cell
+void pmCellFreeReadouts(pmCell *cell);    ///< Cell for which to free readouts
+
+/// Free all cells within a chip
+void pmChipFreeCells(pmChip *chip);       ///< Chip for which to free cells
+
+/// Free all data within a readout
+void pmReadoutFreeData(pmReadout *readout); ///< Readout for which to free data
+
+/// Free all data within a cell (all readouts as well as metadata)
+void pmCellFreeData(pmCell *cell);        ///< Cell for which to free data
+
+/// Free all data within a chip (all cells as well as metadata)
+void pmChipFreeData(pmChip *chip);        ///< Chip for which to free data
+
+/// Free all data within an FPA (all chips as well as metadata)
+void pmFPAFreeData(pmFPA *fpa);           ///< FPA for which to free data
+
+/// Allocate a readout associated with a cell
+pmReadout *pmReadoutAlloc(pmCell *cell);  ///< Parent cell, or NULL
+bool psMemCheckReadout(psPtr ptr);
+
+/// Allocate a cell associated with a chip
+///
+/// The name is used to set CELL.NAME within the concepts.
+pmCell *pmCellAlloc(pmChip *chip,       ///< Parent chip, or NULL
+                    const char *name);  ///< Name of cell, for CELL.NAME
+bool psMemCheckCell(psPtr ptr);
+
+
+/// Allocate a chip associated with an FPA
+///
+/// The name is used to set CHIP.NAME within the concepts
+pmChip *pmChipAlloc(pmFPA *fpa,         ///< Parent FPA, or NULL
+                    const char *name);  ///< Name of chip, for CHIP.NAME
+bool psMemCheckChip(psPtr ptr);
+
+
+/// Allocate an FPA
+pmFPA *pmFPAAlloc(const psMetadata *camera, ///< Camera configuration (to store in FPA)
+                  const char *cameraName ///< Name of camera (for FPA.CAMERA concept)
+    );
+bool psMemCheckFPA(psPtr ptr);
+
+/// Check parent links within an FPA
+///
+/// Iterates through the FPA to verify that the "parent" links in the chip, cell and readout are set
+/// correctly.  If there are any incorrect links, they are fixed, and the function returns false.
+bool pmFPACheckParents(pmFPA *fpa);     ///< FPA to check
+
+
+/// Assertions
+
+/// Check that the fundamentals of a readout are set
+#define PM_ASSERT_READOUT_NON_NULL(READOUT, RETVAL) { \
+    if (!(READOUT) || !(READOUT)->bias || !(READOUT)->analysis) { \
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Readout %s or one of its components is NULL.", #READOUT); \
+        return RETVAL; \
+    } \
+    int numCols = 0, numRows = 0; /* Size of readout images */ \
+    psImage *image = (READOUT)->image; /* Image pixels */ \
+    if (image) { \
+        PS_ASSERT_IMAGE_TYPE((READOUT)->image, PS_TYPE_F32, RETVAL); \
+        numCols = image->numCols; \
+        numRows = image->numRows; \
+    } \
+    psImage *mask = (READOUT)->mask; /* Mask pixels */ \
+    if (mask) { \
+        PS_ASSERT_IMAGE_NON_NULL((READOUT)->mask, RETVAL); \
+        if ((numCols != 0 || numRows != 0) && (mask->numCols != numCols || mask->numRows != numRows)) { \
+            psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Mask in readout %s has wrong size (%dx%d vs %dx%d)", \
+                    #READOUT, mask->numCols, mask->numRows, numCols, numRows); \
+            return RETVAL; \
+        } else { \
+            numCols = mask->numCols; \
+            numRows = mask->numRows; \
+        } \
+    } \
+    psImage *weight = (READOUT)->weight; /* Weight map pixels */ \
+    if (weight) { \
+        PS_ASSERT_IMAGE_NON_NULL((READOUT)->weight, RETVAL); \
+        if ((numCols != 0 || numRows != 0) && (weight->numCols != numCols || weight->numRows != numRows)) { \
+            psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Weight in readout %s has wrong size (%dx%d vs %dx%d)", \
+                    #READOUT, weight->numCols, weight->numRows, numCols, numRows); \
+            return RETVAL; \
+        } \
+    } \
+}
+
+/// Assert that a readout contains an image
+#define PM_ASSERT_READOUT_IMAGE(READOUT, RETVAL) \
+    PS_ASSERT_IMAGE_NON_NULL((READOUT)->image, RETVAL);
+
+/// Assert that a readout contains a mask
+#define PM_ASSERT_READOUT_MASK(READOUT, RETVAL) \
+    PS_ASSERT_IMAGE_NON_NULL((READOUT)->mask, RETVAL);
+
+/// Assert that a readout contains a weight map
+#define PM_ASSERT_READOUT_WEIGHT(READOUT, RETVAL) \
+    PS_ASSERT_IMAGE_NON_NULL((READOUT)->weight, RETVAL);
+
+/// @}
+#endif // #ifndef PM_FPA_H
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAAstrometry.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAAstrometry.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAAstrometry.c	(revision 22157)
@@ -0,0 +1,512 @@
+#include <stdio.h>
+#include "pslib.h"
+#include "pmFPAAstrometry.h"
+#include "pmFPA.h"
+
+
+/*****************************************************************************
+checkValidImageCoords(): this is a private function which simply determines if
+the supplied x,y coordinates are in the range for the supplied psImage.
+ 
+XXX: What about col0 and row0
+XXX: This should return a psBool.
+XXX: Macro this for speed.
+ *****************************************************************************/
+static psS32 checkValidImageCoords(
+    double x,
+    double y,
+    psImage* tmpImage)
+{
+    PS_ASSERT_IMAGE_NON_NULL(tmpImage, 0);
+
+    // The FLT_EPSILON is because -0.0 was failing this.
+    if (((x+FLT_EPSILON) < 0.0) || (x > (double)tmpImage->numCols) ||
+            ((y+FLT_EPSILON) < 0.0) || (y > (double)tmpImage->numRows)) {
+        return (0);
+    }
+
+    return (1);
+}
+
+/*****************************************************************************/
+/* FUNCTION IMPLEMENTATION - PUBLIC                                          */
+/*****************************************************************************/
+
+pmCell* pmCellInFPA(
+    const psPlane* fpaCoord,
+    const pmFPA* FPA)
+{
+    PS_ASSERT_PTR_NON_NULL(fpaCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(FPA, NULL);
+
+    pmChip* tmpChip = NULL;
+    psPlane chipCoord;
+    pmCell* outCell = NULL;
+
+    // Determine which chip contains the fpaCoords.
+    tmpChip = pmChipInFPA(fpaCoord, FPA);
+    if (tmpChip == NULL) {
+        return(NULL);
+    }
+
+    // Convert to those chip coordinates.
+    psPlane *rc = pmCoordFPAToChip(&chipCoord, fpaCoord, tmpChip);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine Chip coords.\n");
+        return(NULL);
+    }
+
+    // Determine which cell contains those chip coordinates.
+    outCell = pmCellInChip(&chipCoord, tmpChip);
+    if (outCell == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine the cell.\n");
+        return(NULL);
+    }
+
+    return (outCell);
+}
+
+pmChip* pmChipInFPA(
+    const psPlane* fpaCoord,
+    const pmFPA* FPA)
+{
+    PS_ASSERT_PTR_NON_NULL(fpaCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(FPA, NULL);
+    PS_ASSERT_PTR_NON_NULL(FPA->chips, NULL);
+
+    psArray* chips = FPA->chips;
+    psS32 nChips = chips->n;
+    psPlane chipCoord;
+    pmCell *tmpCell = NULL;
+
+    //
+    // Loop through every chip in this FPA.  Convert the original FPA
+    // coordinates to chip coordinates for that chip.  Then, determine if any
+    // cells in that chip contain those chip coordinates.
+    // XXX: Depending on the number of chips, and their topology, there may be
+    // a much more efficient way of doing this.
+    //
+    for (psS32 i = 0; i < nChips; i++) {
+        pmChip* tmpChip = chips->data[i];
+        PS_ASSERT_PTR_NON_NULL(tmpChip, NULL);
+        PS_ASSERT_PTR_NON_NULL(tmpChip->fromFPA, NULL);
+
+        psPlaneTransformApply(&chipCoord, tmpChip->fromFPA, fpaCoord);
+
+        tmpCell = pmCellInChip(&chipCoord, tmpChip);
+        if (tmpCell != NULL) {
+            return(tmpChip);
+        }
+    }
+
+    psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine the chip.\n");
+    return (NULL);
+}
+
+
+pmCell* pmCellInChip(
+    const psPlane* chipCoord,
+    const pmChip* chip)
+{
+    PS_ASSERT_PTR_NON_NULL(chipCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(chip, NULL);
+
+    psPlane cellCoord;
+    psArray* cells;
+
+    cells = chip->cells;
+    if (cells == NULL) {
+        return NULL;
+    }
+
+    //
+    // We loop over each cell in the chip.  We transform the chipCoord into
+    // a cellCoord for that cell and determine if that cellCoord is valid.
+    // If so, then we return that cell.
+    // XXX: Depending on the number of cells, and their topology, there may be
+    // a much more efficient way of doing this.
+    //
+    for (psS32 i = 0; i < cells->n; i++) {
+        pmCell* tmpCell = (pmCell* ) cells->data[i];
+        PS_ASSERT_PTR_NON_NULL(tmpCell, NULL);
+
+        psPlaneTransform *chipToCell = NULL;
+        if (true ==  p_psIsProjectionLinear(tmpCell->toChip)) {
+            chipToCell = p_psPlaneTransformLinearInvert(tmpCell->toChip);
+        } else {
+            psLogMsg(__func__, PS_LOG_WARN, "WARNING: non-linear cell->chip transforms are not yet implemented.\n");
+            //chipToCell = psPlaneTransformInvert(NULL, tmpCell->toChip, NULL, -1);
+            chipToCell = NULL;
+        }
+        if (chipToCell == NULL) {
+            psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not invert the Cell->toChip transform.\n");
+            return(NULL);
+        }
+        psArray* readouts = tmpCell->readouts;
+
+        if (readouts != NULL) {
+            for (psS32 j = 0; j < readouts->n; j++) {
+                pmReadout* tmpReadout = readouts->data[j];
+                PS_ASSERT_READOUT_NON_NULL(tmpReadout, NULL);
+
+                psPlaneTransformApply(&cellCoord,
+                                      chipToCell,
+                                      chipCoord);
+
+                if (checkValidImageCoords(cellCoord.x,
+                                          cellCoord.y,
+                                          tmpReadout->image)) {
+                    psFree(chipToCell);
+                    return (tmpCell);
+                }
+            }
+        }
+        psFree(chipToCell);
+    }
+
+    //psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine the cell.\n");
+    return (NULL);
+}
+
+
+psPlane* pmCoordCellToFPA(
+    psPlane* fpaCoord,
+    const psPlane* cellCoord,
+    const pmCell* cell)
+{
+    PS_ASSERT_PTR_NON_NULL(cellCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+
+    psPlane *rc = psPlaneTransformApply(fpaCoord, cell->toFPA, cellCoord);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform cell coords to FPA coords.\n");
+    }
+    return(rc);
+}
+
+
+psPlane* pmCoordChipToFPA(
+    psPlane* outCoord,
+    const psPlane* inCoord,
+    const pmChip* chip)
+{
+    PS_ASSERT_PTR_NON_NULL(inCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(chip, NULL);
+
+    psPlane *rc = psPlaneTransformApply(outCoord, chip->toFPA, inCoord);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform chip coords to FPA coords.\n");
+    }
+    return(rc);
+}
+
+
+psPlane* pmCoordFPAToChip(
+    psPlane* chipCoord,
+    const psPlane* fpaCoord,
+    const pmChip* chip)
+{
+    PS_ASSERT_PTR_NON_NULL(fpaCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(chip, NULL);
+    PS_ASSERT_PTR_NON_NULL(chip->fromFPA, NULL);
+
+    psPlane *rc = psPlaneTransformApply(chipCoord, chip->fromFPA, fpaCoord);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform FPA coords to Chip coords.\n");
+    }
+    return(rc);
+}
+
+psPlane* pmCoordCellToChip(
+    psPlane* outCoord,
+    const psPlane* inCoord,
+    const pmCell* cell)
+{
+    PS_ASSERT_PTR_NON_NULL(inCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+
+    psPlane *rc = psPlaneTransformApply(outCoord, cell->toChip, inCoord);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform Cell coords to Chip coords.\n");
+    }
+    return(rc);
+}
+
+psPlane* pmCoordChipToCell(
+    psPlane* cellCoord,
+    const psPlane* chipCoord,
+    const pmCell* cell)
+{
+    PS_ASSERT_PTR_NON_NULL(chipCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->parent, NULL);
+
+    pmCell *tmpCell = pmCellInChip(chipCoord, cell->parent);
+    if (tmpCell == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine the proper cell.\n");
+        return(NULL);
+    }
+
+    psPlaneTransform *tmpChipToCell = NULL;
+    PS_ASSERT_PTR_NON_NULL(tmpCell->toChip, NULL);
+    if (true ==  p_psIsProjectionLinear(tmpCell->toChip)) {
+        tmpChipToCell = p_psPlaneTransformLinearInvert(tmpCell->toChip);
+    } else {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: non-linear cell->chip transforms are not yet implemented.\n");
+        // XXX: tmpChipToCell = psPlaneTransformInvert(NULL, tmpCell->toChip, NULL, -1);
+        tmpChipToCell = NULL;
+    }
+    if (tmpChipToCell == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not invert the Cell->toChip transform.\n");
+        return(NULL);
+    }
+
+    psPlane *rc = psPlaneTransformApply(cellCoord, tmpChipToCell, chipCoord);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform Chip coords to Cell coords.\n");
+    }
+    psFree(tmpChipToCell);
+    return(rc);
+}
+
+psPlane* pmCoordFPAToTP(
+    psPlane* outCoord,
+    const psPlane* inCoord,
+    double color,
+    double magnitude,
+    const pmFPA* fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(inCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    psPlane *rc = psPlaneDistortApply(outCoord, fpa->toTangentPlane, inCoord, color, magnitude);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform FPA coords to tangent plane coords.\n");
+    }
+    return(rc);
+}
+
+psPlane* pmCoordTPToFPA(
+    psPlane* fpaCoord,
+    const psPlane* tpCoord,
+    double color,
+    double magnitude,
+    const pmFPA* fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(tpCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa->fromTangentPlane, NULL);
+
+    psPlane *rc = psPlaneDistortApply(fpaCoord, fpa->fromTangentPlane, tpCoord, color, magnitude);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform tangent plane coords to FPA coords.\n");
+    }
+    return(rc);
+}
+
+
+/*****************************************************************************
+XXXDeproject(outSphere, coord, projection): This private routine is a wrapper
+for p_psDeproject().  The reason: p_psDeproject() and p_psProject() combined
+do not seem to produce the original coordinates when they even though they
+should.  XXXDeproject() simply negates the ->r and ->d members of the output
+psSphere if the input ->y is larger than 0.0.  I don't know why it works.
+ 
+I'm guessing the p_psProject() and p_psDeproject() functions have bugs.
+ 
+XXX: It appears that p_psProject() and p_psDeproject() have been fixed.
+Remove this.
+ *****************************************************************************/
+psSphere* XXXDeproject(
+    psSphere *outSphere,
+    const psPlane* coord,
+    const psProjection* projection)
+{
+    psSphere *rc = p_psDeproject(outSphere, coord, projection);
+
+    if (coord->y >= 0.0) {
+        rc->d = -rc->d;
+        rc->r = -rc->r;
+    }
+
+    return(rc);
+}
+
+/*****************************************************************************
+  *****************************************************************************/
+psSphere* pmCoordTPToSky(
+    psSphere* outSphere,
+    const psPlane* tpCoord,
+    const psProjection *projection)
+{
+    PS_ASSERT_PTR_NON_NULL(tpCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(projection, NULL);
+
+    //    psSphere *rc = XXXDeproject(outSphere, tpCoord, projection);
+    psSphere *rc = p_psDeproject(outSphere, tpCoord, projection);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform tangent plane coords to sky coords.\n");
+    }
+    return(rc);
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+psPlane* pmCoordSkyToTP(
+    psPlane* tpCoord,
+    const psSphere* in,
+    const psProjection *projection)
+{
+    PS_ASSERT_PTR_NON_NULL(in, NULL);
+    PS_ASSERT_PTR_NON_NULL(projection, NULL);
+
+    psPlane *rc = p_psProject(tpCoord, in, projection);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform sky to tangent plane coords.\n");
+    }
+    return(rc);
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+psSphere* pmCoordCellToSky(
+    psSphere* skyCoord,
+    const psPlane* cellCoord,
+    double color,
+    double magnitude,
+    const pmCell* cell)
+{
+    PS_ASSERT_PTR_NON_NULL(cellCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->toFPA, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->parent, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->parent->parent, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->parent->parent->toTangentPlane, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->parent->parent->projection, NULL);
+    psPlane fpaCoord;
+    psPlane tpCoord;
+    psPlane *rc;
+    pmFPA* parFPA = (cell->parent)->parent;
+
+    // Convert the input cell coordinates to FPA coordinates.
+    rc = psPlaneTransformApply(&fpaCoord, cell->toFPA, cellCoord);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could transform cell coords to FPA coords.\n");
+        return(NULL);
+    }
+
+    // Convert the FPA coordinates to tangent plane Coordinates.
+    rc = psPlaneDistortApply(&tpCoord, parFPA->toTangentPlane, &fpaCoord, color, magnitude);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could transform FPA coords to tangent plane coords.\n");
+        return(NULL);
+    }
+
+    // Convert the tangent plane Coordinates to sky coordinates.
+    psSphere *rc2 = pmCoordTPToSky(skyCoord, &tpCoord, parFPA->projection);
+    if (rc2 == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform cell coords to sky coords.\n");
+    }
+
+    return(rc2);
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+psPlane* pmCoordSkyToCell(
+    psPlane* cellCoord,
+    const psSphere* skyCoord,
+    float color,
+    float magnitude,
+    const pmCell* cell)
+{
+    PS_ASSERT_PTR_NON_NULL(skyCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->parent, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->parent->parent, NULL);
+    pmChip *parChip = cell->parent;
+    pmFPA *parFPA = parChip->parent;
+    psPlane tpCoord;
+    psPlane fpaCoord;
+    psPlane chipCoord;
+    psPlane *rc;
+
+    // Convert the skyCoords to tangent plane coords.
+    rc = pmCoordSkyToTP(&tpCoord, skyCoord, parFPA->projection);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine tangent plane coords.\n");
+        return(NULL);
+    }
+
+    // Convert the tangent plane coords to FPA coords.
+    rc = pmCoordTPToFPA(&fpaCoord, &tpCoord, color, magnitude, parFPA);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine FPA coords.\n");
+        return(NULL);
+    }
+
+    // Convert the FPA coords to chip coords.
+    rc = pmCoordFPAToChip(&chipCoord, &fpaCoord, parChip);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine chip coords.\n");
+        return(NULL);
+    }
+
+    // Convert the chip coords to cell coords.
+    rc = pmCoordChipToCell(cellCoord, &chipCoord, cell);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine cell coords.\n");
+        return(NULL);
+    }
+
+    return (cellCoord);
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+psSphere* pmCoordCellToSkyQuick(
+    psSphere* outSphere,
+    const psPlane* cellCoord,
+    const pmCell* cell)
+{
+    PS_ASSERT_PTR_NON_NULL(cellCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->toSky, NULL);
+    psPlane outPlane;
+    psPlane *rc;
+    rc = psPlaneTransformApply(&outPlane, cell->toSky, cellCoord);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could transform cell coords to sky coords.\n");
+        return(NULL);
+    }
+
+    psSphere *out = outSphere;
+    if (out == NULL) {
+        out = psSphereAlloc();
+    }
+    out->r = outPlane.y;
+    out->d = outPlane.x;
+
+    return(out);
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+psPlane* pmCoordSkyToCellQuick(
+    psPlane* cellCoord,
+    const psSphere* skyCoord,
+    const pmCell* cell)
+{
+    PS_ASSERT_PTR_NON_NULL(skyCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->toSky, NULL);
+    psPlane skyPlane;
+    skyPlane.y = skyCoord->r;
+    skyPlane.x = skyCoord->d;
+
+    psPlane *rc = psPlaneTransformApply(cellCoord, cell->toSky, &skyPlane);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform sky to cell coords.\n");
+    }
+    return(cellCoord);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAAstrometry.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAAstrometry.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAAstrometry.h	(revision 22157)
@@ -0,0 +1,188 @@
+#ifndef PM_FPA_ASTROMETRY_H
+#define PM_FPA_ASTROMETRY_H
+
+#include "pslib.h"
+#include "pmFPA.h"
+
+/** Find cooresponding cell for given FPA coordinate
+ *
+ *  @return pmCell*    the cell cooresponding to the coord in FPA
+ */
+pmCell* pmCellInFPA(
+    const psPlane* coord,              ///< the coordinate in FPA plane
+    const pmFPA* FPA                   ///< the FPA to search for the cell
+);
+
+
+/** Find cooresponding chip for given FPA coordinate
+ *
+ *  @return pmChip*    the chip cooresponding to coord
+ */
+pmChip* pmChipInFPA(
+    const psPlane* coord,              ///< the coordinate in FPA plane
+    const pmFPA* FPA                   ///< the FPA to search for the cell
+);
+
+
+/** Find cooresponding cell for given Chip coordinate
+ *
+ *  @return pmCell*    the cell cooresponding to coord
+ */
+pmCell* pmCellInChip(
+    const psPlane* coord,              ///< the coordinate in Chip plane
+    const pmChip* chip                 ///< the chip to search for the cell
+);
+
+
+/** Translate a cell coordinate into a chip coordinate
+ *
+ *  @return psPlane*    the resulting chip coordinate
+ */
+psPlane* pmCoordCellToChip(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the coordinate within Cell
+    const pmCell* cell                 ///< the Cell in interest
+);
+
+
+/** Translate a chip coordinate into a FPA coordinate
+ *
+ *  @return psPlane*    the resulting FPA coordinate
+ */
+psPlane* pmCoordChipToFPA(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the coordinate within Chip
+    const pmChip* chip                 ///< the chip in interest
+);
+
+
+/** Translate a FPA coordinate into a Tangent Plane coordinate
+ *
+ *  @return psPlane*    the resulting Tangent Plane coordinate
+ */
+psPlane* pmCoordFPAToTP(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the coordinate within FPA
+    double color,                      ///< Color of source
+    double magnitude,                  ///< Magnitude of source
+    const pmFPA* fpa                   ///< the FPA in interest
+);
+
+
+/** Translate a Tangent Plane coordinate into a Sky coordinate
+ *
+ *  @return psSphere*    the resulting Sky coordinate
+ */
+psSphere* pmCoordTPToSky(
+    psSphere* out,                     ///< a sphere struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                ///< the coordinate within Tangent Plane
+    const psProjection *projection
+);
+
+/** Translate a cell coordinate into a FPA coordinate
+ *
+ *  @return psPlane*    the resulting FPA coordinate
+ */
+psPlane* pmCoordCellToFPA(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the coordinate within cell
+    const pmCell* cell                 ///< the cell in interest
+);
+
+
+/** Translate a cell coordinate into a Sky coordinate
+ *
+ *  @return psSphere*    the resulting Sky coordinate
+ */
+psSphere* pmCoordCellToSky(
+    psSphere* out,                     ///< a sphere struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the coordinate within cell
+    double color,                      ///< Color of source
+    double magnitude,                  ///< Magnitude of source
+    const pmCell* cell                 ///< the cell in interest
+);
+
+
+/** Translate a cell coordinate into a Sky coordinate using a 'quick and
+ *  dirty' method
+ *
+ *  @return psSphere*    the resulting Sky coordinate
+ */
+psSphere* pmCoordCellToSkyQuick(
+    psSphere* out,                     ///< a sphere struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the coordinate within cell
+    const pmCell* cell                 ///< the cell in interest
+);
+
+
+/** Translate a Sky coordinate into a Tangent Plane coordinate
+ *
+ *  @return psPlane*    the resulting Tangent Plane coordinate
+ */
+psPlane* pmCoordSkyToTP(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psSphere* in,                ///< the sky coordinate
+    const psProjection *projection
+);
+
+/** Translate a Tangent Plane coordinate into a FPA coordinate
+ *
+ *  @return psPlane*    the resulting FPA coordinate
+ */
+psPlane* pmCoordTPToFPA(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the coordinate within tangent plane
+    double color,                      ///< Color of source
+    double magnitude,                  ///< Magnitude of source
+    const pmFPA* fpa                   ///< the FPA of interest
+);
+
+
+/** Translate a FPA coordinate into a chip coordinate
+ *
+ *  @return psPlane*    the resulting chip coordinate
+ */
+psPlane* pmCoordFPAToChip(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the FPA coordinate
+    const pmChip* chip                 ///< the chip of interest
+);
+
+
+/** Translate a chip coordinate into a cell coordinate
+ *
+ *  @return psPlane*    the resulting cell coordinate
+ */
+psPlane* pmCoordChipToCell(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the Chip coordinate
+    const pmCell* cell                 ///< the cell of interest
+);
+
+
+/** Translate a sky coordinate into a cell coordinate
+ *
+ *  @return psPlane*    the resulting cell coordinate
+ */
+psPlane* pmCoordSkyToCell(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psSphere* in,                ///< the Sky coordinate
+    float color,                       ///< Color of source
+    float magnitude,                   ///< Magnitude of source
+    const pmCell* cell                 ///< the cell of interest
+);
+
+
+/** Translate a sky coordinate into a cell coordinate using a 'quick and
+ *  dirty' method
+ *
+ *  @return psPlane*    the resulting cell coordinate
+ */
+psPlane* pmCoordSkyToCellQuick(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psSphere* in,                ///< the Sky coordinate
+    const pmCell* cell                 ///< the cell of interest
+);
+
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPACalibration.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPACalibration.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPACalibration.c	(revision 22157)
@@ -0,0 +1,63 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+
+#include "pmFPACalibration.h"
+
+float pmFPADarkNorm(const pmFPA *fpa, const pmFPAview *view, float expTime)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, NAN);
+    PS_ASSERT_PTR_NON_NULL(view, NAN);
+
+    psMetadata *darkNorms = psMetadataLookupMetadata(NULL, fpa->camera, "DARK.NORM"); // Dark normalisations
+    if (!darkNorms) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to find DARK.NORM in camera configuration.");
+        return NAN;
+    }
+
+    const char *key = psMetadataLookupStr(NULL, fpa->camera, "DARK.NORM.KEY"); // Key for normalisation
+    if (!key || strlen(key) == 0) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to find DARK.NORM.KEY in camera configuration.");
+        return NAN;
+    }
+
+    psString keyResolved = pmFPANameFromRule(key, fpa, view); // Resolved version
+    if (!keyResolved || strlen(keyResolved) == 0) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to resolve DARK.NORM.KEY: %s", key);
+        return NAN;
+    }
+
+    psMetadata *polyMD = psMetadataLookupMetadata(NULL, darkNorms, keyResolved); // Metadata with polynomial
+    if (!polyMD) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to find %s polynomial in the DARK.NORM metadata", keyResolved);
+        psFree(keyResolved);
+        return NAN;
+    }
+
+    psPolynomial1D *poly = psPolynomial1DfromMetadata(polyMD); // Polynomial
+    if (!poly) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to determine polynomial from %s in the DARK.NORM metadata",
+                keyResolved);
+        psFree(keyResolved);
+        return NAN;
+    }
+    psFree(keyResolved);
+
+    float value = psPolynomial1DEval(poly, expTime);
+
+    psFree(poly);
+
+    return value;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPACalibration.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPACalibration.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPACalibration.h	(revision 22157)
@@ -0,0 +1,17 @@
+#ifndef PM_FPA_CALIBRATION_H
+#define PM_FPA_CALIBRATION_H
+
+/// Return the dark normalisation value
+///
+/// Unfortunately, dark current is not linear with the exposure time, but application of a polynomial
+/// correction to the exposure time should make it linear.  This function returns the appropriate value with
+/// which to normalise a dark frame.  The polynomial is obtained from DARK.NORM in the camera configuration.
+/// The specific polynomial metadata to use is provided by DARK.NORM.KEY, which is keyword expanded in the
+/// usual manner (e.g., try "{CHIP.NAME}").
+float pmFPADarkNorm(const pmFPA *fpa,   ///< FPA for which to get the normalisation
+                    const pmFPAview *view, ///< View to the FPA component of interest
+                    float expTime       ///< The nominal exposure time
+    );
+
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAConstruct.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAConstruct.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAConstruct.c	(revision 22157)
@@ -0,0 +1,1577 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <strings.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAFlags.h"
+#include "pmConcepts.h"
+#include "pmFPAConstruct.h"
+#include "pmFPAUtils.h"
+#include "pmHDUUtils.h"
+
+#define TABLE_OF_CONTENTS "CONTENTS"    // Name for camera format metadata containing the contents
+#define CHIP_TYPES "CHIPS"              // Name for camera format metadata containing the chip types
+#define CELL_TYPES "CELLS"              // Name for camera format metadata containing the cell types
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Read data for a particular cell from the camera format description
+static psMetadata *getCellData(const psMetadata *format, // The camera format description
+                               const char *cellName // The name of the cell
+                              )
+{
+    assert(format);
+    assert(cellName && strlen(cellName) > 0);
+
+    bool status = true;                 // Result of MD lookup
+    psMetadata *cells = psMetadataLookupMetadata(&status, format, CELL_TYPES); // The CELLS
+    if (!status || !cells) {
+        psError(PS_ERR_IO, true, "Unable to find %s in camera format.\n", CELL_TYPES);
+        return NULL;
+    }
+
+    psMetadata *cellData = psMetadataLookupMetadata(&status, cells, cellName); // The data for the particular cell
+    if (!status || !cellData) {
+        psWarning("Unable to find specs for cell %s: ignored\n", cellName);
+    }
+
+    return cellData;
+}
+
+// Parse a list of first:second:third pairs in a string
+static int parseContent(psArray **first, // Array of the first values
+                         psArray **second, // Array of the second values
+                         psArray **third, // Array of the third values
+                         const char *string // The string to parse
+                        )
+{
+    assert(string && strlen(string) > 0);
+    // Must populate 'first', 'second', 'third' in order.
+    assert(!second || first);
+    assert(!third || second);
+
+    int numArrays = third ? 3 : (second ? 2 : 1); // Number of arrays
+
+    psList *values = psStringSplit(string, " ,;", true); // List of the parts
+    int num = values->n; // number of parsed content elements
+
+    // extend the arrays if they exist, create new ones if they don't
+    if (first && !*first) {
+        *first = psArrayAllocEmpty(values->n);
+    }
+    if (second && !*second) {
+        *second = psArrayAllocEmpty(values->n);
+    }
+    if (third && !*third) {
+        *third = psArrayAllocEmpty(values->n);
+    }
+
+    psListIterator *valuesIter = psListIteratorAlloc(values, PS_LIST_HEAD, false); // Iterator for values
+    psString value = NULL;               // "first:second:third" string
+    while ((value = psListGetAndIncrement(valuesIter))) {
+        psArray *fst = psStringSplitArray(value, ":", true); // First, second, third
+        switch (numArrays) {
+          case 3:
+            psArrayAdd(*third, 8, fst->data[2]);
+          case 2:
+            psArrayAdd(*second, 8, fst->data[1]);
+          case 1:
+            psArrayAdd(*first, 8, fst->data[0]);
+            break;
+        default:
+          psAbort("Should never get here.");
+        }
+        psFree(fst);
+    }
+    psFree(valuesIter);
+    psFree(values);
+
+    return num;
+}
+
+// Get the name of a PHU chip or cell from the header
+static psString phuNameFromHeader(const char *name, // The name to lookup: "CELL.NAME" or "CHIP.NAME"
+                                  const psMetadata *fileInfo, // FILE within the camera format description
+                                  const psMetadata *header // Primary header
+                                 )
+{
+    assert(name && strlen(name) > 0);
+    assert(fileInfo);
+    assert(header);
+
+    bool mdok = true;                   // Result of MD lookup
+    psString keyword = psMetadataLookupStr(&mdok, fileInfo, name);
+    if (!mdok || strlen(keyword) == 0) {
+        return false;
+    }
+    psMetadataItem *resultItem = psMetadataLookup(header, keyword);
+    if (!resultItem) {
+        psError(PS_ERR_IO, true, "Unable to find %s in primary header to identify %s.\n", keyword, name);
+        return NULL;
+    }
+    return psMetadataItemParseString(resultItem);
+}
+
+// Add an HDU to the FPA
+static bool addHDUtoFPA(pmFPA *fpa,     // FPA to which to add
+                        pmHDU *hdu      // HDU to be added
+                       )
+{
+    assert(fpa);
+    assert(hdu);
+
+    if (fpa->hdu) {
+        // Something's already here
+        if (fpa->hdu != hdu) {
+            psError(PS_ERR_IO, true, "Unable to add HDU since FPA already has one.\n");
+        }
+        return false;
+    }
+    fpa->hdu = psMemIncrRefCounter(hdu);
+    pmFPASetFileStatus(fpa, true);
+
+    return true;
+}
+
+// Add an HDU to the chip
+static bool addHDUtoChip(pmChip *chip,  // Chip to which to add
+                         pmHDU *hdu     // HDU to be added
+                        )
+{
+    assert(chip);
+    assert(hdu);
+
+    if (chip->hdu) {
+        // Something's already here
+        if (chip->hdu != hdu) {
+            psError(PS_ERR_IO, true, "Unable to add HDU since chip already has one.\n");
+        }
+        return false;
+    }
+    chip->hdu = psMemIncrRefCounter(hdu);
+    pmChipSetFileStatus(chip, true);
+
+    return true;
+}
+
+// Add an HDU to the cell
+static bool addHDUtoCell(pmCell *cell,  // Cell to which to add
+                         pmHDU *hdu     // HDU to be added
+                        )
+{
+    assert(cell);
+    assert(hdu);
+
+    if (cell->hdu) {
+        // Something's already here
+        if (cell->hdu != hdu) {
+            psError(PS_ERR_IO, true, "Unable to add HDU since cell already has one.\n");
+        }
+        return false;
+    }
+    cell->hdu = psMemIncrRefCounter(hdu);
+    pmCellSetFileStatus(cell, true);
+
+    return true;
+}
+
+
+// Looks up the particular content, based on the chip and cell
+static const char *getContent(const psMetadata *fileInfo, // The FILE from the camera format configuration
+                              const psMetadata *header, // The FITS header
+                              const psMetadata *contents // The CONTENTS from the camera format configuration
+                             )
+{
+    assert(fileInfo);
+    assert(contents);
+    assert(header);
+
+    const char *contentHeader = psMetadataLookupStr(NULL, fileInfo, "CONTENT"); // Keyword to get contents
+    if (!contentHeader || strlen(contentHeader) == 0) {
+        psError(PS_ERR_UNEXPECTED_NULL, false,
+                "Unable to find CONTENT in FILE within camera format configuration.\n");
+        return NULL;
+    }
+
+    psMetadataItem *contentKey = psMetadataLookup(header, contentHeader); // Key to CONTENTS menu
+    if (!contentKey) {
+        psError(PS_ERR_UNEXPECTED_NULL, false,
+                "Unable to find %s in header to determine file content.", contentHeader);
+        return NULL;
+    }
+
+    psString contentKeyStr = psMetadataItemParseString(contentKey); // Key, as a string
+
+    psTrace("psModules.camera", 5, "Looking up %s in the CONTENTS.\n", contentKeyStr);
+    const char *content = psMetadataLookupStr(NULL, contents, contentKeyStr);
+    if (!content || strlen(content) == 0) {
+        psError(PS_ERR_IO, false, "Unable to find %s in the CONTENTS.\n", contentKeyStr);
+        return NULL;
+    }
+
+    psFree(contentKeyStr);
+
+    return content;
+}
+
+
+// Given a list of contents, put the HDU in the correct place and plug in the cell configuration information
+static bool processContents(pmFPA *fpa,  // The FPA
+                            pmChip *chip, // The chip
+                            pmHDU *hdu,  // The HDU to be added
+                            pmFPALevel level, // The level at which to add the HDU
+                            psArray *chipNames, // The chip names
+                            psArray *cellNames, // The cell names
+                            psArray *cellTypes, // The cell types
+                            const psMetadata *format // Camera format configuration
+                            )
+{
+    assert(fpa);
+    assert(cellTypes);
+    long num = cellTypes->n;            // Number of entries to add
+    assert(chip || (chipNames && chipNames->n == num));
+    assert(cellNames && cellNames->n == num);
+    assert(format);
+
+    if (hdu && level == PM_FPA_LEVEL_FPA) {
+        if (!addHDUtoFPA(fpa, hdu)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to add HDU to FPA");
+            return false;
+        }
+    }
+    // Load fpa-related concepts
+    if (!pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_DEFAULTS, false, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read concepts from camera and defaults for fpa\n");
+        return false;
+    }
+
+    for (int i = 0; i < num; i++) {
+        psString cellType = cellTypes->data[i]; // The type of the cell
+
+        // Find the chip
+        pmChip *newChip;                // Chip of interest
+        if (chip) {
+            newChip = chip;
+        } else {
+            psString chipName = chipNames->data[i]; // The name of the chip
+            int chipNum = pmFPAFindChip(fpa, chipName); // The chip we're looking for
+            if (chipNum == -1) {
+                psError(PS_ERR_LOCATION_INVALID, false,
+                        "Unable to find chip %s in fpa --- ignored.\n", chipName);
+                return false;
+            }
+            newChip = fpa->chips->data[chipNum];
+        }
+
+        // Put in the HDU
+        if (hdu && level == PM_FPA_LEVEL_CHIP) {
+            addHDUtoChip(newChip, hdu);
+        }
+        // Load chip-related concepts
+        if (!pmConceptsReadChip(newChip, PM_CONCEPT_SOURCE_DEFAULTS, false, false, NULL)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to read concepts from camera and defaults for chip\n");
+            return false;
+        }
+
+        // Find the cell
+        pmCell *newCell;                // Cell of interest
+        psString cellName = cellNames->data[i]; // The name of the cell
+        int cellNum = pmChipFindCell(newChip, cellName); // The cell we're looking for
+        if (cellNum == -1) {
+            psError(PS_ERR_LOCATION_INVALID, false, "Unable to find cell %s in chip --- ignored.\n",
+                    cellName);
+            return false;
+        }
+        newCell = newChip->cells->data[cellNum];
+
+        psMetadata *cellData = getCellData(format, cellType); // Data for this cell
+
+        if (hdu && level == PM_FPA_LEVEL_CELL) {
+            addHDUtoCell(newCell, hdu);
+        }
+
+        // Put in the cell data
+        if (newCell->config) {
+            psWarning("Overwriting cell data in chip\n");
+            psFree(newCell->config); // Make way!
+        }
+        newCell->config = psMemIncrRefCounter(cellData);
+        if (!pmConceptsReadCell(newCell, PM_CONCEPT_SOURCE_CELLS | PM_CONCEPT_SOURCE_DEFAULTS,
+                                false, NULL)) {
+            psError(PS_ERR_UNKNOWN, false,
+                    "Unable to read concepts from camera and defaults for cell type %s", cellType);
+            return false;
+        }
+    }
+
+    return true;
+}
+
+#if 0
+// Return the level at which EXTENSIONS go, from the FILE metadata within the camera format
+static pmFPALevel hduLevel(const psMetadata *format // The camera format configuration
+                          )
+{
+    assert(format);
+
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *file = psMetadataLookupMetadata(&mdok, format, "FILE"); // File information
+    if (!mdok || !file) {
+        psError(PS_ERR_IO, true, "Unable to find FILE information in camera format configuration.\n");
+        return PM_FPA_LEVEL_NONE;
+    }
+    const char *extType = psMetadataLookupStr(&mdok, file, "EXTENSIONS");
+    if (!mdok || !extType || strlen(extType) == 0) {
+        psError(PS_ERR_IO, true, "Unable to find EXTENSIONS in the FILE information in the camera format"
+                " configuration.\n");
+        return PM_FPA_LEVEL_NONE;
+    }
+
+    // Where do we stick in the HDUs?
+    pmFPALevel level = PM_FPA_LEVEL_NONE; // Level for HDU insertion
+    if (strcasecmp(extType, "CHIP") == 0) {
+        level = PM_FPA_LEVEL_CHIP;
+    } else if (strcasecmp(extType, "CELL") == 0) {
+        level = PM_FPA_LEVEL_CELL;
+    } else if (strcasecmp(extType, "NONE") != 0) {
+        psError(PS_ERR_IO, true, "EXTENSIONS is not CHIP or CELL or NONE.\n");
+    }
+
+    return level;
+}
+#endif
+
+// Find the chip of interest within the FPA
+static bool whichChip(int *chipNum, // Chip number, modified
+                      psString *chipType, // Type of chip, modified
+                      const pmFPA *fpa, // FPA holding chip of interest
+                      const char *content // Content consisting of chipName:chipType
+                      )
+{
+    assert(chipType);
+    assert(fpa);
+    assert(content);
+
+    psArray *chipNames = NULL;
+    psArray *chipTypes = NULL;
+    if (parseContent(&chipNames, &chipTypes, NULL, content) != 1) {
+        psError(PS_ERR_UNKNOWN, false,
+                "Unable to parse chipName:chipType in %s in camera format",
+                TABLE_OF_CONTENTS);
+        return false;
+    }
+
+    psString chipName = psMemIncrRefCounter(chipNames->data[0]); // Name of chip
+    *chipType = psMemIncrRefCounter(chipTypes->data[0]); // Type of chip
+    psFree(chipNames);
+    psFree(chipTypes);
+
+    psTrace("psModules.camera", 5, "This is chip %s\n", chipName);
+
+    // Get the chip
+    *chipNum = pmFPAFindChip(fpa, chipName); // Chip number
+    if (*chipNum == -1) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find chip %s in FPA.\n", chipName);
+        psFree(chipName);
+        return false;
+    }
+    psFree(chipName);
+
+    return true;
+}
+
+
+// Process a chip, using the cellName:cellType pair
+static bool processChip(const psMetadata *format, // Camera format
+                        const psMetadataItem *chipContents, // Contents of chip, cellName:cellType pairs (either in a string or a metadata)
+                        pmFPA *fpa, // FPA of interest
+                        pmChip *chip, // Chip of interest
+                        pmFPALevel level, // Level for HDU to go
+                        pmHDU *hdu      // HDU to add
+    )
+{
+    assert(format);
+    assert(chipContents);
+    assert(fpa);
+
+    psMetadata *chips = psMetadataLookupMetadata(NULL, format, CHIP_TYPES); // The chip types
+    if (!chips) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", CHIP_TYPES);
+        return false;
+    }
+
+    psArray *cellNames = NULL;      // Cell names
+    psArray *cellTypes = NULL;      // Cell types
+
+    int nParsed = 0;
+
+    switch (chipContents->type) {
+      case PS_DATA_STRING: {
+          nParsed = parseContent(&cellNames, &cellTypes, NULL, chipContents->data.str);
+          break;
+      }
+      case PS_DATA_METADATA: {
+          psMetadataIterator *iter = psMetadataIteratorAlloc(chipContents->data.md, PS_LIST_HEAD, NULL); // Iterator
+          psMetadataItem *item;           // Item from iteration
+          while ((item = psMetadataGetAndIncrement(iter))) {
+              if (item->type != PS_DATA_STRING) {
+                  psWarning ("Item %s in camera format chip table is not of type STR.", item->name);
+                  continue;
+              }
+              nParsed += parseContent(&cellNames, &cellTypes, NULL, item->data.str);
+          }
+          psFree (iter);
+          break;
+      }
+      default:
+        psWarning ("Item %s in camera format chip table is not of type STR.", chipContents->name);
+        break;
+    }
+
+    if (nParsed == 0) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, false,
+                "Unable to parse chip contents (within %s in camera format) as cellName:cellType",
+                CHIP_TYPES);
+        psFree(cellNames);
+        psFree(cellTypes);
+        return false;
+    }
+
+    if (!processContents(fpa, chip, hdu, level, NULL, cellNames, cellTypes, format)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to set contents for chip from camera format.");
+        psFree(cellNames);
+        psFree(cellTypes);
+        return false;
+    }
+
+    psFree(cellNames);
+    psFree(cellTypes);
+
+    return true;
+}
+
+// Given a chip, find the corresponding type by searching through the contents, looking for a match to its
+// name
+psString findChipType(const pmChip *chip, // Chip of interest
+                      psMetadata *contents // Contents, from camera format
+                      )
+{
+    assert(chip);
+    assert(contents);
+
+    const char *chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME"); // Name of chip
+    assert(chipName);
+
+    psString chipType = NULL;           // Type of chip
+    psMetadataIterator *iter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *item;           // Item from iteration
+    while ((item = psMetadataGetAndIncrement(iter))) {
+        if (item->type != PS_DATA_STRING) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                    "Item %s within %s in camera format is not of type STR.", item->name, TABLE_OF_CONTENTS);
+            psFree(iter);
+            psFree(chipType);
+            return NULL;
+        }
+
+        psArray *chipNames = NULL;  // Chip names
+        psArray *chipTypes = NULL;  // Chip types
+        if (parseContent(&chipNames, &chipTypes, NULL, item->data.str) != 1) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
+                    "Unable to parse contents (within %s in camera format) as chipName:chipType",
+                    TABLE_OF_CONTENTS);
+            psFree(chipNames);
+            psFree(chipTypes);
+            psFree(iter);
+            psFree(chipType);
+            return NULL;
+        }
+
+        if (strcmp(chipName, chipNames->data[0]) == 0) {
+            if (chipType) {
+                if (strcmp(chipType, chipTypes->data[0]) != 0) {
+                    psError(PS_ERR_UNKNOWN, true,
+                            "Multiple instances of chip %s in contents, with differing chipType "
+                            "(%s vs %s)", chipName, chipType, (char*)chipTypes->data[0]);
+                    psFree(chipNames);
+                    psFree(chipTypes);
+                    psFree(iter);
+                    psFree(chipType);
+                    return NULL;
+                }
+            } else {
+                chipType = psMemIncrRefCounter(chipTypes->data[0]);
+            }
+        }
+        psFree(chipNames);
+        psFree(chipTypes);
+    }
+    psFree(iter);
+
+    if (!chipType) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to identify chip type for chip %s", chipName);
+        return NULL;
+    }
+
+    return chipType;
+}
+
+// PHU=FPA and EXTENSIONS=CHIP:
+// TABLE_OF_CONTENTS(METADATA) has a list of extensions, each with a chipName:chipType.
+// CHIP_TYPES(METADATA) has a list of chip types, each with cellName:cellType
+static bool addSource_FPA_CHIP(pmFPA *fpa, // FPA to which to add
+                               const psMetadata *format // The camera format
+                               )
+{
+    assert(fpa);
+    assert(format);
+
+    psMetadata *contents = psMetadataLookupMetadata(NULL, format, TABLE_OF_CONTENTS); // The contents
+    if (!contents) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", TABLE_OF_CONTENTS);
+        return false;
+    }
+
+    psMetadata *chips = psMetadataLookupMetadata(NULL, format, CHIP_TYPES); // The chip types
+    if (!chips) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", CHIP_TYPES);
+        return false;
+    }
+
+    // Iterate over all extensions
+    psMetadataIterator *contentsIter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *item;               // Item from iteration
+    while ((item = psMetadataGetAndIncrement(contentsIter))) {
+        if (item->type != PS_DATA_STRING) {
+            psError(PS_ERR_BAD_PARAMETER_TYPE, true,
+                    "Type for %s (%x) in %s METADATA in camera format is not STR",
+                    item->name, item->type, TABLE_OF_CONTENTS);
+            psFree(contentsIter);
+            return false;
+        }
+
+        const char *extname = item->name; // Extension name
+        pmHDU *hdu = pmHDUAlloc(extname); // HDU for this extension
+        // Casting to avoid "warning: passing arg 1 of `p_psMemIncrRefCounter' discards qualifiers from
+        // pointer target type"
+        hdu->format = psMemIncrRefCounter((const psPtr)format);
+
+        // What's in the extension?  It's specified by chipName:chipType
+        // Assume that an extension contains only a single chip, instead of multiple chips
+        psString chipType = NULL;       // Type of chip
+        int chipNum = -1;               // Chip number
+        if (!whichChip(&chipNum, &chipType, fpa, item->data.str)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to determine chip from contents");
+            return false;
+        }
+        pmChip *chip = fpa->chips->data[chipNum]; // Chip of interest
+
+        const psMetadataItem *chipContents = psMetadataLookup(chips, chipType); // Contents of chip
+        psFree(chipType);
+        if (!chipContents) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find chip type %s in %s.",
+                    chipType, CHIP_TYPES);
+            psFree(hdu);
+            psFree(contentsIter);
+            return false;
+        }
+
+        if (!processChip(format, chipContents, fpa, chip, PM_FPA_LEVEL_CHIP, hdu)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to process chip %d\n", chipNum);
+            psFree(hdu);
+            psFree(contentsIter);
+            return false;
+        }
+
+        psFree(hdu);                    // Drop reference
+    }
+    psFree(contentsIter);
+
+    return true;
+}
+
+// PHU=FPA and EXTENSIONS=CELL:
+// TABLE_OF_CONTENTS(METADATA) has a list of extensions, each with a chipName:cellName:cellType.
+static bool addSource_FPA_CELL(pmFPA *fpa, // FPA to which to add
+                               const psMetadata *format // The camera format
+                               )
+{
+    assert(fpa);
+    assert(format);
+
+    psMetadata *contents = psMetadataLookupMetadata(NULL, format, TABLE_OF_CONTENTS); // The contents
+    if (!contents) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", TABLE_OF_CONTENTS);
+        return false;
+    }
+
+    // Iterate over all extensions
+    psMetadataIterator *contentsIter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *item;               // Item from iteration
+    while ((item = psMetadataGetAndIncrement(contentsIter))) {
+        if (item->type != PS_DATA_STRING) {
+            psError(PS_ERR_BAD_PARAMETER_TYPE, true,
+                    "Type for %s (%x) in %s METADATA in camera format is not STR",
+                    item->name, item->type, TABLE_OF_CONTENTS);
+            psFree(contentsIter);
+            return false;
+        }
+
+        const char *extname = item->name; // Extension name
+        pmHDU *hdu = pmHDUAlloc(extname); // HDU for this extension
+        // Casting to avoid "warning: passing arg 1 of `p_psMemIncrRefCounter' discards qualifiers from
+        // pointer target type"
+        hdu->format = psMemIncrRefCounter((const psPtr)format);
+
+        // What's in the extension?  It's specified by (possibly multiple) chipName:cellName:cellType
+
+        psArray *chipNames = NULL;      // Chip names
+        psArray *cellNames = NULL;      // Cell names
+        psArray *cellTypes = NULL;      // Cell types
+        if (parseContent(&chipNames, &cellNames, &cellTypes, item->data.str) == 0) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
+                    "Unable to parse extension contents (within %s->%s in camera format) as "
+                    "chipName:cellName:cellType", TABLE_OF_CONTENTS, extname);
+            psFree(chipNames);
+            psFree(cellNames);
+            psFree(cellTypes);
+            psFree(hdu);
+            psFree(contentsIter);
+            return false;
+        }
+
+        if (!processContents(fpa, NULL, hdu, PM_FPA_LEVEL_CELL, chipNames, cellNames, cellTypes,
+                             format)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to set contents from camera format.");
+            psFree(chipNames);
+            psFree(cellNames);
+            psFree(cellTypes);
+            psFree(hdu);
+            psFree(contentsIter);
+            return false;
+        }
+
+        psFree(chipNames);
+        psFree(cellNames);
+        psFree(cellTypes);
+
+        psFree(hdu);                    // Drop reference
+    }
+    psFree(contentsIter);
+
+    return true;
+}
+
+// PHU=FPA and EXTENSIONS=NONE:
+// TABLE_OF_CONTENTS(STR) has a list of chipName:cellName:cellType.
+static bool addSource_FPA_NONE(pmFPA *fpa, // FPA to which to add
+                               const psMetadata *format // The camera format
+                               )
+{
+    assert(fpa);
+    assert(format);
+
+    psString contents = psMetadataLookupStr(NULL, format, TABLE_OF_CONTENTS); // The contents
+    if (!contents) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", TABLE_OF_CONTENTS);
+        return false;
+    }
+
+    // What's in the file?  It's specified by (possibly multiple) chipName:cellName:cellType
+
+    psArray *chipNames = NULL;          // Chip names
+    psArray *cellNames = NULL;          // Cell names
+    psArray *cellTypes = NULL;          // Cell types
+    if (parseContent(&chipNames, &cellNames, &cellTypes, contents) == 0) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, false,
+                "Unable to parse contents (within %s in camera format) as chipName:cellName:cellType",
+                TABLE_OF_CONTENTS);
+        psFree(chipNames);
+        psFree(cellNames);
+        psFree(cellTypes);
+        return false;
+    }
+
+    if (!processContents(fpa, NULL, NULL, PM_FPA_LEVEL_NONE, chipNames, cellNames, cellTypes,
+                         format)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to set contents from camera format.");
+        psFree(chipNames);
+        psFree(cellNames);
+        psFree(cellTypes);
+        return false;
+    }
+
+    psFree(chipNames);
+    psFree(cellNames);
+    psFree(cellTypes);
+
+    return true;
+}
+
+
+// PHU=CHIP and EXTENSIONS=CELL:
+// TABLE_OF_CONTENTS(METADATA) has a menu of contents, each with a chipName:chipType.
+// CHIP_TYPES(METADATA) has a list of chip types(METADATA), each with extension(STR) with cellName:cellType
+static bool addSource_CHIP_CELL(pmFPAview *view, // View for PHU, modified
+                                pmFPA *fpa, // FPA to which to add
+                                pmChip *chip, // Known chip to which to add, or NULL
+                                const psMetadata *format, // The camera format
+                                pmHDU *phdu, // The Primary HDU
+                                bool install // Install the HDUs?
+                                )
+{
+    assert(view);
+    assert(fpa);
+    assert(format);
+    assert(phdu);
+
+    psMetadata *contents = psMetadataLookupMetadata(NULL, format, TABLE_OF_CONTENTS); // The contents
+    if (!contents) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", TABLE_OF_CONTENTS);
+        return false;
+    }
+
+    psMetadata *chips = psMetadataLookupMetadata(NULL, format, CHIP_TYPES); // The chip types
+    if (!chips) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", CHIP_TYPES);
+        return false;
+    }
+
+    psMetadata *fileInfo = psMetadataLookupMetadata(NULL, format, "FILE"); // The file information
+    if (!fileInfo) {
+        psError(PS_ERR_IO, false, "Unable to find FILE in the camera format configuration.\n");
+        return false;
+    }
+
+
+    psString chipType = NULL;           // Type of chip
+    if (chip) {
+        // We're given the chip (adding source from view)
+        // Need to identify the chip type, which we will do by traversing the contents
+        chipType = findChipType(chip, contents);
+    } else {
+        // We're given a header, from which to identify what chip we've got, and its type
+        const char *content = getContent(fileInfo, phdu->header, contents); // The contents of this chip
+        if (!content) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to determine content of file.");
+            return false;
+        }
+
+        int chipNum = -1;               // Chip number
+        if (!whichChip(&chipNum, &chipType, fpa, content)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to determine chip from contents");
+            return false;
+        }
+        chip = fpa->chips->data[chipNum]; // Chip of interest
+        view->chip = chipNum;
+    }
+
+    if (!install) {
+        // Everything below is about installing the HDUs
+        psFree(chipType);
+        return true;
+    }
+
+    if (!addHDUtoChip(chip, phdu)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to add HDU to chip\n");
+        psFree(chipType);
+        return false;
+    }
+
+    psMetadata *chipContents = psMetadataLookupMetadata(NULL, chips, chipType); // Contents of chip
+    psFree(chipType);
+    if (!chipContents) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find chip type %s in %s.",
+                chipType, CHIP_TYPES);
+        return false;
+    }
+
+    psMetadataIterator *contentsIter = psMetadataIteratorAlloc(chipContents, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *contentItem;        // Content, from iteration
+    while ((contentItem = psMetadataGetAndIncrement(contentsIter))) {
+        pmHDU *hdu = pmHDUAlloc(contentItem->name); // HDU for this extension
+        // Casting to avoid "warning: passing arg 1 of `p_psMemIncrRefCounter' discards qualifiers from
+        // pointer target type"
+        hdu->format = psMemIncrRefCounter((const psPtr)format);
+
+        if (!processChip(format, contentItem, fpa, chip, PM_FPA_LEVEL_CELL, hdu)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to process chip\n");
+            psFree(hdu);
+            psFree(contentsIter);
+            return false;
+        }
+
+        psFree(hdu);                    // Drop reference
+    }
+    psFree(contentsIter);
+
+    if (!pmConceptsReadChip(chip, PM_CONCEPT_SOURCE_DEFAULTS | PM_CONCEPT_SOURCE_PHU, true, true, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read concepts for chip.");
+        return false;
+    }
+
+    return true;
+}
+
+// PHU=CHIP and EXTENSIONS=NONE:
+// TABLE_OF_CONTENTS(METADATA) has a menu of contents, each with a chipName:chipType.
+// CHIP_TYPES(METADATA) has a list of chip types, each with cellName:cellType
+static bool addSource_CHIP_NONE(pmFPAview *view, // View for PHU, modified
+                                pmFPA *fpa, // FPA to which to add
+                                pmChip *chip, // Known chip to which to add, or NULL
+                                const psMetadata *format, // The camera format
+                                pmHDU *phdu, // Primary HDU
+                                bool install // Install the HDUs?
+                                )
+{
+    assert(fpa);
+    assert(format);
+    assert(phdu);
+
+    psMetadata *contents = psMetadataLookupMetadata(NULL, format, TABLE_OF_CONTENTS); // The contents
+    if (!contents) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", TABLE_OF_CONTENTS);
+        return false;
+    }
+
+    psMetadata *chips = psMetadataLookupMetadata(NULL, format, CHIP_TYPES); // The chip types
+    if (!chips) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", CHIP_TYPES);
+        return false;
+    }
+
+    psMetadata *fileInfo = psMetadataLookupMetadata(NULL, format, "FILE"); // The file information
+    if (!fileInfo) {
+        psError(PS_ERR_IO, false, "Unable to find FILE in the camera format configuration.\n");
+        return false;
+    }
+
+    psString chipType = NULL;           // Type of chip
+    if (chip) {
+        // We're given the chip (adding source from view)
+        // Need to identify the chip type, which we will do by traversing the contents
+        chipType = findChipType(chip, contents);
+    } else {
+        const char *content = getContent(fileInfo, phdu->header, contents); // The chip type
+
+        int chipNum = -1;               // Chip number
+        if (!whichChip(&chipNum, &chipType, fpa, content)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to determine chip from contents");
+            return false;
+        }
+        chip = fpa->chips->data[chipNum]; // Chip of interest
+        view->chip = chipNum;
+    }
+
+    if (!install) {
+        // Everything below is about installing the HDU
+        psFree(chipType);
+        return true;
+    }
+
+    if (!addHDUtoChip(chip, phdu)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to add HDU to chip\n");
+        psFree(chipType);
+        return false;
+    }
+
+    // What's in the chip?
+    psMetadataItem *chipContents = psMetadataLookup(chips, chipType); // Contents of the chip
+    if (!chipContents) {
+        psError(PS_ERR_UNEXPECTED_NULL, false,
+                "Unable to find chip type %s in %s of camera format", chipType, CHIP_TYPES);
+        return false;
+    }
+    psFree(chipType);
+
+    if (!processChip(format, chipContents, fpa, chip, PM_FPA_LEVEL_NONE, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to process chip\n");
+        return false;
+    }
+
+    if (!pmConceptsReadChip(chip, PM_CONCEPT_SOURCE_DEFAULTS | PM_CONCEPT_SOURCE_PHU, true, true, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read concepts for chip.");
+        return false;
+    }
+
+    return true;
+}
+
+// PHU=CELL and EXTENSIONS=NONE:
+// TABLE_OF_CONTENTS(METADATA) has a menu of contents, each with a chipName:cellName:cellType
+static bool addSource_CELL_NONE(pmFPAview *view, // View for PHU, modified
+                                pmFPA *fpa, // FPA to which to add
+                                pmCell *cell, // Known cell to which to add, or NULL
+                                const psMetadata *format, // The camera format
+                                pmHDU *phdu, // The Primary HDU
+                                bool install // Install the HDUs?
+                                )
+{
+    assert(fpa);
+    assert(format);
+    assert(phdu);
+
+    psMetadata *contents = psMetadataLookupMetadata(NULL, format, TABLE_OF_CONTENTS); // The contents
+    if (!contents) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s in camera format.", TABLE_OF_CONTENTS);
+        return false;
+    }
+
+    psMetadata *fileInfo = psMetadataLookupMetadata(NULL, format, "FILE"); // The file information
+    if (!fileInfo) {
+        psError(PS_ERR_IO, false, "Unable to find FILE in the camera format configuration.\n");
+        return false;
+    }
+
+    psArray *chipNames = NULL;          // Chip names
+    psArray *cellNames = NULL;          // Cell names
+    psArray *cellTypes = NULL;          // Cell types
+    pmChip *chip = NULL;                // Chip of interest
+    if (cell) {
+        // We're given the chip and cell (adding source from view)
+        // Need to identify the cell type, which we will do by traversing the contents
+
+        chip = cell->parent;            // The chip of interest
+        psString cellType = NULL;       // Type of cell
+
+        // The below is very similar to findChipType(), but with modifications for finding the cellType
+        const char *chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME"); // Name of chip
+        assert(chipName);
+        const char *cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME"); // Name of cell
+        assert(cellName);
+
+        psMetadataIterator *iter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL); // Iterator
+        psMetadataItem *item;           // Item from iteration
+        while ((item = psMetadataGetAndIncrement(iter))) {
+            if (item->type != PS_DATA_STRING) {
+                psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                        "Item %s within %s in camera format is not of type STR.",
+                        item->name, TABLE_OF_CONTENTS);
+                psFree(chipNames);
+                psFree(cellNames);
+                psFree(cellTypes);
+                psFree(iter);
+                psFree(cellType);
+                return false;
+            }
+
+            psArray *testChipNames = NULL; // Chip names
+            psArray *testCellNames = NULL; // Cell names
+            psArray *testCellTypes = NULL; // Cell types
+            if (parseContent(&testChipNames, &testCellTypes, &testCellTypes, item->data.str) != 1) {
+                psError(PS_ERR_BAD_PARAMETER_VALUE, false,
+                        "Unable to parse contents (within %s in camera format) as chipName:cellName:cellType",
+                        TABLE_OF_CONTENTS);
+                psFree(chipNames);
+                psFree(cellNames);
+                psFree(cellTypes);
+                psFree(testChipNames);
+                psFree(testCellNames);
+                psFree(testCellTypes);
+                psFree(iter);
+                psFree(cellType);
+                return false;
+            }
+
+            if (strcmp(chipName, chipNames->data[0]) == 0 && strcmp(cellName, cellNames->data[0]) == 0) {
+                if (cellType) {
+                    if (strcmp(cellType, cellTypes->data[0]) != 0) {
+                        psError(PS_ERR_UNKNOWN, true,
+                                "Multiple instances of chip %s cell %s in contents, with differing cellType "
+                                "(%s vs %s)", chipName, cellName, cellType, (char*)cellTypes->data[0]);
+                        psFree(chipNames);
+                        psFree(cellNames);
+                        psFree(cellTypes);
+                        psFree(testChipNames);
+                        psFree(testCellNames);
+                        psFree(testCellTypes);
+                        psFree(iter);
+                        psFree(cellType);
+                        return false;
+                    }
+                } else {
+                    cellType = psMemIncrRefCounter(cellTypes->data[0]);
+                    chipNames = psMemIncrRefCounter(testChipNames);
+                    cellNames = psMemIncrRefCounter(testCellNames);
+                    cellTypes = psMemIncrRefCounter(testCellTypes);
+                }
+            }
+            psFree(testChipNames);
+            psFree(testCellNames);
+            psFree(testCellTypes);
+        }
+        psFree(iter);
+
+        if (!cellType) {
+            psError(PS_ERR_UNKNOWN, true, "Unable to identify cell type for chip %s cell %s",
+                    chipName, cellName);
+            psFree(chipNames);
+            psFree(cellNames);
+            psFree(cellTypes);
+            return false;
+        }
+
+        // We don't really care about the cell type here --- it's taken care of by processContents
+        psFree(cellType);
+
+    } else {
+        const char *content = getContent(fileInfo, phdu->header, contents); // Content of cell
+
+        if (parseContent(&chipNames, &cellNames, &cellTypes, content) != 1) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
+                    "Unable to parse cell contents (%s) as cellName:cellType", content);
+            psFree(chipNames);
+            psFree(cellNames);
+            psFree(cellTypes);
+            return false;
+        }
+
+        int chipNum = pmFPAFindChip(fpa, chipNames->data[0]); // Chip number
+        if (chipNum == -1) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to find chip %s referred to in contents",
+                    (char*)chipNames->data[0]);
+            psFree(chipNames);
+            psFree(cellNames);
+            psFree(cellTypes);
+            return false;
+        }
+        chip = fpa->chips->data[chipNum];
+
+        int cellNum = pmChipFindCell(chip, cellNames->data[0]); // Cell number
+        if (cellNum == -1) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to find cell %s referred to in contents",
+                    (char*)cellNames->data[0]);
+            psFree(chipNames);
+            psFree(cellNames);
+            psFree(cellTypes);
+            return false;
+        }
+        cell = chip->cells->data[cellNum];
+
+        view->chip = chipNum;
+        view->cell = cellNum;
+
+        psFree(chipNames);
+        psFree(cellNames);
+        psFree(cellTypes);
+    }
+
+    if (!install) {
+        // Everything below is about installing the HDU
+        psFree(chipNames);
+        psFree(cellNames);
+        psFree(cellTypes);
+        return true;
+    }
+
+    if (!processContents(fpa, NULL, phdu, PM_FPA_LEVEL_NONE, chipNames, cellNames, cellTypes, format)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to set contents for cell from camera format.");
+        psFree(chipNames);
+        psFree(cellNames);
+        psFree(cellTypes);
+        return false;
+    }
+
+    psFree(chipNames);
+    psFree(cellNames);
+    psFree(cellTypes);
+
+    if (!pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_DEFAULTS | PM_CONCEPT_SOURCE_PHU, true, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read concepts for cell.");
+        return false;
+    }
+
+    return true;
+}
+
+
+// This is the engine for the pmFPAAddSourceFrom{Header,View} functions.
+// It uses the camera format configuration information to determine where HDUs go in the FPA.
+// It returns a view corresponding to the PHU
+static pmFPAview *addSource(pmFPA *fpa,       // The FPA
+                            const char *fpaObs, // The desired FPA observation name
+                            const pmFPAview *phuView, // The view corresponding to the PHU, or NULL
+                            const psMetadata *header, // The PHU header, or NULL
+                            const psMetadata *format, // Format of file
+                            bool install // Install the provided header in the location that we find?
+                           )
+{
+    assert(fpa);
+    assert(phuView || header);
+    assert(format);
+
+    bool mdok;                          // Status of MD lookup
+
+    // If FPA.OBS is already defined, new name must match it; otherwise, warn the user that something
+    // potentially bad is happening.
+    if (fpaObs && install) {
+        const char *currentName = psMetadataLookupStr(&mdok, fpa->concepts, "FPA.OBS"); // Current name
+        if (mdok && currentName && strlen(currentName) > 0 && strcmp(currentName, fpaObs) != 0) {
+            psWarning("FPA.OBS for new source (%s) doesn't match FPA.OBS for current fpa (%s).",
+                      fpaObs, currentName);
+        }
+        psMetadataAddStr(fpa->concepts, PS_LIST_HEAD, "FPA.OBS", PS_META_REPLACE, "Observation identifier",
+                         fpaObs);
+    } else if (!psMetadataLookup(fpa->concepts, "FPA.OBS")) {
+        // Make sure there is an FPA.OBS
+        psMetadataAddStr(fpa->concepts, PS_LIST_HEAD, "FPA.OBS", 0, "Observation identifier", "UNKNOWN");
+    }
+
+    psMetadata *fileInfo = psMetadataLookupMetadata(&mdok, format, "FILE"); // The file information
+    if (!mdok || !fileInfo) {
+        psError(PS_ERR_IO, false, "Unable to find FILE in the camera format configuration.\n");
+        return NULL;
+    }
+
+    // At what level does the PHU go?
+    const char *phuType = psMetadataLookupStr(&mdok, fileInfo, "PHU"); // What is the PHU?
+    if (!mdok || strlen(phuType) == 0) {
+        psError(PS_ERR_IO, false, "Unable to find PHU in the format specification.\n");
+        return NULL;
+    }
+
+    // Prepare the PHU to be placed in the camera hierarchy
+    pmHDU *phdu = pmHDUAlloc(NULL);     // The primary header data unit
+    // Casting to psPtr to avoide "warning: passing arg 1 of `p_psMemIncrRefCounter' discards qualifiers from
+    // pointer target type"
+    phdu->header = psMemIncrRefCounter((const psPtr)header);
+    phdu->format = psMemIncrRefCounter((const psPtr)format);
+    pmFPAview *view = pmFPAviewAlloc(0); // View, to be returned
+    if (phuView) {
+        // Copy the view, for the case where we're given a view.
+        *view = *phuView;
+    }
+
+    // And at what level do the individual extensions go?
+    const char *extType = psMetadataLookupStr(&mdok, fileInfo, "EXTENSIONS"); // What's in the extns?
+    if (!mdok || strlen(extType) == 0) {
+        psError(PS_ERR_IO, false, "Unable to find EXTENSIONS in the format specification.\n");
+        psFree(view);
+        return NULL;
+    }
+
+    // Now, there are a few cases:
+
+    // Case    PHU     EXTENSIONS     Description
+    // ====    ===     ==========     ===========
+    // 1.      FPA     CHIP           CONTENTS(METADATA) has a list of extensions, each with chipName:chipType
+    //                                CHIPS(METADATA) has a list of chip types, each with cell:type
+    // 2.      FPA     CELL           CONTENTS(METADATA) has a list of extensions, each with chip:cell:type
+    //                                No need for CHIPS.
+    // 3.      FPA     NONE           CONTENTS(STRING) has a list of extensions, chip:cell:type
+    //                                No need for CHIPS
+    // 4.      CHIP    CELL           CONTENTS(METADATA) is a menu, each with a chipName:chipType
+    //                                CHIPS(METADATA) has a list of chip types(METADATA), containg a list of
+    //                                extensions.
+    // 5.      CHIP    NONE           CONTENTS(METADATA) is a menu, each with a chipName:chipType
+    //                                CHIPS(METADATA) has a list of chip types(STRING) with cell:type
+    // 6.      CELL    NONE           CONTENTS(METADATA) is a menu, each with a chipName:cellName:cellType.
+    //                                No need for CHIPS.
+
+
+    pmFPALevel phuLevel = pmFPALevelFromName(phuType); // Level for PHU
+    pmFPALevel extLevel = pmFPALevelFromName(extType); // Level for extensions
+
+    switch (phuLevel) {
+      case PM_FPA_LEVEL_FPA: {
+          // We don't have to work out where the PHU is --- there's only one FPA.
+          // 'view' already points to the FPA.
+          switch (extLevel) {
+            case PM_FPA_LEVEL_CHIP:
+              phdu->blankPHU = true;
+              if (install) {
+                  if (!addHDUtoFPA(fpa, phdu) || !addSource_FPA_CHIP(fpa, format) ||
+                      !pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_DEFAULTS | PM_CONCEPT_SOURCE_PHU,
+                                         true, NULL)) {
+                      psError(PS_ERR_UNKNOWN, false, "Unable to add source.");
+                      psFree(phdu);
+                      psFree(view);
+                      return NULL;
+                  }
+              }
+              psFree(phdu);
+              return view;
+            case PM_FPA_LEVEL_CELL:
+              if (install) {
+                  phdu->blankPHU = true;
+                  if (!addHDUtoFPA(fpa, phdu) || !addSource_FPA_CELL(fpa, format) ||
+                      !pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_DEFAULTS | PM_CONCEPT_SOURCE_PHU,
+                                         true, NULL)) {
+                      psError(PS_ERR_UNKNOWN, false, "Unable to add source.");
+                      psFree(phdu);
+                      psFree(view);
+                      return NULL;
+                  }
+              }
+              psFree(phdu);
+              return view;
+            case PM_FPA_LEVEL_NONE:
+              if (install) {
+                  phdu->blankPHU = false;
+                  if (!addHDUtoFPA(fpa, phdu) || !addSource_FPA_NONE(fpa, format) ||
+                      !pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_DEFAULTS | PM_CONCEPT_SOURCE_PHU,
+                                         true, NULL)) {
+                      psError(PS_ERR_UNKNOWN, false, "Unable to add source.");
+                      psFree(phdu);
+                      psFree(view);
+                      return NULL;
+                  }
+              }
+              psFree(phdu);
+             return view;
+            default:
+              psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                      "EXTENSIONS level (%s) incompatible with PHU level (FPA)", extType);
+              psFree(phdu);
+              psFree(view);
+              return NULL;
+          }
+          break;
+      }
+      case PM_FPA_LEVEL_CHIP: {
+          pmChip *chip = NULL;          // Appropriate chip, if the view is specified
+          if (phuView) {
+              chip = fpa->chips->data[phuView->chip];
+          }
+          switch (extLevel) {
+            case PM_FPA_LEVEL_CELL:
+              phdu->blankPHU = true;
+              if (!addSource_CHIP_CELL(view, fpa, chip, format, phdu, install)) {
+                  psError(PS_ERR_UNKNOWN, false, "Unable to add source.");
+                  psFree(phdu);
+                  psFree(view);
+                  return NULL;
+              }
+              psFree(phdu);
+              return view;
+            case PM_FPA_LEVEL_NONE:
+              phdu->blankPHU = false;
+              if (!addSource_CHIP_NONE(view, fpa, chip, format, phdu, install)) {
+                  psError(PS_ERR_UNKNOWN, false, "Unable to add source.");
+                  psFree(phdu);
+                  psFree(view);
+                  return NULL;
+              }
+              psFree(phdu);
+              return view;
+            default:
+              psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                      "EXTENSIONS level (%s) incompatible with PHU level (CHIP)", extType);
+              return NULL;
+          }
+          break;
+      }
+      case PM_FPA_LEVEL_CELL: {
+          if (extLevel != PM_FPA_LEVEL_NONE) {
+              psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                      "EXTENSIONS level (%s) incompatible with PHU level (CELL)", extType);
+              return NULL;
+          }
+          pmChip *chip = NULL;          // Appropriate chip, if the view is specified
+          pmCell *cell = NULL;          // Appropriate cell, if the view is specified
+          if (phuView) {
+              chip = fpa->chips->data[phuView->chip];
+              cell = chip->cells->data[phuView->cell];
+          }
+          phdu->blankPHU = false;
+          if (!addSource_CELL_NONE(view, fpa, cell, format, phdu, install)) {
+              psError(PS_ERR_UNKNOWN, false, "Unable to add source.");
+              psFree(phdu);
+              psFree(view);
+              return NULL;
+          }
+          psFree(phdu);
+          return view;
+          break;
+      }
+      default:
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Bad PHU level: %s", phuType);
+        return NULL;
+    }
+
+    return NULL;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+pmFPA *pmFPAConstruct(const psMetadata *camera, const char *cameraName)
+{
+    PS_ASSERT_PTR_NON_NULL(camera, NULL);
+
+    pmFPA *fpa = pmFPAAlloc(camera, cameraName);    // The FPA to fill out
+
+    bool mdok = true;                   // Status from MD lookups
+    psMetadata *components = psMetadataLookupMetadata(&mdok, camera, "FPA"); // FPA components
+    if (!mdok || !components) {
+        psError(PS_ERR_IO, true, "Failed to lookup \"FPA\"");
+        psFree(fpa);
+        return NULL;
+    }
+    psMetadataIterator *componentsIter = psMetadataIteratorAlloc(components, PS_LIST_HEAD, NULL);
+    psMetadataItem *componentsItem = NULL; // Item from components
+    while ((componentsItem = psMetadataGetAndIncrement(componentsIter))) {
+        const char *chipName = componentsItem->name; // Name of the chip
+        if (componentsItem->type != PS_DATA_STRING) {
+            psWarning("Element %s in FPA within the camera configuration is not of "
+                     "type STR (type=%x) --- ignored.\n", chipName, componentsItem->type);
+            continue;
+        }
+
+        pmChip *chip = pmChipAlloc(fpa, chipName); // The chip
+        psList *cellNames = psStringSplit(componentsItem->data.V, " ,;", true); // List of cell names
+        psListIterator *cellNamesIter = psListIteratorAlloc(cellNames, PS_LIST_HEAD, false); // Iterator
+
+        psString cellName = NULL;       // Name of cell
+        while ((cellName = psListGetAndIncrement(cellNamesIter))) {
+            pmCell *cell = pmCellAlloc(chip, cellName); // New cell
+            psFree(cell);               // Drop reference
+        }
+        psFree(chip);                   // Drop reference
+        psFree(cellNamesIter);
+        psFree(cellNames);
+    }
+    psFree(componentsIter);
+
+    return fpa;
+}
+
+bool pmFPAAddSourceFromFormat(pmFPA *fpa, const char *fpaObs, const psMetadata *format)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_METADATA_NON_NULL(format, false);
+
+    // Generate the correct structure
+    pmFPALevel phuLevel = pmFPAPHULevel(format); // Level at which PHU goes
+    pmFPAview *view = pmFPAviewAlloc(0);// View for current level
+    if (phuLevel == PM_FPA_LEVEL_FPA) {
+        if (!pmFPAAddSourceFromView(fpa, fpaObs, view, format)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to add PHU to FPA.");
+            psFree(view);
+            return false;
+        }
+    } else {
+        pmChip *chip;                       // Chip from FPA
+        while ((chip = pmFPAviewNextChip(view, fpa, 1))) {
+            if (phuLevel == PM_FPA_LEVEL_CHIP) {
+                if (!pmFPAAddSourceFromView(fpa, fpaObs, view, format)) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to add PHU to FPA.");
+                    psFree(view);
+                    return false;
+                }
+            } else {
+                pmCell *cell;                   // Cell from chip
+                while ((cell = pmFPAviewNextCell(view, fpa, 1))) {
+                    if (phuLevel == PM_FPA_LEVEL_CELL) {
+                        if (!pmFPAAddSourceFromView(fpa, fpaObs, view, format)) {
+                            psError(PS_ERR_UNKNOWN, false, "Unable to add PHU to FPA.");
+                            psFree(view);
+                            return false;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    psFree(view);
+
+    return true;
+}
+
+bool pmFPAAddSourceFromView(pmFPA *fpa, const char *fpaObs, const pmFPAview *phuView,
+                            const psMetadata *format)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(phuView, false);
+    PS_ASSERT_PTR_NON_NULL(format, false);
+
+    pmFPAview *view = addSource(fpa, fpaObs, phuView, NULL, format, true);
+    bool status = (view != NULL);
+    psFree(view);
+    return status;
+}
+
+pmFPAview *pmFPAAddSourceFromHeader(pmFPA *fpa, psMetadata *phu, const psMetadata *format)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+    PS_ASSERT_PTR_NON_NULL(phu, NULL);
+    PS_ASSERT_PTR_NON_NULL(format, NULL);
+
+    bool mdok = true;                   // Status from metadata lookups
+    psMetadata *fileInfo = psMetadataLookupMetadata(&mdok, format, "FILE"); // The file information
+    if (!mdok || !fileInfo) {
+        psError(PS_ERR_IO, false, "Unable to find FILE in the camera format configuration.\n");
+        return NULL;
+    }
+
+    // Check the name of the FPA
+    psString fpaObs = phuNameFromHeader("FPA.OBS", fileInfo, phu); // New observation name for the FPA
+    if (!fpaObs || strlen(fpaObs) == 0) {
+        psWarning("Unable to determine FPA.OBS: check for FPA.OBS in FILE in camera format");
+    }
+
+    pmFPAview *view = addSource(fpa, fpaObs, NULL, phu, format, true); // View of PHU, to return
+    psFree(fpaObs);
+
+    return view;
+}
+
+
+pmFPAview *pmFPAIdentifySourceFromHeader(pmFPA *fpa, psMetadata *phu, const psMetadata *format)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+    PS_ASSERT_PTR_NON_NULL(phu, NULL);
+    PS_ASSERT_PTR_NON_NULL(format, NULL);
+
+    return addSource(fpa, NULL, NULL, phu, format, false);
+}
+
+
+// Print spaces to indent
+#define INDENT(FILE, LEVEL) \
+{ \
+    for (int i = 0; i < (LEVEL); i++) { \
+        fprintf(FILE, " "); \
+    } \
+}
+
+void pmFPAPrint(FILE *fd, const pmFPA *fpa, bool header, bool concepts)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa,);
+
+    INDENT(fd, 1);
+    fprintf(fd, "FPA:\n");
+    if (fpa->hdu) {
+        pmHDUPrint(fd, fpa->hdu, 2, header);
+    }
+    if (concepts) {
+        psMetadataPrint(fd, fpa->concepts, 2);
+    }
+
+    psArray *chips = fpa->chips;        // Array of chips
+    // Iterate over the FPA
+    for (int i = 0; i < chips->n; i++) {
+        INDENT(fd, 3);
+        fprintf(fd, "Chip: %d\n", i);
+        pmChip *chip = chips->data[i]; // The chip
+        if (chip->hdu) {
+            pmHDUPrint(fd, chip->hdu, 4, header);
+        }
+        if (concepts) {
+            psMetadataPrint(fd, chip->concepts, 4);
+        }
+
+        // Iterate over the chip
+        psArray *cells = chip->cells;   // Array of cells
+        for (int j = 0; j < cells->n; j++) {
+            INDENT(fd, 5);
+            fprintf(fd, "Cell: %d\n", j);
+            pmCell *cell = cells->data[j]; // The cell
+            if (cell->hdu) {
+                pmHDUPrint(fd, cell->hdu, 6, header);
+            }
+            if (concepts) {
+                psMetadataPrint(fd, cell->concepts, 6);
+            }
+
+            psArray *readouts = cell->readouts; // Array of readouts
+            for (int k = 0; k < readouts->n; k++) {
+                pmReadout *readout = readouts->data[k]; // The readout
+                INDENT(fd, 6);
+                fprintf(fd, "Readout %d:\n", k);
+                INDENT(fd, 7);
+                fprintf(fd, "col0: %d\n", readout->col0);
+                INDENT(fd, 7);
+                fprintf(fd, "row0: %d\n", readout->row0);
+                psImage *image = readout->image; // The image
+                psImage *mask = readout->mask; // The mask
+                psImage *weight = readout->weight; // The weight
+                psList *bias = readout->bias; // The list of bias images
+                if (image) {
+                    INDENT(fd, 7);
+                    fprintf(fd, "Image: [%d:%d,%d:%d] (%dx%d)\n", image->col0, image->col0 +
+                            image->numCols, image->row0, image->row0 + image->numRows, image->numCols,
+                            image->numRows);
+                }
+                if (bias) {
+                    psListIterator *biasIter = psListIteratorAlloc(bias, PS_LIST_HEAD, false); // Iterator
+                    psImage *biasImage = NULL; // Bias image from iteration
+                    while ((biasImage = psListGetAndIncrement(biasIter))) {
+                        INDENT(fd, 7);
+                        fprintf(fd, "Bias:  [%d:%d,%d:%d] (%dx%d)\n", biasImage->col0,
+                                biasImage->col0 + biasImage->numCols, biasImage->row0,
+                                biasImage->row0 + biasImage->numRows, biasImage->numCols, biasImage->numRows);
+                    }
+                    psFree(biasIter);
+                }
+                if (mask) {
+                    INDENT(fd, 7);
+                    fprintf(fd, "Mask: [%d:%d,%d:%d] (%dx%d)\n", mask->col0, mask->col0 +
+                            mask->numCols, mask->row0, mask->row0 + mask->numRows, mask->numCols,
+                            mask->numRows);
+                }
+                if (weight) {
+                    INDENT(fd, 7);
+                    fprintf(fd, "Weight: [%d:%d,%d:%d] (%dx%d)\n", weight->col0, weight->col0 +
+                            weight->numCols, weight->row0, weight->row0 + weight->numRows, weight->numCols,
+                            weight->numRows);
+                }
+            } // Iterating over cell
+        } // Iterating over chip
+    } // Iterating over FPA
+
+}
+
+
+pmFPALevel pmFPAPHULevel(const psMetadata *format)
+{
+    PS_ASSERT_METADATA_NON_NULL(format, PM_FPA_LEVEL_NONE);
+
+    bool mdok;                          // Status of MD lookup
+    psMetadata *fileInfo = psMetadataLookupMetadata(&mdok, format, "FILE"); // Contents of FILE metadata
+    if (!mdok || !fileInfo) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find FILE in camera format configuration.\n");
+        return PM_FPA_LEVEL_NONE;
+    }
+    const char *phu = psMetadataLookupStr(&mdok, fileInfo, "PHU"); // PHU level
+    if (!mdok || !phu || strlen(phu) == 0) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find PHU in FILE in camera format configuration.\n");
+        return PM_FPA_LEVEL_NONE;
+    }
+
+    return pmFPALevelFromName(phu);
+}
+
+pmFPALevel pmFPAExtensionsLevel(const psMetadata *format)
+{
+    PS_ASSERT_METADATA_NON_NULL(format, PM_FPA_LEVEL_NONE);
+
+    bool mdok;                          // Status of MD lookup
+    psMetadata *fileInfo = psMetadataLookupMetadata(&mdok, format, "FILE"); // Contents of FILE metadata
+    if (!mdok || !fileInfo) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find FILE in camera format configuration.\n");
+        return PM_FPA_LEVEL_NONE;
+    }
+
+    const char *extensions = psMetadataLookupStr(&mdok, fileInfo, "EXTENSIONS"); // EXTENSIONS level
+    if (!mdok || !extensions || strlen(extensions) == 0) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find EXTENSIONS in FILE in camera format configuration.\n");
+        return PM_FPA_LEVEL_NONE;
+    }
+
+    return pmFPALevelFromName(extensions);
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAConstruct.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAConstruct.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAConstruct.h	(revision 22157)
@@ -0,0 +1,78 @@
+/* @file pmFPAConstruct.h
+ * @brief Functions to create an FPA, and add data sources to it.
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.11 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-06-05 01:31:33 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_CONSTRUCT_H
+#define PM_FPA_CONSTRUCT_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+/// Construct an FPA instance on the basis of a camera configuration
+///
+/// This is the function that creates the FPA hierarchy on the basis of the camera configuration.  The "FPA"
+/// entry in the camera configuration specifies the chips (each of type STR) with their component cells listed
+/// as the corresponding values (whitespace separated).  The FPA hierarchy is created devoid of any
+/// input/output sources (i.e., HDUs).
+pmFPA *pmFPAConstruct(const psMetadata *camera, ///< The camera configuration
+                      const char *cameraName ///< Name of the camera (for FPA.CAMERA concept)
+                     );
+
+/// Add a source to the focal plane hierarchy, specified by a camera format
+///
+/// This is suitable for generating an output FPA given the desired format.
+bool pmFPAAddSourceFromFormat(pmFPA *fpa, ///< The FPA
+                              const char *fpaObs, ///< FPA.NAME for the source
+                              const psMetadata *format ///< Format of file
+    );
+
+/// Add an (input or output) source to the focal plane hierarchy, specified by a view
+///
+/// Given an FPA, add an HDU by specifying where it goes (i.e., by an FPAview).  The camera format
+/// configuration is required in order to describe how the FPA is laid out in terms of disk files.
+bool pmFPAAddSourceFromView(pmFPA *fpa,   ///< The FPA
+                            const char *fpaObs, ///< FPA.NAME for the source
+                            const pmFPAview *phuView, ///< The view, corresponding to the PHU
+                            const psMetadata *format ///< Format of file
+                           );
+
+/// Add an (input or output) source to the focal plane hierarchy, specified by a (primary) header
+///
+/// Given an FPA, add an HDU by specifying a primary header, which is used to determine the FITS file
+/// contents, and therefore the proper location for the HDU.  The camera format configuration is required in
+/// order to describe how the FPA is laid out in terms of disk files.
+pmFPAview *pmFPAAddSourceFromHeader(pmFPA *fpa, ///< The FPA
+                                    psMetadata *phu, ///< Primary header of file
+                                    const psMetadata *format ///< Format of file
+                                   );
+
+/// Identify a source in the focal plane hierarchy, specified by a (primary) header
+///
+/// This is the same as pmFPAAddSourceFromHeader, except the input is not added into the FPA hierarchy.
+/// This function serves only to identify where in the hierarchy it should go, not prepare for reading, etc.
+pmFPAview *pmFPAIdentifySourceFromHeader(pmFPA *fpa, psMetadata *phu, const psMetadata *format);
+
+/// Print a representation of the FPA, including its headers and concepts.
+///
+/// This function is intended for testing and development purposes.
+void pmFPAPrint(FILE *fd,               ///< File descriptor to which to print
+                const pmFPA *fpa,       ///< FPA to print
+                bool header,            ///< Print headers?
+                bool concepts           ///< Print concepts?
+               );
+
+/// Return the PHU level for an FPA, given the format
+pmFPALevel pmFPAPHULevel(const psMetadata *format);
+
+/// Return the Extensions level for an FPA, given the format
+pmFPALevel pmFPAExtensionsLevel(const psMetadata *format);
+
+/// @}
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPACopy.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPACopy.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPACopy.c	(revision 22157)
@@ -0,0 +1,546 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <strings.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAUtils.h"
+#include "pmHDUUtils.h"
+#include "pmFPACopy.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static functions and macros
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Copy the value for a concept
+#define COPY_CONCEPT(TARGET, SOURCE, NAME, TYPE) { \
+    psMetadataItem *targetItem = psMetadataLookup(TARGET, NAME); \
+    psMetadataItem *sourceItem = psMetadataLookup(SOURCE, NAME); \
+    targetItem->data.TYPE = sourceItem->data.TYPE; }
+
+// Find the blank (image-less) PHU, given a cell.
+static pmHDU *findBlankPHU(const pmCell *cell // The cell for which to find the PHU
+                          )
+{
+    assert(cell);
+
+    if (cell->hdu && cell->hdu->blankPHU) {
+        return cell->hdu;
+    }
+    pmChip *chip = cell->parent;        // The parent chip
+    if (chip->hdu && chip->hdu->blankPHU) {
+        return chip->hdu;
+    }
+    pmFPA *fpa = chip->parent;  // The parent FPA
+    if (fpa->hdu && fpa->hdu->blankPHU) {
+        return fpa->hdu;
+    }
+
+    return NULL;
+}
+
+// copy one of the psImage components of the readout
+static void readoutCopyComponent (psImage **target, psImage *source, psImageBinning *binning, bool xFlip, bool yFlip, bool pixels) {
+
+    if (!source) return;
+
+    if (*target) {
+        psFree(*target);
+    }
+    if (pixels) {
+        *target = psImageFlip(NULL, source, xFlip, yFlip);
+        return;
+    }
+
+    // I have the fine image size, I know the binning factor, determine the ruff image size
+    binning->nXfine = source->numCols;
+    binning->nYfine = source->numRows;
+    psImageBinningSetRuffSize(binning, PS_IMAGE_BINNING_CENTER);
+    *target = psImageAlloc(binning->nXruff, binning->nYruff, source->type.type);
+    return;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static engine functions --- these do all the work.  Actually, cellCopy does all the work; the others
+// merely iterate on the higher-level components.
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Common engine for pmCellCopy and pmCellCopyStructure
+// Does the actual splitting/splicing that's required to copy an FPA to a different representation.
+static bool cellCopy(pmCell *target,     // The target cell
+                     const pmCell *source, // The source cell, to be copied
+                     bool pixels,        // Copy the pixels?
+                     int xBin, int yBin  // (Relative) binning factors in x and y
+                    )
+{
+    assert(target);
+    assert(source);
+    assert(xBin > 0 && yBin > 0);
+
+    if (!source->data_exists) {
+        // Copied everything that exists
+        return true;
+    }
+
+    // XXX this is a programming / config error
+    if (pixels && (xBin != 1 || yBin != 1)) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to copy pixels if binning is set\n");
+        return false;
+    }
+
+    // the binning structure carries the information no how to rebin the images if needed
+    psImageBinning *binning = psImageBinningAlloc();
+    binning->nXbin = xBin;
+    binning->nYbin = yBin;
+
+    psArray *sourceReadouts = source->readouts; // The source readouts
+    int numReadouts = sourceReadouts->n; // Number of readouts copied
+
+    // Need to check/change CELL.XPARITY and CELL.YPARITY
+    bool mdokS = true;                   // Status of MD lookup
+    bool mdokT = true;                   // Status of MD lookup
+    bool xFlip = false;                 // Switch parity in x?
+    bool yFlip = false;                 // Switch parity in y?
+
+    // enforce the following conditions:
+    // CELL.XPARITY is required for source
+    // CELL.XPARITY must be +/- 1
+    int xParitySource = psMetadataLookupS32(&mdokS, source->concepts, "CELL.XPARITY"); // Source parity
+    int xParityTarget = psMetadataLookupS32(&mdokT, target->concepts, "CELL.XPARITY"); // Target x parity
+    assert(mdokS && mdokT);
+    if (abs(xParitySource) != 1) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "CELL.XPARITY is not set for source (%d)",
+                xParitySource);
+        psFree(binning);
+        return false;
+    } else {
+        // Use the source parity
+        COPY_CONCEPT(target->concepts, source->concepts, "CELL.XPARITY", S32);
+        xParityTarget = xParitySource;
+    }
+    if (xParityTarget != xParitySource) {
+        xFlip = true;
+    }
+
+    int yParityTarget = psMetadataLookupS32(&mdokT, target->concepts, "CELL.YPARITY"); // Target y parity
+    int yParitySource = psMetadataLookupS32(&mdokS, source->concepts, "CELL.YPARITY"); // Source parity
+    assert(mdokS && mdokT);
+    if (abs(yParitySource) != 1) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "CELL.YPARITY is not set for source (%d)",
+                yParitySource);
+        psFree(binning);
+        return false;
+    } else {
+        // Use the source parity
+        COPY_CONCEPT(target->concepts, source->concepts, "CELL.YPARITY", S32);
+        yParityTarget = yParitySource;
+    }
+    if (yParityTarget != yParitySource) {
+        yFlip = true;
+    }
+    psTrace("psModules.camera", 3, "xFlip: %d; yFlip: %d\n", xFlip, yFlip);
+
+    // Perform deep copy of the images.  I would prefer *not* to do a deep copy, in the interests of speed (we
+    // still need to do another deep copy into the HDU for when we write out), but this is the only way I can
+    // think of to provide security against copying a cell and then unknowingly changing the source when
+    // manipulating the target.
+    for (int i = 0; i < numReadouts; i++) {
+        pmReadout *sourceReadout = sourceReadouts->data[i]; // The source readout
+        pmReadout *targetReadout = pmReadoutAlloc(target); // The target readout; this adds it to the cell
+
+        // Copy attributes
+        // XXX is this correct under binning?
+        targetReadout->col0 = sourceReadout->col0;
+        targetReadout->row0 = sourceReadout->row0;
+        targetReadout->process = sourceReadout->process;
+        targetReadout->file_exists = sourceReadout->file_exists;
+        targetReadout->data_exists = sourceReadout->data_exists;
+
+        // Copy all three image components (image, mask, weight)
+        readoutCopyComponent (&targetReadout->image,  sourceReadout->image,  binning, xFlip, yFlip, pixels);
+        readoutCopyComponent (&targetReadout->mask,   sourceReadout->mask,   binning, xFlip, yFlip, pixels);
+        readoutCopyComponent (&targetReadout->weight, sourceReadout->weight, binning, xFlip, yFlip, pixels);
+
+        // Copy bias
+        while (targetReadout->bias->n > 0) {
+            psListRemove(targetReadout->bias, PS_LIST_HEAD);
+        }
+
+        // Iterate over the biases
+        psListIterator *biasIter = psListIteratorAlloc(sourceReadout->bias, PS_LIST_HEAD, false);
+        psImage *bias = NULL;           // Bias image from iteration
+        while ((bias = psListGetAndIncrement(biasIter))) {
+            psImage *biasCopy = NULL;          // Copy of the bias
+            readoutCopyComponent (&biasCopy, bias, binning, xFlip, yFlip, pixels);
+            psListAdd(targetReadout->bias, PS_LIST_TAIL, biasCopy);
+            psFree(biasCopy);           // Drop reference
+        }
+        psFree(biasIter);
+
+        targetReadout->data_exists = true;
+        psFree(targetReadout);          // Drop reference
+    }
+
+    // Copy the remaining "concepts" over.  Don't copy the TRIMSEC or BIASSEC, since these will be created by
+    // pmHDUGenerate if they don't already exist in the target.  Don't copy the XPARITY or YPARITY, since
+    // we've used those to do the flips.  Don't copy the X0 and Y0 because they are updated below (and are
+    // dependent upon the flips we've done above).
+    psMetadataIterator *conceptsIter = psMetadataIteratorAlloc(source->concepts, PS_LIST_HEAD, NULL);
+    psMetadataItem *conceptItem = NULL; // Item from iteration
+    while ((conceptItem = psMetadataGetAndIncrement(conceptsIter))) {
+        psString name = conceptItem->name; // Name of concept
+        if (!strcmp(name, "CELL.TRIMSEC")) continue;
+        if (!strcmp(name, "CELL.BIASSEC")) continue;
+        if (!strcmp(name, "CELL.XPARITY")) continue;
+        if (!strcmp(name, "CELL.YPARITY")) continue;
+        if (!strcmp(name, "CELL.X0")) continue;
+        if (!strcmp(name, "CELL.Y0")) continue;
+
+        psMetadataItem *copy = psMetadataItemCopy(conceptItem); // Copy of the concept
+        psMetadataAddItem(target->concepts, copy, PS_LIST_TAIL, PS_META_REPLACE);
+        psFree(copy);               // Drop reference
+    }
+    psFree(conceptsIter);
+
+    // Need to update CELL.TRIMSEC and CELL.BIASSEC if we changed the binning and they exist already.
+    // XXX this code seems to be very similar to pmConceptsUpdate
+    if ((binning->nXbin != 1) || (binning->nYbin != 1)) {
+        bool mdok = false;
+        psRegion *trimsec = psMetadataLookupPtr(&mdok, target->concepts, "CELL.TRIMSEC"); // The trim section
+        if (mdok && trimsec && !psRegionIsNaN(*trimsec)) {
+            *trimsec = psImageBinningSetRuffRegion(binning, *trimsec);
+            // force integer pixels : truncate x0, roundup x1:
+            trimsec->x0 = (int)trimsec->x0;
+            if (trimsec->x1 > (int)trimsec->x1) {
+                trimsec->x1 = (int)trimsec->x1 + 1;
+            } else {
+                trimsec->x1 = (int)trimsec->x1;
+            }
+            trimsec->y0 = (int)trimsec->y0;
+            if (trimsec->y1 > (int)trimsec->y1) {
+                trimsec->y1 = (int)trimsec->y1 + 1;
+            } else {
+                trimsec->y1 = (int)trimsec->y1;
+            }
+        }
+        psList *biassecs = psMetadataLookupPtr(&mdok, target->concepts, "CELL.BIASSEC"); // The bias sections
+        if (mdok && biassecs && biassecs->n > 0) {
+            psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, true); // Iterator
+            psRegion *biassec = NULL;   // Bias section, from iteration
+            while ((biassec = psListGetAndIncrement(biassecsIter))) {
+                if (!psRegionIsNaN(*biassec)) {
+                    *biassec = psImageBinningSetRuffRegion(binning, *biassec);
+                    // force integer pixels : truncate x0, roundup x1:
+                    biassec->x0 = (int)biassec->x0;
+                    if (biassec->x1 > (int)biassec->x1) {
+                        biassec->x1 = (int)biassec->x1 + 1;
+                    } else {
+                        biassec->x1 = (int)biassec->x1;
+                    }
+                    biassec->y0 = (int)biassec->y0;
+                    if (biassec->y1 > (int)biassec->y1) {
+                        biassec->y1 = (int)biassec->y1 + 1;
+                    } else {
+                        biassec->y1 = (int)biassec->y1;
+                    }
+                }
+            }
+            psFree(biassecsIter);
+        }
+    }
+
+    // Need to update CELL.X0 and CELL.Y0 if we flipped
+    // XXX this section should probably use a common function consistent with psImageBinning
+    if (xFlip) {
+        int xZero = psMetadataLookupS32(NULL, source->concepts, "CELL.X0"); // CELL.X0 from source
+        int xParity = psMetadataLookupS32(NULL, source->concepts, "CELL.XPARITY"); // Parity in x
+        int sourceBin = psMetadataLookupS32(NULL, source->concepts, "CELL.XBIN"); // CELL.XBIN from source
+        int xSize = psMetadataLookupS32(NULL, source->concepts, "CELL.XSIZE"); // CELL.XSIZE of source
+
+        if (sourceBin == 0) {
+            // Don't know the binning; assume it is unity
+            sourceBin = binning->nXbin;
+        }
+
+        // XXX make sure this is consistent with the psImageBinning
+        psTrace("psModules.camera", 3, "CELL.X0: Before: %d After: %d\n", xZero, xZero + (xSize - 1) * xParity * sourceBin);
+        psTrace("psModules.camera", 9, "(xParity: %d xBin: %d numCols: %d)\n", xParity, sourceBin, xSize);
+
+        if (xParity == 0 || xSize == 0) {
+            psWarning("New CELL.X0 may be incorrect due to missing concepts (CELL.XPARITY, CELL.XSIZE)");
+        }
+
+        xZero += (xSize - 1) * xParity * sourceBin; // Change the parity on the X0 position
+        psMetadataItem *newItem = psMetadataLookup(target->concepts, "CELL.X0"); // CELL.X0 from target
+        newItem->data.S32 = xZero;
+    }
+    if (yFlip) {
+        int yZero = psMetadataLookupS32(NULL, source->concepts, "CELL.Y0"); // CELL.Y0 from source
+        int yParity = psMetadataLookupS32(NULL, source->concepts, "CELL.YPARITY"); // Parity in y
+        int sourceBin = psMetadataLookupS32(NULL, source->concepts, "CELL.YBIN"); // Binning in y
+        int ySize = psMetadataLookupS32(NULL, source->concepts, "CELL.YSIZE"); // CELL.YSIZE of source
+
+        if (sourceBin == 0) {
+            // Don't know the binning; assume it is unity
+            sourceBin = binning->nYbin;
+        }
+
+        psTrace("psModules.camera", 3, "CELL.Y0: Before: %d After: %d\n", yZero, yZero + (ySize - 1) * yParity * sourceBin);
+        psTrace("psModules.camera", 9, "(yParity: %d yBin: %d numRows: %d)\n", yParity, sourceBin, ySize);
+
+        if (yParity == 0 || ySize == 0) {
+            psWarning("New CELL.Y0 may be incorrect due to missing concepts "
+                      "(CELL.Y0, CELL.YPARITY, CELL.YBIN, CELL.YSIZE)");
+        }
+
+        yZero += (ySize - 1) * yParity * sourceBin; // Change the parity on the Y0 position
+        psMetadataItem *newItem = psMetadataLookup(target->concepts, "CELL.Y0"); // CELL.Y0 from target
+        newItem->data.S32 = yZero;
+    }
+
+    // Update the binning concepts
+    // XXX this should probably be done with a common function using psImageBinning
+    psMetadataItem *binItem = psMetadataLookup(target->concepts, "CELL.XBIN");
+    binItem->data.S32 *= xBin;
+    binItem = psMetadataLookup(target->concepts, "CELL.YBIN");
+    binItem->data.S32 *= yBin;
+
+    // Copy any headers
+    pmHDU *targetHDU = pmHDUFromCell(target); // The target HDU
+    if (targetHDU) {
+        pmHDU *sourceHDU = pmHDUFromCell(source); // The source HDU
+        if (!sourceHDU) {
+            psWarning("Unable to copy header: no source header.");
+        } else if (sourceHDU->header) {
+            targetHDU->header = psMetadataCopy(targetHDU->header, sourceHDU->header);
+        }
+    }
+
+    // Copy the PHU over as well, if required
+    pmHDU *targetPHU = findBlankPHU(target); // The target PHU
+    if (targetPHU && targetPHU != targetHDU) {
+        // pmHDU *sourcePHU = pmHDUGetHighest(source->parent->parent, source->parent, source); // A source HDU
+        pmHDU *sourcePHU = findBlankPHU(source); // The target PHU
+        if (sourcePHU && sourcePHU->header) {
+            targetPHU->header = psMetadataCopy(targetPHU->header, sourcePHU->header);
+        }
+    }
+
+    psFree (binning);
+    target->data_exists = true;
+    target->parent->data_exists = true;
+    return true;
+}
+
+// Common engine for pmChipCopy and pmChipCopyStructure
+// Iterate on the components
+static bool chipCopy(pmChip *target,          // The target chip
+                     const pmChip *source, // The source chip, to be copied
+                     bool pixels,             // Copy the pixels?
+                     int xBin, int yBin       // (Relative) binning factors in x and y
+                    )
+{
+    assert(target);
+    assert(source);
+    assert(xBin > 0);
+    assert(yBin > 0);
+
+    if (!source->data_exists) {
+        // Copied everything that exists
+        return true;
+    }
+
+    psArray *targetCells = target->cells; // The target cells
+    psArray *sourceCells = source->cells; // The source cells
+    if (targetCells->n != sourceCells->n) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "Number of source cells (%ld) differs from the number of target cells (%ld)\n",
+                sourceCells->n, targetCells->n);
+        return false;
+    }
+
+    bool status = true;                 // Status of copy
+    for (int i = 0; i < targetCells->n; i++) {
+        pmCell *targetCell = targetCells->data[i]; // The target cell
+        const char *cellName = psMetadataLookupStr(NULL, targetCell->concepts, "CELL.NAME"); // Name of cell
+        int cellNum = pmChipFindCell(source, cellName); // Number of cell with that name
+        if (cellNum >= 0) {
+            pmCell *sourceCell = sourceCells->data[cellNum]; // The source cell
+            status &= cellCopy(targetCell, sourceCell, pixels, xBin, yBin);
+        }
+    }
+
+    // Update the concepts
+    psMetadataItem *chipName = psMemIncrRefCounter(psMetadataLookup(target->concepts, "CHIP.NAME"));
+    psMetadataCopy(target->concepts, source->concepts);
+    psMetadataAddItem(target->concepts, chipName, PS_LIST_TAIL, PS_META_REPLACE);
+    psFree(chipName);
+    psMetadataCopy(target->parent->concepts, source->parent->concepts);
+
+    target->data_exists = true;
+    return status;
+}
+
+// create a new pmChip with the data derived from the supplied chip
+pmChip *pmChipDuplicate(pmFPA *fpa, const pmChip *sourceChip)
+{
+    assert(sourceChip);
+
+    bool status;
+    char *chipName = psMetadataLookupStr(&status, sourceChip->concepts, "CHIP.NAME");
+    pmChip *targetChip = pmChipAlloc (NULL, chipName);
+    targetChip->parent = fpa;
+
+    psArray *sourceCells = sourceChip->cells; // The source cells
+
+    for (int i = 0; i < sourceCells->n; i++) {
+        pmCell *sourceCell = sourceCells->data[i]; // The sources cell
+        const char *cellName = psMetadataLookupStr(NULL, sourceCell->concepts, "CELL.NAME"); // Name of cell
+        // XXX are there other concepts I need to copy first?
+        pmCell *targetCell = pmCellAlloc (targetChip, cellName);
+        int xParityTarget = psMetadataLookupS32(&status, sourceCell->concepts, "CELL.XPARITY"); // Target x parity
+        psMetadataAddS32 (targetCell->concepts, PS_LIST_TAIL, "CELL.XPARITY", PS_META_REPLACE, "", xParityTarget);
+        int yParityTarget = psMetadataLookupS32(&status, sourceCell->concepts, "CELL.YPARITY"); // Target y parity
+        psMetadataAddS32 (targetCell->concepts, PS_LIST_TAIL, "CELL.YPARITY", PS_META_REPLACE, "", yParityTarget);
+        if (!cellCopy(targetCell, sourceCell, true, 1, 1)) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, false, "failed to duplicate chip\n");
+            return NULL;
+        }
+        // update the attributes
+        targetCell->file_exists = sourceCell->file_exists;
+        targetCell->data_exists = sourceCell->data_exists;
+        targetCell->process     = sourceCell->process;
+    }
+
+    // Update the concepts
+    psMetadataCopy(targetChip->concepts, sourceChip->concepts);
+
+    // update the attributes
+    targetChip->file_exists = sourceChip->file_exists;
+    targetChip->data_exists = sourceChip->data_exists;
+    targetChip->process     = sourceChip->process;
+
+    return targetChip;
+}
+
+// Common engine for pmFPACopy and pmFPACopyStructure.
+// Iterate on the components
+static bool fpaCopy(pmFPA *target,      // The target FPA
+                    const pmFPA *source, // The source FPA, to be copied
+                    bool pixels,        // Copy the pixels?
+                    int xBin, int yBin  // (Relative) binning factors in x and y
+                   )
+{
+    assert(target);
+    assert(source);
+    assert(xBin > 0);
+    assert(yBin > 0);
+
+    psArray *targetChips = target->chips; // The target chips
+    psArray *sourceChips = source->chips; // The source chips
+    if (targetChips->n != sourceChips->n) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "Number of source chips (%ld) differs from the number of target chips (%ld)\n",
+                sourceChips->n, targetChips->n);
+        return false;
+    }
+
+    bool status = true;                 // Status of copy
+    for (int i = 0; i < targetChips->n; i++) {
+        pmChip *targetChip = targetChips->data[i]; // The target chip
+        const char *chipName = psMetadataLookupStr(NULL, targetChip->concepts, "CHIP.NAME"); // Name of chip
+        int chipNum = pmFPAFindChip(source, chipName); // Number of chip with that name
+        if (chipNum >= 0) {
+            pmChip *sourceChip = sourceChips->data[chipNum]; // The source chip
+            status &= chipCopy(targetChip, sourceChip, pixels, xBin, yBin);
+        }
+    }
+
+    // Update the concepts
+    psMetadataCopy(target->concepts, source->concepts);
+
+    return status;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmFPACopy(pmFPA *target, const pmFPA *source)
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    if (target == source) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Can't copy FPA onto itself.");
+        return false;
+    }
+    return fpaCopy(target, source, true, 1, 1);
+}
+
+bool pmChipCopy(pmChip *target, const pmChip *source)
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    if (target == source) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Can't copy chip onto itself.");
+        return false;
+    }
+    return chipCopy(target, source, true, 1, 1);
+}
+
+bool pmCellCopy(pmCell *target, const pmCell *source)
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    if (target == source) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Can't copy cell onto itself.");
+        return false;
+    }
+    return cellCopy(target, source, true, 1, 1);
+}
+
+
+bool pmFPACopyStructure(pmFPA *target, const pmFPA *source, int xBin, int yBin)
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_INT_POSITIVE(xBin, false);
+    PS_ASSERT_INT_POSITIVE(yBin, false);
+    if (target == source) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Can't copy FPA onto itself.");
+        return false;
+    }
+    return fpaCopy(target, source, false, xBin, yBin);
+}
+
+bool pmChipCopyStructure(pmChip *target, const pmChip *source, int xBin, int yBin)
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_INT_POSITIVE(xBin, false);
+    PS_ASSERT_INT_POSITIVE(yBin, false);
+    if (target == source) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Can't copy chip onto itself.");
+        return false;
+    }
+    return chipCopy(target, source, false, xBin, yBin);
+}
+
+bool pmCellCopyStructure(pmCell *target, const pmCell *source, int xBin, int yBin)
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_INT_POSITIVE(xBin, false);
+    PS_ASSERT_INT_POSITIVE(yBin, false);
+    if (target == source) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Can't copy cell onto itself.");
+        return false;
+    }
+    return cellCopy(target, source, false, xBin, yBin);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPACopy.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPACopy.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPACopy.h	(revision 22157)
@@ -0,0 +1,78 @@
+/* @file pmFPACopy.h
+ * @brief Functions to copy FPA components.
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-04-12 02:51:44 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_COPY_H
+#define PM_FPA_COPY_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+/// Copy an FPA and components, including the pixels, to a different representation of the same camera
+///
+/// This function is useful for converting between different representations of the same camera.  For example,
+/// between Megacam "RAW" (one amp per extension) and Megacam "SPLICED" formats (two amps = 1 chip per
+/// extension, spliced together).  Components are spliced together as necessary.
+bool pmFPACopy(pmFPA *target,           ///< The target FPA
+               const pmFPA *source      ///< The source FPA, to be copied
+              );
+
+/// Copy a chip and components, including the pixels, to a different representation of the same camera
+///
+/// This function is useful for converting between different representations of the same camera.  For example,
+/// between Megacam "RAW" (one amp per extension) and Megacam "SPLICED" formats (two amps = 1 chip per
+/// extension, spliced together).  Components are spliced together as necessary.
+bool pmChipCopy(pmChip *target,         ///< The target chip
+                const pmChip *source    ///< The source chip, to be copied
+               );
+
+/// Copy a cell and components, including the pixels, to a different representation of the same camera
+///
+/// This function is useful for converting between different representations of the same camera.  For example,
+/// between Megacam "RAW" (one amp per extension) and Megacam "SPLICED" formats (two amps = 1 chip per
+/// extension, spliced together).  Components are spliced together as necessary.
+bool pmCellCopy(pmCell *target,         ///< The target cell
+                const pmCell *source    ///< The source cell, to be copied
+               );
+
+
+/// Copy an FPA, but not the pixels, to a different representation of the same camera
+///
+/// This function the same as pmFPACopy, except that the pixels are not copied (though images of sufficient
+/// size are allocated in the target).  Changes the CELL.XBIN and CELL.YBIN according to the provided binning
+/// factors.
+bool pmFPACopyStructure(pmFPA *target,   ///< The target FPA
+                        const pmFPA *source, ///< The source FPA, to be copied
+                        int xBin, int yBin ///< Binning factors in x and y
+                       );
+
+/// Copy a chip, but not the pixels, to a different representation of the same camera
+///
+/// This function the same as pmChipCopy, except that the pixels are not copied (though images of sufficient
+/// size are allocated in the target).  Changes the CELL.XBIN and CELL.YBIN according to the provided binning
+/// factors.
+bool pmChipCopyStructure(pmChip *target, ///< The target chip
+                         const pmChip *source, ///< The source chip, to be copied
+                         int xBin, int yBin ///< Binning factors in x and y
+                        );
+
+/// Copy a cell, but not the pixels, to a different representation of the same camera
+///
+/// This function the same as pmCellCopy, except that the pixels are not copied (though images of sufficient
+/// size are allocated in the target).  Changes the CELL.XBIN and CELL.YBIN according to the provided binning
+/// factors.
+bool pmCellCopyStructure(pmCell *target, ///< The target cell
+                         const pmCell *source, ///< The source cell, to be copied
+                         int xBin, int yBin ///< Binning factors in x and y
+                        );
+
+pmChip *pmChipDuplicate(pmFPA *fpa, const pmChip *source);
+
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAExtent.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAExtent.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAExtent.c	(revision 22157)
@@ -0,0 +1,155 @@
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmHDUUtils.h"
+
+// return cell pixels bounding the readout
+psRegion *pmReadoutExtent(const pmReadout *readout)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, NULL);
+
+    psImage *image = readout->image;    // Image from which to get dimensions
+    if (!image) {
+        image = readout->mask;
+    }
+    if (!image) {
+        image = readout->weight;
+    }
+
+    int xSize = 0;
+    int ySize = 0;
+
+    if (!image) {
+	pmHDU *hdu = pmHDUFromReadout (readout);
+	if (hdu && hdu->header) {
+	    xSize = psMetadataLookupS32(NULL, hdu->header, "NAXIS1");
+	    ySize = psMetadataLookupS32(NULL, hdu->header, "NAXIS2");
+	} else {
+        // Don't have anything to base the true extent on, so have to give the hardwired value (largest possible extent)
+	    xSize = psMetadataLookupS32(NULL, readout->parent->concepts, "CELL.XSIZE");
+	    ySize = psMetadataLookupS32(NULL, readout->parent->concepts, "CELL.YSIZE");
+	}
+        return psRegionAlloc(0, xSize, 0, ySize);
+    }
+
+    // Get the offset to the CCD window
+    int xWindow = psMetadataLookupS32(NULL, readout->parent->concepts, "CELL.XWINDOW");
+    int yWindow = psMetadataLookupS32(NULL, readout->parent->concepts, "CELL.YWINDOW");
+    return psRegionAlloc(xWindow, xWindow + image->numCols,
+                         yWindow, yWindow + image->numRows);
+}
+
+// return chip pixels bounding the cell (all readouts)
+psRegion *pmCellExtent(const pmCell *cell)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+
+    psArray *readouts = cell->readouts; // Array of component readouts
+    psRegion *cellExtent = psRegionAlloc(INFINITY, 0, INFINITY, 0); // Extent of cell
+    for (long i = 0; i < readouts->n; i++) {
+        pmReadout *readout = readouts->data[i]; // Readout of interest
+        psRegion *roExtent = pmReadoutExtent(readout); // Extent of readout
+        cellExtent->x0 = PS_MIN(cellExtent->x0, roExtent->x0);
+        cellExtent->x1 = PS_MAX(cellExtent->x1, roExtent->x1);
+        cellExtent->y0 = PS_MIN(cellExtent->y0, roExtent->y0);
+        cellExtent->y1 = PS_MAX(cellExtent->y1, roExtent->y1);
+        psFree(roExtent);
+    }
+
+    bool mdok;                          // Status of MD lookup
+    int x0 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.X0"); // Cell x offset
+    if (!mdok) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find CELL.X0.\n");
+        psFree(cellExtent);
+        return NULL;
+    }
+    int y0 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.Y0"); // Cell y offset
+    if (!mdok) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find CELL.Y0.\n");
+        psFree(cellExtent);
+        return NULL;
+    }
+
+    cellExtent->x0 += x0;
+    cellExtent->x1 += x0;
+    cellExtent->y0 += y0;
+    cellExtent->y1 += y0;
+
+    return cellExtent;
+}
+
+// return chip pixels included in all cells
+psRegion *pmChipPixels(const pmChip *chip)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, NULL);
+
+    psArray *cells = chip->cells;       // Array of component cells
+    psRegion *chipExtent = psRegionAlloc(INFINITY, 0, INFINITY, 0); // Extent of chip
+    for (long i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // Cell of interest
+        psRegion *cellExtent = pmCellExtent(cell); // Extent of cell
+        chipExtent->x0 = PS_MIN(chipExtent->x0, cellExtent->x0);
+        chipExtent->x1 = PS_MAX(chipExtent->x1, cellExtent->x1);
+        chipExtent->y0 = PS_MIN(chipExtent->y0, cellExtent->y0);
+        chipExtent->y1 = PS_MAX(chipExtent->y1, cellExtent->y1);
+        psFree(cellExtent);
+    }
+
+    return chipExtent;
+}
+
+// return pixels in basic FPA grid bounded by chip
+// this FPA grid has 0,0 at the 0,0 corner of one chip, and is NOT the same
+// as the astrometry focal plane coordinate system
+psRegion *pmChipExtent(const pmChip *chip)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, NULL);
+
+    psRegion *chipExtent = pmChipPixels(chip);
+    if (!chipExtent) return NULL;
+
+    bool mdok;                          // Status of MD lookup
+    int x0 = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.X0"); // Chip x offset
+    if (!mdok) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find CHIP.X0.\n");
+        psFree(chipExtent);
+        return NULL;
+    }
+    int y0 = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.Y0"); // Chip y offset
+    if (!mdok) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find CHIP.Y0.\n");
+        psFree(chipExtent);
+        return NULL;
+    }
+
+    chipExtent->x0 += x0;
+    chipExtent->x1 += x0;
+    chipExtent->y0 += y0;
+    chipExtent->y1 += y0;
+
+    return chipExtent;
+}
+
+// return FPA pixels included in all chips
+// this FPA grid has 0,0 at the 0,0 corner of one chip, and is NOT the same
+// as the astrometry focal plane coordinate system
+psRegion *pmFPAPixels(const pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    psArray *chips = fpa->chips;       // Array of component chips
+    psRegion *fpaExtent = psRegionAlloc(INFINITY, 0, INFINITY, 0); // Extent of fpa
+    for (long i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // Chip of interest
+        psRegion *chipExtent = pmChipExtent(chip); // Extent of chip
+        fpaExtent->x0 = PS_MIN(fpaExtent->x0, chipExtent->x0);
+        fpaExtent->x1 = PS_MAX(fpaExtent->x1, chipExtent->x1);
+        fpaExtent->y0 = PS_MIN(fpaExtent->y0, chipExtent->y0);
+        fpaExtent->y1 = PS_MAX(fpaExtent->y1, chipExtent->y1);
+        psFree(chipExtent);
+    }
+
+    return fpaExtent;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAExtent.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAExtent.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAExtent.h	(revision 22157)
@@ -0,0 +1,49 @@
+/* @file  pmPFAExtent.h
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-03-21 22:01:32 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_EXTENT_H
+#define PM_FPA_EXTENT_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+/// Return the extent of a readout
+///
+/// The extent is determined from an image, if present, or the CELL.TRIMSEC otherwise.
+psRegion *pmReadoutExtent(const pmReadout *readout ///< The readout of interest
+                         );
+
+/// Return the extent of a cell
+///
+/// The extent is determined from the extent of the component readouts, plus CELL.X0,Y0
+psRegion *pmCellExtent(const pmCell *cell ///< The cell of interest
+                      );
+
+// return chip pixels included in all cells
+psRegion *pmChipPixels(const pmChip *chip);
+
+/// Return the extent of a chip
+///
+/// The extent is determined from the extent of the component cells, plus CHIP.X0,Y0
+psRegion *pmChipExtent(const pmChip *chip ///< The chip of interest
+                      );
+
+// return FPA pixels included in all chips
+// this FPA grid has 0,0 at the 0,0 corner of one chip, and is NOT the same
+// as the astrometry focal plane coordinate system
+psRegion *pmFPAPixels(const pmFPA *fpa);
+
+/// Return the extent of an FPA
+///
+/// The extent is determined from the extent of the component chips.
+psRegion *pmFPAExtent(const pmFPA *fpa ///< The FPA of interest
+                     );
+
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAFlags.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAFlags.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAFlags.c	(revision 22157)
@@ -0,0 +1,341 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAFlags.h"
+
+
+/** functions to turn on/off the file_exists flag **/
+bool pmFPASetFileStatus(pmFPA *fpa, bool status)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        pmChipSetFileStatus (chip, status);
+    }
+    return true;
+}
+
+bool pmChipSetFileStatus(pmChip *chip, bool status)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+
+    chip->file_exists = status;
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCell *cell = chip->cells->data[i];
+        pmCellSetFileStatus (cell, status);
+    }
+    return true;
+}
+
+bool pmCellSetFileStatus(pmCell *cell, bool status)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+
+    cell->file_exists = status;
+    for (int i = 0; i < cell->readouts->n; i++) {
+        pmReadout *readout = cell->readouts->data[i];
+        readout->file_exists = status;
+    }
+    return true;
+}
+
+bool pmFPACheckFileStatus(const pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        if (!pmChipCheckFileStatus(chip)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool pmChipCheckFileStatus(const pmChip *chip)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    if (!chip->file_exists) {
+        return false;
+    }
+
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCell *cell = chip->cells->data[i];
+        if (!pmCellCheckFileStatus(cell)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool pmCellCheckFileStatus(const pmCell *cell)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    if (!cell->file_exists) {
+        return false;
+    }
+
+    for (int i = 0; i < cell->readouts->n; i++) {
+        pmReadout *readout = cell->readouts->data[i];
+        if (!readout->file_exists) {
+            return false;
+        }
+    }
+    return true;
+}
+
+/** functions to turn on/off the data_exists flag **/
+bool pmFPASetDataStatus(pmFPA *fpa, bool status)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        pmChipSetDataStatus (chip, status);
+    }
+    return true;
+}
+
+bool pmChipSetDataStatus(pmChip *chip, bool status)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+
+    chip->data_exists = status;
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCell *cell = chip->cells->data[i];
+        pmCellSetDataStatus (cell, status);
+    }
+    return true;
+}
+
+bool pmCellSetDataStatus (pmCell *cell, bool status)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+
+    cell->data_exists = status;
+    for (int i = 0; i < cell->readouts->n; i++) {
+        pmReadout *readout = cell->readouts->data[i];
+        readout->data_exists = status;
+    }
+    return true;
+}
+
+// pmFPA does not have its own data_exists flag; check its children
+bool pmFPACheckDataStatus (const pmFPA *fpa) {
+
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        if (chip == NULL) continue;
+        if (chip->data_exists) return true;
+    }
+    return false;
+}
+
+bool pmChipCheckDataStatus (const pmChip *chip) {
+
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+
+    return (chip->data_exists);
+}
+
+bool pmCellCheckDataStatus (const pmCell *cell) {
+
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+
+    return (cell->data_exists);
+}
+
+bool pmReadoutCheckDataStatus (const pmReadout *readout) {
+
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+
+    return (readout->data_exists);
+}
+
+bool pmFPAviewCheckDataStatus (const pmFPA *fpa, const pmFPAview *view) {
+
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    if (view->chip == -1) {
+        bool exists = pmFPACheckDataStatus (fpa);
+        return exists;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        psError(PS_ERR_IO, true, "Requested chip == %d >= fpa->chips->n == %ld", view->chip, fpa->chips->n);
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        bool exists = pmChipCheckDataStatus (chip);
+        return exists;
+    }
+
+    if (view->cell >= chip->cells->n) {
+        psError(PS_ERR_IO, true, "Requested cell == %d >= chip->cells->n == %ld", view->cell, chip->cells->n);
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        bool exists = pmCellCheckDataStatus (cell);
+        return exists;
+    }
+
+    if (view->readout >= cell->readouts->n) {
+        psError(PS_ERR_IO, true, "Requested readout == %d >= cell->readouds->n == %ld", view->readout, cell->readouts->n);
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[view->readout];
+
+    bool exists = pmReadoutCheckDataStatus (readout);
+    return exists;
+}
+
+// Set cells within a chip to be processed or not
+static bool setCellsProcess(const pmChip *chip, // Chip of interest
+                            bool process  // Process this chip?
+                           )
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+
+    psArray *cells = chip->cells;       // Component cells
+    if (! cells) {
+        return false;
+    }
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *tmpCell = cells->data[i]; // Cell of interest
+        if (tmpCell) {
+            tmpCell->process = process;
+        }
+    }
+
+    return true;
+}
+
+
+bool pmFPASelectChip(pmFPA *fpa, int chipNum, bool exclusive)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+
+    psArray *chips = fpa->chips;        // Component chips
+    if ((chips == NULL) || (chipNum >= chips->n)) {
+        return(false);
+    }
+
+    for (int i = 0 ; i < chips->n ; i++) {
+        pmChip *tmpChip = (pmChip *) chips->data[i];
+        if (tmpChip == NULL) {
+            continue;
+        }
+        if (i == chipNum) {
+            tmpChip->process = true;
+            setCellsProcess(tmpChip, true);
+        } else {
+            if (exclusive) {
+                tmpChip->process = false;
+                setCellsProcess(tmpChip, false);
+            }
+        }
+
+    }
+
+    return true;
+}
+
+// XXX this function should probably be re-defined to merge with 'setCellsProcess'
+bool pmChipSelectCell(pmChip *chip, int cellNum, bool exclusive)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+
+    psArray *cells = chip->cells;       // Component cells
+    if (!cells || cellNum > cells->n) {
+        return false;
+    }
+
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];
+        if (!cell) {
+            continue;
+        }
+        if (i == cellNum) {
+            cell->process = true;
+        } else {
+            if (exclusive) {
+                cell->process = false;
+            }
+        }
+    }
+    return true;
+}
+
+
+int pmFPAExcludeChip(pmFPA *fpa, int chipNum)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, -1);
+
+    psArray *chips = fpa->chips;        // Component chips
+    if (chips == NULL) {
+        psWarning("WARNING: fpa->chips == NULL\n");
+        return(0);
+    }
+    if ((chipNum >= chips->n) || (NULL == (pmChip *) chips->data[chipNum])) {
+        psWarning("WARNING: the specified chip (%d) does not exist.\n", chipNum);
+        return(0);
+    }
+
+    int numChips = 0;                   // Number of chips to be processed
+    for (int i = 0 ; i < chips->n ; i++) {
+        pmChip *tmpChip = (pmChip *) chips->data[i]; // Chip of interest
+        if (tmpChip != NULL) {
+            if (i == chipNum) {
+                tmpChip->process = false;
+                setCellsProcess(tmpChip, false); // Wipe out the cell as well
+            } else if (tmpChip->process) {
+                numChips++;
+            }
+        }
+    }
+
+    return(numChips);
+}
+
+
+int pmChipExcludeCell(pmChip *chip, int cellNum)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, -1);
+
+    psArray *cells = chip->cells;       // The component cells
+    if (!cells || cellNum > cells->n) {
+        return 0;
+    }
+
+    int numCells = 0;                   // Number of cells to be processed
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];
+        if (!cell) {
+            continue;
+        }
+        if (i == cellNum) {
+            cell->process = false;
+        } else {
+            numCells++;
+        }
+    }
+
+    return numCells;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAFlags.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAFlags.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAFlags.h	(revision 22157)
@@ -0,0 +1,122 @@
+/*  @file pmFPAFlags.h
+ *  @brief Functions for setting and checking the status flags within the FPA hierarchy
+ * 
+ *  @author George Gusciora, MHPCC
+ *  @author Paul Price, IfA
+ *  @author Eugene Magnier, IfA
+ * 
+ *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-05-03 20:04:01 $
+ *  Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_FLAGS_H
+#define PM_FPA_FLAGS_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+// Functions to turn on/off the file_exists flags
+
+/// Set the file_exists flag for an FPA and components
+bool pmFPASetFileStatus(pmFPA *fpa,     ///< FPA for which to set status
+                        bool status     ///< Status to set
+                       );
+
+/// Set the file_exists flag for a chip and components
+bool pmChipSetFileStatus(pmChip *chip,  ///< Chip for which to set status
+                         bool status    ///< Status to set
+                        );
+
+/// Set the file_exists flag for a cell and components
+bool pmCellSetFileStatus(pmCell *cell,  ///< Cell for which to set status
+                         bool status    ///< Status to set
+                        );
+
+// Functions to check the file_exists flags
+
+/// Return the file_exists status for an FPA and components
+bool pmFPACheckFileStatus(const pmFPA *fpa ///< FPA for which to check status
+                         );
+
+/// Return the file_exists status for a chip and components
+bool pmChipCheckFileStatus(const pmChip *chip ///< Chip for which to check status
+                          );
+
+/// Return the file_exists status for a chip and components
+bool pmCellCheckFileStatus(const pmCell *cell ///< Cell for which to check status
+                          );
+
+// Functions to turn on/off the data_exists flags
+
+/// Set the data_exists flag for an FPA and components
+bool pmFPASetDataStatus(pmFPA *fpa,     ///< FPA for which to set status
+                        bool status     ///< Status to set
+                       );
+
+/// Set the data_exists flag for a chip and components
+bool pmChipSetDataStatus(pmChip *chip,  ///< Chip for which to set status
+                         bool status    ///< Status to set
+                        );
+
+/// Set the data_exists flag for a cell and components
+bool pmCellSetDataStatus(pmCell *cell,  ///< Cell for which to set status
+                         bool status    ///< Status to set
+                        );
+
+
+// Functions the check the data_exists flags
+
+/// Check data_exists for the element of this fpa at this view
+bool pmFPAviewCheckDataStatus (const pmFPA *fpa, ///< FPA to check
+			       const pmFPAview *view ///< check for this view 
+  );
+
+/// Check data_exists for this fpa
+bool pmFPACheckDataStatus (const pmFPA *fpa ///< FPA to check
+  );
+
+/// Check data_exists for this chip
+bool pmChipCheckDataStatus (const pmChip *chip ///< Chip to check
+);
+
+/// Check data_exists for this cell
+bool pmCellCheckDataStatus (const pmCell *cell ///< Cell to check
+);
+
+/// Check data_exists for this readout
+bool pmReadoutCheckDataStatus (const pmReadout *readout ///< Readout to check
+);
+
+
+// Functions to set the process flags
+
+/// Select a chip within an FPA for processing
+///
+/// If exclusive is true, the specified chip is the only chip to be processed.  A negative value for chipNum
+/// is valid and, in combinations with exclusive, de-selects all chips.
+bool pmFPASelectChip(pmFPA *fpa,        ///< FPA containing the chip of interest
+                     int chipNum,       ///< Chip number to select
+                     bool exclusive     ///< Process this chip exclusive of the others?
+                    );
+
+/// Select a chip within a chip for processing
+///
+/// If exclusive is true, the specified cell is the only chip to be processed.  A negative value for cellNum
+/// is valid and, in combinations with exclusive, de-selects all chips.
+bool pmChipSelectCell(pmChip *chip,     ///< Chip containing the cell of interest
+                      int cellNum,      ///< Cell number to select
+                      bool exclusive    ///< Process this cell exclusive of the others?
+                     );
+
+/// Exclude a chip within an FPA from processing
+int pmFPAExcludeChip(pmFPA *fpa,        ///< FPA containing the chip of interest
+                     int chipNum        ///< Chip number to exclude
+                    );
+
+/// Exclude a cell within a chip from processing
+int pmChipExcludeCell(pmChip *chip,     ///< Chip containing the chip of interest
+                      int cellNum       ///< Cell number to exclude
+                     );
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAHeader.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAHeader.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAHeader.c	(revision 22157)
@@ -0,0 +1,77 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmConcepts.h"
+#include "pmFPAHeader.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmCellReadHeader(pmCell *cell, psFits *fits, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    if (!cell->hdu) {
+        return pmChipReadHeader(cell->parent, fits, config);
+    }
+    if (!pmHDUReadHeader(cell->hdu, fits)) {
+        psError(PS_ERR_IO, false, "Unable to read header for cell.\n");
+        return false;
+    }
+
+    return pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_DATABASE, false, config);
+}
+
+
+bool pmChipReadHeader(pmChip *chip, psFits *fits, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    if (!chip->hdu) {
+        return pmFPAReadHeader(chip->parent, fits, config);
+    }
+    if (!pmHDUReadHeader(chip->hdu, fits)) {
+        psError(PS_ERR_IO, false, "Unable to read header for cell.\n");
+        return false;
+    }
+
+    if (!pmConceptsReadChip(chip, PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_DATABASE, true, true, config)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read concepts for chip.\n");
+        return false;
+    }
+
+    return true;
+}
+
+
+bool pmFPAReadHeader(pmFPA *fpa, psFits *fits, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    if (!fpa->hdu) {
+        return false;
+    }
+    if (!pmHDUReadHeader(fpa->hdu, fits)) {
+        psError(PS_ERR_IO, false, "Unable to read header for cell.\n");
+        return false;
+    }
+
+    if (!pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_DATABASE, true, config)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read concepts for FPA.\n");
+        return false;
+    }
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAHeader.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAHeader.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAHeader.h	(revision 22157)
@@ -0,0 +1,44 @@
+/*  @file pmFPAHeader.h
+ *  @brief Functions read FITS headers for FPA components
+ *
+ *  @author Paul Price, IfA
+ *
+ *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-06-17 22:16:38 $
+ *  Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_HEADER_H
+#define PM_FPA_HEADER_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+/// Read the FITS header (and ingest concepts) for an FPA, if it exists at this level
+///
+/// Returns false if there was a problem.  Returns true if it successfully read the header, or if the header
+/// was already there.  No iteration to lower levels is performed.
+bool pmFPAReadHeader(pmFPA *fpa,        ///< FPA for which to read header
+                     psFits *fits,       ///< FITS file handle
+                     pmConfig *config   ///< Configuration
+                    );
+
+/// Read the FITS header (and ingest concepts) for a chip, if it exists at this level
+///
+/// Returns false if there was a problem.  Returns true if it successfully read the header, or if the header
+/// was already there.  No iteration to lower levels is performed.
+bool pmChipReadHeader(pmChip *chip,     ///< Chip for which to read header
+                      psFits *fits,      ///< FITS file handle
+                     pmConfig *config   ///< Configuration
+                     );
+
+/// Read the FITS header (and ingest concepts) for a cell, if it exists at this level
+///
+/// Returns false if there was a problem.  Returns true if it successfully read the header, or if the header
+/// was already there.  No iteration to lower levels is performed.
+bool pmCellReadHeader(pmCell *cell,     ///< Cell for which to read header
+                      psFits *fits,      ///< FITS file handle
+                     pmConfig *config   ///< Configuration
+                     );
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPALevel.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPALevel.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPALevel.c	(revision 22157)
@@ -0,0 +1,67 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <strings.h>
+#include <pslib.h>
+
+#include "pmFPALevel.h"
+
+static const char *nameNONE = "NONE";   ///< Name for PM_FPA_LEVEL_NONE
+static const char *nameFPA = "FPA";     ///< Name for PM_FPA_LEVEL_FPA
+static const char *nameCHIP = "CHIP";   ///< Name for PM_FPA_LEVEL_CHIP
+static const char *nameCELL = "CELL";   ///< Name for PM_FPA_LEVEL_CELL
+static const char *nameREADOUT = "READOUT"; ///< Name for PM_FPA_LEVEL_READOUT
+static const char *nameCHUNK = "CHUNK"; ///< Name for PM_FPA_LEVEL_CHUNK
+
+const char *pmFPALevelToName(pmFPALevel level)
+{
+    switch (level) {
+    case PM_FPA_LEVEL_NONE:
+        return nameNONE;
+    case PM_FPA_LEVEL_FPA:
+        return nameFPA;
+    case PM_FPA_LEVEL_CHIP:
+        return nameCHIP;
+    case PM_FPA_LEVEL_CELL:
+        return nameCELL;
+    case PM_FPA_LEVEL_READOUT:
+        return nameREADOUT;
+    case PM_FPA_LEVEL_CHUNK:
+        return nameCHUNK;
+    default:
+        psAbort("You can't get here; level = %d", level);
+    }
+    return NULL;
+}
+
+pmFPALevel pmFPALevelFromName(const char *name)
+{
+    if (name == NULL) {
+        return PM_FPA_LEVEL_NONE;
+    }
+    if (!strcasecmp(name, nameFPA)) {
+        return PM_FPA_LEVEL_FPA;
+    }
+    if (!strcasecmp(name, nameCHIP)) {
+        return PM_FPA_LEVEL_CHIP;
+    }
+    if (!strcasecmp(name, nameCELL)) {
+        return PM_FPA_LEVEL_CELL;
+    }
+    if (!strcasecmp(name, nameREADOUT)) {
+        return PM_FPA_LEVEL_READOUT;
+    }
+    if (!strcasecmp(name, nameCHUNK)) {
+        return PM_FPA_LEVEL_CHUNK;
+    }
+    if (!strcasecmp(name, nameNONE)) {
+        return PM_FPA_LEVEL_NONE;
+    }
+
+    psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unrecognised FPA level name: %s", name);
+    return PM_FPA_LEVEL_NONE;
+}
+
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPALevel.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPALevel.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPALevel.h	(revision 22157)
@@ -0,0 +1,37 @@
+/* @file pmFPALevel.h
+ * @brief Defines enum and string representations for the FPA levels
+ *
+ * @author Eugene Magnier, IfA
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-02-07 00:10:08 $
+ * Copyright 2005-2008 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_LEVEL_H
+#define PM_FPA_LEVEL_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+/// Specify the level of the FPA hierarchy
+typedef enum {
+    PM_FPA_LEVEL_NONE,                  ///< No particular level specified
+    PM_FPA_LEVEL_FPA,                   ///< Level corresponds to an FPA
+    PM_FPA_LEVEL_CHIP,                  ///< Level corresponds to a Chip
+    PM_FPA_LEVEL_CELL,                  ///< Level corresponds to a Cell
+    PM_FPA_LEVEL_READOUT,               ///< Level corresponds to a Readout
+    PM_FPA_LEVEL_CHUNK,                 ///< Level corresponds to a chunk
+} pmFPALevel;
+
+
+/// Return the string representation of the FPA level
+const char *pmFPALevelToName(pmFPALevel level ///< Level enum
+                            );
+
+/// Return the enum representation of the FPA level
+pmFPALevel pmFPALevelFromName(const char *name ///< Level name
+                             );
+/// @}
+#endif // #ifndef PM_FPA_LEVEL_H
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAMaskWeight.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAMaskWeight.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAMaskWeight.c	(revision 22157)
@@ -0,0 +1,432 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmHDUUtils.h"
+#include "pmHDUGenerate.h"
+#include "pmFPAMaskWeight.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static (private) functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Create the parent mask images that reside in the HDU
+static void createParentMasks(pmHDU *hdu // The HDU for which to create
+                             )
+{
+    assert(hdu);
+    assert(hdu->images);
+
+    // Generate the parent mask images
+    psArray *images = hdu->images;      // Array of images
+    psArray *masks = hdu->masks;        // Array of masks
+    if (!masks) {
+        masks = psArrayAlloc(images->n);
+        hdu->masks = masks;
+    }
+
+    for (long i = 0; i < images->n; i++) {
+        psImage *image = images->data[i]; // The image for this readout
+        if (!image || masks->data[i]) {
+            continue;
+        }
+        masks->data[i] = psImageAlloc(image->numCols, image->numRows, PS_TYPE_U8);
+        psImageInit(masks->data[i], 0);
+    }
+
+    return;
+}
+
+// Create the parent weight images that reside in the HDU
+static void createParentWeights(pmHDU *hdu // The HDU for which to create
+                               )
+{
+    assert(hdu);
+    assert(hdu->images);
+
+    // Generate the parent mask images
+    psArray *images = hdu->images;      // Array of images
+    psArray *weights = hdu->weights;    // Array of weight images
+    if (!weights) {
+        weights = psArrayAlloc(images->n);
+        hdu->weights = weights;
+    }
+
+    for (long i = 0; i < images->n; i++) {
+        psImage *image = images->data[i]; // The image for this readout
+        if (!image || weights->data[i]) {
+            continue;
+        }
+        weights->data[i] = psImageAlloc(image->numCols, image->numRows, PS_TYPE_F32);
+    }
+
+    return;
+}
+
+// Identify a readout within the HDU, on the basis of the image pointer.
+// This is a little dirty, but hopefully should work....
+static long identifyReadout(pmHDU *hdu, // The HDU containing the readouts
+                            pmReadout *readout // The readout to be identified
+                           )
+{
+    assert(hdu);
+    assert(readout);
+
+    long index = -1;                    // Index of the readout
+    for (long i = 0; i < hdu->images->n && index == -1; i++) {
+        if (hdu->images->data[i] == readout->image->parent) {
+            index = i;
+        }
+    }
+
+    return index;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmReadoutSetMask(pmReadout *readout, psMaskType satMask, psMaskType badMask)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_IMAGE_NON_NULL(readout->image, false);
+
+    pmCell *cell = readout->parent;     // The parent cell
+    bool mdok = true;                   // Status of MD lookup
+
+    // Get the "concepts" of interest
+    float saturation = psMetadataLookupF32(&mdok, cell->concepts, "CELL.SATURATION"); // Saturation level
+    if (!mdok || isnan(saturation)) {
+        psError(PS_ERR_IO, true, "CELL.SATURATION is not set --- unable to set mask.\n");
+        return false;
+    }
+    float bad = psMetadataLookupF32(&mdok, cell->concepts, "CELL.BAD"); // Bad level
+    if (!mdok || isnan(bad)) {
+        psError(PS_ERR_IO, true, "CELL.BAD is not set --- unable to set mask.\n");
+        return false;
+    }
+    psTrace("psModules.camera", 5, "Saturation: %f, bad: %f\n", saturation, bad);
+
+
+    // Set up the mask
+    psImage *image = readout->image;    // The image pixels
+    if (!readout->mask) {
+        // Generate a (throwaway) mask image, if required
+        readout->mask = psImageAlloc(image->numCols, image->numRows, PS_TYPE_U8);
+    }
+    psImage *mask = readout->mask;      // The mask pixels
+    psImageInit(mask, 0);
+
+    // Dereference pointers for speed
+    psF32 **imageData = image->data.F32;// The image
+    psU8  **maskData  = mask->data.U8;  // The mask
+
+    for (int i = 0; i < image->numRows; i++) {
+        for (int j = 0; j < image->numCols; j++) {
+            if (imageData[i][j] >= saturation) {
+                maskData[i][j] |= satMask;
+            }
+            if (imageData[i][j] <= bad) {
+                maskData[i][j] |= badMask;
+            }
+            if (!isfinite(imageData[i][j])) {
+                maskData[i][j] |= badMask;
+            }
+        }
+    }
+
+    return true;
+}
+
+// XXX this function creates the mask pixels, or uses the existing mask
+// pixels.  currently, it will set mask bits if (value <= BAD) or (value >= SATURATION)
+// should we optionally ignore these tests?
+bool pmReadoutGenerateMask(pmReadout *readout, psMaskType satMask, psMaskType badMask)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+
+    pmCell *cell = readout->parent;     // The parent cell
+    bool mdok = true;                   // Status of MD lookup
+
+    // Create the mask image if required
+    if (!readout->mask) {
+        psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim section
+        if (!mdok || psRegionIsNaN(*trimsec)) {
+            psError(PS_ERR_IO, true, "CELL.TRIMSEC is not set --- unable to set mask.\n");
+            return false;
+        }
+
+        pmHDU *hdu = pmHDUFromCell(cell);   // The HDU containing the cell's pixels
+        PS_ASSERT_PTR_NON_NULL(hdu, false);
+        if (!hdu->images && !pmHDUGenerateForCell(cell)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to generate HDU for cell.\n");
+            return false;
+        }
+
+        createParentMasks(hdu);
+
+        // Need to identify which readout we're working with....
+        long index = identifyReadout(hdu, readout); // Index of the readout
+        if (index == -1) {
+            psError(PS_ERR_UNKNOWN, true, "Unable to identify readout image in HDU.\n");
+            return false;
+        }
+
+        psImage *mask = psImageSubset(hdu->masks->data[index], *trimsec); // The mask pixels
+        if (!mask) {
+            psString trimsecString = psRegionToString(*trimsec);
+            psError(PS_ERR_UNKNOWN, false, "Unable to set mask from HDU with trimsec: %s.\n", trimsecString);
+            psFree(trimsecString);
+            return false;
+        }
+        psImageInit(mask, 0);
+        assert (readout->mask == NULL); // or else this is a memory leak.
+        readout->mask = mask;
+    }
+
+    return pmReadoutSetMask(readout, satMask, badMask);
+}
+
+bool pmReadoutSetWeight(pmReadout *readout, bool poisson)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+
+    pmCell *cell = readout->parent;     // The parent cell
+    bool mdok = true;                   // Status of MD lookup
+
+    // Get the "concepts" of interest
+    float gain = psMetadataLookupF32(&mdok, cell->concepts, "CELL.GAIN"); // Cell gain
+    if (!mdok || isnan(gain)) {
+        psError(PS_ERR_IO, true, "CELL.GAIN is not set --- unable to set weight.\n");
+        return false;
+    }
+    float readnoise = psMetadataLookupF32(&mdok, cell->concepts, "CELL.READNOISE"); // Cell read noise
+    if (!mdok || isnan(readnoise)) {
+        psError(PS_ERR_IO, true, "CELL.READNOISE is not set --- unable to set weight.\n");
+        return false;
+    }
+
+    if (poisson) {
+        // Set weight image to the variance in ADU = f/g + rn^2
+        psImage *image = readout->image;    // The image pixels
+        readout->weight = (psImage*)psBinaryOp(readout->weight, image, "/", psScalarAlloc(gain, PS_TYPE_F32));
+
+        // a negative weight is non-sensical. if the image value drops below 1, the weight must be 1.
+        readout->weight = (psImage*)psUnaryOp(readout->weight, readout->weight, "abs");
+        readout->weight = (psImage*)psBinaryOp(readout->weight, readout->weight, "max",
+                                               psScalarAlloc(1, PS_TYPE_F32));
+    } else {
+        // Just use the read noise
+        if (!readout->weight) {
+            readout->weight = psImageAlloc(readout->image->numCols, readout->image->numRows, PS_TYPE_F32);
+        }
+        psImageInit(readout->weight, 0.0);
+    }
+
+    readout->weight = (psImage*)psBinaryOp(readout->weight, readout->weight, "+",
+                                           psScalarAlloc(readnoise*readnoise/gain/gain, PS_TYPE_F32));
+
+    return true;
+}
+
+// this function creates the weight pixels, or uses the existing weight pixels.  it will set
+// the noise pixel values only if the weight image is not supplied
+bool pmReadoutGenerateWeight(pmReadout *readout, bool poisson)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+
+    pmCell *cell = readout->parent;     // The parent cell
+    bool mdok = true;                   // Status of MD lookup
+
+    // Create the weight image if required
+    if (readout->weight)
+        return true;
+
+    psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim section
+    if (!mdok || psRegionIsNaN(*trimsec)) {
+        psError(PS_ERR_IO, true, "CELL.TRIMSEC is not set --- unable to set weight.\n");
+        return false;
+    }
+
+    pmHDU *hdu = pmHDUFromCell(cell);   // The HDU containing the cell's pixels
+    PS_ASSERT_PTR_NON_NULL(hdu, false);
+    if (!hdu->images && !pmHDUGenerateForCell(cell)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to generate HDU for cell.\n");
+        return false;
+    }
+
+    createParentWeights(hdu);
+
+    // Need to identify which readout we're working with....
+    long index = identifyReadout(hdu, readout); // Index of the readout
+    if (index == -1) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to identify readout image in HDU.\n");
+        return false;
+    }
+
+    psImage *weight = psImageSubset(hdu->weights->data[index], *trimsec); // The weight pixels
+    if (!weight) {
+        psString trimsecString = psRegionToString(*trimsec);
+        psError(PS_ERR_UNKNOWN, false, "Unable to set weight from HDU with trimsec: %s.\n",
+                trimsecString);
+        psFree(trimsecString);
+        return false;
+    }
+    psImageInit(weight, 0);
+    readout->weight = weight;
+
+    return pmReadoutSetWeight(readout, poisson);
+}
+
+bool pmReadoutGenerateMaskWeight(pmReadout *readout, psMaskType satMask, psMaskType badMask, bool poisson)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+
+    bool success = true;                // Was everything successful?
+
+    success &= pmReadoutGenerateMask(readout, satMask, badMask);
+    success &= pmReadoutGenerateWeight(readout, poisson);
+
+    return success;
+}
+
+bool pmCellGenerateMaskWeight(pmCell *cell, psMaskType satMask, psMaskType badMask, bool poisson)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+
+    bool success = true;                // Was everything successful?
+    psArray *readouts = cell->readouts; // Array of readouts
+    for (int i = 0; i < readouts->n; i++) {
+        pmReadout *readout = readouts->data[i]; // The readout
+        success &= pmReadoutGenerateMaskWeight(readout, poisson, satMask, badMask);
+    }
+
+    return success;
+}
+
+
+bool pmReadoutWeightRenorm(const pmReadout *readout, psMaskType maskVal, psStatsOptions meanStat,
+                           psStatsOptions stdevStat, int width, psRandom *rng)
+{
+    PM_ASSERT_READOUT_NON_NULL(readout, false);
+    PM_ASSERT_READOUT_IMAGE(readout, false);
+    PM_ASSERT_READOUT_WEIGHT(readout, false);
+    PS_ASSERT_INT_POSITIVE(width, false);
+
+    if (!psMemIncrRefCounter(rng)) {
+        rng = psRandomAlloc(PS_RANDOM_TAUS, 0);
+    }
+
+    psImage *image = readout->image, *mask = readout->mask, *weight = readout->weight; // Readout images
+    int numCols = image->numCols, numRows = image->numRows; // Size of images
+    int xNum = numCols / width + 1, yNum = numRows / width + 1; // Number of renormalisation regions
+    float xSize = numCols / (float)xNum, ySize = numRows / (float)yNum; // Size of renormalisation regions
+
+    psStats *meanStats = psStatsAlloc(meanStat), *stdevStats = psStatsAlloc(stdevStat); // Statistics
+    psVector *buffer = NULL;
+
+    for (int j = 0; j < yNum; j++) {
+        // Bounds in y
+        int yMin = j * ySize;
+        int yMax = (j + 1) * ySize;
+        for (int i = 0; i < xNum; i++) {
+            // Bounds in x
+            int xMin = i * xSize;
+            int xMax = (i + 1) * xSize;
+
+            psRegion region = psRegionSet(xMin, xMax, yMin, yMax); // Region of interest
+            psImage *subImage = psImageSubset(image, region); // Sub-image of the image pixels
+            psImage *subWeight = psImageSubset(weight, region); // Sub image of the weight pixels
+            psImage *subMask = mask ? psImageSubset(mask, region) : NULL; // Sub-image of the mask pixels
+
+            if (!psImageBackground(stdevStats, &buffer, subImage, subMask, maskVal, rng) ||
+                !psImageBackground(meanStats, &buffer, subWeight, subMask, maskVal, rng)) {
+                // Nothing we can do about it, but don't want to keel over and die, so do our best to flag it.
+                psString regionStr = psRegionToString(region); // String with region
+                psWarning("Unable to measure statistics over %s", regionStr);
+                psFree(regionStr);
+                psErrorClear();
+                psImageInit(subWeight, NAN);
+                if (subMask) {
+                    psImageInit(subMask, maskVal);
+                }
+            } else {
+                double meanVar = psStatsGetValue(meanStats, meanStat); // Mean of variance map
+                double stdev = psStatsGetValue(stdevStats, stdevStat); // Standard deviation of image
+                psTrace("psModules.camera", 3,
+                        "Region [%d:%d,%d:%d] has variance %lf, but mean of variance map is %lf\n",
+                        xMin, xMax, yMin, yMax, PS_SQR(stdev), meanVar);
+                psBinaryOp(subWeight, subWeight, "*", psScalarAlloc(PS_SQR(stdev) / meanVar, PS_TYPE_F32));
+            }
+
+            psFree(subImage);
+            psFree(subWeight);
+            psFree(subMask);
+        }
+    }
+    psFree(meanStats);
+    psFree(stdevStats);
+    psFree(rng);
+    psFree(buffer);
+
+    return true;
+}
+
+
+bool pmReadoutMaskNonfinite(pmReadout *readout, psMaskType maskVal)
+{
+    PM_ASSERT_READOUT_NON_NULL(readout, false);
+    PM_ASSERT_READOUT_IMAGE(readout, false);
+
+    psImage *image = readout->image;    // Readout's image
+    psImage *weight = readout->weight;  // Readout's weight
+    int numCols = image->numCols, numRows = image->numRows; // Size of image
+
+    if (!readout->mask) {
+        readout->mask = psImageAlloc(numCols, numRows, PS_TYPE_MASK);
+    }
+    psImage *mask = readout->mask;      // Readout's mask
+
+    for (int y = 0; y < numRows; y++) {
+        for (int x = 0; x < numCols; x++) {
+            if (!isfinite(image->data.F32[y][x]) || (weight && !isfinite(weight->data.F32[y][x]))) {
+                mask->data.PS_TYPE_MASK_DATA[y][x] |= maskVal;
+            }
+        }
+    }
+
+    return true;
+}
+
+
+
+bool pmReadoutMaskApply(pmReadout *readout, psMaskType maskVal)
+{
+    PM_ASSERT_READOUT_NON_NULL(readout, false);
+    PM_ASSERT_READOUT_IMAGE(readout, false);
+    PM_ASSERT_READOUT_MASK(readout, false);
+
+    int numCols = readout->image->numCols, numRows = readout->image->numRows; // Size of image
+    psMaskType **maskData = readout->mask->data.PS_TYPE_MASK_DATA; // Dereference mask
+    psF32 **imageData = readout->image->data.F32;// Dereference image
+    psF32 **weightData = readout->weight ? readout->weight->data.F32 : NULL; // Dereference weight map
+
+    for (int y = 0; y < numRows; y++) {
+        for (int x = 0; x < numCols; x++) {
+            if (maskData[y][x] & maskVal) {
+                imageData[y][x] = NAN;
+                if (weightData) {
+                    weightData[y][x] = NAN;
+                }
+            }
+        }
+    }
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAMaskWeight.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAMaskWeight.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAMaskWeight.h	(revision 22157)
@@ -0,0 +1,127 @@
+/* @file pmFPAHeader.h
+ * @brief Functions read FITS headers for FPA components
+ *
+ * @author Paul Price, IfA
+ * @author Eugene Magnier, IfA
+ *
+ * @version $Revision: 1.13.8.1 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-07-11 18:35:04 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_MASK_WEIGHT_H
+#define PM_FPA_MASK_WEIGHT_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+#if 0
+/// Pixel mask values
+typedef enum {
+    PM_MASK_CLEAR    = 0x00,            ///< The pixel is not masked
+    PM_MASK_BLANK    = 0x01,            ///< The pixel is blank or has no (valid) data
+    PM_MASK_FLAT     = 0x02,            ///< The pixel is non-positive in the flat-field
+    PM_MASK_DETECTOR = 0x02,            ///< The detector pixel is bad (e.g., bad column, charge trap)
+    PM_MASK_SAT      = 0x04,            ///< The pixel is saturated in the image of interest
+    PM_MASK_BAD      = 0x04,            ///< The pixel is low in the image of interest
+    PM_MASK_RANGE    = 0x04,            ///< The pixel is out of range in the image of interest
+    PM_MASK_CR       = 0x08,            ///< The pixel is probably a CR
+    PM_MASK_SPARE1   = 0x10,            ///< Spare mask value
+    PM_MASK_SPARE2   = 0x20,            ///< Spare mask value
+    PM_MASK_SUSPECT  = 0x40,            ///< The pixel is suspected of being bad, but may not be
+    PM_MASK_MARK     = 0x80,            ///< The pixel is marked as temporarily ignored
+} pmMaskValue;
+#define PM_MASK_MARK 0x80            ///< The pixel is marked as temporarily ignored
+#define PM_MASK_SAT  0x04            ///< The pixel is saturated in the image of interest
+#endif
+
+/// Set a temporary readout mask using CELL.SATURATION and CELL.BAD
+///
+/// Identifies pixels that are saturated (>= CELL.SATURATION) or bad (<= CELL.BAD).  The mask that is produced
+/// within the readout is temporary --- it is not added to the HDU.  This is intended for when the user is
+/// iterating using pmReadoutReadNext, in which case the HDU can't be generated.
+bool pmReadoutSetMask(pmReadout *readout, ///< Readout for which to set mask
+                      psMaskType satMask, ///< Mask value to give saturated pixels
+                      psMaskType badMask  ///< Mask value to give bad (low) pixels
+    );
+
+
+/// Set a temporary readout weight map using CELL.GAIN and CELL.READNOISE
+///
+/// Calculates weights (actually variances) for each pixel using photon statistics and the cell gain
+/// (CELL.GAIN) and read noise (CELL.READNOISE).  The weight map that is produced within the readout is
+/// temporary --- it is not added to the HDU.  This is intended for when the user is iterating using
+/// pmReadoutReadNext, in which case the HDU can't be generated.
+bool pmReadoutSetWeight(pmReadout *readout, ///< Readout for which to set weight
+                        bool poisson    ///< Use poisson weights (in addition to read noise)?
+                       );
+
+/// Generate a readout mask (suitable for output) using CELL.SATURATION and CELL.BAD
+///
+/// Identifies pixels that are saturated (>= CELL.SATURATION) or bad (<= CELL.BAD).  The mask that is produced
+/// is suitable for output (complete with HDU entry).  This is intended for most operations.
+bool pmReadoutGenerateMask(pmReadout *readout, ///< Readout for which to generate mask
+                           psMaskType sat, ///< Mask value to give saturated pixels
+                           psMaskType bad ///< Mask value to give bad (low) pixels
+    );
+
+/// Generate a weight map (suitable for output) using CELL.GAIN and CELL.READNOISE
+///
+/// Calculates weights (actually variances) for each pixel using photon statistics and the cell gain
+/// (CELL.GAIN) and read noise (CELL.READNOISE).  The weight map that is produced within the readout is
+/// suitable for output (complete with HDU entry).  This is intended for most operations.
+bool pmReadoutGenerateWeight(pmReadout *readout, ///< Readout for which to generate weight
+                             bool poisson    ///< Use poisson weights (in addition to read noise)?
+                            );
+
+/// Generate mask and weight map for a readout
+///
+/// Calls pmReadoutGenerateMask and pmReadoutGenerateWeight for the readout
+bool pmReadoutGenerateMaskWeight(pmReadout *readout, ///< Readout for which to generate mask and weights
+                                 psMaskType sat, ///< Mask value to give saturated pixels
+                                 psMaskType bad, ///< Mask value to give bad (low) pixels
+                                 bool poisson ///< Use poisson weights (in addition to read noise)?
+                                );
+
+/// Generate mask and weight maps for all readouts within a cell
+///
+/// Calls pmReadoutGenerateMaskWeight for each readout within the cell.
+bool pmCellGenerateMaskWeight(pmCell *cell, ///< Cell for which to generate mask and weights
+                              psMaskType sat, ///< Mask value to give saturated pixels
+                              psMaskType bad, ///< Mask value to give bad (low) pixels
+                              bool poisson ///< Use poisson weights (in addition to read noise)?
+                             );
+
+/// Renormalise the weight map to match the actual variance
+///
+/// The variance in the image is measured in patches, and the variance map is adjusted so that the mean for
+/// that patch corresponds.
+bool pmReadoutWeightRenorm(const pmReadout *readout, // Readout to normalise
+                           psMaskType maskVal, // Value to mask
+                           psStatsOptions meanStat, // Statistic to measure the mean (of the variance map)
+                           psStatsOptions stdevStat, // Statistic to measure the stdev (of the image)
+                           int width,   // Width of patch (pixels)
+                           psRandom *rng // Random number generator (for sub-sampling images)
+    );
+
+/// Explicitly mask non-finite pixels
+///
+/// Since unmasked non-finite pixels can occur (e.g., by out-of-range in quantisation), it is sometimes
+/// necessary to mask them explicitly.  Non-finite pixels in the image or weight have their mask OR-ed with
+/// the provided value.
+bool pmReadoutMaskNonfinite(pmReadout *readout, ///< Readout to mask
+                            psMaskType maskVal ///< Mask value to give non-finite pixels
+    );
+
+/// Apply a mask to the image and weight map
+///
+/// Unfortunately, image subtraction may result in a bi-modal image in masked areas, which can upset image
+/// statistics (very important for quantising images so that a product can be written out!).  This function
+/// sets masked areas to NAN in the image and weight.
+bool pmReadoutMaskApply(pmReadout *readout, ///< Readout to mask
+                        psMaskType maskVal ///< Mask value for which to apply mask
+    );
+
+
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAMosaic.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAMosaic.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAMosaic.c	(revision 22157)
@@ -0,0 +1,1358 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAFlags.h"
+#include "pmConceptsAverage.h"
+#include "pmHDUUtils.h"
+#include "pmConfig.h"
+#include "pmAstrometryWCS.h"
+#include "pmFPAExtent.h"
+
+#include "pmFPAMosaic.h"
+
+
+#define CELL_LIST_BUFFER 10             // Buffer size for cell lists
+
+#define BLANK_VALUE 0.0                 // Value for pixels that are blank in the mosaicked image (e.g., //
+                                        // between cells).
+                                        // XXX This should ultimately be set to NAN, but psphot doesn't like
+                                        // that (masking needs to be more thorough).
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static (private) functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Do two regions overlap?
+#define REGIONS_OVERLAP(region1, region2) \
+((region1->x0 > region2->x0 && region1->x0 < region2->x1) || \
+ (region1->x1 > region2->x0 && region1->x1 < region2->x1) || \
+ (region1->y0 > region2->y0 && region1->y0 < region2->y1) || \
+ (region1->y1 > region2->y0 && region1->y1 < region2->y1))
+
+// Compare a value with a maximum and minimum
+#define COMPARE(value,min,max) \
+if ((value) < (min)) { \
+    (min) = (value); \
+} \
+if ((value) > (max)) { \
+    (max) = (value); \
+}
+
+// Update a concept to the assumed value
+#define FIX_CONCEPT(SOURCE, NAME, TYPE, VALUE) \
+psMetadataItem *item = psMetadataLookup(SOURCE, NAME); \
+item->data.TYPE = VALUE;
+
+// Get the bounds for an chip's pixels on the HDU
+static bool chipBounds(psRegion *bounds, // The bounds for the chip
+                       const pmChip *chip // The chip to examine for contiguity
+                      )
+{
+    assert(chip);
+
+    psArray *cells = chip->cells;       // The array of cells
+    bool mdok = true;                   // Status of MD lookup
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // Cell of interest
+        psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim section
+        if (!mdok || !trimsec || psRegionIsNaN(*trimsec)) {
+            psError(PS_ERR_UNKNOWN, true, "CELL.TRIMSEC hasn't been set for cell %d.\n", i);
+            return false;
+        }
+
+        if (trimsec->x0 < bounds->x0) {
+            bounds->x0 = trimsec->x0;
+        }
+        if (trimsec->x1 > bounds->x1) {
+            bounds->x1 = trimsec->x1;
+        }
+        if (trimsec->y0 < bounds->y0) {
+            bounds->y0 = trimsec->y0;
+        }
+        if (trimsec->y1 > bounds->y1) {
+            bounds->y1 = trimsec->y1;
+        }
+    }
+
+    return true;
+}
+
+// Make sure the TRIMSEC doesn't overlap with the established image bounds
+static bool chipContiguousTrimsec(psRegion *bounds, // The bounds of the image, altered if primary==true
+                                  const pmChip *chip // The chip to examine for contiguity
+                                 )
+{
+    assert(bounds);
+    assert(chip);
+
+    psArray *cells = chip->cells;       // The array of cells
+    bool mdok = true;                   // Status of MD lookup
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // Cell of interest
+        psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim section
+        if (!mdok || !trimsec || psRegionIsNaN(*trimsec)) {
+            psError(PS_ERR_UNKNOWN, true, "CELL.TRIMSEC hasn't been set for cell %d.\n", i);
+            return false;
+        }
+
+        if (REGIONS_OVERLAP(trimsec, bounds)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+// Make sure the BIASSEC doesn't overlap with the established image bounds
+static bool chipContiguousBiassec(psRegion *bounds, // The bounds of the image, altered if primary==true
+                                  const pmChip *chip // The chip to examine for contiguity
+                                 )
+{
+    assert(bounds);
+    assert(chip);
+
+    // Check that the biases don't get in the way
+    psArray *cells = chip->cells;       // The array of cells
+    bool mdok = true;                   // Status of MD lookup
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // Cell of interest
+        psList *biassecs = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.BIASSEC"); // Bias sections
+        if (!mdok || !biassecs) {
+            psError(PS_ERR_UNKNOWN, true, "CELL.BIASSEC hasn't been set for cell %d.\n", i);
+            return false;
+        }
+        if (biassecs->n == 0) {
+            // No point allocating an iterator if there's nothing there to iterate on
+            continue;
+        }
+        psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, false); // Iterator
+        psRegion *biassec = NULL;       // Bias section from iteration
+        while ((biassec = psListGetAndIncrement(biassecsIter))) {
+            if (psRegionIsNaN(*biassec)) {
+                continue;
+            }
+            if (REGIONS_OVERLAP(biassec, bounds)) {
+                psFree(biassecsIter);
+                return false;
+            }
+        }
+        psFree(biassecsIter);
+    }
+
+    // If we've gotten this far, everything is fine.
+    return true;
+}
+
+// Are the pixels for the FPA contiguous on the HDU?
+// Work this out by examining all the CELL.TRIMSEC and CELL.BIASSEC regions for the component cells
+static bool fpaContiguous(psRegion *bounds, // The bounds of the image, returned
+                          const pmFPA *fpa // The FPA to examine for contiguity
+                         )
+{
+    assert(bounds);
+    assert(fpa);
+
+    *bounds = psRegionSet(INFINITY, 0, INFINITY, 0);
+
+    // Get the size of the pixels on the HDU
+    psArray *chips = fpa->chips;        // The array of chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // Chip of interest
+        if (!chipBounds(bounds, chip)) {
+            return false;
+        }
+    }
+
+    // Make sure the bias regions don't get in the way of the HDU
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // Chip of interest
+        if (!chipContiguousBiassec(bounds, chip)) {
+            return false;
+        }
+    }
+
+    // If we got through it all, they must all be contiguous
+    return true;
+}
+
+
+
+// Check a cell for niceness in the parity and binning
+static bool niceCellParityBinning(int *xBin, int *yBin, // Binning for cell, to be returned
+                                  const pmCell *cell // Cell to check for niceness
+                                 )
+{
+    assert(xBin);
+    assert(yBin);
+    assert(cell);
+
+    // A "nice" cell must have only a single readout
+    if (cell->readouts->n != 1) {
+        return false;
+    }
+
+    // A "nice" cell must have parity == 1
+    bool mdok = true;                   // Status of MD lookup
+    int xParity = psMetadataLookupS32(&mdok, cell->concepts, "CELL.XPARITY"); // Parity in x
+    if (!mdok || xParity == 0) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.XPARITY hasn't been set for cell.\n");
+        return false;
+    }
+    if (xParity != 1) {
+        return false;
+    }
+    int yParity = psMetadataLookupS32(&mdok, cell->concepts, "CELL.YPARITY"); // Parity in y
+    if (!mdok || yParity == 0) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.YPARITY hasn't been set for cell.\n");
+        return false;
+    }
+    if (yParity != 1) {
+        return false;
+    }
+
+    // A "nice" cell must have consistent binning
+    int xBinCell = psMetadataLookupS32(&mdok, cell->concepts, "CELL.XBIN"); // Binning in x
+    if (!mdok || xBin <= 0) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.XBIN hasn't been set for cell.\n");
+        return false;
+    }
+    int yBinCell = psMetadataLookupS32(&mdok, cell->concepts, "CELL.YBIN"); // Binning in y
+    if (!mdok || yBin <= 0) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.YBIN hasn't been set for cell.\n");
+        return false;
+    }
+    if (*xBin == 0 || *yBin == 0) {
+        *xBin = xBinCell;
+        *yBin = yBinCell;
+    } else if (xBinCell != *xBin || yBinCell != *yBin) {
+        return false;
+    }
+
+    return true;
+}
+
+
+// Check a cell for niceness in the boundaries
+static bool niceCellBounds(const pmCell *cell, // Cell to check for niceness
+                           const psRegion *imageBounds // Bounds of the image on the HDU
+                          )
+{
+    // A "nice" cell must have the (0,0) pixel at CELL.X0,CELL.Y0
+    bool mdok = true;                   // Status of MD lookup
+    int x0 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.X0"); // Position of (0,0) on chip
+    if (!mdok) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.X0 hasn't been set for cell.\n");
+        return false;
+    }
+    int y0 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.Y0"); // Position of (0,0) on chip
+    if (!mdok) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.Y0 hasn't been set for cell.\n");
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[0]; // A representative readout
+    if (!readout) {
+        return false;                   // Nothing here
+    }
+    if (x0 != readout->col0 + readout->image->col0 - (int)imageBounds->x0 ||
+            y0 != readout->row0 + readout->image->row0 - (int)imageBounds->y0) {
+        psTrace("psModules.camera", 5, "CELL.X0,Y0 don't match: %d,%d vs %d,%d\n", x0, y0,
+                readout->col0 + readout->image->col0 - (int)imageBounds->x0,
+                readout->row0 + readout->image->row0 - (int)imageBounds->y0);
+        return false;
+    }
+
+    return true;
+}
+
+
+// Is the chip "nice"?  If so, return the region containing the chip pixels
+static psRegion *niceChip(int *xBinChip, int *yBinChip, // Binning for chip, to be returned
+                          const pmChip *chip // Chip to examine for "niceness".
+                         )
+{
+    assert(xBinChip);
+    assert(yBinChip);
+    assert(chip);
+
+    // Check that we've got the HDU in the chip or the FPA
+    if ((!chip->hdu || !chip->hdu->images) && (!chip->parent->hdu || !chip->parent->hdu->images)) {
+        return NULL;
+    }
+
+    // Check parity and binning for component cells
+    *xBinChip = 0;
+    *yBinChip = 0;
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCell *cell = chip->cells->data[i]; // The cell of interest
+        if (!niceCellParityBinning(xBinChip, yBinChip, cell)) {
+            return NULL;
+        }
+    }
+
+    // Now check that the pixels are all contiguous
+    psRegion *imageBounds = psRegionAlloc(INFINITY, 0, INFINITY, 0); // Bound of image on HDU
+    if (!chipBounds(imageBounds, chip) || !chipContiguousBiassec(imageBounds, chip)) {
+        psTrace("psModules.camera", 5, "Image isn't contiguous.\n");
+        psFree(imageBounds);
+        return NULL;
+    }
+
+    psString region = psRegionToString(*imageBounds);
+    psTrace("psModules.camera", 7, "Image bounds: %s\n", region);
+    psFree(region);
+
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCell *cell = chip->cells->data[i]; // The cell of interest
+        if (!niceCellBounds(cell, imageBounds)) {
+            psFree(imageBounds);
+            return NULL;
+        }
+    }
+
+    // Need to check all the other chips if the HDU is in the FPA
+    pmFPA *fpa = chip->parent;          // The parent FPA
+    if (fpa->hdu && fpa->hdu->images) {
+        psArray *chips = fpa->chips;    // Array of chips
+        for (int i = 0; i < chips->n; i++) {
+            pmChip *testChip = chips->data[i]; // The chip of interest
+            if (testChip == chip) {
+                // Already done this one
+                continue;
+            }
+            if (!chipContiguousTrimsec(imageBounds, testChip) ||
+                    !chipContiguousBiassec(imageBounds, testChip)) {
+                psTrace("psModules.camera", 5, "Image isn't contiguous.\n");
+                psFree(imageBounds);
+                return NULL;
+            }
+        }
+    }
+
+    return imageBounds;
+}
+
+// Is the FPA "nice"?  If so, return the region containing the FPA pixels
+static psRegion *niceFPA(int *xBinFPA, int *yBinFPA, // Binning for FPA, to be returned
+                         const pmFPA *fpa  // FPA to examine for "niceness".
+                        )
+{
+    assert(xBinFPA);
+    assert(yBinFPA);
+    assert(fpa);
+
+    // Check that we've got the HDU in the chip or the FPA
+    if (!fpa->hdu || !fpa->hdu->images) {
+        return NULL;
+    }
+
+    // Check parity and binning for component cells
+    *xBinFPA = 0;
+    *yBinFPA = 0;
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i]; // The chip of interest
+        for (int j = 0; i < chip->cells->n; i++) {
+            pmCell *cell = chip->cells->data[j]; // The cell of interest
+            if (!niceCellParityBinning(xBinFPA, yBinFPA, cell)) {
+                return NULL;
+            }
+        }
+    }
+
+    // Now check that the pixels are all contiguous
+    psRegion *imageBounds = psRegionAlloc(0, 0, 0, 0); // Bound of image on HDU
+    if (!fpaContiguous(imageBounds, fpa)) {
+        psTrace("psModules.camera", 5, "Image isn't contiguous.\n");
+        psFree(imageBounds);
+        return NULL;
+    }
+
+    psString region = psRegionToString(*imageBounds);
+    psTrace("psModules.camera", 7, "Image bounds: %s\n", region);
+    psFree(region);
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i]; // The chip of interest
+        for (int j = 0; i < chip->cells->n; i++) {
+            pmCell *cell = chip->cells->data[j]; // The cell of interest
+            if (!niceCellBounds(cell, imageBounds)) {
+                psFree(imageBounds);
+                return NULL;
+            }
+        }
+    }
+
+    return imageBounds;
+}
+
+// supporting macros used by imageMosaic()
+// copy pixels without binning
+#define COPY_WITH_PARITY_DIFFERENCE(TYPE) \
+        case PS_TYPE_##TYPE: { \
+                for (int y = 0; y < image->numRows; y++) { \
+                    int yTarget =  yTargetBase + yParity * y; \
+                    for (int x = 0; x < image->numCols; x++) { \
+                        int xTarget = xTargetBase + xParity * x; \
+                        mosaic->data.TYPE[yTarget][xTarget] = image->data.TYPE[y][x]; \
+                    } \
+                } \
+            } \
+            break;
+
+// In case the original image is binned but the mosaic is not, we need to fill in the values in
+// the mosaic.  this operation should be replaced with a call to one of the functions defined
+// in psImageBinning
+#define FILL_IN(TYPE) \
+        case PS_TYPE_##TYPE: \
+            for (int y = 0; y < image->numRows; y++) { \
+                float yTargetBinBase = yTargetBase + yParity * yBinSource->data.S32[i] * y / yBinTarget; \
+                for (int x = 0; x < image->numCols; x++) { \
+                    float xTargetBinBase = xTargetBase + xParity * xBinSource->data.S32[i] * x / xBinTarget; \
+                    for (int j = 0; j < yBinSource->data.S32[i]; j++) { \
+                        int yTarget = (int)(yTargetBinBase + yParity * (float)j / (float)yBinTarget); \
+                        for (int k = 0; k < xBinSource->data.S32[i]; k++) { \
+                            int xTarget = (int)(xTargetBinBase + xParity * (float)k / (float)xBinTarget); \
+                            mosaic->data.TYPE[yTarget][xTarget] = image->data.TYPE[y][x]; \
+                        } \
+                    } \
+                } \
+            } \
+            break;
+
+// Mosaic multiple images, with flips, binning and offsets
+static psImage *imageMosaic(const psArray *source, // Images to splice in
+                            const psVector *xFlip, const psVector *yFlip, // Need to flip x and y?
+                            const psVector *xBinSource, // Binning in x of source images
+                            const psVector *yBinSource, // Binning in y of source images
+                            int xBinTarget, int yBinTarget, // Binning in x and y of target images
+                            const psVector *x0, const psVector *y0, // Offsets for source images on target
+                            double unexposed // Value for unexposed pixels
+                           )
+{
+    assert(source);
+    assert(xFlip && xFlip->type.type == PS_TYPE_U8);
+    assert(yFlip && yFlip->type.type == PS_TYPE_U8);
+    assert(xBinSource && xBinSource->type.type == PS_TYPE_S32);
+    assert(yBinSource && yBinSource->type.type == PS_TYPE_S32);
+    assert(x0 && x0->type.type == PS_TYPE_S32);
+    assert(y0 && y0->type.type == PS_TYPE_S32);
+    assert(xFlip->n == source->n);
+    assert(yFlip->n == source->n);
+    assert(xBinSource->n == source->n);
+    assert(yBinSource->n == source->n);
+    assert(x0->n == source->n);
+    assert(y0->n == source->n);
+
+    if (source->n == 0) {
+        return NULL;
+    }
+
+    // Get the maximum extent of the mosaic image
+    int xMin = +INT_MAX;
+    int xMax = -INT_MAX;
+    int yMin = +INT_MAX;
+    int yMax = -INT_MAX;
+    psElemType type = 0;
+    int numImages = 0;                  // Number of images
+    psTrace("psModules.camera", 3, "Mosaicking %ld cells.\n", source->n);
+    for (int i = 0; i < source->n; i++) {
+        psImage *image = source->data[i]; // The image of interest
+        if (!image) {
+            continue;
+        }
+        numImages++;
+
+        // Only implemented for F32 and U8 images so far.
+        assert(image->type.type == PS_TYPE_F32 || image->type.type == PS_TYPE_U8);
+        // All input types must be the same
+        if (type == 0) {
+            type = image->type.type;
+        }
+        assert(type == image->type.type);
+
+        // Size of cell in x and y
+        int xParity = xFlip->data.U8[i] ? -1 : 1;
+        int yParity = yFlip->data.U8[i] ? -1 : 1;
+        psTrace("psModules.camera", 5, "Extent of cell %d: %d -> %d , %d -> %d\n", i, x0->data.S32[i],
+                x0->data.S32[i] + xParity * xBinSource->data.S32[i] * image->numCols, y0->data.S32[i],
+                y0->data.S32[i] + yParity * yBinSource->data.S32[i] * image->numRows);
+
+        COMPARE(x0->data.S32[i], xMin, xMax);
+        COMPARE(y0->data.S32[i], yMin, yMax);
+        // Subtract the parity to get the inclusive limit (not exclusive)
+        COMPARE(x0->data.S32[i] + xParity * xBinSource->data.S32[i] * image->numCols - xParity, xMin, xMax);
+        COMPARE(y0->data.S32[i] + yParity * yBinSource->data.S32[i] * image->numRows - yParity, yMin, yMax);
+    }
+    if (numImages == 0) {
+        return NULL;
+    }
+
+    // Set up the image
+    // Since both upper and lower values are inclusive, we need to add one to the size
+    float xSize = (float)(xMax - xMin + 1) / (float)xBinTarget;
+    if (xSize - (int)xSize > 0) {
+        xSize += 1;
+    }
+    float ySize = (float)(yMax - yMin + 1) / (float)yBinTarget;
+    if (ySize - (int)ySize > 0) {
+        ySize += 1;
+    }
+
+    psTrace("psModules.camera", 3, "Spliced image will be %dx%d\n", (int)xSize, (int)ySize);
+    psImage *mosaic = psImageAlloc((int)xSize, (int)ySize, type); // The mosaic image
+    psImageInit(mosaic, unexposed);
+
+    // Next pass through the images to do the mosaicking
+    // XXX this function uses summing for the output: is this the right choice?
+    for (int i = 0; i < source->n; i++) {
+        psImage *image = source->data[i]; // The image of interest
+        if (!image) {
+            continue;
+        }
+        int xParity = xFlip->data.U8[i] ? -1 : 1; // Parity difference, in x
+        int yParity = yFlip->data.U8[i] ? -1 : 1; // Parity difference, in y
+        int xTargetBase = (x0->data.S32[i] - xMin) / xBinTarget; // The base x position in the target frame
+        int yTargetBase = (y0->data.S32[i] - yMin) / yBinTarget; // The base y position in the target frame
+
+        // in the first case, we are just copy a section pixel-by-pixel
+        if ((xBinSource->data.S32[i] == xBinTarget) &&
+            (yBinSource->data.S32[i] == yBinTarget) &&
+            (xFlip->data.U8[i] == 0) &&
+            (yFlip->data.U8[i] == 0)) {
+            // Let someone else do the hard work
+            psImageOverlaySection(mosaic, image, xTargetBase, yTargetBase, "=");
+            continue;
+        }
+
+        // in the second case, there's a difference with the parities, but we don't have to
+        // worry about binning
+        if (xBinSource->data.S32[i] == xBinTarget && yBinSource->data.S32[i] == yBinTarget) {
+            switch (type) {
+                COPY_WITH_PARITY_DIFFERENCE(F32);
+                COPY_WITH_PARITY_DIFFERENCE(U8);
+              default:
+                psAbort("Should never get here.\n");
+            }
+            continue;
+        }
+
+        // In the third case, the images are flipped and have different binnnig.
+        // We have to do all of the hard work ourselves
+        switch (type) {
+            FILL_IN(F32);
+            FILL_IN(U8);
+          default:
+            psAbort("Should never get here.\n");
+        }
+    } // Iterating over images
+
+    return mosaic;
+}
+
+// Add a cell and its various properties to the arrays
+static bool addCell(psArray *images,    // Array of images
+                    psArray *masks,     // Array of masks
+                    psArray *weights,   // Array of weights
+                    psVector *x0,       // Array of X0
+                    psVector *y0,       // Array of Y0
+                    psVector *xBin,     // Array of XBIN
+                    psVector *yBin,     // Array of YBIN
+                    psVector *xFlip,    // Array indicating whether x axis should be flipped
+                    psVector *yFlip,    // Array indicating whether y axis should be flipped
+                    const pmCell *cell, // Cell to add
+                    int *xBinMin,       // The minimum x binning, returned
+                    int *yBinMin,       // The minimum y binning, returned
+                    bool chipStuff,      // Worry about chip stuff as well?
+                    int x0Target, int y0Target, // Target x0 and y0 offsets
+                    int xParityTarget, int yParityTarget // Target parities
+                   )
+{
+    if (!cell) {
+        return false;
+    }
+
+    // Expand the arrays and vectors to handle new data
+    long index = images->n;               // The index to use
+    if (images->n == images->nalloc) {
+        images  = psArrayRealloc(images,  index + CELL_LIST_BUFFER);
+        masks   = psArrayRealloc(masks,   index + CELL_LIST_BUFFER);
+        weights = psArrayRealloc(weights, index + CELL_LIST_BUFFER);
+        x0    = psVectorRealloc(x0,    index+ CELL_LIST_BUFFER);
+        y0    = psVectorRealloc(y0,    index+ CELL_LIST_BUFFER);
+        xBin  = psVectorRealloc(xBin,  index+ CELL_LIST_BUFFER);
+        yBin  = psVectorRealloc(yBin,  index+ CELL_LIST_BUFFER);
+        xFlip = psVectorRealloc(xFlip, index+ CELL_LIST_BUFFER);
+        yFlip = psVectorRealloc(yFlip, index+ CELL_LIST_BUFFER);
+    }
+
+    images->n = index + 1;
+    masks->n = index + 1;
+    weights->n = index + 1;
+    x0->n = index + 1;
+    y0->n = index + 1;
+    xBin->n = index + 1;
+    yBin->n = index + 1;
+    xFlip->n = index + 1;
+    yFlip->n = index + 1;
+
+    bool mdok = true;                   // Status of MD lookup
+    bool good = true;                   // Is everything good?
+
+    // Offset of the cell on the chip
+    int x0Cell = psMetadataLookupS32(&mdok, cell->concepts, "CELL.X0");
+    if (!mdok) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.X0 for cell is not set.\n");
+        good = false;
+    }
+    int y0Cell = psMetadataLookupS32(&mdok, cell->concepts, "CELL.Y0");
+    if (!mdok) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.Y0 for cell is not set.\n");
+        good = false;
+    }
+    psTrace("psModules.camera", 5, "Cell %ld: x0=%d y0=%d\n", index, x0Cell, y0Cell);
+
+    // Offset of the chip on the FPA
+    int x0Chip = 0, y0Chip = 0;
+    if (chipStuff) {
+        pmChip *chip = cell->parent;    // The parent chip
+        if (!chip) {
+            psError(PS_ERR_UNKNOWN, true, "Cell has no parent chip --- can't find CHIP.X0 and CHIP.Y0\n");
+            good = false;
+        }
+        x0Chip = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.X0");
+        if (!mdok) {
+            psError(PS_ERR_UNKNOWN, true, "CHIP.X0 for chip is not set.\n");
+            good = false;
+        }
+        y0Chip = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.Y0");
+        if (!mdok) {
+            psError(PS_ERR_UNKNOWN, true, "CHIP.Y0 for chip is not set.\n");
+            good = false;
+        }
+    }
+
+    // Binning
+    xBin->data.S32[index] = psMetadataLookupS32(&mdok, cell->concepts, "CELL.XBIN");
+    if (!mdok || xBin->data.S32[index] == 0) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.XBIN for cell is not set.\n");
+        return false;
+    } else if (xBin->data.S32[index] < *xBinMin) {
+        *xBinMin = xBin->data.S32[index];
+    }
+    yBin->data.S32[index] = psMetadataLookupS32(&mdok, cell->concepts, "CELL.YBIN");
+    if (!mdok || yBin->data.S32[index] == 0) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.YBIN for cell is not set.\n");
+        return false;
+    } else if (yBin->data.S32[index] < *yBinMin) {
+        *yBinMin = yBin->data.S32[index];
+    }
+
+    // Do we need to flip?
+    int xParityCell = psMetadataLookupS32(&mdok, cell->concepts, "CELL.XPARITY");
+    if (!mdok || (xParityCell != 1 && xParityCell != -1)) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.XPARITY for cell is not set.\n");
+        return false;
+    }
+    int yParityCell = psMetadataLookupS32(&mdok, cell->concepts, "CELL.YPARITY");
+    if (!mdok || (yParityCell != 1 && yParityCell != -1)) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.YPARITY for cell is not set.\n");
+        return false;
+    }
+
+    // Parity of the chip on the FPA
+    int xParityChip = 1, yParityChip = 1;
+    if (chipStuff) {
+        pmChip *chip = cell->parent;    // The parent chip
+        xParityChip = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.XPARITY");
+        if (!mdok || (xParityChip != 1 && xParityChip != -1)) {
+            psError(PS_ERR_UNKNOWN, true, "CHIP.XPARITY for chip is not set.\n");
+            return false;
+        }
+        yParityChip = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.YPARITY");
+        if (!mdok || (yParityChip != 1 && yParityChip != -1)) {
+            psError(PS_ERR_UNKNOWN, true, "CHIP.YPARITY for chip is not set.\n");
+            return false;
+        }
+    }
+
+    // Set the flips on the basis of the parity
+    // XXX if (level == CHIP) : only apply Cell parity
+    // XXX if (level == FPA) : apply Chip & Cell parity
+    if (xParityCell * xParityChip == xParityTarget) {
+        xFlip->data.U8[index] = 0;
+    } else {
+        xFlip->data.U8[index] = 1;
+    }
+    if (yParityCell * yParityChip == yParityTarget) {
+        yFlip->data.U8[index] = 0;
+    } else {
+        yFlip->data.U8[index] = 1;
+    }
+
+    x0->data.S32[index] = x0Chip + x0Cell - x0Target;
+    y0->data.S32[index] = y0Chip + y0Cell - y0Target;
+
+    // Add the readout to the array of images to be mosaicked
+    psArray *readouts = cell->readouts; // The array of readouts
+    if (readouts->n > 1) {
+        psWarning("Cell contains more than one readout (%ld) --- only the first will be mosaicked.\n",
+                  readouts->n);
+    }
+    pmReadout *readout = readouts->data[0]; // The only readout we'll bother with
+
+    // The images to put into the mosaic
+    images->data[index]  = psMemIncrRefCounter(readout->image);
+    weights->data[index] = psMemIncrRefCounter(readout->weight);
+    masks->data[index]   = psMemIncrRefCounter(readout->mask);
+
+    psTrace("psModules.camera", 9, "Added cell (%p) %ld: %d,%d; %d,%d, %d,%d.\n", cell, index,
+            x0->data.S32[index], y0->data.S32[index], xBin->data.S32[index], yBin->data.S32[index],
+            xFlip->data.U8[index], yFlip->data.U8[index]);
+
+    return true;
+}
+
+
+// Mosaic together the cells in a chip
+static bool chipMosaic(psImage **mosaicImage, // The mosaic image, to be returned
+                       psImage **mosaicMask, // The mosaic mask, to be returned
+                       psImage **mosaicWeights, // The mosaic weights, to be returned
+                       int *xBinChip, int *yBinChip, // The binning in x and y, to be returned
+                       const pmChip *chip, // Chip to mosaic
+                       const pmCell *targetCell, // Cell to which to mosaic
+                       psMaskType blank // Mask value to give blank pixels
+                      )
+{
+    assert(mosaicImage);
+    assert(mosaicMask);
+    assert(mosaicWeights);
+    assert(xBinChip);
+    assert(yBinChip);
+    assert(chip);
+    assert(targetCell);
+
+    psArray *images = psArrayAlloc(0); // Array of images that will be mosaicked
+    psArray *weights = psArrayAlloc(0); // Array of weight images to be mosaicked
+    psArray *masks = psArrayAlloc(0); // Array of mask images to be mosaicked
+    psVector *x0 = psVectorAlloc(0, PS_TYPE_S32); // Origin x coordinates
+    psVector *y0 = psVectorAlloc(0, PS_TYPE_S32); // Origin y coordinates
+    psVector *xBin = psVectorAlloc(0, PS_TYPE_S32); // Binning in x
+    psVector *yBin = psVectorAlloc(0, PS_TYPE_S32); // Binning in y
+    psVector *xFlip = psVectorAlloc(0, PS_TYPE_U8); // Flip in x?
+    psVector *yFlip = psVectorAlloc(0, PS_TYPE_U8); // Flip in y?
+
+    // Get the target characteristics
+    bool mdok = true;                   // Status of MD lookup
+    int x0Target = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.X0");
+    if (!mdok) {
+        psWarning("CELL.X0 is not set for the target cell; assuming 0.\n");
+        FIX_CONCEPT(targetCell->concepts, "CELL.X0", S32, 0);
+    }
+    int y0Target = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.Y0");
+    if (!mdok) {
+        psWarning("CELL.Y0 is not set for the target cell; assuming 0.\n");
+        FIX_CONCEPT(targetCell->concepts, "CELL.Y0", S32, 0);
+    }
+    int xParityTarget = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.XPARITY");
+    if (!mdok || (xParityTarget != -1 && xParityTarget != 1)) {
+        psWarning("CELL.XPARITY is not set for the target cell; assuming 1.\n");
+        FIX_CONCEPT(targetCell->concepts, "CELL.XPARITY", S32, 1);
+        xParityTarget = 1;
+    }
+    int yParityTarget = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.YPARITY");
+    if (!mdok || (yParityTarget != -1 && yParityTarget != 1)) {
+        psWarning("CELL.YPARITY is not set for the target cell; assuming 1.\n");
+        FIX_CONCEPT(targetCell->concepts, "CELL.YPARITY", S32, 1);
+        yParityTarget = 1;
+    }
+
+    // Binning for the mosaicked chip is the minimum binning allowed by the cells
+    *xBinChip = INT_MAX;
+    *yBinChip = INT_MAX;
+
+    // Set up the required inputs
+    bool allGood = true;                // Is everything good, well-behaved?
+    psArray *cells = chip->cells;       // The array of cells
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // The cell of interest
+        if (!cell || !cell->data_exists) {
+            continue;
+        }
+        allGood &= addCell(images, masks, weights, x0, y0, xBin, yBin, xFlip, yFlip,
+                           cell, xBinChip, yBinChip, false, x0Target, y0Target,
+                           xParityTarget, yParityTarget);
+    }
+
+    // Check to see if the target has a smaller binning in mind
+    int xBinTarget = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.XBIN");
+    if (!mdok || xBinTarget == 0) {
+        // CELL.XBIN is not set for the target cell --- assume it's the same as the source
+        FIX_CONCEPT(targetCell->concepts, "CELL.XBIN", S32, *xBinChip);
+    } else {
+        *xBinChip = xBinTarget;
+    }
+    int yBinTarget = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.YBIN");
+    if (!mdok || yBinTarget == 0) {
+        // CELL.YBIN is not set for the target cell --- assume it's the same as the source
+        FIX_CONCEPT(targetCell->concepts, "CELL.YBIN", S32, *yBinChip);
+    } else {
+        *yBinChip = yBinTarget;
+    }
+
+    // Mosaic the images together and we're done
+    if (allGood) {
+        *mosaicImage = imageMosaic(images, xFlip, yFlip, xBin, yBin, *xBinChip, *yBinChip, x0, y0, BLANK_VALUE);
+        *mosaicWeights = imageMosaic(weights, xFlip, yFlip, xBin, yBin, *xBinChip, *yBinChip, x0, y0, BLANK_VALUE);
+        *mosaicMask = imageMosaic(masks, xFlip, yFlip, xBin, yBin, *xBinChip, *yBinChip, x0, y0, blank);
+    }
+
+    // Clean up
+    psFree(images);
+    psFree(weights);
+    psFree(masks);
+    psFree(xFlip);
+    psFree(yFlip);
+    psFree(xBin);
+    psFree(yBin);
+    psFree(x0);
+    psFree(y0);
+
+    return allGood;
+}
+
+// Mosaic together the cells in a FPA
+static bool fpaMosaic(psImage **mosaicImage, // The mosaic image, to be returned
+                      psImage **mosaicMask, // The mosaic mask, to be returned
+                      psImage **mosaicWeights, // The mosaic weights, to be returned
+                      int *xBinFPA, int *yBinFPA, // The binning in x and y, to be returned
+                      const pmFPA *fpa,  // FPA to mosaic
+                      const pmChip *targetChip, // Chip to which to mosaic
+                      const pmCell *targetCell, // Cell to which to mosaic
+                      psMaskType blank  // Mask value to give blank pixels
+                     )
+{
+    assert(mosaicImage);
+    assert(mosaicMask);
+    assert(mosaicWeights);
+    assert(xBinFPA);
+    assert(yBinFPA);
+    assert(fpa);
+    assert(targetChip);
+    assert(targetCell);
+
+    psArray *images = psArrayAlloc(0); // Array of images that will be mosaicked
+    psArray *weights = psArrayAlloc(0); // Array of weight images to be mosaicked
+    psArray *masks = psArrayAlloc(0); // Array of mask images to be mosaicked
+    psVector *x0 = psVectorAlloc(0, PS_TYPE_S32); // Origin x coordinates
+    psVector *y0 = psVectorAlloc(0, PS_TYPE_S32); // Origin y coordinates
+    psVector *xBin = psVectorAlloc(0, PS_TYPE_S32); // Binning in x
+    psVector *yBin = psVectorAlloc(0, PS_TYPE_S32); // Binning in y
+    psVector *xFlip = psVectorAlloc(0, PS_TYPE_U8); // Flip in x?
+    psVector *yFlip = psVectorAlloc(0, PS_TYPE_U8); // Flip in y?
+
+    // Get the target characteristics
+    bool mdok = true;                   // Status of MD lookup
+    int x0Target = psMetadataLookupS32(&mdok, targetChip->concepts, "CHIP.X0");
+    if (!mdok) {
+        psWarning("CHIP.X0 is not set for the target chip; assuming 0.\n");
+        FIX_CONCEPT(targetChip->concepts, "CHIP.X0", S32, 0);
+    }
+    int y0Target = psMetadataLookupS32(&mdok, targetChip->concepts, "CHIP.Y0");
+    if (!mdok) {
+        psWarning("CHIP.Y0 is not set for the target chip; assuming 0.\n");
+        FIX_CONCEPT(targetChip->concepts, "CHIP.Y0", S32, 0);
+    }
+    x0Target += psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.X0");
+    if (!mdok) {
+        psWarning("CELL.X0 is not set for the target cell; assuming 0.\n");
+        FIX_CONCEPT(targetCell->concepts, "CELL.X0", S32, 0);
+    }
+    y0Target += psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.Y0");
+    if (!mdok) {
+        psWarning("CELL.Y0 is not set for the target cell; assuming 0.\n");
+        FIX_CONCEPT(targetCell->concepts, "CELL.Y0", S32, 0);
+    }
+    int xParityChipTarget = psMetadataLookupS32(&mdok, targetChip->concepts, "CHIP.XPARITY");
+    if (!mdok || (xParityChipTarget != -1 && xParityChipTarget != 1)) {
+        psWarning("CHIP.XPARITY is not set for the target chip; assuming 1.\n");
+        FIX_CONCEPT(targetChip->concepts, "CHIP.XPARITY", S32, 1);
+        xParityChipTarget = 1;
+    }
+    int yParityChipTarget = psMetadataLookupS32(&mdok, targetChip->concepts, "CHIP.YPARITY");
+    if (!mdok || (yParityChipTarget != -1 && yParityChipTarget != 1)) {
+        psWarning("CHIP.YPARITY is not set for the target chip; assuming 1.\n");
+        FIX_CONCEPT(targetChip->concepts, "CHIP.YPARITY", S32, 1);
+        yParityChipTarget = 1;
+    }
+    int xParityCellTarget = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.XPARITY");
+    if (!mdok || (xParityCellTarget != -1 && xParityCellTarget != 1)) {
+        psWarning("CELL.XPARITY is not set for the target cell; assuming 1.\n");
+        FIX_CONCEPT(targetCell->concepts, "CELL.XPARITY", S32, 1);
+        xParityCellTarget = 1;
+    }
+    int yParityCellTarget = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.YPARITY");
+    if (!mdok || (yParityCellTarget != -1 && yParityCellTarget != 1)) {
+        psWarning("CELL.YPARITY is not set for the target cell; assuming 1.\n");
+        FIX_CONCEPT(targetCell->concepts, "CELL.YPARITY", S32, 1);
+        yParityCellTarget = 1;
+    }
+    int xParityTarget = xParityChipTarget * xParityCellTarget;
+    int yParityTarget = yParityChipTarget * yParityCellTarget;
+
+    // Binning for the mosaicked chip is the minimum binning allowed by the cells
+    *xBinFPA = INT_MAX;
+    *yBinFPA = INT_MAX;
+
+    // Set up the required inputs
+    bool allGood = true;                // Is everything good, well-behaved?
+    psArray *chips = fpa->chips;        // Array of chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // The chip of interest
+        if (!chip || !chip->data_exists) {
+            continue;
+        }
+        psArray *cells = chip->cells;   // The array of cells
+        for (int j = 0; j < cells->n; j++) {
+            pmCell *cell = cells->data[j];  // The cell of interest
+            if (!cell || !cell->data_exists) {
+                continue;
+            }
+            allGood |= addCell(images, masks, weights, x0, y0, xBin, yBin, xFlip, yFlip,
+                               cell, xBinFPA, yBinFPA, true, x0Target, y0Target,
+                               xParityTarget, yParityTarget);
+        }
+    }
+
+    // Check to see if the target has a smaller binning in mind
+    int xBinTarget = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.XBIN");
+    if (mdok && xBinTarget != 0) {
+        *xBinFPA = xBinTarget;
+    }
+    int yBinTarget = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.YBIN");
+    if (mdok && yBinTarget != 0) {
+        *yBinFPA = yBinTarget;
+    }
+
+    // Mosaic the images together and we're done
+    if (allGood) {
+        *mosaicImage = imageMosaic(images, xFlip, yFlip, xBin, yBin, *xBinFPA, *yBinFPA, x0, y0, BLANK_VALUE);
+        *mosaicWeights = imageMosaic(weights, xFlip, yFlip, xBin, yBin, *xBinFPA, *yBinFPA, x0, y0, BLANK_VALUE);
+        *mosaicMask = imageMosaic(masks, xFlip, yFlip, xBin, yBin, *xBinFPA, *yBinFPA, x0, y0, blank);
+    }
+
+    // Clean up
+    psFree(images);
+    psFree(weights);
+    psFree(masks);
+    psFree(xFlip);
+    psFree(yFlip);
+    psFree(xBin);
+    psFree(yBin);
+    psFree(x0);
+    psFree(y0);
+
+    return allGood;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Mosaic all the cells in a chip together.
+//
+// It is desirable to do this without using psImageOverlay (or similar) if it can be at all avoided (because
+// it's really really slow in that case).  There are therefore two cases:
+//
+// 1. The HDU is at the Chip or FPA level.  This is the fast case, and only works if the HDU is "nice", by
+// which I mean:
+//
+//    - the CELL.TRIMSECs are contiguous on the HDU image
+//    - the CELL.PARITYs are identically +1
+//    - the CELL.XBIN and CELL.YBIN are all identical
+//
+// Then we can just use psImageSubset to get the "mosaicked" chip.
+//
+//
+// 2. The HDU is at the cell level, or the above requirements are not met, in which case we mosaic the cells.
+// This is the slow case.  We need to:
+//
+//    - Throw away the bias regions
+//    - Convert all cells to common parity
+//    - Mosaic the cells into an HDU image using CELL.X0 and CELL.Y0
+//    - Update CELL.TRIMSECs
+//
+// Once the demands of case 1 have been met, or case 2 has been performed, then we can create a cell to hold
+// the mosaic image.
+
+bool pmChipMosaic(pmChip *target, const pmChip *source, bool deepCopy, psMaskType blank)
+{
+    // Target exists, and has only a single cell
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(target->cells, false);
+    if (target->cells->n != 1) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Target chip for mosaicking must contain a single cell.\n");
+        return false;
+    }
+    pmCell *targetCell = target->cells->data[0]; // The target cell
+    PS_ASSERT_PTR_NON_NULL(targetCell, false);
+    // Source exists
+    PS_ASSERT_PTR_NON_NULL(source, false);
+
+
+    psImage *mosaicImage   = NULL;      // The mosaic image
+    psImage *mosaicMask    = NULL;      // The mosaic mask
+    psImage *mosaicWeights = NULL;      // The mosaic weights
+
+    // Find the HDU
+    psRegion *chipRegion = NULL;        // Region on the HDU that corresponds to the chip
+    int xBin = 0, yBin = 0;             // Binning for the chip mosaic
+    if (!deepCopy && (chipRegion = niceChip(&xBin, &yBin, source))) {
+        // Case 1 --- we need only cut out the region
+        psTrace("psModules.camera", 1, "Case 1 mosaicking: simple cut-out.\n");
+        pmHDU *hdu = source->hdu;       // The HDU that has the pixels
+        if (!hdu || !hdu->images) {
+            hdu = source->parent->hdu;
+        }
+        // force limits to land on chip
+        psRegion bounds = psRegionForImage (hdu->images->data[0], *chipRegion);
+        mosaicImage = psImageSubset(hdu->images->data[0], bounds);
+        if (!mosaicImage) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to select image pixels.\n");
+            return false;
+        }
+        if (hdu->masks) {
+            mosaicMask = psImageSubset(hdu->masks->data[0], bounds);
+            if (!mosaicMask) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to select mask pixels.\n");
+                return false;
+            }
+        }
+        if (hdu->weights) {
+            mosaicWeights = psImageSubset(hdu->weights->data[0], bounds);
+            if (!mosaicWeights) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to select weight pixels.\n");
+                return false;
+            }
+        }
+    } else {
+        // Case 2 --- we need to mosaic by cut and paste
+        psTrace("psModules.camera", 1, "Case 2 mosaicking: cut and paste.\n");
+        if (!chipMosaic(&mosaicImage, &mosaicMask, &mosaicWeights, &xBin, &yBin, source, targetCell, blank)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to mosaic cells.\n");
+            return false;
+        }
+        chipRegion = psRegionAlloc(0, 0, 0, 0); // We've cut and paste, so there's no valid trimsec
+        *chipRegion = psRegionForImage (mosaicImage, *chipRegion);
+    }
+    psTrace("psModules.camera", 1, "xBin,yBin: %d,%d\n", xBin, yBin);
+
+    // Set the concepts for the target cell
+    psList *sourceCells = psArrayToList(source->cells); // List of cells
+    pmConceptsAverageCells(targetCell, sourceCells, chipRegion, NULL, false);
+    {
+        psMetadataItem *item = psMetadataLookup(targetCell->concepts, "CELL.X0");
+        item->data.S32 = 0;
+        item = psMetadataLookup(targetCell->concepts, "CELL.Y0");
+        item->data.S32 = 0;
+        item = psMetadataLookup(targetCell->concepts, "CELL.XBIN");
+        item->data.S32 = xBin;
+        item = psMetadataLookup(targetCell->concepts, "CELL.YBIN");
+        item->data.S32 = yBin;
+    }
+    psFree(sourceCells);
+    psFree(chipRegion);
+
+    // Copy the concepts
+    target->concepts = psMetadataCopy(target->concepts, source->concepts); // Chip level
+    target->parent->concepts = psMetadataCopy(target->parent->concepts, source->parent->concepts); // FPA lvl
+
+    // Now make a new readout to go in the target cell
+    pmReadout *newReadout = pmReadoutAlloc(targetCell); // New readout
+    newReadout->image  = mosaicImage;
+    newReadout->mask   = mosaicMask;
+    newReadout->weight = mosaicWeights;
+    psFree(newReadout);                 // Drop reference
+
+    // Data now exists in the targets
+    pmChipSetDataStatus(target, true);
+    pmCellSetDataStatus(targetCell, true);
+    newReadout->data_exists = true;
+
+    // Update the headers
+    pmHDU *sourceHDU = pmHDUFromChip(source); // The HDU for the source
+    pmHDU *targetHDU = pmHDUFromChip(target); // The HDU for the target
+    targetHDU->header = psMetadataCopy(targetHDU->header, sourceHDU->header);
+    pmHDU *targetPHU = pmHDUGetHighest(target->parent, target, NULL);
+    pmHDU *sourcePHU = pmHDUGetHighest(source->parent, source, NULL);
+
+    // Need to update NAXIS1, NAXIS2 in the target header, so that when we write a CMF, it has the correct
+    // extent.  I'm not convinced that this is the best way to do this, but it should be, at worst, harmless,
+    // since NAXIS[12] will get overwritten for an image with the proper dimensions.
+    psRegion *naxis = pmChipExtent(target);
+    psMetadataAddS32(targetHDU->header, PS_LIST_TAIL, "NAXIS1", PS_META_REPLACE, "Size in x",
+                     naxis->x1 - naxis->x0);
+    psMetadataAddS32(targetHDU->header, PS_LIST_TAIL, "NAXIS2", PS_META_REPLACE, "Size in y",
+                     naxis->y1 - naxis->y0);
+    psFree(naxis);
+
+
+    if (!targetPHU) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find HDU after mosaicking.\n");
+        return false;
+    }
+    if (!targetPHU->header) {
+        // if we don't yet have a header, copy this one.
+        // XXX do we need to create an empty one if the levels do not match??
+        if (true) {
+            targetPHU->header = psMetadataCopy(targetPHU->header, sourcePHU->header);
+        } else {
+            targetPHU->header = psMetadataAlloc();
+        }
+    }
+
+    if (!pmConfigConformHeader(targetPHU->header, targetPHU->format)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to conform header after mosaicking.\n");
+        return false;
+    }
+
+    // If the cells contain the headers, we need to apply the WCS terms from (one of?) the cells
+    int xParityCellTarget = psMetadataLookupS32(NULL, targetCell->concepts, "CELL.XPARITY");
+    if (xParityCellTarget != -1 && xParityCellTarget != 1) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "CELL.XPARITY is not set for target.");
+        return false;
+    }
+    int xParityChipTarget = psMetadataLookupS32(NULL, target->concepts, "CHIP.XPARITY");
+    if (xParityChipTarget != -1 && xParityChipTarget != 1) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "CHIP.XPARITY is not set for target.");
+        return false;
+    }
+    int xParityTarget = xParityCellTarget * xParityChipTarget; // Target parity in x
+
+    int yParityCellTarget = psMetadataLookupS32(NULL, targetCell->concepts, "CELL.YPARITY");
+    if (yParityCellTarget != -1 && yParityCellTarget != 1) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "CELL.YPARITY is not set for target.");
+        return false;
+    }
+    int yParityChipTarget = psMetadataLookupS32(NULL, target->concepts, "CHIP.YPARITY");
+    if (yParityChipTarget != -1 && yParityChipTarget != 1) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "CHIP.YPARITY is not set for target.");
+        return false;
+    }
+    int yParityTarget = yParityCellTarget * yParityChipTarget; // Target parity in y
+
+    for (int i = 0; i < source->cells->n; i++) {
+        pmCell *cell = source->cells->data[i];
+        if (!cell || !cell->hdu || !cell->hdu->header) {
+            continue;
+        }
+
+        pmAstromWCS *wcs = pmAstromWCSfromHeader(cell->hdu->header); // WCS terms for this cell
+        if (!wcs) {
+            psTrace("psModules.camera", 1, "Unable to read cell WCS to generate chip WCS --- ignored.");
+            continue;
+        }
+
+        int xBinCell = psMetadataLookupS32(NULL, cell->concepts, "CELL.XBIN"); // Cell binning in x
+        if (xBinCell == 0) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "CELL.XBIN is not set.");
+            return false;
+        }
+        int xParitySource = psMetadataLookupS32(NULL, cell->concepts, "CELL.XPARITY") *
+            psMetadataLookupS32(NULL, source->concepts, "CHIP.XPARITY"); // Source parity in x
+        if (xParitySource != -1 && xParitySource != 1) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "CHIP.XPARITY or CELL.XPARITY is not set for source.");
+            return false;
+        }
+        bool xFlip = (xParitySource == xParityTarget ? false : true); // Flip the x sense of the WCS?
+        int x0Cell = psMetadataLookupS32(NULL, cell->concepts, "CELL.X0"); // Cell offset in x
+
+        // Modify the wcs terms for the cell offset, binning, and parity
+        float xBinRatio = (float)xBinCell / (float)xBin;
+        if (xFlip) {
+            wcs->crpix1 = x0Cell - wcs->crpix1 * xBinRatio;
+            wcs->cdelt1 *= -1;
+        } else {
+            wcs->crpix1 = x0Cell + wcs->crpix1 * xBinRatio;
+        }
+        wcs->cdelt1 *= xBinRatio;
+
+        int yBinCell = psMetadataLookupS32(NULL, cell->concepts, "CELL.YBIN"); // Cell binning in y
+        if (yBinCell == 0) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "CELL.YBIN is not set.");
+            return false;
+        }
+        int yParitySource = psMetadataLookupS32(NULL, cell->concepts, "CELL.YPARITY") *
+            psMetadataLookupS32(NULL, cell->parent->concepts, "CHIP.YPARITY"); // Source parity in y
+        if (yParitySource != -1 && yParitySource != 1) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "CHIP.YPARITY or CELL.YPARITY is not set for source.");
+            return false;
+        }
+        bool yFlip = (yParitySource == yParityTarget ? false : true); // Flip the y sense of the WCS?
+        int y0Cell = psMetadataLookupS32(NULL, cell->concepts, "CELL.Y0"); // Cell offset in y
+
+        float yBinRatio = (float)yBinCell / (float)yBin;
+        if (yFlip) {
+            wcs->crpix2 = y0Cell - wcs->crpix2 * yBinRatio;
+            wcs->cdelt2 *= -1;
+        } else {
+            wcs->crpix2 = y0Cell + wcs->crpix2 * yBinRatio;
+        }
+        wcs->cdelt2 *= yBinRatio;
+
+        if (!pmAstromWCStoHeader(targetHDU->header, wcs)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to generate chip WCS from cell WCS.");
+            psFree(wcs);
+            return false;
+        }
+        psFree(wcs);
+
+        // XXX rather than quitting at this point, we could save this wcs structure and compare
+        // its values to the equivalent version from one of the other cells.
+        break;
+    }
+
+    return true;
+}
+
+
+bool pmFPAMosaic(pmFPA *target, const pmFPA *source, bool deepCopy, psMaskType blank)
+{
+    // Target exists, and has only a single chip with single cell
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(target->chips, false);
+    if (target->chips->n != 1) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Target FPA for mosaicking must contain a single chip.\n");
+        return false;
+    }
+    pmChip *targetChip = target->chips->data[0]; // The target chip
+    PS_ASSERT_PTR_NON_NULL(targetChip, false);
+    PS_ASSERT_PTR_NON_NULL(targetChip->cells, false);
+    if (target->chips->n != 1) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Target FPA for mosaicking must contain a single cell.\n");
+        return false;
+    }
+    pmCell *targetCell = targetChip->cells->data[0]; // The target cell
+    PS_ASSERT_PTR_NON_NULL(targetCell, false);
+    // Source exists
+    PS_ASSERT_PTR_NON_NULL(source, false);
+
+    psImage *mosaicImage   = NULL;      // The mosaic image
+    psImage *mosaicMask    = NULL;      // The mosaic mask
+    psImage *mosaicWeights = NULL;      // The mosaic weights
+
+    // Find the HDU
+    psRegion *fpaRegion = NULL;         // Region on the HDU that corresponds to the FPA
+    int xBin = 0, yBin = 0;             // Binning for the FPA mosaic
+    if (!deepCopy && (fpaRegion = niceFPA(&xBin, &yBin, source))) {
+        // Case 1 --- we need only cut out the region
+        psTrace("psModules.camera", 1, "Case 1 mosaicking: simple cut-out.\n");
+        pmHDU *hdu = source->hdu;         // The HDU that has the pixels
+        mosaicImage = psImageSubset(hdu->images->data[0], *fpaRegion);
+        if (hdu->masks) {
+            mosaicMask = psImageSubset(hdu->masks->data[0], *fpaRegion);
+        }
+        if (hdu->weights) {
+            mosaicWeights = psImageSubset(hdu->weights->data[0], *fpaRegion);
+        }
+    } else {
+        // Case 2 --- we need to mosaic by cut and paste
+        psTrace("psModules.camera", 1, "Case 2 mosaicking: cut and paste.\n");
+        if (!fpaMosaic(&mosaicImage, &mosaicMask, &mosaicWeights, &xBin, &yBin, source,
+                       targetChip, targetCell, blank)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to mosaic chips.\n");
+            return false;
+        }
+        fpaRegion = psRegionAlloc(NAN, NAN, NAN, NAN); // We've cut and paste, so there's no valid trimsec
+    }
+
+    // Set the concepts for the target cell, and add the mosaic in
+    // First we need a list of cells
+    psList *sourceCells = psListAlloc(NULL); // List of source cells
+    psArray *chips = source->chips;        // Array of chips
+    pmChip *firstSourceChip = NULL;     // The first chip in the source FPA; for headers
+    pmCell *firstSourceCell = NULL;     // The first cell in the source FPA; for headers
+    for (long i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // Chip of interest
+        if (!chip || !chip->data_exists) {
+            continue;
+        }
+        psArray *cells = chip->cells;
+        for (long j = 0; j < cells->n; j++) {
+            pmCell *cell = cells->data[j]; // Cell of interest
+            if (!cell || !cell->data_exists) {
+                continue;
+            }
+            psListAdd(sourceCells, PS_LIST_TAIL, cell);
+
+            // These are valid chip and cell to use for the header; grab the first such
+            if (!firstSourceCell && !firstSourceChip) {
+                firstSourceCell = cell;
+                firstSourceChip = chip;
+            }
+        }
+    }
+    pmConceptsAverageCells(targetCell, sourceCells, fpaRegion, NULL, false);
+    {
+        psMetadataItem *item = psMetadataLookup(targetCell->concepts, "CELL.X0");
+        item->data.S32 = 0;
+        item = psMetadataLookup(targetCell->concepts, "CELL.Y0");
+        item->data.S32 = 0;
+        item = psMetadataLookup(targetCell->concepts, "CELL.XBIN");
+        item->data.S32 = xBin;
+        item = psMetadataLookup(targetCell->concepts, "CELL.YBIN");
+        item->data.S32 = yBin;
+    }
+    psFree(sourceCells);
+    psFree(fpaRegion);
+
+    // Currently, there's nothing interesting in the chip concepts that needs to be updated.
+
+    // Copy the concepts for the target FPA
+    target->concepts = psMetadataCopy(target->concepts, source->concepts);
+
+    // Now make a new readout to go in the new cell
+    pmReadout *newReadout = pmReadoutAlloc(targetCell); // New readout
+    newReadout->image  = mosaicImage;
+    newReadout->mask   = mosaicMask;
+    newReadout->weight = mosaicWeights;
+    psFree(newReadout);                 // Drop reference
+
+    // Data now exists in the targets
+    pmChipSetDataStatus(targetChip, true);
+    pmCellSetDataStatus(targetCell, true);
+    newReadout->data_exists = true;
+
+    // Update the headers
+    pmHDU *sourceHDU = pmHDUGetHighest(source, firstSourceChip, firstSourceCell); // The HDU for the source
+    if (!sourceHDU) {
+        psWarning("Unable to find HDU in source FPA; unable to copy headers.\n");
+        return false;
+    }
+    pmHDU *targetHDU = pmHDUGetHighest(target, targetChip, targetCell); // The HDU for the target
+    if (!targetHDU) {
+        psWarning("Unable to find HDU in target FPA; unable to copy headers.\n");
+        return false;
+    }
+
+    if (sourceHDU->header) {
+        targetHDU->header = psMetadataCopy(targetHDU->header, sourceHDU->header);
+    } else if (!targetHDU->header) {
+        targetHDU->header = psMetadataAlloc();
+    }
+
+    if (!pmConfigConformHeader(targetHDU->header, targetHDU->format)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to conform header after mosaicking.\n");
+        return false;
+    }
+
+    return true;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAMosaic.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAMosaic.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAMosaic.h	(revision 22157)
@@ -0,0 +1,41 @@
+/* @file pmFPAMosaic.h
+ * @brief Functions to mosaic FPA components into a single entity
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.8 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-06-20 02:22:26 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_CHIP_MOSAIC_H
+#define PM_CHIP_MOSAIC_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+/// Mosaic all cells within a chip
+///
+/// Mosaics all cells within the source into a single cell within the target (which must have only a single
+/// cell).  Cells are placed on the chip according to the CELL.X0 and CELL.Y0 offsets.  This is useful for
+/// getting an image of the chip on the sky.  The mosaicking is done so as to avoid performing a deep copy of
+/// the pixels, if possible.
+bool pmChipMosaic(pmChip *target,       ///< Target chip --- may contain only a single cell
+                  const pmChip *source, ///< Source chip whose cells will be mosaicked
+                  bool deepCopy,        ///< Require a deep copy (disregard 'nice' chip)
+                  psMaskType blank      ///< Mask value to give blank pixels
+    );
+
+/// Mosaic all cells within an FPA
+///
+/// Mosaics all cells within the source into a single chip with single cell within the target (which must have
+/// only a single chip with single cell).  Cells are placed on the FPA according to the CHIP.X0, CHIP.Y0,
+/// CELL.X0 and CELL.Y0 offsets.  This is useful for getting an image of the FPA on the sky.  The mosaicking
+/// is done so as to avoid performing a deep copy of the pixels, if possible.
+bool pmFPAMosaic(pmFPA *target, ///< Target FPA --- may contain only a single chip with a single cell
+                 const pmFPA *source,   ///< FPA whose chips and cells will be mosaicked
+                 bool deepCopy,         ///< Require a deep copy (disregard 'nice' chip)
+                 psMaskType blank       ///< Mask value to give blank pixels
+                );
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPARead.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPARead.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPARead.c	(revision 22157)
@@ -0,0 +1,1200 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <strings.h>
+#include <assert.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAFlags.h"
+#include "pmHDUUtils.h"
+#include "pmConcepts.h"
+#include "pmFPAHeader.h"
+
+#include "pmFPARead.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Definitions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Specify what to read
+typedef enum {
+    FPA_READ_TYPE_IMAGE,                // Read image
+    FPA_READ_TYPE_MASK,                 // Read mask
+    FPA_READ_TYPE_WEIGHT,               // Read weight map
+    FPA_READ_TYPE_HEADER                // Read header
+} fpaReadType;
+
+// Desired type for pixels; the index corresponds to the fpaReadType, above.
+static psElemType pixelTypes[] = {
+    PS_TYPE_F32,
+    PS_TYPE_MASK,
+    PS_TYPE_F32,
+    0
+};
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Return pointer to appropriate value for tracking scans
+static int *readoutScansByType(pmReadout *readout, // Readout of interest
+                               fpaReadType type // Type of image
+    )
+{
+    switch (type) {
+      case FPA_READ_TYPE_IMAGE:
+        return &readout->imageScan;
+      case FPA_READ_TYPE_MASK:
+        return &readout->maskScan;
+      case FPA_READ_TYPE_WEIGHT:
+        return &readout->weightScan;
+      default:
+        psAbort("Unknown read type: %x\n", type);
+    }
+}
+
+// Return pointer to appropriate image
+static psImage **readoutImageByType(pmReadout *readout, // Readout of interest
+                                    fpaReadType type // Type of image
+    )
+{
+    switch (type) {
+      case FPA_READ_TYPE_IMAGE:
+        return &readout->image;
+      case FPA_READ_TYPE_MASK:
+        return &readout->mask;
+      case FPA_READ_TYPE_WEIGHT:
+        return &readout->weight;
+      default:
+        psAbort("Unknown read type: %x\n", type);
+    }
+}
+
+// Determine number of readouts in the FITS file
+// In the process, reads the header and concepts
+static bool cellNumReadouts(pmCell *cell,    // Cell of interest
+                            psFits *fits,    // FITS file
+                            pmConfig *config // Configuration
+    )
+{
+    assert(cell);
+    assert(fits);
+
+    // Get the HDU and read the header
+    pmHDU *hdu = pmHDUFromCell(cell);   // The HDU
+    if (!hdu || hdu->blankPHU) {
+        psError(PS_ERR_IO, true, "Unable to find HDU");
+        return false;
+    }
+    if (!pmCellReadHeader(cell, fits, config)) {
+        psError(PS_ERR_IO, false, "Unable to read header for cell!\n");
+        return false;
+    }
+    if (!pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_CELLS, true, NULL)) {
+        psError(PS_ERR_IO, false, "Failed to read concepts for cell.\n");
+        return false;
+    }
+
+    // Get the size of the third dimension
+    bool mdok;                          // Status of MD lookup
+    int naxis = psMetadataLookupS32(&mdok, hdu->header, "NAXIS"); // The number of axes
+    if (!mdok) {
+        psError(PS_ERR_IO, true, "Unable to find NAXIS in header for extension %s\n", hdu->extname);
+        return false;
+    }
+    if (naxis == 0) {
+        // No pixels to read
+        psError(PS_ERR_IO, true, "No pixels in extension %s.", hdu->extname);
+        return false;
+    }
+    if (naxis < 2 || naxis > 3) {
+        psError(PS_ERR_IO, true, "NAXIS in header of extension %s (= %d) is not valid.\n",
+                hdu->extname, naxis);
+        return false;
+    }
+    int naxis3;                     // Number of image planes
+    if (naxis == 3) {
+        naxis3 = psMetadataLookupS32(&mdok, hdu->header, "NAXIS3");
+        if (!mdok) {
+            psError(PS_ERR_IO, true, "Unable to find NAXIS3 in header for extension %s\n", hdu->extname);
+            return false;
+        }
+    } else {
+        naxis3 = 1;
+    }
+
+    return naxis3;
+}
+
+
+// Determine readout scan properties: the next and last scans
+// Requires that cellNumReadouts() has been called before (for header and concepts to have been read)
+// In the process, adjusts the TRIMSEC
+static bool readoutScanProperties(int *next, // Index of next scan
+                                  int *last, // Index of last scan
+                                  pmReadout *readout, // Readout of interest
+                                  int numScans, // Number of scans to read at a time
+                                  fpaReadType type, // Type of image
+                                  pmConfig *config // Configuration
+    )
+{
+    assert(next);
+    assert(last);
+    assert(readout);
+
+    psImage *image = *readoutImageByType(readout, type); // Appropriate image from readout
+
+    if (!pmConceptsReadCell(readout->parent, PM_CONCEPT_SOURCE_DEFAULTS | PM_CONCEPT_SOURCE_DATABASE,
+                            true, config)) {
+        psError(PS_ERR_IO, false, "Failed to read concepts for cell.");
+        return false;
+    }
+    // Header and concepts have been read by a call to cellNumReadouts(), so we can just assume they're there.
+
+    // Get the trim and bias sections
+    pmCell *cell = readout->parent;     // Parent cell
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    pmHDU *hdu = pmHDUFromCell(cell);   // HDU for data
+    bool mdok = true;                   // Status of MD lookup
+    psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim sections
+    if (!mdok || !trimsec || psRegionIsNaN(*trimsec)) {
+        psError(PS_ERR_IO, true, "CELL.TRIMSEC is not set.\n");
+        return false;
+    }
+    int readdir = psMetadataLookupS32(&mdok, cell->concepts, "CELL.READDIR"); // Read direction
+    if (!mdok || readdir == 0 || (readdir != 1 && readdir != 2)) {
+        psError(PS_ERR_IO, true, "CELL.READDIR is not set to -1 or +1.\n");
+        return false;
+    }
+
+    // Rationalize trimsec against naxis1, naxis2:  valid range for trimsec is 1-Nx,1-Ny
+    if (trimsec->x1 < 1) {
+        int naxis1 = psMetadataLookupS32(&mdok, hdu->header, "NAXIS1"); // The number of columns
+        if (!mdok) {
+            psError(PS_ERR_IO, true, "Unable to find NAXIS1 in header for extension %s\n", hdu->extname);
+            return false;
+        }
+        trimsec->x1 = naxis1 + trimsec->x1;
+    }
+    if (trimsec->y1 < 1) {
+        int naxis2 = psMetadataLookupS32(&mdok, hdu->header, "NAXIS2"); // The number of columns
+        if (!mdok) {
+            psError(PS_ERR_IO, true, "Unable to find NAXIS2 in header for extension %s\n", hdu->extname);
+            return false;
+        }
+        trimsec->y1 = naxis2 + trimsec->y1;
+    }
+
+    // Calculate the segment offset and upper limit
+    if (numScans == 0) {
+        *next = (readdir == 1) ? trimsec->y0 : trimsec->x0;
+    } else {
+        *next = image ? *readoutScansByType(readout, type) + numScans : 0;
+    }
+    *last = (readdir == 1) ? trimsec->y1 : trimsec->x1;
+
+    return true;
+}
+
+static bool readoutMore(pmReadout *readout, // Readout of interest
+                        psFits *fits,    // FITS file
+                        int z,          // Plane number
+                        int numScans,   // Number of scans to read at a time
+                        fpaReadType type, // Type of image
+                        pmConfig *config// Configuration
+    )
+{
+    assert(readout);
+    assert(fits);
+
+    psImage *image = *readoutImageByType(readout, type);
+
+    if (!image) {
+        return true;
+    } else if (numScans == 0) {
+        // Can only read the entire image once
+        return false;
+    }
+
+    pmCell *cell = readout->parent;     // Parent cell
+    if (!cell) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find parent cell.");
+        return false;
+    }
+    int naxis3 = cellNumReadouts(cell, fits, config); // Number of planes
+    if (z >= naxis3) {
+        // No more to read
+        return false;
+    }
+
+    int next;                           // Next position
+    int last;                           // Last position
+    if (!readoutScanProperties(&next, &last, readout, numScans, type, config)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to determine readout properties.");
+        return false;
+    }
+
+    return (next < last);
+}
+
+// Carve a readout from the image pixels
+static bool readoutCarve(pmReadout *readout, // Readout to be carved up
+                         psImage *image, // Image that will be carved
+                         const psRegion *trimsec, // Trim section
+                         const psList *biassecs, // Bias sections
+                         fpaReadType type // Type of image
+                        )
+{
+    assert(readout);
+    assert(image);
+    assert(trimsec);
+    assert(biassecs);
+
+    // The image corresponding to the trim region
+    if (psRegionIsNaN(*trimsec)) {
+        psString regionString = psRegionToString(*trimsec);
+        psError(PS_ERR_UNKNOWN, true, "Invalid trim section: %s\n", regionString);
+        psFree(regionString);
+        psFree(readout);
+        return false;
+    }
+    psRegion region = psRegionSet(PS_MAX(trimsec->x0 - readout->col0, 0), // x0
+                                  PS_MIN(trimsec->x1 - readout->col0, image->numCols), // x1
+                                  PS_MAX(trimsec->y0 - readout->row0, 0), // y0
+                                  PS_MIN(trimsec->y1 - readout->row0, image->numRows) // y1
+                                 );
+
+    // Place the image subset in the appropriate target location, freeing if needed
+    psImage **target = readoutImageByType(readout, type); // Target image
+    if (*target) {
+        psFree(*target);
+    }
+    *target = psImageSubset(image, region);
+
+    // Get the list of overscans: only for IMAGE types (no overscan for MASK and WEIGHT)
+    if (type == FPA_READ_TYPE_IMAGE) {
+        if (readout->bias->n != 0) {
+            // Make way!
+            psFree(readout->bias);
+            readout->bias = psListAlloc(NULL);
+        }
+        psListIterator *iter = psListIteratorAlloc((psList*)biassecs, PS_LIST_HEAD, false); // Iterator
+        psRegion *biassec = NULL;       // A BIASSEC region from the list
+        while ((biassec = psListGetAndIncrement(iter))) {
+            if (psRegionIsNaN(*biassec)) {
+                psString regionString = psRegionToString(*biassec);
+                psError(PS_ERR_IO, true, "Invalid bias section: %s\n", regionString);
+                psFree(regionString);
+                psFree(readout);
+                return false;
+            }
+            psRegion region = psRegionSet(PS_MAX(biassec->x0 - readout->col0, 0), // x0
+                                          PS_MIN(biassec->x1 - readout->col0, image->numCols), // x1
+                                          PS_MAX(biassec->y0 - readout->row0, 0), // y0
+                                          PS_MIN(biassec->y1 - readout->row0, image->numRows) // y1
+                );
+            psImage *overscan = psImageSubset(image, region);
+            psListAdd(readout->bias, PS_LIST_TAIL, overscan);
+            psFree(overscan);
+        }
+        psFree(iter);
+    }
+
+    return true;
+}
+
+// Read a component of a readout.  We read in only the rows from min to max for plane z, for
+// the full region requested.  if we request a range outside the region, we will pad to fill
+// out the edges of the region with 'bad' pixels.  The output image always has max-min rows.
+// The region represents the maximum bounds of the full image
+static psImage *readoutReadComponent(psImage *image, // Image into which to read
+                                     psFits *fits, // FITS file from which to read
+                                     const psRegion *fullImage, // full image region, read a subset
+                                     int readdir, // Read direction (1=rows, 2=cols)
+                                     int min,  // Minimum row/col number to read
+                                     int max,   // Maximum row/col number to read
+                                     int z,     // Image plane to read
+                                     float bad, // Bad value
+                                     psElemType type // Expected type for image
+    )
+{
+    assert(fits);
+    assert(fullImage);
+    assert((readdir == 1) || (readdir == 2));
+
+    int nRead = 0;                      // Number of scans read
+    int nScans = max - min;             // Number of scans desired
+    assert(nScans > 0);
+
+    psRegion toRead = *fullImage;  // full image region
+
+    int dX = 0, dY = 0;                 // Offset from image in FITS file to lower left corner of what's read
+    int nX = 0, nY = 0;                 // Size of region to read
+
+    if (readdir == 1) {
+        toRead.y0 = PS_MAX(toRead.y0, min);
+        toRead.y1 = PS_MIN(toRead.y1, max);
+        nRead = toRead.y1 - toRead.y0;
+        if (min < fullImage->y0) {
+            dY = toRead.y0;
+        }
+        nX = toRead.x1 - toRead.x0;
+        nY = nScans;
+    } else {
+        toRead.x0 = PS_MAX(toRead.x0, min);
+        toRead.x1 = PS_MIN(toRead.x1, max);
+        nRead = toRead.x1 - toRead.x0;
+        if (min < fullImage->x0) {
+            dX = toRead.x0;
+        }
+        nX = nScans;
+        nY = toRead.y1 - toRead.y0;
+    }
+
+    psTrace("psModules.camera", 5, "Reading section [%.0f:%.0f,%.0f:%.0f]\n",
+            toRead.x0, toRead.x1, toRead.y0, toRead.y1);
+    image = psFitsReadImageBuffer(image, fits, toRead, z); // Desired pixels
+    psTrace("psModules.camera", 7, "Image is %dx%d\n", image->numCols, image->numRows);
+
+    // Ensure the pixel type corresponds to what we desire
+    if (image->type.type != type) {
+        psImage *temp = psImageCopy(NULL, image, type);
+        psFree(image);
+        image = temp;
+    }
+
+    // Resize the image so that it matches the number of scans requested
+    // XXX this modification is not carried back up stream: it affects readout->row0,col0
+    //
+    // XXXXX Do we really want to do this???  Why???
+    if (nRead < nScans) {
+        // The region of interest is smaller than the number of pixels we want.
+        psTrace("psModules.camera", 5, "Resizing image to %d,%d\n", nX, nY);
+        psImage *temp = psImageAlloc(nX, nY, image->type.type);
+        psImageInit(temp, bad);
+        psImageOverlaySection(temp, image, dX, dY, "=");
+        psFree(image);
+        image = temp;
+    }
+
+    return image;
+}
+
+// Read a chunk of a readout (or the whole lot)
+static bool readoutReadChunk(pmReadout *readout, // Readout into which to read
+                             psFits *fits, // FITS file
+                             int z,     // Desired image plane
+                             int numScans, // Number of scans (row or col depends on CELL.READDIR); 0 for all
+                             int overlap, // Number of scans (row/col) to overlap between scans
+                             fpaReadType type, // Type of image
+                             pmConfig *config   // Configuration
+    )
+{
+    assert(readout);
+    assert(fits);
+    assert(z >= 0);
+    assert(numScans >= 0);
+    assert(overlap >= 0);
+
+    psImage **image = readoutImageByType(readout, type); // Pointer to the image of interest
+    if (*image && numScans == 0) {
+        psError(PS_ERR_UNKNOWN, true, "Already read entire image --- won't clobber.");
+        return false;
+    }
+
+    pmCell *cell = readout->parent;     // The parent cell
+    if (!cell) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find parent cell.");
+        return false;
+    }
+
+    int naxis3 = cellNumReadouts(cell, fits, config); // Number of image planes
+    if (z >= naxis3) {
+        psError(PS_ERR_IO, false, "Desired image plane (%d) exceeds available number (%d).",
+                z, naxis3);
+        return false;
+    }
+
+    int next;                           // Next position
+    int last;                           // Last position
+    if (!readoutScanProperties(&next, &last, readout, numScans, type, config)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to determine readout properties.");
+        return false;
+    }
+    if (next >= last) {
+        psError(PS_ERR_IO, true, "No more of the readout to read.");
+        return false;
+    }
+
+    pmHDU *hdu = pmHDUFromCell(cell);   // The HDU
+    assert(hdu && !hdu->blankPHU);      // Checked by cellNumReadouts()
+
+    bool mdok;                          // Status of MD lookup
+    int readdir = psMetadataLookupS32(&mdok, cell->concepts, "CELL.READDIR"); // Read direction
+    if (!mdok || readdir == 0 || (readdir != 1 && readdir != 2)) {
+        psError(PS_ERR_IO, true, "CELL.READDIR is not set to -1 or +1.\n");
+        return false;
+    }
+
+    // XXX for IMAGE, we need the CELL.BAD value, but for MASK, we need the BAD mask value
+
+    float bad = 0;
+    if (type == FPA_READ_TYPE_MASK) {
+      bad = 1.0;
+    } else {
+      bad = psMetadataLookupF32(&mdok, cell->concepts, "CELL.BAD"); // Bad level
+    }
+
+    psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim sections
+    if (!mdok || !trimsec || psRegionIsNaN(*trimsec)) {
+        psError(PS_ERR_IO, true, "CELL.TRIMSEC is not set.\n");
+        return false;
+    }
+
+
+
+    // Check the third dimension
+    int naxis = psMetadataLookupS32(&mdok, hdu->header, "NAXIS"); // The number of axes
+    if (!mdok) {
+        psError(PS_ERR_IO, true, "Unable to find NAXIS in header for extension %s\n", hdu->extname);
+        return false;
+    }
+    if (naxis == 0) {
+        // No pixels to read
+        psError(PS_ERR_IO, true, "No pixels in extension %s.", hdu->extname);
+        return false;
+    }
+    if (naxis < 2 || naxis > 3) {
+        psError(PS_ERR_IO, true, "NAXIS in header of extension %s (= %d) is not valid.\n",
+                hdu->extname, naxis);
+        return false;
+    }
+
+    // Update number of scans read
+    int *scansTracker = readoutScansByType(readout, type); // Tracker for how many scans have been read
+    *scansTracker = next;
+    if (next == 0) {
+        overlap = 0;
+    }
+    next -= overlap;
+
+    // Calculate limits, adjust readout->row0,col0
+    // XXX Should row0,col0 be adjusted, since they are used for astrometry???
+    // Just moved to using the "scansTracker" for tracking position in chunk reads, so row0,col0 not required
+    if (readdir == 1) {
+        // Reading rows
+        readout->row0 = next;
+        readout->col0 = trimsec->x0;
+        if (numScans == 0) {
+            numScans = trimsec->y1 - trimsec->y0;
+        }
+    } else {
+        // Reading cols
+        readout->col0 = next;
+        readout->row0 = trimsec->y0;
+        if (numScans == 0) {
+            numScans = trimsec->x1 - trimsec->x0;
+        }
+    }
+    int upper = next + numScans + overlap;        // Upper limit to next section
+
+    // Blow away existing data.
+    // Do this before returning, so that we're not returning data from a previous read
+    psFree(*image);
+    *image = NULL;
+    *image = readoutReadComponent(*image, fits, trimsec, readdir, next, upper, z, bad, pixelTypes[type]);
+
+    // Read overscans only for "image" type --- weights and masks shouldn't record overscans
+    if (type == FPA_READ_TYPE_IMAGE) {
+        // Blow away existing data
+        while (readout->bias->n > 0) {
+            psListRemove(readout->bias, PS_LIST_HEAD);
+        }
+
+        // Get the new bias sections
+        psList *biassecs = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.BIASSEC"); // Bias sections
+        if (!mdok || !biassecs) {
+            psError(PS_ERR_IO, true, "CELL.BIASSEC is not set.\n");
+            return false;
+        }
+        psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, false); // Iterator
+        psRegion *biassec = NULL;           // Bias section from iteration
+        while ((biassec = psListGetAndIncrement(biassecsIter))) {
+            psImage *bias = readoutReadComponent(NULL, fits, biassec, readdir, next, upper, z,
+                                                 bad, pixelTypes[type]); // The bias
+            psListAdd(readout->bias, PS_LIST_TAIL, bias);
+            psFree(bias);                   // Drop reference
+        }
+        psFree(biassecsIter);
+    }
+
+    return true;
+}
+
+// Read into an cell; this is the engine for pmCellRead, pmCellReadMask, pmCellReadWeight
+// Does most of the work for the reading --- reads the HDU, and portions the HDU into readouts.
+static bool cellRead(pmCell *cell,      // Cell into which to read
+                     psFits *fits,      // FITS file from which to read
+                     pmConfig *config,  // Configuration
+                     fpaReadType type   // Type to read
+                    )
+{
+    assert(cell);
+    assert(fits);
+
+    pmHDU *hdu = pmHDUFromCell(cell);   // The HDU
+    if (!hdu) {
+        return true;                    // We read everything we could
+    }
+
+    // check if we have read the desired data, read it if needed
+    bool (*hduReadFunc)(pmHDU*, psFits*) = NULL; // Function to use to read the HDU
+    void *dataPointer = NULL;           // pointer to location of desired data
+    switch (type) {
+      case FPA_READ_TYPE_IMAGE:
+        hduReadFunc = pmHDURead;
+        dataPointer = hdu->images;
+        break;
+      case FPA_READ_TYPE_HEADER:
+        hduReadFunc = pmHDUReadHeader;
+        dataPointer = hdu->header;
+        break;
+      case FPA_READ_TYPE_MASK:
+        hduReadFunc = pmHDUReadMask;
+        dataPointer = hdu->masks;
+        break;
+      case FPA_READ_TYPE_WEIGHT:
+        hduReadFunc = pmHDUReadWeight;
+        dataPointer = hdu->weights;
+        break;
+      default:
+        psAbort("Unknown read type: %x\n", type);
+    }
+
+    // do we have the data we want (image, header, or etc).
+    if (!dataPointer) {
+        // attempt to read in the desired data
+        if (!hduReadFunc(hdu, fits)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to read HDU for cell.\n");
+            return false;
+        }
+    }
+
+    // load in the concept information for this cell
+    if (!pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_DATABASE, true, config)) {
+        //psError(PS_ERR_UNKNOWN, false, "Failed to read concepts for cell");
+        //return false;
+        psWarning("Difficulty reading concepts for cell; attempting to proceed.");
+    }
+
+    // skip the image arrays completely for the header-only files
+    if (type == FPA_READ_TYPE_HEADER) {
+        pmCellSetDataStatus(cell, true);
+        return true;
+    }
+
+    // set up pointers for the different possible image arrays
+    psArray *imageArray = NULL; // Array of images in the HDU
+    psElemType imageType = pixelTypes[type]; // Expected type for image
+    switch (type) {
+      case FPA_READ_TYPE_IMAGE:
+        imageArray = hdu->images;
+        break;
+      case FPA_READ_TYPE_MASK:
+        imageArray = hdu->masks;
+        break;
+      case FPA_READ_TYPE_WEIGHT:
+        imageArray = hdu->weights;
+        break;
+      default:
+        psAbort("Unknown read type: %x\n", type);
+    }
+
+    // Having read the cell, we now have to cut it up
+    psRegion *trimsec = psMetadataLookupPtr(NULL, cell->concepts, "CELL.TRIMSEC");
+    psList *biassecs = psMetadataLookupPtr(NULL, cell->concepts, "CELL.BIASSEC");
+    if (psRegionIsNaN(*trimsec)) {
+        psError(PS_ERR_IO, false, "CELL.TRIMSEC is not set --- can't read cell.\n");
+        return false;
+    }
+
+    // Iterate over each of the image planes, converting type if necessary, and extracting the bits that
+    // matter (CELL.TRIMSEC, CELL.BIASSEC) into readouts with readoutCarve.
+    for (int i = 0; i < imageArray->n; i++) {
+        psImage *source = imageArray->data[i]; // Source image, from the i-th plane
+        PS_ASSERT_IMAGE_NON_NULL(source, false);
+
+        // Type conversion here to support the modules, which don't have multiple type support yet
+        if (source->type.type != imageType) {
+            psImage *temp = psImageCopy(NULL, source, imageType); // Temporary image
+            psFree(imageArray->data[i]);
+            imageArray->data[i] = temp;
+            source = temp;
+        }
+
+        pmReadout *readout;             // Readout into which to read
+        if (cell->readouts->n > i && cell->readouts->data[i]) {
+            readout = psMemIncrRefCounter(cell->readouts->data[i]);
+        } else {
+            readout = pmReadoutAlloc(cell);
+        }
+
+        if (!readoutCarve(readout, source, trimsec, biassecs, type)) {
+            psError(PS_ERR_UNEXPECTED_NULL, false,
+                    "Unable to carve readout into image and bias sections for %d the plane.", i);
+            return NULL;
+        }
+        psFree(readout);                // Drop reference
+    }
+
+    pmCellSetDataStatus(cell, true);
+    return true;
+}
+
+
+// Read into an chip; this is the engine for pmChipRead, pmChipReadMask, pmChipReadWeight
+// Iterates over component cells, reading each
+static bool chipRead(pmChip *chip,      // Chip into which to read
+                     psFits *fits,      // FITS file from which to read
+                     pmConfig *config,  // Configuration
+                     fpaReadType type   // Type to read
+                    )
+{
+    assert(chip);
+    assert(fits);
+
+    bool success = false;               // Were we able to read at least one HDU?
+    psArray *cells = chip->cells;       // Array of cells
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // The cell of interest
+        success |= cellRead(cell, fits, config, type);
+    }
+    if (success) {
+        if (!pmConceptsReadChip(chip, PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_DATABASE,
+                                true, true, NULL)) {
+            psError(PS_ERR_IO, false, "Failed to read concepts for chip.\n");
+            return false;
+        }
+        // XXX probably could just use chip->data_exists
+        pmChipSetDataStatus(chip, true);
+    }
+
+    return success;
+}
+
+
+// Read into an FPA; this is the engine for pmFPARead, pmFPAReadMask, pmFPAReadWeight
+// Iterates over component chips, reading each
+static bool fpaRead(pmFPA *fpa,         // FPA into which to read
+                    psFits *fits,       // FITS file from which to read
+                    pmConfig *config,   // Configuration
+                    fpaReadType type    // Type to read
+                   )
+{
+    assert(fpa);
+    assert(fits);
+
+    bool success = false;               // Were we able to read at least one HDU?
+    psArray *chips = fpa->chips;        // Array of chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // The cell of interest
+        success |= chipRead(chip, fits, config, type);
+    }
+    if (success) {
+        if (!pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_DATABASE, true, NULL)) {
+            psError(PS_ERR_IO, false, "Failed to read concepts for FPA.\n");
+            return false;
+        }
+    } else {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read any chips in FPA");
+    }
+
+    return success;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Reading images
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// pmReadoutReadNext is maintained here (for now) to maintain backwards compatibility.
+// pmReadoutReadNext has been replaced by pmReadoutRead, pmReadoutReadChunk, pmReadoutMore
+bool pmReadoutReadNext(bool *status, pmReadout *readout, psFits *fits, int z, int numScans, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    PS_ASSERT_INT_NONNEGATIVE(z, false);
+    PS_ASSERT_INT_NONNEGATIVE(numScans, false);
+
+    assert (numScans || !readout->image); // cannot have readout->image and !numScans
+
+    *status = false;
+
+    // Get the HDU and read the header
+    pmCell *cell = readout->parent;     // The parent cell
+
+    pmHDU *hdu = pmHDUFromCell(cell);   // The HDU
+    if (!hdu || hdu->blankPHU) {
+        // XXX is this an error condition?
+        *status = true;
+        return false;
+    }
+
+    if (!pmCellReadHeader(cell, fits, config)) {
+        psError(PS_ERR_IO, false, "Unable to read header for cell!\n");
+        return false;
+    }
+
+    // Make sure we have the information we need
+    if (!pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_CELLS |
+                            PM_CONCEPT_SOURCE_DEFAULTS | PM_CONCEPT_SOURCE_DATABASE, true, NULL)) {
+        psError(PS_ERR_IO, false, "Failed to read concepts for cell.\n");
+        return false;
+    }
+
+    // Get the trim and bias sections
+    bool mdok = true;                   // Status of MD lookup
+    psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim sections
+    if (!mdok || !trimsec || psRegionIsNaN(*trimsec)) {
+        psError(PS_ERR_IO, true, "CELL.TRIMSEC is not set.\n");
+        return false;
+    }
+    psList *biassecs = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.BIASSEC"); // Bias sections
+    if (!mdok || !biassecs) {
+        psError(PS_ERR_IO, true, "CELL.BIASSEC is not set.\n");
+        return false;
+    }
+    int readdir = psMetadataLookupS32(&mdok, cell->concepts, "CELL.READDIR"); // Read direction
+    if (!mdok || readdir == 0 || (readdir != 1 && readdir != 2)) {
+        psError(PS_ERR_IO, true, "CELL.READDIR is not set to -1 or +1.\n");
+        return false;
+    }
+    float bad = psMetadataLookupF32(&mdok, cell->concepts, "CELL.BAD"); // Bad level
+    if (!mdok) {
+        psLogMsg(__func__, PS_LOG_WARN, "CELL.BAD is not set --- assuming zero.\n");
+        bad = 0.0;
+    }
+
+    // Check the third dimension
+    int naxis = psMetadataLookupS32(&mdok, hdu->header, "NAXIS"); // The number of axes
+    if (!mdok) {
+        psError(PS_ERR_IO, true, "Unable to find NAXIS in header for extension %s\n", hdu->extname);
+        return false;
+    }
+    if (naxis == 0) {
+        // No pixels to read, as for a PHU.
+        *status = true;
+        return false;
+    }
+    if (naxis < 2 || naxis > 3) {
+        psError(PS_ERR_IO, true, "NAXIS in header of extension %s (= %d) is not valid.\n",
+                hdu->extname, naxis);
+        return false;
+    }
+    int naxis3 = 1;                     // The number of image planes
+    if (naxis == 3) {
+        naxis3 = psMetadataLookupS32(&mdok, hdu->header, "NAXIS3");
+        if (!mdok) {
+            psError(PS_ERR_IO, true, "Unable to find NAXIS3 in header for extension %s\n", hdu->extname);
+            return false;
+        }
+    }
+    if (z >= naxis3) {
+        // Nothing to see here.  Move along.
+        *status = true;
+        return false;
+    }
+
+    // Get the size of the image plane
+    int naxis1 = psMetadataLookupS32(&mdok, hdu->header, "NAXIS1"); // The number of columns
+    if (!mdok) {
+        psError(PS_ERR_IO, true, "Unable to find NAXIS1 in header for extension %s\n", hdu->extname);
+        return false;
+    }
+    int naxis2 = psMetadataLookupS32(&mdok, hdu->header, "NAXIS2"); // The number of columns
+    if (!mdok) {
+        psError(PS_ERR_IO, true, "Unable to find NAXIS2 in header for extension %s\n", hdu->extname);
+        return false;
+    }
+
+    // rationalize trimsec against naxis1, naxis2
+    // valid range for trimsec is 1-Nx,1-Ny
+    // if (trimsec->x0 == 0) trimsec->x0 = 1;
+    if (trimsec->x1 <  1)
+        trimsec->x1 = naxis1 + trimsec->x1;
+    // if (trimsec->y0 == 0) trimsec->y0 = 1;
+    if (trimsec->y1 <  1)
+        trimsec->y1 = naxis2 + trimsec->y1;
+
+    int maxSize;                        // Number of cols,rows in image
+    if (readdir == 1) {
+        maxSize = PS_MIN(naxis2, trimsec->y1 - trimsec->y0);
+    } else {
+        maxSize = PS_MIN(naxis1, trimsec->x1 - trimsec->x0);
+    }
+
+    int offset;                         // start of the segment
+    int upper;                          // end of the segment
+    int lastScan;                       // last possible scan
+
+    // Calculate the segment offset and upper limit, adjust readout->row0,col0
+    if (readdir == 1) {
+        // Reading rows
+        offset = (readout->image) ? readout->row0 + numScans : 0; // extend to next section or start at beginning?
+        offset = (numScans == 0) ? trimsec->x0 : offset; // full array ? read full trimsec : read section
+        readout->row0 = offset;
+        readout->col0 = trimsec->x0;
+        lastScan = trimsec->y1;
+    } else {
+        // Reading cols
+        offset = (readout->image) ? readout->col0 + numScans : 0;
+        offset = (numScans == 0) ? trimsec->y0 : offset; // full array ? read full trimsec : read section
+        readout->col0 = offset;
+        readout->row0 = trimsec->y0;
+        lastScan = trimsec->x1;
+    }
+    upper = offset + numScans;
+
+    // Blow away existing data.
+    // Do this before returning, so that we're not returning data from a previous read
+    psFree(readout->image);
+    readout->image = NULL;
+
+    while (readout->bias->n > 0) {
+        psListRemove(readout->bias, PS_LIST_HEAD);
+    }
+
+    if (offset >= lastScan) {
+        // We've read everything there is
+        psTrace("psModules.camera", 7, "Read everything.\n");
+        *status = true;
+        return false;
+    }
+
+    psTrace("psModules.camera", 7, "offset=%d, upper = %d, image is %dx%d, trimsec [%.0f:%.0f,%.0f:%.0f]\n",
+            offset, upper, naxis1, naxis2, trimsec->x0, trimsec->x1, trimsec->y0, trimsec->y1);
+
+    // Get the new the trim section
+    readout->image = readoutReadComponent(readout->image, fits, trimsec, readdir, offset, upper, z, bad,
+                                          PS_TYPE_F32); // The image
+
+    // Get the new bias sections
+    psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, false); // Iterator for BIASSEC
+    psRegion *biassec = NULL;           // Bias section from iteration
+    while ((biassec = psListGetAndIncrement(biassecsIter))) {
+        psImage *bias = readoutReadComponent(NULL, fits, biassec, readdir, offset, upper, z, bad,
+                                             PS_TYPE_F32); // The bias
+        psListAdd(readout->bias, PS_LIST_TAIL, bias);
+        psFree(bias);                   // Drop reference
+    }
+    psFree(biassecsIter);
+
+    *status = true;
+    return true;
+}
+
+
+
+bool pmReadoutMore(pmReadout *readout, psFits *fits, int z, int numScans, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return readoutMore(readout, fits, z, numScans, FPA_READ_TYPE_IMAGE, config);
+}
+
+bool pmReadoutReadChunk(pmReadout *readout, psFits *fits, int z, int numScans, int overlap, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_INT_NONNEGATIVE(z, false);
+    PS_ASSERT_INT_NONNEGATIVE(numScans, false);
+
+    return readoutReadChunk(readout, fits, z, numScans, overlap, FPA_READ_TYPE_IMAGE, config);
+}
+
+bool pmReadoutRead(pmReadout *readout, psFits *fits, int z, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return readoutReadChunk(readout, fits, z, 0, 0, FPA_READ_TYPE_IMAGE, config);
+}
+
+int pmCellNumReadouts(pmCell *cell, psFits *fits, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return cellNumReadouts(cell, fits, config);
+}
+
+bool pmCellRead(pmCell *cell, psFits *fits, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return cellRead(cell, fits, config, FPA_READ_TYPE_IMAGE);
+}
+
+bool pmChipRead(pmChip *chip, psFits *fits, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return chipRead(chip, fits, config, FPA_READ_TYPE_IMAGE);
+}
+
+bool pmFPARead(pmFPA *fpa, psFits *fits, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return fpaRead(fpa, fits, config, FPA_READ_TYPE_IMAGE);
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Reading the mask
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmReadoutMoreMask(pmReadout *readout, psFits *fits, int z, int numScans, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return readoutMore(readout, fits, z, numScans, FPA_READ_TYPE_MASK, config);
+}
+
+bool pmReadoutReadChunkMask(pmReadout *readout, psFits *fits, int z, int numScans, int overlap,
+                            pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_INT_NONNEGATIVE(z, false);
+    PS_ASSERT_INT_NONNEGATIVE(numScans, false);
+
+    return readoutReadChunk(readout, fits, z, numScans, overlap, FPA_READ_TYPE_MASK, config);
+}
+
+bool pmReadoutReadMask(pmReadout *readout, psFits *fits, int z, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return readoutReadChunk(readout, fits, z, 0, 0, FPA_READ_TYPE_MASK, config);
+}
+
+bool pmCellReadMask(pmCell *cell, psFits *fits, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return cellRead(cell, fits, config, FPA_READ_TYPE_MASK);
+}
+
+bool pmChipReadMask(pmChip *chip, psFits *fits, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return chipRead(chip, fits, config, FPA_READ_TYPE_MASK);
+}
+
+bool pmFPAReadMask(pmFPA *fpa, psFits *fits, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return fpaRead(fpa, fits, config, FPA_READ_TYPE_MASK);
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Reading the weight map
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmReadoutMoreWeight(pmReadout *readout, psFits *fits, int z, int numScans, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return readoutMore(readout, fits, z, numScans, FPA_READ_TYPE_WEIGHT, config);
+}
+
+bool pmReadoutReadChunkWeight(pmReadout *readout, psFits *fits, int z, int numScans, int overlap,
+                              pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_INT_NONNEGATIVE(z, false);
+    PS_ASSERT_INT_NONNEGATIVE(numScans, false);
+
+    return readoutReadChunk(readout, fits, z, numScans, overlap, FPA_READ_TYPE_WEIGHT, config);
+}
+
+bool pmReadoutReadWeight(pmReadout *readout, psFits *fits, int z, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return readoutReadChunk(readout, fits, z, 0, 0, FPA_READ_TYPE_WEIGHT, config);
+}
+
+bool pmCellReadWeight(pmCell *cell, psFits *fits, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return cellRead(cell, fits, config, FPA_READ_TYPE_WEIGHT);
+}
+
+bool pmChipReadWeight(pmChip *chip, psFits *fits, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return chipRead(chip, fits, config, FPA_READ_TYPE_WEIGHT);
+}
+
+bool pmFPAReadWeight(pmFPA *fpa, psFits *fits, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return fpaRead(fpa, fits, config, FPA_READ_TYPE_WEIGHT);
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Reading the image header
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmCellReadHeaderSet(pmCell *cell, psFits *fits, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return cellRead(cell, fits, config, FPA_READ_TYPE_HEADER);
+}
+
+bool pmChipReadHeaderSet(pmChip *chip, psFits *fits, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return chipRead(chip, fits, config, FPA_READ_TYPE_HEADER);
+}
+
+bool pmFPAReadHeaderSet(pmFPA *fpa, psFits *fits, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    return fpaRead(fpa, fits, config, FPA_READ_TYPE_HEADER);
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Reading FITS tables
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+int pmCellReadTable(pmCell *cell, psFits *fits, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, 0);
+    PS_ASSERT_FITS_NON_NULL(fits, 0);
+    PS_ASSERT_STRING_NON_EMPTY(name, 0);
+
+    const char *chipName = psMetadataLookupStr(NULL, cell->parent->concepts, "CHIP.NAME"); // Name of chip
+    const char *cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME"); // Name of cell
+    psString extname = NULL;            // Extension name
+    psStringAppend(&extname, "%s_%s_%s", name, chipName, cellName);
+
+    // XXX Could do a table lookup from the camera format, in case the input file isn't laid out with
+    // NAME_CHIP_CELL extension names --- use these as keys, and the value as the proper extension name.
+    // Allow interpolation of concepts, e.g., "{CHIP.NAME}" --> "ccd13".
+
+    if (!psFitsMoveExtName(fits, extname)) {
+        psError(PS_ERR_IO, false, "Unable to move to extension %s\n", extname);
+        psFree(extname);
+        return 0;
+    }
+
+    psMetadata *header = psFitsReadHeader(NULL, fits); // The FITS header
+    if (!header) {
+        psError(PS_ERR_IO, false, "Unable to read header for extension %s\n", extname);
+        psFree(extname);
+        psFree(header);
+        return 0;
+    }
+
+    psString headerName = NULL;         // Name for header
+    psStringAppend(&headerName, "%s.HEADER", name);
+    if (!psMetadataAdd(cell->analysis, PS_LIST_TAIL, headerName, PS_DATA_METADATA,
+                       "FITS table header", header)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to add header from extension %s to analysis metadata "
+                "for chip %s, cell %s\n", extname, chipName, cellName);
+        psFree(headerName);
+        psFree(header);
+        psFree(extname);
+        return 0;
+    }
+    psFree(headerName);
+    psFree(header);
+
+    psArray *table = psFitsReadTable(fits); // The table
+    if (!psMetadataAdd(cell->analysis, PS_LIST_TAIL, name, PS_DATA_ARRAY, "FITS table", table)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to add table from extension %s to analysis metadata "
+                "for chip %s, cell %s\n", extname, chipName, cellName);
+        psFree(table);
+        psFree(extname);
+        return 0;
+    }
+
+    psFree(extname);
+    psFree(table);                      // Dropping reference
+
+    return 1;
+}
+
+
+int pmChipReadTable(pmChip *chip, psFits *fits, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, 0);
+    PS_ASSERT_FITS_NON_NULL(fits, 0);
+    PS_ASSERT_STRING_NON_EMPTY(name, 0);
+
+    int numRead = 0;                    // Number of reads
+    psArray *cells = chip->cells;       // Array of cells
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // Cell of interest
+        numRead += pmCellReadTable(cell, fits, name);
+    }
+
+    return numRead;
+}
+
+
+int pmFPAReadTable(pmFPA *fpa, psFits *fits, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, 0);
+    PS_ASSERT_FITS_NON_NULL(fits, 0);
+    PS_ASSERT_STRING_NON_EMPTY(name, 0);
+
+    int numRead = 0;                    // Number of reads
+    psArray *chips = fpa->chips;        // Array of chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // Chip of interest
+        numRead += pmChipReadTable(chip, fits, name);
+    }
+
+    return numRead;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPARead.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPARead.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPARead.h	(revision 22157)
@@ -0,0 +1,248 @@
+/* @file pmFPARead.h
+ * @brief Functions to read FPA components from a FITS file
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.15 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-06-17 22:16:38 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_READ_H
+#define PM_FPA_READ_H
+
+#include <pmConfig.h>
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+/// Check to see if there is more to read when reading a readout incrementally
+bool pmReadoutMore(pmReadout *readout,  ///< Readout of interest
+                   psFits *fits,        ///< FITS file from which to read
+                   int z,               ///< Readout number/plane; zero-offset indexing
+                   int numScans,        ///< Number of scans (rows/cols) to read
+                   pmConfig *config     ///< Configuration
+    );
+
+/// Read a chunk of a readout.
+///
+/// Allows reading the readout incrementally
+bool pmReadoutReadChunk(pmReadout *readout, ///< Readout of interest
+                        psFits *fits,   ///< FITS file from which to read
+                        int z,          ///< Readout number/plane; zero-offset indexing
+                        int numScans,   ///< Number of scans (rows/cols) to read
+                        int overlap,    ///< Overlap between consecutive reads
+                        pmConfig *config ///< Configuration
+    );
+
+/// Read the entire readout
+bool pmReadoutRead(pmReadout *readout,  ///< Readout of interest
+                   psFits *fits,        ///< FITS file from which to read
+                   int z,               ///< Readout number/plane; zero-offset indexing
+                   pmConfig *config     ///< Configuration
+    );
+
+/// Read a readout incrementally --- this is maintained temporarily only for backwards compatibility; it has
+/// been replaced by pmReadoutRead, pmReadoutReadChunk and pmReadoutMore.
+///
+/// Multiple calls to this function moves through a readout within a cell incrementally.  It is required to
+/// pass the readout previously acquired (or a newly-allocated one) in order to preserve state information
+/// (where the read is at).  Only a maximum of numRows rows are read at a time.  Returns true if pixels are
+/// read, and false otherwise.  To facilitate looping, no error is generated for reading a plane that doesn't
+/// exist.  Note that this function doesn't put pixels in the HDU.  It is therefore NOT COMPATIBLE with
+/// pmCellWrite, pmChipWrite, and pmFPAWrite (or any function that uses or creates an HDU for that matter).
+/// Use pmReadoutWriteNext to write the data that's read by this function.  This function is intended for
+/// reading in many readouts into memory at once (e.g., for stacking) where the input is not written out.
+bool pmReadoutReadNext(bool *status,    // non-error exit condition?
+                       pmReadout *readout, // Readout into which to read
+                       psFits *fits,    // FITS file from which to read
+                       int z,           // Readout number/plane; zero-offset indexing
+                       int numRows,      // The number of rows to read
+                       pmConfig *config
+                      );
+
+/// Return the number of readouts within a cell
+///
+/// This function is type-independent (doesn't matter if you are interested in the image/mask/weight).
+int pmCellNumReadouts(pmCell *cell, psFits *fits, pmConfig *config);
+
+/// Read an entire cell
+///
+/// Reads the appropriate HDU, ingests concepts from the header, and portions pixels into readouts.  Pixels
+/// are converted to F32.
+bool pmCellRead(pmCell *cell,           // Cell to read into
+                psFits *fits,           // FITS file from which to read
+                pmConfig *config        // Configuration
+               );
+
+/// Read a chip
+///
+/// Iterates over component cells, reading each with pmCellRead.
+bool pmChipRead(pmChip *chip,           // Chip to read into
+                psFits *fits,           // FITS file from which to read
+                pmConfig *config        // Configuration
+               );
+
+/// Read an FPA
+///
+/// Iterates over component chips, reading each with pmChipRead.
+bool pmFPARead(pmFPA *fpa,              // FPA to read into
+               psFits *fits,            // FITS file from which to read
+               pmConfig *config         // Configuration
+              );
+
+// Mask functions follow
+
+/// Check to see if there is more to read when reading a readout incrementally into the mask
+bool pmReadoutMoreMask(pmReadout *readout, ///< Readout of interest
+                       psFits *fits,    ///< FITS file from which to read
+                       int z,           ///< Readout number/plane; zero-offset indexing
+                       int numScans,    ///< Number of scans (rows/cols) to read
+                       pmConfig *config ///< Configuration
+    );
+
+/// Read a chunk of a readout into the mask
+///
+/// Allows reading the readout incrementally
+bool pmReadoutReadChunkMask(pmReadout *readout, ///< Readout of interest
+                            psFits *fits, ///< FITS file from which to read
+                            int z,      ///< Readout number/plane; zero-offset indexing
+                            int numScans, ///< Number of scans (rows/cols) to read
+                            int overlap, ///< Overlap between consecutive reads
+                            pmConfig *config ///< Configuration
+    );
+
+/// Read the entire readout into the mask
+bool pmReadoutReadMask(pmReadout *readout, ///< Readout of interest
+                       psFits *fits,    ///< FITS file from which to read
+                       int z,           ///< Readout number/plane; zero-offset indexing
+                       pmConfig *config ///< Configuration
+    );
+
+/// Read an entire cell into the mask
+///
+/// Same as pmCellRead, but reads into the mask element of the readouts.
+bool pmCellReadMask(pmCell *cell,       // Cell to read into
+                    psFits *fits,       // FITS file from which to read
+                    pmConfig *config    // Configuration
+                   );
+
+/// Read an entire chip into the mask
+///
+/// Same as pmChipRead, but reads into the mask element of the readouts.
+bool pmChipReadMask(pmChip *chip,       // Chip to read into
+                    psFits *fits,       // FITS file from which to read
+                    pmConfig *config    // Configuration
+                   );
+
+/// Read an entire FPA into the mask
+///
+/// Same as pmFPARead, but reads into the mask element of the readouts.
+bool pmFPAReadMask(pmFPA *fpa,          // FPA to read into
+                   psFits *fits,        // FITS file from which to read
+                   pmConfig *config     // Configuration
+                  );
+
+// Weight functions follow
+
+/// Check to see if there is more to read when reading a readout incrementally into the weight
+bool pmReadoutMoreWeight(pmReadout *readout, ///< Readout of interest
+                         psFits *fits,  ///< FITS file from which to read
+                         int z,         ///< Readout number/plane; zero-offset indexing
+                         int numScans,  ///< Number of scans (rows/cols) to read
+                         pmConfig *config ///< Configuration
+    );
+
+/// Read a chunk of a readout into the weight
+///
+/// Allows reading the readout incrementally
+bool pmReadoutReadChunkWeight(pmReadout *readout, ///< Readout of interest
+                              psFits *fits, ///< FITS file from which to read
+                              int z,    ///< Readout number/plane; zero-offset indexing
+                              int numScans, ///< Number of scans (rows/cols) to read
+                              int overlap, ///< Overlap between consecutive reads
+                              pmConfig *config ///< Configuration
+    );
+
+/// Read the entire readout into the weight
+bool pmReadoutReadWeight(pmReadout *readout, ///< Readout of interest
+                         psFits *fits,  ///< FITS file from which to read
+                         int z,         ///< Readout number/plane; zero-offset indexing
+                         pmConfig *config ///< Configuration
+    );
+
+/// Read an entire cell into the weight
+///
+/// Same as pmCellRead, but reads into the weight element of the readouts.
+bool pmCellReadWeight(pmCell *cell,     // Cell to read into
+                      psFits *fits,     // FITS file from which to read
+                      pmConfig *config  // Configuration
+                     );
+
+/// Read an entire chip into the weight
+///
+/// Same as pmChipRead, but reads into the weight element of the readouts.
+bool pmChipReadWeight(pmChip *chip,     // Chip to read into
+                      psFits *fits,     // FITS file from which to read
+                      pmConfig *config  // Configuration
+                     );
+
+/// Read an entire FPA into the weight
+///
+/// Same as pmFPARead, but reads into the weight element of the readouts.
+bool pmFPAReadWeight(pmFPA *fpa,        // FPA to read into
+                     psFits *fits,      // FITS file from which to read
+                     pmConfig *config   // Configuration
+                    );
+
+/// Read cell headers
+///
+/// Same as pmCellRead, but reads only the headers of the readouts.
+bool pmCellReadHeaderSet(pmCell *cell,  // Cell to read into
+                         psFits *fits,  // FITS file from which to read
+                         pmConfig *config // Configuration
+    );
+
+/// Read chip headers
+///
+/// Same as pmChipRead, but reads only the headers of the readouts.
+bool pmChipReadHeaderSet(pmChip *chip,  // Chip to read into
+                      psFits *fits,     // FITS file from which to read
+                      pmConfig *config  // Configuration
+                     );
+
+/// Read FPA headers
+///
+/// Same as pmFPARead, but reads only the headers of the readouts.
+bool pmFPAReadHeaderSet(pmFPA *fpa,     // FPA to read into
+                        psFits *fits,   // FITS file from which to read
+                        pmConfig *config // Configuration
+    );
+
+/// Read a FITS table into the cell
+///
+/// Given a name, which is combined with the chip and cell to identify the extension name ("NAME_CHIP_CELL"),
+/// read the FITS table into the cell analysis metadata (with key being the provided name).  The header is
+/// also read and included in the cell analysis metadata under "name.HEADER".
+int pmCellReadTable(pmCell *cell,       ///< Cell for which to read table
+                    psFits *fits,       ///< FITS file from which the table
+                    const char *name    ///< Specifies the extension name, and target in the analysis metadata
+                   );
+
+/// Read a FITS table into the component cells
+///
+/// Iterates over component cells, calling pmCellReadTable.
+int pmChipReadTable(pmChip *chip,       ///< Cell for which to read table
+                    psFits *fits,       ///< FITS file from which the table
+                    const char *name    ///< Specifies the extension name, and target in the analysis metadata
+                   );
+
+/// Read a FITS table into the component cells
+///
+/// Iterates over component chips, calling pmChipReadTable.
+int pmFPAReadTable(pmFPA *fpa,          ///< Cell for which to read table
+                   psFits *fits,        ///< FITS file from which the table
+                   const char *name     ///< Specifies the extension name, and target in the analysis metadata
+                  );
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAUtils.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAUtils.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAUtils.c	(revision 22157)
@@ -0,0 +1,55 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAUtils.h"
+
+int pmFPAFindChip(const pmFPA *fpa, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, -1);
+    PS_ASSERT_PTR_NON_NULL(name, -1);
+    if (strlen(name) == 0) {
+        return -1;
+    }
+
+    psArray *chips = fpa->chips;        // Array of chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i]; // The chip of interest
+        psString testName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME"); // Name of this chip
+        if (strcmp(name, testName) == 0) {
+            return i;
+        }
+    }
+
+    psError(PS_ERR_IO, true, "Unable to find chip %s\n", name);
+    return -1;
+}
+
+
+int pmChipFindCell(const pmChip *chip, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, -1);
+    PS_ASSERT_PTR_NON_NULL(name, -1);
+    if (strlen(name) == 0) {
+        return -1;
+    }
+
+    psArray *cells = chip->cells;    // Array of cells
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i]; // The cell of interest
+        psString testName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME"); // Name of this cell
+        if (strcmp(name, testName) == 0) {
+            return i;
+        }
+    }
+
+    psError(PS_ERR_IO, true, "Unable to find cell %s\n", name);
+    return -1;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAUtils.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAUtils.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAUtils.h	(revision 22157)
@@ -0,0 +1,33 @@
+/* @file pmFPAUtils.h
+ * @brief Utility functions for FPAs
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-03-30 21:12:56 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_UTILS_H
+#define PM_FPA_UTILS_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+/// Find a chip by name; return the index
+///
+/// Looks for a chip within the FPA with CHIP.NAME matching the provided name.  Returns the index of the chip,
+/// or -1 if it was not found.
+int pmFPAFindChip(const pmFPA *fpa,     ///< FPA in which to find the chip
+                  const char *name      ///< Name of the chip
+                 );
+
+/// Find a cell by name; return the index
+///
+/// Looks for a cell within the chip with CELL.NAME matching the provided name.  Returns the index of the
+/// cell, or -1 if it was not found.
+int pmChipFindCell(const pmChip *chip,  // Chip in which to find the cell
+                   const char *name     // Name of the cell
+                  );
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAWrite.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAWrite.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAWrite.c	(revision 22157)
@@ -0,0 +1,510 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <strings.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmDetrendDB.h"
+#include "pmFPAfile.h"
+#include "pmHDUUtils.h"
+#include "pmHDUGenerate.h"
+#include "pmConcepts.h"
+
+#include "pmFPAWrite.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Definitions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Specify what to read
+typedef enum {
+    FPA_WRITE_TYPE_IMAGE,               // Write image
+    FPA_WRITE_TYPE_MASK,                // Write mask
+    FPA_WRITE_TYPE_WEIGHT               // Write weight map
+} fpaWriteType;
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static (private) functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+// Update the FPA.OBS, CHIP.NAME and CELL.NAME in the FITS header, if required
+bool pmFPAUpdateNames(pmFPA *fpa, pmChip *chip, pmCell *cell)
+{
+    pmHDU *hdu = pmHDUGetHighest(fpa, chip, cell); // Highest HDU, i.e., the PHU
+    if (!hdu) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find HDU.\n");
+        return false;
+    }
+    if (!hdu->header) {
+        hdu->header = psMetadataAlloc();
+    }
+    bool mdok;                          // Status of MD lookup
+    psMetadata *fileData = psMetadataLookupMetadata(&mdok, hdu->format, "FILE"); // File information
+    if (!mdok || !fileData) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find FILE information in camera format.\n");
+        return false;
+    }
+    if (fpa) {
+        const char *fpaObsHdr = psMetadataLookupStr(&mdok, fileData, "FPA.OBS");
+        if (mdok && fpaObsHdr && strlen(fpaObsHdr) > 0) {
+            const char *fpaObs = psMetadataLookupStr(NULL, fpa->concepts, "FPA.OBS");
+            psMetadataAddStr(hdu->header, PS_LIST_TAIL, fpaObsHdr, PS_META_REPLACE,
+                             "Observation identifier", fpaObs);
+        }
+    }
+
+    if (fpa && !fpa->hdu && (chip || cell)) {
+        const char *rule = psMetadataLookupStr(NULL, fileData, "CONTENT.RULE"); // How to define the CONTENT
+        if (!rule) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to find CONTENT.RULE in FILE in camera format.");
+            return false;
+        }
+
+        pmFPAview *view = pmFPAviewGenerate(fpa, chip, cell, NULL); // View for fpa, chip, cell
+        psString content = pmFPANameFromRule(rule, fpa, view); // Content of this file, specified by the rule
+        psFree(view);
+
+        const char *contentKey = psMetadataLookupStr(NULL, fileData, "CONTENT"); // The CONTENT header keyword
+        if (!contentKey) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to find CONTENT in FILE in the camera format.");
+            psFree(content);
+            return false;
+        }
+
+        psMetadataAddStr(hdu->header, PS_LIST_TAIL, contentKey, PS_META_REPLACE, "Content of file", content);
+        psFree(content);                // Drop reference
+    }
+
+    return true;
+}
+
+// Return the appropriate image array for the given type
+static psArray **appropriateImageArray(pmHDU *hdu, // HDU containing the image arrays
+                                       fpaWriteType type // Type to write
+                                      )
+{
+    switch (type) {
+    case FPA_WRITE_TYPE_IMAGE:
+        return &hdu->images;
+    case FPA_WRITE_TYPE_MASK:
+        return &hdu->masks;
+    case FPA_WRITE_TYPE_WEIGHT:
+        return &hdu->weights;
+    default:
+        psAbort("Unknown write type: %x\n", type);
+    }
+    return NULL;
+}
+
+// Run the appropriate HDU write function
+static bool appropriateWriteFunc(pmHDU *hdu, // HDU to write
+                                 psFits *fits, // FITS file to which to write
+                                 fpaWriteType type // Type to write
+                                )
+{
+    switch (type) {
+    case FPA_WRITE_TYPE_IMAGE:
+        return pmHDUWrite(hdu, fits);
+    case FPA_WRITE_TYPE_MASK:
+        return pmHDUWriteMask(hdu, fits);
+    case FPA_WRITE_TYPE_WEIGHT:
+        return pmHDUWriteWeight(hdu, fits);
+    default:
+        psAbort("Unknown write type: %x\n", type);
+    }
+    return false;
+}
+
+// Write a cell image/mask/weight
+static bool cellWrite(pmCell *cell,     // Cell to write
+                      psFits *fits,     // FITS file to which to write
+                      pmConfig *config, // Configuration
+                      bool blank,       // Write a blank PHU?
+                      fpaWriteType type // Type to write
+                     )
+{
+    assert(cell);
+    assert(fits);
+
+    psTrace ("pmFPAWrite", 5, "writing to Cell (%d)\n", blank);
+
+    pmHDU *hdu = cell->hdu;             // The HDU
+    if (!hdu || !cell->data_exists) {
+        return true;                    // We wrote every HDU that exists
+    }
+
+    psArray **imageArray = appropriateImageArray(hdu, type); // Array of images in the HDU
+
+    // Generate the HDU if needed --- this is required after a pmFPACopy, or similar, which does not
+    // generate the HDU, but only copies the structure.
+    if (!blank && !hdu->blankPHU && !*imageArray && (!pmHDUGenerateForCell(cell) || !*imageArray)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to generate HDU for cell --- likely programming error.\n");
+        return false;
+    }
+
+    // We only write out a blank PHU if it's specifically requested.
+    bool writeBlank = blank && hdu->blankPHU && !*imageArray; // Write a blank PHU?
+    bool writeImage = !blank && !hdu->blankPHU && *imageArray; // Write an image?
+
+    if (writeBlank || writeImage) {
+
+        pmFPAUpdateNames(cell->parent->parent, cell->parent, cell);
+        pmConceptSource source = PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_CELLS |
+                                 PM_CONCEPT_SOURCE_DEFAULTS | PM_CONCEPT_SOURCE_DATABASE;
+        if (!pmConceptsWriteCell(cell, source, true, config)) {
+            psError(PS_ERR_IO, false, "Unable to write concepts for cell.\n");
+            return false;
+        }
+        if (!appropriateWriteFunc(hdu, fits, type)) {
+            psError(PS_ERR_IO, false, "Unable to write HDU for cell.\n");
+            return false;
+        }
+    }
+    // No lower levels to which to recurse
+
+    return true;
+}
+
+// Write a chip image/mask/weight
+static bool chipWrite(pmChip *chip,     // Chip to write
+                      psFits *fits,     // FITS file to which to write
+                      pmConfig *config, // Configuration
+                      bool blank,       // Write a blank PHU?
+                      bool recurse,     // Recurse to lower levels?
+                      fpaWriteType type // Type to write
+                     )
+{
+    assert(chip);
+    assert(fits);
+
+    pmHDU *hdu = chip->hdu;             // The HDU
+
+    psTrace ("pmFPAWrite", 5, "writing to Chip (%d, %d)\n", blank, recurse);
+
+    // If we have data at this level, try to write it out
+    if (hdu && chip->data_exists) {
+        psArray **imageArray = appropriateImageArray(hdu, type); // Array of images in HDU
+
+        // Generate the HDU if needed --- this is required after a pmFPACopy, or similar, which does not
+        // generate the HDU, but only copies the structure.
+        if (!blank && !hdu->blankPHU && !*imageArray && (!pmHDUGenerateForChip(chip) || !*imageArray)) {
+            psError(PS_ERR_UNKNOWN, false,
+                    "Unable to generate HDU for chip --- likely programming error.\n");
+            return false;
+        }
+
+        // We only write out a blank PHU if it's specifically requested.
+        bool writeBlank = blank && hdu->blankPHU && !*imageArray; // Write a blank HDU?
+        bool writeImage = !blank && !hdu->blankPHU && *imageArray; // Write an image?
+
+        if (writeBlank || writeImage) {
+            pmFPAUpdateNames(chip->parent, chip, NULL);
+            pmConceptSource source = PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_CELLS |
+                                     PM_CONCEPT_SOURCE_DEFAULTS | PM_CONCEPT_SOURCE_DATABASE;
+            if (!pmConceptsWriteChip(chip, source, true, true, config)) {
+                psError(PS_ERR_IO, false, "Unable to write concepts for chip.\n");
+                return false;
+            }
+            if (!appropriateWriteFunc(hdu, fits, type)) {
+                psError(PS_ERR_IO, false, "Unable to write HDU for chip.\n");
+                return false;
+            }
+        }
+    }
+
+    // Recurse to lower level if specifically requested.
+    // XXX recursion implies blank == false (must be called on correct level?)
+    if (recurse) {
+        psArray *cells = chip->cells;       // Array of cells
+        for (int i = 0; i < cells->n; i++) {
+            pmCell *cell = cells->data[i];  // The cell of interest
+            if (!cellWrite(cell, fits, config, false, type)) {
+                psError(PS_ERR_IO, false, "Unable to write Chip.\n");
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
+
+// Write an FPA image/mask/weight
+static bool fpaWrite(pmFPA *fpa,        // FPA to write
+                     psFits *fits,      // FITS file to which to write
+                     pmConfig *config,  // Configuration
+                     bool blank,        // Write a blank PHU?
+                     bool recurse,      // Recurse to lower levels?
+                     fpaWriteType type  // Type to write
+                    )
+{
+    assert(fpa);
+    assert(fits);
+
+    pmHDU *hdu = fpa->hdu;              // The HDU
+
+    psTrace ("pmFPAWrite", 5, "writing to FPA (%d, %d)\n", blank, recurse);
+
+    // If we have data at this level, try to write it out
+    if (hdu) {
+        psArray **imageArray = appropriateImageArray(hdu, type); // Array of images in HDU
+
+        // Generate the HDU if needed --- this is required after a pmFPACopy, or similar, which does not
+        // generate the HDU, but only copies the structure.
+        if (!blank && !hdu->blankPHU && !*imageArray && (!pmHDUGenerateForFPA(fpa) || !*imageArray)) {
+            psError(PS_ERR_UNKNOWN, false,
+                    "Unable to generate HDU for FPA --- likely programming error.\n");
+            return false;
+        }
+
+        // We only write out a blank PHU if it's specifically requested.
+        bool writeBlank = blank && hdu->blankPHU && !*imageArray; // Write a blank PHU?
+        bool writeImage = !blank && !hdu->blankPHU && *imageArray; // Write an image?
+
+        if (writeBlank || writeImage) {
+            pmFPAUpdateNames(fpa, NULL, NULL);
+            pmConceptSource source = PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_CELLS |
+                                     PM_CONCEPT_SOURCE_DEFAULTS | PM_CONCEPT_SOURCE_DATABASE;
+            if (!pmConceptsWriteFPA(fpa, source, true, config)) {
+                psError(PS_ERR_IO, false, "Unable to write concepts for FPA.\n");
+                return false;
+            }
+            if (!appropriateWriteFunc(hdu, fits, type))  {
+                psError(PS_ERR_IO, false, "Unable to write HDU for FPA.\n");
+                return false;
+            }
+        }
+    }
+
+    // Recurse to lower levels if requested
+    if (recurse) {
+        psArray *chips = fpa->chips;        // Array of chips
+        for (int i = 0; i < chips->n; i++) {
+            pmChip *chip = chips->data[i];  // The chip of interest
+            if (!chipWrite(chip, fits, config, false, true, type)) {
+                psError(PS_ERR_IO, false, "Unable to write FPA.\n");
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmReadoutWriteNext(pmReadout *readout, psFits *fits, int z)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    pmHDU *hdu = pmHDUFromReadout(readout); // The HDU to which to write
+    if (!hdu) {
+        psError(PS_ERR_IO, false, "Unable to find HDU for readout.\n");
+        return false;
+    }
+    psMetadata *header = hdu->header;   // The FITS header
+    if (!header) {
+        psError(PS_ERR_IO, true, "No FITS header available in the HDU.\n");
+        return false;
+    }
+
+    // We have to rely to a great extent on the FITS header, because otherwise we simply don't know how many
+    // image planes there are (NAXIS3) or the size of the original image (NAXIS1, NAXIS2).
+    bool mdok = true;                   // Status of MD lookup
+    int naxis1 = psMetadataLookupS32(&mdok, header, "NAXIS1"); // Number of columns
+    if (!mdok || naxis1 <= 0) {
+        psError(PS_ERR_IO, true, "Can't find NAXIS1 in header.\n");
+        return false;
+    }
+    int naxis2 = psMetadataLookupS32(&mdok, header, "NAXIS2"); // Number of rows
+    if (!mdok || naxis2 <= 0) {
+        psError(PS_ERR_IO, true, "Can't find NAXIS2 in header.\n");
+        return false;
+    }
+    int naxis3 = psMetadataLookupS32(&mdok, header, "NAXIS3"); // Number of image planes
+    if (!mdok || naxis3 <= 0) {
+        naxis3 = 1;
+    }
+    if (z >= naxis3) {
+        psError(PS_ERR_IO, true, "Specified a plane number (%d) greater than NAXIS3 allows.\n", z);
+        return false;
+    }
+
+    if (!hdu->images) {
+        psError(PS_ERR_IO, true, "No images allocated in HDU.\n");
+        return false;
+    }
+    psImage *image = readout->image; // The image from the HDU to write
+    if (readout->row0 == 0 && readout->col0 == 0 && z == 0) {
+        // Then we can assume that nothing has been written to the FITS file for now
+        if (naxis1 == image->numCols && naxis2 == image->numRows) {
+            // We can write the whole lot at once
+            return psFitsWriteImage(fits, header, image, z, hdu->extname);
+        }
+        // Create a dummy image so we can write something larger than we actually have
+        psImage *dummy = psImageAlloc(naxis1, naxis2, image->type.type); // Dummy image
+        psImageInit(dummy, 0);
+        psImageOverlaySection(dummy, image, 0, 0, "=");
+        bool result = psFitsWriteImage(fits, header, dummy, z, hdu->extname);
+        psFree(dummy);
+        return result;
+    }
+
+    // We can simply update an existing HDU
+    if (hdu->blankPHU && !psFitsMoveExtNum(fits, 0, false)) {
+        psError(PS_ERR_IO, false, "Unable to move to PHU\n");
+        return false;
+    }
+    return psFitsUpdateImage(fits, image, readout->col0, readout->row0, z);
+}
+
+
+bool pmCellWrite(pmCell *cell, psFits *fits, pmConfig *config, bool blank)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    return cellWrite(cell, fits, config, blank, FPA_WRITE_TYPE_IMAGE);
+}
+
+bool pmChipWrite(pmChip *chip, psFits *fits, pmConfig *config, bool blank, bool recurse)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    return chipWrite(chip, fits, config, blank, recurse, FPA_WRITE_TYPE_IMAGE);
+}
+
+bool pmFPAWrite(pmFPA *fpa, psFits *fits, pmConfig *config, bool blank, bool recurse)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    return fpaWrite(fpa, fits, config, blank, recurse, FPA_WRITE_TYPE_IMAGE);
+}
+
+
+bool pmCellWriteMask(pmCell *cell, psFits *fits, pmConfig *config, bool blank)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    return cellWrite(cell, fits, config, blank, FPA_WRITE_TYPE_MASK);
+}
+
+bool pmChipWriteMask(pmChip *chip, psFits *fits, pmConfig *config, bool blank, bool recurse)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    return chipWrite(chip, fits, config, blank, recurse, FPA_WRITE_TYPE_MASK);
+}
+
+bool pmFPAWriteMask(pmFPA *fpa, psFits *fits, pmConfig *config, bool blank, bool recurse)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    return fpaWrite(fpa, fits, config, blank, recurse, FPA_WRITE_TYPE_MASK);
+}
+
+
+bool pmCellWriteWeight(pmCell *cell, psFits *fits, pmConfig *config, bool blank)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    return cellWrite(cell, fits, config, blank, FPA_WRITE_TYPE_WEIGHT);
+}
+
+bool pmChipWriteWeight(pmChip *chip, psFits *fits, pmConfig *config, bool blank, bool recurse)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    return chipWrite(chip, fits, config, blank, recurse, FPA_WRITE_TYPE_WEIGHT);
+}
+
+bool pmFPAWriteWeight(pmFPA *fpa, psFits *fits, pmConfig *config, bool blank, bool recurse)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    return fpaWrite(fpa, fits, config, blank, recurse, FPA_WRITE_TYPE_WEIGHT);
+}
+
+
+int pmCellWriteTable(psFits *fits, const pmCell *cell, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, 0);
+    PS_ASSERT_PTR_NON_NULL(fits, 0);
+    PS_ASSERT_STRING_NON_EMPTY(name, 0);
+
+    const char *chipName = psMetadataLookupStr(NULL, cell->parent->concepts, "CHIP.NAME"); // Name of chip
+    const char *cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME"); // Name of cell
+
+    psArray *table = psMetadataLookupPtr(NULL, cell->analysis, name); // The FITS table
+    if (!table) {
+        // We wrote everything we could find
+        return 0;
+    }
+
+    psString headerName = NULL;         // Name for header in analysis metadata
+    psStringAppend(&headerName, "%s.HEADER", name);
+    psMetadata *header = psMetadataLookupMetadata(NULL, cell->analysis, headerName); // The FITS header
+    psFree(headerName);
+
+    psString extname = NULL;            // Extension name
+    psStringAppend(&extname, "%s_%s_%s", name, chipName, cellName);
+
+    // XXX Could do a table lookup from the camera format, in case the input file isn't laid out with
+    // NAME_CHIP_CELL extension names --- use these as keys, and the value as the proper extension name.
+    // Allow interpolation of concepts, e.g., "{CHIP.NAME}" --> "ccd13".
+
+    if (!psFitsWriteTable(fits, header, table, extname)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to write table from chip %s, cell %s to extension %s\n",
+                chipName, cellName, extname);
+        psFree(extname);
+        return 0;
+    }
+
+    psFree(extname);
+    return 1;
+}
+
+
+int pmChipWriteTable(psFits *fits, const pmChip *chip, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, 0);
+    PS_ASSERT_PTR_NON_NULL(fits, 0);
+    PS_ASSERT_STRING_NON_EMPTY(name, 0);
+
+    int numWrite = 0;                    // Number of reads
+    psArray *cells = chip->cells;       // Array of cells
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // Cell of interest
+        numWrite += pmCellWriteTable(fits, cell, name);
+    }
+
+    return numWrite;
+}
+
+
+int pmFPAWriteTable(psFits *fits, const pmFPA *fpa, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, 0);
+    PS_ASSERT_PTR_NON_NULL(fits, 0);
+    PS_ASSERT_STRING_NON_EMPTY(name, 0);
+
+    int numWrite = 0;                    // Number of reads
+    psArray *chips = fpa->chips;        // Array of chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // Chip of interest
+        numWrite += pmChipWriteTable(fits, chip, name);
+    }
+
+    return numWrite;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAWrite.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAWrite.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAWrite.h	(revision 22157)
@@ -0,0 +1,171 @@
+/* @file pmFPAWrite.h
+ * @brief Write FPA components to a FITS file
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.11 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-06-17 22:16:38 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_WRITE_H
+#define PM_FPA_WRITE_H
+
+#include <pmConfig.h>
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+/// Write a readout incrementally
+///
+/// Writes a readout to a FITS file, perhaps incrementally (if it has been read in using pmReadoutReadNext).
+/// Relies on the FITS header to specify how many image planes there are, and the width and height.
+bool pmReadoutWriteNext(pmReadout *readout, ///< Readout to write
+                        psFits *fits,   ///<  FITS file to which to write
+                        int z           ///<  Image plane to write
+                       );
+
+/// Write a cell to a FITS file
+///
+/// Generates CELL.TRIMSEC, CELL.BIASSEC and the HDU pixels if required.  A blank (i.e., image-less header) is
+/// written only if specifically requested.  Writes the concepts to the various locations, and then the HDU to
+/// the FITS file.  This function should be called at the beginning of the output cell loop with blank=true in
+/// order to produce the correct file structure.
+bool pmCellWrite(pmCell *cell,          ///<  Cell to write
+                 psFits *fits,          ///<  FITS file to which to write
+                 pmConfig *config,      ///<  Configuration
+                 bool blank             ///<  Write a blank PHU?
+                );
+
+/// Write a chip to a FITS file
+///
+/// Generates CELL.TRIMSEC, CELL.BIASSEC and the HDU pixels if required.  A blank (i.e., image-less header) is
+/// written only if specifically requested.  Writes the concepts to the various locations, and then the HDU to
+/// the FITS file, optionally recursing to lower levels.  This function should be called at the beginning of
+/// the output chip loop with blank=true and recurse=false in order to produce the correct file structure.
+bool pmChipWrite(pmChip *chip,          ///<  Chip to write
+                 psFits *fits,          ///<  FITS file to which to write
+                 pmConfig *config,      ///<  Configuration
+                 bool blank,            ///<  Write a blank PHU?
+                 bool recurse           ///<  Recurse to lower levels?
+                );
+
+/// Write an FPA to a FITS file
+///
+/// Generates CELL.TRIMSEC, CELL.BIASSEC and the HDU pixels if required.  A blank (i.e., image-less header) is
+/// written only if specifically requested.  Writes the concepts to the various locations, and then the HDU to
+/// the FITS file, optionally recursing to lower levels.  This function should be called at the beginning of
+/// the output FPA loop with blank=true and recurse=false in order to produce the correct file structure.
+bool pmFPAWrite(pmFPA *fpa,             ///<  FPA to write
+                psFits *fits,           ///<  FITS file to which to write
+                pmConfig *config,       ///<  Configuration
+                bool blank,             ///<  Write a blank PHU?
+                bool recurse            ///<  Recurse to lower levels?
+               );
+
+/// Write a cell mask to a FITS file
+///
+/// Generates CELL.TRIMSEC, CELL.BIASSEC and the HDU mask pixels if required.  A blank (i.e., image-less
+/// header) is written only if specifically requested.  Writes the concepts to the various locations, and then
+/// the HDU mask to the FITS file.  This function should be called at the beginning of the output cell loop
+/// with blank=true in order to produce the correct file structure.
+bool pmCellWriteMask(pmCell *cell,      ///<  Cell to write
+                     psFits *fits,      ///<  FITS file to which to write
+                     pmConfig *config,  ///<  Configuration
+                     bool blank         ///<  Write a blank PHU?
+                    );
+
+/// Write a chip mask to a FITS file
+///
+/// Generates CELL.TRIMSEC, CELL.BIASSEC and the HDU mask pixels if required.  A blank (i.e., image-less
+/// header) is written only if specifically requested.  Writes the concepts to the various locations, and then
+/// the HDU mask to the FITS file, optionally recursing to lower levels.  This function should be called at
+/// the beginning of the output chip loop with blank=true and recurse=false in order to produce the correct
+/// file structure.
+bool pmChipWriteMask(pmChip *chip,      ///<  Chip to write
+                     psFits *fits,      ///<  FITS file to which to write
+                     pmConfig *config,  ///<  Configuration
+                     bool blank,        ///<  Write a blank PHU?
+                     bool recurse       ///<  Recurse to lower levels?
+                    );
+
+/// Write an FPA mask to a FITS file
+///
+/// Generates CELL.TRIMSEC, CELL.BIASSEC and the HDU mask pixels if required.  A blank (i.e., image-less
+/// header) is written only if specifically requested.  Writes the concepts to the various locations, and then
+/// the HDU mask to the FITS file, optionally recursing to lower levels.  This function should be called at
+/// the beginning of the output FPA loop with blank=true and recurse=false in order to produce the correct
+/// file structure.
+bool pmFPAWriteMask(pmFPA *fpa,         ///<  FPA to write
+                    psFits *fits,       ///<  FITS file to which to write
+                    pmConfig *config,   ///<  Configuration
+                    bool blank,         ///<  Write a blank PHU?
+                    bool recurse        ///<  Recurse to lower levels?
+                   );
+
+/// Write a cell weight to a FITS file
+///
+/// Generates CELL.TRIMSEC, CELL.BIASSEC and the HDU weight pixels if required.  A blank (i.e., image-less
+/// header) is written only if specifically requested.  Writes the concepts to the various locations, and then
+/// the HDU weight to the FITS file.  This function should be called at the beginning of the output cell loop
+/// with blank=true in order to produce the correct file structure.
+bool pmCellWriteWeight(pmCell *cell,    ///<  Cell to write
+                       psFits *fits,    ///<  FITS file to which to write
+                       pmConfig *config, ///<  Configuration
+                       bool blank       ///<  Write a blank PHU?
+                      );
+
+/// Write a chip weight to a FITS file
+///
+/// Generates CELL.TRIMSEC, CELL.BIASSEC and the HDU weight pixels if required.  A blank (i.e., image-less
+/// header) is written only if specifically requested.  Writes the concepts to the various locations, and then
+/// the HDU weight to the FITS file, optionally recursing to lower levels.  This function should be called at
+/// the beginning of the output chip loop with blank=true and recurse=false in order to produce the correct
+/// file structure.
+bool pmChipWriteWeight(pmChip *chip,    ///<  Chip to write
+                       psFits *fits,    ///<  FITS file to which to write
+                       pmConfig *config, ///<  Configuration
+                       bool blank,      ///<  Write a blank PHU?
+                       bool recurse     ///<  Recurse to lower levels?
+                      );
+
+/// Write an FPA weight to a FITS file
+///
+/// Generates CELL.TRIMSEC, CELL.BIASSEC and the HDU weight pixels if required.  A blank (i.e., image-less
+/// header) is written only if specifically requested.  Writes the concepts to the various locations, and then
+/// the HDU weight to the FITS file, optionally recursing to lower levels.  This function should be called at
+/// the beginning of the output FPA loop with blank=true and recurse=false in order to produce the correct
+/// file structure.
+bool pmFPAWriteWeight(pmFPA *fpa,       ///<  FPA to write
+                      psFits *fits,     ///<  FITS file to which to write
+                      pmConfig *config, ///<  Configuration
+                      bool blank,       ///<  Write a blank PHU?
+                      bool recurse      ///<  Recurse to lower levels?
+                     );
+
+
+/// Write a FITS table from the cell's analysis metadata.
+///
+/// The FITS table (a psArray of psMetadatas) from the cell's analysis metadata (under "name") is written to
+/// the FITS file, at an extension specified by the name, chip name and cell name ("NAME_CHIP_CELL").  If a
+/// header is present in the analysis metadata ("name.HEADER"), then it is written also.
+int pmCellWriteTable(psFits *fits,      ///< FITS file to which to write
+                     const pmCell *cell, ///< Cell containing FITS table in the analysis metadata
+                     const char *name   ///< Name for the table data, and the extension name
+                    );
+
+int pmChipWriteTable(psFits *fits,      ///< FITS file to which to write
+                     const pmChip *chip, ///< Chip containing cells with tables to write
+                     const char *name   ///< Name for the table data, and the extension name
+                    );
+
+int pmFPAWriteTable(psFits *fits,       ///< FITS file to which to write
+                    const pmFPA *fpa,   ///< FPA containing cells with tables to write
+                    const char *name    ///< Name for the table data, and the extension name
+                   );
+
+// XXX better name, please
+bool pmFPAUpdateNames(pmFPA *fpa, pmChip *chip, pmCell *cell);
+
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPA_JPEG.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPA_JPEG.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPA_JPEG.c	(revision 22157)
@@ -0,0 +1,225 @@
+/** @file  pmFPA_JPEG.c
+ *
+ * This file contains functions to write JPEG images.
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.23 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-09-21 00:02:11 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/*****************************************************************************/
+/* INCLUDE FILES                                                             */
+/*****************************************************************************/
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>            /* for strn?casecmp */
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+#include "pmFPA_JPEG.h"
+
+
+bool pmFPAviewWriteJPEG(const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        pmFPAWriteJPEG (fpa, view, file, config);
+        return true;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        pmChipWriteJPEG (chip, view, file, config);
+        return true;
+    }
+
+    if (view->cell >= chip->cells->n) {
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        pmCellWriteJPEG (cell, view, file, config);
+        return true;
+    }
+
+    if (view->readout >= cell->readouts->n) {
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[view->readout];
+
+    pmReadoutWriteJPEG (readout, view, file, config);
+    return true;
+}
+
+// read in all chip-level JPEG files for this FPA
+bool pmFPAWriteJPEG (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+
+        pmChip *chip = fpa->chips->data[i];
+        pmChipWriteJPEG (chip, view, file, config);
+    }
+    return true;
+}
+
+// read in all cell-level JPEG files for this chip
+bool pmChipWriteJPEG (pmChip *chip, const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    for (int i = 0; i < chip->cells->n; i++) {
+
+        pmCell *cell = chip->cells->data[i];
+        pmCellWriteJPEG (cell, view, file, config);
+    }
+    return true;
+}
+
+// read in all readout-level JPEG files for this cell
+bool pmCellWriteJPEG (pmCell *cell, const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    for (int i = 0; i < cell->readouts->n; i++) {
+
+        pmReadout *readout = cell->readouts->data[i];
+        pmReadoutWriteJPEG (readout, view, file, config);
+    }
+    return true;
+}
+
+// write JPEG image for readout
+// note that this function will try as hard a possible to write out a jpeg.  it will not fail
+// just because the values are in a poor range.  it is more convenient to know you are getting
+// a jpeg which is weird than to fail to get the file at all.
+bool pmReadoutWriteJPEG (pmReadout *readout, const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    bool status;
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    if (file->type != PM_FPA_FILE_JPEG) {
+        psError(PS_ERR_UNKNOWN, true, "warning: type mismatch");
+        return false;
+    }
+
+    psTrace("psModules.camera", 3, "writing jpeg for readout %zd\n", (size_t) readout);
+
+    psMetadata *recipe = psMetadataLookupMetadata(&status, config->recipes, "PPIMAGE");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "missing recipe PPIMAGE in config data");
+        return false;
+    }
+
+    psMetadata *options = psMetadataLookupMetadata(&status, recipe, file->name);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "missing %s options in PPIMAGE recipe", file->name);
+        return false;
+    }
+
+    float min = 0;
+    float max = 0;                 // Minimum and maximum for stretch
+    float mean = 0;
+    float delta = 0;
+
+    // measure the image statistics for scaling
+    psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, 0);
+    psStats *stats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN);
+    stats->nSubsample = 10000;
+    if (!psImageBackground(stats, NULL, readout->image, NULL, 0, rng)) {
+        psStats *statsAlt = psStatsAlloc(PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV);
+        stats->nSubsample = 10000;
+        if (!psImageBackground(stats, NULL, readout->image, NULL, 0, rng)) {
+            psLogMsg("jpeg", PS_LOG_WARN, "Unable to measure statistics for image, writing blank jpeg\n");
+            mean = 0;
+            delta = 1;
+        } else {
+            mean = statsAlt->sampleMean;
+            delta = 2*statsAlt->sampleStdev;
+        }
+        psFree(statsAlt);
+    } else {
+        mean = stats->robustMedian;
+        delta = stats->robustUQ - stats->robustLQ;
+    }
+    psFree(rng);
+    psFree(stats);
+
+    char *colormapName = psMemIncrRefCounter (psMetadataLookupStr (NULL, options, "COLORMAP"));
+    if (!colormapName)
+        colormapName = psStringCopy ("-greyscale");
+    char *mode = psMemIncrRefCounter (psMetadataLookupStr (NULL, options, "SCALE.MODE"));
+    if (!mode)
+        mode = psStringCopy ("RANGE");
+    float fmin = psMetadataLookupF32 (&status, options, "SCALE.MIN");
+    if (!status)
+        fmin = -3.0;
+    float fmax = psMetadataLookupF32 (&status, options, "SCALE.MAX");
+    if (!status)
+        fmax = +6.0;
+
+    if (!strcasecmp (mode, "RANGE")) {
+        min = mean + fmin*delta;
+        max = mean + fmax*delta;
+    }
+    if (!strcasecmp (mode, "FRACTION")) {
+        min = fmin*mean;
+        max = fmax*mean;
+    }
+    if (!strcasecmp (mode, "VALUE")) {
+        min = fmin;
+        max = fmax;
+    }
+
+    if (!isfinite(min) || !isfinite(max)) {
+        psLogMsg("jpeg", PS_LOG_WARN, "The stretch parameters are not both finite, writing blank jpeg\n");
+        min = 0;
+        max = 1;
+    }
+
+    psImageJpegColormap *map = psImageJpegColormapSet (NULL, colormapName);
+    if (!map) {
+        map = psImageJpegColormapSet (NULL, "-greyscale");
+    }
+
+    psImageJpeg (map, readout->image, file->filename, min, max);
+
+    psFree(map);
+    psFree(colormapName);
+    psFree(mode);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPA_JPEG.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPA_JPEG.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPA_JPEG.h	(revision 22157)
@@ -0,0 +1,24 @@
+/* @file  pmFPAview.h
+ * @brief Tools to manipulate the FPA structure elements.
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-04-14 03:22:47 $
+ * Copyright 2004-2005 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_JPEG_H
+#define PM_FPA_JPEG_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+bool pmFPAviewWriteJPEG (const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmFPAWriteJPEG (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmChipWriteJPEG (pmChip *chip, const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmCellWriteJPEG (pmCell *cell, const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmReadoutWriteJPEG (pmReadout *readout, const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+
+/// @}
+# endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPA_MANAPLOT.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPA_MANAPLOT.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPA_MANAPLOT.c	(revision 22157)
@@ -0,0 +1,176 @@
+/** @file  pmFPA_MANAPLOT.c
+ *
+ * This file contains functions to write MANAPLOT images.
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-06-10 20:58:28 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/*****************************************************************************/
+/* INCLUDE FILES                                                             */
+/*****************************************************************************/
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAfile.h"
+#include "pmFPAview.h"
+#include "pmFPA_MANAPLOT.h"
+
+
+bool pmFPAviewWriteMANAPLOT(const pmFPAview *view, pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        pmFPAWriteMANAPLOT (fpa, view, file, config);
+        return true;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        pmChipWriteMANAPLOT (chip, view, file, config);
+        return true;
+    }
+
+    if (view->cell >= chip->cells->n) {
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        pmCellWriteMANAPLOT (cell, view, file, config);
+        return true;
+    }
+
+    if (view->readout >= cell->readouts->n) {
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[view->readout];
+
+    pmReadoutWriteMANAPLOT (readout, view, file, config);
+    return true;
+}
+
+// read in all chip-level MANAPLOT files for this FPA
+bool pmFPAWriteMANAPLOT (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+
+        pmChip *chip = fpa->chips->data[i];
+        pmChipWriteMANAPLOT (chip, view, file, config);
+    }
+    return true;
+}
+
+// read in all cell-level MANAPLOT files for this chip
+bool pmChipWriteMANAPLOT (pmChip *chip, const pmFPAview *view, pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    for (int i = 0; i < chip->cells->n; i++) {
+
+        pmCell *cell = chip->cells->data[i];
+        pmCellWriteMANAPLOT (cell, view, file, config);
+    }
+    return true;
+}
+
+// read in all readout-level MANAPLOT files for this cell
+bool pmCellWriteMANAPLOT (pmCell *cell, const pmFPAview *view, pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    for (int i = 0; i < cell->readouts->n; i++) {
+
+        pmReadout *readout = cell->readouts->data[i];
+        pmReadoutWriteMANAPLOT (readout, view, file, config);
+    }
+    return true;
+}
+
+// read in all readout-level Objects files for this cell
+bool pmReadoutWriteMANAPLOT (pmReadout *readout, const pmFPAview *view, pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    if (file->type != PM_FPA_FILE_MANAPLOT) {
+        psError(PS_ERR_UNKNOWN, true, "warning: type mismatch");
+        return false;
+    }
+
+    // XXX does not have to be a mana script...
+    // this function will call a mana script of the form:
+    // scriptname (input) (output)
+    // scriptname : extname
+    // input : filextra
+    // output : filerule
+
+    bool create = file->mode == PM_FPA_MODE_WRITE ? true : false;
+
+    psString tmpName;
+    tmpName = pmFPAfileNameFromRule (file->filextra, file, view);
+    psString input = pmConfigConvertFilename (tmpName, config, create, false);
+    psFree (tmpName);
+
+    tmpName = pmFPAfileNameFromRule (file->filerule, file, view);
+    psString output = pmConfigConvertFilename (tmpName, config, create, false);
+    psFree (tmpName);
+
+    tmpName = pmFPAfileNameFromRule (file->extname, file, view);
+    psString script = pmConfigConvertFilename (tmpName, config, create, false);
+    psFree (tmpName);
+
+    psString line = NULL;
+    psStringAppend (&line, "%s %s %s", script, input, output);
+
+    // capture the stdout and stderr?
+    // XXX use psPipe instead?
+    int status = system (line);
+    if (status == -1) {
+        psError(PS_ERR_UNKNOWN, true, "fork failure: %s", script);
+        return false;
+    }
+    if (WEXITSTATUS(status) != 0) {
+        psError(PS_ERR_UNKNOWN, true, "error running: %s", script);
+        return false;
+    }
+
+    psFree(line);
+    psFree(input);
+    psFree(output);
+    psFree(script);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPA_MANAPLOT.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPA_MANAPLOT.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPA_MANAPLOT.h	(revision 22157)
@@ -0,0 +1,24 @@
+/* @file  pmFPAview.h
+ * @brief Tools to manipulate the FPA structure elements.
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-01-24 02:54:14 $
+ * Copyright 2004-2005 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_MANAPLOT_H
+#define PM_FPA_MANAPLOT_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+bool pmFPAviewWriteMANAPLOT (const pmFPAview *view, pmFPAfile *file, pmConfig *config);
+bool pmFPAWriteMANAPLOT (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file, pmConfig *config);
+bool pmChipWriteMANAPLOT (pmChip *chip, const pmFPAview *view, pmFPAfile *file, pmConfig *config);
+bool pmCellWriteMANAPLOT (pmCell *cell, const pmFPAview *view, pmFPAfile *file, pmConfig *config);
+bool pmReadoutWriteMANAPLOT (pmReadout *readout, const pmFPAview *view, pmFPAfile *file, pmConfig *config);
+
+/// @}
+# endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAfile.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAfile.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAfile.c	(revision 22157)
@@ -0,0 +1,565 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>            /* for strn?casecmp */
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+#include "pmFPACopy.h"
+#include "pmConcepts.h"
+
+static void pmFPAfileFree(pmFPAfile *file)
+{
+    if (!file) {
+	return;
+    }
+
+    psTrace ("pmFPAfileFree", 5, "freeing %s\n", file->name);
+    psFree (file->fpa);
+    psFree (file->src);
+    psFree (file->readout);
+    psFree (file->names);
+
+    psFree (file->camera);
+    psFree (file->cameraName);
+    psFree (file->format);
+    psFree (file->formatName);
+    psFree (file->name);
+
+    if (file->fits != NULL) {
+	psFitsClose (file->fits);
+    }
+    psFree(file->compression);
+    psFree(file->options);
+
+    psFree (file->filerule);
+
+    psFree (file->filesrc);
+    psFree (file->detrend);
+
+    psFree (file->filename);
+    psFree (file->extname);
+
+    return;
+}
+
+pmFPAfile *pmFPAfileAlloc()
+{
+    pmFPAfile *file = psAlloc(sizeof(pmFPAfile));
+    psMemSetDeallocator(file, (psFreeFunc) pmFPAfileFree);
+
+    file->wrote_phu = false;
+    file->readout = NULL;
+    file->header = NULL;
+
+    file->fileLevel = PM_FPA_LEVEL_NONE;
+    file->dataLevel = PM_FPA_LEVEL_NONE;
+    file->freeLevel = PM_FPA_LEVEL_NONE;
+    file->mosaicLevel = PM_FPA_LEVEL_NONE;
+
+    file->type = PM_FPA_FILE_NONE;
+    file->mode = PM_FPA_MODE_NONE;
+    file->state = PM_FPA_STATE_CLOSED;
+
+    file->fpa = NULL;
+    file->fits = NULL;
+    file->compression = NULL;
+    file->options = NULL;
+    file->names = psMetadataAlloc();
+
+    file->camera = NULL;
+    file->cameraName = NULL;
+    file->format = NULL;
+    file->formatName = NULL;
+    file->name = NULL;
+
+    file->filerule = NULL;
+
+    file->filename = NULL;
+    file->extname  = NULL;
+
+    file->filesrc = NULL;
+    file->detrend = NULL;
+
+    file->xBin = 1;
+    file->yBin = 1;
+    file->src = NULL;
+
+    file->save = false;
+
+    return file;
+}
+
+// select the readout from the named pmFPAfile; if the named file does not exist,
+pmReadout *pmFPAfileThisReadout (psMetadata *files, const pmFPAview *view, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(files, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(name, false);
+    PS_ASSERT_INT_POSITIVE(strlen(name), false);
+
+    bool status;
+
+    pmFPAfile *file = psMetadataLookupPtr (&status, files, name);
+    if (file == NULL) {
+	return NULL;
+    }
+
+    // internal files have the readout as a separate element:
+    if (file->mode == PM_FPA_MODE_INTERNAL) {
+	return file->readout;
+    }
+
+    pmReadout *readout = pmFPAviewThisReadout (view, file->fpa);
+    return readout;
+}
+
+// select the cell from the named pmFPAfile; if the named file does not exist,
+pmCell *pmFPAfileThisCell (psMetadata *files, const pmFPAview *view, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(files, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(name, false);
+    PS_ASSERT_INT_POSITIVE(strlen(name), false);
+
+    bool status;
+
+    pmFPAfile *file = psMetadataLookupPtr (&status, files, name);
+    if (file == NULL) {
+	return NULL;
+    }
+
+    // internal files have the readout as a separate element:
+    if (file->mode == PM_FPA_MODE_INTERNAL) {
+	return NULL;
+    }
+
+    pmCell *cell = pmFPAviewThisCell(view, file->fpa);
+    return cell;
+}
+
+// select the readout from the named pmFPAfile; if the named file does not exist,
+pmChip *pmFPAfileThisChip (psMetadata *files, const pmFPAview *view, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(files, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(name, false);
+    PS_ASSERT_INT_POSITIVE(strlen(name), false);
+
+    bool status;
+
+    pmFPAfile *file = psMetadataLookupPtr (&status, files, name);
+    if (file == NULL) {
+	return NULL;
+    }
+
+    // internal files have the readout as a separate element:
+    if (file->mode == PM_FPA_MODE_INTERNAL) {
+	return NULL;
+    }
+
+    pmChip *chip = pmFPAviewThisChip (view, file->fpa);
+    return chip;
+}
+
+psString pmFPANameFromRule(const char *rule, const pmFPA *fpa, const pmFPAview *view)
+{
+    PS_ASSERT_STRING_NON_EMPTY(rule, NULL);
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    psString newName = NULL;            // New name, to be returned
+    newName = psStringCopy(rule);
+
+    if (strstr (newName, "{FPA.OBS}") != NULL) {
+	char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.OBS");
+	if (name != NULL) {
+	    psStringSubstitute(&newName, name, "{FPA.OBS}");
+	}
+    }
+    if (strstr (newName, "{FPA.NAME}") != NULL) {
+	char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.NAME");
+	if (name != NULL) {
+	    psStringSubstitute(&newName, name, "{FPA.NAME}");
+	}
+    }
+    if (strstr (newName, "{CHIP.NAME}") != NULL) {
+	pmChip *chip = pmFPAviewThisChip (view, fpa);
+	if (chip != NULL) {
+	    char *name = psMetadataLookupStr (NULL, chip->concepts, "CHIP.NAME");
+	    if (name != NULL) {
+		psStringSubstitute(&newName, name, "{CHIP.NAME}");
+	    }
+	}
+    }
+    if (strstr (newName, "{CHIP.ID}") != NULL) {
+	pmChip *chip = pmFPAviewThisChip (view, fpa);
+	if (chip != NULL) {
+	    char *name = psMetadataLookupStr (NULL, chip->concepts, "CHIP.ID");
+	    if (name != NULL) {
+		psStringSubstitute(&newName, name, "{CHIP.ID}");
+	    }
+	}
+    }
+    if (strstr (newName, "{CHIP.N}") != NULL) {
+	char *name = NULL;
+	if (view->chip < 0) {
+	    psStringAppend (&name, "XX");
+	} else {
+	    psStringAppend (&name, "%02d", view->chip);
+	}
+	psStringSubstitute(&newName, name, "{CHIP.N}");
+	psFree (name);
+    }
+    if (strstr (newName, "{CELL.NAME}") != NULL) {
+	pmCell *cell = pmFPAviewThisCell (view, fpa);
+	if (cell != NULL) {
+	    char *name = psMetadataLookupStr (NULL, cell->concepts, "CELL.NAME");
+	    if (name != NULL) {
+		psStringSubstitute(&newName, name, "{CELL.NAME}");
+	    }
+	}
+    }
+    if (strstr (newName, "{CELL.N}") != NULL) {
+	char *name = NULL;
+	if (view->cell < 0) {
+	    psStringAppend (&name, "XX");
+	} else {
+	    psStringAppend (&name, "%02d", view->cell);
+	}
+	psStringSubstitute(&newName, name, "{CELL.N}");
+    }
+    if (strstr (newName, "{EXTNAME}") != NULL) {
+	pmHDU *hdu = pmFPAviewThisHDU (view, fpa);
+	if (hdu->extname && *hdu->extname) {
+	    psStringSubstitute(&newName, hdu->extname, "{EXTNAME}");
+	}
+    }
+    if (strstr (newName, "{FILTER}") != NULL) {
+	if (fpa != NULL) {
+	    char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.FILTER");
+	    if (name && *name) {
+		psStringSubstitute(&newName, name, "{FILTER}");
+	    }
+	}
+    }
+    if (strstr (newName, "{FILTER.ID}") != NULL) {
+	if (fpa != NULL) {
+	    char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.FILTERID");
+	    if (name && *name) {
+		psStringSubstitute(&newName, name, "{FILTER.ID}");
+	    }
+	}
+    }
+    if (strstr (newName, "{CAMERA}") != NULL) {
+	if (fpa != NULL) {
+	    char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.INSTRUMENT");
+	    if (name && *name) {
+		psStringSubstitute(&newName, name, "{CAMERA}");
+	    }
+	}
+    }
+    if (strstr (newName, "{INSTRUMENT}") != NULL) {
+	if (fpa != NULL) {
+	    char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.INSTRUMENT");
+	    if (name && *name) {
+		psStringSubstitute(&newName, name, "{INSTRUMENT}");
+	    }
+	}
+    }
+    if (strstr (newName, "{DETECTOR}") != NULL) {
+	if (fpa != NULL) {
+	    char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.DETECTOR");
+	    if (name && *name) {
+		psStringSubstitute(&newName, name, "{DETECTOR}");
+	    }
+	}
+    }
+    if (strstr (newName, "{TELESCOPE}") != NULL) {
+	if (fpa != NULL) {
+	    char *name = psMetadataLookupStr (NULL, fpa->concepts, "FPA.TELESCOPE");
+	    if (name && *name) {
+		psStringSubstitute(&newName, name, "{TELESCOPE}");
+	    }
+	}
+    }
+    return newName;
+}
+
+// select the rule from the camera configuration, perform substitutions as needed
+psString pmFPAfileNameFromRule(const char *rule, const pmFPAfile *file, const pmFPAview *view)
+{
+    PS_ASSERT_PTR_NON_NULL(rule, NULL);
+    PS_ASSERT_INT_POSITIVE(strlen(rule), NULL);
+    PS_ASSERT_PTR_NON_NULL(file, NULL);
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+
+    psString newRule = NULL;            // Rule to pass on to pmFPANameFromRule
+    newRule = psStringCopy(rule);
+
+    if (strstr(newRule, "{OUTPUT}") != NULL) {
+	char *name = psMetadataLookupStr(NULL, file->names, "OUTPUT");
+	if (name) {
+	    psStringSubstitute(&newRule, name, "{OUTPUT}");
+	}
+    }
+
+    psString newName = pmFPANameFromRule(newRule, file->fpa, view); // New name, to be returned
+    psFree(newRule);
+
+    return newName;
+}
+
+// given an already-opened fits file, write the components corresponding
+// to the specified view
+bool pmFPAfileCopyView (pmFPA *out, pmFPA *in, const pmFPAview *view)
+{
+    PS_ASSERT_PTR_NON_NULL(out, false);
+    PS_ASSERT_PTR_NON_NULL(in, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    // pmFPAWrite takes care of all PHUs as needed
+    if (view->chip == -1) {
+	pmFPACopy (out, in);
+	return true;
+    }
+    if (view->chip >= in->chips->n) {
+	psError(PS_ERR_IO, true, "Requested chip == %d >= in->chips->n == %ld", view->chip, in->chips->n);
+	return false;
+    }
+    pmChip *inChip = in->chips->data[view->chip];
+    pmChip *outChip = out->chips->data[view->chip];
+
+    if (view->cell == -1) {
+	pmChipCopy (outChip, inChip);
+	return true;
+    }
+    if (view->cell >= inChip->cells->n) {
+	psError(PS_ERR_IO, true, "Requested cell == %d>= inChip->cells->n == %ld",
+		view->cell, inChip->cells->n);
+	return false;
+    }
+    pmCell *inCell = inChip->cells->data[view->cell];
+    pmCell *outCell = outChip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+	pmCellCopy (outCell, inCell);
+	return true;
+    }
+    psError(PS_ERR_UNKNOWN, true, "Returning false");
+    return false;
+
+    // XXX add readout / segment equivalents
+}
+
+// given an already-opened fits file, write the components corresponding
+// to the specified view
+bool pmFPAfileCopyStructureView (pmFPA *out, const pmFPA *in, int xBin, int yBin, const pmFPAview *view)
+{
+    bool status;
+    PS_ASSERT_PTR_NON_NULL(out, false);
+    PS_ASSERT_PTR_NON_NULL(in, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    // XXX this should be smarter (ie, only copy concepts from the current chips)
+    // but such a call is needed, so re-copy stuff rather than no copy
+    pmFPACopyConcepts (out, in);
+
+    // pmFPAWrite takes care of all PHUs as needed
+    if (view->chip == -1) {
+	status = pmFPACopyStructure (out, in, xBin, yBin);
+	return status;
+    }
+    if (view->chip >= in->chips->n) {
+	psError(PS_ERR_IO, true, "Requested chip == %d >= in->chips->n == %ld", view->chip, in->chips->n);
+	return false;
+    }
+    pmChip *inChip = in->chips->data[view->chip];
+    pmChip *outChip = out->chips->data[view->chip];
+
+    if (view->cell == -1) {
+	status = pmChipCopyStructure (outChip, inChip, xBin, yBin);
+	return status;
+    }
+    if (view->cell >= inChip->cells->n) {
+	psError(PS_ERR_IO, true, "Requested cell == %d>= inChip->cells->n == %ld",
+		view->cell, inChip->cells->n);
+	return false;
+    }
+    pmCell *inCell = inChip->cells->data[view->cell];
+    pmCell *outCell = outChip->cells->data[view->cell];
+
+    status = pmCellCopyStructure (outCell, inCell, xBin, yBin);
+    return status;
+}
+
+pmFPAfileType pmFPAfileTypeFromString(const char *type)
+{
+    PS_ASSERT_STRING_NON_EMPTY(type, PM_FPA_FILE_NONE);
+
+    if (!strcasecmp (type, "SX"))     {
+	return PM_FPA_FILE_SX;
+    }
+    if (!strcasecmp (type, "OBJ"))     {
+	return PM_FPA_FILE_OBJ;
+    }
+    if (!strcasecmp (type, "CMP"))     {
+	return PM_FPA_FILE_CMP;
+    }
+    if (!strcasecmp (type, "CMF"))     {
+	return PM_FPA_FILE_CMF;
+    }
+    if (!strcasecmp (type, "RAW"))     {
+	return PM_FPA_FILE_RAW;
+    }
+    if (!strcasecmp (type, "IMAGE"))     {
+	return PM_FPA_FILE_IMAGE;
+    }
+    if (!strcasecmp (type, "PSF"))     {
+	return PM_FPA_FILE_PSF;
+    }
+    if (!strcasecmp (type, "JPEG"))     {
+	return PM_FPA_FILE_JPEG;
+    }
+    if (!strcasecmp (type, "KAPA"))     {
+	return PM_FPA_FILE_KAPA;
+    }
+    if (!strcasecmp (type, "MASK"))     {
+	return PM_FPA_FILE_MASK;
+    }
+    if (!strcasecmp (type, "WEIGHT"))     {
+	return PM_FPA_FILE_WEIGHT;
+    }
+    if (!strcasecmp (type, "FRINGE")) {
+	return PM_FPA_FILE_FRINGE;
+    }
+    if (!strcasecmp (type, "DARK"))     {
+	return PM_FPA_FILE_DARK;
+    }
+    if (!strcasecmp (type, "HEADER"))     {
+	return PM_FPA_FILE_HEADER;
+    }
+    // deprecate this?
+    if (!strcasecmp (type, "ASTROM"))     {
+	return PM_FPA_FILE_ASTROM_MODEL;
+    }
+    if (!strcasecmp (type, "ASTROM.MODEL"))     {
+	return PM_FPA_FILE_ASTROM_MODEL;
+    }
+    if (!strcasecmp (type, "ASTROM.REFSTARS"))     {
+	return PM_FPA_FILE_ASTROM_REFSTARS;
+    }
+
+    return PM_FPA_FILE_NONE;
+}
+
+const char *pmFPAfileStringFromType(pmFPAfileType type)
+{
+    switch (type) {
+      case PM_FPA_FILE_SX:
+	return ("SX");
+      case PM_FPA_FILE_OBJ:
+	return ("OBJ");
+      case PM_FPA_FILE_CMP:
+	return ("CMP");
+      case PM_FPA_FILE_CMF:
+	return ("CMF");
+      case PM_FPA_FILE_RAW:
+	return ("RAW");
+      case PM_FPA_FILE_IMAGE:
+	return ("IMAGE");
+      case PM_FPA_FILE_PSF:
+	return ("PSF");
+      case PM_FPA_FILE_JPEG:
+	return ("JPEG");
+      case PM_FPA_FILE_KAPA:
+	return ("KAPA");
+      case PM_FPA_FILE_MASK:
+	return ("MASK");
+      case PM_FPA_FILE_WEIGHT:
+	return ("WEIGHT");
+      case PM_FPA_FILE_FRINGE:
+	return ("FRINGE");
+      case PM_FPA_FILE_DARK:
+	return("DARK");
+      case PM_FPA_FILE_HEADER:
+	return ("HEADER");
+      case PM_FPA_FILE_ASTROM_MODEL:
+	return ("ASTROM.MODEL");
+      case PM_FPA_FILE_ASTROM_REFSTARS:
+	return ("ASTROM.REFSTARS");
+      default:
+	return ("NONE");
+    }
+    return ("NONE");
+}
+
+
+psArray *pmFPAfileSelect(psMetadata *files, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(files, NULL);
+
+    psList *list = psListAlloc(NULL);   // List of files selected
+
+    psString regex = NULL;              // Regular expression
+    if (name) {
+        if (!psMetadataLookup(files, name)) {
+            psFree (list);
+            return NULL;
+        }
+        psStringAppend(&regex, "^%s$", name);
+    }
+    psMetadataIterator *iter = psMetadataIteratorAlloc(files, PS_LIST_HEAD, regex); // Iterator
+    psFree(regex);
+    psMetadataItem *item;               // Item from iteration
+    while ((item = psMetadataGetAndIncrement(iter))) {
+        pmFPAfile *file = item->data.V; // File of iterest
+        psListAdd(list, PS_LIST_TAIL, file);
+    }
+    psFree(iter);
+
+    psArray *array = psListToArray(list); // Array generated from list
+    psFree(list);
+
+    return array;
+}
+
+pmFPAfile *pmFPAfileSelectSingle(psMetadata *files, const char *name, int num)
+{
+    PS_ASSERT_PTR_NON_NULL(files, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(num, NULL);
+
+    psString regex = NULL;              // Regular expression
+    if (name) {
+        if (!psMetadataLookup(files, name)) {
+            // No files
+            return NULL;
+        }
+        psStringAppend(&regex, "^%s$", name);
+    }
+
+    psMetadataIterator *iter = psMetadataIteratorAlloc(files, PS_LIST_HEAD, regex); // Iterator
+    psFree(regex);
+    psMetadataItem *item;               // Item from iteration
+    int i = 0;                          // Counter
+    while ((item = psMetadataGetAndIncrement(iter))) {
+        if (i++ == num) {
+            psFree(iter);
+            return item->data.V;
+        }
+    }
+    psFree(iter);
+
+    psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to find instance %d of file %s", num, name);
+    return NULL;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAfile.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAfile.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAfile.h	(revision 22157)
@@ -0,0 +1,150 @@
+/* @file  pmFPAview.h
+ * @brief Tools to manipulate the FPA structure elements.
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.30 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-03-18 18:38:14 $
+ * Copyright 2004-2005 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_FILE_H
+#define PM_FPA_FILE_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+typedef enum {
+    PM_FPA_BEFORE,
+    PM_FPA_AFTER,
+} pmFPAfilePlace;
+
+typedef enum {
+    PM_FPA_FILE_NONE,
+    PM_FPA_FILE_SX,
+    PM_FPA_FILE_OBJ,
+    PM_FPA_FILE_CMP,
+    PM_FPA_FILE_CMF,
+    PM_FPA_FILE_RAW,
+    PM_FPA_FILE_IMAGE,
+    PM_FPA_FILE_MASK,
+    PM_FPA_FILE_WEIGHT,
+    PM_FPA_FILE_FRINGE,
+    PM_FPA_FILE_DARK,
+    PM_FPA_FILE_PSF,
+    PM_FPA_FILE_JPEG,
+    PM_FPA_FILE_KAPA,
+    PM_FPA_FILE_HEADER,
+    PM_FPA_FILE_ASTROM_MODEL,
+    PM_FPA_FILE_ASTROM_REFSTARS,
+} pmFPAfileType;
+
+typedef enum {
+    PM_FPA_MODE_NONE,
+    PM_FPA_MODE_READ,
+    PM_FPA_MODE_WRITE,
+    PM_FPA_MODE_INTERNAL,
+    PM_FPA_MODE_REFERENCE,
+} pmFPAfileMode;
+
+typedef enum {
+    PM_FPA_STATE_OPEN     = 0x01,
+    PM_FPA_STATE_CLOSED   = 0x02,
+    PM_FPA_STATE_INACTIVE = 0x04,
+} pmFPAfileState;
+
+typedef struct {
+    pmFPAfileMode mode;                 // is this file read, written, or only used internally?
+    pmFPAfileType type;                 // what type of data is read from / written to disk?
+    pmFPAfileState state;               // have we opened the file, etc?
+
+    pmFPALevel fileLevel;               // what level in the FPA hierarchy represents a unique file?
+    pmFPALevel dataLevel;               // at what level do we read/write the data segment? (request by user)
+    pmFPALevel freeLevel;               // at what level do we free the data segment? (set by program)
+    pmFPALevel mosaicLevel;             // at what level is the mosaic?
+
+    pmFPA *fpa;                         // for I/O files, we carry a pointer to the complete fpa
+    psFits *fits;                       // for I/O files of fits type (IMAGE, CMP, CMF) we carry a file handle
+    psFitsCompression *compression;     // Compression for FITS images
+    psFitsOptions *options;             // FITS I/O options
+
+    bool wrote_phu;                     // have we written a PHU for this file?
+    psMetadata *header;                 // pointer (view) to the current hdu header
+
+    pmReadout *readout;                 // for internal files, we only carry a single readout
+
+    psMetadata *names;                  // filenames supplied by the cmdline or detdb are saved here
+
+    char *filerule;                     // rule for constructing a filename when needed
+    char *filesrc;                      // rule to find file in pmFPAfile->names list
+
+    char *name;                         // the name of the rule (useful for debugging / tracing)
+    char *filename;                     // the current name of an active file
+    char *extname;                      // the current name of an active file extension
+
+    pmDetrendSelectResults *detrend;    // Detrend information, from pmDetrendSelect
+
+    bool save;                          // Should the file be saved?
+
+    // the following elements are used for WRITE-mode IMAGE-type pmFPAfiles to inform
+    // the creation of a new image based on an existing image
+    pmFPA *src;                         // if an output FPA, inherit from this FPA
+    int xBin;                           // desired binning in x direction
+    int yBin;                           // desired binning in y direction
+
+    psMetadata *camera;                 // Camera configuration
+    psString cameraName;                // Name of the camera
+    psMetadata *format;                 // Camera format
+    psString formatName;                // name of the camera format
+} pmFPAfile;
+
+// allocate an empty pmFPAfile structure
+pmFPAfile *pmFPAfileAlloc ();
+
+// select the readout from the named pmFPAfile; if the named file does not exist,
+pmReadout *pmFPAfileThisReadout (psMetadata *files, const pmFPAview *view, const char *name);
+
+// select the cell from the named pmFPAfile; if the named file does not exist,
+pmCell *pmFPAfileThisCell (psMetadata *files, const pmFPAview *view, const char *name);
+
+// select the chip from the named pmFPAfile; if the named file does not exist,
+pmChip *pmFPAfileThisChip (psMetadata *files, const pmFPAview *view, const char *name);
+
+// add the specified filename info (value) to the files of the given mode using the given reference name
+bool pmFPAfileAddFileNames (psMetadata *files, char *name, char *value, int mode);
+
+// convert the rule to a name based on the current view
+psString pmFPANameFromRule(const char *rule, const pmFPA *fpa, const pmFPAview *view);
+
+// convert the rule to a name based on the current view
+psString pmFPAfileNameFromRule(const char *rule, const pmFPAfile *file, const pmFPAview *view);
+
+bool pmFPAfileCopyView (pmFPA *out, pmFPA *in, const pmFPAview *view);
+
+bool pmFPAfileCopyStructureView (pmFPA *out, const pmFPA *in, int xBin, int yBin, const pmFPAview *view);
+
+// Return the file type enum from a string
+pmFPAfileType pmFPAfileTypeFromString(const char *type);
+
+// Return the file type as a string
+const char *pmFPAfileStringFromType(pmFPAfileType type);
+
+/// Select files with the same name from the list of files
+///
+/// Returns all files if name is NULL.
+psArray *pmFPAfileSelect(psMetadata *files, ///< All files
+                         const char *name ///< Name of file(s) to return, or NULL for all
+    );
+
+/// Select a specific instance of a file from the list of files
+///
+/// Returns the num-th instance of all files if name is NULL.
+pmFPAfile *pmFPAfileSelectSingle(psMetadata *files, ///< All files
+                                 const char *name, ///< Name of file
+                                 int num ///< Instance number of specific instance
+    );
+
+
+
+/// @}
+# endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAfileDefine.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAfileDefine.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAfileDefine.c	(revision 22157)
@@ -0,0 +1,1428 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>            /* for strn?casecmp */
+#include <pslib.h>
+
+#include "pmErrorCodes.h"
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+#include "pmFPAfile.h"
+#include "pmFPAConstruct.h"
+
+#include "pmConcepts.h"
+
+# define FPA_TEST_ASSERT(A){ \
+	assert(A->format == NULL); \
+	assert(A->formatName == NULL); \
+	assert(A->filerule == NULL); \
+	assert(A->filesrc == NULL); }
+
+// Parse an option from a metadata, returning the appropriate integer value
+static int parseOptionInt(const psMetadata *md, // Metadata containing the option
+                          const char *name, // Option name
+                          const char *source, // Description of source, for warning messages
+                          int defaultValue // Default value
+                          )
+{
+    psMetadataItem *item = psMetadataLookup(md, name); // Item with the value of interest
+    if (!item) {
+        psWarning("Unable to find value for %s in %s --- set to %d.", name, source, defaultValue);
+        return defaultValue;
+    }
+    int value = psMetadataItemParseS32(item); // Value of interst
+    return value;
+}
+
+// Parse an option from a metadata, returning the appropriate float value
+static float parseOptionFloat(const psMetadata *md, // Metadata containing the option
+                              const char *name, // Option name
+                              const char *source // Description of source, for warning messages
+                              )
+{
+    psMetadataItem *item = psMetadataLookup(md, name); // Item with the value of interest
+    if (!item) {
+        psWarning("Unable to find value for %s in %s", name, source);
+        return NAN;
+    }
+    int value = psMetadataItemParseF32(item); // Value of interst
+    return value;
+}
+
+// Parse an option from a metadata, returning the appropriate double value
+static double parseOptionDouble(const psMetadata *md, // Metadata containing the option
+                                const char *name, // Option name
+                                const char *source // Description of source, for warning messages
+                                )
+{
+    psMetadataItem *item = psMetadataLookup(md, name); // Item with the value of interest
+    if (!item) {
+        psWarning("Unable to find value for %s in %s", name, source);
+        return NAN;
+    }
+    int value = psMetadataItemParseF64(item); // Value of interst
+    return value;
+}
+
+
+// define an input-type pmFPAfile, bind to the optional fpa if supplied
+pmFPAfile *pmFPAfileDefineInput(const pmConfig *config, pmFPA *fpa, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->files, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->camera, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(name, NULL);
+
+    bool status;
+    char *type;
+
+    const psMetadata *camera = (fpa ? fpa->camera : config->camera); // Camera configuration for this file
+    psMetadata *data = pmConfigFileRule(config, camera, name); // File rule
+    if (!data) {
+        psError(PS_ERR_IO, true, "Can't find file rule %s!", name);
+        return NULL;
+    }
+
+    pmFPAfile *file = pmFPAfileAlloc();
+
+    // save the name of this pmFPAfile
+    file->name = psStringCopy(name);
+
+    file->filerule = psMemIncrRefCounter(psMetadataLookupStr (&status, data, "FILENAME.RULE"));
+
+    type = psMetadataLookupStr(&status, data, "FILE.TYPE");
+    file->type = pmFPAfileTypeFromString(type);
+    if (file->type == PM_FPA_FILE_NONE) {
+        psError(PS_ERR_IO, true, "FILE.TYPE is not defined for %s\n", name);
+        psFree(file);
+        return NULL;
+    }
+
+    file->mode = PM_FPA_MODE_READ;
+    file->fileLevel = PM_FPA_LEVEL_NONE; // the fileLevel depends on the input data
+
+    file->dataLevel = pmFPALevelFromName(psMetadataLookupStr (&status, data, "DATA.LEVEL"));
+    if (file->dataLevel == PM_FPA_LEVEL_NONE) {
+        psError(PS_ERR_IO, true, "DATA.LEVEL is not set for %s\n", name);
+        psFree(file);
+        return NULL;
+    }
+    // default is to free the data after use (after written out)
+    // this can be overridden for pmFPAfiles used as carriers as well
+    file->freeLevel = file->dataLevel;
+
+    if (fpa) {
+        file->fpa = psMemIncrRefCounter(fpa);
+        file->camera = psMemIncrRefCounter((psMetadata *)fpa->camera);
+        file->cameraName = psMemIncrRefCounter(config->cameraName); // XXX Is this the correct thing to do?
+    } else {
+        file->camera = psMemIncrRefCounter(config->camera);
+        file->cameraName = psMemIncrRefCounter(config->cameraName);
+    }
+
+    // XXX ppImage and similar require the added file to be unique
+    // XXX ppFocus wants to override the selection with the new selection
+    // XXX require programs like ppFocus to remove existing files by hand
+    if (!psMetadataAddPtr(config->files, PS_LIST_TAIL, name,
+                          PS_DATA_UNKNOWN | PS_META_DUPLICATE_OK, "", file)) {
+        psError(PS_ERR_IO, false, "could not add %s to config files", name);
+        return NULL;
+    }
+    psFree(file);
+    return file;
+}
+
+// Define an output pmFPAfile
+pmFPAfile *pmFPAfileDefineOutputForFormat(const pmConfig *config, // Configuration
+                                          pmFPA *fpa, // Optional FPA to bind
+                                          const char *name, // Name of file rule
+                                          psString cameraName, // Name of camera configuration to use
+                                          psString formatName // Name of camera format to use
+    )
+{
+    bool status;
+
+    // Use the camera we were told to, the camera of the provided FPA, or default to the default camera
+    psMetadata *camera;                 // Camera configuration
+    if (!cameraName || strlen(cameraName) == 0) {
+        if (fpa && fpa->camera) {
+            camera = (psMetadata*)fpa->camera; // Casting away const, so I can put it in the file
+        } else {
+            camera = config->camera;
+            cameraName = config->cameraName;
+        }
+    } else {
+        bool mdok;                      // Status of MD lookup
+        psMetadata *cameras = psMetadataLookupMetadata(&mdok, config->complete, "CAMERAS"); // Known cameras
+        if (!mdok || !cameras) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find CAMERAS in the system configuration.\n");
+            return NULL;
+        }
+        camera = psMetadataLookupMetadata(&mdok, cameras, cameraName); // Camera configuration of interest
+        if (!mdok || !camera) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find automatically generated "
+                    "camera configuration %s in system configuration.\n", cameraName);
+            return NULL;
+        }
+
+        if (fpa && fpa->camera && fpa->camera != camera) {
+            psAbort("Camera of bound FPA is not the requested camera --- there is an inconsistency!");
+        }
+    }
+
+    psMetadata *filerule = pmConfigFileRule(config, camera, name); // File rule
+    if (!filerule) {
+        psError(PS_ERR_IO, true, "Can't find file rule %s!", name);
+        return NULL;
+    }
+
+    pmFPAfile *file = pmFPAfileAlloc();
+
+    // save the name of this pmFPAfile
+    file->name = psStringCopy(name);
+
+    // this is the filename rule
+    file->filerule = psMemIncrRefCounter(psMetadataLookupStr(&status, filerule, "FILENAME.RULE"));
+
+    const char *type = psMetadataLookupStr(&status, filerule, "FILE.TYPE");
+    file->type = pmFPAfileTypeFromString(type);
+    if (file->type == PM_FPA_FILE_NONE) {
+        psError(PS_ERR_IO, true, "FILE.TYPE is not defined for %s\n", name);
+        psFree(file);
+        return NULL;
+    }
+
+    file->mode = PM_FPA_MODE_WRITE;
+    file->save = false;
+
+    file->camera = psMemIncrRefCounter(camera);
+    file->cameraName = psMemIncrRefCounter(cameraName);
+
+    // XXX this seems a bit of a hack: use the cameraName to determine the mosaic level...
+    # if (0)
+    if (cameraName) {
+        if (!strcmp(cameraName + strlen(cameraName) - 5, "-CHIP")) {
+            file->mosaicLevel = PM_FPA_LEVEL_CHIP;
+        }
+        if (!strcmp(cameraName + strlen(cameraName) - 5, "-FPA")) {
+            file->mosaicLevel = PM_FPA_LEVEL_FPA;
+        }
+    }
+    # endif
+
+    // Use the format we were told to, the format specified in the file rule, or default to the default format
+    if (!formatName || strlen(formatName) == 0) {
+        // select the format list from the selected camera
+        formatName = psMetadataLookupStr(&status, filerule, "FILE.FORMAT");
+        if (!formatName || strcmp(formatName, "NONE") == 0) {
+            // Try to get by with the default
+            formatName = config->formatName;
+        }
+    }
+    psMetadata *formats = psMetadataLookupMetadata(&status, file->camera, "FORMATS"); // List of formats
+    psMetadata *format = psMetadataLookupMetadata(&status, formats, formatName); // Camera format to use
+    if (!format) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to find format %s for file %s.\n",
+                formatName, file->name);
+        psFree(file);
+        return NULL;
+    }
+    file->format = psMemIncrRefCounter(format);
+    file->formatName = psStringCopy(formatName);
+
+    if (fpa) {
+        file->fpa = psMemIncrRefCounter(fpa);
+    } else {
+        file->fpa = pmFPAConstruct(file->camera, file->cameraName);
+    }
+
+    // Get FITS output scheme
+    const char *fitsType = psMetadataLookupStr(&status, filerule, "FITS.TYPE"); // Name of FITS scheme to use
+    if (fitsType && strcasecmp(fitsType, "NONE") != 0) {
+
+        // load the FITSTYPE scheme for this file
+        psMetadata *scheme = pmConfigFitsType(config, camera, fitsType); // File rule
+        if (!scheme) {
+            // XXX change to a config error?
+            psWarning("Unable to find %s in FITS in camera configuration --- will use defaults.", fitsType);
+            goto FITS_OPTIONS_DONE;
+        }
+
+        psString source = NULL;     // Source of options
+        psStringAppend(&source, "%s in FITS in camera configuration", fitsType);
+
+        psFitsOptions *options = file->options = psFitsOptionsAlloc(); // FITS I/O options
+
+        // Custom floating-point
+        bool mdok;                      // Status of MD lookup
+        const char *floatName = psMetadataLookupStr(&mdok, scheme, "FLOAT"); // Name of custom float
+        if (mdok && floatName) {
+            psString fullName = NULL;   // Full name of custom floating-point
+            psStringAppend(&fullName, "FLOAT_%s", floatName);
+            options->floatType = psFitsFloatTypeFromString(fullName);
+            psFree(fullName);
+        }
+
+        options->bitpix = parseOptionInt(scheme, "BITPIX", source, 0); // Bits per pixel
+
+        // Scaling options
+        const char *scalingString = psMetadataLookupStr(&mdok, scheme, "SCALING"); // Scaling name
+        if (scalingString) {
+            options->scaling = psFitsScalingFromString(scalingString); // Scaling method
+
+            switch (options->scaling) {
+              case PS_FITS_SCALE_NONE:
+              case PS_FITS_SCALE_RANGE:
+                // No options required
+                break;
+              case PS_FITS_SCALE_STDEV_POSITIVE:
+              case PS_FITS_SCALE_STDEV_NEGATIVE:
+                options->stdevNum = parseOptionFloat(scheme, "STDEV.NUM", source); // Padding to edge
+                if (!isfinite(options->stdevNum)) {
+                    psError(PS_ERR_BAD_PARAMETER_VALUE, false, "Bad value for STDEV.NUM for %s", source);
+                    psFree(source);
+                    psFree(file);
+                    return NULL;
+                }
+                // Flow through
+              case PS_FITS_SCALE_STDEV_BOTH:
+                options->stdevBits = parseOptionInt(scheme, "STDEV.BITS", source, 0); // Bits for stdev
+                if (options->stdevBits <= 0) {
+                    psError(PS_ERR_BAD_PARAMETER_VALUE, false, "Bad value for STDEV.BITS (%d) for %s",
+                            options->stdevBits, source);
+                    psFree(source);
+                    psFree(file);
+                    return NULL;
+                }
+                break;
+              case PS_FITS_SCALE_MANUAL:
+                options->bscale = parseOptionDouble(scheme, "BSCALE", source); // Scaling
+                options->bzero = parseOptionDouble(scheme, "BZERO", source); // Zero point
+                break;
+              default:
+                psAbort("Should never get here.");
+            }
+        }
+
+        // Compression options
+        const char *compressString = psMetadataLookupStr(&mdok, scheme, "COMPRESSION"); // Compression type
+        if (mdok && compressString) {
+            psFitsCompressionType type = psFitsCompressionTypeFromString(compressString); // Compression
+            psVector *tile = psVectorAlloc(3, PS_TYPE_S32); // Tile sizes
+            tile->data.S32[0] = parseOptionInt(scheme, "TILE.X", source, 0); // Tiling in x
+            tile->data.S32[1] = parseOptionInt(scheme, "TILE.Y", source, 1); // Tiling in y
+            tile->data.S32[2] = parseOptionInt(scheme, "TILE.Z", source, 1); // Tiling in z
+            int noise = parseOptionInt(scheme, "NOISE", source, 16); // Noise bits
+            int hscale = 0, hsmooth = 0;// Scaling and smoothing for HCOMPRESS
+            if (type == PS_FITS_COMPRESS_HCOMPRESS) {
+                hscale = parseOptionInt(scheme, "HSCALE", source, 0);
+                hsmooth = parseOptionInt(scheme, "HSMOOTH", source, 0);
+            }
+
+            file->compression = psFitsCompressionAlloc(type, tile, noise, hscale, hsmooth);
+            psFree(tile);
+        }
+
+        psFree(source);
+    }
+ FITS_OPTIONS_DONE:
+
+    file->fileLevel = pmFPAPHULevel(format);
+    if (file->fileLevel == PM_FPA_LEVEL_NONE) {
+        psError(PS_ERR_IO, true, "Unable to determine file level for %s\n", name);
+        psFree(file);
+        return NULL;
+    }
+
+    file->dataLevel = pmFPALevelFromName(psMetadataLookupStr(&status, filerule, "DATA.LEVEL"));
+    if (file->dataLevel == PM_FPA_LEVEL_NONE) {
+        psError(PS_ERR_IO, true, "DATA.LEVEL is not set for %s\n", name);
+        psFree(file);
+        return NULL;
+    }
+    // default is to free the data after use (after written out)
+    // this can be overridden for pmFPAfiles used as carriers as well
+    file->freeLevel = file->dataLevel;
+    file->fileLevel = PS_MIN (file->fileLevel, file->dataLevel);
+
+    // XXX the file/data/free level must be consistent with the reference fpa (but since we
+    // don't have access to its pmFPAfile, we cannot enforce this here...
+
+    pmFPALevel extLevel = pmFPAExtensionsLevel(format); // Level for extensions
+    if (extLevel != PM_FPA_LEVEL_NONE) {
+        if (extLevel < file->dataLevel) {
+            psWarning("Level for extensions is higher than desired data level --- adjusting.\n");
+            file->dataLevel = extLevel;
+        }
+        if (extLevel < file->freeLevel) {
+            psWarning("Level for extensions is higher than desired free level --- adjusting.\n");
+            file->freeLevel = extLevel;
+        }
+    } else {
+        // if we do not have extensions in the file, we are forced to write out at the file level
+        file->dataLevel = file->fileLevel;
+        file->freeLevel = file->fileLevel;
+    }
+
+    psTrace ("psModules.camera", 5, "file: %s, format: %s, fileLevel: %s, extLevel: %s, dataLevel: %s, freeLevel: %s\n",
+             file->name, file->formatName, pmFPALevelToName (file->fileLevel), pmFPALevelToName(extLevel), pmFPALevelToName (file->dataLevel), pmFPALevelToName (file->freeLevel));
+
+    // add argument-supplied OUTPUT name to this file
+    char *outname = psMetadataLookupStr(&status, config->arguments, "OUTPUT");
+    psMetadataAddStr(file->names, PS_LIST_TAIL, "OUTPUT", PS_META_NO_REPLACE, "", outname);
+
+    // place the resulting file in the config system
+    psMetadataAddPtr (config->files, PS_LIST_TAIL, name, PS_DATA_UNKNOWN, "", file);
+    psFree(file);                       // we free this copy of file, but 'files' still has a copy
+    return file;                        // the returned value is a view into the version on 'files'
+}
+
+// define a pmFPAfile, bind to the optional fpa if supplied
+pmFPAfile *pmFPAfileDefineOutput(const pmConfig *config, pmFPA *fpa, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->files, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->camera, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(name, NULL);
+
+    return pmFPAfileDefineOutputForFormat(config, fpa, name, NULL, NULL);
+}
+
+// define a pmFPAfile, bind to the optional file if supplied
+pmFPAfile *pmFPAfileDefineOutputFromFile(const pmConfig *config, pmFPAfile *file, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->files, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->camera, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(name, NULL);
+
+    char *cameraName = NULL, *formatName = NULL; // Name of camera and format
+    pmFPA *fpa = NULL;                  // FPA for file
+    if (file) {
+        cameraName = file->cameraName;
+        formatName = file->formatName;
+        fpa = file->fpa;
+    }
+
+    return pmFPAfileDefineOutputForFormat(config, fpa, name, cameraName, formatName);
+}
+
+// search for argname on the config->argument list
+// construct an FPA based on the files in this list (must represent a single FPA)
+// built the association between the FPA elements (CHIP/CELL) and the files
+// define the pmFPAfile filename and bind it to this FPA
+// save the pmFPAfile on config->files
+// return the pmFPAfile (a view to the one saved on config->files)
+pmFPAfile *pmFPAfileDefineFromArgs(bool *success, pmConfig *config, const char *filename, const char *argname)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(argname, NULL);
+
+    bool status;
+    pmFPA *fpa = NULL;
+    psFits *fits = NULL;
+    pmFPAfile *file = NULL;
+    psMetadata *phu = NULL;
+    psMetadata *format = NULL;
+    psMetadata *camera = NULL;
+    psString formatName = NULL;
+
+    // use success to identify valid exit conditions (as opposed to 'argument not supplied')
+    if (success) {
+        *success = false;
+    }
+
+    // we search the argument data for the named fileset (argname)
+    psArray *infiles = psMetadataLookupPtr(&status, config->arguments, argname);
+    if (!status) {
+        if (success) {
+            *success = true;
+        }
+        return NULL;
+    }
+    if (infiles->n < 1) {
+        psError(PS_ERR_IO, false, "Found n == %ld files in %s in arguments\n", infiles->n, argname);
+        return NULL;
+    }
+
+    // this function is implicitly an INPUT operation: do not create the file
+    psString realName = pmConfigConvertFilename (infiles->data[0], config, false, false);
+    if (!realName) {
+        psError(PS_ERR_IO, false, "Failed to convert file name %s\n", (char *) infiles->data[0]);
+        return NULL;
+    }
+
+    // load the header of the first image
+    // EXTWORD (fits->extword) is not relevant to the PHU
+    fits = psFitsOpen (realName, "r");
+    if (!fits) {
+        psError(PS_ERR_IO, false, "Failed to open file %s\n", realName);
+        psFree (realName);
+        return NULL;
+    }
+    phu = psFitsReadHeader (NULL, fits);
+    if (!phu) {
+        psError(PS_ERR_IO, false, "Failed to read file header %s\n", realName);
+        psFree (realName);
+        return NULL;
+    }
+    psFitsClose(fits);
+
+    // Determine the current format from the header; Determine camera if not specified already.
+    // the returned pointers 'camera' and 'formatName' are allocated here
+    format = pmConfigCameraFormatFromHeader(&camera, &formatName, config, phu, true);
+    if (!format) {
+        psError(PS_ERR_IO, false, "Failed to read CCD format from %s\n", realName);
+        psFree(phu);
+	psFree(camera);
+	psFree(formatName);
+        psFree(realName);
+        return NULL;
+    }
+
+    if (file->type == PM_FPA_FILE_MASK) {
+	if (!pmConfigMaskReadHeader (config, phu)) {
+	    psError(PS_ERR_IO, false, "error in mask bits");
+	    return NULL;
+	}
+    }
+
+    // build the template fpa, set up the basic view
+    // XXX do we want this to be the baseCamera name or the metaCamera name?
+    fpa = pmFPAConstruct(camera, config->cameraName);
+    if (!fpa) {
+        psError(PS_ERR_IO, false, "Failed to construct FPA from %s", realName);
+        psFree(phu);
+	psFree(camera);
+	psFree(formatName);
+        psFree(realName);
+        psFree(format);
+        return NULL;
+    }
+    psFree (realName);
+    psFree (camera);
+
+    // load the given filerule (from config->camera) and bind it to the fpa
+    // the returned file is just a view to the entry on config->files
+    file = pmFPAfileDefineInput(config, fpa, filename);
+    if (!file) {
+        psError(PS_ERR_IO, false, "file %s not defined\n", filename);
+        psFree(phu);
+	psFree(formatName);
+        psFree(format);
+        psFree(fpa);
+        return NULL;
+    }
+    psFree (file->filerule); // this is set in pmFPAfileDefineInput 
+    file->format = format;
+    file->formatName = formatName;
+    file->filerule = psStringCopy("@FILES");
+    file->filesrc = psStringCopy("{CHIP.NAME}.{CELL.NAME}");
+    // we use the above rules to identify these files in the file->names data
+
+    file->fileLevel = pmFPAPHULevel(format);
+    if (file->fileLevel == PM_FPA_LEVEL_NONE) {
+        psError(PS_ERR_IO, true, "Unable to determine file level for %s\n", file->name);
+        psFree(phu);
+        psFree(fpa);
+        return NULL;
+    }
+
+    // examine the list of input files and validate their cameras
+    // associated each filename with an element of the FPA
+    // save the association on file->names
+    for (int i = 0; i < infiles->n; i++) {
+        if (i > 0) {
+            // this function is implicitly an INPUT operation: do not create the file
+            psString realName = pmConfigConvertFilename (infiles->data[i], config, false, false);
+            if (!realName) {
+                psError(PS_ERR_IO, false, "Failed to convert file name %s", (char *) infiles->data[i]);
+		psFree(phu);
+		psFree(fpa);
+                return NULL;
+            }
+            // EXTWORD (fits->extword) is not relevant to the PHU
+            fits = psFitsOpen (realName, "r");
+            if (!fits) {
+                psError(PS_ERR_IO, false, "Failed to open file %s\n", realName);
+                psFree(realName);
+		psFree(phu);
+		psFree(fpa);
+                return NULL;
+            }
+            phu = psFitsReadHeader (NULL, fits);
+            if (!phu) {
+                psError(PS_ERR_IO, false, "Failed to read file header %s", realName);
+                psFree(realName);
+                psFitsClose(fits);
+		psFree(phu);
+		psFree(fpa);
+                return NULL;
+            }
+            bool valid = false;
+            if (!pmConfigValidateCameraFormat (&valid, format, phu)) {
+                psError(PS_ERR_UNKNOWN, false, "Error in config scripts\n");
+                psFree(realName);
+                psFitsClose(fits);
+		psFree(phu);
+		psFree(fpa);
+                return NULL;
+            }
+            if (!valid) {
+                psError(PS_ERR_IO, false, "file %s is not from the required camera", realName);
+                psFree(realName);
+                psFitsClose(fits);
+		psFree(phu);
+		psFree(fpa);
+                return NULL;
+            }
+            psFree(realName);
+            psFitsClose(fits);
+        }
+
+        // set the view to the corresponding entry for this phu
+        pmFPAview *view = pmFPAAddSourceFromHeader (fpa, phu, format);
+        if (!view) {
+            psError(PS_ERR_IO, false, "Unable to determine source for %s", file->name);
+            psFree(phu);
+            psFree(fpa);
+            return NULL;
+        }
+
+        // associate the filename with the FPA element
+        char *name = pmFPAfileNameFromRule(file->filesrc, file, view);
+
+        // save the name association in the pmFPAfile structure
+        psMetadataAddStr(file->names, PS_LIST_TAIL, name, 0, "", infiles->data[i]);
+
+        psFree(view);
+        psFree(name);
+        psFree(phu);
+    }
+    psFree(fpa);
+    if (success) {
+        *success = true;
+    }
+
+    return file;
+}
+
+// search for argname on the config->argument list
+// construct an FPA based on the files in this list (must represent a single FPA)
+// built the association between the FPA elements (CHIP/CELL) and the files
+// define the pmFPAfile filename and bind it to this FPA
+// save the pmFPAfile on config->files
+// return the pmFPAfile (a view to the one saved on config->files)
+pmFPAfile *pmFPAfileBindFromArgs (bool *success, pmFPAfile *input, pmConfig *config, const char *filename, const char *argname)
+{
+    PS_ASSERT_PTR_NON_NULL(input, NULL);
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(argname, NULL);
+
+    bool status;
+    psFits *fits = NULL;
+    pmFPAfile *file = NULL;
+    psMetadata *phu = NULL;
+
+    // use success to identify valid exit conditions (as opposed to 'argument not supplied')
+    if (success) {
+        *success = false;
+    }
+
+    // we search the argument data for the named fileset (argname)
+    psArray *infiles = psMetadataLookupPtr(&status, config->arguments, argname);
+    if (!status) {
+        // this is not an error: this just means no matching argument was supplied
+        if (success) {
+            *success = true;
+        }
+        return NULL;
+    }
+    if (infiles->n < 1) {
+        psError(PS_ERR_IO, false, "Found n == %ld files in %s in arguments\n", infiles->n, argname);
+        return NULL;
+    }
+
+    // load the given filerule (from config->camera) and bind it to the fpa
+    // the returned file is just a view to the entry on config->files
+    file = pmFPAfileDefineInput (config, input->fpa, filename);
+    if (!file) {
+        psError(PS_ERR_IO, false, "file %s not defined\n", filename);
+        psFree(phu);
+        return NULL;
+    }
+
+    // set derived values
+    file->fileLevel = input->fileLevel;
+
+
+    // define the rule to identify these files in the file->names data
+    psFree (file->filerule);
+    psFree (file->filesrc);
+    file->filerule = psStringCopy ("@FILES");
+    file->filesrc = psStringCopy ("{CHIP.NAME}.{CELL.NAME}");
+
+    // examine the list of input files and validate their cameras
+    // associated each filename with an element of the FPA
+    // save the association on file->names
+    psMetadata *format = NULL;
+    for (int i = 0; i < infiles->n; i++) {
+        // this function is implicitly an INPUT operation: do not create the file
+        psString realName = pmConfigConvertFilename (infiles->data[i], config, false, false);
+        if (!realName) {
+            psError(PS_ERR_IO, false, "Failed to convert file name %s", (char *) infiles->data[i]);
+            return NULL;
+        }
+        // EXTWORD (fits->extword) is not relevant to the PHU
+        fits = psFitsOpen (realName, "r");
+        if (!fits) {
+            psError(PS_ERR_IO, false, "Failed to open file %s\n", realName);
+            psFree(realName);
+            return NULL;
+        }
+        phu = psFitsReadHeader (NULL, fits);
+        if (!phu) {
+            psError(PS_ERR_IO, false, "Failed to read file header %s", realName);
+            psFree(realName);
+            psFitsClose(fits);
+            return NULL;
+        }
+
+        if (!format) {
+            format = pmConfigCameraFormatFromHeader(NULL, NULL, config, phu, true);
+            if (!format) {
+                psError(PS_ERR_IO, false, "Failed to read CCD format from %s\n", realName);
+                psFree(phu);
+                psFree(realName);
+                psFitsClose(fits);
+                return NULL;
+            }
+        } else {
+            bool valid = false;
+            if (!pmConfigValidateCameraFormat(&valid, format, phu)) {
+                psError(PS_ERR_UNKNOWN, false, "Error in config scripts\n");
+                psFree(realName);
+                psFitsClose(fits);
+                return NULL;
+            }
+            if (!valid) {
+                psError(PS_ERR_IO, false, "specified data file %s does not match format of supplied INPUT\n",
+                        realName);
+                psFree(realName);
+                psFitsClose(fits);
+                return NULL;
+            }
+        }
+
+        psFree(realName);
+        psFitsClose(fits);
+
+        // set the view to the corresponding entry for this phu
+        pmFPAview *view = pmFPAIdentifySourceFromHeader (input->fpa, phu, format);
+        if (!view) {
+            psError(PS_ERR_IO, false, "Unable to determine source for %s", file->name);
+            psFree(phu);
+            return NULL;
+        }
+
+        // associate the filename with the FPA element
+        char *name = pmFPAfileNameFromRule(file->filesrc, file, view);
+
+        // save the name association in the pmFPAfile structure
+        psMetadataAddStr (file->names, PS_LIST_TAIL, name, 0, "", infiles->data[i]);
+
+	if ((i == 0) && (file->type == PM_FPA_FILE_MASK)) {
+	    if (!pmConfigMaskReadHeader (config, phu)) {
+		psError(PS_ERR_IO, false, "error in mask bits");
+		return NULL;
+	    }
+	}
+
+        psFree(view);
+        psFree(name);
+        psFree(phu);
+    }
+    file->format = format;
+    file->formatName = psStringCopy(config->formatName);
+
+    if (success) {
+        *success = true;
+    }
+    return file;
+}
+
+// search for argname on the config->argument list
+// construct an FPA based on the files in this list (each represents the same FPA)
+// built the association between the FPA elements (CHIP/CELL) and the files
+// define the pmFPAfile filenames and bind them to the FPAs
+// save the pmFPAfiles on config->files
+// return the pmFPAfiles (a view to the one saved on config->files)
+pmFPAfile *pmFPAfileDefineSingleFromArgs (bool *success, pmConfig *config, const char *filename,
+        const char *argname, int entry)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(argname, NULL);
+
+    bool status;
+    pmFPA *fpa = NULL;
+    psFits *fits = NULL;
+    pmFPAfile *file = NULL;
+    psMetadata *phu = NULL;
+    psMetadata *format = NULL;
+
+    if (success) {
+        *success = false;
+    }
+
+    // we search the argument data for the named fileset (argname)
+    psArray *infiles = psMetadataLookupPtr(&status, config->arguments, argname);
+    if (!status) {
+        psTrace("psModules.camera", 5, "Failed to find %s in argument list", argname);
+        if (success) {
+            *success = true;
+        }
+        return NULL;
+    }
+    if (infiles->n <= entry) {
+        psError(PS_ERR_IO, false, "only %ld files in %s in argument, entry %d requested\n",
+                infiles->n, argname, entry);
+        return NULL;
+    }
+
+    // examine the list of input files and validate their cameras
+    // associated each filename with an element of the FPA
+    // save the association on file->names
+    // EXTWORD (fits->extword) is not relevant to the PHU
+    fits = psFitsOpen (infiles->data[entry], "r");
+    phu = psFitsReadHeader (NULL, fits);
+    psFitsClose (fits);
+
+    // on first call to this function, config->camera is not set.
+    // later calls will give an error if the cameras do not match
+    psMetadata *camera = NULL;
+    psString formatName = NULL;
+    format = pmConfigCameraFormatFromHeader (&camera, &formatName, config, phu, true);
+    if (!format) {
+        psError(PS_ERR_IO, false, "Failed to read CCD format from %s\n", (char *)infiles->data[0]);
+        psFree(phu);
+	psFree(camera);
+	psFree(formatName);
+        return NULL;
+    }
+
+    if (file->type == PM_FPA_FILE_MASK) {
+	if (!pmConfigMaskReadHeader (config, phu)) {
+	    psError(PS_ERR_IO, false, "error in mask bits");
+	    return NULL;
+	}
+    }
+
+    // build the template fpa, set up the basic view
+    fpa = pmFPAConstruct (camera, config->cameraName);
+    if (!fpa) {
+        psError(PS_ERR_IO, false, "Failed to construct FPA from %s", (char *)infiles->data[0]);
+        psFree(phu);
+	psFree(camera);
+	psFree(formatName);
+        psFree(format);
+        return NULL;
+    }
+    psFree(camera);
+
+    // load the given filerule (from config->camera) and bind it to the fpa
+    // the returned file is just a view to the entry on config->files
+    // we need a variable name here... (but in filerule)
+    file = pmFPAfileDefineInput (config, fpa, filename);
+    if (!file) {
+        psError(PS_ERR_IO, false, "file %s not defined\n", filename);
+        psFree(phu);
+        psFree(fpa);
+        psFree(format);
+        return NULL;
+    }
+    FPA_TEST_ASSERT (file);
+    file->format = format;
+    file->formatName = formatName;
+    file->filerule = psStringCopy ("@FILES");
+    file->filesrc = psStringCopy ("{CHIP.NAME}.{CELL.NAME}");
+    // adjust the above rules to identify these files in the file->names data
+
+    file->fileLevel = pmFPAPHULevel(format);
+    if (file->fileLevel == PM_FPA_LEVEL_NONE) {
+        psError(PS_ERR_IO, true, "Unable to determine file level for %s\n", file->name);
+        psFree(phu);
+        psFree(fpa);
+        return NULL;
+    }
+
+    // set the view to the corresponding entry for this phu
+    pmFPAview *view = pmFPAAddSourceFromHeader (fpa, phu, format);
+    if (!view) {
+        psError(PS_ERR_IO, false, "Unable to determine source for %s", file->name);
+        psFree(phu);
+        psFree(fpa);
+        return NULL;
+    }
+
+    // associate the filename with the FPA element
+    char *name = pmFPAfileNameFromRule (file->filesrc, file, view);
+
+    // save the name association in the pmFPAfile structure
+    psMetadataAddStr (file->names, PS_LIST_TAIL, name, 0, "", infiles->data[entry]);
+
+    psFree(phu);
+    psFree(fpa);
+    psFree(view);
+    psFree(name);
+
+    if (success) {
+        *success = true;
+    }
+    return file;
+}
+
+// define the named pmFPAfile from the camera->config
+// only valid for pmFPAfile->mode = READ
+pmFPAfile *pmFPAfileDefineFromConf (bool *success, const pmConfig *config, const char *filename)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+
+    if (success) {
+        *success = false;
+    }
+
+    // a camera config is needed (as source of file rule)
+    if (config->camera == NULL) {
+        psError(PS_ERR_IO, true, "camera is not defined");
+        return NULL;
+    }
+
+    // build the template fpa, set up the basic view
+    pmFPA *fpa = pmFPAConstruct(config->camera, config->cameraName);
+    if (!fpa) {
+        psError(PS_ERR_IO, false, "Failed to construct FPA for %s", filename);
+        return NULL;
+    }
+
+    // load the given filerule (from config->camera) and bind it to the fpa
+    // the returned file is just a view to the entry on config->files
+    pmFPAfile *file = pmFPAfileDefineInput(config, fpa, filename);
+    psFree (fpa);
+    if (!file) {
+        psError(PS_ERR_IO, false, "file %s not defined\n", filename);
+        return NULL;
+    }
+
+    // image names may not come from file->names
+    if (!strcasecmp(file->filerule, "@FILES")) {
+        psError(PS_ERR_IO, true, "supplied filerule uses illegal value @FILES");
+        // XXX remove the file from config->files
+        return NULL;
+    }
+
+    // image names may come from the detrend database
+    if (!strcasecmp(file->filerule, "@DETDB")) {
+        psTrace ("pmFPAfile", 5, "requiring use of detrend database source\n");
+        // don't free the file here: it is left on config->files
+        // to be used optionally by pmFPAfileDefineFromDetDB (or others)
+        if (success) {
+            *success = true;
+        }
+        return NULL;
+    }
+
+    // Prepend the global path to the file rule
+    // this function is implicitly an INPUT operation: do not create the file
+    psString tmpName = pmConfigConvertFilename(file->filerule, config, false, false);
+    psFree (file->filerule);
+    file->filerule = tmpName;
+
+    if (success) {
+        *success = true;
+    }
+
+    return file;
+}
+
+// construct an FPA based on the supplied config->camera
+// built the association between the FPA elements (CHIP/CELL) and the files
+// define the pmFPAfile filename and bind it to this FPA
+// save the pmFPAfile on config->files
+// return the pmFPAfile (a view to the one saved on config->files)
+pmFPAfile *pmFPAfileDefineFromDetDB (bool *success, const pmConfig *config, const char *filename,
+                                     pmFPA *input, pmDetrendType type)
+{
+    PS_ASSERT_PTR_NON_NULL(input, NULL);
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->camera, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->files, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+
+    bool status;
+    pmFPA *fpa = NULL;
+    pmFPAfile *file = NULL;
+
+    if (success) {
+        *success = false;
+    }
+
+    // a camera config is needed (as source of file rule)
+    if (config->camera == NULL) {
+        psError(PS_ERR_IO, true, "camera is not defined");
+        return NULL;
+    }
+    // a camera config is needed (as source of file rule)
+    if (config->cameraName == NULL) {
+        psAbort("camera defined but not cameraName!");
+    }
+
+    // find or define a pmFPAfile with this name
+    file = psMetadataLookupPtr (NULL, config->files, filename);
+    if (!file) {
+        // build the template fpa, set up the basic view
+        fpa = pmFPAConstruct(config->camera, config->cameraName);
+        if (!fpa) {
+            psError(PS_ERR_IO, false, "Failed to construct FPA for %s", filename);
+            return NULL;
+        }
+        // load the given filerule (from config->camera) and bind it to the fpa
+        // the returned file is just a view to the entry on config->files
+        file = pmFPAfileDefineInput (config, fpa, filename);
+        if (!file) {
+            psError(PS_ERR_IO, false, "file %s not defined\n", filename);
+            psFree(fpa);
+            return NULL;
+        }
+    }
+
+    // we are constructing a detselect command of the form:
+    //   detselect -search -inst (camera) -type (type) -time (time) [others]
+    // camera, type, and time are derived from pmFPA *input, other options are
+    // added if specified for the particular detrend type by the DETREND.CONSTRAINTS
+    // note that the filter-dependent choices are set for ppImage in ppImageParseCamera
+    // XXX make all of the detrend constraints explicit in DETREND.CONSTRAINTS?
+
+    // Get the time from FPA.TIME
+    psTime *time = psMetadataLookupPtr(NULL, input->concepts, "FPA.TIME");
+    if (time->sec == 0 && time->nsec == 0) {
+        psLogMsg ("psModules.camera", PS_LOG_WARN, "FPA.TIME has not been set.\n");
+    }
+
+    // XXX careful about this: is this set correctly in the camera.config files?
+    char *instrument = psMetadataLookupStr(NULL, input->concepts, "FPA.INSTRUMENT");
+    pmDetrendSelectOptions *options = pmDetrendSelectOptionsAlloc(instrument, *time, type);
+
+    // add additional constraints based on the type defined in the PPIMAGE recipe
+    // XXX use PPIMAGE or DETREND for the recipe name?
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, "PPIMAGE");
+    if (!status) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "PPIMAGE recipe not found.");
+        psFree(options);
+        psFree(fpa);
+        return false;
+    }
+    psMetadata *detConstraints = psMetadataLookupPtr (&status, recipe, "DETREND.CONSTRAINTS");
+    if (!status) {
+        psWarning("DETREND.CONSTRAINTS not found --- no constraints will be applied.");
+        goto DETREND_SELECT;
+    }
+
+    psString typeName = pmDetrendTypeToString (type);
+    psMetadata *constraints = psMetadataLookupPtr (&status, detConstraints, typeName);
+    if (!status) {
+        psWarning("DETREND.CONSTRAINTS for type %s not found --- no contraints will be applied.", typeName);
+        psFree(typeName);
+        goto DETREND_SELECT;
+    }
+    psFree(typeName);
+
+    // loop over the constraints and include in the detselect options
+    psMetadataIterator *iter = psMetadataIteratorAlloc (constraints, PS_LIST_HEAD, NULL);
+    psMetadataItem *item = NULL;
+    while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
+        if (item->type != PS_DATA_STRING) {
+            psWarning("Invalid type for DETREND.CONSTRAINT element %s --- ignoring constraint", item->name);
+            continue;
+        }
+        char *option  = item->name;     // item->name must correspond to a valid detselect option
+        char *concept = item->data.V;
+
+        // these items refer to the corresponding values for the input image
+        // (ie, -filter input:filter or -exptime input:exptime)
+        if (!strcasecmp (option, "filter")) {
+            options->filter = psMetadataLookupPtr (&status, input->concepts, concept);
+            psMemIncrRefCounter (options->filter);
+            if (!status)
+                psAbort("failed to find filter (concept %s)", concept);
+        } else if (!strcasecmp (option, "exptime")) {
+            options->exptime = psMetadataLookupF32 (&status, input->concepts, concept);
+            options->exptimeSet = true;
+            if (!status)
+                psAbort("exptime not found (concept %s)", concept);
+        } else if (!strcasecmp (option, "airmass")) {
+            options->airmass = psMetadataLookupF32 (&status, input->concepts, concept);
+            options->airmassSet = true;
+            if (!status)
+                psAbort("airmass not found (concept %s)", concept);
+        } else if (!strcasecmp (option, "dettemp")) {
+            options->dettemp = psMetadataLookupF32 (&status, input->concepts, concept);
+            options->dettempSet = true;
+            if (!status)
+                psAbort("dettemp not found (concept %s)", concept);
+        } else if (!strcasecmp (option, "twilight")) {
+            options->twilight = psMetadataLookupF32 (&status, input->concepts, concept);
+            options->twilightSet = true;
+            if (!status)
+                psAbort("twilight not found (concept %s)", concept);
+        }
+
+        // the version is applied literally
+        if (!strcasecmp (option, "version")) {
+            options->version = psMemIncrRefCounter (concept);
+        }
+        // we can override the detrend database dettype if desired
+        // ie, use DOMEFLAT for type FLAT
+        // the dettype string is applied literally
+        if (!strcasecmp (option, "dettype")) {
+            options->dettype = psMemIncrRefCounter (concept);
+        }
+    }
+    psFree(iter);
+
+DETREND_SELECT:
+    {
+        // search for existing detrend data (detID)
+        pmDetrendSelectResults *results = pmDetrendSelect (options, config);
+        if (!results) {
+            psError (PS_ERR_IO, false, "no matching detrend data");
+            return NULL;
+        }
+        file->detrend = results;
+        file->fileLevel = pmFPALevelFromName(results->level);
+        if (file->fileLevel == PM_FPA_LEVEL_NONE) {
+            psError (PS_ERR_IO, false, "invalid file level for selected detrend data");
+            return NULL;
+        }
+    }
+
+    psFree (options);
+
+    if (success) {
+        *success = true;
+    }
+    return file;
+}
+
+// create a new output pmFPAfile based on an existing FPA
+// only valid for pmFPAfile->mode == WRITE (or internal?)
+pmFPAfile *pmFPAfileDefineFromFPA (const pmConfig *config, pmFPA *src, int xBin, int yBin, const char *filename)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(src, false);
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+
+    pmFPA *fpa = pmFPAConstruct(src->camera, psMetadataLookupStr(NULL, src->concepts, "FPA.CAMERA"));
+    // XXX should this use DefineOutputForFormat?
+    pmFPAfile *file = pmFPAfileDefineOutput (config, fpa, filename);
+    if (!file) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "file %s not defined\n", filename);
+        return NULL;
+    }
+    file->src = psMemIncrRefCounter(src); // inherit output elements from this source pmFPA
+    file->xBin = xBin;
+    file->yBin = yBin;
+    psFree (fpa);
+    return file;
+}
+
+pmFPAfile *pmFPAfileDefineFromFile(const pmConfig *config, pmFPAfile *src, int xBin, int yBin,
+                                   const char *filename)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(src, false);
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+
+    pmFPAfile *file = pmFPAfileDefineOutputForFormat(config, NULL, filename, src->cameraName,
+                                                     src->formatName);
+    file->src = psMemIncrRefCounter(src->fpa); // inherit output elements from this source pmFPA
+    file->xBin = xBin;
+    file->yBin = yBin;
+
+    // inherit the concepts from the src fpa:
+    pmFPACopyConcepts(file->fpa, file->src);
+
+    return file;
+}
+
+// create a new output pmFPAfile based on an existing FPA
+// only valid for pmFPAfile->mode == WRITE (or internal?)
+pmFPAfile *pmFPAfileDefineNewCamera (const pmConfig *config, const char *filename)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+
+    pmFPAfile *file = pmFPAfileDefineOutput (config, NULL, filename);
+    if (!file) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "file %s not defined\n", filename);
+        return NULL;
+    }
+    if (!file->camera) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "file %s does not define a new camera\n", filename);
+        return NULL;
+    }
+    file->fpa = pmFPAConstruct(file->camera, file->cameraName);
+
+    return file;
+}
+
+pmFPAfile *pmFPAfileDefineSkycell(const pmConfig *config, pmFPA *fpa, const char *filename)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(config->cameraName, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(config->formatName, NULL);
+
+    pmFPAfile *file;                    // The new file
+
+    if (config->cameraName[0] == '_' &&
+        strcmp(config->cameraName + strlen(config->cameraName) - 8, "-SKYCELL") == 0) {
+        // The input camera is already a skycell
+        file = pmFPAfileDefineOutputForFormat(config, fpa, filename, config->cameraName, "SKYCELL");
+    } else {
+        psString cameraName = NULL;         // Name of the old camera configuration
+        if (config->cameraName[0] == '_' &&
+            strcmp(config->cameraName + strlen(config->cameraName) - 5, "-CHIP") == 0) {
+            cameraName = psStringNCopy(config->cameraName + 1, strlen(config->cameraName) - 6);
+        } else if (config->cameraName[0] == '_' &&
+                   strcmp(config->cameraName + strlen(config->cameraName) - 4 , "-FPA") == 0) {
+            cameraName = psStringNCopy(config->cameraName + 1, strlen(config->cameraName) - 5);
+        } else {
+            cameraName = psMemIncrRefCounter(config->cameraName);
+        }
+        psString newCameraName = NULL;  // Name of the new (automatically-generated) camera configuration
+        psStringAppend(&newCameraName, "_%s-SKYCELL", cameraName);
+        file = pmFPAfileDefineOutputForFormat(config, fpa, filename, newCameraName, "SKYCELL");
+        psFree(cameraName);
+        psFree(newCameraName);
+    }
+    if (!file) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "file %s not defined\n", filename);
+        return NULL;
+    }
+
+    // Ensure everything is written out at the appropriate level
+    file->fileLevel = PM_FPA_LEVEL_FPA;
+    file->dataLevel = PM_FPA_LEVEL_FPA;
+    file->freeLevel = PM_FPA_LEVEL_FPA;
+
+    return file;
+}
+
+pmFPAfile *pmFPAfileDefineChipMosaic(const pmConfig *config, pmFPA *src, const char *filename)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(src, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(config->cameraName, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(config->formatName, NULL);
+
+    pmFPAfile *file;                    // The new file
+    if (config->cameraName[0] == '_' &&
+        (strcmp(config->cameraName + strlen(config->cameraName) - 5, "-CHIP") == 0 ||
+         strcmp(config->cameraName + strlen(config->cameraName) - 8, "-SKYCELL") == 0)) {
+        // The input camera has already been mosaicked to this level
+        file = pmFPAfileDefineOutputForFormat(config, NULL, filename, config->cameraName, config->formatName);
+    } else {
+        psString cameraName = NULL; // Name of the new (automatically-generated) camera configuration
+        if (config->cameraName[0] == '_' &&
+            strcmp(config->cameraName + strlen(config->cameraName) - 4 , "-FPA") == 0) {
+            cameraName = psStringNCopy(config->cameraName + 1, strlen(config->cameraName) - 5);
+        } else {
+            cameraName = psMemIncrRefCounter(config->cameraName);
+        }
+        psString newCameraName = NULL;  // Name of the new (automatically-generated) camera configuration
+        psStringAppend(&newCameraName, "_%s-CHIP", cameraName);
+
+        // Find the correct camera configuration
+        file = pmFPAfileDefineOutputForFormat(config, NULL, filename, newCameraName, config->formatName);
+        psFree(newCameraName);
+        psFree(cameraName);
+    }
+    if (!file) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "file %s not defined\n", filename);
+        return NULL;
+    }
+
+    file->src = psMemIncrRefCounter(src); // inherit output elements from this source pmFPA
+    if (src) {
+        if (!pmConceptsCopyFPA(file->fpa, src, true, false)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to copy concepts from source to new FPA");
+            return NULL;
+        }
+    }
+
+    file->mosaicLevel = PM_FPA_LEVEL_CHIP; // don't do any I/O on this at a lower level
+
+    return file;
+}
+
+pmFPAfile *pmFPAfileDefineFPAMosaic(const pmConfig *config, pmFPA *src, const char *filename)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(src, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(config->cameraName, NULL);
+
+    pmFPAfile *file;                    // The new file
+    if (config->cameraName[0] == '_' &&
+        (strcmp(config->cameraName + strlen(config->cameraName) - 4 , "-FPA") == 0 ||
+         strcmp(config->cameraName + strlen(config->cameraName) - 8, "-SKYCELL") == 0)) {
+        // The input camera has already been mosaicked to this level
+        file = pmFPAfileDefineOutputForFormat(config, NULL, filename, config->cameraName, config->formatName);
+    } else {
+
+        psString original = NULL;       // Name of the original camera configuration
+        if (config->cameraName[0] == '_' &&
+            strcmp(config->cameraName + strlen(config->cameraName) - 5 , "-CHIP") == 0) {
+            // It's a chip mosaic; we need to get the original name
+            original = psStringNCopy(config->cameraName + 1, strlen(config->cameraName) - 6);
+        } else if (config->cameraName[0] == '_' &&
+            strcmp(config->cameraName + strlen(config->cameraName) - 8, "-SKYCELL") == 0) {
+            original = psStringNCopy(config->cameraName + 1, strlen(config->cameraName) - 9);
+        } else {
+            original = psMemIncrRefCounter(config->cameraName);
+        }
+        psString cameraName = NULL;
+        psStringAppend(&cameraName, "_%s-FPA", original);
+        psFree(original);
+
+        file = pmFPAfileDefineOutputForFormat(config, NULL, filename, cameraName, config->formatName);
+        psFree(cameraName);
+    }
+    if (!file) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "file %s not defined\n", filename);
+        return NULL;
+    }
+
+    file->src = psMemIncrRefCounter(src); // inherit output elements from this source pmFPA
+    if (src) {
+        if (!pmConceptsCopyFPA(file->fpa, src, false, false)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to copy concepts from source to new FPA");
+            return NULL;
+        }
+    }
+
+    file->mosaicLevel = PM_FPA_LEVEL_FPA; // don't do any I/O on this at a lower level
+
+    return file;
+}
+
+// create a file with the given name, assign it type "INTERNAL", and supply it with an image
+// of the requested dimensions. (image only, mask and weight are ignored)
+pmReadout *pmFPAfileDefineInternal (psMetadata *files, const char *name, int Nx, int Ny, int type)
+{
+    PS_ASSERT_PTR_NON_NULL(files, false);
+    PS_ASSERT_STRING_NON_EMPTY(name, NULL);
+
+    pmReadout *readout = pmReadoutAlloc(NULL);
+    readout->image = psImageAlloc(Nx, Ny, type);
+
+    // I want an image from the
+    pmFPAfile *file = pmFPAfileAlloc();
+    file->mode = PM_FPA_MODE_INTERNAL;
+    file->name = psStringCopy (name);
+
+    file->readout = readout;
+    psMetadataAddPtr(files, PS_LIST_TAIL, name, PS_DATA_UNKNOWN, "", file);
+    psFree(file);
+    // we free this copy of file, but 'files' still has a copy
+
+    return readout;
+}
+
+bool pmFPAfileDropInternal(psMetadata *files, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(files, false);
+    PS_ASSERT_STRING_NON_EMPTY(name, NULL);
+
+    bool status = false;
+
+    pmFPAfile *file = psMetadataLookupPtr(&status, files, name);
+    if (!status) {
+        psTrace("psModules.camera", 6, "Internal File %s not in file list", name);
+        return true;
+    }
+    if (file == NULL) {
+        psError(PS_ERR_IO, true, "file %s is NULL", name);
+        return false;
+    }
+    if (file->mode != PM_FPA_MODE_INTERNAL) {
+        psTrace("psModules.camera", 6, "FPA File %s not Internal, not dropping", name);
+        return true;
+    }
+
+    psTrace("psModules.camera", 6, "dropping Internal FPA File %s", name);
+    psMetadataRemoveKey(files, name);
+    return true;
+}
+
+// Select or construct the requested readout.  If the named entry does not exist, generate it based
+// on the specified fpa and binning.  We have 4 possibilities: (INTERNAL or I/O file) and (exists or
+// not).  This call is used after all user-requested pmFPAfiles have been generated.  A missing
+// pmFPAfile is being used internally.
+pmReadout *pmFPAGenerateReadout(const pmConfig *config, // configuration information
+                                const pmFPAview *view, // select background for this entry
+                                const char *name, // name of internal/external file
+                                const pmFPA *fpa, // use this fpa to generate
+                                const psImageBinning *binning) {
+  pmReadout *readout = NULL;
+
+  bool status = true;
+  pmFPAfile *file = psMetadataLookupPtr(&status, config->files, name);
+
+  // if the file does not exist, it is not being used as an I/O file: define an internal version
+  if (file == NULL) {
+    readout = pmFPAfileDefineInternal (config->files, name, binning->nXruff, binning->nYruff, PS_TYPE_F32);
+    return readout;
+  }
+
+  // if the mode is INTERNAL, it has been defined in a previous call.  XXX This seems to require
+  // that the readout have the same dimensions for all entries.
+  if (file->mode == PM_FPA_MODE_INTERNAL) {
+    readout = file->readout;
+    return readout;
+  }
+
+  // we are using this pmFPAfile as an I/O file: select readout or create
+  readout = pmFPAviewThisReadout (view, file->fpa);
+  if (readout == NULL) {
+    // readout does not yet exist: create from input
+    // XXX we have an inconsistency in this calculation here and in pmFPACopy
+    // XXX use the psImageBinning functions to set the output image size
+    if (binning == NULL) {
+      pmFPAfileCopyStructureView (file->fpa, fpa, 1, 1, view);
+      readout = pmFPAviewThisReadout (view, file->fpa);
+    } else {
+      pmFPAfileCopyStructureView (file->fpa, fpa, binning->nXbin, binning->nYbin, view);
+      readout = pmFPAviewThisReadout (view, file->fpa);
+      PS_ASSERT (binning->nXruff == readout->image->numCols, false);
+      PS_ASSERT (binning->nYruff == readout->image->numRows, false);
+    }
+  }
+
+  return readout;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAfileDefine.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAfileDefine.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAfileDefine.h	(revision 22157)
@@ -0,0 +1,156 @@
+/* @file  pmFPAview.h
+ * @brief Tools to manipulate the FPA structure elements.
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.17 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-05-12 21:41:55 $
+ * Copyright 2004-2005 Institute for Astronomy, University of Hawaii
+ */
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+#ifndef PM_FPA_FILE_DEFINE_H
+#define PM_FPA_FILE_DEFINE_H
+
+// load the pmFPAfile information from the camera configuration data
+//
+// Note that the returned pmFPAfile is a view only, so it should not be freed by the caller --- the only
+// reference count is held by the config->files metadata.  Multiple file rules of the same name are permitted
+// if multiple is true.
+pmFPAfile *pmFPAfileDefineInput (const pmConfig *config, pmFPA *fpa, const char *name);
+
+// load the pmFPAfile information from the camera configuration data
+//
+// Note that the returned pmFPAfile is a view only, so it should not be freed by the caller --- the only
+// reference count is held by the config->files metadata.
+// Define an output pmFPAfile
+pmFPAfile *pmFPAfileDefineOutput(const pmConfig *config, // Configuration
+                                 pmFPA *fpa, // Optional FPA to bind
+                                 const char *name // Name of file rule
+    );
+
+/// Same as pmFPAfileDefineOutput, but binds to the fpa in the provided file
+pmFPAfile *pmFPAfileDefineOutputFromFile(const pmConfig *config, // Configuration
+                                         pmFPAfile *file, // File to bind FPAs, or NULL
+                                         const char *name // Name of file rule
+    );
+
+/// Define the FPA file using the provided camera and format names.
+///
+/// Note that the returned pmFPAfile is a view only, so it should not be freed by the caller --- the only
+/// reference count is held by the config->files metadata.
+pmFPAfile *pmFPAfileDefineOutputForFormat(const pmConfig *config, // Configuration
+                                          pmFPA *fpa, // Optional FPA to bind
+                                          const char *name, // Name of file rule
+                                          psString cameraName, // Name of camera configuration to use
+                                          psString formatName // Name of camera format to use
+    );
+
+// look for the given argname on the argument list.  find the give filename from the file rules
+//
+// Note that the returned pmFPAfile is a view only, so it should not be freed by the caller --- the only
+// reference count is held by the config->files metadata.
+pmFPAfile *pmFPAfileDefineFromArgs (bool *found, pmConfig *config, const char *filename, const char *argname);
+
+// look for the given argname on the argument list; bind the associated files to the specified
+// fpa.  these are, eg, mask or weight images.
+// Note that the returned pmFPAfile is a view only, so it should not be freed by the caller --- the only
+// reference count is held by the config->files metadata.
+pmFPAfile *pmFPAfileBindFromArgs (bool *found, pmFPAfile *input, pmConfig *config, const char *filename, const char *argname);
+
+// look for the given argname on the argument list.  find the give filename from the file rules
+//
+// Note that the returned pmFPAfile is a view only, so it should not be freed by the caller --- the only
+// reference count is held by the config->files metadata.
+pmFPAfile *pmFPAfileDefineFromConf (bool *found, const pmConfig *config, const char *filename);
+
+// look for the given argname on the argument list.  find the give filename from the file rules
+//
+// Note that the returned pmFPAfile is a view only, so it should not be freed by the caller --- the only
+// reference count is held by the config->files metadata.
+pmFPAfile *pmFPAfileDefineFromDetDB (bool *found, const pmConfig *config, const char *filename,
+                                     pmFPA *input, pmDetrendType type);
+
+// create a new output pmFPAfile based on an existing FPA
+//
+// Note that the returned pmFPAfile is a view only, so it should not be freed by the caller --- the only
+// reference count is held by the config->files metadata.
+pmFPAfile *pmFPAfileDefineFromFPA (const pmConfig *config, pmFPA *src, int xBin, int yBin, const char *filename);
+
+/// Same as pmFPAfileDefineFromFPA, except it uses an FPA file instead of an FPA
+///
+/// The new pmFPAfile is inserted into the config->files metadata, freed and returned; so that the user does
+/// not have to (and should not!) free the result.
+pmFPAfile *pmFPAfileDefineFromFile(const pmConfig *config, // Configuration
+                                   pmFPAfile *src, // Source file for this file
+                                   int xBin, int yBin, // Binning for this file
+                                   const char *filename // Name of file rule
+    );
+
+
+// create a new output pmFPAfile based on an existing FPA
+// only valid for pmFPAfile->mode == WRITE (or internal?)
+//
+// Note that the returned pmFPAfile is a view only, so it should not be freed by the caller --- the only
+// reference count is held by the config->files metadata.
+pmFPAfile *pmFPAfileDefineNewCamera (const pmConfig *config, const char *filename);
+
+/// Create a new output pmFPAfile for a skycell of the default camera
+///
+/// The new pmFPAfile is inserted into the config->files metadata, freed and returned; so that the user does
+/// not have to (and should not!) free the result.
+pmFPAfile *pmFPAfileDefineSkycell(const pmConfig *config, ///< Configuration data
+                                  pmFPA *fpa, ///< FPA to which to bind
+                                  const char *filename ///< Output (root) filename
+    );
+
+
+/// Create a new output pmFPAfile based upon a chip mosaic of an existing FPA
+///
+/// The new pmFPAfile is inserted into the config->files metadata, freed and returned; so that the user does
+/// not have to (and should not!) free the result.
+pmFPAfile *pmFPAfileDefineChipMosaic(const pmConfig *config, ///< Configuration data
+                                     pmFPA *src, ///< Source FPA
+                                     const char *filename ///< Output (root) filename
+                                    );
+
+/// Create a new output pmFPAfile based upon an FPA mosaic of an existing FPA
+///
+/// The new pmFPAfile is inserted into the config->files metadata, freed and returned; so that the user does
+/// not have to (and should not!) free the result.
+pmFPAfile *pmFPAfileDefineFPAMosaic(const pmConfig *config, ///< Configuration data
+                                    pmFPA *src, ///< Source FPA
+                                    const char *filename ///< Output (root) filename
+                                   );
+
+// create a file with the given name, assign it type "INTERNAL", and supply it with an image
+// of the requested dimensions. (image only, mask and weight are ignored)
+///
+/// The new pmFPAfile is inserted into the config->files metadata, freed and returned; so that the user does
+/// not have to (and should not!) free the result.
+pmReadout *pmFPAfileDefineInternal(psMetadata *files, const char *name, int Nx, int Ny, int type);
+
+// delete the INTERNAL file of the given name (if it exists)
+bool pmFPAfileDropInternal(psMetadata *files, const char *name);
+
+// look for the given argname on the argument list.  find the give filename from the file rules
+//
+// Note that the returned pmFPAfile is a view only, so it should not be freed by the caller --- the only
+// reference count is held by the config->files metadata.
+pmFPAfile *pmFPAfileDefineSingleFromArgs(bool *found, pmConfig *config, const char *filename,
+                                         const char *argname, int entry);
+
+// Select or construct the requested readout.  If the named entry does not exist, generate it based
+// on the specified fpa and binning.  We have 4 possibilities: (INTERNAL or I/O file) and (exists or
+// not).  This call is used after all user-requested pmFPAfiles have been generated.  A missing
+// pmFPAfile is being used internally.
+pmReadout *pmFPAGenerateReadout(const pmConfig *config, // configuration information
+                                const pmFPAview *view, // select background for this entry
+                                const char *name, // name of internal/external file
+                                const pmFPA *fpa, // use this fpa to generate
+                                const psImageBinning *binning);
+
+/// @}
+# endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAfileFitsIO.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAfileFitsIO.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAfileFitsIO.c	(revision 22157)
@@ -0,0 +1,535 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmConfigMask.h"
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPARead.h"
+#include "pmFPAWrite.h"
+#include "pmFPAMaskWeight.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+#include "pmFPAfileFitsIO.h"
+#include "pmFPACopy.h"
+#include "pmFPAConstruct.h"
+#include "pmDark.h"
+
+pmFPA *pmFPAfileSuitableFPA(const pmFPAfile *file, const pmFPAview *view, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(file, NULL);
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+
+    if (!file->format) {                // Working with the same output format as input format
+        return psMemIncrRefCounter(file->fpa);
+    }
+
+    // May need to change format
+    pmFPALevel level = pmFPAviewLevel(view); // Level for the view
+    if (level == PM_FPA_LEVEL_NONE || level == PM_FPA_LEVEL_READOUT) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "This function shouldn't be called at the readout (or unknown) level.");
+        return NULL;
+    }
+
+    // Does the HDU of interest conform to the desired format?
+    pmHDU *hdu = pmFPAviewThisHDU(view, file->fpa); // The HDU of interest
+    if (hdu && hdu->format == file->format) {
+        // No work required
+        return psMemIncrRefCounter(file->fpa);
+    }
+
+    // Otherwise, we have to generate a copy with the correct format
+
+    pmFPAview *phuView = pmFPAviewAlloc(0); // View corresponding to the PHU
+    *phuView = *view;               // Copy contents
+    pmFPALevel phuLevel = pmFPAPHULevel(file->format); // Level for the PHU
+    switch (phuLevel) {
+      case PM_FPA_LEVEL_FPA:
+        phuView->chip = -1;
+        // Flow through
+      case PM_FPA_LEVEL_CHIP:
+        phuView->cell = -1;
+        // Flow through
+      case PM_FPA_LEVEL_CELL:
+        phuView->readout = -1;
+        break;
+      case PM_FPA_LEVEL_READOUT:
+      case PM_FPA_LEVEL_NONE:
+      default:
+        psAbort("Should never get here: bad phu level.\n");
+    }
+
+    pmFPA *nameSource = file->src; // Source of FPA.OBS
+    if (!nameSource) {
+        nameSource = file->fpa;
+    }
+    bool mdok;                  // Status of MD lookup
+    const char *fpaObs = psMetadataLookupStr(&mdok, nameSource->concepts, "FPA.OBS"); // Observation id
+
+    pmFPA *copy = pmFPAConstruct(file->camera, file->cameraName);  // FPA to return
+    if (!pmFPAAddSourceFromView(copy, fpaObs, phuView, file->format)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to insert HDU into FPA for writing.\n");
+        psFree(copy);
+        psFree(phuView);
+        return NULL;
+    }
+    psFree(phuView);
+
+    switch (level) {
+      case PM_FPA_LEVEL_FPA:
+        if (!pmFPACopy(copy, file->fpa)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to copy FPA for format conversion.\n");
+            return NULL;
+        }
+        return copy;
+      case PM_FPA_LEVEL_CHIP: {
+          pmChip *chip = pmFPAviewThisChip(view, copy); // Chip of interest
+          pmChip *srcChip = pmFPAviewThisChip(view, file->fpa); // Source chip
+          if (!pmChipCopy(chip, srcChip)) {
+              psError(PS_ERR_UNKNOWN, false, "Unable to copy chip for format conversion.\n");
+              return false;
+          }
+          return copy;
+      }
+      case PM_FPA_LEVEL_CELL: {
+          pmCell *cell = pmFPAviewThisCell(view, copy); // Cell of interest
+          pmCell *srcCell = pmFPAviewThisCell(view, file->fpa); // Source cell
+          if (!pmCellCopy(cell, srcCell)) {
+              psError(PS_ERR_UNKNOWN, false, "Unable to copy cell for format conversion.\n");
+              return false;
+          }
+          return copy;
+      }
+      case PM_FPA_LEVEL_READOUT:
+      case PM_FPA_LEVEL_NONE:
+      default:
+        psAbort("Should never get here: bad phu level.\n");
+    }
+
+    return NULL;
+
+}
+
+// given an already-opened fits file, read the table corresponding to the specified view
+bool pmFPAviewReadFitsTable(const pmFPAview *view, pmFPAfile *file, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    pmFPA *fpa = file->fpa;             // FPA of interest
+    psFits *fits = file->fits;          // FITS file
+
+    if (view->chip == -1) {
+        return pmFPAReadTable(fpa, fits, name) > 0;
+    }
+
+    if (view->cell == -1) {
+        pmChip *chip = pmFPAviewThisChip(view, fpa); // Chip of interest
+        return pmChipReadTable(chip, fits, name) > 0;
+    }
+
+    pmCell *cell = pmFPAviewThisCell(view, fpa); // Cell of interest
+    return pmCellReadTable(cell, fits, name) > 0;
+}
+
+// given an already-opened fits file, write the table corresponding to the specified view
+bool pmFPAviewWriteFitsTable(const pmFPAview *view, pmFPAfile *file, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    pmFPA *fpa = file->fpa;             // FPA of interest
+    psFits *fits = file->fits;          // FITS file
+
+    if (view->chip == -1) {
+        return pmFPAWriteTable(fits, fpa, name) > 0;
+    }
+
+    if (view->cell == -1) {
+        pmChip *chip = pmFPAviewThisChip(view, fpa); // Chip of interest
+        return pmChipWriteTable(fits, chip, name) > 0;
+    }
+
+    pmCell *cell = pmFPAviewThisCell(view, fpa); // Cell of interest
+    return pmCellWriteTable(fits, cell, name) > 0;
+}
+
+
+// given an already-opened fits file, read the components corresponding to the specified view
+static bool fpaViewReadFitsImage(const pmFPAview *view, // FPA view, specifying the level of interest
+                                 pmFPAfile *file, // FPA file of interest
+                                 pmConfig *config, // Configuration
+                                 bool (*fpaReadFunc)(pmFPA*, psFits*, pmConfig*), // Function to read FPA
+                                 bool (*chipReadFunc)(pmChip*, psFits*, pmConfig*), // Function to read chip
+                                 bool (*cellReadFunc)(pmCell*, psFits*, pmConfig*) // Function to read cell
+                                )
+{
+    assert(view);
+    assert(file);
+
+    pmFPA *fpa = file->fpa;             // FPA of interest
+    psFits *fits = file->fits;          // FITS file from which to read
+
+    if (view->chip == -1) {
+        return fpaReadFunc(fpa, fits, config);
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        psError(PS_ERR_IO, true, "Requested chip == %d >= fpa->chips->n == %ld", view->chip, fpa->chips->n);
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip]; // Chip of interest
+
+    if (view->cell == -1) {
+        return chipReadFunc(chip, fits, config);
+    }
+
+    if (view->cell >= chip->cells->n) {
+        psError(PS_ERR_IO, true, "Requested cell == %d >= chip->cells->n == %ld", view->cell, chip->cells->n);
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell]; // Cell of interest
+
+    if (view->readout == -1) {
+        return cellReadFunc(cell, fits, config);
+    }
+    psError(PS_ERR_UNKNOWN, true, "Bad view: %d,%d", view->chip, view->cell);
+    return false;
+
+    // XXX pmReadoutRead, pmReadoutReadSegement disabled for now
+    #if 0
+
+    if (view->readout >= cell->readouts->n) {
+        psError(PS_ERR_IO, true, "Requested readout == %d >= cell->readouts->n == %d",
+                view->readout, cell->readouts->n);
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[view->readout];
+
+    if (view->nRows == 0) {
+        pmReadoutRead (readout, fits, config);
+    } else {
+        pmReadoutReadSegment (readout, fits, view->nRows, view->iRows, NULL, NULL);
+    }
+    return true;
+    #endif
+}
+
+
+bool pmFPAviewReadFitsImage(const pmFPAview *view, pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    return fpaViewReadFitsImage(view, file, config, pmFPARead, pmChipRead, pmCellRead);
+}
+
+bool pmFPAviewReadFitsMask(const pmFPAview *view, pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    return fpaViewReadFitsImage(view, file, config, pmFPAReadMask, pmChipReadMask, pmCellReadMask);
+}
+
+bool pmFPAviewReadFitsWeight(const pmFPAview *view, pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    return fpaViewReadFitsImage(view, file, config, pmFPAReadWeight, pmChipReadWeight, pmCellReadWeight);
+}
+
+bool pmFPAviewReadFitsDark(const pmFPAview *view, pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    return fpaViewReadFitsImage(view, file, config, pmFPAReadDark, pmChipReadDark, pmCellReadDark);
+}
+
+bool pmFPAviewReadFitsHeaderSet(const pmFPAview *view, pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    return fpaViewReadFitsImage(view, file, config, pmFPAReadHeaderSet, pmChipReadHeaderSet, pmCellReadHeaderSet);
+}
+
+// given an already-opened fits file, write the components corresponding
+// to the specified view. when the file was opened, pmFPA/Chip/CellWrite was
+// called on it with blank=true to write the (possible) blank PHU
+// do NOT call the functions below with blank=true or they will write
+// out data in an inconsistent fashion
+// the calls below should recurse down the element to write out all components.
+static bool fpaViewWriteFitsImage(const pmFPAview *view, // FPA view, specifying the level of interest
+                                  pmFPAfile *file, // FPA file of interest
+                                  pmConfig *config, // Configuration
+                                  bool (*fpaWriteFunc)(pmFPA*, psFits*, pmConfig*, bool, bool), // Func FPA
+                                  bool (*chipWriteFunc)(pmChip*, psFits*, pmConfig*, bool, bool),// Func chip
+                                  bool (*cellWriteFunc)(pmCell*, psFits*, pmConfig*, bool) // Func cell
+                                 )
+{
+    assert(view);
+    assert(file);
+
+    psFits *fits = file->fits;          // FITS file
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    pmFPA *fpa = pmFPAfileSuitableFPA(file, view, config); // FPA to write
+
+    switch (pmFPAviewLevel(view)) {
+    case PM_FPA_LEVEL_FPA: {
+            bool success = fpaWriteFunc(fpa, fits, config, false, true);
+            psFree(fpa);
+            return success;
+        }
+    case PM_FPA_LEVEL_CHIP: {
+            pmChip *chip = pmFPAviewThisChip(view, fpa); // Chip of interest
+            bool success = chipWriteFunc(chip, fits, config, false, true);
+            psFree(fpa);
+            return success;
+        }
+    case PM_FPA_LEVEL_CELL: {
+            pmCell *cell = pmFPAviewThisCell(view, fpa); // Cell of interest
+            bool success = cellWriteFunc(cell, fits, config, false);
+            psFree(fpa);
+            return success;
+        }
+    case PM_FPA_LEVEL_READOUT:
+        #if 0 // XXX disable readout write for now
+
+        {
+            pmReadout *readout = pmFPAviewThisReadout(view, file->fpa); // Readout of interest
+            if (changeFormat)
+        {
+            // No copy function defined for readouts!
+            psError(PS_ERR_UNKNOWN, false, "Unable to copy readout for format conversion on write.\n");
+                return false;
+            }
+            if (view->nRows == 0)
+        {
+            return pmReadoutWrite(readout, fits, NULL, NULL);
+            } else
+            {
+                return pmReadoutWriteSegment(readout, fits, view->nRows, view->iRows, NULL, NULL);
+            }
+        }
+        #endif
+    case PM_FPA_LEVEL_NONE:
+    default:
+        psAbort("Should never reach here: invalid file level.");
+    }
+
+    return false;
+}
+
+bool pmFPAviewWriteFitsImage(const pmFPAview *view, pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    return fpaViewWriteFitsImage(view, file, config, pmFPAWrite, pmChipWrite, pmCellWrite);
+}
+
+bool pmFPAviewWriteFitsMask(const pmFPAview *view, pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    return fpaViewWriteFitsImage(view, file, config, pmFPAWriteMask, pmChipWriteMask, pmCellWriteMask);
+}
+
+bool pmFPAviewWriteFitsWeight(const pmFPAview *view, pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    return fpaViewWriteFitsImage(view, file, config, pmFPAWriteWeight, pmChipWriteWeight, pmCellWriteWeight);
+}
+
+bool pmFPAviewWriteFitsDark(const pmFPAview *view, pmFPAfile *file, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    return fpaViewWriteFitsImage(view, file, config, pmFPAWriteDark, pmChipWriteDark, pmCellWriteDark);
+}
+
+// given an already-opened fits file, read the components corresponding
+// to the specified view
+bool pmFPAviewFreeData(const pmFPAview *view, pmFPAfile *file)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        psTrace ("pmFPAfile", 5, "freeing fpa for %s\n", file->filename);
+        pmFPAFreeData (fpa);
+        // XXX drop me: file->fpa = NULL;
+        return true;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        psError(PS_ERR_IO, true, "Requested chip == %d >= fpa->chips->n == %ld", view->chip, fpa->chips->n);
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        psTrace ("pmFPAfile", 5, "freeing chip %d for %s\n", view->chip, file->filename);
+        pmChipFreeData (chip);
+        return true;
+    }
+
+    if (view->cell >= chip->cells->n) {
+        psError(PS_ERR_IO, true, "Requested cell == %d >= chip->cells->n == %ld", view->cell, chip->cells->n);
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        psTrace ("pmFPAfile", 5, "freeing cell %d for %s\n", view->cell, file->filename);
+        pmCellFreeData (cell);
+        return true;
+    }
+    psError(PS_ERR_UNKNOWN, true, "Returning false");
+    return false;
+
+    // XXX pmReadoutRead, pmReadoutReadSegement disabled for now
+    #if 0
+
+    if (view->readout >= cell->readouts->n) {
+        psError(PS_ERR_IO, true, "Requested readout == %d >= cell->readouts->n == %d",
+                view->readout, cell->readouts->n);
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[view->readout];
+
+    if (view->nRows == 0) {
+        pmReadoutRead (readout, fits, NULL);
+    } else {
+        pmReadoutReadSegment (readout, fits, view->nRows, view->iRows, NULL, NULL);
+    }
+    return true;
+    #endif
+}
+
+#if 0
+// Shouldn't need this --- when we want to free fringe data, we want to free the whole level, not just the
+// table.
+
+// Free the table within a cell
+static void freeTable(pmCell *cell,     // Cell of interest
+                      const char *name  // Name of table to free
+                     )
+{
+    assert(cell);
+    assert(name && strlen(name) > 0);
+
+    psString headerName = NULL;         // Name of header
+    psStringAppend(&headerName, "%s.HEADER", name);
+    if (psMetadataLookup(cell->analysis, headerName)) {
+        psMetadataRemoveKey(cell->analysis, headerName);
+    }
+    psFree(headerName);
+
+    if (psMetadataLookup(cell->analysis, name)) {
+        psMetadataRemoveKey(cell->analysis, name);
+    }
+
+    return;
+}
+
+// given a file, free the components corresponding to the specified view
+bool pmFPAviewFreeFitsTable (const pmFPAview *view, pmFPAfile *file, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        psArray *chips = fpa->chips;    // Array of chips
+        for (int i = 0; i < chips->n; i++) {
+            pmChip *chip = chips->data[i]; // Chip of interest
+            psArray *cells = chip->cells; // Array of cells
+            for (int j = 0; j < cells->n; j++) {
+                pmCell *cell = cells->data[j]; // Cell of interest
+                freeTable(cell, name);
+            }
+        }
+        return true;
+    }
+
+    if (view->cell == -1) {
+        pmChip *chip = pmFPAviewThisChip(view, fpa); // Chip of interest
+        psArray *cells = chip->cells;   // Array of cells
+        for (int i = 0; i < cells->n; i++) {
+            pmCell *cell = cells->data[i]; // Cell of interest
+            freeTable(cell, name);
+        }
+        return true;
+    }
+
+    pmCell *cell = pmFPAviewThisCell(view, fpa); // Cell of interest
+    freeTable(cell, name);
+    return true;
+}
+
+#endif
+
+bool pmFPAviewFitsWritePHU (const pmFPAview *view, pmFPAfile *file, pmConfig *config) {
+
+    bool status = false;
+
+    if (file->mode != PM_FPA_MODE_WRITE) return true;
+    if (file->wrote_phu) return true;
+
+    // select or generate the desired fpa in the correct output format
+    pmFPA *fpa = pmFPAfileSuitableFPA(file, view, config);
+    pmHDU *phu = pmFPAviewThisHDU(view, fpa);
+    if (!phu || !phu->blankPHU) {
+        // No PHU to write!
+        psFree(fpa);
+        return true;
+    }
+
+    // whenever we write out a mask image, we should define the bits which represent mask concepts
+    if (file->type == PM_FPA_FILE_MASK) {
+	assert (phu->header);
+	if (!pmConfigMaskWriteHeader (config, phu->header)) {
+	    psError(PS_ERR_UNKNOWN, false, "failed to set the bitmask names in the PHU header for Image %s (%s)\n", file->filename, file->name);
+	    return false;
+	}
+    }
+
+    switch (file->fileLevel) {
+      case PM_FPA_LEVEL_FPA:
+        status = pmFPAWrite(fpa, file->fits, config, true, false);
+        break;
+      case PM_FPA_LEVEL_CHIP: {
+          pmChip *chip = pmFPAviewThisChip(view, fpa);
+          status = pmChipWrite(chip, file->fits, config, true, false);
+          break;
+      }
+      case PM_FPA_LEVEL_CELL: {
+          pmCell *cell = pmFPAviewThisCell(view, fpa);
+          status = pmCellWrite(cell, file->fits, config, true);
+          break;
+      }
+      default:
+        psAbort("fileLevel not correctly set");
+        break;
+    }
+
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to write PHU for Image %s (%s)\n", file->filename, file->name);
+        return false;
+    }
+
+    psFree(fpa);
+    file->wrote_phu = true;
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAfileFitsIO.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAfileFitsIO.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAfileFitsIO.h	(revision 22157)
@@ -0,0 +1,104 @@
+/* @file  pmFPAview.h
+ * @brief Tools to manipulate the FPA structure elements.
+ *
+ * @author EAM, IfA
+ * @author PAP, IfA
+ *
+ * @version $Revision: 1.14 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-06-30 00:53:45 $
+ * Copyright 2004-2005 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_FILE_FITS_IO_H
+#define PM_FPA_FILE_FITS_IO_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+/// Read an image into the current view
+bool pmFPAviewReadFitsImage(const pmFPAview *view, ///< View specifying level of interest
+                            pmFPAfile *file, ///< FPA file into which to read
+                            pmConfig *config
+                           );
+
+/// Read a mask into the current view
+bool pmFPAviewReadFitsMask(const pmFPAview *view, ///< View specifying level of interest
+                           pmFPAfile *file, ///< FPA file into which to read
+                            pmConfig *config
+                          );
+/// Read a weight map into the current view
+bool pmFPAviewReadFitsWeight(const pmFPAview *view,  ///< View specifying level of interest
+                             pmFPAfile *file, ///< FPA file into which to read
+                            pmConfig *config
+                            );
+
+/// Read a dark into the current view
+bool pmFPAviewReadFitsDark(const pmFPAview *view,  ///< View specifying level of interest
+                           pmFPAfile *file, ///< FPA file into which to read
+                            pmConfig *config
+    );
+
+/// Read an image header into the current view
+bool pmFPAviewReadFitsHeaderSet(const pmFPAview *view,  ///< View specifying level of interest
+                                pmFPAfile *file, ///< FPA file into which to read
+                            pmConfig *config
+    );
+
+/// Write the image for the specified view
+bool pmFPAviewWriteFitsImage(const pmFPAview *view, ///< View specifying level of interest
+                             pmFPAfile *file, ///< FPA file to write
+                             pmConfig *config ///< Configuration
+                            );
+
+/// Write the mask for the specified view
+bool pmFPAviewWriteFitsMask(const pmFPAview *view, ///< View specifying level of interest
+                            pmFPAfile *file, ///< FPA file to write
+                            pmConfig *config ///< Configuration
+                           );
+
+/// Write the weight map for the specified view
+bool pmFPAviewWriteFitsWeight(const pmFPAview *view, ///< View specifying level of interest
+                              pmFPAfile *file, ///< FPA file to write
+                              pmConfig *config ///< Configuration
+                             );
+
+/// Write the dark for the specified view
+bool pmFPAviewWriteFitsDark(const pmFPAview *view, ///< View specifying level of interest
+                            pmFPAfile *file, ///< FPA file to write
+                            pmConfig *config ///< Configuration
+    );
+
+/// Write a PHU for a fits image if needed
+bool pmFPAviewFitsWritePHU (const pmFPAview *view, pmFPAfile *file, pmConfig *config);
+
+/// Free the data for the specified view
+bool pmFPAviewFreeData(const pmFPAview *view, ///< View specifying level of interest
+                       pmFPAfile *file  ///< FPA file to free data
+                      );
+
+/// Read a table into the current view
+bool pmFPAviewReadFitsTable(const pmFPAview *view, ///<  View specifying level of interest
+                            pmFPAfile *file, ///< FPA file into which to read
+                            const char *name ///< Name of table
+                           );
+
+/// Write the table for the specified view
+bool pmFPAviewWriteFitsTable(const pmFPAview *view, ///<  View specifying level of interest
+                             pmFPAfile *file, ///< FPA file to write
+                             const char *name ///< Name of table
+                            );
+
+/// Produce a suitable FPA for writing, on the basis of the input FPAfile
+///
+/// A new FPA with a changed format is generated if required (file->format is set and file->camera is equal to
+/// the default, indicating a change in the format without changing the camera --- changes to the camera are
+/// handled using other systems --- see pmFPAfileDefineChipMosaic, pmFPAfileDefineFPAMosaic).  Otherwise the
+/// file->fpa is returned (incremented).
+pmFPA *pmFPAfileSuitableFPA(const pmFPAfile *file,///< File containing the fpa
+                            const pmFPAview *view, ///< View at which to produce the fpa
+                            const pmConfig *config ///< Configuration
+                           );
+
+/// @}
+
+# endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAfileIO.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAfileIO.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAfileIO.c	(revision 22157)
@@ -0,0 +1,1035 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>            /* for strn?casecmp */
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmConfigMask.h"
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAMaskWeight.h"
+#include "pmFPAview.h"
+#include "pmFPAFlags.h"
+#include "pmFPAfile.h"
+#include "pmFPACopy.h"
+#include "pmFPARead.h"
+#include "pmFPAWrite.h"
+#include "pmFPAfileIO.h"
+#include "pmFPAfileFitsIO.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmResiduals.h"
+#include "pmGrowthCurve.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmSourceIO.h"
+#include "pmResiduals.h"
+#include "pmPSF_IO.h"
+#include "pmAstrometryModel.h"
+#include "pmAstrometryRefstars.h"
+#include "pmFPA_JPEG.h"
+#include "pmSourcePlots.h"
+#include "pmFPAConstruct.h"
+#include "pmConcepts.h"
+
+// attempt create, read, write, close, or free pmFPAfiles available in files files are
+// automatically opened before they are read.  In the case of MEF files, the PHU is
+// read when the file is opened and written before the first extension is written.
+bool pmFPAfileIOChecks (pmConfig *config, const pmFPAview *view, pmFPAfilePlace place)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(config->files, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    psMetadata *files = config->files;
+
+    // attempt to perform all create, read, write, close operations
+    psMetadataItem *item = NULL;
+    psMetadataIterator *iter = psMetadataIteratorAlloc (files, PS_LIST_HEAD, NULL);
+    while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
+        pmFPAfile *file = item->data.V;
+
+        switch (place) {
+        case PM_FPA_BEFORE:
+            if (!pmFPAfileRead (file, view, config)) {
+                psError(PS_ERR_IO, false, "failed READ in FPA_BEFORE block for %s", file->name);
+                goto failure;
+            }
+            if (!pmFPAfileCreate(file, view, config)) {
+                psError(PS_ERR_IO, false, "failed CREATE in FPA_BEFORE block for %s", file->name);
+                goto failure;
+            }
+            break;
+        case PM_FPA_AFTER:
+            if (!pmFPAfileWrite (file, view, config)) {
+                psError(PS_ERR_IO, false, "failed WRITE in FPA_AFTER block for %s", file->name);
+                goto failure;
+            }
+            if (!pmFPAfileClose(file, view)) {
+                psError(PS_ERR_IO, false, "failed CLOSE in FPA_AFTER block for %s", file->name);
+                goto failure;
+            }
+            break;
+        default:
+            psAbort("You can't get here");
+        }
+    }
+
+    // attempt to free data that is no longer needed
+    psMetadataIteratorSet (iter, PS_LIST_HEAD);
+    while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
+        pmFPAfile *file = item->data.V;
+
+        switch (place) {
+        case PM_FPA_BEFORE:
+            break;
+        case PM_FPA_AFTER:
+            if (!pmFPAfileFreeData(file, view)) {
+                if (!psMetadataRemoveKey(files, file->name)) {
+                    psError(PS_ERR_IO, false, "failed to remove %s in FPA_AFTER block", file->name);
+                    goto failure;
+                }
+            }
+            break;
+        default:
+            psAbort("You can't get here");
+        }
+    }
+    psFree (iter);
+    return true;
+
+failure:
+    psFree (iter);
+    return false;
+}
+
+// read the file, if necessary and possible
+bool pmFPAfileRead(pmFPAfile *file, const pmFPAview *view, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    // an internal file should not be sent here (should not be left on config->files)
+    PS_ASSERT(file->mode != PM_FPA_MODE_INTERNAL, false);
+
+    // skip the following states
+    if (file->state & PM_FPA_STATE_INACTIVE) {
+        psTrace("psModules.camera", 6, "skip read for %s, file is inactive", file->name);
+        return true;
+    }
+    if (file->mode != PM_FPA_MODE_READ) {
+        psTrace("psModules.camera", 6, "skip read for %s, mode is not READ", file->name);
+        return true;
+    }
+
+    // get the current level
+    pmFPALevel level = pmFPAviewLevel (view);
+
+    // do we need to read this file? defer until we read the correct level
+    if (level != file->dataLevel) {
+        psTrace("psModules.camera", 6, "skip reading of %s at this level %s: dataLevel is %s",
+                file->name, pmFPALevelToName(level), pmFPALevelToName(file->dataLevel));
+        return true;
+    }
+
+    // do we need to open this file?
+    if (level >= file->fileLevel) {
+        // we are allowed to open a file at a level which is not the fileLevel, but we need to
+        // supply a view at the fileLevel for the file lookup functions below
+        pmFPAview *fileView = pmFPAviewForLevel (file->fileLevel, view);
+        if (!pmFPAfileOpen (file, fileView, config)) {
+            psError(PS_ERR_IO, false, "failed to open file %s (%s)", file->filename, file->name);
+            psFree (fileView);
+            return false;
+        }
+        psFree (fileView);
+    }
+
+    // We need to read it --- double-check it's open!
+    if (file->state == PM_FPA_STATE_CLOSED) {
+        psError(PS_ERR_IO, false, "failed to open file %s when attempting to read", file->name);
+        return false;
+    }
+
+    // select a reading method
+    bool status = true;
+    switch (file->type) {
+      case PM_FPA_FILE_IMAGE:
+        status = pmFPAviewReadFitsImage(view, file, config);
+        break;
+      case PM_FPA_FILE_MASK:
+        status = pmFPAviewReadFitsMask(view, file, config);
+        break;
+      case PM_FPA_FILE_WEIGHT:
+        status = pmFPAviewReadFitsWeight(view, file, config);
+        break;
+      case PM_FPA_FILE_HEADER:
+        status = pmFPAviewReadFitsHeaderSet(view, file, config);
+        break;
+      case PM_FPA_FILE_DARK:
+        status = pmFPAviewReadFitsDark(view, file, config);
+        break;
+      case PM_FPA_FILE_FRINGE:
+        status = pmFPAviewReadFitsImage(view, file, config);
+        if (status) {
+            if (!pmFPAviewReadFitsTable(view, file, "FRINGE")) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to read fringe data from %s.\n", file->filename);
+                return false;
+            }
+        }
+        break;
+      case PM_FPA_FILE_SX:
+      case PM_FPA_FILE_RAW:
+      case PM_FPA_FILE_OBJ:
+      case PM_FPA_FILE_CMP:
+      case PM_FPA_FILE_CMF:
+        status = pmFPAviewReadObjects (view, file, config);
+        break;
+      case PM_FPA_FILE_PSF:
+        status = pmPSFmodelReadForView (view, file, config);
+        break;
+      case PM_FPA_FILE_ASTROM_MODEL:
+        status = pmAstromModelReadForView (view, file, config);
+        break;
+      case PM_FPA_FILE_ASTROM_REFSTARS:
+      case PM_FPA_FILE_JPEG:
+      case PM_FPA_FILE_KAPA:
+        break;
+      default:
+        psError(PS_ERR_IO, true, "warning: type mismatch; saw type %d (%s)", file->type, file->name);
+        return false;
+    }
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to read %s (%s)\n", file->filename, file->name);
+        return false;
+    }
+    psTrace ("psModules.camera", 5, "read %s (%s) (%d:%d:%d)\n", file->filename, file->name, view->chip, view->cell, view->readout);
+    return true;
+}
+
+// create the data elements (headers, images) appropriate for this view
+bool pmFPAfileCreate (pmFPAfile *file, const pmFPAview *view, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    // these are not error conditions; these are state tests
+    if (file->state & PM_FPA_STATE_INACTIVE) {
+        psTrace("psModules.camera", 6, "skip create for inactive file %s", file->name);
+        return true;
+    }
+    if (file->mode != PM_FPA_MODE_WRITE) {
+        psTrace("psModules.camera", 6, "skip create for non-write file %s", file->name);
+        return true;
+    }
+
+    // an internal file should not be returned to here
+    PS_ASSERT(file->mode != PM_FPA_MODE_INTERNAL, false);
+
+    // get the current level
+    pmFPALevel level = pmFPAviewLevel (view);
+
+    // don't create the file if the src (FPA) is not defined
+    if (file->src == NULL) {
+        psTrace("psModules.camera", 6, "skip create for FPA without src FPA for %s", file->name);
+        return true;
+    }
+
+    // do we need to write this file?
+    if (level != file->fileLevel || file->mosaicLevel != PM_FPA_LEVEL_NONE) {
+        psTrace("psModules.camera", 6, "skip creation of %s at this level %s: fileLevel is %s",
+                file->name, pmFPALevelToName(level), pmFPALevelToName(file->fileLevel));
+        return true;
+    }
+
+    switch (file->type) {
+      case PM_FPA_FILE_IMAGE:
+      case PM_FPA_FILE_MASK:
+      case PM_FPA_FILE_WEIGHT:
+      case PM_FPA_FILE_FRINGE:
+      case PM_FPA_FILE_DARK: {
+            // create FPA structure component based on view
+            psMetadata *format = file->format; // Camera format configuration
+            if (!format) {
+                format = config->format;
+            }
+
+            pmFPA *nameSource = file->src; // Source of FPA.OBS
+            if (!nameSource) {
+                nameSource = file->fpa;
+            }
+            bool mdok;                  // Status of MD lookup
+            const char *fpaObs = psMetadataLookupStr(&mdok, nameSource->concepts, "FPA.OBS"); // Obs. id
+
+            pmFPAAddSourceFromView(file->fpa, fpaObs, view, format);
+            psTrace ("psModules.camera", 5, "created fpa data elements for %s (%s) (%d:%d:%d)\n",
+                     file->name, file->name, view->chip, view->cell, view->readout);
+            break;
+    }
+    case PM_FPA_FILE_HEADER:
+      psAbort ("Create not defined for HEADER");
+      break;
+    case PM_FPA_FILE_SX:
+    case PM_FPA_FILE_RAW:
+    case PM_FPA_FILE_OBJ:
+    case PM_FPA_FILE_CMP:
+    case PM_FPA_FILE_CMF:
+    case PM_FPA_FILE_PSF:
+    case PM_FPA_FILE_ASTROM_MODEL:
+    case PM_FPA_FILE_ASTROM_REFSTARS:
+    case PM_FPA_FILE_JPEG:
+    case PM_FPA_FILE_KAPA:
+        break;
+
+    default:
+        psError(PS_ERR_IO, true, "Unsupported type for %s: %d", file->name, file->type);
+        return false;
+    }
+    return true;
+}
+
+bool pmFPAfileWrite(pmFPAfile *file, const pmFPAview *view, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    if (file->state & PM_FPA_STATE_INACTIVE) {
+        psTrace("psModules.camera", 6, "skip write for %s, files is inactive", file->name);
+        return true;
+    }
+
+    if (file->mode != PM_FPA_MODE_WRITE) {
+        psTrace("psModules.camera", 6, "skip write for %s, mode is not WRITE", file->name);
+        return true;
+    }
+
+    // an internal file should not be returned to here
+    if (file->mode == PM_FPA_MODE_INTERNAL) {
+        psError(PS_ERR_IO, true, "File is mode PM_FPA_MODE_INTERNAL");
+        return false;
+    }
+
+    if (!file->save) {
+        psTrace("psModules.camera", 6, "skip write for %s, save is FALSE", file->name);
+        return true;
+    }
+
+    // get the current level
+    pmFPALevel level = pmFPAviewLevel (view);
+
+    // the effective dataLevel (for mosaics, we have preserved the original dataLevel)
+    pmFPALevel dataLevel = file->dataLevel;
+    if (file->mosaicLevel != PM_FPA_LEVEL_NONE && file->mosaicLevel < dataLevel) {
+        dataLevel = file->mosaicLevel;
+    }
+
+    // do we need to write this file?
+    if (level != dataLevel) {
+        psTrace("psModules.camera", 6, "skip writing of %s at this level %s: dataLevel is %s",
+                file->name, pmFPALevelToName(level), pmFPALevelToName(dataLevel));
+        return true;
+    }
+
+    // do we have data to write at this level?
+    if (!pmFPAviewCheckDataStatus (file->fpa, view)) {
+        psTrace("psModules.camera", 6, "skip write for %s, no data for this entry", file->name);
+        return true;
+    }
+
+    // note that for CMF and PSF, the test above is not sufficient to determine if there
+    // is actually any data to write out.  this is because these types of output files
+    // have their data stored on the readout->analysis metadata structure of another
+    // (existing) fpa
+    if (file->type == PM_FPA_FILE_CMF) {
+        if (!pmFPAviewCheckDataStatusForSources (view, file)) {
+        psTrace("psModules.camera", 6, "skip write for %s, no data for this entry", file->name);
+        return true;
+      }
+    }
+    if (file->type == PM_FPA_FILE_PSF) {
+      if (!pmPSFmodelCheckDataStatusForView (view, file)) {
+        psTrace("psModules.camera", 6, "skip write for %s, no data for this entry", file->name);
+        return true;
+      }
+    }
+    if (file->type == PM_FPA_FILE_ASTROM_MODEL) {
+      if (!pmAstromModelCheckDataStatusForView (view, file)) {
+        psTrace("psModules.camera", 6, "skip write for %s, no data for this entry", file->name);
+        return true;
+      }
+    }
+    if (file->type == PM_FPA_FILE_ASTROM_REFSTARS) {
+      if (!pmAstromRefstarsCheckDataStatusForView (view, file)) {
+        psTrace("psModules.camera", 6, "skip write for %s, no data for this entry", file->name);
+        return true;
+      }
+    }
+
+    // open the file if not yet opened
+    // XXX do we need to test mosaicLevel?
+    if (level >= file->fileLevel) {
+        // we are allowed to open a file at a level which is not the fileLevel, but
+        // we need to supply view at the fileLevel for the file lookup functions below
+        pmFPAview *fileView = pmFPAviewForLevel (file->fileLevel, view);
+        if (!pmFPAfileOpen (file, fileView, config)) {
+            psError(PS_ERR_IO, false, "failed to open %s (%s)", file->filename, file->name);
+            psFree (fileView);
+            return false;
+        }
+
+        // do we need to write out a PHU?
+        if (!pmFPAfileWritePHU(file, fileView, config)) {
+            psError(PS_ERR_IO, false, "failed to write phu for %s (%s)", file->filename, file->name);
+            return false;
+        }
+
+        psFree (fileView);
+    }
+
+    if (file->compression) {
+        psTrace("psModules.camera", 7, "Setting compression for %s (%s)\n", file->filename, file->name);
+        if (!psFitsCompressionApply(file->fits, file->compression)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to set compression options for %s (%s) (%d:%d:%d)\n",
+                    file->filename, file->name, view->chip, view->cell, view->readout);
+            return false;
+        }
+    }
+
+    // check if the file is a FITS file (or uses the fits header)
+    bool fitsType = false;
+    fitsType |= (file->type == PM_FPA_FILE_IMAGE);
+    fitsType |= (file->type == PM_FPA_FILE_MASK);
+    fitsType |= (file->type == PM_FPA_FILE_WEIGHT);
+    fitsType |= (file->type == PM_FPA_FILE_HEADER);
+    fitsType |= (file->type == PM_FPA_FILE_FRINGE);
+    fitsType |= (file->type == PM_FPA_FILE_DARK);
+    fitsType |= (file->type == PM_FPA_FILE_CMP);
+    fitsType |= (file->type == PM_FPA_FILE_CMF);
+    fitsType |= (file->type == PM_FPA_FILE_PSF);
+    fitsType |= (file->type == PM_FPA_FILE_ASTROM_MODEL);
+    fitsType |= (file->type == PM_FPA_FILE_ASTROM_REFSTARS);
+
+    // Ensure headers and all are updated
+    // This is here so that the individual write functions (e.g., images, PSFs, sources, etc) don't have to
+    // take care of all this themselves (because they generally don't).
+    if (fitsType) {
+        pmHDU *hdu = pmFPAviewThisHDU(view, file->fpa);
+        if (hdu) {
+            if (!hdu->header) {
+                hdu->header = psMetadataAlloc();
+            }
+            pmConfigConformHeader(hdu->header, file->format);
+
+	    // whenever we write out a mask image, we should define the bits which represent mask concepts
+	    if (file->type == PM_FPA_FILE_MASK) {
+		assert (hdu->header);
+		if (!pmConfigMaskWriteHeader (config, hdu->header)) {
+		    psError(PS_ERR_UNKNOWN, false, "failed to set the bitmask names in the PHU header for Image %s (%s)\n", file->filename, file->name);
+		    return false;
+		}
+	    }
+        }
+
+        pmFPA *fpa = file->fpa;         // FPA of interest
+        pmChip *chip = pmFPAviewThisChip(view, file->fpa); // Chip of interest, or NULL
+        pmCell *cell = pmFPAviewThisCell(view, file->fpa); // Cell of interest, or NULL
+        if (!pmFPAUpdateNames(fpa, chip, cell)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to update names in header.");
+            return false;
+        }
+
+        pmConceptSource sources = PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_CELLS |
+            PM_CONCEPT_SOURCE_DEFAULTS | PM_CONCEPT_SOURCE_DATABASE; // Concept sources to write
+        if (cell) {
+            if (!pmConceptsWriteCell(cell, sources, true, config)) {
+                psError(PS_ERR_IO, false, "Unable to write concepts for cell.\n");
+                return false;
+            }
+        } else if (chip) {
+            if (!pmConceptsWriteChip(chip, sources, true, true, config)) {
+                psError(PS_ERR_IO, false, "Unable to write concepts for chip.\n");
+                return false;
+            }
+        } else if (!pmConceptsWriteFPA(fpa, sources, true, config)) {
+            psError(PS_ERR_IO, false, "Unable to write concepts for FPA.\n");
+            return false;
+        }
+    }
+
+    // select a writing method
+    bool status = true;
+    switch (file->type) {
+      case PM_FPA_FILE_IMAGE:
+        status = pmFPAviewWriteFitsImage(view, file, config);
+        break;
+      case PM_FPA_FILE_MASK:
+        status = pmFPAviewWriteFitsMask(view, file, config);
+        break;
+      case PM_FPA_FILE_WEIGHT:
+        status = pmFPAviewWriteFitsWeight(view, file, config);
+        break;
+      case PM_FPA_FILE_HEADER:
+        psAbort ("no HEADER write functions defined");
+        break;
+      case PM_FPA_FILE_DARK:
+        status = pmFPAviewWriteFitsDark(view, file, config);
+        break;
+      case PM_FPA_FILE_FRINGE:
+        status = pmFPAviewWriteFitsImage (view, file, config);
+        if (status) {
+            if (!pmFPAviewWriteFitsTable(view, file, "FRINGE")) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to write fringe data from %s.\n", file->filename);
+                return false;
+            }
+        }
+        break;
+      case PM_FPA_FILE_SX:
+      case PM_FPA_FILE_RAW:
+      case PM_FPA_FILE_OBJ:
+      case PM_FPA_FILE_CMP:
+      case PM_FPA_FILE_CMF:
+        status = pmFPAviewWriteObjects (view, file, config);
+        break;
+
+      case PM_FPA_FILE_PSF:
+        status = pmPSFmodelWriteForView (view, file, config);
+        break;
+
+      case PM_FPA_FILE_ASTROM_MODEL:
+        status = pmAstromModelWriteForView (view, file, config);
+        break;
+
+      case PM_FPA_FILE_ASTROM_REFSTARS:
+        status = pmAstromRefstarsWriteForView (view, file, config);
+        break;
+
+      case PM_FPA_FILE_JPEG:
+        status = pmFPAviewWriteJPEG (view, file, config);
+        break;
+
+      case PM_FPA_FILE_KAPA:
+        status = pmFPAviewWriteSourcePlot (view, file, config);
+        break;
+
+      default:
+        psError(PS_ERR_IO, true, "warning: type mismatch; saw type %d (%s)", file->type, file->name);
+        return false;
+    }
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to write %s (%s)\n", file->filename, file->name);
+        return false;
+    }
+    psTrace ("psModules.camera", 5, "wrote %s (%s) (%d:%d:%d)\n", file->filename, file->name, view->chip, view->cell, view->readout);
+    return true;
+}
+
+bool pmFPAfileClose (pmFPAfile *file, const pmFPAview *view)
+{
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    // an internal file should not be sent here (should not be left on config->files)
+    PS_ASSERT(file->mode != PM_FPA_MODE_INTERNAL, false);
+
+    // skip the following states
+    if (file->state & PM_FPA_STATE_INACTIVE) {
+        psTrace("psModules.camera", 6, "skip close for %s, files is inactive", file->name);
+        return true;
+    }
+    if (file->state == PM_FPA_STATE_CLOSED) {
+        psTrace("psModules.camera", 6, "skip close for %s, files is closed", file->name);
+        return true;
+    }
+
+    // is current level == open level?
+    pmFPALevel level = pmFPAviewLevel (view);
+    if (file->fileLevel != level) {
+        psTrace("psModules.camera", 6, "skip closing of %s at this level %s: fileLevel is %s",
+                file->name, pmFPALevelToName(level), pmFPALevelToName(file->fileLevel));
+        return true;
+    }
+
+    // check if we are actually open
+    bool status = true;
+    switch (file->type) {
+        // check the FITS types
+      case PM_FPA_FILE_IMAGE:
+      case PM_FPA_FILE_MASK:
+      case PM_FPA_FILE_WEIGHT:
+      case PM_FPA_FILE_HEADER:
+      case PM_FPA_FILE_FRINGE:
+      case PM_FPA_FILE_DARK:
+      case PM_FPA_FILE_CMF:
+      case PM_FPA_FILE_PSF:
+      case PM_FPA_FILE_ASTROM_MODEL:
+      case PM_FPA_FILE_ASTROM_REFSTARS:
+        psTrace ("psModules.camera", 5, "closing %s (%s) (%d:%d:%d)\n", file->filename, file->name, view->chip, view->cell, view->readout);
+        status = psFitsClose (file->fits);
+        file->fits = NULL;
+        file->header = NULL;
+        file->state = PM_FPA_STATE_CLOSED;
+        file->wrote_phu = false;
+        break;
+
+        // ignore the TEXT types
+      case PM_FPA_FILE_SX:
+      case PM_FPA_FILE_RAW:
+      case PM_FPA_FILE_OBJ:
+      case PM_FPA_FILE_CMP:
+      case PM_FPA_FILE_JPEG:
+      case PM_FPA_FILE_KAPA:
+        break;
+
+      default:
+        psError(PS_ERR_IO, true, "type mismatch: %d (%s)", file->type, file->name);
+        return false;
+    }
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to close %s (%s) (%d:%d:%d)\n", file->filename, file->name, view->chip, view->cell, view->readout);
+        return false;
+    }
+    return true;
+}
+
+bool pmFPAfileFreeData(pmFPAfile *file, const pmFPAview *view)
+{
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    // an internal file should not be sent here (should not be left on config->files)
+    PS_ASSERT(file->mode != PM_FPA_MODE_INTERNAL, false);
+
+    if (file->state & PM_FPA_STATE_INACTIVE) {
+        psTrace("psModules.camera", 6, "skip free for %s, files is inactive", file->name);
+        return true;
+    }
+
+    // get the current level
+    pmFPALevel level = pmFPAviewLevel (view);
+
+    // do we need to free this file?
+    if (level != file->freeLevel) {
+        psTrace("psModules.camera", 6, "skip free of %s at this level %s: freeLevel is %s",
+                file->name, pmFPALevelToName(level), pmFPALevelToName(file->freeLevel));
+        return true;
+    }
+
+    bool status = true;
+    switch (file->type) {
+      case PM_FPA_FILE_IMAGE:
+      case PM_FPA_FILE_MASK:
+      case PM_FPA_FILE_WEIGHT:
+      case PM_FPA_FILE_HEADER:
+      case PM_FPA_FILE_FRINGE:
+      case PM_FPA_FILE_DARK:
+        status = pmFPAviewFreeData(view, file);
+        break;
+      case PM_FPA_FILE_SX:
+      case PM_FPA_FILE_RAW:
+      case PM_FPA_FILE_OBJ:
+      case PM_FPA_FILE_CMP:
+      case PM_FPA_FILE_CMF:
+      case PM_FPA_FILE_PSF:
+      case PM_FPA_FILE_ASTROM_MODEL:
+      case PM_FPA_FILE_ASTROM_REFSTARS:
+        psTrace ("psModules.camera", 6, "NOT freeing %s (%s) : save for further analysis\n", file->filename, file->name);
+        return true;
+      case PM_FPA_FILE_JPEG:
+      case PM_FPA_FILE_KAPA:
+        psTrace ("psModules.camera", 5, "nothing to free for %s (%s)\n", file->filename, file->name);
+        return true;
+      default:
+        psError(PS_ERR_IO, true, "warning: type mismatch; saw type %d", file->type);
+        return false;
+    }
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to read %s (%s)\n", file->filename, file->name);
+        return false;
+    }
+    psTrace ("psModules.camera", 5, "freed %s (%s) (%d:%d:%d)\n", file->filename, file->name, view->chip, view->cell, view->readout);
+    return true;
+}
+
+// open file (if not already opened).
+// this function is only called only within pmFPAfileRead or pmFPAfileWrite.
+bool pmFPAfileOpen (pmFPAfile *file, const pmFPAview *view, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    bool status;
+    char *mode = NULL;
+    char *readMode = "r";
+    char *writeMode = "w";
+
+    if (file->state & PM_FPA_STATE_INACTIVE) {
+        psTrace("psModules.camera", 6, "skip open for %s, files is inactive", file->name);
+        return true;
+    }
+
+    if (file->state == PM_FPA_STATE_OPEN) {
+        return true;
+    }
+
+    // these are programming errors
+    PS_ASSERT(file->mode != PM_FPA_MODE_NONE, false);
+    PS_ASSERT(file->mode != PM_FPA_MODE_INTERNAL, false);
+
+    if (file->mode == PM_FPA_MODE_READ) {
+        mode = readMode;
+    }
+    if (file->mode == PM_FPA_MODE_WRITE) {
+        mode = writeMode;
+    }
+    if ((file->mode == PM_FPA_MODE_WRITE) && !file->save) {
+        psTrace("psModules.camera", 6, "skip open for %s, output file not requested", file->name);
+        return true;
+    }
+
+    // determine the file name, free a name allocated earlier
+    psFree (file->filename);
+    file->filename = pmFPAfileNameFromRule (file->filerule, file, view);
+    if (file->filename == NULL) {
+        psError(PS_ERR_IO, true, "Filename is NULL");
+        return false;
+    }
+
+    // indirect filenames: these come from a list on the command line or elsewhere
+    if (!strcasecmp (file->filename, "@FILES")) {
+        char *filesrc = pmFPAfileNameFromRule (file->filesrc, file, view);
+        if (filesrc == NULL) {
+            psError(PS_ERR_IO, false, "error converting filesrc to name %s\n", file->filesrc);
+            return false;
+        }
+
+        psFree (file->filename);
+        file->filename = psMetadataLookupStr (&status, file->names, filesrc);
+
+        if (file->filename == NULL) {
+            psError(PS_ERR_IO, true, "filename lookup error (@FILES) for %s : %s\n", file->filesrc, filesrc);
+            psFree (filesrc);
+            return false;
+        }
+        // psMetadataLookupStr just returns a view, file->filename must be protected
+        psMemIncrRefCounter (file->filename);
+        psFree (filesrc);
+    }
+
+    // get name from detrend database
+    // file->detrend->detID contains the desired -det_id detID -iteration iter string
+    if (!strcasecmp (file->filename, "@DETDB")) {
+        if (!file->detrend) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find information about selected detrend.");
+            return false;
+        }
+
+        psString classId = NULL;        // The class identifier, to pass to pmDetrendFile
+        psMetadata *menu = psMetadataLookupMetadata(&status, file->camera, "CLASSID"); // Menu of class IDs
+        if (!status || !menu) {
+            psError(PS_ERR_IO, false, "Unable to find CLASSID metadata in camera configuration");
+            return false;
+        }
+        const char *rule = psMetadataLookupStr(&status, menu, file->detrend->level); // Rule for class_id
+        if (!status || !rule || strlen(rule) == 0) {
+            psError(PS_ERR_IO, false, "Unable to find %s in CLASSID in camera configuration", file->detrend->level);
+            return false;
+        }
+        classId = pmFPAfileNameFromRule(rule, file, view);
+        if (!classId) {
+            psError(PS_ERR_IO, false, "error converting CLASSID rule to name: %s\n", rule);
+            return false;
+        }
+        psTrace ("psModules.camera", 6, "looking for detrend (%s, %s)\n", file->detrend->detID, classId);
+        psFree (file->filename);
+
+        file->filename = pmDetrendFile(file->detrend->detID, classId, config);
+        if (file->filename == NULL) {
+            psError(PS_ERR_IO, false, "failed to find a valid detrend image for detID %s : classID %s\n", file->detrend->detID, classId);
+            psFree (classId);
+            return false;
+        }
+
+        psTrace ("psModules.camera", 6, "got detrend file %s\n", file->filename);
+        psFree (classId);
+    }
+
+    // apply filename mangling rules (file://, path://, neb://)
+    bool create = file->mode == PM_FPA_MODE_WRITE ? true : false;
+    psString tmpName = pmConfigConvertFilename (file->filename, config, create, false);
+    psFree (file->filename);
+    file->filename = tmpName;
+
+    switch (file->type) {
+        // open the FITS types:
+      case PM_FPA_FILE_IMAGE:
+      case PM_FPA_FILE_MASK:
+      case PM_FPA_FILE_WEIGHT:
+      case PM_FPA_FILE_HEADER:
+      case PM_FPA_FILE_FRINGE:
+      case PM_FPA_FILE_DARK:
+      case PM_FPA_FILE_CMF:
+      case PM_FPA_FILE_PSF:
+      case PM_FPA_FILE_ASTROM_MODEL:
+      case PM_FPA_FILE_ASTROM_REFSTARS:
+        psTrace ("psModules.camera", 5, "opening %s (%s) (%d:%d:%d)\n",
+                 file->filename, file->name, view->chip, view->cell, view->readout);
+        file->fits = psFitsOpen (file->filename, mode);
+        if (file->fits == NULL) {
+            psError(PS_ERR_IO, false, "error opening file %s\n", file->filename);
+            return false;
+        }
+        file->state = PM_FPA_STATE_OPEN;
+
+        file->fits->options = psMemIncrRefCounter(file->options);
+
+        // in most cases, we have already open and read the phu and determined the format.
+        // in some cases, (eg DetDB images), we have only just determined the filename.
+        // we need to check the file format before we can work with the file
+        if (!file->format) {
+          psMetadata *phu = psFitsReadHeader (NULL, file->fits);
+          if (!phu) {
+            psError(PS_ERR_IO, false, "Failed to read file header %s\n", file->filename);
+            return false;
+          }
+
+	  // XXX if we have a mask file, then we need to read the mask bit names
+	  // defined for this file
+	  if (file->type == PM_FPA_FILE_MASK) {
+	    if (!pmConfigMaskReadHeader (config, phu)) {
+		psError(PS_ERR_IO, false, "error in mask bits");
+		return false;
+	    }
+	  }
+
+          // determine the current format from the header
+          // determine camera if not specified already
+          // XXX can I actually reach this with camera not specified??
+          file->format = pmConfigCameraFormatFromHeader (NULL, NULL, config, phu, true);
+          if (!file->format) {
+            psError(PS_ERR_IO, false, "Failed to read CCD format from %s\n", file->filename);
+            psFree(phu);
+            return false;
+          }
+          psFree(phu);
+        }
+
+        // if needed, set the optional EXTWORD field based on the camera value
+        psMetadata *fileMenu = psMetadataLookupMetadata (NULL, file->format, "FILE");
+        if (!fileMenu) {
+          psError (PS_ERR_IO, true, "FILE METADATA missing from camera format %s\n",
+                   config->formatName);
+          return false;
+        }
+        char *extword = psMetadataLookupStr (&status, fileMenu, "EXTWORD");
+        if (status) {
+          psFitsSetExtnameWord (file->fits, extword);
+        }
+
+        // XXX these are probably only needed for WRITE files
+        if (file->compression) {
+            psTrace("psModules.camera", 7, "Setting compression for %s (%s)\n", file->filename, file->name);
+            if (!psFitsCompressionApply(file->fits, file->compression)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to set compression options for %s (%s) (%d:%d:%d)\n",
+                        file->filename, file->name, view->chip, view->cell, view->readout);
+                return false;
+            }
+        }
+
+        // In some cases, we need to read the PHU after we've opened the file.  This happens for the images
+        // supplied by the detrend database, which are only identified here (pmConfigConvertFilename).
+        if (!pmFPAfileReadPHU (file, view, config)) {
+            psError (PS_ERR_IO, true, "error reading PHU for %s (%s) (%d:%d:%d)\n",
+                     file->filename, file->name, view->chip, view->cell, view->readout);
+            return false;
+        }
+        break;
+
+        // defer opening TEXT types:
+      case PM_FPA_FILE_SX:
+      case PM_FPA_FILE_OBJ:
+      case PM_FPA_FILE_CMP:
+      case PM_FPA_FILE_RAW:
+      case PM_FPA_FILE_JPEG:
+      case PM_FPA_FILE_KAPA:
+        psTrace ("psModules.camera", 5, "defer opening %s\n", file->filename);
+        break;
+
+      default:
+        psError(PS_ERR_IO, true, "type mismatch for %s : %d\n", file->filename, file->type);
+        return false;
+    }
+    return true;
+}
+
+// for this file and view, if we need to read a PHU, read it.  return true for any non-error
+// condition. this function should be called by pmFPAfileOpen.  this function is only called
+// for files for which the PHU is not already loaded (files passed via the db or files after
+// the first in a multifile dataset)
+bool pmFPAfileReadPHU (pmFPAfile *file, const pmFPAview *view, pmConfig *config)
+{
+    // required conditions
+    if (file->mode != PM_FPA_MODE_READ) return true;
+    if (file->state != PM_FPA_STATE_OPEN) psAbort ("pmFPAfileReadPHU called on unopened file");
+    if (file->fpa == NULL) psAbort ("pmFPAfileReadPHU called on file without an FPA");
+
+    // check if we need to read a PHU (if not, return true)
+    switch (file->fileLevel) {
+      case PM_FPA_LEVEL_FPA:
+        if (file->fpa->hdu) return true;
+        break;
+      case PM_FPA_LEVEL_CHIP: {
+          pmChip *chip = pmFPAviewThisChip(view, file->fpa);
+          if (!chip) psAbort ("inconsistent file/fpa: fileLevel is CHIP, view is FPA");
+          if (chip->hdu) return true;
+          break;
+      }
+      case PM_FPA_LEVEL_CELL: {
+          pmCell *cell = pmFPAviewThisCell(view, file->fpa);
+          if (!cell) psAbort ("inconsistent file/fpa: fileLevel is CELL, view is FPA");
+          if (cell->hdu) return true;
+          break;
+      }
+      case PM_FPA_LEVEL_NONE:
+        // Might get here immediately after opening a file selected from the detrend database.
+        break;
+      default:
+        psAbort("fileLevel not correctly set");
+        break;
+    }
+
+    // XXX do we need to advance to the first HDU?
+    psMetadata *phu = psFitsReadHeader (NULL, file->fits);
+    if (!file->format) {
+        // determine the format (camera is already known); do not load the recipe
+        file->format = pmConfigCameraFormatFromHeader (NULL, &file->formatName, config, phu, false);
+        if (!file->format) {
+            psError(PS_ERR_IO, false, "Failed to read CCD format from %s\n", file->filename);
+            psFree(phu);
+            return false;
+        }
+    } else {
+        bool valid;
+        if (!pmConfigValidateCameraFormat (&valid, file->format, phu)) {
+            psError (PS_ERR_UNKNOWN, false, "Error in camera configuration\n");
+            psFree (phu);
+            return false;
+        }
+        if (!valid) {
+            psError(PS_ERR_IO, false, "file %s is not from the required camera", file->filename);
+            psFree (phu);
+            return false;
+        }
+    }
+    pmFPAview *thisView = pmFPAAddSourceFromHeader (file->fpa, phu, file->format);
+    assert (thisView); // XXX we are having some trouble with input psf files not having the Cell and fpa names matching.
+    psFree (thisView);
+    psFree (phu);
+    // XXX we can check the output view to be sure it corresponds to our current view
+    return true;
+}
+
+// XXX this function is only called from pmFPAfileWrite
+// XXX for each data type, there should be a function which writes the PHU, if needed
+bool pmFPAfileWritePHU(pmFPAfile *file, const pmFPAview *view, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    if (file->wrote_phu) return true;
+
+    bool status = true;
+    switch (file->type) {
+      case PM_FPA_FILE_IMAGE:
+      case PM_FPA_FILE_MASK:
+      case PM_FPA_FILE_WEIGHT:
+      case PM_FPA_FILE_DARK:
+      case PM_FPA_FILE_FRINGE:
+        status = pmFPAviewFitsWritePHU (view, file, config);
+        break;
+      case PM_FPA_FILE_CMF:
+        status = pmSource_CMF_WritePHU (view, file, config);
+        break;
+      case PM_FPA_FILE_PSF:
+        status = pmPSFmodelWritePHU (view, file, config);
+        break;
+      case PM_FPA_FILE_ASTROM_REFSTARS:
+        status = pmAstromRefstarsWritePHU (view, file, config);
+        break;
+      case PM_FPA_FILE_ASTROM_MODEL:
+      case PM_FPA_FILE_SX:
+      case PM_FPA_FILE_RAW:
+      case PM_FPA_FILE_OBJ:
+      case PM_FPA_FILE_CMP:
+      case PM_FPA_FILE_JPEG:
+      case PM_FPA_FILE_KAPA:
+        break;
+      default:
+        fprintf (stderr, "warning: type mismatch\n");
+        return false;
+    }
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to write PHU for %s (%s)\n", file->filename, file->name);
+        return false;
+    }
+    // XXX this is also being set in the individual functions.  choose one or the other
+    file->wrote_phu = true;
+    return true;
+}
+
+
+// set the state of the specified pmFPAfile(s) to active (state == true) or inactive
+// if name is NULL, set the state for all pmFPAfiles
+bool pmFPAfileActivate(psMetadata *files, bool state, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(files, false);
+
+    // return false if the requested file is not in the list (not an error, but informational)
+    psArray *selected = pmFPAfileSelect(files, name);
+    if (!selected) {
+        return false;
+    }
+    for (int i = 0; i < selected->n; i++) {
+        pmFPAfile *file = selected->data[i]; // File of interest
+        if (!file) {
+            continue;
+        }
+        if (state) {
+            file->state &= PS_NOT_U8(PM_FPA_STATE_INACTIVE);
+        } else {
+            file->state |= PM_FPA_STATE_INACTIVE;
+        }
+    }
+    psFree(selected);
+
+    return true;
+}
+
+
+pmFPAfile *pmFPAfileActivateSingle(psMetadata *files, bool state, const char *name, int num)
+{
+    PS_ASSERT_PTR_NON_NULL(files, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(name, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(num, NULL);
+
+    pmFPAfile *file = pmFPAfileSelectSingle(files, name, num);
+    if (!file) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to select instance %d of file %s", num, name);
+        return NULL;
+    }
+    if (state) {
+        file->state &= PS_NOT_U8(PM_FPA_STATE_INACTIVE);
+    } else {
+        file->state |= PM_FPA_STATE_INACTIVE;
+    }
+
+    return file;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAfileIO.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAfileIO.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAfileIO.h	(revision 22157)
@@ -0,0 +1,55 @@
+/* @file  pmFPAview.h
+ * @brief Tools to manipulate the FPA structure elements.
+ *
+ * @author EAM, IfA
+ * @author PAP, IfA
+ *
+ * @version $Revision: 1.9 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-06-30 00:53:45 $
+ * Copyright 2004-2005 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_FILE_IO_H
+#define PM_FPA_FILE_IO_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+// open the real file corresponding to the given pmFPAfile appropriate to the current view
+bool pmFPAfileOpen (pmFPAfile *file, const pmFPAview *view, pmConfig *config);
+
+// read from the real file corresponding to the given pmFPAfile for the current view
+bool pmFPAfileRead (pmFPAfile *file, const pmFPAview *view, pmConfig *config);
+
+bool pmFPAfileCreate (pmFPAfile *file, const pmFPAview *view, const pmConfig *config);
+
+// write to the real file corresponding to the given pmFPAfile for the current view
+bool pmFPAfileWrite (pmFPAfile *file, const pmFPAview *view, pmConfig *config);
+
+// close the real file corresponding to the given pmFPAfile appropriate to the current view
+bool pmFPAfileClose (pmFPAfile *file, const pmFPAview *view);
+
+// free the data at this level
+bool pmFPAfileFreeData(pmFPAfile *file, const pmFPAview *view);
+
+// set the state of the specified pmFPAfile to active (state == true) or inactive
+// if name is NULL, set the state for all pmFPAfiles
+bool pmFPAfileActivate (psMetadata *files, bool state, const char *name);
+
+/// Set the state of a single pmFPAfile (in the case of multiple files with the same name)
+///
+/// Returns file activated
+pmFPAfile *pmFPAfileActivateSingle(psMetadata *files, ///< Files to activate
+                                   bool state, ///< State to set
+                                   const char *name, ///< Name of file to activate
+                                   int num    ///< Sequence numbner of file to activate
+    );
+
+// examine all pmFPAfiles listed in the files and perform the needed I/O operations (open,read,write,close)
+bool pmFPAfileIOChecks (pmConfig *config, const pmFPAview *view, pmFPAfilePlace place);
+
+bool pmFPAfileWritePHU(pmFPAfile *file, const pmFPAview *view, pmConfig *config);
+bool pmFPAfileReadPHU (pmFPAfile *file, const pmFPAview *view, pmConfig *config);
+
+/// @}
+# endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAview.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAview.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAview.c	(revision 22157)
@@ -0,0 +1,375 @@
+/** @file  pmFPAview.c
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.14 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-01-02 20:33:14 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmHDUUtils.h"
+#include "pmFPAview.h"
+
+static void pmFPAviewFree(pmFPAview *view)
+{
+    // No reason to keep this function, apart from the fact that it allows us to type the memBlock
+    return;
+}
+
+pmFPAview *pmFPAviewAlloc(int nRows)
+{
+    pmFPAview *view = psAlloc(sizeof(pmFPAview));
+    psMemSetDeallocator(view, (psFreeFunc) pmFPAviewFree);
+
+    view->nRows = nRows;
+    pmFPAviewReset(view);
+    return view;
+}
+
+bool psMemCheckFPAview(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) pmFPAviewFree);
+}
+
+
+bool pmFPAviewReset(pmFPAview *view)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    view->chip    = -1;
+    view->cell    = -1;
+    view->readout = -1;
+    view->iRows   =  0;
+    return true;
+}
+
+// return a view restricted to the level (must be >= the input level)
+pmFPAview *pmFPAviewForLevel(pmFPALevel level, const pmFPAview *input)
+{
+    PS_ASSERT_PTR_NON_NULL(input, NULL);
+
+    pmFPAview *output = pmFPAviewAlloc (input->nRows);
+    *output = *input;
+
+    switch (level) {
+      case PM_FPA_LEVEL_FPA:
+        output->chip = -1;
+      case PM_FPA_LEVEL_CHIP:
+        output->cell = -1;
+      case PM_FPA_LEVEL_CELL:
+        output->readout = -1;
+        break;
+      default:
+        break;
+    }
+    return output;
+}
+
+pmFPALevel pmFPAviewLevel(const pmFPAview *view)
+{
+    PS_ASSERT_PTR_NON_NULL(view, PM_FPA_LEVEL_NONE);
+
+    if (view->chip < 0) {
+        return PM_FPA_LEVEL_FPA;
+    }
+    if (view->cell < 0) {
+        return PM_FPA_LEVEL_CHIP;
+    }
+    if (view->readout < 0) {
+        return PM_FPA_LEVEL_CELL;
+    }
+    return PM_FPA_LEVEL_READOUT;
+}
+
+pmChip *pmFPAviewThisChip(const pmFPAview *view, const pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa->chips, NULL);
+
+    if (view->chip < 0) {
+        return NULL;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        return NULL;
+    }
+
+    pmChip *chip = fpa->chips->data[view->chip];
+    return chip;
+}
+
+pmChip *pmFPAviewNextChip(pmFPAview *view, const pmFPA *fpa, int nStep)
+{
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    view->cell = -1;
+    view->readout = -1;
+    view->iRows = 0;
+
+    // if there are no available chips, return NULL
+    if (fpa->chips->n <= 0) {
+        view->chip = -1;
+        return NULL;
+    }
+
+    // clean up < -1 values
+    if (view->chip < -1) {
+        view->chip = -1;
+    }
+
+    // increment to the next chip
+    view->chip += nStep;
+
+    // if we are at the end of the stack, return NULL
+    if (view->chip >= fpa->chips->n) {
+        view->chip = -1;
+        return NULL;
+    }
+
+    // get the correct chip pointer
+    pmChip *chip = fpa->chips->data[view->chip];
+    return (chip);
+}
+
+pmCell *pmFPAviewThisCell(const pmFPAview *view, const pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+    if (view->cell < 0) {
+        return NULL;
+    }
+    PS_ASSERT_PTR_NON_NULL(fpa->chips, NULL);
+
+    pmChip *chip = pmFPAviewThisChip (view, fpa);
+    PS_ASSERT_PTR_NON_NULL(chip, NULL);
+
+    if (view->cell >= chip->cells->n) {
+        return NULL;
+    }
+
+    pmCell *cell = chip->cells->data[view->cell];
+    return cell;
+}
+
+pmCell *pmFPAviewNextCell (pmFPAview *view, const pmFPA *fpa, int nStep)
+{
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    pmChip *chip = pmFPAviewThisChip (view, fpa);
+    PS_ASSERT_PTR_NON_NULL(chip, NULL);
+
+    view->readout = -1;
+    view->iRows = 0;
+
+    // if there are no available cells, return NULL
+    if (chip->cells->n <= 0) {
+        view->cell = -1;
+        return NULL;
+    }
+
+    // clean up < -1 values
+    if (view->cell < -1) {
+        view->cell = -1;
+    }
+
+    // increment to the next cell
+    view->cell += nStep;
+
+    // if we are at the end of the stack, return NULL
+    if (view->cell >= chip->cells->n) {
+        view->cell = -1;
+        return NULL;
+    }
+
+    // get the correct cell pointer
+    pmCell *cell = chip->cells->data[view->cell];
+    return (cell);
+}
+
+pmReadout *pmFPAviewThisReadout (const pmFPAview *view, const pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    if (view->readout < 0) {
+        return NULL;
+    }
+
+    pmCell *cell = pmFPAviewThisCell (view, fpa);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->readouts, NULL);
+
+    if (view->readout >= cell->readouts->n) {
+        return NULL;
+    }
+
+    pmReadout *readout = cell->readouts->data[view->readout];
+    return readout;
+}
+
+pmReadout *pmFPAviewNextReadout (pmFPAview *view, const pmFPA *fpa, int nStep)
+{
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    pmCell *cell = pmFPAviewThisCell (view, fpa);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+
+    view->iRows = 0;
+
+    // if there are no available cells, return NULL
+    if (cell->readouts->n <= 0) {
+        view->readout = -1;
+        return NULL;
+    }
+
+    // clean up < -1 values
+    if (view->readout < -1) {
+        view->readout = -1;
+    }
+
+    // increment to the next cell
+    view->readout += nStep;
+
+    // if we are at the end of the stack, return NULL
+    if (view->readout >= cell->readouts->n) {
+        view->readout = -1;
+        return NULL;
+    }
+
+    // get the correct cell pointer
+    pmReadout *readout = cell->readouts->data[view->readout];
+    return (readout);
+}
+
+pmHDU *pmFPAviewThisHDU(const pmFPAview *view, const pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    // the HDU is attached to a cell, chip or fpa
+    // if this view has a -1 for the level which contains the hdu,
+    // there is no unambiguous HDU
+
+    if (view->chip < 0) {
+        return pmHDUFromFPA (fpa);
+    }
+    if (view->cell < 0) {
+        return pmHDUFromChip (pmFPAviewThisChip (view, fpa));
+    }
+    if (view->readout < 0) {
+        return pmHDUFromCell (pmFPAviewThisCell (view, fpa));
+    }
+    return pmHDUFromReadout (pmFPAviewThisReadout (view, fpa));
+}
+
+pmHDU *pmFPAviewThisPHU(const pmFPAview *view, const pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    // select the HDU which corresponds to the PHU containing this view
+
+    pmHDU *hdu;
+    pmFPAview new;
+    pmChip *chip;
+    pmCell *cell;
+
+    new = *view;
+
+    if (view->chip < 0) {
+        hdu = pmHDUFromFPA (fpa);
+        if (!hdu)
+            return NULL;
+        if (hdu->blankPHU)
+            return hdu;
+        return NULL;
+    }
+    if (view->cell < 0) {
+        chip = pmFPAviewThisChip (view, fpa);
+        hdu  = pmHDUFromChip (chip);
+        if (!hdu)
+            return NULL;
+        if (hdu->blankPHU)
+            return hdu;
+        new.chip = -1;
+        hdu = pmFPAviewThisPHU (&new, fpa);
+        return hdu;
+    }
+    if (view->readout < 0) {
+        cell = pmFPAviewThisCell (view, fpa);
+        hdu  = pmHDUFromCell (cell);
+        if (!hdu) {
+            psAbort("a split readout is not covered by the current paradigm");
+        }
+        if (hdu->blankPHU)
+            return hdu;
+        new.cell = -1;
+        hdu = pmFPAviewThisPHU (&new, fpa);
+        return hdu;
+    }
+    return NULL;
+}
+
+pmFPAview *pmFPAviewGenerate(const pmFPA *fpa, const pmChip *chip, const pmCell *cell,
+                             const pmReadout *readout)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    pmFPAview *view = pmFPAviewAlloc(0);// View to return
+
+    if (!chip) {
+        return view;
+    }
+
+    for (view->chip = 0; view->chip < fpa->chips->n && fpa->chips->data[view->chip] != chip; view->chip++);
+    if (view->chip == fpa->chips->n) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find chip %p in fpa.", chip);
+        psFree(view);
+        return NULL;
+    }
+
+    if (!cell) {
+        return view;
+    }
+
+    for (view->cell = 0; view->cell < chip->cells->n && chip->cells->data[view->cell] != cell; view->cell++);
+    if (view->cell == chip->cells->n) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find cell %p in chip.", cell);
+        psFree(view);
+        return NULL;
+    }
+
+    if (!readout) {
+        return view;
+    }
+
+    for (view->readout = 0;
+         view->readout < cell->readouts->n && cell->readouts->data[view->readout] != readout;
+         view->readout++);
+    if (view->readout == cell->readouts->n) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find readout %p in cell.", readout);
+        psFree(view);
+        return NULL;
+    }
+
+    return view;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAview.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAview.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmFPAview.h	(revision 22157)
@@ -0,0 +1,127 @@
+/* @file pmFPA.h
+ * @brief Tools to manipulate the FPA structure elements.
+ *
+ * @author Eugene Magnier, IfA
+ *
+ * @version $Revision: 1.10 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-01-02 20:33:14 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FPA_VIEW_H
+#define PM_FPA_VIEW_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+// #include "pmFPA.h"
+// #include "pmFPALevel.h"
+
+/// Identifier for FPA components
+///
+/// This structure allows the identification of a single component of the focal plane hierarchy (or multiple,
+/// if we consider selecting all those components below the selected component).  Components are identified on
+/// the basis of their chip, cell, readout index.  An index of -1 means all components at that level.
+/// Additionally, since readouts may be read piecemeal, there are additional indices for these.
+typedef struct
+{
+    int chip;                           ///< Number of the chip, or -1 for all
+    int cell;                           ///< Number of the cell, or -1 for all
+    int readout;                        ///< Number of the readout, or -1 for all
+    int nRows;                          ///< Maximum number of rows per readout segment read, or 0 for all
+    int iRows;                          ///< Starting point for this read
+}
+pmFPAview;
+
+/// Allocator for pmFPAview
+pmFPAview *pmFPAviewAlloc(int nRows);   ///< Maximum number of rows per readout segment read, or 0 for all
+bool psMemCheckFPAview(psPtr ptr);
+
+/// Reset a view to select all components
+bool pmFPAviewReset(pmFPAview *view     ///< View to reset
+                   );
+
+// return a view restricted to the level (must be >= the input level)
+pmFPAview *pmFPAviewForLevel(pmFPALevel level, const pmFPAview *input);
+
+/// Determine the current view level
+///
+/// Returns the level appropriate for the view
+pmFPALevel pmFPAviewLevel(const pmFPAview *view ///< View to examine
+                         );
+
+// Lookups
+
+/// Return the currently selected chip for this view
+///
+/// Returns NULL if the selection is not specific or invalid
+pmChip *pmFPAviewThisChip(const pmFPAview *view, ///< Current view
+                          const pmFPA *fpa ///< FPA containing chip
+                         );
+
+/// Return the currently selected cell for this view
+///
+/// Returns NULL if the selection is not specific or invalid
+pmCell *pmFPAviewThisCell(const pmFPAview *view, ///< Current view
+                          const pmFPA *fpa ///< FPA containing cell
+                         );
+
+/// Return the currently selected readout for this view
+///
+/// Returns NULL if the selection is not specific or invalid
+pmReadout *pmFPAviewThisReadout(const pmFPAview *view, ///< Current view
+                                const pmFPA *fpa ///< FPA containing readout
+                               );
+
+// Incrementors
+
+/// Advance view to the next chip
+///
+/// Returns NULL if there is no next
+pmChip *pmFPAviewNextChip(pmFPAview *view, ///< Current view
+                          const pmFPA *fpa, ///< FPA containing chips
+                          int nStep     ///< Number of chips to increment
+                         );
+
+/// Advance view to the next cell
+///
+/// Returns NULL if there is no next
+pmCell *pmFPAviewNextCell(pmFPAview *view, ///< Current view
+                          const pmFPA *fpa, ///< FPA containing cells
+                          int nStep     ///< Number of cells to increment
+                         );
+
+/// Advance view to the next readout
+///
+/// Returns NULL if there is no next
+pmReadout *pmFPAviewNextReadout(pmFPAview *view, ///< Current view
+                                const pmFPA *fpa, ///< FPA containing readouts
+                                int nStep ///< Number of readouts to increment
+                               );
+
+/// Return the HDU corresponding to the current view
+///
+/// Uses the pmHDUFrom* functions, combined with the view.
+pmHDU *pmFPAviewThisHDU(const pmFPAview *view, ///< Current view
+                        const pmFPA *fpa ///< FPA for view
+                       );
+
+/// Return the blank Primary HDU corresponding to the current view, if any
+///
+/// Similar to pmFPAviewThisHDU, except returns NULL if no HDU is found, or the HDU is not a blank Primary HDU
+pmHDU *pmFPAviewThisPHU(const pmFPAview *view, ///< Current view
+                        const pmFPA *fpa ///< FPA for view
+                       );
+
+/// Generate a view, given a chip, cell, readout.
+///
+/// Uses the pointer value in the array of the parent to locate the child
+pmFPAview *pmFPAviewGenerate(const pmFPA *fpa, ///< FPA of interest
+                             const pmChip *chip, ///< Chip of interest, or NULL
+                             const pmCell *cell, ///< Cell of interest, or NULL
+                             const pmReadout *reaodut ///< Readout of interest, or NULL
+    );
+
+
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmHDU.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmHDU.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmHDU.c	(revision 22157)
@@ -0,0 +1,238 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmHDU.h"
+#include "pmFPA.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static (private) functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Move to the appropriate extension in FITS file for HDU
+static bool hduMove(pmHDU *hdu,         // HDU with extname
+                    psFits *fits        // FITS file in which to move
+                   )
+{
+    // Deal with the PHU case
+    if (hdu->blankPHU || !hdu->extname) {
+        if (!psFitsMoveExtNum(fits, 0, false)) {
+            psError(PS_ERR_IO, false, "Unable to move to primary header!\n");
+            return false;
+        }
+        return true;
+    }
+
+    if (!psFitsMoveExtName(fits, hdu->extname)) {
+        psError(PS_ERR_IO, false, "Unable to move to extension %s\n", hdu->extname);
+        return false;
+    }
+
+    return true;
+}
+
+static void hduFree(pmHDU *hdu)
+{
+    psFree(hdu->extname);
+    psFree(hdu->format);
+    psFree(hdu->header);
+    psFree(hdu->images);
+    psFree(hdu->weights);
+    psFree(hdu->masks);
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+pmHDU *pmHDUAlloc(const char *extname)
+{
+    pmHDU *hdu = psAlloc(sizeof(pmHDU));
+    psMemSetDeallocator(hdu, (psFreeFunc)hduFree);
+
+    if (!extname || strlen(extname) == 0) {
+        hdu->blankPHU = true;
+        hdu->extname = NULL;
+    } else {
+        hdu->blankPHU = false;
+        hdu->extname = psStringCopy(extname);
+    }
+    hdu->format  = NULL;
+    hdu->header  = NULL;
+    hdu->images  = NULL;
+    hdu->weights = NULL;
+    hdu->masks   = NULL;
+
+    return hdu;
+}
+
+bool psMemCheckHDU(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) hduFree);
+}
+
+
+bool pmHDUReadHeader(pmHDU *hdu, psFits *fits)
+{
+    PS_ASSERT_PTR_NON_NULL(hdu, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    // Move to the appropriate extension
+    psTrace("psModules.camera", 5, "Moving to extension %s...\n", hdu->extname);
+    if (!hduMove(hdu, fits)) {
+        return false;
+    }
+
+    if (!hdu->header) {
+        psTrace("psModules.camera", 5, "Reading the header...\n");
+        hdu->header = psFitsReadHeader(hdu->header, fits);
+        if (! hdu->header) {
+            psError(PS_ERR_IO, false, "Unable to read header for extension %s\n", hdu->extname);
+            return false;
+        }
+    }
+
+    return true;
+}
+
+// Read an HDU from a FITS file
+// XXX: Add a region specifier?
+bool hduRead(pmHDU *hdu,                // HDU to write
+             psArray **images,          // Images into which to read
+             psFits *fits               // FITS file to read
+            )
+{
+    assert(hdu);
+    assert(images);
+    assert(fits);
+
+    // Read the header; includes the move
+    if (!pmHDUReadHeader(hdu, fits)) {
+        return false;
+    }
+
+    if (hdu->blankPHU) {
+        // Done already!
+        return true;
+    }
+
+    if (*images) {
+        psWarning("HDU %s has already been read --- overwriting.\n", hdu->extname);
+        psFree(*images);                // Blow away anything existing
+    }
+    psTrace("psModules.camera", 5, "Reading the pixels...\n");
+    *images = psFitsReadImageCube(fits, psRegionSet(0,0,0,0));
+    if (!*images) {
+        psError(PS_ERR_IO, false, "Unable to read pixels for extension %s\n", hdu->extname);
+        return false;
+    }
+    return true;
+}
+
+bool pmHDURead(pmHDU *hdu, psFits *fits)
+{
+    PS_ASSERT_PTR_NON_NULL(hdu, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    return hduRead(hdu, &hdu->images, fits);
+}
+
+bool pmHDUReadMask(pmHDU *hdu, psFits *fits)
+{
+    PS_ASSERT_PTR_NON_NULL(hdu, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    return hduRead(hdu, &hdu->masks, fits);
+}
+
+bool pmHDUReadWeight(pmHDU *hdu, psFits *fits)
+{
+    PS_ASSERT_PTR_NON_NULL(hdu, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    return hduRead(hdu, &hdu->weights, fits);
+}
+
+// Write an HDU to a FITS file
+static bool hduWrite(pmHDU *hdu,        // HDU to write
+                     psArray *images,   // Images to write
+                     psFits *fits       // FITS file to which to write
+                    )
+{
+    assert(hdu);
+    assert(fits);
+
+    psTrace("psModules.camera", 7, "Writing HDU %s\n", hdu->extname);
+
+    if (!images && !hdu->header) {
+        psWarning("Nothing to write for HDU %s\n", hdu->extname);
+        return false;
+    }
+
+    // Preserve the extension name, if it's the PHU
+    char *extname = hdu->extname;       // The name of the extension
+    if (!extname && hdu->header) {
+        bool mdok = true;               // Status of MD lookup
+        extname = psMetadataLookupStr(&mdok, hdu->header, "EXTNAME");
+        if (!mdok || !extname || strlen(extname) == 0) {
+            extname = "";
+        }
+    }
+
+    // Make sure it's recognisable as what it's supposed to be
+    if (!hdu->header) {
+        hdu->header = psMetadataAlloc();
+    }
+    if (!pmConfigConformHeader(hdu->header, hdu->format)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to conform header to format.\n");
+        return false;
+    }
+
+    // Only a header
+    if (!images && !psFitsWriteBlank(fits, hdu->header, extname)) {
+        psError(PS_ERR_IO, false, "Unable to write header for extension %s\n", extname);
+        return false;
+    }
+
+    if (images) {
+        psTrace("psModules.camera", 9, "Writing pixels for %s\n", hdu->extname);
+        if (!psFitsWriteImageCube(fits, hdu->header, images, extname)) {
+            psError(PS_ERR_IO, false, "Unable to write image to extension %s\n", hdu->extname);
+            return false;
+        }
+    }
+    return true;
+}
+
+// XXX: Add a region specifier?
+bool pmHDUWrite(pmHDU *hdu, psFits *fits)
+{
+    PS_ASSERT_PTR_NON_NULL(hdu, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    return hduWrite(hdu, hdu->images, fits);
+}
+
+bool pmHDUWriteMask(pmHDU *hdu, psFits *fits)
+{
+    PS_ASSERT_PTR_NON_NULL(hdu, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    return hduWrite(hdu, hdu->masks, fits);
+}
+
+bool pmHDUWriteWeight(pmHDU *hdu, psFits *fits)
+{
+    PS_ASSERT_PTR_NON_NULL(hdu, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    return hduWrite(hdu, hdu->weights, fits);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmHDU.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmHDU.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmHDU.h	(revision 22157)
@@ -0,0 +1,82 @@
+/* @file pmHDU.h
+ * @brief Define a header data unit (from a FITS file), with functions to read and write
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.8 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-01-02 20:33:41 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_HDU_H
+#define PM_HDU_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+/// An instance of the FITS Header Data Unit
+///
+/// Of course, it is not an exact replica of a FITS HDU --- they have no mask and weight data, but these are
+/// stored here for convenience --- it keeps all the relevant data about the image in one place.
+typedef struct
+{
+    psString extname;                   ///< The extension name
+    bool blankPHU;                      ///< Is this a blank FITS Primary Header Unit, i.e., no data?
+    psMetadata *format;                 ///< The camera format
+    psMetadata *header;                 ///< The FITS header, or NULL if primary for FITS; or section info
+    psArray *images;                    ///< The pixel data
+    psArray *weights;                   ///< The pixel data
+    psArray *masks;                     ///< The pixel data
+}
+pmHDU;
+
+
+/// Allocator for pmHDU
+pmHDU *pmHDUAlloc(const char *extname);   ///< Extension name, or NULL for PHU
+bool psMemCheckHDU(psPtr ptr);
+
+/// Read the HDU header only
+///
+/// Moves to the appropriate extension
+bool pmHDUReadHeader(pmHDU *hdu,        ///< HDU for which to read header
+                     psFits *fits       ///< FITS file from which to read
+                    );
+
+/// Read the HDU header and pixels
+///
+/// Moves to the appropriate extension
+bool pmHDURead(pmHDU *hdu,              ///< HDU to read
+               psFits *fits             ///< FITS file to read from
+              );
+
+/// Read the HDU header and mask
+///
+/// Moves to the appropriate extension
+bool pmHDUReadMask(pmHDU *hdu,          ///< HDU to read
+                   psFits *fits         ///< FITS file to read from
+                  );
+
+/// Read the HDU header and weight map
+///
+/// Moves to the appropriate extension
+bool pmHDUReadWeight(pmHDU *hdu,        ///< HDU to read
+                     psFits *fits       ///< FITS file to read from
+                    );
+
+/// Write the HDU header and pixels
+bool pmHDUWrite(pmHDU *hdu,             ///< HDU to write
+                psFits *fits            ///< FITS file to write to
+               );
+
+/// Write the HDU header and mask
+bool pmHDUWriteMask(pmHDU *hdu,         ///< HDU to write
+                    psFits *fits        ///< FITS file to write to
+                   );
+
+/// Write the HDU header and weight map
+bool pmHDUWriteWeight(pmHDU *hdu,       ///< HDU to write
+                      psFits *fits      ///< FITS file to write to
+                     );
+
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmHDUGenerate.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmHDUGenerate.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmHDUGenerate.c	(revision 22157)
@@ -0,0 +1,711 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <strings.h>            /* for strn?casecmp */
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmHDUUtils.h"
+#include "pmConcepts.h"
+#include "pmConceptsStandard.h"
+#include "pmHDUGenerate.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Add cells in a chip to a list
+static bool addCellsFromChip(psList *list, // List of cells
+                             const pmChip *chip // The chip from which to add cells
+                            )
+{
+    assert(list);
+    assert(chip);
+
+    psArray *cells = chip->cells;       // Array of cells
+    bool result = true;                 // Result of adding cells
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // A cell
+        if (!cell->hdu) {               // Don't add cells that have their own HDU
+            result |= psListAdd(list, PS_LIST_TAIL, cell);
+        }
+    }
+
+    return result;
+}
+
+// Add cells in an FPA to a list
+static bool addCellsFromFPA(psList *list, // List of cells
+                            const pmFPA *fpa // The FPA from which to add cells
+                           )
+{
+    assert(list);
+    assert(fpa);
+
+    psArray *chips = fpa->chips;        // Array of chips
+    bool result = true;                 // Result of adding cells
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // A chip
+        if (! chip->hdu) {              // Don't add chips that have their own HDU
+            result |= addCellsFromChip(list, chip);
+        }
+    }
+
+    return result;
+}
+
+// Get the maximum extent of the HDU from the trimsec and biassecs
+static bool sizeHDU(int *xSize, int *ySize, // Size of HDU
+                    psList *cells       // List of cells
+                   )
+{
+    psListIterator *cellsIter = psListIteratorAlloc(cells, PS_LIST_HEAD, false); // Iterator for cells
+    pmCell *cell = NULL;                // The cell from iteration
+    bool mdok = true;                   // Status of MD lookup
+    *xSize = 0;
+    *ySize = 0;
+    while ((cell = psListGetAndIncrement(cellsIter))) {
+        psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim section
+        if (mdok && trimsec && !psRegionIsNaN(*trimsec)) {
+            *xSize = PS_MAX(trimsec->x1, *xSize);
+            *ySize = PS_MAX(trimsec->y1, *ySize);
+        } else {
+            psFree(cellsIter);
+            return false;
+        }
+        psList *biassecs = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.BIASSEC"); // Bias sections
+        if (mdok && biassecs) {
+            psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, false); // Iterator
+            psRegion *biassec = NULL;   // The bias section
+            while ((biassec = psListGetAndIncrement(biassecsIter))) {
+                if (!psRegionIsNaN(*biassec)) {
+                    *xSize = PS_MAX(biassec->x1, *xSize);
+                    *ySize = PS_MAX(biassec->y1, *ySize);
+                } else {
+                    psFree(biassecsIter);
+                    psFree(cellsIter);
+                    return false;
+                }
+            }
+            psFree(biassecsIter);
+        }
+    }
+    psFree(cellsIter);
+
+    return (*xSize != 0 && *ySize != 0);
+}
+
+
+static psRegion *sectionForImage(int *position, // Position on the output image, updated
+                                 const psImage *image, // Image containing the sizes and offsets
+                                 int readdir // Read direction, 1=rows, 2=cols
+                                )
+{
+    psRegion *region = NULL;            // The region to return
+    switch (readdir) {
+    case 1:                           // Read direction is rows
+        region = psRegionAlloc(*position, *position + image->numCols, image->row0,
+                               image->row0 + image->numRows);
+        *position += image->numCols;
+        break;
+    case 2:                           // Read direction is columns
+        region = psRegionAlloc(image->col0, image->col0 + image->numCols, *position,
+                               *position + image->numRows);
+        *position += image->numRows;
+        break;
+    default:
+        psAbort("Shouldn't ever get here!\n");
+    }
+
+    return region;
+}
+
+static bool doBiasSections(int *position, // Position on the output image, updated
+                           int *readdir,// Read direction for cells
+                           pmCell *cell // Cell
+                          )
+{
+    psMetadataItem *biassecItem = psMetadataLookup(cell->concepts, "CELL.BIASSEC"); // Bias sections
+    if (!biassecItem) {
+        psWarning("CELL.BIASSEC has not been initialised in cell --- ignored.\n");
+        return false;
+    }
+    psFree(biassecItem->data.V);        // Blow away the old list
+    psList *biassecs = psListAlloc(NULL);
+    biassecItem->data.V = biassecs;
+
+    bool mdok = true;                   // Status of MD lookup
+    int cellreaddir = psMetadataLookupS32(&mdok, cell->concepts, "CELL.READDIR"); // Read direction
+    if (!mdok || (cellreaddir != 1 && cellreaddir != 2)) {
+        // Probably unnecessary, but just in case....
+        psWarning("CELL.READDIR is not set in cell --- ignored.\n");
+        return false;
+    }
+    if (*readdir == 0) {
+        *readdir = cellreaddir;
+    } else if (*readdir != cellreaddir) {
+        psWarning("CELL.READDIR does not match read direction for HDU --- ignored.\n");
+        return false;
+    }
+
+    pmReadout *readout = cell->readouts->data[0]; // The first readout, as representative
+    psList *biases = readout->bias; // The bias images from the readout
+
+    psListIterator *biasIter = psListIteratorAlloc(biases, PS_LIST_HEAD, true); // Iterator for biases
+    psImage *bias = NULL;       // Bias image from iteration
+    while ((bias = psListGetAndIncrement(biasIter))) {
+        // Construct a region
+        psRegion *biassec = sectionForImage(position, bias, *readdir);
+        psListAdd(biassecs, PS_LIST_TAIL, biassec);
+        psFree(biassec);        // Drop reference
+    }
+    psFree(biasIter);
+
+    return true;
+}
+
+// Attempt to read CELL.TRIMSEC and CELL.BIASSEC from the cell format if they are specified by VALUE
+static bool readTrimBias(psList *cells // List of cells below the HDU
+    )
+{
+    bool mdok;                          // Status of MD lookup
+    psListIterator *cellsIter = psListIteratorAlloc(cells, PS_LIST_HEAD, false); // Iterator for cells
+    pmCell *cell = NULL;                // Cell from iteration
+    bool allFixed = true;               // We're able to fix all TRIMSEC and BIASSEC
+    while ((cell = psListGetAndIncrement(cellsIter))) {
+        psMetadataItem *trimsecItem = psMetadataLookup(cell->concepts, "CELL.TRIMSEC"); // Item with trimsec
+        if (!trimsecItem || trimsecItem->type != PS_DATA_REGION) {
+            psWarning("CELL.TRIMSEC has not been initialised in cell --- ignored.\n");
+            return false;
+        }
+        psRegion *trimsec = trimsecItem->data.V; // Trim section
+        if (!trimsec || psRegionIsNaN(*trimsec)) {
+            const char *trimsecSource = psMetadataLookupStr(&mdok, cell->config, "CELL.TRIMSEC.SOURCE");
+            if (strcmp(trimsecSource, "VALUE") == 0) {
+
+                const char *trimsecStr = psMetadataLookupStr(&mdok, cell->config, "CELL.TRIMSEC");
+                if (!trimsec) {
+                    trimsec = trimsecItem->data.V = psRegionAlloc(NAN, NAN, NAN, NAN);
+                }
+                *trimsec = psRegionFromString(trimsecStr);
+            } else {
+                allFixed = false;
+            }
+        }
+
+        psMetadataItem *biassecItem = psMetadataLookup(cell->concepts, "CELL.BIASSEC"); // Bias sections
+        if (!biassecItem) {
+            psWarning("CELL.BIASSEC has not been initialised in cell --- ignored.\n");
+            return false;
+        }
+        psList *biassecs = biassecItem->data.V;
+        if (!biassecs || biassecs->n != 0) {
+            allFixed = false;
+            continue;
+        }
+
+        const char *biassecSource = psMetadataLookupStr(&mdok, cell->config, "CELL.BIASSEC.SOURCE");
+        if (biassecSource && strcmp(biassecSource, "VALUE") == 0) {
+            const char *biassecStr = psMetadataLookupStr(&mdok, cell->config, "CELL.BIASSEC");
+            psFree(biassecItem->data.V);
+            biassecItem->data.V = p_pmConceptParseRegions(biassecStr);
+        } else {
+            allFixed = false;
+        }
+    }
+    psFree(cellsIter);
+
+    return allFixed;
+}
+
+
+// Generate CELL.TRIMSEC and CELL.BIASSEC for the cells
+static bool generateTrimBias(psList *cells // List of cells below the HDU
+                            )
+{
+    pmCell *cell = NULL;                // Cell from iteration
+    int numCells = cells->n;            // Number of cells
+    int cellNum = 0;                    // The cell number
+    int position = 0;                   // Position on the image
+    bool mdok = true;                   // Status of MD lookup
+    int readdir = 0;                    // Read direction (1=rows, 2=cols)
+
+    // First run through to do the LHS biases
+    psListIterator *cellsIter = psListIteratorAlloc(cells, PS_LIST_HEAD, false); // Iterator for cells
+    bool done = false;                  // Done with iteration (due to being halfway through)?
+    while ((cell = psListGetAndIncrement(cellsIter)) && !done) {
+        if (cellNum <= numCells/2 - 1) {
+            doBiasSections(&position, &readdir, cell);
+            cellNum++;
+        } else {
+            done = true;
+        }
+    }
+
+    // Second run through to do the trim sections
+    psListIteratorSet(cellsIter, PS_LIST_HEAD);
+    while ((cell = psListGetAndIncrement(cellsIter))) {
+        psMetadataItem *trimsecItem = psMetadataLookup(cell->concepts, "CELL.TRIMSEC"); // Item with trimsec
+        if (!trimsecItem || trimsecItem->type != PS_DATA_REGION) {
+            psWarning("CELL.TRIMSEC has not been initialised in cell --- ignored.\n");
+            continue;
+        }
+        psRegion *trimsec = trimsecItem->data.V; // Trim section
+
+        int cellreaddir = psMetadataLookupS32(&mdok, cell->concepts, "CELL.READDIR"); // Read direction
+        if (!mdok || (cellreaddir != 1 && cellreaddir != 2)) {
+            // Probably unnecessary, but just in case....
+            psWarning("CELL.READDIR is not set in cell --- ignored.\n");
+            continue;
+        }
+        if (readdir == 0 && mdok && cellreaddir != 0) {
+            readdir = cellreaddir;
+        } else if (readdir != cellreaddir) {
+            psWarning("CELL.READDIR for cells within the HDU do not match!\n");
+            cellreaddir = readdir;
+        }
+
+        pmReadout *readout = cell->readouts->data[0]; // The first readout, as representative
+        // The proper image, used to get the size
+        psImage *image = readout->image ? readout->image : (readout->mask ? readout->mask : readout->weight);
+        if (!image) {
+            continue;
+        }
+        if (readout->mask &&
+                (readout->mask->numCols != image->numCols || readout->mask->numRows != image->numRows)) {
+            psWarning("Image and mask have different sizes (%dx%d vs %dx%d)!\n",
+                     image->numCols, image->numRows, readout->mask->numCols, readout->mask->numRows);
+        }
+        if (readout->weight &&
+                (readout->weight->numCols != image->numCols || readout->weight->numRows != image->numRows)) {
+            psWarning("Image and weight have different sizes (%dx%d vs %dx%d)!\n",
+                     image->numCols, image->numRows, readout->weight->numCols, readout->weight->numRows);
+        }
+        // New reference
+        trimsec = sectionForImage(&position, image, cellreaddir);
+        psFree(trimsecItem->data.V);
+        trimsecItem->data.V = trimsec;
+    }
+
+    // A final run through to do the RHS biases
+    psListIteratorSet(cellsIter, cellNum);
+    while ((cell = psListGetAndIncrement(cellsIter))) {
+        doBiasSections(&position, &readdir, cell);
+    }
+
+    // Clean up
+    psFree(cellsIter);
+
+    return (position > 0);
+}
+
+// Check the type for a current image against a previous type
+static psElemType checkTypes(psElemType previous, // Previously defined type, or 0
+                             psElemType current // Current type
+                            )
+{
+    if (previous == 0) {
+        return current;
+    }
+
+    if (previous != current) {
+        psWarning("Images within the HDU are of different types (%x vs %x) --- promoting\n",
+                  previous, current);
+        return PS_MAX(previous, current);
+    }
+
+    return previous;
+}
+
+
+// Paste the source image into the target, according to the provided region.  The source is then updated to
+// reference the region within the target.
+static psImage *pasteImage(psImage *target, // Target image, into which the paste is made
+                           psImage *source,// Source image, from which the paste is made, and then changed
+                           psRegion *region // Image section into which to paste
+                          )
+{
+    if (source->numCols != region->x1 - region->x0 || source->numRows != region->y1 - region->y0) {
+        psString regionString = psRegionToString(*region);
+        psWarning("Image size (%dx%d) does not match region (%s).\n",
+                 source->numCols, source->numRows, regionString);
+        psFree(regionString);
+    }
+    psImageOverlaySection(target, source, region->x0, region->y0, "=");
+
+    // Reference the HDU version, so that subsequent changes will touch the HDU
+    return psImageSubset(target, *region);
+}
+
+
+// Generate the HDU, given a list of cells below that HDU.  This is the main engine function, that does all
+// the work.
+static bool generateHDU(pmHDU *hdu,     // HDU to generate
+                        psList *cells   // List of cells
+                       )
+{
+    // Check the number of readouts is consistent within the HDU
+    int numReadouts = -1;               // Number of readouts
+    psElemType imageType = 0;           // Type of readout images
+    psElemType maskType = 0;            // Type of readout masks
+    psElemType weightType = 0;          // Type of readout weights
+    {
+        psListIterator *iter = psListIteratorAlloc(cells, PS_LIST_HEAD, false); // Iterator for cells
+        pmCell *cell = NULL;                // The cell from iteration
+        while ((cell = psListGetAndIncrement(iter)))
+        {
+            psArray *readouts = cell->readouts;
+            if (numReadouts == -1) {
+                numReadouts = readouts->n;
+            } else if (readouts->n != numReadouts) {
+                psError(PS_ERR_IO, true, "Number of readouts doesn't match: %ld vs %d\n", readouts->n,
+                        numReadouts);
+                return false;
+            }
+            for (int i = 0; i < numReadouts; i++) {
+                pmReadout *readout = readouts->data[i]; // The readout
+                if (!readout) {
+                    continue;
+                }
+
+                if (!hdu->images && readout->image) {
+                    imageType = checkTypes(imageType, readout->image->type.type);
+                }
+                if (!hdu->masks && readout->mask) {
+                    maskType = checkTypes(maskType, readout->mask->type.type);
+                }
+                if (!hdu->weights && readout->weight) {
+                    weightType = checkTypes(weightType, readout->weight->type.type);
+                }
+            }
+        }
+        psFree(iter);
+    }
+    if (numReadouts == 0 || (imageType == 0 && maskType == 0 && weightType == 0)) {
+        // Nothing from which to create an HDU
+        psFree(cells);
+        psError(PS_ERR_IO, true, "Nothing from which to create an HDU\n");
+        return false;
+    }
+
+    // Get the size of the HDU, either from existing trimsec and biassec, or generate these and try again
+    int xSize = 0, ySize = 0;           // Size of HDU
+    if (!sizeHDU(&xSize, &ySize, cells) &&
+        !(readTrimBias(cells) && sizeHDU(&xSize, &ySize, cells)) &&
+        !(generateTrimBias(cells) && sizeHDU(&xSize, &ySize, cells))) {
+        psError(PS_ERR_IO, true, "Unable to determine size of HDU!\n");
+        return false;
+    }
+
+    // Generate the HDU
+    if (imageType) {
+        hdu->images = psArrayAlloc(numReadouts);
+        for (int i = 0; i < numReadouts; i++) {
+            psImage *image = psImageAlloc(xSize, ySize, imageType);
+            psImageInit(image, 0.0);
+            hdu->images->data[i] = image;
+        }
+    }
+    if (maskType) {
+        hdu->masks = psArrayAlloc(numReadouts);
+        for (int i = 0; i < numReadouts; i++) {
+            psImage *mask = psImageAlloc(xSize, ySize, maskType);
+            psImageInit(mask, 0);
+            hdu->masks->data[i] = mask;
+        }
+    }
+    if (weightType) {
+        hdu->weights = psArrayAlloc(numReadouts);
+        for (int i = 0; i < numReadouts; i++) {
+            psImage *weight = psImageAlloc(xSize, ySize, weightType);
+            psImageInit(weight, 0.0);
+            hdu->weights->data[i] = weight;
+        }
+    }
+
+    // Insert the pixels into the HDU
+    {
+        psListIterator *iter = psListIteratorAlloc(cells, PS_LIST_HEAD, false); // Iterator for cells
+        pmCell *cell = NULL;           // The cell from iteration
+        bool mdok = true;               // Result of MD lookup
+        while ((cell = psListGetAndIncrement(iter)))
+        {
+            psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim section
+            if (!mdok || !trimsec) {
+                psAbort("Shouldn't ever get here --- CELL.TRIMSEC should have been set above.\n");
+            }
+            psList *biassecs = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.BIASSEC"); // Bias secionts
+            if (!mdok || !biassecs) {
+                psAbort("Shouldn't ever get here --- CELL.BIASSEC should have been set above.\n");
+            }
+            psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, false); // Iterator
+
+            psArray *readouts = cell->readouts; // Array of readouts
+            psArray *hduImages = hdu->images; // Array of images in the HDU
+            psArray *hduMasks = hdu->masks; // Array of masks in the HDU
+            psArray *hduWeights = hdu->weights; // Array of weights in the HDU
+            for (int i = 0; i < readouts->n; i++) {
+                pmReadout *readout = readouts->data[i]; // The readout of interest
+                if (!readout) {
+                    continue;
+                }
+
+                if (readout->image) {
+                    psImage *new = pasteImage(hduImages->data[i], readout->image, trimsec);
+                    psFree(readout->image);
+                    readout->image = new;
+                }
+                if (readout->mask) {
+                    psImage *new = pasteImage(hduMasks->data[i], readout->mask, trimsec);
+                    psFree(readout->mask);
+                    readout->mask = new;
+                }
+                if (readout->weight) {
+                    psImage *new = pasteImage(hduWeights->data[i], readout->weight, trimsec);
+                    psFree(readout->weight);
+                    readout->weight = new;
+                }
+
+                if (biassecs->n != readout->bias->n) {
+                    psWarning("Number of bias sections (%ld) and number of biases (%ld) do not match.\n",
+                              biassecs->n, readout->bias->n);
+                }
+                psListIterator *biasIter = psListIteratorAlloc(readout->bias, PS_LIST_HEAD, false); // Iteratr
+                psImage *bias = NULL;   // Bias image, from iteration
+                psListIteratorSet(biassecsIter, PS_LIST_HEAD);
+                psRegion *biassec = NULL; // Bias region, from iteration
+                psList *newBias = psListAlloc(NULL); // New list of bias images
+                while ((bias = psListGetAndIncrement(biasIter)) &&
+                        (biassec = psListGetAndIncrement(biassecsIter))) {
+                    psImage *new = pasteImage(hduImages->data[i], bias, biassec);
+                    psListAdd(newBias, PS_LIST_TAIL, new);
+                    psFree(new);        // Drop reference
+                }
+                psFree(biasIter);
+
+                // Add on the new list of bias images
+                psFree(readout->bias);
+                readout->bias = newBias;
+            }
+            psFree(biassecsIter);
+        } // Iterating over cells within the HDU
+        psFree(iter);
+    }
+    psFree(cells);
+
+    return true;
+}
+
+// Return the level that an extension applies to
+static pmFPALevel extensionLevel(pmHDU *hdu // HDU to check
+                                )
+{
+    if (!hdu->format) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "HDU does not have a camera format.\n");
+        return PM_FPA_LEVEL_NONE;
+    }
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *file = psMetadataLookupMetadata(&mdok, hdu->format, "FILE"); // File information for camera format
+    if (!mdok || !file) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Can't file FILE information for camera format "
+                "configuration.\n");
+        return PM_FPA_LEVEL_NONE;
+    }
+    psString extensions = psMetadataLookupStr(&mdok, file, "EXTENSIONS"); // Where the HDUs are
+    if (!mdok || !extensions || strlen(extensions) == 0) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Can't find EXTENSIONS in the FILE information of the camera "
+                "format configuration.\n");
+        return PM_FPA_LEVEL_NONE;
+    }
+    if (strcasecmp(extensions, "CELL") == 0) {
+        return PM_FPA_LEVEL_CELL;
+    }
+    if (strcasecmp(extensions, "CHIP") == 0) {
+        return PM_FPA_LEVEL_CHIP;
+    }
+    if (strcasecmp(extensions, "FPA") == 0) {
+        return PM_FPA_LEVEL_FPA;
+    }
+    if (strcasecmp(extensions, "NONE") == 0) {
+        return PM_FPA_LEVEL_NONE;
+    }
+    // No idea what it is
+    psError(PS_ERR_IO, true, "EXTENSIONS (%s) in FILE information is not FPA, CHIP, CELL or NONE.\n",
+            extensions);
+    return PM_FPA_LEVEL_NONE;
+}
+
+// Generate HDU for cells belonging to a chip --- just an iterator
+static bool generateForCells(pmChip *chip // Chip for which to generate HDUs
+                            )
+{
+    psArray *cells = chip->cells;       // Array of cells
+    bool status = true;                 // Status of HDU generation
+    for (long i = 0; i < cells->n; i++) {
+        status |= pmHDUGenerateForCell(cells->data[i]);
+    }
+    return status;
+}
+
+// Generate HDU for chips belonging to an FPA --- just an iterator
+static bool generateForChips(pmFPA *fpa // FPA for which to generate HDUs
+                            )
+{
+    psArray *chips = fpa->chips;        // Array of chips
+    bool status = true;                 // Status of HDU generation
+    for (long i = 0; i < chips->n; i++) {
+        status |= pmHDUGenerateForChip(chips->data[i]);
+    }
+    return status;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmHDUGenerateForCell(pmCell *cell)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+
+    // Get the HDU and a list of cells below it
+    pmHDU *hdu = pmHDUFromCell(cell); // The HDU in the cell
+    if (!hdu) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Can't find an HDU for cell.\n");
+        return false;
+    }
+    if (hdu->images && hdu->masks && hdu->weights) {
+        // It's already here!
+        return true;
+    }
+
+    pmFPALevel extLevel = extensionLevel(hdu);
+    switch (extLevel) {
+    case PM_FPA_LEVEL_NONE:
+    case PM_FPA_LEVEL_CELL: {
+            psList *cells = psListAlloc(NULL); // List of cells below the HDU
+
+            if (cell->hdu) {
+                psListAdd(cells, PS_LIST_TAIL, cell);
+            } else {
+                pmChip *chip = cell->parent;    // The parent chip
+                if (chip->hdu) {
+                    addCellsFromChip(cells, chip);
+                } else {
+                    pmFPA *fpa = chip->parent;  // The parent FPA
+                    if (fpa->hdu) {
+                        addCellsFromFPA(cells, fpa);
+                    }
+                }
+            }
+            if (cells->n == 0) {
+                // Nothing to do
+                return true;
+            }
+
+            return generateHDU(hdu, cells);
+        }
+    case PM_FPA_LEVEL_CHIP:
+    case PM_FPA_LEVEL_FPA:
+        return pmHDUGenerateForChip(cell->parent);
+    default:
+        psAbort("Shouldn't ever get here: check your camera format configuration.\n");
+    }
+    return false;
+}
+
+bool pmHDUGenerateForChip(pmChip *chip)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+
+    // Get the HDU and a list of cells below it
+    pmHDU *hdu = pmHDUFromChip(chip);   // The HDU in the chip
+    if (!hdu) {
+        // Nothing here; need to look further down
+        return generateForCells(chip);
+    }
+    if (hdu->images && hdu->masks && hdu->weights) {
+        // It's already here!
+        return true;
+    }
+
+    pmFPALevel extLevel = extensionLevel(hdu);
+    switch (extLevel) {
+    case PM_FPA_LEVEL_CELL:
+        // Work on lower levels
+        return generateForCells(chip);
+    case PM_FPA_LEVEL_NONE:
+    case PM_FPA_LEVEL_CHIP: {
+            // Work on this level
+            psList *cells = psListAlloc(NULL);  // List of cells below the HDU
+            if (chip->hdu) {
+                addCellsFromChip(cells, chip);
+            } else {
+                pmFPA *fpa = chip->parent;  // The parent FPA
+                if (fpa->hdu) {
+                    addCellsFromFPA(cells, fpa);
+                }
+            }
+            if (cells->n == 0) {
+                // Nothing to do
+                return true;
+            }
+
+            return generateHDU(hdu, cells);
+        }
+    case PM_FPA_LEVEL_FPA:
+        // Work on higher levels
+        return pmHDUGenerateForFPA(chip->parent);
+    default:
+        psAbort("Shouldn't ever get here: check your camera format configuration.\n");
+    }
+    return false;
+}
+
+
+bool pmHDUGenerateForFPA(pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+
+    // Get the HDU and a list of cells below it
+    pmHDU *hdu = pmHDUFromFPA(fpa);     // The HDU in the FPA
+    if (!hdu) {
+        // Nothing here; need to look further down
+        return generateForChips(fpa);
+    }
+    if (hdu->images && hdu->masks && hdu->weights) {
+        // It's already here!
+        return true;
+    }
+
+    pmFPALevel extLevel = extensionLevel(hdu);
+    switch (extLevel) {
+    case PM_FPA_LEVEL_CELL:
+    case PM_FPA_LEVEL_CHIP:
+        // Work on lower levels
+        return generateForChips(fpa);
+    case PM_FPA_LEVEL_NONE:
+    case PM_FPA_LEVEL_FPA: {
+            // Work on this level
+            psList *cells = psListAlloc(NULL); // List of cells below the HDU
+            if (fpa->hdu) {
+                addCellsFromFPA(cells, fpa);
+            }
+            if (cells->n == 0) {
+                // Nothing to do
+                return true;
+            }
+
+            return generateHDU(hdu, cells);
+        }
+    default:
+        psAbort("Shouldn't ever get here: check your camera format configuration.\n");
+    }
+    return false;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmHDUGenerate.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmHDUGenerate.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmHDUGenerate.h	(revision 22157)
@@ -0,0 +1,53 @@
+/* @file pmHDUGenerate.h
+ * @brief Generate HDU pixels from FPA components that have pixels
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-03-30 21:12:56 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_HDU_GENERATE_H
+#define PM_HDU_GENERATE_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+/// Generate an HDU (with CELL.TRIMSEC, CELL.BIASSEC and pixels) for a cell with pixels
+///
+/// The write functions for the FPA hierarchy use pmHDUWrite, which assumes that the images in the readouts
+/// are subimages of the pixels in the HDU structure.  If this is not the case, the HDU pixels can be
+/// generated using some simple assumptions.  Splices the images and overscans together without regard for
+/// CELL.X0 and CELL.Y0 (for a proper mosaic, see pmFPAMosaic), though it should respect CELL.READDIR (so that
+/// the bias and trim sections match properly).  A warning may be generated after running this function if the
+/// bias and trim sections are specified in the camera format by default values rather than in the header.
+/// Failure of this function is often due to a bad camera format file.
+bool pmHDUGenerateForCell(pmCell *cell  ///< The cell for which to generate an HDU
+                         );
+
+/// Generate an HDU (with CELL.TRIMSEC, CELL.BIASSEC and pixels) for a cell with pixels
+///
+/// The write functions for the FPA hierarchy use pmHDUWrite, which assumes that the images in the readouts
+/// are subimages of the pixels in the HDU structure.  If this is not the case, the HDU pixels can be
+/// generated using some simple assumptions.  Splices the images and overscans together without regard for
+/// CELL.X0 and CELL.Y0 (for a proper mosaic, see pmFPAMosaic), though it should respect CELL.READDIR (so that
+/// the bias and trim sections match properly).  A warning may be generated after running this function if the
+/// bias and trim sections are specified in the camera format by default values rather than in the header.
+/// Failure of this function is often due to a bad camera format file.
+bool pmHDUGenerateForChip(pmChip *chip  ///< The chip for which to generate an HDU
+                         );
+
+// Generate an HDU (with CELL.TRIMSEC, CELL.BIASSEC and pixels) from an FPA with pixels
+///
+/// The write functions for the FPA hierarchy use pmHDUWrite, which assumes that the images in the readouts
+/// are subimages of the pixels in the HDU structure.  If this is not the case, the HDU pixels can be
+/// generated using some simple assumptions.  Splices the images and overscans together without regard for
+/// CELL.X0 and CELL.Y0 (for a proper mosaic, see pmFPAMosaic), though it should respect CELL.READDIR (so that
+/// the bias and trim sections match properly).  A warning may be generated after running this function if the
+/// bias and trim sections are specified in the camera format by default values rather than in the header.
+/// Failure of this function is often due to a bad camera format file.
+bool pmHDUGenerateForFPA(pmFPA *fpa     ///< The fpa for which to generate an HDU
+                        );
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmHDUUtils.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmHDUUtils.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmHDUUtils.c	(revision 22157)
@@ -0,0 +1,172 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmHDUUtils.h"
+
+pmHDU *pmHDUGetFirst (const pmFPA *fpa) {
+
+    // XXX we probably should have an indicator in pmFPA about the depths.
+
+    if (!fpa) return NULL;
+    if (fpa->hdu) return fpa->hdu;
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+	pmChip *chip = fpa->chips->data[i];
+	if (!chip) continue;
+	if (chip->hdu) return chip->hdu;
+	if (!chip->cells) continue;
+	for (int j = 0; j < chip->cells->n; j++) {
+	    pmCell *cell = chip->cells->data[j];
+	    if (!cell) continue;
+	    if (cell->hdu) return cell->hdu;
+	}
+    }
+    return NULL;
+}
+
+pmHDU *pmHDUFromFPA(const pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+    return fpa->hdu;
+}
+
+pmHDU *pmHDUFromChip(const pmChip *chip)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, NULL);
+
+    pmHDU *hdu = chip->hdu;             // The HDU information
+    if (!hdu) {
+        hdu = pmHDUFromFPA(chip->parent); // Grab HDU info from the FPA
+    }
+
+    return hdu;
+}
+
+pmHDU *pmHDUFromCell(const pmCell *cell)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+
+    pmHDU *hdu = cell->hdu;             // The HDU information
+    if (!hdu) {
+        hdu = pmHDUFromChip(cell->parent); // Grab HDU info from the chip
+    }
+
+    return hdu;
+}
+
+pmHDU *pmHDUFromReadout(const pmReadout *readout)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, NULL);
+
+    pmCell *cell = readout->parent; // cell containing this readout;
+    pmHDU *hdu = pmHDUFromCell(cell);
+    return hdu;
+}
+
+// Get the lowest HDU
+pmHDU *pmHDUGetLowest(const pmFPA *fpa, const pmChip *chip, const pmCell *cell)
+{
+    pmHDU *hdu = NULL;          // The HDU that's at the lowest level
+    if (cell) {
+        hdu = pmHDUFromCell(cell);
+    } else if (chip) {
+        hdu = pmHDUFromChip(chip);
+    } else if (fpa) {
+        hdu = pmHDUFromFPA(fpa);
+    }
+
+    return hdu;
+}
+
+// Get the highest HDU
+pmHDU *pmHDUGetHighest(const pmFPA *fpa, const pmChip *chip, const pmCell *cell)
+{
+    pmHDU *hdu = NULL;          // The HDU that's at the highest level
+    if (fpa) {
+        hdu = pmHDUFromFPA(fpa);
+    }
+    if (!hdu && chip) {
+        hdu = pmHDUFromChip(chip);
+    }
+    if (!hdu && cell) {
+        hdu = pmHDUFromCell(cell);
+    }
+
+    return hdu;
+}
+
+// Print spaces to indent
+#define INDENT(FILE, LEVEL) \
+{ \
+    for (int i = 0; i < (LEVEL); i++) { \
+        fprintf(FILE, " "); \
+    } \
+}
+
+void pmHDUPrint(FILE *fd, const pmHDU *hdu, int level, bool header)
+{
+    PS_ASSERT_PTR_NON_NULL(hdu,);
+
+    INDENT(fd, level);
+    if (hdu->blankPHU) {
+        fprintf(fd, "HDU: (PHU)\n");
+    } else {
+        fprintf(fd, "HDU: %s\n", hdu->extname);
+    }
+
+    INDENT(fd, level + 1);
+    fprintf(fd, "Format: %p\n", hdu->format);
+    if (header) {
+        INDENT(fd, level + 1);
+        if (hdu->header) {
+            fprintf(fd, "Header:\n");
+            psMetadataPrint(fd, hdu->header, level + 2);
+        } else {
+            fprintf(fd, "No header.\n");
+        }
+    }
+
+    INDENT(fd, level + 1);
+    if (hdu->images) {
+        fprintf(fd, "Images:\n");
+        for (long i = 0; i < hdu->images->n; i++) {
+            psImage *image = hdu->images->data[i]; // Image of interest
+            INDENT(fd, level + 2);
+            fprintf(fd, "%ld: %dx%d\n", i, image->numCols, image->numRows);
+        }
+    } else {
+        fprintf(fd, "NO images.\n");
+    }
+
+    INDENT(fd, level + 1);
+    if (hdu->masks) {
+        fprintf(fd, "Masks:\n");
+        for (long i = 0; i < hdu->masks->n; i++) {
+            psImage *mask = hdu->masks->data[i]; // Mask of interest
+            INDENT(fd, level + 2);
+            fprintf(fd, "%ld: %dx%d\n", i, mask->numCols, mask->numRows);
+        }
+    } else {
+        fprintf(fd, "NO masks.\n");
+    }
+
+    INDENT(fd, level + 1);
+    if (hdu->weights) {
+        fprintf(fd, "Weights:\n");
+        for (long i = 0; i < hdu->weights->n; i++) {
+            psImage *weight = hdu->weights->data[i]; // Weight image of interest
+            INDENT(fd, level + 2);
+            fprintf(fd, "%ld: %dx%d\n", i, weight->numCols, weight->numRows);
+        }
+    } else {
+        fprintf(fd, "NO weights.\n");
+    }
+
+    return;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmHDUUtils.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmHDUUtils.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmHDUUtils.h	(revision 22157)
@@ -0,0 +1,61 @@
+/* @file pmHDUUtils.h
+ * @brief Utility functions for working with an HDU
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.9.46.1 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-07-07 04:23:26 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_HDU_UTILS_H
+#define PM_HDU_UTILS_H
+
+/// @addtogroup Camera Camera Layout
+/// @{
+
+/// Get the first HDU encountered in the hierarchy
+pmHDU *pmHDUGetFirst (const pmFPA *fpa);
+
+/// Get the lowest HDU in the hierarchy
+///
+/// The lowest HDU in the hierarchy will be the one with the actual pixels (if all levels are supplied).
+pmHDU *pmHDUGetLowest(const pmFPA *fpa, ///< The FPA
+                      const pmChip *chip, ///< The chip, or NULL
+                      const pmCell *cell ///< The cell, or NULL
+                     );
+
+/// Get the highest HDU in the hierarchy
+///
+/// The highest HDU in the hierarchy will be the PHU (might get NULL if not all levels are supplied)
+pmHDU *pmHDUGetHighest(const pmFPA *fpa, ///< The FPA
+                       const pmChip *chip, ///< The chip, or NULL
+                       const pmCell *cell ///< The cell, or NULL
+                      );
+
+/// Given an FPA, return the HDU (or NULL if all HDUs reside below the FPA)
+pmHDU *pmHDUFromFPA(const pmFPA *fpa    ///< FPA for which to find HDU
+                   );
+
+/// Given a chip, return the HDU (or NULL if it resides below the chip)
+pmHDU *pmHDUFromChip(const pmChip *chip ///< Chip for which to find HDU
+                    );
+
+/// Given a cell, return the HDU
+pmHDU *pmHDUFromCell(const pmCell *cell ///< Cell for which to find HDU
+                    );
+
+/// Given a readout, return the HDU
+pmHDU *pmHDUFromReadout(const pmReadout *readout ///< Readout for which to find HDU
+                       );
+
+/// Print details about an HDU
+///
+/// This is intended for testing or development use.
+void pmHDUPrint(FILE *fd,               ///< File descriptor to which to print
+                const pmHDU *hdu,       ///< HDU to print
+                int level,              ///< Level at which to print
+                bool header             ///< Print header?
+               );
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmReadoutFake.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmReadoutFake.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmReadoutFake.c	(revision 22157)
@@ -0,0 +1,156 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+
+#include "pmMoments.h"
+#include "pmResiduals.h"
+#include "pmGrowthCurve.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmModelClass.h"
+#include "pmPeaks.h"
+#include "pmSource.h"
+#include "pmSourceUtils.h"
+#include "pmModelUtils.h"
+
+#include "pmReadoutFake.h"
+
+#define MODEL_TYPE "PS_MODEL_RGAUSS"    // Type of model to use
+#define MAX_AXIS_RATIO 20.0             // Maximum axis ratio for PSF model
+#define SOURCE_MASK (PM_SOURCE_MODE_FAIL | PM_SOURCE_MODE_DEFECT | PM_SOURCE_MODE_SATURATED | \
+                     PM_SOURCE_MODE_CR_LIMIT) // Mask to apply to input sources
+
+
+// Given an object model, circularise it by setting the axes to be identical
+static bool circulariseModel(pmModel *model // Model to circularise
+    )
+{
+    assert(model);
+
+    psF32 *params = model->params->data.F32; // Model parameters
+    psEllipseAxes axes = pmPSF_ModelToAxes(params, MAX_AXIS_RATIO); // Ellipse axes
+    // Curiously, the minor axis can be larger than the major axis, so need to check.
+    if (axes.major >= axes.minor) {
+        axes.minor = axes.major;
+    } else {
+        axes.major = axes.minor;
+    }
+    return pmPSF_AxesToModel(params, axes);
+}
+
+bool pmReadoutFakeFromSources(pmReadout *readout, int numCols, int numRows, const psArray *sources,
+                              const psVector *xOffset, const psVector *yOffset, const pmPSF *psf,
+                              float minFlux, int radius, bool circularise)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_INT_LARGER_THAN(numCols, 0, false);
+    PS_ASSERT_INT_LARGER_THAN(numRows, 0, false);
+    PS_ASSERT_ARRAY_NON_NULL(sources, false);
+
+    if (xOffset || yOffset) {
+        PS_ASSERT_VECTOR_NON_NULL(xOffset, false);
+        PS_ASSERT_VECTOR_NON_NULL(yOffset, false);
+        PS_ASSERT_VECTORS_SIZE_EQUAL(xOffset, yOffset, false);
+        PS_ASSERT_VECTOR_TYPE(xOffset, PS_TYPE_S32, false);
+        PS_ASSERT_VECTOR_TYPE_EQUAL(xOffset, yOffset, false);
+        if (xOffset->n != sources->n) {
+            psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                    "Number of offset vectors (%ld) and sources (%ld) doesn't match",
+                    xOffset->n, sources->n);
+            return false;
+        }
+    }
+    PS_ASSERT_PTR_NON_NULL(psf, false);
+    if (radius > 0 && isfinite(minFlux) && minFlux > 0.0) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Cannot define both minimum flux and fixed radius.");
+        return false;
+    }
+
+    readout->image = psImageRecycle(readout->image, numCols, numRows, PS_TYPE_F32);
+    psImageInit(readout->image, 0);
+
+    int numSources = sources->n;          // Number of stars
+
+    pmModel *fakeModel = pmModelFromPSFforXY(psf, (float)numCols / 2.0, (float)numRows / 2.0,
+                                             1.0); // Fake model, with central intensity of 1.0
+
+    float flux0 = fakeModel->modelFlux(fakeModel->params); // Flux for central intensity of 1.0
+
+    if (circularise && !circulariseModel(fakeModel)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to circularise PSF model.");
+        psFree(fakeModel);
+        return false;
+    }
+    psFree(fakeModel);
+
+    for (int i = 0; i < numSources; i++) {
+        pmSource *source = sources->data[i]; // Source of interest
+        if (source->mode & SOURCE_MASK) {
+            continue;
+        }
+        if (!isfinite(source->psfMag)) {
+            continue;
+        }
+        float x, y;                     // Coordinates of source
+        if (source->modelPSF) {
+            x = source->modelPSF->params->data.F32[PM_PAR_XPOS];
+            y = source->modelPSF->params->data.F32[PM_PAR_YPOS];
+        } else {
+            x = source->peak->xf;
+            y = source->peak->yf;
+        }
+
+        pmModel *fakeModel = pmModelFromPSFforXY(psf, x, y, powf(10.0, -0.4 * source->psfMag) / flux0);
+        if (!fakeModel) {
+            psErrorClear();
+            continue;
+        }
+        if (circularise && !circulariseModel(fakeModel)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to circularise PSF model.");
+            psFree(fakeModel);
+            return false;
+        }
+
+        psTrace("psModules.camera", 10, "Adding source at %f,%f with flux %f\n",
+                fakeModel->params->data.F32[PM_PAR_XPOS], fakeModel->params->data.F32[PM_PAR_YPOS],
+                fakeModel->params->data.F32[PM_PAR_I0]);
+
+        pmSource *fakeSource = pmSourceAlloc(); // Fake source to generate
+        fakeSource->peak = pmPeakAlloc(x, y, fakeModel->params->data.F32[PM_PAR_I0], PM_PEAK_LONE);
+        float fakeRadius = radius > 0 ? radius : fakeModel->modelRadius(fakeModel->params, minFlux); // Radius
+
+        if (xOffset) {
+            if (!pmSourceDefinePixels(fakeSource, readout, x + xOffset->data.S32[i],
+                                      y + yOffset->data.S32[i], fakeRadius)) {
+                psErrorClear();
+                continue;
+            }
+            if (!pmModelAddWithOffset(fakeSource->pixels, NULL, fakeModel, PM_MODEL_OP_FULL, 0,
+                                      - xOffset->data.S32[i], - yOffset->data.S32[i])) {
+                psErrorClear();
+                continue;
+            }
+        } else {
+            if (!pmSourceDefinePixels(fakeSource, readout, x, y, fakeRadius)) {
+                psErrorClear();
+                continue;
+            }
+            if (!pmModelAdd(fakeSource->pixels, NULL, fakeModel, PM_MODEL_OP_FULL, 0)) {
+                psErrorClear();
+                continue;
+            }
+        }
+        psFree(fakeSource);
+        psFree(fakeModel);
+    }
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmReadoutFake.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmReadoutFake.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmReadoutFake.h	(revision 22157)
@@ -0,0 +1,26 @@
+#ifndef PM_READOUT_FAKE_H
+#define PM_READOUT_FAKE_H
+
+#include <pslib.h>
+#include <pmHDU.h>
+#include <pmFPA.h>
+
+#include <pmMoments.h>
+#include <pmResiduals.h>
+#include <pmGrowthCurve.h>
+#include <pmTrend2D.h>
+#include <pmPSF.h>
+
+/// Generate a fake readout from an array of sources
+bool pmReadoutFakeFromSources(pmReadout *readout, ///< Output readout, or NULL
+                              int numCols, int numRows, ///< Dimension of image
+                              const psArray *sources, ///< Array of pmSource
+                              const psVector *xOffset, ///< x offsets for sources (source -> img), or NULL
+                              const psVector *yOffset, ///< y offsets for sources (source -> img), or NULL
+                              const pmPSF *psf, ///< PSF for sources
+                              float minFlux, ///< Minimum flux to bother about; for setting source radius
+                              int radius, ///< Fixed radius for sources
+                              bool circularise ///< Circularise PSF model?
+    );
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmReadoutStack.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmReadoutStack.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmReadoutStack.c	(revision 22157)
@@ -0,0 +1,184 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmReadoutStack.h"
+
+psImage *pmReadoutAnalysisImage(pmReadout *readout, // Readout containing image
+                                const char *name, // Name of image in analysis metadata
+                                int numCols, int numRows, // Expected size of image
+                                psElemType type, // Expected type of image
+                                double init // Initial value
+    )
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_STRING_NON_EMPTY(name, false);
+
+    bool mdok;                          // Status of MD lookup
+    psImage *image = psMetadataLookupPtr(&mdok, readout->analysis, name);
+    if (!image) {
+        image = psImageAlloc(numCols, numRows, type);
+        psMetadataAddImage(readout->analysis, PS_LIST_TAIL, name, 0, "Analysis image from " __FILE__, image);
+        psImageInit(image, init);
+        return image;
+    }
+    if (image->numCols != numCols || image->numRows != numRows) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Analysis image %s has incorrect size (%dx%d vs %dx%d)",
+                name, image->numCols, image->numRows, numCols, numRows);
+        return NULL;
+    }
+    if (image->type.type != type) {
+        psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Analysis image %s has incorrect type (%x vs %x)",
+                name, image->type.type, type);
+        return NULL;
+    }
+    return psMemIncrRefCounter(image);
+}
+
+bool pmReadoutUpdateSize(pmReadout *readout, int minCols, int minRows,
+                         int numCols, int numRows, bool mask, bool weight,
+                         psMaskType blank)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+
+    if (readout->image) {
+        readout->col0 = PS_MIN(minCols, readout->col0);
+        readout->row0 = PS_MIN(minRows, readout->row0);
+    } else {
+        readout->col0 = minCols;
+        readout->row0 = minRows;
+    }
+
+    // (reAllocate the images
+    if (!readout->image) {
+        readout->image = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+        psImageInit(readout->image, NAN);
+    }
+    if (readout->image->numCols < numCols || readout->image->numRows < numRows) {
+        // Generate the new output image by extending the current one, or making a whole new one
+        psImage *newImage = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+        psImageInit(newImage, NAN);
+        psImageOverlaySection(newImage, readout->image, readout->col0, readout->row0, "=");
+        psFree(readout->image);
+        readout->image = newImage;
+    }
+
+    if (mask) {
+        if (!readout->mask) {
+            readout->mask = psImageAlloc(numCols, numRows, PS_TYPE_MASK);
+            psImageInit(readout->mask, blank);
+        }
+        if (readout->mask->numCols < numCols || readout->mask->numRows < numRows) {
+            psImage *newMask = psImageAlloc(numCols, numRows, PS_TYPE_MASK);
+            psImageInit(newMask, blank);
+            psImageOverlaySection(newMask, readout->mask, readout->col0, readout->row0, "=");
+            psFree(readout->mask);
+            readout->mask = newMask;
+        }
+    }
+
+    if (weight) {
+        if (!readout->weight) {
+            readout->weight = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+            psImageInit(readout->weight, NAN);
+        }
+        if (readout->weight->numCols < numCols || readout->weight->numRows < numRows) {
+            psImage *newWeight = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+            psImageInit(newWeight, NAN);
+            psImageOverlaySection(newWeight, readout->weight, readout->col0, readout->row0, "=");
+            psFree(readout->weight);
+            readout->weight = newWeight;
+        }
+    }
+
+    return true;
+}
+
+bool pmReadoutStackValidate(int *minInputColsPtr, int *maxInputColsPtr, int *minInputRowsPtr,
+                            int *maxInputRowsPtr, int *numColsPtr, int *numRowsPtr,
+                            const psArray *inputs)
+{
+    PS_ASSERT_ARRAY_NON_NULL(inputs, false);
+    PS_ASSERT_PTR_NON_NULL(minInputColsPtr, false);
+    PS_ASSERT_PTR_NON_NULL(maxInputColsPtr, false);
+    PS_ASSERT_PTR_NON_NULL(minInputRowsPtr, false);
+    PS_ASSERT_PTR_NON_NULL(maxInputRowsPtr, false);
+    PS_ASSERT_PTR_NON_NULL(numColsPtr, false);
+    PS_ASSERT_PTR_NON_NULL(numRowsPtr, false);
+
+    // Step through each readout in the input image list to determine how big of an output image is needed to
+    // combine these input images.
+    int maxInputCols = 0;               // The largest input column value
+    int maxInputRows = 0;               // The largest input row value
+    int minInputCols = INT_MAX;         // The smallest input column value
+    int minInputRows = INT_MAX;         // The smallest input row value
+    int xSize = 0, ySize = 0;           // The size of the output image
+
+    int xMin = INT_MAX;
+    int yMin = INT_MAX;
+    int xMax = 0;
+    int yMax = 0;
+
+    bool valid = false;                 // Do we have a single valid input?
+    for (long i = 0; i < inputs->n; i++) {
+        pmReadout *readout = inputs->data[i]; // Readout of interest
+
+        if (!readout) {
+            continue;
+        }
+        if (!readout->image) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "Input readout %ld has NULL image.\n", i);
+            return false;
+        }
+
+        // use the trimsec to define the max full range of the output pixels
+        pmCell *cell = readout->parent; // The parent cell
+        bool mdok = true;       // Status of MD lookup
+        psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim section
+        if (!mdok || !trimsec || psRegionIsNaN(*trimsec)) {
+            psWarning("CELL.TRIMSEC is not set for readout %ld --- ignored.\n", i);
+        } else {
+            xSize = PS_MAX(xSize, trimsec->x1 - trimsec->x0);
+            ySize = PS_MAX(ySize, trimsec->y1 - trimsec->y0);
+            xMin  = PS_MIN(xMin,  trimsec->x0);
+            xMax  = PS_MAX(xMax,  trimsec->x1);
+            yMin  = PS_MIN(yMin,  trimsec->y0);
+            yMax  = PS_MAX(yMax,  trimsec->y1);
+        }
+
+        valid = true;
+
+        // Range of pixels on output images
+        minInputCols = PS_MAX(xMin, PS_MIN(minInputCols, readout->col0));
+        maxInputCols = PS_MIN(xMax, PS_MAX(maxInputCols, readout->col0 + readout->image->numCols));
+        minInputRows = PS_MAX(yMin, PS_MIN(minInputRows, readout->row0));
+        maxInputRows = PS_MIN(yMax, PS_MAX(maxInputRows, readout->row0 + readout->image->numRows));
+        psTrace("psModules.camera", 7, "Readout %ld: offset %d,%d; size %dx%d\n", i,
+                readout->col0, readout->row0, readout->image->numCols, readout->image->numRows);
+    }
+
+    if (minInputColsPtr) {
+        *minInputColsPtr = minInputCols;
+    }
+    if (maxInputColsPtr) {
+        *maxInputColsPtr = maxInputCols;
+    }
+    if (minInputRowsPtr) {
+        *minInputRowsPtr = minInputRows;
+    }
+    if (maxInputRowsPtr) {
+        *maxInputRowsPtr = maxInputRows;
+    }
+    if (numColsPtr) {
+        *numColsPtr = xSize;
+    }
+    if (numRowsPtr) {
+        *numRowsPtr = ySize;
+    }
+
+    return valid;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmReadoutStack.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmReadoutStack.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/camera/pmReadoutStack.h	(revision 22157)
@@ -0,0 +1,34 @@
+#ifndef PM_READOUT_STACK_H
+#define PM_READOUT_STACK_H
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+
+#define PM_READOUT_STACK_ANALYSIS_COUNT "STACK.COUNT" // Name for count image in analysis metadata
+#define PM_READOUT_STACK_ANALYSIS_SIGMA "STACK.SIGMA" // Name for sigma image in analysis metadata
+
+/// Update an output readout (for a stack) with the correct col0,row0 and the image size
+bool pmReadoutUpdateSize(pmReadout *readout, ///< Readout which to update
+                         int minCols, int minRows, ///< Minimum coordinates
+                         int numCols, int numRows, ///< Size of images
+                         bool mask,     ///< Worry about the mask?
+                         bool weight,   ///< Worry about the weight?
+                         psMaskType blank ///< Mask value to give to blank pixels
+    );
+
+/// Determine how large an output image is needed to combine the input readouts
+bool pmReadoutStackValidate(int *minInputColsPtr, int *maxInputColsPtr, ///< Min and max size in x
+                            int *minInputRowsPtr, int *maxInputRowsPtr, ///< Min and max size in y
+                            int *numColsPtr, int *numRowsPtr, ///< Size of image
+                            const psArray *inputs ///< Array of pmReadouts
+    );
+
+/// Return an image from analysis metadata, produced while stacking
+psImage *pmReadoutAnalysisImage(pmReadout *readout, // Readout containing image
+                                const char *name, // Name of image in analysis metadata
+                                int numCols, int numRows, // Expected size of image
+                                psElemType type, // Expected type of image
+                                double init // Initial value
+    );
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/.cvsignore	(revision 22157)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.la
+*.lo
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/Makefile.am	(revision 22157)
@@ -0,0 +1,23 @@
+noinst_LTLIBRARIES = libpsmodulesconcepts.la
+
+libpsmodulesconcepts_la_CPPFLAGS = $(SRCINC) $(PSMODULES_CFLAGS)
+libpsmodulesconcepts_la_LDFLAGS  = -release $(PACKAGE_VERSION)
+libpsmodulesconcepts_la_SOURCES  = \
+	pmConcepts.c \
+	pmConceptsAverage.c \
+	pmConceptsRead.c \
+	pmConceptsWrite.c \
+	pmConceptsStandard.c \
+	pmConceptsPhotcode.c \
+	pmConceptsUpdate.c
+
+pkginclude_HEADERS = \
+	pmConcepts.h \
+	pmConceptsAverage.h \
+	pmConceptsRead.h \
+	pmConceptsWrite.h \
+	pmConceptsStandard.h \
+	pmConceptsPhotcode.h \
+	pmConceptsUpdate.h
+
+CLEANFILES = *~
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConcepts.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConcepts.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConcepts.c	(revision 22157)
@@ -0,0 +1,950 @@
+// XXX *REALLY* need generic "concept update" and "concept read" functions that handles the type transparently
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <pslib.h>
+#include <string.h>
+
+#include "pmConfig.h"
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmHDUUtils.h"
+#include "pmConcepts.h"
+#include "pmConceptsRead.h"
+#include "pmConceptsWrite.h"
+#include "pmConceptsStandard.h"
+#include "pmConceptsUpdate.h"
+
+static bool conceptsInitialised = false;// Have concepts been read?
+static psMetadata *conceptsFPA = NULL;  // Known concepts for FPA
+static psMetadata *conceptsChip = NULL; // Known concepts for chip
+static psMetadata *conceptsCell = NULL; // Known concepts for cell
+
+// Return the appropriate concepts metadata, given the level
+static psMetadata *conceptsFromLevel(pmFPALevel level)
+{
+    switch (level) {
+    case PM_FPA_LEVEL_FPA:
+        return conceptsFPA;
+    case PM_FPA_LEVEL_CHIP:
+        return conceptsChip;
+    case PM_FPA_LEVEL_CELL:
+        return conceptsCell;
+    default:
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Invalid concept level provided: %d\n", level);
+        return NULL;
+    }
+}
+
+// Free a concept
+static void conceptSpecFree(pmConceptSpec *spec)
+{
+    psFree(spec->blank);
+}
+
+pmConceptSpec *pmConceptSpecAlloc(psMetadataItem *blank, pmConceptParseFunc parse,
+                                  pmConceptFormatFunc format, bool required)
+{
+    pmConceptSpec *spec = psAlloc(sizeof(pmConceptSpec));
+    psMemSetDeallocator(spec, (psFreeFunc)conceptSpecFree);
+
+    spec->blank = psMemIncrRefCounter(blank);
+    spec->parse = parse;
+    spec->format = format;
+    spec->required = required;
+
+    return spec;
+}
+
+psList *pmConceptsList(pmFPALevel level)
+{
+    if (!conceptsInitialised) {
+        pmConceptsInit();
+    }
+
+    // Get the appropriate concepts
+    psMetadata *concepts = conceptsFromLevel(level); // Metadata of concepts specs
+    if (!concepts) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Invalid concept level provided: %d\n", level);
+        return NULL;
+    }
+
+    // Pull out the names
+    psList *list = psListAlloc(NULL);   // List of concepts' names
+    psMetadataIterator *iter = psMetadataIteratorAlloc(concepts, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *item;               // Item from iteration
+    while ((item = psMetadataGetAndIncrement(iter))) {
+        psListAdd(list, PS_LIST_TAIL, item->name);
+    }
+    psFree(iter);
+    return list;
+}
+
+bool pmConceptGetRequired(const char *name, pmFPALevel level)
+{
+    PS_ASSERT_STRING_NON_EMPTY(name, false);
+    if (!conceptsInitialised) {
+        pmConceptsInit();
+    }
+
+    psMetadata *concepts = conceptsFromLevel(level); // The metadata of known concepts
+
+    bool mdok;                          // Status of MD lookup
+    pmConceptSpec *spec = psMetadataLookupPtr(&mdok, concepts, name); // The specification
+    if (!spec) {
+        // Won't throw an error, because we can't distinguish an error from the desired result.
+        // However, that doesn't really matter, because if we can't find it, then it can't be required!
+        return false;
+    }
+
+    return spec->required;
+}
+
+bool pmConceptSetRequired(const char *name, pmFPALevel level, bool required)
+{
+    PS_ASSERT_STRING_NON_EMPTY(name, false);
+
+    if (!conceptsInitialised) {
+        pmConceptsInit();
+    }
+
+    psMetadata *concepts = conceptsFromLevel(level); // The metadata of known concepts
+
+    bool mdok;                          // Status of MD lookup
+    pmConceptSpec *spec = psMetadataLookupPtr(&mdok, concepts, name); // The specification
+    if (!spec) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to find defined concept %s in level %d.",
+                name, level);
+        return false;
+    }
+    spec->required = required;
+
+    return true;
+}
+
+bool pmConceptRegister(psMetadataItem *blank, pmConceptParseFunc parse,
+                        pmConceptFormatFunc format, bool required, pmFPALevel level)
+{
+    PS_ASSERT_PTR_NON_NULL(blank, false);
+
+    if (!conceptsInitialised) {
+        pmConceptsInit();
+    }
+
+    psMetadata *target = conceptsFromLevel(level); // The metadata of known concepts to write to
+    if (!target) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, false,
+                "Unable to register concept at invalid concept level.");
+        return false;
+    }
+
+    pmConceptSpec *spec = pmConceptSpecAlloc(blank, parse, format, required); // The concept specification
+    psMetadataAdd(target, PS_LIST_TAIL, blank->name, PS_DATA_UNKNOWN | PS_META_REPLACE,
+                  "Concepts specification", spec);
+    psFree(spec);                       // Drop reference
+
+    return true;
+}
+
+
+// Set all registered concepts to blank value for the specified level
+static bool conceptsBlank(psMetadata **specs, // One of the concepts specifications
+                          psMetadata *target // Place to install the concepts
+                         )
+{
+    assert(specs);
+    assert(target);
+
+    if (!conceptsInitialised) {
+        pmConceptsInit();
+    }
+    psMetadataIterator *specsIter = psMetadataIteratorAlloc(*specs, PS_LIST_HEAD, NULL); // Iterator on specs
+    psMetadataItem *specItem = NULL;    // Item from the specs metadata
+    while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+        psTrace("psModules.concepts", 9, "Blanking %s...\n", specItem->name);
+        pmConceptSpec *spec = specItem->data.V; // The specification
+        psMetadataItem *blank = spec->blank; // The concept
+        psMetadataItem *copy = NULL;    // Copy of the blank concept
+        // Trap the lists, which can't be copied in the ordinary way without a warning
+        if (blank->type == PS_DATA_LIST) {
+            copy = psMetadataItemAllocPtr(blank->name, PS_DATA_LIST, blank->comment, blank->data.V);
+        } else {
+            copy = psMetadataItemCopy(blank);
+        }
+        if (!psMetadataAddItem(target, copy, PS_LIST_TAIL, PS_META_REPLACE)) {
+            psLogMsg(__func__, PS_LOG_WARN, "Unable to add blank version of concept %s\n", blank->name);
+        }
+        psFree(copy);                   // Drop reference
+    }
+    psFree(specsIter);
+
+    return true;
+}
+
+
+
+// Read all registered concepts for the specified level
+static bool conceptsRead(psMetadata **specs, // One of the concepts specifications
+                         pmFPA *fpa,    // The FPA
+                         pmChip *chip,  // The chip
+                         pmCell *cell, // The cell
+                         unsigned int *read,     // What's already been read
+                         pmConceptSource source, // The source of the concepts to read
+                         pmConfig *config, // Configuration
+                         psMetadata *target // Place into which to read the concepts
+                        )
+{
+    assert(specs);
+    assert(read);
+    assert(target);
+
+    if (!conceptsInitialised) {
+        pmConceptsInit();
+    }
+
+    // At least one HDU is required for the reading functions
+    pmHDU *hduLow = pmHDUGetLowest(fpa, chip, cell); // Lowest HDU.
+    if (!hduLow) {
+        // Can't do anything --- don't record any success, but don't return an error either
+        return true;
+    }
+    pmHDU *hduHigh = pmHDUGetHighest(fpa, chip, cell); // Highest HDU
+
+    if (cell && (cell->conceptsRead == PM_CONCEPT_SOURCE_NONE)) {
+	pmConceptsBlankCell(cell);
+	cell->conceptsRead = PM_CONCEPT_SOURCE_BLANK;
+    }
+    if (chip && (chip->conceptsRead == PM_CONCEPT_SOURCE_NONE)) {
+	pmConceptsBlankChip(chip);
+	chip->conceptsRead = PM_CONCEPT_SOURCE_BLANK;
+    }
+    if (fpa && (fpa->conceptsRead == PM_CONCEPT_SOURCE_NONE)) {
+	pmConceptsBlankFPA(fpa);
+	fpa->conceptsRead = PM_CONCEPT_SOURCE_BLANK;
+    }
+
+    bool success = true;                // Success in reading concepts?
+    if (source & PM_CONCEPT_SOURCE_CELLS && !(*read & PM_CONCEPT_SOURCE_CELLS) && cell) {
+        if (p_pmConceptsReadFromCells(target, *specs, cell)) {
+            *read |= PM_CONCEPT_SOURCE_CELLS;
+        } else {
+            psError(PS_ERR_UNKNOWN, false, "Error reading concepts from camera configuration.\n");
+            success = false;
+        }
+    }
+
+    if (source & PM_CONCEPT_SOURCE_DEFAULTS && !(*read & PM_CONCEPT_SOURCE_DEFAULTS)) {
+        if (p_pmConceptsReadFromDefaults(target, *specs, fpa, chip, cell)) {
+            *read |= PM_CONCEPT_SOURCE_DEFAULTS;
+        } else {
+            psError(PS_ERR_UNKNOWN, false, "Error reading concepts from defaults.\n");
+            success = false;
+        }
+    }
+
+    if (source & PM_CONCEPT_SOURCE_PHU && !(*read & PM_CONCEPT_SOURCE_PHU) && hduHigh->header) {
+        if (p_pmConceptsReadFromHeader(target, *specs, fpa, chip, cell)) {
+            *read |= PM_CONCEPT_SOURCE_PHU;
+        } else {
+            psError(PS_ERR_UNKNOWN, false, "Error reading concepts from PHU.\n");
+            success = false;
+        }
+    }
+
+    // If there are multiple HDUs, then it may be that one of them hasn't been read yet (hdu->header not set)
+    if (source & PM_CONCEPT_SOURCE_HEADER && !(*read & PM_CONCEPT_SOURCE_HEADER) &&
+        hduLow != hduHigh && hduLow->header) {
+        if (p_pmConceptsReadFromHeader(target, *specs, fpa, chip, cell)) {
+            *read |= PM_CONCEPT_SOURCE_HEADER;
+        } else {
+            psError(PS_ERR_UNKNOWN, false, "Error reading concepts from header.\n");
+            success = false;
+        }
+    }
+
+    #ifdef HAVE_PSDB
+    if (source & PM_CONCEPT_SOURCE_DATABASE && !(*read & PM_CONCEPT_SOURCE_DATABASE)) {
+        if (p_pmConceptsReadFromDatabase(target, *specs, fpa, chip, cell, config)) {
+            *read |= PM_CONCEPT_SOURCE_DATABASE;
+        } else {
+            psError(PS_ERR_UNKNOWN, false, "Error reading concepts from database.\n");
+            success = false;
+        }
+    }
+    #endif
+
+    pmConceptsUpdate(fpa, chip, cell);
+
+    return success;
+}
+
+// Write all registered concepts for the specified level
+static bool conceptsWrite(psMetadata **specs, // One of the concepts specifications
+                          const pmFPA *fpa,   // The FPA
+                          const pmChip *chip, // The chip
+                          const pmCell *cell, // The cell
+                          pmConceptSource source, // The source of the concepts to write
+                          pmConfig *config, // Configuration
+                          const psMetadata *concepts // The concepts to write out
+                         )
+{
+    assert(specs);
+    assert(concepts);
+
+    if (!conceptsInitialised) {
+        pmConceptsInit();
+    }
+
+    psTrace("psModules.concepts", 3, "Writing concepts (%p %p %p): %d\n", fpa, chip, cell, source);
+
+    if (source & PM_CONCEPT_SOURCE_CELLS) {
+        p_pmConceptsWriteToCells(*specs, cell, concepts);
+    }
+    if (source & PM_CONCEPT_SOURCE_DEFAULTS) {
+        p_pmConceptsWriteToDefaults(*specs, fpa, chip, cell, concepts);
+    }
+    if (source & (PM_CONCEPT_SOURCE_PHU | PM_CONCEPT_SOURCE_HEADER)) {
+        p_pmConceptsWriteToHeader(*specs, fpa, chip, cell, concepts);
+    }
+    if (source & PM_CONCEPT_SOURCE_DATABASE) {
+        p_pmConceptsWriteToDatabase(*specs, fpa, chip, cell, config, concepts);
+    }
+
+    return true;
+}
+
+
+bool pmConceptsRead(pmFPA *fpa, pmChip *chip, pmCell *cell, pmConceptSource source, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    bool success = conceptsRead(&conceptsFPA, fpa, chip, cell, &fpa->conceptsRead, source,
+                                config, fpa->concepts);
+    if (chip) {
+        success &= conceptsRead(&conceptsChip, fpa, chip, cell, &chip->conceptsRead, source,
+                                config, chip->concepts);
+    }
+    if (cell) {
+        success &= conceptsRead(&conceptsCell, fpa, chip, cell, &cell->conceptsRead, source,
+                                config, cell->concepts);
+    }
+
+    return success;
+}
+
+
+bool pmConceptsBlankFPA(pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    psTrace("psModules.concepts", 5, "Blanking FPA concepts: %p %p\n", conceptsFPA, fpa->concepts);
+    return conceptsBlank(&conceptsFPA, fpa->concepts);
+}
+
+
+bool pmConceptsReadFPA(pmFPA *fpa, pmConceptSource source, bool propagateDown, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    psTrace("psModules.concepts", 5, "Reading FPA concepts: %p %p\n", conceptsFPA, fpa->concepts);
+    bool success = conceptsRead(&conceptsFPA, fpa, NULL, NULL, &fpa->conceptsRead, source,
+                                config, fpa->concepts);
+    if (propagateDown) {
+        psArray *chips = fpa->chips;    // Array of chips
+        for (long i = 0; i < chips->n; i++) {
+            pmChip *chip = chips->data[i]; // Chip of interest
+            if (chip) {
+                success &= pmConceptsReadChip(chip, source, false, true, config);
+            }
+        }
+    }
+
+    return success;
+}
+
+bool pmConceptsWriteFPA(const pmFPA *fpa, pmConceptSource source, bool propagateDown, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    psTrace("psModules.concepts", 5, "Writing FPA concepts: %p %p\n", conceptsFPA, fpa->concepts);
+    bool success = conceptsWrite(&conceptsFPA, fpa, NULL, NULL, source, config, fpa->concepts);
+    if (propagateDown) {
+        psArray *chips = fpa->chips;        // Array of chips
+        for (long i = 0; i < chips->n; i++) {
+            pmChip *chip = chips->data[i];  // Chip of interest
+            if (chip && !chip->hdu) {
+                success &= pmConceptsWriteChip(chip, source, false, true, config);
+            }
+        }
+    }
+    return success;
+}
+
+bool pmConceptsBlankChip(pmChip *chip)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    psTrace("psModules.concepts", 5, "Blanking chip concepts: %p %p\n", conceptsChip, chip->concepts);
+    return conceptsBlank(&conceptsChip, chip->concepts);
+}
+
+bool pmConceptsReadChip(pmChip *chip, pmConceptSource source, bool propagateUp,
+                        bool propagateDown, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    psTrace("psModules.concepts", 5, "Reading chip concepts: %p %p\n", conceptsChip, chip->concepts);
+    pmFPA *fpa = chip->parent;          // FPA to which the chip belongs
+    bool success = conceptsRead(&conceptsChip, fpa, chip, NULL, &chip->conceptsRead, source, config,
+                                chip->concepts);
+    if (propagateUp) {
+        success &= conceptsRead(&conceptsFPA, fpa, chip, NULL, &fpa->conceptsRead, source,
+                                config, fpa->concepts);
+    }
+    if (propagateDown) {
+        psArray *cells = chip->cells;        // Array of cells
+        for (long i = 0; i < cells->n; i++) {
+            pmCell *cell = cells->data[i];  // Cell of interest
+            if (cell) {
+                success &= pmConceptsReadCell(cell, source, false, config);
+            }
+        }
+    }
+    return success;
+}
+
+bool pmConceptsWriteChip(const pmChip *chip, pmConceptSource source, bool propagateUp,
+                         bool propagateDown, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    psTrace("psModules.concepts", 5, "Writing chip concepts: %p %p\n", conceptsChip, chip->concepts);
+    pmFPA *fpa = chip->parent;          // FPA to which the chip belongs
+    bool success = conceptsWrite(&conceptsChip, fpa, chip, NULL, source, config, chip->concepts);
+    if (propagateUp && !fpa->hdu) {
+        success &= conceptsWrite(&conceptsFPA, fpa, chip, NULL, source, config, fpa->concepts);
+    }
+    if (propagateDown) {
+        psArray *cells = chip->cells;        // Array of cells
+        for (long i = 0; i < cells->n; i++) {
+            pmCell *cell = cells->data[i];  // Cell of interest
+            if (cell && !cell->hdu) {
+                success &= pmConceptsWriteCell(cell, source, false, config);
+            }
+        }
+    }
+    return success;
+}
+
+bool pmConceptsBlankCell(pmCell *cell)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    psTrace("psModules.concepts", 5, "Blanking cell concepts: %p %p\n", conceptsCell, cell->concepts);
+    return conceptsBlank(&conceptsCell, cell->concepts);
+}
+
+bool pmConceptsReadCell(pmCell *cell, pmConceptSource source, bool propagateUp, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    psTrace("psModules.concepts", 5, "Reading cell concepts: %p %p\n", conceptsCell, cell->concepts);
+    pmChip *chip = cell->parent;        // Chip to which the cell belongs
+    pmFPA *fpa = chip->parent;          // FPA to which the chip belongs
+
+    bool success = conceptsRead(&conceptsCell, fpa, chip, cell, &cell->conceptsRead, source, config,
+                                cell->concepts);
+    if (propagateUp) {
+        success &= conceptsRead(&conceptsChip, fpa, chip, cell, &chip->conceptsRead, source, config,
+                                chip->concepts);
+        success &= conceptsRead(&conceptsFPA, fpa, chip, cell, &fpa->conceptsRead, source, config,
+                                fpa->concepts);
+    }
+
+    return success;
+}
+
+bool pmConceptsWriteCell(const pmCell *cell, pmConceptSource source, bool propagateUp, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    psTrace("psModules.concepts", 5, "Writing cell concepts: %p %p\n", conceptsCell, cell->concepts);
+    pmChip *chip = cell->parent;        // Chip to which the cell belongs
+    pmFPA *fpa = chip->parent;          // FPA to which the chip belongs
+
+    bool success = conceptsWrite(&conceptsCell, fpa, chip, cell, source, config, cell->concepts);
+    if (propagateUp) {
+        if (!chip->hdu) {
+            success &= conceptsWrite(&conceptsChip, fpa, chip, cell, source, config, chip->concepts);
+            if (!fpa->hdu) {
+                success &= conceptsWrite(&conceptsFPA, fpa, chip, cell, source, config, fpa->concepts);
+            }
+        }
+    }
+
+    return success;
+}
+
+// Register a concept
+#define CONCEPT_REGISTER_FUNCTION(TYPENAME, SUFFIX, DEFAULT) \
+static void conceptRegister##SUFFIX(const char *name, /* Name of concept */ \
+                                    const char *comment, /* Comment for concept */ \
+                                    pmConceptParseFunc parse, /* Parsing function */ \
+                                    pmConceptFormatFunc format, /* Formatting function */ \
+                                    bool required, /* Required concept? */ \
+                                    pmFPALevel level /* Level at which concept applies */ \
+    ) \
+{ \
+    psMetadataItem *item = psMetadataItemAlloc##TYPENAME(name, comment, DEFAULT); /* Item to add */ \
+    pmConceptRegister(item, parse, format, required, level); \
+    psFree(item); \
+    return; \
+}
+
+CONCEPT_REGISTER_FUNCTION(Str, Str, "");
+CONCEPT_REGISTER_FUNCTION(F32, F32, NAN);
+CONCEPT_REGISTER_FUNCTION(F64, F64, NAN);
+CONCEPT_REGISTER_FUNCTION(S32, Enum, -1); // For enums: set default to -1
+CONCEPT_REGISTER_FUNCTION(S32, S32, 0); // For values: set default to 0
+
+static void conceptRegisterTime(const char *name, /* Name of concept */ \
+                                const char *comment, /* Comment for concept */ \
+                                bool required, /* Required concept? */ \
+                                pmFPALevel level /* Level at which concept applies */ \
+    )
+{
+    psTime *time = psTimeAlloc(PS_TIME_TAI); // Blank time
+    // Not particularly distinguishing, but should be good enough
+    time->sec = 0;
+    time->nsec = 0;
+    psMetadataItem *item = psMetadataItemAlloc(name, PS_DATA_TIME, comment, time);
+    psFree(time);
+    pmConceptRegister(item, p_pmConceptParse_TIME, p_pmConceptFormat_TIME, required, level);
+    psFree(item);
+}
+
+bool pmConceptsInit(void)
+{
+    conceptsInitialised = true;
+
+    p_psMemAllocatePersistent(true);
+
+    bool init = false;                  // Did we initialise anything?
+
+    if (! conceptsFPA) {
+        conceptsFPA = psMetadataAlloc();
+        init = true;
+
+        // Install the standard concepts
+        conceptRegisterStr("FPA.TELESCOPE", "Telescope of origin", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterStr("FPA.INSTRUMENT", "Instrument name (according to the instrument)", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterStr("FPA.DETECTOR", "Detector name", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterStr("FPA.COMMENT", "Observation comment", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.FOCUS", "Telescope focus", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.AIRMASS", "Airmass at boresight", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterStr("FPA.FILTERID", "Filter used (parsed, abstract name)", p_pmConceptParse_FPA_FILTER, p_pmConceptFormat_FPA_FILTER, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterStr("FPA.FILTER", "Filter used (instrument name)", false, NULL, NULL, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.POSANGLE", "Position angle of instrument", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterStr("FPA.RADECSYS", "Celestial coordinate system", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF64("FPA.RA", "Right Ascension of boresight", p_pmConceptParse_FPA_Coords, p_pmConceptFormat_FPA_Coords, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF64("FPA.DEC", "Declination of boresight", p_pmConceptParse_FPA_Coords, p_pmConceptFormat_FPA_Coords, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterStr("FPA.OBSTYPE", "Type of observation", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterStr("FPA.OBJECT", "Object of observation", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF64("FPA.ALT", "Altitude of boresight", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF64("FPA.AZ", "Azimuth of boresight", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterEnum("FPA.TIMESYS", "Time system", p_pmConceptParse_TIMESYS, p_pmConceptFormat_TIMESYS, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterTime("FPA.TIME", "Time of exposure", false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.TEMP", "Temperature of focal plane", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.M1X", "Primary Mirror X Position", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.M1Y", "Primary Mirror Y Position", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.M1Z", "Primary Mirror Z Position", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.M1TIP", "Primary Mirror TIP", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.M1TILT", "Primary Mirror TILT", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.M2X", "Primary Mirror X Position", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.M2Y", "Primary Mirror Y Position", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.M2Z", "Primary Mirror Z Position", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.M2TIP", "Primary Mirror TIP", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.M2TILT", "Primary Mirror TILT", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.ENV.TEMP", "Environment: Temperature", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.ENV.HUMID", "Environment: Humidity", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.ENV.WIND", "Environment: Wind speed", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.ENV.DIR", "Environment: Wind direction", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.TELTEMP.M1", "Telescope Temperatures: M1", p_pmConceptParse_TELTEMPS, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.TELTEMP.M1CELL", "Telescope Temperatures: M1 cell", p_pmConceptParse_TELTEMPS, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.TELTEMP.M2", "Telescope Temperatures: M2", p_pmConceptParse_TELTEMPS, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.TELTEMP.SPIDER", "Telescope Temperatures: spider", p_pmConceptParse_TELTEMPS, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.TELTEMP.TRUSS", "Telescope Temperatures: truss", p_pmConceptParse_TELTEMPS, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.TELTEMP.EXTRA", "Telescope Temperatures: extra", p_pmConceptParse_TELTEMPS, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.PON.TIME", "Power On Time", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+        conceptRegisterF32("FPA.EXPOSURE", "Exposure time (sec)", NULL, NULL, false, PM_FPA_LEVEL_FPA);
+    }
+    if (! conceptsChip) {
+        conceptsChip = psMetadataAlloc();
+        init = true;
+
+        // Install the standard concepts
+        conceptRegisterS32("CHIP.XPARITY", "Orientation in x compared to the rest of the FPA", NULL, NULL, true, PM_FPA_LEVEL_CHIP);
+        conceptRegisterS32("CHIP.YPARITY", "Orientation in y compared to the rest of the FPA", NULL, NULL, true, PM_FPA_LEVEL_CHIP);
+        conceptRegisterS32("CHIP.X0", "Position of (0,0) on the FPA",p_pmConceptParse_Positions,p_pmConceptFormat_Positions, true, PM_FPA_LEVEL_CHIP);
+        conceptRegisterS32("CHIP.Y0", "Position of (0,0) on the FPA",p_pmConceptParse_Positions,p_pmConceptFormat_Positions, true, PM_FPA_LEVEL_CHIP);
+        conceptRegisterS32("CHIP.XSIZE", "Size of chip (pixels)", NULL, NULL, true, PM_FPA_LEVEL_CHIP);
+        conceptRegisterS32("CHIP.YSIZE", "Size of chip (pixels)", NULL, NULL, true, PM_FPA_LEVEL_CHIP);
+        conceptRegisterF32("CHIP.TEMP", "Temperature of chip", NULL, NULL, false, PM_FPA_LEVEL_CHIP);
+        conceptRegisterStr("CHIP.ID", "Chip identifier", NULL, NULL, false, PM_FPA_LEVEL_CHIP);
+    }
+
+    if (! conceptsCell) {
+        conceptsCell = psMetadataAlloc();
+        init = true;
+
+        // Install the standard concepts
+        conceptRegisterF32("CELL.GAIN", "CCD gain (e/count)", NULL, NULL, true, PM_FPA_LEVEL_CELL);
+        conceptRegisterF32("CELL.READNOISE", "CCD read noise (e)", NULL, NULL, true, PM_FPA_LEVEL_CELL);
+        conceptRegisterF32("CELL.SATURATION", "Saturation level (counts)", NULL, NULL, true, PM_FPA_LEVEL_CELL);
+        conceptRegisterF32("CELL.BAD", "Bad level (counts)", NULL, NULL, true, PM_FPA_LEVEL_CELL);
+        conceptRegisterS32("CELL.XPARITY", "Orientation in x compared to the rest of the chip", NULL, NULL, true, PM_FPA_LEVEL_CELL);
+        conceptRegisterS32("CELL.YPARITY", "Orientation in y compared to the rest of the chip", NULL, NULL, true, PM_FPA_LEVEL_CELL);
+        conceptRegisterS32("CELL.READDIR", "Read direction, rows=1, cols=2", NULL, NULL, true, PM_FPA_LEVEL_CELL);
+
+        // These (CELL.EXPOSURE and CELL.DARKTIME) used to be READOUT.EXPOSURE and READOUT.DARKTIME, but that
+        // doesn't really make sense at the moment.  Maybe we need to add a "parent" link to the readouts.
+        // But then how are the exposure times REALLY derived?  They're not in the FITS headers, because a
+        // readout is a plane in a 3D image.  We'll have to dream up some additional suffix to specify these,
+        // but for now....
+        conceptRegisterF32("CELL.EXPOSURE", "Exposure time (sec)", NULL, NULL, false, PM_FPA_LEVEL_CELL);
+        conceptRegisterF32("CELL.DARKTIME", "Time since flush (sec)", NULL, NULL, false, PM_FPA_LEVEL_CELL);
+
+        conceptRegisterS32("CELL.XBIN", "Binning in x", p_pmConceptParse_CELL_Binning,p_pmConceptFormat_CELL_XBIN, true, PM_FPA_LEVEL_CELL);
+        conceptRegisterS32("CELL.YBIN", "Binning in y",p_pmConceptParse_CELL_Binning,p_pmConceptFormat_CELL_YBIN, true, PM_FPA_LEVEL_CELL);
+        conceptRegisterEnum("CELL.TIMESYS", "Time system", p_pmConceptParse_TIMESYS,p_pmConceptFormat_TIMESYS, false, PM_FPA_LEVEL_CELL);
+        conceptRegisterTime("CELL.TIME", "Time of exposure", false, PM_FPA_LEVEL_CELL);
+        conceptRegisterS32("CELL.X0", "Position of (0,0) on the chip",p_pmConceptParse_Positions,p_pmConceptFormat_Positions, true, PM_FPA_LEVEL_CELL);
+        conceptRegisterS32("CELL.Y0", "Position of (0,0) on the chip",p_pmConceptParse_Positions,p_pmConceptFormat_Positions, true, PM_FPA_LEVEL_CELL);
+        conceptRegisterS32("CELL.XSIZE", "Size of cell (pixels)", NULL, NULL, true, PM_FPA_LEVEL_CELL);
+        conceptRegisterS32("CELL.YSIZE", "Size of cell (pixels)", NULL, NULL, true, PM_FPA_LEVEL_CELL);
+        conceptRegisterS32("CELL.XWINDOW", "Start of cell window (pixels)",p_pmConceptParse_Positions,p_pmConceptFormat_Positions, true, PM_FPA_LEVEL_CELL);
+        conceptRegisterS32("CELL.YWINDOW", "Start of cell window (pixels)",p_pmConceptParse_Positions,p_pmConceptFormat_Positions, true, PM_FPA_LEVEL_CELL);
+        conceptRegisterF32("CELL.VARFACTOR", "Variance factor for conversion from large to small scales", NULL, NULL, true, PM_FPA_LEVEL_CELL);
+
+        // CELL.TRIMSEC
+        {
+            psRegion *trimsec = psAlloc(sizeof(psRegion)); // Blank trimsec
+            trimsec->x0 = trimsec->y0 = trimsec->x1 = trimsec->y1 = NAN;
+            psMetadataItem *cellTrimsec = psMetadataItemAllocPtr("CELL.TRIMSEC", PS_DATA_REGION,
+                                          "Trim section", trimsec);
+            psFree(trimsec);
+            pmConceptRegister(cellTrimsec, p_pmConceptParse_CELL_TRIMSEC,p_pmConceptFormat_CELL_TRIMSEC, true, PM_FPA_LEVEL_CELL);
+            psFree(cellTrimsec);
+        }
+
+        // CELL.BIASSEC
+        {
+            psList *biassecs = psListAlloc(NULL); // Blank biassecs
+            psMetadataItem *cellBiassec = psMetadataItemAllocPtr("CELL.BIASSEC", PS_DATA_LIST,
+                                          "Bias sections", biassecs);
+            psFree(biassecs);
+            pmConceptRegister(cellBiassec, p_pmConceptParse_CELL_BIASSEC, p_pmConceptFormat_CELL_BIASSEC, true, PM_FPA_LEVEL_CELL);
+            psFree(cellBiassec);
+        }
+
+    }
+
+    p_psMemAllocatePersistent(false);
+
+    return init;
+}
+
+void pmConceptsDone(void)
+{
+    psFree(conceptsFPA);
+    conceptsFPA = NULL;
+    psFree(conceptsChip);
+    conceptsChip = NULL;
+    psFree(conceptsCell);
+    conceptsCell = NULL;
+
+    conceptsInitialised = false;
+}
+
+
+// List of concepts not to copy, for each level.
+// Must be NULL-terminated
+static const char *dontCopyConceptsFPA[] = { "FPA.NAME", "FPA.CAMERA", 0 };
+static const char *dontCopyConceptsChip[] = { "CHIP.NAME", 0 };
+static const char *dontCopyConceptsCell[] = { "CELL.NAME", "CELL.TRIMSEC", "CELL.BIASSEC", 0 };
+
+// Copy concepts from a source container to a target container, avoiding certain entries
+static void copyConcepts(psMetadata *target, // Target metadata container
+                         psMetadata *source, // Source metadata container
+                         const char *dontCopyConcepts[] // Don't copy these concepts
+                         )
+{
+    assert(target);
+    assert(source);
+    assert(dontCopyConcepts);
+
+    psMetadataIterator *iter = psMetadataIteratorAlloc(source, PS_LIST_HEAD, NULL);
+    psMetadataItem *item;               // Item from iteration
+    while ((item = psMetadataGetAndIncrement(iter))) {
+        const char *name = item->name;  // Name of concept
+        bool copyOK = true;            // OK to copy
+        for (int i = 0; dontCopyConcepts[i] && copyOK; i++) {
+            if (!strcmp(name, dontCopyConcepts[i])) {
+                copyOK = false;
+            }
+        }
+        if (!copyOK) {
+            continue;
+        }
+        psMetadataItem *new = psMetadataItemCopy(item); // New item, a copy of the old
+        psMetadataAddItem(target, new, PS_LIST_TAIL, PS_META_REPLACE);
+        psFree(new);                    // Drop reference
+    }
+    psFree(iter);
+
+    return;
+}
+
+
+bool pmFPACopyConcepts(pmFPA *target, const pmFPA *source)
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+
+    // Copy FPA concepts
+    copyConcepts(target->concepts, source->concepts, dontCopyConceptsFPA);
+
+    // Copy chip concepts
+    psArray *targetChips = target->chips; // Chips in target
+    psArray *sourceChips = source->chips; // Chips in source
+    if (targetChips->n != sourceChips->n) {
+        psError(PS_ERR_IO, true, "Number of chips in target (%ld) and source (%ld) differ --- unable to copy "
+                "concepts.\n", targetChips->n, sourceChips->n);
+        return false;
+    }
+    for (int i = 0; i < targetChips->n; i++) {
+        pmChip *targetChip = targetChips->data[i]; // Target chip of interest
+        pmChip *sourceChip = sourceChips->data[i]; // Source chip of interest
+        if (! targetChip || ! sourceChip) {
+            continue;
+        }
+
+        copyConcepts(targetChip->concepts, sourceChip->concepts, dontCopyConceptsChip);
+
+        // Copy cell concepts
+        psArray *targetCells = targetChip->cells; // Cells in target
+        psArray *sourceCells = sourceChip->cells; // Cells in source
+        if (targetCells->n != sourceCells->n) {
+            psError(PS_ERR_IO, true, "Number of cells in target (%ld) and source (%ld) differ for chip %d ---"
+                    " unable to copy concepts.\n", targetCells->n, sourceCells->n, i);
+            return false;
+        }
+        for (int j = 0; j < targetCells->n; j++) {
+            pmCell *targetCell = targetCells->data[j]; // Target chip of interest
+            pmCell *sourceCell = sourceCells->data[j]; // Source chip of interest
+            if (! targetCell || ! sourceCell) {
+                continue;
+            }
+
+            copyConcepts(targetCell->concepts, sourceCell->concepts, dontCopyConceptsCell);
+        }
+    }
+
+    return true;
+}
+
+
+bool pmConceptsCopyFPA(pmFPA *target, const pmFPA *source, bool chips, bool cells)
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+
+    // Copy FPA concepts
+    copyConcepts(target->concepts, source->concepts, dontCopyConceptsFPA);
+
+    // Copy chip concepts
+    bool status = true;                 // Status of chips
+    if (chips) {
+        psArray *targetChips = target->chips; // Chips in target
+        psArray *sourceChips = source->chips; // Chips in source
+        if (targetChips->n != sourceChips->n) {
+            psError(PS_ERR_IO, true,
+                    "Number of chips in target (%ld) and source (%ld) differ --- unable to copy concepts.",
+                    targetChips->n, sourceChips->n);
+            return false;
+        }
+        for (int i = 0; i < targetChips->n; i++) {
+            pmChip *targetChip = targetChips->data[i]; // Target chip of interest
+            pmChip *sourceChip = sourceChips->data[i]; // Source chip of interest
+            if (! targetChip || ! sourceChip) {
+                continue;
+            }
+
+            status &= pmConceptsCopyChip(targetChip, sourceChip, cells);
+        }
+    }
+
+    return status;
+}
+
+bool pmConceptsCopyChip(pmChip *target, const pmChip *source, bool cells)
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+
+    // Copy chip concepts
+    copyConcepts(target->concepts, source->concepts, dontCopyConceptsChip);
+
+    // Copy cell concepts
+    bool status = true;                 // Status of cells
+    if (cells) {
+        psArray *targetCells = target->cells; // Cells in target
+        psArray *sourceCells = source->cells; // Cells in source
+        if (targetCells->n != sourceCells->n) {
+            psError(PS_ERR_IO, true,
+                    "Number of cells in target (%ld) and source (%ld) differ --- unable to copy concepts.",
+                    targetCells->n, sourceCells->n);
+            return false;
+        }
+        for (int j = 0; j < targetCells->n; j++) {
+            pmCell *targetCell = targetCells->data[j]; // Target chip of interest
+            pmCell *sourceCell = sourceCells->data[j]; // Source chip of interest
+            if (! targetCell || ! sourceCell) {
+                continue;
+            }
+
+            status &= pmConceptsCopyCell(targetCell, sourceCell);
+        }
+    }
+
+    return status;
+}
+
+
+bool pmConceptsCopyCell(pmCell *target, const pmCell *source)
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+
+    copyConcepts(target->concepts, source->concepts, dontCopyConceptsCell);
+
+    return true;
+}
+
+
+// Interpolate the concept.  Generalises the FPA/Chip/Cell
+#define CONCEPT_INTERPOLATE(SOURCE, NAME) \
+    if (strncmp(concept, NAME, strlen(NAME)) == 0) { \
+        if (!(SOURCE)) { \
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Cannot interpolate %s because %s not provided", \
+                    concept, NAME); \
+            psFree(string); \
+            return NULL; \
+        } \
+        psMetadataItem *item = psMetadataLookup((SOURCE)->concepts, concept); /* Item with concept */ \
+        if (!item) { \
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Can't find concept %s in %s", concept, NAME); \
+            psFree(string); \
+            return NULL; \
+        } \
+        \
+        psString value = psMetadataItemParseString(item); /* Value of concept */ \
+        if (!value) { \
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to parse concept %s", concept); \
+            psFree(string); \
+            return NULL; \
+        } \
+        \
+        char replace[length + 2];       /* String to replace with value */ \
+        replace[0] = '{'; \
+        strcpy(replace + 1, concept); \
+        strcpy(replace + length, "}"); \
+        \
+        psTrace("psModules.concepts", 10, "Interpolating concept %s for %s", replace, value); \
+        \
+        if (!psStringSubstitute(&string, value, replace)) { \
+            psError(PS_ERR_UNKNOWN, false, "Unable to replace concept %s", concept); \
+            psFree(string); \
+            psFree(value); \
+            return NULL; \
+        } \
+        psFree(value); \
+        \
+        continue; \
+    }
+
+
+// XXX Could make the concept delimiters, currently '{' and '}', configurable
+psString pmConceptsInterpolate(const char *input,
+                               const pmFPA *fpa,
+                               const pmChip *chip,
+                               const pmCell *cell
+    )
+{
+    PS_ASSERT_STRING_NON_EMPTY(input, NULL);
+
+    psString string = psStringCopy(input); // Interpolated string, to return
+
+    char *start;                        // Start of a concept
+    while ((start = strchr(string, '{'))) {
+        char *stop = strchr(start, '}'); // End of a concept
+        int length = stop - start;      // Length of the concept name, including terminating \0
+        char concept[length];  // Name of concept
+        strncpy(concept, start + 1, length - 1);
+        concept[length - 1] = '\0';
+
+        psTrace("psModules.concepts", 7, "Interpolating concept %s", concept);
+
+        CONCEPT_INTERPOLATE(fpa,  "FPA");
+        CONCEPT_INTERPOLATE(chip, "CHIP");
+        CONCEPT_INTERPOLATE(cell, "CELL");
+
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unrecognised concept: %s", concept);
+        psFree(string);
+        return NULL;
+    }
+
+    return string;
+}
+
+
+psMetadataItem *p_pmConceptsDepend(const char *name, const psMetadata *menu, const psMetadata *source,
+                                   const pmFPA *fpa, const pmChip *chip, const pmCell *cell)
+{
+    psAssert(name && strlen(name) > 0, "Concept name is empty");
+    psAssert(menu, "Must have menu");
+    psAssert(source, "Must have source");
+
+    // Check for DEPEND
+    psString depend = NULL; // The CONCEPT.DEPEND
+    psStringAppend(&depend, "%s.DEPEND", name);
+    bool mdok;                          // Status of MD lookup
+    const char *dependConcept = psMetadataLookupStr(&mdok, source, depend); // The concept name
+    if (!mdok || !dependConcept || strlen(dependConcept) == 0) {
+        psError(PS_ERR_IO, true, "Unable to parse %s: couldn't find %s in DEFAULTS.\n", name, depend);
+        psFree(depend);
+        return NULL;
+    }
+    psFree(depend);
+    // Now look up the depend value
+    psMetadataItem *dependValue = NULL; // The value of the concept we're looking up
+    if (cell) {
+        dependValue = psMetadataLookup(cell->concepts, dependConcept);
+    }
+    if (chip && !dependValue) {
+        dependValue = psMetadataLookup(chip->concepts, dependConcept);
+    }
+    if (fpa && !dependValue) {
+        dependValue = psMetadataLookup(chip->concepts, dependConcept);
+    }
+    if (!dependValue) {
+        // Not an error --- it may be specified some other way
+        psTrace("psModules.concepts", 7, "Couldn't find DEPEND for %s", name);
+        return NULL;
+    }
+    if (dependValue->type != PS_DATA_STRING) {
+        psError(PS_ERR_BAD_PARAMETER_TYPE, true, "%s is required to resolve %s in DEFAULTS, "
+                "but it is not of type STRING.\n", dependConcept, name);
+        return NULL;
+    }
+    const char *key = dependValue->data.V; // The key to the DEPEND menu
+    psTrace("psModules.concepts", 7, "%s.DEPEND resolves to %s....\n", name, key);
+
+    return psMetadataLookup(menu, key);
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConcepts.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConcepts.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConcepts.h	(revision 22157)
@@ -0,0 +1,248 @@
+/* @file pmConcepts.h
+ * @brief Top-level functions for defining, registering, reading and writing concepts
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.19 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-06-30 00:53:45 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_CONCEPTS_H
+#define PM_CONCEPTS_H
+
+#include <pmConfig.h>
+
+/// @addtogroup Concepts Data Abstraction Concepts
+/// @{
+
+/// Source for concepts when reading and writing.
+///
+/// Since some sources become available at different times from others, we need to provide some specificity to
+/// reading and writing concepts (or we're forced to wait until everything's available, which we don't want to
+/// do).  Concepts may be read from or written to multiple sources at once by OR-ing them.
+typedef enum {
+    PM_CONCEPT_SOURCE_NONE     = 0x00,  ///< No concepts
+    PM_CONCEPT_SOURCE_BLANK    = 0x01,  ///< Blank concepts defined, but not read
+    PM_CONCEPT_SOURCE_CELLS    = 0x02,  ///< Concept comes from the camera information
+    PM_CONCEPT_SOURCE_DEFAULTS = 0x04,  ///< Concept comes from defaults
+    PM_CONCEPT_SOURCE_PHU      = 0x08,  ///< Concept comes from PHU
+    PM_CONCEPT_SOURCE_HEADER   = 0x10,  ///< Concept comes from FITS header
+    PM_CONCEPT_SOURCE_DATABASE = 0x20,  ///< Concept comes from database
+    PM_CONCEPT_SOURCE_ALL      = 0xfe   ///< All concepts (exclude BLANK)
+} pmConceptSource;
+
+/// Function to call to parse a concept once it has been read
+typedef psMetadataItem* (*pmConceptParseFunc)(const psMetadataItem *concept, ///< Concept to parse
+        const psMetadataItem *pattern, ///< Pattern for parsing
+                                              pmConceptSource source, ///< Source of concept
+        const psMetadata *cameraFormat, ///< Camera format definition
+        const pmFPA *fpa, ///< FPA for concept, or NULL
+        const pmChip *chip, ///< Chip for concept, or NULL
+        const pmCell *cell ///< Cell for concept, or NULL
+                                             );
+
+/// Function to call to format a concept for writing
+typedef psMetadataItem* (*pmConceptFormatFunc)(const psMetadataItem *concept, ///< Concept to format
+                                               pmConceptSource source, ///< Source of concept
+        const psMetadata *cameraFormat, ///< Camera format definition
+        const pmFPA *fpa, ///< FPA for concept, or NULL
+        const pmChip *chip, ///< Chip for concept, or NULL
+        const pmCell *cell ///< Cell for concept, or NULL
+                                              );
+
+/// A "concept" specification
+///
+/// Defines the name, default comment, blank value, and functions to parse (after reading) and format (before
+/// writing) the concept.
+typedef struct
+{
+    psMetadataItem *blank;              ///< Blank value of concept; also contains the name and comment
+    pmConceptParseFunc parse;           ///< Function to call to read the concept
+    pmConceptFormatFunc format;         ///< Function to call to write the concept
+    bool required;                      ///< Is concept required (throw an error on problems)?
+}
+pmConceptSpec;
+
+/// Allocator for pmConceptSpec
+pmConceptSpec *pmConceptSpecAlloc(psMetadataItem *blank, ///< Blank value; contains the name
+                                  pmConceptParseFunc parse, ///< Function to call to parse the concept
+                                  pmConceptFormatFunc format, ///< Function to call to format the concept
+                                  bool required ///< Is concept required?
+                                 );
+
+/// Get whether a particular concept is required
+bool pmConceptGetRequired(const char *name, ///< Name of concept
+                          pmFPALevel level ///< Level at which concept resides
+    );
+
+/// Set whether a particular concept is required
+bool pmConceptSetRequired(const char *name, ///< Name of concept
+                          pmFPALevel level, ///< Level at which concept resides
+                          bool required ///< Whether concept is required or not
+    );
+
+/// Register a new concept for parsing and formatting
+///
+/// Defines a new concept, based on the blank value (with name and default comment), and functions to parse
+/// and format the concept.  The new concept is registered at the specified level (FPA, chip or cell).  If the
+/// parse function is NULL, then a default parse function is used, which performs minimal parsing.  Similarly
+/// for the format function.
+bool pmConceptRegister(psMetadataItem *blank, ///< Blank value; contains the name and default comment
+                       pmConceptParseFunc parse, ///< Function to call to parse the concept, or NULL
+                       pmConceptFormatFunc format, ///< Function to call to format the concept, or NULL
+                       bool required,   ///< Is concept required?
+                       pmFPALevel level ///< Level at which to store concept in the FPA hierarchy
+                      );
+
+/// Get a list of defined concepts for a particular level.
+psList *pmConceptsList(pmFPALevel level);
+
+/// Read the concepts for the given set of fpa, chip, cell
+///
+/// Attempts to read as many concepts as possible from the specified source for the specified FPA, chip and
+/// cell.  That is, it will read chip- and cell-level concepts in addition to fpa-level concepts, if the chip
+/// and cell are provided.
+bool pmConceptsRead(pmFPA *fpa,         ///< FPA for which to read concepts
+                    pmChip *chip,       ///< Chip for which to read concepts, or NULL
+                    pmCell *cell,       ///< Cell for which to read concepts, or NULL
+                    pmConceptSource source, ///< The source of the concepts to read
+                    pmConfig *config    ///< Configuration
+                   );
+
+/// Set the concepts within the FPA to the blank value
+bool pmConceptsBlankFPA(pmFPA *fpa      ///< FPA for which to set blank concepts
+                       );
+
+/// Read concepts for an FPA; optionally, read concepts at all lower levels.
+///
+/// Once concepts should be available for reading at the FPA-level, this function attempts to read the
+/// concepts from the specified source.  It also allows concepts to be read at lower levels by iterating over
+/// the components.
+bool pmConceptsReadFPA(pmFPA *fpa,      ///< FPA for which to read concepts
+                       pmConceptSource source, ///< Source for concepts
+                       bool propagateDown, ///< Propagate to lower levels?
+                       pmConfig *config         ///< Configuration
+                      );
+
+/// Write concepts for an FPA; optionally, write concepts at all lower levels.
+///
+/// This function writes all concepts for the FPA to the specified "source".  It also allows concepts to be
+/// written for all lower levels by iterating over the components.
+bool pmConceptsWriteFPA(const pmFPA *fpa,     ///< FPA for which to write concepts
+                        pmConceptSource source, ///< Source for concepts
+                        bool propagateDown, ///< Propagate to lower levels?
+                        pmConfig *config        ///< Configuration
+                       );
+
+/// Set the concepts within the chip to the blank value
+bool pmConceptsBlankChip(pmChip *chip   ///< FPA for which to set blank concepts
+                        );
+
+/// Read concepts for a chip; optionally, read concepts at the FPA and cell levels.
+///
+/// Once concepts should be available for reading at the FPA-level, this function attempts to read the
+/// concepts from the specified source.  It also allows concepts to be read at the fpa level (through the
+/// parent), and the cell level by iterating over the components.
+bool pmConceptsReadChip(pmChip *chip,   ///< Chip for which to read concepts
+                        pmConceptSource source, ///< Source for concepts
+                        bool propagateUp, ///< Propagate to higher levels?
+                        bool propagateDown, ///< Propagate to lower levels?
+                        pmConfig *config        ///< Configuration
+                       );
+
+/// Write concepts for a chip; optionally, write concepts at the FPA and cell levels.
+///
+/// This function writes all concepts for the chip to the specified "source".  It also allows concepts to be
+/// written for the FPA, and the cell level by iterating over the components.
+bool pmConceptsWriteChip(const pmChip *chip,  ///< Chip for which to write concepts
+                         pmConceptSource source, ///< Source for concepts
+                         bool propagateUp,///< Propagate to higher levels?
+                         bool propagateDown, ///< Propagate to lower levels?
+                         pmConfig *config       ///< Configuration
+                        );
+
+/// Set the concepts within the cell to the blank value
+bool pmConceptsBlankCell(pmCell *cell   ///< Cell for which to set blank concepts
+                        );
+
+/// Read concepts for a cell; optionally, read concepts for the parents.
+///
+/// Once concepts should be available for reading at the FPA-level, this function attempts to read the
+/// concepts from the specified source.  It also allows concepts to be read at upper levels through the
+/// parents (note, it would not read concepts for all chips, but only the parent of this cell).
+bool pmConceptsReadCell(pmCell *cell,   ///< Cell for which to read concepts
+                        pmConceptSource source, ///< Source for concepts
+                        bool propagateUp, ///< Propagate to higher levels?
+                        pmConfig *config        ///< Configuration
+                       );
+
+/// Write concepts for a cell; optionally, write concepts for the parents.
+///
+/// This function writes all concepts for the chip to the specified "source".  It also allows concepts to be
+/// written for the upper levels through the parents (note, it would not write concepts for all chips, but
+/// only the parent of this cell).
+bool pmConceptsWriteCell(const pmCell *cell,  ///< FPA for which to write concepts
+                         pmConceptSource source, ///< Source for concepts
+                         bool propagateUp, ///< Propagate to higher levels?
+                         pmConfig *config ///< Configuration
+                        );
+
+/// Initialise the concepts system.
+///
+/// Register the standard concepts, so that concepts may be read and written.  This function is called
+/// automatically the first time the concepts functions are used.
+bool pmConceptsInit(void);
+
+/// Signifies that the user is done with the concepts system.
+///
+/// Frees the registered concepts so there is no memory leak when the user checks "persistent" memory.
+void pmConceptsDone(void);
+
+/// Copy all the concepts within an FPA to another FPA
+///
+/// Iterates over all components of the FPA, and copies the concepts metadata from the source to the target.
+bool pmFPACopyConcepts(pmFPA *target,   ///< The target FPA
+                       const pmFPA *source    ///< The source FPA
+                      );
+
+/// Copy the concepts within an FPA to another FPA; optionally recurse to lower levels
+bool pmConceptsCopyFPA(pmFPA *target,   ///< Target FPA
+                       const pmFPA *source, ///< Source FPA
+                       bool chips,      ///< Recurse to chips level?
+                       bool cells       ///< Recurse to cells level?
+    );
+
+/// Copy the concepts within a chip to another chip; optionally recurse to lower level
+bool pmConceptsCopyChip(pmChip *target, ///< Target chip
+                        const pmChip *source, ///< Source chip
+                        bool cells      ///< Recurse to cells level?
+    );
+
+/// Copy the concepts within a cell to another cell
+bool pmConceptsCopyCell(pmCell *target, ///< Target cell
+                        const pmCell *source ///< Source cell
+    );
+
+/// Interpolate a concept name to the actual value
+///
+/// Concepts enclosed within braces {}, are replaced with the value of the concept
+psString pmConceptsInterpolate(const char *input, ///< Input string
+                               const pmFPA *fpa, ///< FPA with concept values, or NULL
+                               const pmChip *chip, ///< Chip with concept values, or NULL
+                               const pmCell *cell ///< Cell with concept values, or NULL
+    );
+
+/// Look up a dependency menu to get a concept's value
+///
+/// Returns a psMetadataItem with the concept value
+psMetadataItem *p_pmConceptsDepend(const char *name, ///< Name of concept for which to get dependent value
+                                   const psMetadata *menu, ///< Menu in which to look up key
+                                   const psMetadata *source, ///< Source metadata with CONCEPT.DEPEND
+                                   const pmFPA *fpa, ///< FPA for dependency
+                                   const pmChip *chip, ///< Chip for dependency
+                                   const pmCell *cell ///< Cell for dependency
+    );
+
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsAverage.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsAverage.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsAverage.c	(revision 22157)
@@ -0,0 +1,306 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <string.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmConcepts.h"
+#include "pmConceptsAverage.h"
+
+// Update a metadata entry directly
+#define MD_UPDATE(MD, NAME, TYPE, VALUE) \
+{ \
+    psMetadataItem *item = psMetadataLookup(MD, NAME); \
+    item->data.TYPE = VALUE; \
+}
+
+// Update a metadata string entry directly
+#define MD_UPDATE_STR(MD, NAME, VALUE) \
+{ \
+    psMetadataItem *item = psMetadataLookup(MD, NAME); \
+    psFree(item->data.str); \
+    item->data.str = psStringCopy(VALUE); \
+}
+
+bool pmConceptsAverageFPAs(pmFPA *target, psList *sources)
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+    PS_ASSERT_INT_POSITIVE(sources->n, false);
+
+    double time      = 0.0;             // Time of observation
+    psTimeType timeSys = 0;             // Time system
+    char *filter     = NULL;            // Filter
+
+    int num = 0;                        // Number of FPAs
+    psListIterator *sourcesIter = psListIteratorAlloc(sources, PS_LIST_HEAD, false); // Iterator for sources
+    pmFPA *fpa = NULL;                  // Source FPA from iteration
+    while ((fpa = psListGetAndIncrement(sourcesIter))) {
+        if (!fpa) {
+            continue;
+        }
+
+        num++;
+
+        psTime *fpaTime = psMetadataLookupPtr(NULL, fpa->concepts, "FPA.TIME");
+        time       += psTimeToMJD(fpaTime);
+        if (num == 1) {
+            timeSys = psMetadataLookupS32(NULL, fpa->concepts, "FPA.TIMESYS");
+            filter = psMetadataLookupStr(NULL, fpa->concepts, "FPA.FILTER");
+        } else {
+            if (timeSys != psMetadataLookupS32(NULL, fpa->concepts, "FPA.TIMESYS")) {
+                psWarning("Differing FPA.TIMESYS in use: %d vs %d\n",
+                          timeSys, psMetadataLookupS32(NULL, fpa->concepts, "FPA.TIMESYS"));
+            }
+            if (strcmp(filter, psMetadataLookupStr(NULL, fpa->concepts, "FPA.FILTER")) != 0) {
+                psWarning("Differing FPA.FILTER in use: %s vs %s\n",
+                          filter, psMetadataLookupStr(NULL, fpa->concepts, "FPA.FILTER"));
+            }
+        }
+    }
+    psFree(sourcesIter);
+
+    time      /= (double)num;
+
+    MD_UPDATE(target->concepts, "FPA.TIMESYS", S32, timeSys);
+    MD_UPDATE_STR(target->concepts, "FPA.FILTER", filter);
+
+    // FPA.TIME needs special care
+    {
+        psMetadataItem *timeItem = psMetadataLookup(target->concepts, "FPA.TIME");
+        psFree(timeItem->data.V);
+        timeItem->data.V = psTimeFromMJD(time);
+    }
+
+    return true;
+}
+
+
+// Set a variety of concepts in a cell by averaging over several
+// XXX does not properly set XSIZE, YSIZE
+bool pmConceptsAverageCells(pmCell *target, psList *sources, psRegion *trimsec, psRegion *biassec, bool same)
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+    PS_ASSERT_INT_POSITIVE(sources->n, false);
+
+    float gain       = 0.0;             // Gain
+    float readnoise  = 0.0;             // Read noise
+    float saturation = INFINITY;        // Saturation level
+    float bad        = -INFINITY;       // Bad level
+    float exposure   = 0.0;             // Exposure time
+    float darktime   = 0.0;             // Dark time
+    double time      = 0.0;             // Time of observation
+    psTimeType timeSys = 0;             // Time system
+    int readdir      = 0;               // Cell read direction
+    int xBin = 0, yBin = 0;             // Binning
+    int x0 = 0, y0 = 0;                 // Offset
+    int xParity = 0, yParity = 0;       // Parity
+
+    int nCells = 0;                     // Number of cells;
+    psListIterator *sourcesIter = psListIteratorAlloc(sources, PS_LIST_HEAD, false); // Iterator for sources
+    pmCell *cell = NULL;                // Source cell from iteration
+    while ((cell = psListGetAndIncrement(sourcesIter))) {
+        if (!cell) {
+            continue;
+        }
+
+        nCells++;
+        gain       += psMetadataLookupF32(NULL, cell->concepts, "CELL.GAIN");
+        readnoise  += psMetadataLookupF32(NULL, cell->concepts, "CELL.READNOISE");
+        exposure   += psMetadataLookupF32(NULL, cell->concepts, "CELL.EXPOSURE");
+        darktime   += psMetadataLookupF32(NULL, cell->concepts, "CELL.DARKTIME");
+        psTime *cellTime = psMetadataLookupPtr(NULL, cell->concepts, "CELL.TIME");
+        time       += psTimeToMJD(cellTime);
+        if (nCells == 1) {
+            timeSys = psMetadataLookupS32(NULL, cell->concepts, "CELL.TIMESYS");
+            readdir = psMetadataLookupS32(NULL, cell->concepts, "CELL.READDIR");
+            xBin    = psMetadataLookupS32(NULL, cell->concepts, "CELL.XBIN");
+            yBin    = psMetadataLookupS32(NULL, cell->concepts, "CELL.YBIN");
+
+            if (same) {
+                // Only makes sense to update these if they are the same cell
+                x0 = psMetadataLookupS32(NULL, cell->concepts, "CELL.X0");
+                y0 = psMetadataLookupS32(NULL, cell->concepts, "CELL.Y0");
+                xParity = psMetadataLookupS32(NULL, cell->concepts, "CELL.XPARITY");
+                yParity = psMetadataLookupS32(NULL, cell->concepts, "CELL.YPARITY");
+            }
+        } else {
+            if (timeSys != psMetadataLookupS32(NULL, cell->concepts, "CELL.TIMESYS")) {
+                psWarning("Differing CELL.TIMESYS in use: %d vs %d\n",
+                          timeSys, psMetadataLookupS32(NULL, cell->concepts, "CELL.TIMESYS"));
+            }
+            if (readdir != psMetadataLookupS32(NULL, cell->concepts, "CELL.READDIR")) {
+                psWarning("Differing CELL.READDIR in use: %d vs %d\n",
+                          readdir, psMetadataLookupS32(NULL, cell->concepts, "CELL.READDIR"));
+            }
+            if (xBin != psMetadataLookupS32(NULL, cell->concepts, "CELL.XBIN")) {
+                psWarning("Differing CELL.XBIN in use: %d vs %d\n",
+                          xBin, psMetadataLookupS32(NULL, cell->concepts, "CELL.XBIN"));
+            }
+            if (yBin != psMetadataLookupS32(NULL, cell->concepts, "CELL.YBIN")) {
+                psWarning("Differing CELL.YBIN in use: %d vs %d\n",
+                          yBin, psMetadataLookupS32(NULL, cell->concepts, "CELL.YBIN"));
+            }
+            if (same) {
+                if (x0 != psMetadataLookupS32(NULL, cell->concepts, "CELL.X0")) {
+                    psWarning("Differing CELL.X0 in use: %d vs %d\n",
+                              x0, psMetadataLookupS32(NULL, cell->concepts, "CELL.X0"));
+                }
+                if (y0 != psMetadataLookupS32(NULL, cell->concepts, "CELL.Y0")) {
+                    psWarning("Differing CELL.Y0 in use: %d vs %d\n",
+                              y0, psMetadataLookupS32(NULL, cell->concepts, "CELL.Y0"));
+                }
+                if (xParity != psMetadataLookupS32(NULL, cell->concepts, "CELL.XPARITY")) {
+                    psWarning("Differing CELL.XPARITY in use: %d vs %d\n",
+                              xParity, psMetadataLookupS32(NULL, cell->concepts, "CELL.XPARITY"));
+                }
+                if (yParity != psMetadataLookupS32(NULL, cell->concepts, "CELL.YPARITY")) {
+                    psWarning("Differing CELL.YPARITY in use: %d vs %d\n",
+                              yParity, psMetadataLookupS32(NULL, cell->concepts, "CELL.YPARITY"));
+                }
+            }
+        }
+
+        float cellSaturation = psMetadataLookupF32(NULL, cell->concepts, "CELL.SATURATION");
+        if (cellSaturation < saturation) {
+            saturation = cellSaturation;
+        }
+        float cellBad = psMetadataLookupF32(NULL, cell->concepts, "CELL.BAD");
+        if (cellBad > bad) {
+            bad = cellBad;
+        }
+    }
+    psFree(sourcesIter);
+
+    gain      /= (float)nCells;
+    readnoise /= (float)nCells;
+    exposure  /= (float)nCells;
+    darktime  /= (float)nCells;
+    time      /= (double)nCells;
+
+    MD_UPDATE(target->concepts, "CELL.GAIN", F32, gain);
+    MD_UPDATE(target->concepts, "CELL.READNOISE", F32, readnoise);
+    MD_UPDATE(target->concepts, "CELL.SATURATION", F32, saturation);
+    MD_UPDATE(target->concepts, "CELL.BAD", F32, bad);
+    MD_UPDATE(target->concepts, "CELL.EXPOSURE", F32, exposure);
+    MD_UPDATE(target->concepts, "CELL.DARKTIME", F32, darktime);
+    MD_UPDATE(target->concepts, "CELL.TIMESYS", S32, timeSys);
+    MD_UPDATE(target->concepts, "CELL.READDIR", S32, readdir);
+    MD_UPDATE(target->concepts, "CELL.XBIN", S32, xBin);
+    MD_UPDATE(target->concepts, "CELL.YBIN", S32, yBin);
+    if (same) {
+        MD_UPDATE(target->concepts, "CELL.X0", S32, x0);
+        MD_UPDATE(target->concepts, "CELL.Y0", S32, y0);
+        MD_UPDATE(target->concepts, "CELL.XPARITY", S32, xParity);
+        MD_UPDATE(target->concepts, "CELL.YPARITY", S32, yParity);
+    }
+
+    // CELL.TIME needs special care
+    {
+        psMetadataItem *timeItem = psMetadataLookup(target->concepts, "CELL.TIME");
+        psFree(timeItem->data.V);
+        timeItem->data.V = psTimeFromMJD(time);
+    }
+
+    // CELL.TRIMSEC needs special care
+    if (trimsec) {
+        psMetadataItem *trimsecItem = psMetadataLookup(target->concepts, "CELL.TRIMSEC");
+        psFree(trimsecItem->data.V);
+        trimsecItem->data.V = psMemIncrRefCounter(trimsec);
+    }
+
+    // CELL.BIASSEC needs special care
+    if (biassec) {
+        psMetadataItem *biassecItem = psMetadataLookup(target->concepts, "CELL.BIASSEC");
+        psFree(biassecItem->data.V);
+        biassecItem->data.V = psMemIncrRefCounter(biassec);
+    }
+
+    return true;
+}
+
+bool pmConceptsAverageChips(pmChip *target, psList *sources, bool same)
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+    PS_ASSERT_INT_POSITIVE(sources->n, false);
+
+    float temp       = 0.0;             // Temperature
+    int x0 = 0, y0 = 0;                 // Offset
+    int xParity = 0, yParity = 0;       // Parity
+    int xSize = 0, ySize = 0;
+    psString    id = NULL;
+
+    int nChips = 0;                     // Number of cells;
+    psListIterator *sourcesIter = psListIteratorAlloc(sources, PS_LIST_HEAD, false); // Iterator for sources
+    pmChip *chip = NULL;                // Source cell from iteration
+    while ((chip = psListGetAndIncrement(sourcesIter))) {
+        if (!chip) {
+            continue;
+        }
+        temp += psMetadataLookupF32(NULL, chip->concepts, "CHIP.TEMP");
+        if (nChips == 1) {
+            xSize = psMetadataLookupS32(NULL, chip->concepts, "CHIP.XSIZE");
+            ySize = psMetadataLookupS32(NULL, chip->concepts, "CHIP.YSIZE");
+            xParity = psMetadataLookupS32(NULL, chip->concepts, "CHIP.XPARITY");
+            yParity = psMetadataLookupS32(NULL, chip->concepts, "CHIP.YPARITY");
+            x0 = psMetadataLookupS32(NULL, chip->concepts, "CHIP.X0");
+            y0 = psMetadataLookupS32(NULL, chip->concepts, "CHIP.Y0");
+            id = psMetadataLookupStr(NULL, chip->concepts, "CHIP.ID");
+        } else {
+            if (xSize != psMetadataLookupS32(NULL, chip->concepts, "CHIP.XSIZE")) {
+                psWarning("Differing CHIP.XSIZE in use: %d vs %d\n", 
+                    xSize, psMetadataLookupS32(NULL, chip->concepts, "CHIP.XSIZE"));
+            }
+            if (ySize != psMetadataLookupS32(NULL, chip->concepts, "CHIP.YSIZE")) {
+                psWarning("Differing CHIP.YSIZE in use: %d vs %d\n", 
+                    ySize, psMetadataLookupS32(NULL, chip->concepts, "CHIP.YSIZE"));
+            }
+            if (xParity != psMetadataLookupS32(NULL, chip->concepts, "CHIP.XPARITY")) {
+                psWarning("Differing CHIP.XPARITY in use: %d vs %d\n", 
+                    xParity, psMetadataLookupS32(NULL, chip->concepts, "CHIP.XPARITY"));
+            }
+            if (yParity != psMetadataLookupS32(NULL, chip->concepts, "CHIP.YPARITY")) {
+                psWarning("Differing CHIP.YPARITY in use: %d vs %d\n", 
+                    yParity, psMetadataLookupS32(NULL, chip->concepts, "CHIP.YPARITY"));
+            }
+            if (x0 != psMetadataLookupS32(NULL, chip->concepts, "CHIP.X0")) {
+                psWarning("Differing CHIP.X0 in use: %d vs %d\n", 
+                    x0, psMetadataLookupS32(NULL, chip->concepts, "CHIP.X0"));
+            }
+            if (y0 != psMetadataLookupS32(NULL, chip->concepts, "CHIP.Y0")) {
+                psWarning("Differing CHIP.Y0 in use: %d vs %d\n", 
+                    y0, psMetadataLookupS32(NULL, chip->concepts, "CHIP.Y0"));
+            }
+            psString newID = psMetadataLookupStr(NULL, chip->concepts, "CHIP.ID");
+            if (id && newID && strcmp(id, newID)) {
+                psWarning("Differing CHIP.ID in use: %s vs %s\n", id, newID);
+            }
+        }
+    
+        nChips++;
+    }
+    psFree(sourcesIter);
+
+    temp      /= (float)nChips;
+
+    MD_UPDATE(target->concepts, "CHIP.TEMP", F32, temp);
+    if (same) {
+        MD_UPDATE(target->concepts, "CHIP.X0", S32, x0);
+        MD_UPDATE(target->concepts, "CHIP.Y0", S32, y0);
+        MD_UPDATE(target->concepts, "CHIP.XSIZE", S32, xSize);
+        MD_UPDATE(target->concepts, "CHIP.YSIZE", S32, ySize);
+        MD_UPDATE(target->concepts, "CHIP.XPARITY", S32, xParity);
+        MD_UPDATE(target->concepts, "CHIP.YPARITY", S32, yParity);
+        MD_UPDATE_STR(target->concepts, "CHIP.ID", id);
+    }
+
+    return true;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsAverage.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsAverage.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsAverage.h	(revision 22157)
@@ -0,0 +1,65 @@
+/* @file pmConceptsAverage.h
+ * @brief Average the values of multiple concepts
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.11 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-04-10 06:31:42 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_CONCEPTS_AVERAGE_H
+#define PM_CONCEPTS_AVERAGE_H
+
+/// @addtogroup Concepts Data Abstraction Concepts
+/// @{
+
+/// Set a variety of concepts in an FPA by averaging over several
+///
+/// This function averages the values of the following concepts:
+/// FPA.TIME
+/// And ensure the following concepts are consistent:
+/// FPA.FILTER
+/// FPA.TIMESYS
+/// The following concepts could be of interest to the user, but are not treated:
+/// FPA.EXPOSURE
+bool pmConceptsAverageFPAs(pmFPA *target,///< Target FPA
+                           psList *sources ///< List of source FPAs
+    );
+
+/// Set a variety of concepts in a cell by averaging over several
+///
+/// In some instances, we want to combine the values of a concept for several cells into a single concept for
+/// a single cell (e.g., when mosaicking multiple cells into a chip with one "cell").  This function averages
+/// the values of various concepts:
+/// - CELL.GAIN
+/// - CELL.READNOISE
+/// - CELL.EXPOSURE
+/// - CELL.DARKTIME
+/// - CELL.TIME
+/// For other concepts, it ensures the values are consistent:
+/// - CELL.READDIR
+/// - CELL.TIMESYS
+/// - CELL.X0, CELL.Y0
+/// - CELL.XPARITY, CELL.YPARITY
+/// And for others, it takes the "worst" possible value:
+/// - CELL.SATURATION
+/// - CELL.BAD
+/// These concepts are only handled if the cells are all the same cell (mosaicking vs stacking):
+/// - CELL.X0, CELL.Y0
+/// - CELL.XPARITY, CELL.YPARITY
+bool pmConceptsAverageCells(pmCell *target,///< Target cell
+                            psList *sources, ///< List of source cells
+                            psRegion *trimsec, ///< The new trim section
+                            psRegion *biassec, ///< The new bias section
+                            bool same   ///< Are the cells the same cell from different chips?
+                           );
+
+/// Set a variety of concepts in a chip by averaging over several 
+bool pmConceptsAverageChips(pmChip *target,///< Target chip
+                            psList *sources, ///< List of source chips
+                            bool same   ///< Are the chips the same chip from different exposures?
+                           );
+
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsPhotcode.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsPhotcode.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsPhotcode.c	(revision 22157)
@@ -0,0 +1,49 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+#include "pmConceptsPhotcode.h"
+
+psString pmConceptsPhotcodeForView(pmConfig *config, pmFPAfile *file, const pmFPAview *view)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(file, NULL);
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+
+    if (view->chip < -1) {
+        psError(PS_ERR_IO, true, "Photcodes undefined for FPA: defined by chip\n");
+        return NULL;
+    }
+
+    // select recipe options supplied on command line
+    bool mdok;                          // Status of MD lookup
+    psMetadata *recipe  = psMetadataLookupPtr(&mdok, config->recipes, "PPIMAGE");
+    if (!mdok || !recipe) {
+        psError(PS_ERR_IO, true, "Recipe PPIMAGE not found\n");
+        return NULL;
+    }
+
+    // select photcode rule from recipe
+    char *rule = psMetadataLookupStr(&mdok, recipe, "PHOTCODE.RULE");
+    if (!mdok || !rule || strlen(rule) == 0) {
+        psError(PS_ERR_IO, true, "PHOTCODE.RULE not found in PPIMAGE recipe\n");
+        return NULL;
+    }
+
+    // convert rule to real photcode
+    psString photcode = pmFPAfileNameFromRule(rule, file, view);
+
+    return photcode;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsPhotcode.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsPhotcode.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsPhotcode.h	(revision 22157)
@@ -0,0 +1,25 @@
+/* @file  pmConceptsPhotcode.h
+ * @brief Generate a photcode from the concepts
+ *
+ * @author Eugene Magnier, IfA
+ *
+ * @version $Revision: 1.9 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-05-17 21:35:51 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_CONCEPTS_PHOTCODE_H
+#define PM_CONCEPTS_PHOTCODE_H
+
+/// @addtogroup Concepts Data Abstraction Concepts
+/// @{
+
+/// Return the photcode based on the PHOTCODE.RULE in the PHASE2 recipe
+///
+/// A photometry code ("photcode") is a string that represents the combination of filter and detector (chip).
+/// This functions generates a photcode for a particular chip within the FPA, based on the PHOTCODE.RULE in
+/// the PHASE2 recipe.  Interpolation using the usual syntax (e.g., "{CHIP.NAME}") is permitted.
+psString pmConceptsPhotcodeForView(pmConfig *config, pmFPAfile *file, const pmFPAview *view);
+
+/// @}
+# endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsRead.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsRead.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsRead.c	(revision 22157)
@@ -0,0 +1,459 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <strings.h>            /* for strn?casecmp */
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmHDUUtils.h"
+#include "pmConcepts.h"
+#include "pmConceptsRead.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// This function gets called for the really boring concepts --- where all you have to do is parse from a
+// header or database and you don't need to muck around with conversions.  There is no similar "formatPlain",
+// since the type is already known.
+static psMetadataItem *parsePlain(psMetadataItem *concept, // The concept to parse
+                                  const psMetadataItem *pattern // The concept pattern
+                                 )
+{
+    assert(concept);
+    assert(pattern);
+
+    switch (pattern->type) {
+    case PS_DATA_STRING: {
+            psString string = psMetadataItemParseString(concept); // Get the string, so I can free it after it
+            // goes on the MetadataItem
+            psMetadataItem *item = psMetadataItemAllocStr(pattern->name, pattern->comment, string);
+            psFree(string);
+            return item;
+        }
+    case PS_DATA_S32:
+        return psMetadataItemAllocS32(pattern->name, pattern->comment, psMetadataItemParseS32(concept));
+    case PS_DATA_F32:
+        return psMetadataItemAllocF32(pattern->name, pattern->comment, psMetadataItemParseF32(concept));
+    case PS_DATA_F64:
+        return psMetadataItemAllocF64(pattern->name, pattern->comment, psMetadataItemParseF64(concept));
+    default:
+        psWarning("Concept %s (%s) is not of a standard type (%x)\n",
+                 pattern->name, pattern->comment, pattern->type);
+        return NULL;
+    }
+}
+
+// Parse a single concept
+static bool conceptParse(pmConceptSpec *spec, // The concept specification
+                         psMetadataItem *concept, // The concept to parse
+                         pmConceptSource source, // The concept source
+                         psMetadata *cameraFormat, // The camera format
+                         psMetadata *target, // The target
+                         const pmFPA *fpa, // The FPA
+                         const pmChip *chip, // The chip
+                         const pmCell *cell // The cell
+                        )
+{
+    assert(spec);
+    assert(cameraFormat);
+    assert(target);
+
+    if (!concept) {
+        psError(PS_ERR_UNKNOWN, true, "Concept is NULL");
+        return false;
+    }
+
+    psMetadataItem *parsed = NULL;  // The parsed concept
+    if (spec->parse) {
+        parsed = spec->parse(concept, spec->blank, source, cameraFormat, fpa, chip, cell);
+    } else {
+        parsed = parsePlain(concept, spec->blank);
+    }
+    if (!parsed) {
+        if (spec->required) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to parse concept %s\n", spec->blank->name);
+            return false;
+        } else {
+            psWarning("Unable to parse concept %s, but concept not marked as required.\n", spec->blank->name);
+            psErrorClear();
+            return true; // XXX return?
+        }
+    }
+
+    psTrace ("psModules.concepts", 3, "parsing concept: %s\n", spec->blank->name);
+
+    // Plug the parsed concept into a new psMetadataItem, so each "concept" has its own version that can
+    // be altered without affecting the others.  Also, so that we maintain the template name and comment.
+    psMetadataItem *cleaned = NULL;     // Item that's been cleaned up --- correct name and comment
+    switch (spec->blank->type) {
+    case PS_DATA_STRING:
+        cleaned = psMetadataItemAllocStr(spec->blank->name, spec->blank->comment, parsed->data.V);
+        break;
+    case PS_DATA_S32:
+        cleaned = psMetadataItemAllocS32(spec->blank->name, spec->blank->comment, parsed->data.S32);
+        break;
+    case PS_DATA_F32:
+        cleaned = psMetadataItemAllocF32(spec->blank->name, spec->blank->comment, parsed->data.F32);
+        break;
+    case PS_DATA_F64:
+        cleaned = psMetadataItemAllocF64(spec->blank->name, spec->blank->comment, parsed->data.F64);
+        break;
+    default:
+        cleaned = psMetadataItemAlloc(spec->blank->name, parsed->type, spec->blank->comment,
+                                      parsed->data.V);
+    }
+    psFree(parsed);
+    psMetadataAddItem(target, cleaned, PS_LIST_TAIL, PS_META_REPLACE);
+    psFree(cleaned);                 // Drop reference
+    return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool p_pmConceptsReadFromCells(psMetadata *target, const psMetadata *specs, const pmCell *cell)
+{
+    PS_ASSERT_PTR_NON_NULL(specs, false);
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    if (!cell) {
+        psError(PS_ERR_UNKNOWN, true, "cell is NULL");
+        return false;
+    }
+
+    pmHDU *hdu = pmHDUGetLowest(NULL, NULL, cell); // The HDU at the lowest level
+    if (!hdu) {
+        psError(PS_ERR_UNKNOWN, true, "Can't find HDU for cell");
+        return false;
+    }
+    psMetadata *cameraFormat = hdu->format; // The camera format
+    psMetadata *cellConfig = cell->config; // The camera configuration for this cell
+    psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *specItem = NULL;    // Item from the specs metadata
+    bool status = true;                 // Status of reading concepts
+    while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+        pmConceptSpec *spec = specItem->data.V; // The specification
+        psString name = specItem->name; // The concept name
+        psMetadataItem *conceptItem = psMetadataLookup(cellConfig, name); // The concept, or NULL
+        psTrace("psModules.concepts", 10, "%s: %p\n", name, conceptItem);
+        if (conceptItem) {
+            if (conceptItem->type == PS_DATA_STRING) {
+                // Check the SOURCE
+                psString nameSource = NULL; // String with the concept name and ".SOURCE" added
+                psStringAppend(&nameSource, "%s.SOURCE", name);
+                bool mdok = true;       // Status of MD lookup
+                psString source = psMetadataLookupStr(&mdok, cell->config, nameSource); // The source
+                psFree(nameSource);
+                if (mdok && source && strlen(source) > 0 && strcasecmp(source, "VALUE") == 0) {
+                    if (!conceptParse(spec, conceptItem, PM_CONCEPT_SOURCE_CELLS,
+                                      cameraFormat, target, NULL, NULL, cell)) {
+                        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to parse concept %s from camera "
+                                "configuration\n", name);
+                        status = false;
+                    }
+                } else if (source && (strlen(source) == 0 || strcasecmp(source, "HEADER") != 0)) {
+                    // We leave "HEADER" to pmConceptsReadFromHeader
+                    psError(PS_ERR_IO, true, "%s isn't HEADER or VALUE --- can't read %s\n", source,
+                            name);
+                    continue;
+                }
+            } else {
+                // Another type --- should be OK
+                if (!conceptParse(spec, conceptItem, PM_CONCEPT_SOURCE_CELLS,
+                                  cameraFormat, target, NULL, NULL, cell)) {
+                    psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to parse concept %s from camera "
+                            "configuration.\n", name);
+                    status = false;
+                }
+            }
+        }
+    }
+    psFree(specsIter);
+    return status;
+}
+
+psMetadataItem *p_pmConceptsReadSingleFromDefaults(const char *name, const psMetadata *defaults,
+                                                  const pmFPA *fpa, const pmChip *chip, const pmCell *cell)
+{
+    PS_ASSERT_STRING_NON_EMPTY(name, NULL);
+    PS_ASSERT_METADATA_NON_NULL(defaults, NULL);
+
+    psMetadataItem *item = psMetadataLookup(defaults, name); // The concept, or NULL
+    psTrace("psModules.concepts", 10, "%s: %p\n", name, item);
+    if (item && item->type == PS_DATA_METADATA) {
+        // This is a menu
+        psTrace("psModules.concepts", 5, "%s is of type METADATA.\n", name);
+        item = p_pmConceptsDepend(name, item->data.md, defaults, fpa, chip, cell);
+    }
+    return item;
+}
+
+bool p_pmConceptsReadFromDefaults(psMetadata *target, const psMetadata *specs,
+                                  const pmFPA *fpa, const pmChip *chip, const pmCell *cell)
+{
+    PS_ASSERT_PTR_NON_NULL(specs, false);
+    PS_ASSERT_PTR_NON_NULL(target, false);
+
+    psTrace("psModules.concepts", 3, "Reading concepts from defaults...\n");
+
+    pmHDU *hdu = pmHDUGetLowest(fpa, chip, cell); // The HDU at the lowest level
+    if (!hdu) {
+        // We read the defaults for all the HDUs we could find
+        return true;
+    }
+    psMetadata *cameraFormat = hdu->format; // The camera format
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *defaults = psMetadataLookupMetadata(&mdok, cameraFormat, "DEFAULTS"); // The DEFAULTS spec
+    if (!mdok || !defaults) {
+        psError(PS_ERR_IO, true, "Failed to find \"DEFAULTS\"");
+        return false;
+    }
+    psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *specItem = NULL;    // Item from the specs metadata
+    bool status = true;                 // Status of reading concepts
+    psErrorClear();   // we're going to declare all errors "old" => won't clear stack
+    while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+        pmConceptSpec *spec = specItem->data.V; // The specification
+        psString name = specItem->name; // The concept name
+        psMetadataItem *conceptItem = p_pmConceptsReadSingleFromDefaults(name, defaults, fpa, chip, cell);
+        if (conceptItem && !conceptParse(spec, conceptItem, PM_CONCEPT_SOURCE_DEFAULTS,
+                                         cameraFormat, target, fpa, chip, cell)) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to parse concept %s from DEFAULTS.\n", name);
+            status = false;
+        }
+    }
+    psFree(specsIter);
+    return status;
+}
+
+
+bool p_pmConceptsReadFromHeader(psMetadata *target, const psMetadata *specs,
+                                const pmFPA *fpa, const pmChip *chip, const pmCell *cell)
+{
+    PS_ASSERT_PTR_NON_NULL(specs, false);
+    PS_ASSERT_PTR_NON_NULL(target, false);
+
+    pmHDU *hduLow = pmHDUGetLowest(fpa, chip, cell); // The HDU at the lowest level
+    if (!hduLow) {
+        // We read the defaults for all the HDUs we could find
+        return true;
+    }
+    pmHDU *hduHigh = pmHDUGetHighest(fpa, chip, cell); // The HDU at the highest level
+    if (!hduHigh) {
+        psError(PS_ERR_UNKNOWN, true, "Can't find HDU at the highest level");
+        return false;
+    }
+    assert(hduLow->format == hduHigh->format); // Just in case....
+    psMetadata *cameraFormat = hduLow->format; // The camera format
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *transSpec = psMetadataLookupMetadata(&mdok, cameraFormat, "TRANSLATION"); // TRANSLATION spec
+    if (!mdok || !transSpec) {
+        psError(PS_ERR_IO, true, "Failed to find \"TRANSLATION\"");
+        return false;
+    }
+
+    psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *specItem = NULL;    // Item from the specs metadata
+    bool status = true;                 // Status of reading concepts
+    while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+        pmConceptSpec *spec = specItem->data.V; // The specification
+        psString name = specItem->name; // The concept name
+        psMetadataItem *headerItem = NULL; // The value of the concept from the header
+
+        // First check the cell configuration
+        if (cell && cell->config) {
+            psMetadataItem *conceptItem = psMetadataLookup(cell->config, name); // The concept, or NULL
+            if (conceptItem) {
+                // Check the SOURCE
+                psString nameSource = NULL; // String with the concept name and ".SOURCE" added
+                psStringAppend(&nameSource, "%s.SOURCE", name);
+                psString source = psMetadataLookupStr(&mdok, cell->config, nameSource); // The source
+                psFree(nameSource);
+                if (mdok && strlen(source) && strcasecmp(source, "HEADER") == 0) {
+                    if (hduLow->header) {
+                        headerItem = psMetadataLookup(hduLow->header, conceptItem->data.V);
+                    }
+                    if (!headerItem && hduHigh != hduLow && hduHigh->header) {
+                        headerItem = psMetadataLookup(hduHigh->header, conceptItem->data.V);
+                    }
+                    psMemIncrRefCounter(headerItem);
+                }
+                // Leave the error handling to pmConceptsFromCamera, which should already have been called
+            }
+        }
+
+        if (!headerItem) {
+            psMetadataItem *formatItem = psMetadataLookup(transSpec, name); // Item with keyword
+            if (!formatItem) {
+                continue;
+            }
+            if (formatItem->type == PS_DATA_METADATA) {
+                // This is a menu
+                psTrace("psModules.concepts", 5, "%s is of type METADATA.\n", name);
+                formatItem = p_pmConceptsDepend(name, formatItem->data.md, transSpec, fpa, chip, cell);
+                if (!formatItem) {
+                    continue;
+                }
+            }
+            if (formatItem->type != PS_DATA_STRING) {
+                psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Type for concept %s in TRANSLATION is not STR",
+                        name);
+                psFree(specsIter);
+                return false;
+            }
+            psString keywords = formatItem->data.str; // The FITS keywords
+
+            // In case there are multiple headers
+            psList *keys = psStringSplit(keywords, " ,;", true); // List of keywords
+            if (keys->n == 1) {
+                // Only one key --- proceed as usual
+                if (hduLow->header) {
+                    headerItem = psMetadataLookup(hduLow->header, keywords);
+                }
+                if (!headerItem && hduHigh != hduLow && hduHigh->header) {
+                    headerItem = psMetadataLookup(hduHigh->header, keywords);
+                }
+                psMemIncrRefCounter(headerItem);
+            } else {
+                psListIterator *keysIter = psListIteratorAlloc(keys, PS_LIST_HEAD, false); // Iterator
+                psString key = NULL; // Item from iteration
+                psList *values = psListAlloc(NULL); // List containing the values
+                while ((key = psListGetAndIncrement(keysIter))) {
+                    psMetadataItem *value = NULL;
+                    if (hduLow->header) {
+                        value = psMetadataLookup(hduLow->header, key);
+                    }
+                    if (!value && hduHigh != hduLow && hduHigh->header) {
+                        value = psMetadataLookup(hduHigh->header, key);
+                    }
+                    if (value) {
+                        psListAdd(values, PS_LIST_TAIL, value);
+                    } else {
+                        psWarning("Unable to find header %s --- assuming value is NULL", key);
+                    }
+                }
+                psFree(keysIter);
+                headerItem = psMetadataItemAlloc(name, PS_DATA_LIST, specItem->comment, values);
+                psFree(values);
+            }
+            psFree(keys);
+        }
+
+        // This will also clean up the name
+        if (headerItem) {
+            if (!conceptParse(spec, headerItem, PM_CONCEPT_SOURCE_HEADER,
+                              cameraFormat, target, fpa, chip, cell)) {
+                psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to parse concept %s from header.\n", name);
+                status = false;
+            }
+        }
+        psTrace("psModules.concepts", 10, "%s: %p\n", name, headerItem);
+        psFree(headerItem);
+    }
+    psFree(specsIter);
+    return status;
+}
+
+psMetadataItem *p_pmConceptsReadSingleFromDatabase(const char *name, const psMetadata *database,
+                                                   pmConfig *config, const pmFPA *fpa, const pmChip *chip,
+                                                   const pmCell *cell)
+{
+    PS_ASSERT_STRING_NON_EMPTY(name, NULL);
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    psMetadataItem *item = psMetadataLookup(database, name); // Item to return
+    if (item && item->type == PS_DATA_METADATA) {
+        // This is a menu
+        psTrace("psModules.concepts", 5, "%s is of type METADATA.\n", name);
+        item = p_pmConceptsDepend(name, item->data.md, database, fpa, chip, cell);
+    }
+    if (!item) {
+        return NULL;
+    }
+    if (item->type != PS_DATA_STRING) {
+        psWarning("%s in DATABASE in camera format is not of type STR --- ignored.", name);
+        return NULL;
+    }
+
+    psDB *db = pmConfigDB(config);      // Database handle
+    if (!db) {
+        psErrorClear();
+        psWarning("Unable to initialise database to write concepts.");
+        return NULL;
+    }
+
+    psString sql = pmConceptsInterpolate(item->data.str, fpa, chip, cell);
+    if (!p_psDBRunQuery(config->database, sql)) {
+        psWarning("Unable to query database for concept %s --- ignored.", name);
+        psFree(sql);
+        return NULL;
+    }
+    psFree(sql);
+
+    psArray *rows = p_psDBFetchResult(config->database); // Rows returned from the query
+    if (rows->n == 0) {
+        psWarning("No rows returned from database query for concept %s --- ignored.", name);
+        return NULL;
+    }
+    if (rows->n > 1) {
+        psWarning("Multiple rows returned from database query for concept %s --- using the first", name);
+    }
+    psMetadata *row = rows->data[0]; // First (and only) row
+    if (row->list->n > 1) {
+        psWarning("Multiple columns returned from database query for concept %s --- using the first", name);
+    }
+
+    return psMetadataGet(row, PS_LIST_HEAD); // Item of interest
+}
+
+bool p_pmConceptsReadFromDatabase(psMetadata *target, const psMetadata *specs,
+                                  const pmFPA *fpa, const pmChip *chip, const pmCell *cell, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(specs, false);
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+#ifndef HAVE_PSDB
+    return true;
+#else
+    pmHDU *hdu = pmHDUGetLowest(fpa, chip, cell); // The HDU at the lowest level
+    if (!hdu) {
+        // We read the database for all the HDUs we could find
+        return true;
+    }
+    psMetadata *cameraFormat = hdu->format; // The camera format
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *dbSpec = psMetadataLookupMetadata(&mdok, cameraFormat, "DATABASE"); // The DATABASE spec
+    if (!mdok || !dbSpec) {
+        return true;
+    }
+
+    psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *specItem = NULL;    // Item from the specs metadata
+    bool status = true;                 // Status of reading concepts
+    while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+        pmConceptSpec *spec = specItem->data.V; // The specification
+        psString name = specItem->name; // The concept name
+        psMetadataItem *conceptItem = p_pmConceptsReadSingleFromDatabase(name, dbSpec, config,
+                                                                         fpa, chip, cell);
+        if (conceptItem && !conceptParse(spec, conceptItem, PM_CONCEPT_SOURCE_DATABASE,
+                                         cameraFormat, target, fpa, chip, cell)) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to parse concept %s from database.\n", name);
+            status = false;
+        }
+    } // Iterating through the concept specifications
+    psFree(specsIter);
+
+    return status;
+#endif
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsRead.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsRead.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsRead.h	(revision 22157)
@@ -0,0 +1,82 @@
+/* @file  pmConceptsRead.h
+ * @brief Reading concepts from a variety of sources.
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.8 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-06-17 22:16:38 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_CONCEPTS_READ_H
+#define PM_CONCEPTS_READ_H
+
+/// Read concepts from the camera format file's CELLS.
+///
+/// Examines the CELLS metadata in the camera format file
+/// for the current type of cell, and sucks in the concepts defined there.
+/// This is a useful way of defining concepts that vary depending on the
+/// type of the cell.
+bool p_pmConceptsReadFromCells(psMetadata *target, ///< Place into which to read the concepts
+                               const psMetadata *specs, ///< The concept specifications
+                               const pmCell *cell ///< The cell
+                              );
+
+/// Read a single concept from the DEFAULTS in the camera format
+///
+/// The returned item is NOT parsed, but any interpolation for DEPEND is done.
+psMetadataItem *p_pmConceptsReadSingleFromDefaults(
+    const char *name,                   ///< Name of concept
+    const psMetadata *defaults,         ///< DEFAULTS specifications
+    const pmFPA *fpa,                   ///< The FPA
+    const pmChip *chip,                 ///< The chip, or NULL
+    const pmCell *cell                  ///< The cell, or NULL
+    );
+
+/// Read concepts from the DEFAULTS in the camera format file.
+///
+/// Examines the DEFAULTS metadata in the camera format file
+/// for concepts in the specs, and imports them into the target.
+bool p_pmConceptsReadFromDefaults(psMetadata *target, // Place into which to read the concepts
+                                  const psMetadata *specs, // The concept specifications
+                                  const pmFPA *fpa, // The FPA
+                                  const pmChip *chip, // The chip
+                                  const pmCell *cell // The cell
+                                 );
+
+/// Read concepts from the header TRANSLATION in the camera format file.
+///
+/// Examines the TRANSLATION metadata in the camera format file
+/// for concepts in the specs, and imports them into the target.
+bool p_pmConceptsReadFromHeader(psMetadata *target, // Place into which to read the concepts
+                                const psMetadata *specs, // The concept specifications
+                                const pmFPA *fpa, // The FPA
+                                const pmChip *chip, // The chip
+                                const pmCell *cell  // The cell
+                               );
+
+/// Read a single concept from the DATABASE specification in the camera format
+///
+/// The returned item is NOT parsed, but any interpolation for DEPEND is done.
+psMetadataItem *p_pmConceptsReadSingleFromDatabase(
+    const char *name,                   ///< Name of concept
+    const psMetadata *database,         ///< DATABASE specification
+    pmConfig *config,                   ///< Configuration
+    const pmFPA *fpa,                   ///< The FPA
+    const pmChip *chip,                 ///< The chip, or NULL
+    const pmCell *cell                  ///< The cell, or NULL
+    );
+
+/// Read concepts from the header DATABASE in the camera format file.
+///
+/// Examines the DATABASE metadata in the camera format file
+/// for concepts in the specs, and imports them into the target.
+bool p_pmConceptsReadFromDatabase(psMetadata *target, // Place into which to read the concepts
+                                  const psMetadata *specs, // The concept specifications
+                                  const pmFPA *fpa, // The FPA
+                                  const pmChip *chip, // The chip
+                                  const pmCell *cell,  // The cell
+                                  pmConfig *config // Configuration
+                                 );
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsStandard.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsStandard.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsStandard.c	(revision 22157)
@@ -0,0 +1,1238 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>            /* for strn?casecmp */
+#include <assert.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmConcepts.h"
+#include "pmConceptsRead.h"
+#include "pmConceptsWrite.h"
+#include "pmConceptsStandard.h"
+
+// The functions in this file are intended to be called solely within the psModules concepts code.  For this
+// reason, they use "assert" instead of the PS_ASSERT_WHATEVER functions --- if there's a problem, then
+// there's a BIG problem that affects all of the code.
+
+#define COMPARE_REGIONS(a,b) (((a)->x0 == (b)->x0 && \
+                               (a)->x1 == (b)->x1 && \
+                               (a)->y0 == (b)->y0 && \
+                               (a)->y1 == (b)->y1) ? true : false)
+
+#define TYPE_CASE(assign, item, TYPE) \
+case PS_TYPE_##TYPE: \
+assign = item->data.TYPE; \
+break;
+
+
+
+static double defaultCoordScaling(const psMetadataItem *pattern)
+{
+    if (strcmp(pattern->name, "FPA.RA") == 0) {
+        psWarning("Assuming format for %s is HOURS.\n", pattern->name);
+        return M_PI / 12.0;
+    }
+    if (strcmp(pattern->name, "FPA.DEC") == 0) {
+        psWarning("Assuming format for %s is DEGREES.\n", pattern->name);
+        return M_PI / 180.0;
+    }
+    psAbort("Should never ever get here.\n");
+    return NAN;
+}
+
+// TELTEMPS : parse a list of the form 'X1 X2 X3 X4 X5 ...' : for now use median
+psMetadataItem *p_pmConceptParse_TELTEMPS(const psMetadataItem *concept,
+                                          const psMetadataItem *pattern,
+                                          pmConceptSource source,
+                                          const psMetadata *cameraFormat,
+                                          const pmFPA *fpa,
+                                          const pmChip *chip,
+                                          const pmCell *cell)
+{
+    assert(concept);
+    assert(pattern);
+
+    double value = NAN;
+    switch (concept->type) {
+      case PS_TYPE_F32:
+        value = concept->data.F32;
+        break;
+      case PS_TYPE_F64:
+        value = concept->data.F64;
+        break;
+      case PS_DATA_STRING: {
+          // parse the list of values into an array of substrings
+          psArray *strValues = psStringSplitArray (concept->data.V, " ,;", false);
+          assert (strValues);
+
+          // convert the substrings into a vector
+          psVector *fltValues = psVectorAlloc (strValues->n, PS_DATA_F32);
+          for (int i = 0; i < strValues->n; i++) {
+              fltValues->data.F32[i] = atof(strValues->data[i]);
+          }
+
+          // take the (for now) MEDIAN of the data
+          psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN);
+
+          if (!psVectorStats (stats, fltValues, NULL, NULL, 0)) {
+              psAbort ("how can this stats function fail?");
+          }
+
+          value = stats->sampleMedian;
+          psFree (stats);
+          psFree (fltValues);
+          psFree (strValues);
+          break;
+      }
+
+      default:
+        psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Invalid type for %s (%x)\n", pattern->name, concept->type);
+        return NULL;
+    }
+
+    psMetadataItem *item = psMetadataItemAllocF32(pattern->name, pattern->comment, value);
+    return (item);
+}
+
+// FPA.FILTER
+psMetadataItem *p_pmConceptParse_FPA_FILTER(const psMetadataItem *concept,
+                                            const psMetadataItem *pattern,
+                                            pmConceptSource source,
+                                            const psMetadata *cameraFormat,
+                                            const pmFPA *fpa,
+                                            const pmChip *chip,
+                                            const pmCell *cell)
+{
+    assert(concept);
+    assert(pattern);
+    assert(fpa);
+    assert(fpa->camera);
+
+    if (concept->type != PS_DATA_STRING) {
+        psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Type for %s (%x) is not STR\n",
+                pattern->name, concept->type);
+        return NULL;
+    }
+
+    bool mdok;                          // Status of MD lookup
+    psMetadata *filters = psMetadataLookupMetadata(&mdok, fpa->camera, "FILTER.ID");
+    if (!mdok || !filters) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find FILTER.ID in camera configuration.\n");
+        return NULL;
+    }
+
+    // the metadata is in the format (internal) STR (external)
+    // do a reverse lookup to get the internal name
+    psMetadataIterator *iter = psMetadataIteratorAlloc(filters, PS_LIST_HEAD, NULL); // Iterator for filters
+    psMetadataItem *item;               // Item from iteration
+    char *name = NULL;                  // The winning name
+    while ((item = psMetadataGetAndIncrement(iter))) {
+        if (item->type != PS_DATA_STRING) {
+            psWarning("Type for %s (%x) in FILTER.ID in camera configuration is not STR\n",
+                      item->name, item->type);
+            continue;
+        }
+        if (strcmp(item->data.str, concept->data.str) == 0) {
+            name = item->name;
+            break;
+        }
+    }
+    psFree(iter);
+    if (!name) {
+        psError(PS_ERR_UNEXPECTED_NULL, false,
+                "Unable to find any filter matching %s in FILTER.ID in camera configuration.\n",
+                concept->data.str);
+        return NULL;
+    }
+
+    return psMetadataItemAllocStr(pattern->name, pattern->comment, name);
+}
+
+psMetadataItem *p_pmConceptFormat_FPA_FILTER(const psMetadataItem *concept,
+                                             pmConceptSource source,
+                                             const psMetadata *cameraFormat,
+                                             const pmFPA *fpa,
+                                             const pmChip *chip,
+                                             const pmCell *cell)
+{
+    assert(concept);
+    assert(fpa);
+    assert(fpa->camera);
+
+    if (concept->type != PS_DATA_STRING) {
+        psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Type for %s (%x) is not STR\n",
+                concept->name, concept->type);
+        return NULL;
+    }
+
+    bool mdok;                          // Status of MD lookup
+    psMetadata *filters = psMetadataLookupMetadata(&mdok, fpa->camera, "FILTER.ID");
+    if (!mdok || !filters) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find FILTER.ID in camera configuration.\n");
+        return NULL;
+    }
+
+    const char *key = concept->data.str;        // The name to look up
+    if (!key || strlen(key) == 0) {
+        return psMetadataItemAllocStr(concept->name, concept->comment, NULL);
+    }
+
+    // the metadata is in the format (internal) STR (external)
+    // find the first internal name that matches
+    psMetadataItem *item = psMetadataLookup (filters, key);
+    if (!item) {
+        psError(PS_ERR_UNEXPECTED_NULL, true,
+                "Unable to find %s in FILTER.ID in camera configuration.\n", key);
+        return NULL;
+    }
+
+    if (item->type == PS_DATA_STRING) {
+        return psMetadataItemAllocStr(concept->name, concept->comment, item->data.V);
+    }
+
+    if (item->type == PS_DATA_METADATA_MULTI) {
+        psMetadataItem *entry = psListGet (item->data.list, PS_LIST_HEAD);
+        if (!entry) {
+            psError(PS_ERR_UNEXPECTED_NULL, true,
+                    "List for %s in FILTER.ID in camera configuration is empty.\n", key);
+            return NULL;
+        }
+        return psMetadataItemAllocStr(concept->name, concept->comment, entry->data.V);
+    }
+
+    psError(PS_ERR_UNEXPECTED_NULL, true,
+            "Unable to find %s in FILTER.ID in camera configuration.\n", key);
+    return NULL;
+}
+
+// FPA.RA and FPA.DEC
+psMetadataItem *p_pmConceptParse_FPA_Coords(const psMetadataItem *concept,
+                                            const psMetadataItem *pattern,
+                                            pmConceptSource source,
+                                            const psMetadata *cameraFormat,
+                                            const pmFPA *fpa,
+                                            const pmChip *chip,
+                                            const pmCell *cell)
+{
+    assert(concept);
+    assert(pattern);
+    assert(cameraFormat);
+
+    double coords = NAN;                // The coordinates
+    switch (concept->type) {
+      case PS_TYPE_F32:
+        coords = concept->data.F32;
+        break;
+      case PS_TYPE_F64:
+        coords = concept->data.F64;
+        break;
+      case PS_DATA_STRING:
+        // Sexagesimal format
+        {
+            int big, medium;
+            float small;
+            // XXX: Upgrade path is to allow dd:mm.mmm
+            if (sscanf(concept->data.V, "%d:%d:%f", &big, &medium, &small) != 3 &&
+                sscanf(concept->data.V, "%d %d %f", &big, &medium, &small) != 3)
+            {
+                psError(PS_ERR_UNKNOWN, true, "Cannot interpret %s: %s\n", pattern->name, concept->data.str);
+                break;
+            }
+            coords = abs(big) + (float)medium/60.0 + small/3600.0;
+            if (big < 0)
+            {
+                coords *= -1.0;
+            }
+        }
+        break;
+      default:
+        psError(PS_ERR_UNKNOWN, true, "%s concept is of an unexpected type: %x\n",
+                pattern->name, concept->type);
+        return NULL;
+    }
+
+    // How to interpret the coordinates
+    bool mdok = true;           // Status of MD lookup
+    psMetadata *formats = psMetadataLookupMetadata(&mdok, cameraFormat, "FORMATS");
+    if (mdok && formats) {
+        psString format = psMetadataLookupStr(&mdok, formats, pattern->name);
+        if (mdok && strlen(format) > 0) {
+            if (strcasecmp(format, "HOURS") == 0) {
+                coords *= M_PI / 12.0;
+            } else if (strcasecmp(format, "DEGREES") == 0) {
+                coords *= M_PI / 180.0;
+            } else if (strcasecmp(format, "RADIANS") == 0) {
+                // No action required
+            } else {
+                coords *= defaultCoordScaling(pattern);
+            }
+        } else {
+            coords *= defaultCoordScaling(pattern);
+        }
+    } else {
+        coords *= defaultCoordScaling(pattern);
+    }
+
+    return psMetadataItemAllocF64(pattern->name, pattern->comment, coords);
+}
+
+// FPA.RA and FPA.DEC
+psMetadataItem *p_pmConceptFormat_FPA_Coords(const psMetadataItem *concept,
+                                             pmConceptSource source,
+                                             const psMetadata *cameraFormat,
+                                             const pmFPA *fpa,
+                                             const pmChip *chip,
+                                             const pmCell *cell)
+{
+    assert(concept);
+    assert(cameraFormat);
+
+    double coords = concept->data.F64;  // The coordinates
+
+    if (!isfinite(coords)) {
+        return psMetadataItemAllocF32(concept->name, concept->comment, NAN);
+    }
+
+    // How to interpret the coordinates
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *formats = psMetadataLookupMetadata(&mdok,
+                                                   cameraFormat,
+                                                   "FORMATS");
+    if (mdok && formats) {
+        psString format = psMetadataLookupStr(&mdok,formats, concept->name);
+        if (mdok && strlen(format) > 0) {
+            if (strcasecmp(format, "HOURS") == 0) {
+                coords /= M_PI / 12.0;
+            } else if (strcasecmp(format, "DEGREES") == 0) {
+                coords /= M_PI / 180.0;
+            } else if (strcasecmp(format, "RADIANS") == 0) {
+                // No action required
+            } else {
+                coords /= defaultCoordScaling(concept);
+            }
+        } else {
+            coords /= defaultCoordScaling(concept);
+        }
+    } else {
+        coords /= defaultCoordScaling(concept);
+    }
+
+    // We choose to write sexagesimal format
+    int big, medium;                    // Degrees and minutes
+    float small;                        // Seconds
+    bool negative = (coords < 0);       // Are we working below zero?
+    coords = fabs(coords);
+    big = (int)abs(coords);
+    coords -= big;
+    medium = 60.0 * coords;
+    coords -= medium / 60.0;
+    small = 3600.0 * coords;
+    small = (float)((int)(small * 100.0)) / 100.0;
+    if (negative) {
+        big *= -1;
+    }
+    psString coordString = NULL;        // String with the coordinates in sexagesimal format
+    psStringAppend(&coordString, "%d:%02d:%05.2f", big, medium, small);
+    psMetadataItem *coordItem = psMetadataItemAllocStr(concept->name, concept->comment, coordString);
+    psFree(coordString);
+
+    return coordItem;
+}
+
+
+psMetadataItem *p_pmConceptParse_CELL_TRIMSEC(const psMetadataItem *concept,
+                                              const psMetadataItem *pattern,
+                                              pmConceptSource source,
+                                              const psMetadata *cameraFormat,
+                                              const pmFPA *fpa,
+                                              const pmChip *chip,
+                                              const pmCell *cell)
+{
+    assert(concept);
+    assert(cell);
+    assert(pattern);
+
+    int xParity = 0;
+    int yParity = 0;
+    psRegion *trimsec = psRegionAlloc(0, 0, 0, 0);
+
+    if (concept->type != PS_DATA_STRING) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.TRIMSEC after read is not of type STR (%x)\n", concept->type);
+        psFree(trimsec);
+        return NULL;
+    } else {
+        // allow for x and y flips in regions
+        *trimsec = psRegionAndParityFromString(&xParity, &yParity, concept->data.V);
+    }
+
+    // Need to correct for binning when CELL.TRIMSEC are specified immutably in the CELLS
+    if (source == PM_CONCEPT_SOURCE_CELLS) {
+        psMetadataAddBool(cell->concepts, PS_LIST_TAIL, "CELL.TRIMSEC.UPDATE", PS_META_REPLACE,
+                          "Need to update CELL.TRIMSEC when the binning is available.",
+                          true);
+    }
+
+    psMetadataItem *item = psMetadataItemAllocPtr(pattern->name, PS_DATA_REGION, pattern->comment, trimsec);
+    psFree(trimsec);
+    return item;
+}
+
+
+psList *p_pmConceptParseRegions(const char *region)
+{
+    psList *list = psListAlloc(NULL);   // List of regions
+    if (!region || strlen(region) == 0) {
+        // Empty list
+        return list;
+    }
+
+    // a single BIASSEC is of the form [AAAA]
+    // we may have multiple BIASSEC entries separated by space, commas, or semicolons
+    int xParity = 0, yParity = 0;       // Parity of region
+    char *p = strchr (region, '[');
+    while (p) {
+        char *q = strchr (p, ']');
+        if (!q) {
+            break;
+        }
+        char *regionString = psStringAlloc(q - p + 2);
+        strncpy (regionString, p, q - p + 1);
+        regionString[q - p + 1] = 0;
+
+        psRegion *region = psAlloc(sizeof(psRegion)); // The region
+        *region = psRegionAndParityFromString(&xParity, &yParity, regionString);
+        psListAdd(list, PS_LIST_TAIL, region);
+        psFree(region);           // Drop reference
+        psFree(regionString);     // Drop reference
+
+        p = strchr (q, '[');
+    }
+
+    return list;
+}
+
+psMetadataItem *p_pmConceptParse_CELL_BIASSEC(const psMetadataItem *concept,
+                                              const psMetadataItem *pattern,
+                                              pmConceptSource source,
+                                              const psMetadata *cameraFormat,
+                                              const pmFPA *fpa,
+                                              const pmChip *chip,
+                                              const pmCell *cell)
+{
+    assert(concept);
+    assert(cell);
+    assert(pattern);
+
+    psList *biassecs = NULL; // List of bias sections
+
+    switch (concept->type) {
+      case PS_DATA_STRING: {
+          biassecs = p_pmConceptParseRegions(concept->data.V);
+          break;
+      }
+      case PS_DATA_LIST: {
+          biassecs = psListAlloc(NULL);
+          psList *regions = concept->data.V; // The list of regions
+          psListIterator *regionsIter = psListIteratorAlloc(regions, PS_LIST_HEAD, false); // Iterator
+          psMetadataItem *regionItem = NULL; // Item from list iteration
+          while ((regionItem = psListGetAndIncrement(regionsIter))) {
+              if (regionItem->type != PS_DATA_STRING) {
+                  psWarning("CELL.BIASSEC member is not of type STR --- ignored.\n");
+                  continue;
+              }
+              int xParity = 0;
+              int yParity = 0;
+              psRegion *region = psAlloc(sizeof(psRegion)); // The region
+              *region = psRegionAndParityFromString(&xParity, &yParity, regionItem->data.V);
+              psListAdd(biassecs, PS_LIST_TAIL, region);
+              psFree(region);           // Drop reference
+          }
+          psFree(regionsIter);
+          break;
+      }
+      default:
+        psError(PS_ERR_UNKNOWN, true, "CELL.BIASSEC after read is not of type STRING or LIST --- assuming "
+                "blank.\n");
+    }
+
+    // Need to correct for binning when CELL.BIASSEC are specified immutably in the CELLS
+    if (source == PM_CONCEPT_SOURCE_CELLS) {
+        psMetadataAddBool(cell->concepts, PS_LIST_TAIL, "CELL.BIASSEC.UPDATE", 0,
+                          "Need to update CELL.BIASSEC when the binning is available.",
+                          true);
+    }
+
+    psMetadataItem *item = psMetadataItemAllocPtr(pattern->name, PS_DATA_LIST, pattern->comment, biassecs);
+    psFree(biassecs);               // Drop reference
+    return item;
+}
+
+// CELL.XBIN and CELL.YBIN
+psMetadataItem *p_pmConceptParse_CELL_Binning(const psMetadataItem *concept,
+                                              const psMetadataItem *pattern,
+                                              pmConceptSource source,
+                                              const psMetadata *cameraFormat,
+                                              const pmFPA *fpa,
+                                              const pmChip *chip,
+                                              const pmCell *cell)
+{
+    assert(concept);
+    assert(pattern);
+
+    int binning = 1;                    // Binning factor in x
+    switch (concept->type) {
+      case PS_DATA_STRING: {
+          psString binString = concept->data.V; // The string containing the binning
+          if ((strcmp(pattern->name, "CELL.XBIN") == 0 && sscanf(binString, "%d %*d", &binning) != 1 &&
+               sscanf(binString, "%d,%*d", &binning) != 1) ||
+              (strcmp(pattern->name, "CELL.YBIN") == 0 && sscanf(binString, "%*d %d", &binning) != 1 &&
+               sscanf(binString, "%*d,%d", &binning) != 1)) {
+              psError(PS_ERR_UNKNOWN, true, "Unable to parse string to get %s: %s\n", pattern->name, binString);
+          }
+          break;
+      }
+        TYPE_CASE(binning, concept, U8);
+        TYPE_CASE(binning, concept, U16);
+        TYPE_CASE(binning, concept, U32);
+        TYPE_CASE(binning, concept, S8);
+        TYPE_CASE(binning, concept, S16);
+        TYPE_CASE(binning, concept, S32);
+      default:
+        psError(PS_ERR_UNKNOWN, true, "Note sure how to parse %s of type %x --- assuming 1.\n", pattern->name,
+                concept->type);
+    }
+
+    return psMetadataItemAllocS32(pattern->name, pattern->comment, binning);
+}
+
+
+psMetadataItem *p_pmConceptParse_TIMESYS(const psMetadataItem *concept,
+                                         const psMetadataItem *pattern,
+                                         pmConceptSource source,
+                                         const psMetadata *cameraFormat,
+                                         const pmFPA *fpa,
+                                         const pmChip *chip,
+                                         const pmCell *cell)
+{
+    assert(concept);
+    assert(pattern);
+
+    psTimeType timeSys = PS_TIME_UTC;   // The time system
+    psString sys = concept->data.V;     // The time system string
+    if (concept->type != PS_DATA_STRING || strlen(sys) <= 0) {
+        // XXX is this too low verbosity?
+        psLogMsg ("psModules.concepts", PS_LOG_DETAIL, "Can't interpret %s --- assuming UTC.\n", pattern->name);
+    } else if (strcasecmp(sys, "TAI") == 0) {
+        timeSys = PS_TIME_TAI;
+    } else if (strcasecmp(sys, "UTC") == 0) {
+        timeSys = PS_TIME_UTC;
+    } else if (strcasecmp(sys, "UT1") == 0) {
+        timeSys = PS_TIME_UT1;
+    } else if (strcasecmp(sys, "TT") == 0) {
+        timeSys = PS_TIME_TT;
+    } else {
+        // XXX is this too low verbosity?
+        psLogMsg ("psModules.concepts", PS_LOG_DETAIL, "Can't interpret %s --- assuming UTC.\n", pattern->name);
+    }
+
+    return psMetadataItemAllocS32(pattern->name, pattern->comment, timeSys);
+}
+
+psMetadataItem *p_pmConceptParse_TIME(const psMetadataItem *concept,
+                                      const psMetadataItem *pattern,
+                                      pmConceptSource source,
+                                      const psMetadata *cameraFormat,
+                                      const pmFPA *fpa,
+                                      const pmChip *chip,
+                                      const pmCell *cell)
+{
+    assert(concept);
+    assert(cameraFormat);
+
+    // Need TIMESYS first
+    psString timesysName = psStringCopy(pattern->name); // e.g., "CELL.TIME" --> "CELL.TIMESYS"
+    psStringSubstitute(&timesysName, "TIMESYS", "TIME");
+    bool mdok = false;                  // Result of MD lookup
+    psTimeType timeSys = PS_TIME_UTC; // The time system
+    if (cell) {
+        timeSys = psMetadataLookupS32(&mdok, cell->concepts, timesysName);
+    }
+    if (!mdok && chip) {
+        timeSys = psMetadataLookupS32(&mdok, chip->concepts, timesysName);
+    }
+    if (!mdok && fpa) {
+        timeSys = psMetadataLookupS32(&mdok, fpa->concepts, timesysName);
+    }
+    if (!mdok || (timeSys == 0xffffffff)) {
+        psWarning("Unable to find %s in concepts when parsing %s --- assuming UTC.\n",
+                  timesysName, pattern->name);
+        timeSys = PS_TIME_UTC;
+    }
+    psFree(timesysName);
+
+    // Work out how the time is represented
+    bool separateTime = false;
+    bool usaTime = false;               // Is the time specified in USA (MM-DD-YYYY) order?
+    bool backwardsTime = false;         // Is the time specified in backwards (DD-MM-YYYY) order?
+    bool yearFirst = false;            // Is the time specified in yearFirst (YYYY-MM-DD) order?
+    bool pre2000Time = false;           // Is the time specified pre-2000 (where the year only has two digits)
+    bool mjdTime = false;               // Is the time specified a MJD?
+    bool jdTime = false;                // Is the time specified a JD?
+
+    // Get format
+    psMetadata *formats = psMetadataLookupMetadata(&mdok, cameraFormat, "FORMATS");
+    if (!mdok || !formats) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find FORMATS in camera configuration.\n");
+        return NULL;
+    }
+
+    psString timeFormat = psMetadataLookupStr(&mdok, formats, pattern->name);
+    if (!mdok || strlen(timeFormat) == 0) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find %s in FORMATS.\n", pattern->name);
+        return NULL;
+    }
+
+    // Parse the time format
+    // why should more than one be allowed??
+    psList *timeFormats = psStringSplit(timeFormat, " ,;", false); // List of the format options
+    psListIterator *timeFormatsIter = psListIteratorAlloc(timeFormats, PS_LIST_HEAD, false); // Iter
+    while ((timeFormat = psListGetAndIncrement(timeFormatsIter))) {
+        if (strcasecmp(timeFormat, "SEPARATE") == 0) {
+            separateTime = true;
+        } else if (strcasecmp(timeFormat, "USA") == 0) {
+            usaTime = true;
+            backwardsTime = false;
+            yearFirst = false;
+            jdTime = false;
+            mjdTime = false;
+        } else if (strcasecmp(timeFormat, "BACKWARDS") == 0) {
+            backwardsTime = true;
+            usaTime = false;
+            yearFirst = false;
+            jdTime = false;
+            mjdTime = false;
+        } else if (strcasecmp(timeFormat, "YEAR.FIRST") == 0) {
+            yearFirst = true;
+            usaTime = false;
+            backwardsTime = false;
+            jdTime = false;
+            mjdTime = false;
+        } else if (strcasecmp(timeFormat, "PRE2000") == 0) {
+            pre2000Time = true;
+        } else if (strcasecmp(timeFormat, "MJD") == 0) {
+            mjdTime = true;
+            jdTime = false;
+            yearFirst = false;
+            backwardsTime = false;
+            usaTime = false;
+        } else if (strcasecmp(timeFormat, "JD") == 0) {
+            jdTime = true;
+            mjdTime = false;
+            yearFirst = false;
+            backwardsTime = false;
+            usaTime = false;
+        } else {
+            psError(PS_ERR_UNKNOWN, true, "Unrecognised FORMATS option for %s: %s --- "
+                    "ignored.\n", pattern->name, timeFormat);
+            psFree(timeFormatsIter);
+            psFree(timeFormats);
+            return NULL;
+        }
+    }
+    psFree(timeFormatsIter);
+    psFree(timeFormats);
+
+    psTime *time = NULL;                // The time
+    switch (concept->type) {
+      case PS_DATA_LIST: {
+          if (!separateTime) {
+              psWarning ("DATE and TIME stored separately, but not specified in format\n");
+          }
+          // The date and time are stored separately
+          // Assume the date is first and the time second
+          psList *dateTime = concept->data.V; // The list containing items for date and time
+          if (psListLength(dateTime) != 2) {
+              psError(PS_ERR_BAD_PARAMETER_SIZE, false,
+                      "Unable to parse %s: date and time are not both available.", pattern->name);
+              return NULL;
+          }
+          psMetadataItem *dateItem = psListGet(dateTime, PS_LIST_HEAD); // Item containing the date
+          if (!dateItem) {
+              psError(PS_ERR_UNKNOWN, true, "Date is not found.\n");
+              return NULL;
+          }
+          if (dateItem->type != PS_DATA_STRING) {
+              psError(PS_ERR_UNKNOWN, true, "Date is not of type STR.\n");
+              return NULL;
+          }
+          psString dateString = dateItem->data.V; // The string with the date
+          int day = 0, month = 0, year = 0;
+          if (sscanf(dateString, "%d-%d-%d", &year, &month, &day) != 3 &&
+              sscanf(dateString, "%d/%d/%d", &year, &month, &day) != 3) {
+              psError(PS_ERR_UNKNOWN, true, "Unable to read date: %s\n", dateString);
+              return NULL;
+          }
+          if (backwardsTime) {
+              // Need to switch days and years
+              int temp = day;
+              day = year;
+              year = temp;
+          }
+          if (usaTime) {
+              // Need to switch everything around.... Yanks!
+              int temp = day;
+              day = month;
+              month = year;
+              year = temp;
+          }
+          if (year < 100) {
+              if (pre2000Time) {
+                  year += 1900;
+              } else {
+                  year += 2000;
+              }
+          }
+          sprintf(dateString,"%04d-%02d-%02d", year, month, day);
+
+          psMetadataItem *timeItem = psListGet(dateTime, PS_LIST_HEAD + 1); // Item containing the time
+          if (!timeItem) {
+              psError(PS_ERR_UNKNOWN, true, "Time is not found.\n");
+              return NULL;
+          }
+          psString timeString = NULL; // The string with the time
+          if (timeItem->type == PS_DATA_STRING) {
+              timeString = timeItem->data.V;
+          } else {
+              // Assume that time is specified in Second of Day (!)
+              double seconds = NAN;
+              switch (timeItem->type) {
+                  TYPE_CASE(seconds, timeItem, U8);
+                  TYPE_CASE(seconds, timeItem, U16);
+                  TYPE_CASE(seconds, timeItem, U32);
+                  TYPE_CASE(seconds, timeItem, S8);
+                  TYPE_CASE(seconds, timeItem, S16);
+                  TYPE_CASE(seconds, timeItem, S32);
+                  TYPE_CASE(seconds, timeItem, F32);
+                  TYPE_CASE(seconds, timeItem, F64);
+                default:
+                  psError(PS_ERR_UNKNOWN, true, "Time is not of an expected type: %x\n", timeItem->type);
+                  return NULL;
+              }
+              // Now print to timeString as "hh:mm:ss.ss"
+              int hours = seconds / 3600;
+              seconds -= (double)hours * 3600.0;
+              int minutes = seconds / 60;
+              seconds -= (double)minutes * 60.0;
+              psStringAppend(&timeString, "%02d:%02d:%02f", hours, minutes, seconds);
+          }
+          psString dateTimeString = NULL;
+          psStringAppend(&dateTimeString, "%sT%s", dateString, timeString);
+          time = psTimeFromISO(dateTimeString, timeSys);
+          psFree(dateTimeString);
+          break;
+      }
+      case PS_DATA_STRING: {
+          psString timeString = concept->data.V;   // String with the time
+          if (jdTime) {
+              double timeValue = strtod (timeString, NULL);
+              time = psTimeFromJD(timeValue);
+          } else if (mjdTime) {
+              double timeValue = strtod (timeString, NULL);
+              time = psTimeFromMJD(timeValue);
+          } else {
+              // It's ISO
+              time = psTimeFromISO(timeString, timeSys);
+          } // Interpreting the time string
+          break;
+      }
+      case PS_TYPE_F32: {
+          double timeValue = (double)concept->data.F32;
+          if (jdTime) {
+              time = psTimeFromJD(timeValue);
+          } else if (mjdTime) {
+              time = psTimeFromMJD(timeValue);
+          } else {
+              psError(PS_ERR_UNKNOWN, true, "Not sure how to parse %s (%f) --- trying JD\n",
+                      pattern->name, timeValue);
+              time = psTimeFromJD(timeValue);
+          }
+          break;
+      }
+      case PS_TYPE_F64: {
+          double timeValue = (double)concept->data.F64;
+          if (jdTime) {
+              time = psTimeFromJD(timeValue);
+          } else if (mjdTime) {
+              time = psTimeFromMJD(timeValue);
+          } else {
+              psError(PS_ERR_UNKNOWN, true, "Not sure how to parse %s (%f) --- trying JD\n",
+                      pattern->name, timeValue);
+              time = psTimeFromJD(timeValue);
+          }
+          break;
+      }
+      default:
+        psError(PS_ERR_UNKNOWN, true, "Unable to parse %s.\n", pattern->name);
+        return NULL;
+    }
+
+    psMetadataItem *item = psMetadataItemAllocPtr(pattern->name, PS_DATA_TIME, pattern->comment, time);
+    psFree(time);                       // Drop reference
+    return item;
+}
+
+// Correct a position --- in case the user wants FORTRAN indexing (like the FITS standard...)
+static int fortranCorr(const psMetadata *cameraFormat, // The camera format description
+                       const char *name // Name of concept to check for FORTRAN indexing
+    )
+{
+    bool mdok = false;                  // Result of MD lookup
+    psMetadata *formats = psMetadataLookupMetadata(&mdok, cameraFormat, "FORMATS");
+    if (mdok && formats) {
+        psString format = psMetadataLookupStr(&mdok, formats, name);
+        if (mdok && strlen(format) > 0 && strcasecmp(format, "FORTRAN") == 0) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+psMetadataItem *p_pmConceptParse_Positions(const psMetadataItem *concept,
+                                           const psMetadataItem *pattern,
+                                           pmConceptSource source,
+                                           const psMetadata *cameraFormat,
+                                           const pmFPA *fpa,
+                                           const pmChip *chip,
+                                           const pmCell *cell)
+{
+    assert(concept);
+    assert(cameraFormat);
+
+    int offset = 0;                     // Offset of component (0,0) corner from the parent (0,0) corner
+
+    switch (concept->type) {
+        TYPE_CASE(offset, concept, U8);
+        TYPE_CASE(offset, concept, U16);
+        TYPE_CASE(offset, concept, U32);
+        TYPE_CASE(offset, concept, S8);
+        TYPE_CASE(offset, concept, S16);
+        TYPE_CASE(offset, concept, S32);
+#if 0
+
+      case PS_DATA_STRING: {
+          // Interpret as a region specifier [x0:x1,y0:y1]
+          int xParity = 0;
+          int yParity = 0;
+          psRegion region = psRegionAndParityFromString(&xParity, &yParity, concept->data.V);
+          if (strstr(pattern->name, ".X0")) {
+              offset = region.x0;
+          } else if (strstr(pattern->name, ".Y0")) {
+              offset = region.y0;
+          } else if (strstr(pattern->name, ".X1")) {
+              offset = region.x1;
+          } else if (strstr(pattern->name, ".Y1")) {
+              offset = region.y1;
+          } else {
+              psError(PS_ERR_UNKNOWN, true,
+                      "Unable to interpret %s because unable to determine if concept is X or Y.\n",
+                      pattern->name);
+              return NULL;
+          }
+          break;
+      }
+#endif
+      default:
+        if (concept->type == PS_TYPE_F32 && concept->data.F32 - (int)concept->data.F32 == 0) {
+            offset = concept->data.F32;
+        } else if (concept->type == PS_TYPE_F64 && concept->data.F64 - (int)concept->data.F64 == 0) {
+            offset = concept->data.F64;
+        } else {
+            psError(PS_ERR_UNKNOWN, true, "Concept %s is not of integer type, as expected.\n", pattern->name);
+            return NULL;
+        }
+    }
+    offset -= fortranCorr(cameraFormat, pattern->name);
+    return psMetadataItemAllocS32(pattern->name, pattern->comment, offset);
+}
+
+
+psMetadataItem *p_pmConceptFormat_CELL_TRIMSEC(const psMetadataItem *concept,
+                                               pmConceptSource source,
+                                               const psMetadata *cameraFormat,
+                                               const pmFPA *fpa,
+                                               const pmChip *chip,
+                                               const pmCell *cell)
+{
+    assert(concept);
+
+    psRegion *trimsec = psMemIncrRefCounter(concept->data.V); // The trimsec region
+    if (!trimsec) {
+        return psMetadataItemAllocStr(concept->name, concept->comment, NULL);
+    }
+
+    // Correct trim section for binning if it's specified explicitly (i.e., immutably) in the CELLS.
+    if (source == PM_CONCEPT_SOURCE_CELLS) {
+        bool xStatus, yStatus;          // Status of MD lookups
+        int xBin = psMetadataLookupS32(&xStatus, cell->concepts, "CELL.XBIN");
+        int yBin = psMetadataLookupS32(&yStatus, cell->concepts, "CELL.YBIN");
+        if (!xStatus || !yStatus || xBin == 0 || yBin == 0) {
+            psWarning("Unable to find CELL.XBIN and CELL.YBIN to correct CELL.TRIMSEC.\n");
+            psFree(trimsec);            // Drop reference
+            return psMemIncrRefCounter((psPtr)concept); // Casting away "const" to increment
+        }
+        psRegion *newTrimsec = psRegionAlloc(trimsec->x0 * xBin, trimsec->x1 * xBin,
+                                             trimsec->y0 * yBin, trimsec->y1 * yBin); // Adjusted for binning
+        psFree(trimsec);
+        trimsec = newTrimsec;
+    }
+
+    psString trimsecString = psRegionToString(*trimsec);
+    psFree(trimsec);
+    psMetadataItem *formatted = psMetadataItemAllocStr(concept->name, concept->comment,
+                                                       trimsecString);
+    psFree(trimsecString);
+    return formatted;
+}
+
+psMetadataItem *p_pmConceptFormat_CELL_BIASSEC(const psMetadataItem *concept,
+                                               pmConceptSource source,
+                                               const psMetadata *cameraFormat,
+                                               const pmFPA *fpa,
+                                               const pmChip *chip,
+                                               const pmCell *cell)
+{
+    // Return a metadata item containing a list of metadata items of region strings
+    psList *biassecs = concept->data.V; // The biassecs region list
+    psList *new = psListAlloc(NULL);    // New list containing metadatas
+    if (biassecs) {
+        psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, false); // Iterator
+        psRegion *region = NULL;            // Region from iteration
+        while ((region = psListGetAndIncrement(biassecsIter))) {
+            // Correct bias section for binning if it's specified explicitly (i.e., immutably) in the CELLS.
+            if (source == PM_CONCEPT_SOURCE_CELLS) {
+                bool xStatus, yStatus;          // Status of MD lookups
+                int xBin = psMetadataLookupS32(&xStatus, cell->concepts, "CELL.XBIN");
+                int yBin = psMetadataLookupS32(&yStatus, cell->concepts, "CELL.YBIN");
+                if (!xStatus || !yStatus || xBin == 0 || yBin == 0) {
+                    psWarning("Unable to find CELL.XBIN and CELL.YBIN to correct CELL.BIASSEC.\n");
+                } else {
+                    psRegion *newTrimsec = psRegionAlloc(region->x0 * xBin, region->x1 * xBin,
+                                                         region->y0 * yBin, region->y1 * yBin);
+                    region = newTrimsec;
+                }
+            } else {
+                psMemIncrRefCounter(region);
+            }
+
+            psString regionString = psRegionToString(*region); // The string region "[x0:x1,y0:y1]"
+            psFree(region);
+            psMetadataItem *item = psMetadataItemAllocStr(concept->name, concept->comment, regionString);
+            psFree(regionString);           // Drop reference
+            psListAdd(new, PS_LIST_TAIL, item);
+            psFree(item);                   // Drop reference
+        }
+        psFree(biassecsIter);
+    }
+
+    psMetadataItem *formatted = psMetadataItemAllocPtr(concept->name, PS_DATA_LIST, concept->comment, new);
+    psFree(new);                        // Drop reference
+    return formatted;
+}
+
+// This function actually does both CELL.XBIN and CELL.YBIN if CELL.XBIN and CELL.YBIN are specified by the
+// same header.
+psMetadataItem *p_pmConceptFormat_CELL_XBIN(const psMetadataItem *concept,
+                                            pmConceptSource source,
+                                            const psMetadata *cameraFormat,
+                                            const pmFPA *fpa,
+                                            const pmChip *chip,
+                                            const pmCell *cell)
+{
+    assert(concept);
+
+    psMetadata *translation = psMetadataLookupMetadata(NULL, cameraFormat, "TRANSLATION");
+    bool xBinOK = true, yBinOK = true;  // Status of MD lookups
+    psString xKeyword = psMetadataLookupStr(&xBinOK, translation, "CELL.XBIN");
+    psString yKeyword = psMetadataLookupStr(&yBinOK, translation, "CELL.YBIN");
+    if (xBinOK && yBinOK && strlen(xKeyword) > 0 && strlen(yKeyword) > 0 &&
+        strcasecmp(xKeyword, yKeyword) == 0) {
+        psMetadataItem *yBinItem = psMetadataLookup(cell->concepts, "CELL.YBIN"); // Binning factor in y
+        psString binString = NULL;
+        psStringAppend(&binString, "%d %d", concept->data.S32, yBinItem->data.S32);
+        psMetadataItem *binItem = psMetadataItemAllocStr(concept->name, concept->comment, binString);
+        psFree(binString);
+        return binItem;
+    }
+
+    // Otherwise, there's no formatting required
+    return psMetadataItemCopy(concept);
+}
+
+// Only need to format if both if CELL.XBIN and CELL.YBIN are not specified by the same header.
+psMetadataItem *p_pmConceptFormat_CELL_YBIN(const psMetadataItem *concept,
+                                            pmConceptSource source,
+                                            const psMetadata *cameraFormat,
+                                            const pmFPA *fpa,
+                                            const pmChip *chip,
+                                            const pmCell *cell)
+{
+    assert(concept);
+
+    psMetadata *translation = psMetadataLookupMetadata(NULL, cameraFormat, "TRANSLATION");
+    bool xBinOK = true, yBinOK = true;  // Status of MD lookups
+    psString xKeyword = psMetadataLookupStr(&xBinOK, translation, "CELL.XBIN");
+    psString yKeyword = psMetadataLookupStr(&yBinOK, translation, "CELL.YBIN");
+    if (xBinOK && yBinOK && strlen(xKeyword) > 0 && strlen(yKeyword) > 0 &&
+        strcasecmp(xKeyword, yKeyword) == 0) {
+        // Censor this --- it's already done (though no harm if it's done twice
+        return NULL;
+    }
+
+    // No formatting required
+    return psMetadataItemCopy(concept);
+}
+
+
+psMetadataItem *p_pmConceptFormat_TIMESYS(const psMetadataItem *concept,
+                                          pmConceptSource source,
+                                          const psMetadata *cameraFormat,
+                                          const pmFPA *fpa,
+                                          const pmChip *chip,
+                                          const pmCell *cell)
+{
+    psString sys = NULL;            // String to store
+    switch (concept->data.S32) {
+      case PS_TIME_TAI:
+        sys = psStringCopy("TAI");
+        break;
+      case PS_TIME_UTC:
+        sys = psStringCopy("UTC");
+        break;
+      case PS_TIME_UT1:
+        sys = psStringCopy("UT1");
+        break;
+      case PS_TIME_TT:
+        sys = psStringCopy("TT");
+        break;
+      default:
+        sys = psStringCopy("Unknown");
+    }
+    psMetadataItem *newItem = psMetadataItemAllocStr(concept->name, concept->comment, sys);
+    psFree(sys);
+
+    return newItem;
+}
+
+psMetadataItem *p_pmConceptFormat_TIME(const psMetadataItem *concept,
+                                       pmConceptSource source,
+                                       const psMetadata *cameraFormat,
+                                       const pmFPA *fpa,
+                                       const pmChip *chip,
+                                       const pmCell *cell)
+{
+    psTime *time = concept->data.V;     // The time
+
+    // Work out the format
+    bool separateTime = false;          // Are the date and time stored separately?
+    bool pre2000Time = false;           // Is the year in pre-2000 format (two digits only)?
+    bool backwardsTime = false;         // Is the date stored backwards (DD-MM-YYYY)?
+    bool usaTime = false;               // Is the date stored in USA order (MM-DD-YYYY)?
+    bool jdTime = false;                // Is the date stored as a JD?
+    bool mjdTime = false;               // Is the date stored as a MJD?
+    bool yearFirst = false;
+
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *formats = psMetadataLookupMetadata(&mdok, cameraFormat, "FORMATS"); // The formats
+    if (mdok && formats) {
+        psString format = psMetadataLookupStr(&mdok, formats, concept->name); // The formats for eg, CELL.TIME
+        if (mdok && format && strlen(format) > 0) {
+            psList *formatList = psStringSplit(format, " ,;", false); // List of formats specified
+            psListIterator *formatListIter = psListIteratorAlloc(formatList, PS_LIST_HEAD, false); // Iterator
+            while ((format = psListGetAndIncrement(formatListIter))) {
+                if (strcasecmp(format, "SEPARATE") == 0) {
+                    separateTime = true;
+                } else if (strcasecmp(format, "USA") == 0) {
+                    usaTime = true;
+                    backwardsTime = false;
+                    jdTime = false;
+                    mjdTime = false;
+                } else if (strcasecmp(format, "BACKWARDS") == 0) {
+                    backwardsTime = true;
+                    usaTime = false;
+                    mjdTime = false;
+                    jdTime = false;
+                } else if (strcasecmp(format, "YEAR.FIRST") == 0) {
+                    yearFirst = true;
+                    usaTime = false;
+                    backwardsTime = false;
+                    jdTime = false;
+                    mjdTime = false;
+                } else if (strcasecmp(format, "PRE2000") == 0) {
+                    pre2000Time = true;
+                } else if (strcasecmp(format, "MJD") == 0) {
+                    mjdTime = true;
+                    usaTime = false;
+                    backwardsTime = false;
+                    jdTime = false;
+                    separateTime = false;
+                } else if (strcasecmp(format, "JD") == 0) {
+                    jdTime = true;
+                    usaTime = false;
+                    backwardsTime = false;
+                    mjdTime = false;
+                    separateTime = false;
+                } else {
+                    psWarning("Unrecognised FORMATS option for %s: %s --- "
+                              "ignored.\n", concept->name, format);
+                }
+            }
+            psFree(formatListIter);
+            psFree(formatList);
+        }
+    }
+
+    if (separateTime) {
+        // We're working with two separate headers --- construct a list with the date and time separately
+        psString dateTimeString = psTimeToISO(time); // String representation
+        psList *dateTime = psStringSplit(dateTimeString, "T", true);
+        psFree(dateTimeString);
+        psString dateString = psListGet(dateTime, PS_LIST_HEAD); // The date string
+        psString timeString = psListGet(dateTime, PS_LIST_TAIL); // The time string
+
+        // Need to format the strings....
+        // XXX: Couldn't be bothered doing these right now
+        if (pre2000Time) {
+            psError(PS_ERR_UNKNOWN, true, "Don't you realise it's the twenty-first century?\n");
+            return NULL;
+        }
+        if (backwardsTime) {
+            int day, month, year;
+            psTrace ("psModules.concepts", 5, "ISO time has year first, convert to DD-MM-YYYY");
+            sscanf (dateString, "%d-%d-%d", &year, &month, &day);
+            sprintf (dateString, "%02d-%02d-%04d", day, month, year);
+            // XXX fix this for str length
+        }
+        if (usaTime) {
+            int day, month, year;
+            psTrace ("psModules.concepts", 5, "ISO time has year first, convert to MM-DD-YYYY");
+            sscanf (dateString, "%d-%d-%d", &year, &month, &day);
+            sprintf (dateString, "%02d-%02d-%04d", month, day, year);
+            // XXX fix this for str length
+        }
+        if (yearFirst) {
+            psTrace ("psModules.concepts", 5, "ISO time has year first, no adjustment needed");
+        }
+
+        psMetadataItem *dateItem = psMetadataItemAllocStr(concept->name, "The date of observation",
+                                                          dateString);
+        psMetadataItem *timeItem = psMetadataItemAllocStr(concept->name, "The time of observation",
+                                                          timeString);
+
+        psListRemove(dateTime, PS_LIST_HEAD);
+        psListRemove(dateTime, PS_LIST_HEAD);
+
+        psListAdd(dateTime, PS_LIST_HEAD, dateItem);
+        psListAdd(dateTime, PS_LIST_TAIL, timeItem);
+
+        psMetadataItem *item = psMetadataItemAllocPtr(concept->name, PS_DATA_LIST, concept->comment,
+                                                      dateTime);
+        psFree (dateItem);
+        psFree (timeItem);
+        psFree (dateTime);
+        return item;
+    }
+    if (jdTime) {
+        double jd = psTimeToMJD(time);
+        return psMetadataItemAllocF64(concept->name, concept->comment, jd);
+    }
+    if (mjdTime) {
+        double mjd = psTimeToMJD(time);
+        return psMetadataItemAllocF64(concept->name, concept->comment, mjd);
+    }
+
+    // If we've gotten this far, so it's straight ISO.
+    psString dateTimeString = psTimeToISO(time); // String representation
+    psMetadataItem *item = psMetadataItemAllocStr(concept->name, concept->comment, dateTimeString);
+    psFree(dateTimeString);
+    return item;
+}
+
+
+psMetadataItem *p_pmConceptFormat_Positions(const psMetadataItem *concept,
+                                            pmConceptSource source,
+                                            const psMetadata *cameraFormat,
+                                            const pmFPA *fpa,
+                                            const pmChip *chip,
+                                            const pmCell *cell)
+{
+    assert(concept);
+    assert(cameraFormat);
+
+    if (concept->type != PS_TYPE_S32) {
+        psError(PS_ERR_UNKNOWN, true, "Concept %s is not of type S32, as expected.\n", concept->name);
+        return NULL;
+    }
+
+#if 0
+    // If both the X0 and Y0 positions are specified by the same header keyword, write both together, as part
+    // of the call for the X0 position.
+    // This is a bit of a kludge --- we're going to write it out as [x0:0,y0:0].  This means that we will be
+    // able to read it back in, but we've destroyed the x1 and y1 if it was present.
+    // We *could* attempt to read the header, parse the region, and only update the ones that we're trying
+    // to update.  Consider this an upgrade option later.
+    // Alternatively, we could add X1 and Y1 concepts, and write the whole lot out together.
+    // But until we care about X1 and Y1, it doesn't really matter --- if you want X1 and Y1, look at X0 and
+    // Y0 and add NXAIS1 and NAXIS2, respectively....
+    if (strstr(concept->name, ".X0")) {
+        psString companion = psStringCopy(concept->name); // Companion entry: ".Y" where this one has ".X"
+        psStringSubstitute(&companion, ".Y0", ".X0");
+
+        // Look both up in the camera format config
+        psMetadata *translation = psMetadataLookupMetadata(NULL, cameraFormat, "TRANSLATION");
+        bool xFound = true, yFound = true;  // Status of MD lookups
+        psString xKeyword = psMetadataLookupStr(&xFound, translation, concept->name);
+        psString yKeyword = psMetadataLookupStr(&yFound, translation, companion);
+        if (xFound && yFound && strlen(xKeyword) > 0 && strlen(yKeyword) > 0 &&
+            strcasecmp(xKeyword, yKeyword) == 0) {
+            psMetadataItem *yItem = psMetadataLookup(cell->concepts, companion); // Corresponding y value
+
+            int x = concept->data.S32 + fortranCorr(cameraFormat, concept->name); // x value
+            int y = yItem->data.S32 + fortranCorr(cameraFormat, companion); // y value
+
+            psRegion region = psRegionSet(x, x, y, y);
+            psString string = psRegionToString(region);
+            psMetadataItem *binItem = psMetadataItemAllocStr(concept->name, concept->comment, string);
+            psFree(string);
+            psFree(companion);
+            return binItem;
+        }
+        psFree(companion);
+    } else if (strstr(concept->name, ".Y0")) {
+        psString companion = psStringCopy(concept->name); // Companion entry: ".Y" where this one has ".X"
+        psStringSubstitute(&companion, ".X0", ".Y0");
+
+        // Look both up in the camera format config
+        psMetadata *translation = psMetadataLookupMetadata(NULL, cameraFormat, "TRANSLATION");
+        bool xFound = true, yFound = true;  // Status of MD lookups
+        psString xKeyword = psMetadataLookupStr(&xFound, translation, concept->name);
+        psString yKeyword = psMetadataLookupStr(&yFound, translation, companion);
+        psFree(companion);
+        if (xFound && yFound && strlen(xKeyword) > 0 && strlen(yKeyword) > 0 &&
+            strcasecmp(xKeyword, yKeyword) == 0) {
+            return NULL;                // We did it with the X; don't do anything.
+        }
+    }
+#endif
+
+    int offset = concept->data.S32;
+    offset += fortranCorr(cameraFormat, concept->name);
+    return psMetadataItemAllocS32(concept->name, concept->comment, offset);
+}
+
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsStandard.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsStandard.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsStandard.h	(revision 22157)
@@ -0,0 +1,191 @@
+/* @file  pmConceptsStandard.h
+ * @brief Private functions for parsing and formatting standard concepts.
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.12 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-12-08 03:15:37 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_CONCEPTS_STANDARD_H
+#define PM_CONCEPTS_STANDARD_H
+
+/// @addtogroup Concepts Data Abstraction Concepts
+/// @{
+
+/// Parse a list of region specifiers on a line
+psList *p_pmConceptParseRegions(const char *region ///< Regions, separated by whitespace
+    );
+
+// Parse the TELTEMPS concept : parse a list of the form 'X1 X2 X3 X4 X5 ...' : for now use median
+psMetadataItem *p_pmConceptParse_TELTEMPS(const psMetadataItem *concept,
+					  const psMetadataItem *pattern,
+					  pmConceptSource source,
+					  const psMetadata *cameraFormat,
+					  const pmFPA *fpa,
+					  const pmChip *chip,
+					  const pmCell *cell);
+
+/// Parse the FPA.FILTER concept to apply a lookup table
+psMetadataItem *p_pmConceptParse_FPA_FILTER(const psMetadataItem *concept, ///< Concept to parse
+        const psMetadataItem *pattern, ///< Pattern to use in parsing
+                                            pmConceptSource source, ///< Source for concept
+        const psMetadata *cameraFormat, ///< Camera format definition
+        const pmFPA *fpa, ///< FPA for concept, or NULL
+        const pmChip *chip, ///< Chip for concept, or NULL
+        const pmCell *cell ///< Cell for concept, or NULL
+                                           );
+
+/// Format the FPA.FILTER concept to (reverse-)apply a lookup table
+psMetadataItem *p_pmConceptFormat_FPA_FILTER(const psMetadataItem *concept, ///< Concept to format
+                                            pmConceptSource source, ///< Source for concept
+        const psMetadata *cameraFormat, ///< Camera format definition
+        const pmFPA *fpa, ///< FPA for concept, or NULL
+        const pmChip *chip, ///< Chip for concept, or NULL
+        const pmCell *cell ///< Cell for concept, or NULL
+                                            );
+
+/// Parse the coordinates concepts: FPA.RA and FPA.DEC
+psMetadataItem *p_pmConceptParse_FPA_Coords(const psMetadataItem *concept, ///< Concept to parse
+        const psMetadataItem *pattern, ///< Pattern to use in parsing
+                                            pmConceptSource source, ///< Source for concept
+        const psMetadata *cameraFormat, ///< Camera format definition
+        const pmFPA *fpa, ///< FPA for concept, or NULL
+        const pmChip *chip, ///< Chip for concept, or NULL
+        const pmCell *cell ///< Cell for concept, or NULL
+                                           );
+
+/// Format the coordinates concepts: FPA.RA and FPA.DEC
+psMetadataItem *p_pmConceptFormat_FPA_Coords(const psMetadataItem *concept, ///< Concept to format
+                                            pmConceptSource source, ///< Source for concept
+        const psMetadata *cameraFormat, ///< Camera format definition
+        const pmFPA *fpa, ///< FPA for concept, or NULL
+        const pmChip *chip, ///< Chip for concept, or NULL
+        const pmCell *cell ///< Cell for concept, or NULL
+                                            );
+
+/// Parse the CELL.TRIMSEC concept
+psMetadataItem *p_pmConceptParse_CELL_TRIMSEC(const psMetadataItem *concept, ///< Concept to parse
+        const psMetadataItem *pattern, ///< Pattern to use in parsing
+                                            pmConceptSource source, ///< Source for concept
+        const psMetadata *cameraFormat, ///< Camera format definition
+        const pmFPA *fpa, ///< FPA for concept, or NULL
+        const pmChip *chip, ///< Chip for concept, or NULL
+        const pmCell *cell ///< Cell for concept, or NULL
+                                             );
+
+/// Parse the CELL.BIASSEC concept
+psMetadataItem *p_pmConceptParse_CELL_BIASSEC(const psMetadataItem *concept, ///< Concept to parse
+        const psMetadataItem *pattern, ///< Pattern to use in parsing
+                                            pmConceptSource source, ///< Source for concept
+        const psMetadata *cameraFormat, ///< Camera format definition
+        const pmFPA *fpa, ///< FPA for concept, or NULL
+        const pmChip *chip, ///< Chip for concept, or NULL
+        const pmCell *cell ///< Cell for concept, or NULL
+                                             );
+
+/// Parse the cell binning concepts: CELL.XBIN, CELL.YBIN
+psMetadataItem *p_pmConceptParse_CELL_Binning(const psMetadataItem *concept, ///< Concept to parse
+        const psMetadataItem *pattern, ///< Pattern to use in parsing
+                                            pmConceptSource source, ///< Source for concept
+        const psMetadata *cameraFormat, ///< Camera format definition
+        const pmFPA *fpa, ///< FPA for concept, or NULL
+        const pmChip *chip, ///< Chip for concept, or NULL
+        const pmCell *cell ///< Cell for concept, or NULL
+                                             );
+
+/// Parse the time system concepts: FPA.TIMESYS and CELL.TIMESYS
+psMetadataItem *p_pmConceptParse_TIMESYS(const psMetadataItem *concept, ///< Concept to parse
+        const psMetadataItem *pattern, ///< Pattern to use in parsing
+                                            pmConceptSource source, ///< Source for concept
+        const psMetadata *cameraFormat, ///< Camera format definition
+        const pmFPA *fpa, ///< FPA for concept, or NULL
+        const pmChip *chip, ///< Chip for concept, or NULL
+        const pmCell *cell ///< Cell for concept, or NULL
+                                        );
+
+/// Parse the time concepts: FPA.TIME and CELL.TIME
+psMetadataItem *p_pmConceptParse_TIME(const psMetadataItem *concept, ///< Concept to parse
+                                      const psMetadataItem *pattern, ///< Pattern to use in parsing
+                                            pmConceptSource source, ///< Source for concept
+                                      const psMetadata *cameraFormat, ///< Camera format definition
+                                      const pmFPA *fpa, ///< FPA for concept, or NULL
+                                      const pmChip *chip, ///< Chip for concept, or NULL
+                                      const pmCell *cell ///< Cell for concept, or NULL
+                                     );
+
+/// Parse a cell position concept, e.g., CELL.X0
+psMetadataItem *p_pmConceptParse_Positions(const psMetadataItem *concept, ///< Concept to parse
+        const psMetadataItem *pattern, ///< Pattern to use in parsing
+                                            pmConceptSource source, ///< Source for concept
+       const psMetadata *cameraFormat, ///< Camera format definition
+        const pmFPA *fpa, ///< FPA for concept, or NULL
+        const pmChip *chip, ///< Chip for concept, or NULL
+        const pmCell *cell ///< Cell for concept, or NULL
+                                          );
+
+/// Format the CELL.TRIMSEC concept
+psMetadataItem *p_pmConceptFormat_CELL_TRIMSEC(const psMetadataItem *concept, ///< Concept to format
+                                            pmConceptSource source, ///< Source for concept
+        const psMetadata *cameraFormat, ///< Camera format definition
+        const pmFPA *fpa, ///< FPA for concept, or NULL
+        const pmChip *chip, ///< Chip for concept, or NULL
+        const pmCell *cell ///< Cell for concept, or NULL
+                                              );
+
+/// Format the CELL.BIASSEC concept
+psMetadataItem *p_pmConceptFormat_CELL_BIASSEC(const psMetadataItem *concept, ///< Concept to format
+                                            pmConceptSource source, ///< Source for concept
+        const psMetadata *cameraFormat, ///< Camera format definition
+        const pmFPA *fpa, ///< FPA for concept, or NULL
+        const pmChip *chip, ///< Chip for concept, or NULL
+        const pmCell *cell ///< Cell for concept, or NULL
+                                              );
+
+/// Format the CELL.XBIN concept
+psMetadataItem *p_pmConceptFormat_CELL_XBIN(const psMetadataItem *concept, ///< Concept to format
+                                            pmConceptSource source, ///< Source for concept
+        const psMetadata *cameraFormat, ///< Camera format definition
+        const pmFPA *fpa, ///< FPA for concept, or NULL
+        const pmChip *chip, ///< Chip for concept, or NULL
+        const pmCell *cell ///< Cell for concept, or NULL
+                                           );
+
+/// Format the CELL.YBIN concept
+psMetadataItem *p_pmConceptFormat_CELL_YBIN(const psMetadataItem *concept, ///< Concept to format
+                                            pmConceptSource source, ///< Source for concept
+        const psMetadata *cameraFormat, ///< Camera format definition
+        const pmFPA *fpa, ///< FPA for concept, or NULL
+        const pmChip *chip, ///< Chip for concept, or NULL
+        const pmCell *cell ///< Cell for concept, or NULL
+                                           );
+
+/// Format the time system concepts: FPA.TIMESYS and CELL.TIMESYS
+psMetadataItem *p_pmConceptFormat_TIMESYS(const psMetadataItem *concept, ///< Concept to format
+                                            pmConceptSource source, ///< Source for concept
+        const psMetadata *cameraFormat, ///< Camera format definition
+        const pmFPA *fpa, ///< FPA for concept, or NULL
+        const pmChip *chip, ///< Chip for concept, or NULL
+        const pmCell *cell ///< Cell for concept, or NULL
+                                         );
+
+/// Format the time concepts: FPA.TIME and CELL.TIME
+psMetadataItem *p_pmConceptFormat_TIME(const psMetadataItem *concept, ///< Concept to format
+                                            pmConceptSource source, ///< Source for concept
+                                       const psMetadata *cameraFormat, ///< Camera format definition
+                                       const pmFPA *fpa, ///< FPA for concept, or NULL
+                                       const pmChip *chip, ///< Chip for concept, or NULL
+                                       const pmCell *cell ///< Cell for concept, or NULL
+                                      );
+
+/// Format a cell position concept, e.g., CELL.X0
+psMetadataItem *p_pmConceptFormat_Positions(const psMetadataItem *concept, ///< Concept to format
+                                            pmConceptSource source, ///< Source for concept
+        const psMetadata *cameraFormat, ///< Camera format definition
+        const pmFPA *fpa, ///< FPA for concept, or NULL
+        const pmChip *chip, ///< Chip for concept, or NULL
+        const pmCell *cell ///< Cell for concept, or NULL
+                                           );
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsUpdate.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsUpdate.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsUpdate.c	(revision 22157)
@@ -0,0 +1,90 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmConceptsUpdate.h"
+
+bool pmConceptsUpdate(const pmFPA *fpa, const pmChip *chip, const pmCell *cell)
+{
+    if (fpa) {
+        // Check for FPA concepts updates
+    }
+
+    if (chip) {
+        // Check for chip concepts updates
+    }
+
+    if (cell) {
+        // Check for cell concepts updates
+
+	bool xStatus, yStatus; // Status of MD lookups
+	psImageBinning *binning = psImageBinningAlloc();
+	binning->nXbin = psMetadataLookupS32(&xStatus, cell->concepts, "CELL.XBIN");
+	binning->nYbin = psMetadataLookupS32(&yStatus, cell->concepts, "CELL.YBIN");
+	if (!xStatus || !yStatus) {
+	    // XXX should this be an error condition?
+	    psFree (binning);
+	    return true;
+	}
+	if (!binning->nXbin || !binning->nXbin) {
+	    // XXX should this be an error condition?
+	    psFree (binning);
+	    return true;
+	}
+
+        // CELL.TRIMSEC needs to be updated for the binning
+        if (psMetadataLookup(cell->concepts, "CELL.TRIMSEC.UPDATE")) {
+	    psRegion *trimsec = psMetadataLookupPtr(NULL, cell->concepts, "CELL.TRIMSEC"); // Trim section
+	    *trimsec = psImageBinningSetRuffRegion (binning, *trimsec);
+	    // force integer pixels : truncate x0, roundup x1:
+	    trimsec->x0 = (int)trimsec->x0;
+	    if (trimsec->x1 > (int)trimsec->x1) {
+		trimsec->x1 = (int)trimsec->x1 + 1;
+	    } else {
+		trimsec->x1 = (int)trimsec->x1;
+	    }		
+	    trimsec->y0 = (int)trimsec->y0;
+	    if (trimsec->y1 > (int)trimsec->y1) {
+		trimsec->y1 = (int)trimsec->y1 + 1;
+	    } else {
+		trimsec->y1 = (int)trimsec->y1;
+	    }		
+	    psMetadataRemoveKey(cell->concepts, "CELL.TRIMSEC.UPDATE");
+        }
+
+        // CELL.BIASSEC needs to be updated for the binning
+        if (psMetadataLookup(cell->concepts, "CELL.BIASSEC.UPDATE")) {
+	    psList *biassecs = psMetadataLookupPtr(NULL, cell->concepts, "CELL.BIASSEC"); // Bias sections
+	    psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, true); // Iterator
+	    psRegion *biassec; // Bias region, from iteration
+	    while ((biassec = psListGetAndIncrement(biassecsIter))) {
+		*biassec = psImageBinningSetRuffRegion (binning, *biassec);
+		// force integer pixels : truncate x0, roundup x1:
+		biassec->x0 = (int)biassec->x0;
+		if (biassec->x1 > (int)biassec->x1) {
+		    biassec->x1 = (int)biassec->x1 + 1;
+		} else {
+		    biassec->x1 = (int)biassec->x1;
+		}		
+		biassec->y0 = (int)biassec->y0;
+		if (biassec->y1 > (int)biassec->y1) {
+		    biassec->y1 = (int)biassec->y1 + 1;
+		} else {
+		    biassec->y1 = (int)biassec->y1;
+		}		
+	    }
+	    psFree(biassecsIter);
+	    psMetadataRemoveKey(cell->concepts, "CELL.BIASSEC.UPDATE");
+        }
+	psFree (binning);
+    }
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsUpdate.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsUpdate.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsUpdate.h	(revision 22157)
@@ -0,0 +1,25 @@
+/* @file  pmConceptsUpdate.h
+ * @brief Function to update concepts.
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-03-30 21:12:56 $
+ * Copyright 2005-2007 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_CONCEPTS_UPDATE_H
+#define PM_CONCEPTS_UPDATE_H
+
+/// Check for concepts to update.
+///
+/// Updating concepts is necessary if one concept depends on the value of another.  In that case, a flag
+/// (e.g., CONCEPTNAME.UPDATE" in the concepts) can be set, and it can be updated once the required value is
+/// known.
+bool pmConceptsUpdate(const pmFPA *fpa,       ///< FPA for which to update concepts
+                      const pmChip *chip,     ///< Chip for which to update concepts
+                      const pmCell *cell      ///< Cell for which to update concepts
+    );
+
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsWrite.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsWrite.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsWrite.c	(revision 22157)
@@ -0,0 +1,487 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <strings.h>            /* for strn?casecmp */
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmHDUUtils.h"
+#include "pmConcepts.h"
+#include "pmConceptsRead.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static bool compareConcepts(const psMetadataItem *compare, // Item to compare
+                            const psMetadataItem *standard // Standard for comparison
+                           )
+{
+    // First order checks
+    if (! compare || ! standard) {
+        return false;
+    }
+    if (strcasecmp(compare->name, standard->name) != 0) {
+        return false;
+    }
+
+    // Special case: list
+    if (compare->type == PS_DATA_LIST) {
+        // "compare" contains a list of psMetadataItems
+        // "standard" likely contains just a string (but it might possibly be a list of strings)
+        psList *cList = compare->data.V; // The list from comparison item
+        psList *sList = NULL;         // The list from standard item
+        switch (standard->type) {
+        case PS_DATA_STRING:
+            sList = psStringSplit(standard->data.V, " ;", true);
+            break;
+        case PS_DATA_LIST:
+            sList = psMemIncrRefCounter(standard->data.V);
+            break;
+        default:
+            return false;
+        }
+        if (cList->n != sList->n) {
+            psFree(sList);
+            return false;
+        }
+        psVector *match = psVectorAlloc(cList->n, PS_TYPE_U8); // Array indicating which values match
+        psVectorInit(match, 0);
+        psListIterator *cIter = psListIteratorAlloc(cList, PS_LIST_HEAD, false); // compare iterator
+        psListIterator *sIter = psListIteratorAlloc(sList, PS_LIST_HEAD, false); // standard iterator
+        psMetadataItem *cItem = NULL; // Item from compare list
+        while ((cItem = psListGetAndIncrement(cIter))) {
+            if (cItem->type != PS_DATA_STRING) {
+                psWarning("psMetadataItem from list is of type %x instead of "
+                         "%x (PS_DATA_STRING) --- can't interpret.\n", cItem->type, PS_DATA_STRING);
+                psFree(cIter);
+                psFree(sIter);
+                psFree(match);
+                psFree(sList);
+                return false;
+            }
+            psString cString = cItem->data.V; // String from compare list
+            psListIteratorSet(sIter, PS_LIST_HEAD);
+            int index = 0;            // Index for list
+            bool found = false;       // Found a match?
+            for (psString sString = NULL; (sString = psListGetAndIncrement(sIter)) && !found; index++) {
+                if (strcasecmp(cString, sString) == 0) {
+                    match->data.U8[index]++;
+                    found = true;
+                }
+            }
+            if (! found) {
+                // Can give up immediately
+                psFree(cIter);
+                psFree(sIter);
+                psFree(match);
+                psFree(sList);
+                return false;
+            }
+        }
+        // Make sure we got 100% matches in both directions
+        bool allMatch = true;         // Did all of them match?
+        for (int i = 0; i < match->n && allMatch; i++) {
+            if (!match->data.U8[i]) {
+                allMatch = false;
+            }
+        }
+        psFree(cIter);
+        psFree(sIter);
+        psFree(sList);
+        psFree(match);
+        return allMatch;
+    }
+
+    return psMetadataItemCompare(compare, standard);
+
+}
+
+
+// Format a single concept
+static psMetadataItem *conceptFormat(const pmConceptSpec *spec, // The concept specification
+                                     const psMetadataItem *concept, // The concept to parse
+                                     pmConceptSource source, // The concept source
+                                     const psMetadata *cameraFormat, // The camera format
+                                     const pmFPA *fpa, // The FPA
+                                     const pmChip *chip, // The chip
+                                     const pmCell *cell // The cell
+                                    )
+{
+    assert(spec);
+    assert(cameraFormat);
+
+    if (concept) {
+        psMetadataItem *formatted = NULL;  // The formatted concept
+        if (spec->format) {
+            formatted = spec->format(concept, source, cameraFormat, fpa, chip, cell);
+        } else if (strcmp(concept->name, spec->blank->name) != 0) {
+            // Adjust so that the name is correct
+            formatted = psMetadataItemCopy(concept);
+            psFree(formatted->name);
+            formatted->name = psStringCopy(spec->blank->name);
+        } else {
+            // Can get away with merely incrementing the reference counter
+            formatted = psMemIncrRefCounter((const psPtr)concept);
+        }
+
+        return formatted;
+    }
+    return NULL;
+}
+
+// Write a single value to a header
+static bool writeSingleHeader(pmHDU *hdu, // HDU for which to add to the header
+                              const char *keyword, // Keyword to add
+                              const psMetadataItem *item // Item to add to the header; may be NULL
+                             )
+{
+    assert(hdu);
+    assert(keyword && strlen(keyword) > 0);
+
+    if (!hdu->header) {
+        hdu->header = psMetadataAlloc();
+    }
+    if (!item) {
+        psTrace("psModules.concepts", 9, "Writing header %s: <<<BLANK>>>\n", keyword);
+        // Assume it's a NULL string: it's most easily parsed.
+        return psMetadataAddStr(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, NULL, NULL);
+    }
+    switch (item->type) {
+    case PS_DATA_STRING:
+        psTrace("psModules.concepts", 9, "Writing header %s: %s\n", keyword, item->data.str);
+        return psMetadataAddStr(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
+                                item->data.V);
+    case PS_DATA_S32:
+        psTrace("psModules.concepts", 9, "Writing header %s: %d\n", keyword, item->data.S32);
+        return psMetadataAddS32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
+                                item->data.S32);
+    case PS_DATA_F32:
+        psTrace("psModules.concepts", 9, "Writing header %s: %f\n", keyword, item->data.F32);
+        return psMetadataAddF32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
+                                item->data.F32);
+    case PS_DATA_F64:
+        psTrace("psModules.concepts", 9, "Writing header %s: %f\n", keyword, item->data.F64);
+        return psMetadataAddF64(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
+                                item->data.F64);
+    case PS_DATA_REGION: {
+            psString region = psRegionToString(*(psRegion*)item->data.V);
+            psTrace("psModules.concepts", 9, "Writing header %s: %s\n", keyword, region);
+            bool result = psMetadataAddStr(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
+                                           region);
+            psFree(region);
+            return result;
+        }
+    default:
+        psWarning("Type of %s is not suitable for a FITS header --- not added.\n",
+                 item->name);
+        return false;
+    }
+}
+
+
+// Write potentially multiple values to a header
+static bool writeHeader(pmHDU *hdu,     // HDU for which to add to the header
+                        const char *keywords, // Keywords to add
+                        const psMetadataItem *item // Item to add to the header
+                       )
+{
+    assert(hdu);
+    assert(keywords);
+    assert(item);
+
+    bool status = true;                 // Status of writing headers, to be returned
+    if (item->type == PS_DATA_LIST) {
+        psList *values = item->data.V;  // List of outputs
+        psList *keys = psStringSplit(keywords, " ,;", true); // List of keywords
+        if (keys->n != values->n && values->n != 0) {
+            psError(PS_ERR_UNKNOWN, true, "Number of keywords (%ld) does not match number of "
+                    "values (%ld).\n", keys->n, values->n);
+            psFree(keys);
+            return false;
+        }
+        psListIterator *keysIter = psListIteratorAlloc(keys, PS_LIST_HEAD, false); // Iterator for keywords
+        psListIterator *valuesIter = psListIteratorAlloc(values, PS_LIST_HEAD, false); // Iterator for values
+        psString key = NULL;            // Keyword from iteration
+        while ((key = psListGetAndIncrement(keysIter))) {
+            psMetadataItem *value = psListGetAndIncrement(valuesIter); // Value from iteration; may be NULL
+            status &= writeSingleHeader(hdu, key, value);
+        }
+        psFree(keysIter);
+        psFree(valuesIter);
+        psFree(keys);
+    } else {
+        status = writeSingleHeader(hdu, keywords, item);
+    }
+    return status;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool p_pmConceptsWriteToCells(const psMetadata *specs, const pmCell *cell, const psMetadata *concepts)
+{
+    PS_ASSERT_PTR_NON_NULL(specs, false);
+    PS_ASSERT_PTR_NON_NULL(concepts, false);
+    if (!cell) {
+        return false;
+    }
+    if (!cell->config) {
+        return false;
+    }
+
+    pmHDU *hdu = pmHDUGetLowest(NULL, NULL, cell); // The HDU at the lowest level
+    if (!hdu) {
+        return false;
+    }
+    psMetadata *cameraFormat = hdu->format; // The camera format
+    psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *specItem = NULL;    // Item from the specs metadata
+    while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+        pmConceptSpec *spec = specItem->data.V; // The specification
+        psString name = specItem->name; // The concept name
+        psMetadataItem *cameraItem = psMetadataLookup(cell->config, name); // The concept from the camera,
+        // or NULL
+        if (cameraItem) {
+            // Grab the concept
+            psMetadataItem *conceptItem = psMetadataLookup(concepts, name); // The concept
+
+            psString nameSource = NULL; // String with the concept name and ".SOURCE" added
+            psStringAppend(&nameSource, "%s.SOURCE", name);
+            bool mdok = true;       // Status of MD lookup
+            psString source = psMetadataLookupStr(&mdok, cell->config, nameSource); // The source
+            if (mdok && strlen(source) > 0) {
+                psTrace("psModules.concepts", 8, "%s is %s\n", nameSource, source);
+                if (strcasecmp(source, "HEADER") == 0) {
+                    if (cameraItem->type != PS_DATA_STRING) {
+                        psWarning("Concept %s is specified by header, but is not "
+                                 "of type STR --- ignored.\n", conceptItem->name);
+                        continue;
+                    }
+
+                    // Formatted version
+                    psMetadataItem *formatted = conceptFormat(spec, conceptItem, PM_CONCEPT_SOURCE_HEADER,
+                                                              cameraFormat, NULL, NULL, cell);
+                    if (!formatted) {
+                        continue;
+                    }
+
+                    psTrace("psModules.concepts", 8, "Writing %s to header %s\n", name, cameraItem->data.str);
+                    writeHeader(hdu, cameraItem->data.V, formatted);
+                    psFree(formatted);
+                } else if (strcasecmp(source, "VALUE") == 0) {
+                    // Formatted version
+                    psMetadataItem *formatted = conceptFormat(spec, conceptItem, PM_CONCEPT_SOURCE_CELLS,
+                                                              cameraFormat, NULL, NULL, cell);
+                    if (!formatted) {
+                        continue;
+                    }
+
+                    psTrace("psModules.concepts", 8, "Checking %s against camera format.\n", name);
+                    if (! compareConcepts(formatted, cameraItem)) {
+                        psWarning("Concept %s is specified by value in the camera "
+                                 "format, but the values don't match.\n", name);
+                    }
+                    psFree(formatted);
+                } else {
+                    psWarning("Concept source %s isn't HEADER or VALUE --- can't "
+                             "write\n", nameSource);
+                }
+            } else {
+                // Assume it's specified by value
+                psMetadataItem *formatted = conceptFormat(spec, conceptItem, PM_CONCEPT_SOURCE_CELLS,
+                                                          cameraFormat, NULL, NULL, cell);
+                if (!formatted) {
+                    continue;
+                }
+
+                if (! compareConcepts(formatted, cameraItem)) {
+                    psWarning("Concept %s is specified by value in the camera "
+                             "format, but the values don't match.\n", name);
+                }
+                psFree(formatted);
+            }
+            psFree(nameSource);
+        }
+
+    }
+    psFree(specsIter);
+    return true;
+}
+
+bool p_pmConceptsWriteToDefaults(const psMetadata *specs, const pmFPA *fpa, const pmChip *chip,
+                                 const pmCell *cell, psMetadata *concepts)
+{
+    PS_ASSERT_PTR_NON_NULL(specs, false);
+    PS_ASSERT_PTR_NON_NULL(concepts, false);
+
+    pmHDU *hdu = pmHDUGetLowest(fpa, chip, cell); // The HDU at the lowest level
+    if (!hdu) {
+        return false;
+    }
+    psMetadata *cameraFormat = hdu->format; // The camera format
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *defaults = psMetadataLookupMetadata(&mdok, cameraFormat, "DEFAULTS"); // The DEFAULTS spec
+    if (!mdok || !defaults) {
+        return false;
+    }
+
+    psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *specItem = NULL;    // Item from the specs metadata
+    while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+        pmConceptSpec *spec = specItem->data.V; // The specification
+        psString name = specItem->name; // The concept name
+
+        psMetadataItem *defaultItem = p_pmConceptsReadSingleFromDefaults(name, defaults, fpa, chip, cell);
+        if (!defaultItem) {
+            continue;
+        }
+        psMetadataItem *conceptItem = psMetadataLookup(concepts, name); // The item from the concepts
+        psMetadataItem *formatted = conceptFormat(spec, conceptItem, PM_CONCEPT_SOURCE_DEFAULTS,
+                                                  cameraFormat, fpa, chip, cell);
+        if (!formatted) {
+            continue;
+        }
+
+        if (strcmp(defaultItem->name, name) != 0) {
+            // Correct the name to match the concept name
+            defaultItem = psMetadataItemCopy(defaultItem);
+            psFree(defaultItem->name);
+            defaultItem->name = psStringCopy(name);
+        } else {
+            psMemIncrRefCounter(defaultItem);
+        }
+
+        if (!compareConcepts(formatted, defaultItem)) {
+            psWarning("Concept %s is specified by the DEFAULTS in the camera "
+                      "format, but the values don't match.\n", name);
+        }
+        psFree(defaultItem);
+        psFree(formatted);
+    }
+    psFree(specsIter);
+    return true;
+}
+
+
+bool p_pmConceptsWriteToHeader(const psMetadata *specs, const pmFPA *fpa, const pmChip *chip,
+                               const pmCell *cell, const psMetadata *concepts)
+{
+    PS_ASSERT_PTR_NON_NULL(specs, false);
+    PS_ASSERT_PTR_NON_NULL(concepts, false);
+
+    pmHDU *hdu = pmHDUGetLowest(fpa, chip, cell); // The HDU at the lowest level
+    if (!hdu) {
+        return false;
+    }
+    psMetadata *cameraFormat = hdu->format; // The camera format
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *translation = psMetadataLookupMetadata(&mdok, cameraFormat, "TRANSLATION"); // The TRANSLATION spec
+    if (!mdok || !translation) {
+        return false;
+    }
+
+    psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *specItem = NULL;    // Item from the specs metadata
+    while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+        pmConceptSpec *spec = specItem->data.V; // The specification
+        psString name = specItem->name; // The concept name
+        psMetadataItem *headerItem = psMetadataLookup(translation, name); // The item from the TRANSLATION
+        if (!headerItem) {
+            continue;
+        }
+        if (headerItem->type == PS_DATA_METADATA) {
+            // This is a menu
+            psTrace("psModules.concepts", 5, "%s is of type METADATA.\n", name);
+            headerItem = p_pmConceptsDepend(name, headerItem->data.md, translation, fpa, chip, cell);
+            if (!headerItem) {
+                continue;
+            }
+        }
+        if (headerItem->type != PS_DATA_STRING) {
+            psWarning("TRANSLATION keyword for concept %s isn't of type STR --- ignored.", name);
+            continue;
+        }
+        psTrace("psModules.concepts", 3, "Writing %s to header %s\n", name, headerItem->data.str);
+        psMetadataItem *conceptItem = psMetadataLookup(concepts, name); // The item from the concepts
+        psMetadataItem *formatted = conceptFormat(spec, conceptItem, PM_CONCEPT_SOURCE_HEADER,
+                                                  cameraFormat, fpa, chip, cell);
+        if (!formatted) {
+            continue;
+        }
+        writeHeader(hdu, headerItem->data.V, formatted);
+        psFree(formatted);
+    }
+    psFree(specsIter);
+    return true;
+}
+
+// XXX Warning: This code has not been tested at all
+bool p_pmConceptsWriteToDatabase(const psMetadata *specs, const pmFPA *fpa, const pmChip *chip,
+                                 const pmCell *cell, pmConfig *config, const psMetadata *concepts)
+{
+    PS_ASSERT_PTR_NON_NULL(specs, false);
+    PS_ASSERT_PTR_NON_NULL(concepts, false);
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    #ifndef HAVE_PSDB
+    return false;
+    #else
+
+    pmHDU *hdu = pmHDUGetLowest(fpa, chip, cell); // The HDU at the lowest level
+    if (!hdu) {
+        return false;
+    }
+    psMetadata *cameraFormat = hdu->format; // The camera format
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *database = psMetadataLookupMetadata(&mdok, cameraFormat, "DATABASE"); // The DATABASE spec
+    if (!mdok || !database) {
+        return false;
+    }
+    psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *specItem = NULL;    // Item from the specs metadata
+    while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+        pmConceptSpec *spec = specItem->data.V; // The specification
+        psString name = specItem->name; // The concept name
+
+        psMetadataItem *dbItem = p_pmConceptsReadSingleFromDatabase(name, database, config, fpa, chip, cell);
+        if (!dbItem) {
+            continue;
+        }
+
+        psMetadataItem *conceptItem = psMetadataLookup(concepts, name); // The item from the concepts
+        psMetadataItem *formatted = conceptFormat(spec, conceptItem, PM_CONCEPT_SOURCE_DATABASE,
+                                                  cameraFormat, fpa, chip, cell);
+        if (!formatted) {
+            continue;
+        }
+
+        if (strcmp(dbItem->name, name) != 0) {
+            // Correct the name to match the concept name
+            dbItem = psMetadataItemCopy(dbItem);
+            psFree(dbItem->name);
+            dbItem->name = psStringCopy(name);
+        } else {
+            psMemIncrRefCounter(dbItem);
+        }
+
+        if (!compareConcepts(formatted, dbItem)) {
+            psWarning("Concept %s is specified by the DATABASE in the camera "
+                      "format, but the values don't match.\n", name);
+        }
+        psFree(dbItem);
+        psFree(formatted);
+    }
+    psFree(specsIter);
+    return true;
+    #endif
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsWrite.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsWrite.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/concepts/pmConceptsWrite.h	(revision 22157)
@@ -0,0 +1,61 @@
+/* @file  pmConceptsWrite.h
+ * @brief Writing concepts to a variety of sources.
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.7 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-06-17 22:16:38 $
+ * Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_CONCEPTS_WRITE_H
+#define PM_CONCEPTS_WRITE_H
+
+/// @addtogroup Concepts Data Abstraction Concepts
+/// @{
+
+/// "Write" concepts to (actually, check against) the camera format file's CELLS.
+///
+/// Examines the CELLS metadata in the camera format file for the current type of cell, and checks that the
+/// concepts as defined there match the ones defined in the cell.  A warning is produced if the concepts do
+/// not match.
+bool p_pmConceptsWriteToCells(const psMetadata *specs, ///< The concept specifications
+                              const pmCell *cell, ///< The cell
+                              const psMetadata *concepts ///< The concepts
+                             );
+
+/// "Write" concepts to (actually, check against) the camera format file's DEFAULTS.
+///
+/// Examines the DEFAULTS metadata in the camera format file, and checks that the concepts as defined there
+/// match the ones defined in the cell.  A warning is produced if the concepts do not match.
+bool p_pmConceptsWriteToDefaults(const psMetadata *specs, ///< The concept specifications
+                                 const pmFPA *fpa, ///< The FPA
+                                 const pmChip *chip, ///< The chip
+                                 const pmCell *cell, ///< The cell
+                                 const psMetadata *concepts ///< The concepts
+                                );
+
+/// "Write" concepts to (actually, add to, pending a later write) the FITS header.
+///
+/// Examines the FITS header TRANSLATION metadata in the camera format file, and writes concepts to the
+/// appropriate FITS headers in the HDU, in preparation for a future write of the HDU.
+bool p_pmConceptsWriteToHeader(const psMetadata *specs, ///< The concept specifications
+                               const pmFPA *fpa, ///< The FPA
+                               const pmChip *chip, ///< The chip
+                               const pmCell *cell, ///< The cell
+                               const psMetadata *concepts ///< The concepts
+                              );
+
+/// Write concepts to the database.
+///
+/// Examines the DATABASE metadata in the camera format file, and writes concepts to the database.
+/// Warning: This function has not been tested; use at your own risk.
+bool p_pmConceptsWriteToDatabase(const psMetadata *specs, ///< The concept specifications
+                                 const pmFPA *fpa, ///< The FPA
+                                 const pmChip *chip, ///< The chip
+                                 const pmCell *cell, ///< The cell
+                                 pmConfig *config,///< Configuration
+                                 const psMetadata *concepts ///< The concepts
+                                );
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/config/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/config/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/config/.cvsignore	(revision 22157)
@@ -0,0 +1,8 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.la
+*.lo
+pmErrorCodes.c
+pmErrorCodes.h
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/config/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/config/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/config/Makefile.am	(revision 22157)
@@ -0,0 +1,33 @@
+noinst_LTLIBRARIES = libpsmodulesconfig.la
+
+libpsmodulesconfig_la_CPPFLAGS = $(SRCINC) $(PSMODULES_CFLAGS)
+libpsmodulesconfig_la_LDFLAGS  = -release $(PACKAGE_VERSION)
+libpsmodulesconfig_la_SOURCES  = \
+    pmConfig.c \
+    pmConfigRecipes.c \
+    pmConfigCamera.c \
+    pmConfigCommand.c \
+    pmConfigMask.c \
+    pmVersion.c \
+    pmErrorCodes.c
+
+pkginclude_HEADERS = \
+    pmConfig.h \
+    pmConfigRecipes.h \
+    pmConfigCamera.h \
+    pmConfigCommand.h \
+    pmConfigMask.h \
+    pmVersion.h \
+    pmErrorCodes.h
+
+# Error codes.
+BUILT_SOURCES = pmErrorCodes.h pmErrorCodes.c
+CLEANFILES = *~ pmErrorCodes.h pmErrorCodes.c
+
+pmErrorCodes.h : pmErrorCodes.dat pmErrorCodes.h.in
+	$(ERRORCODES) --data=pmErrorCodes.dat --outdir=. pmErrorCodes.h
+
+pmErrorCodes.c : pmErrorCodes.dat pmErrorCodes.c.in pmErrorCodes.h
+	$(ERRORCODES) --data=pmErrorCodes.dat --outdir=. pmErrorCodes.c
+
+EXTRA_DIST = pmErrorCodes.h.in pmErrorCodes.dat pmErrorCodes.c.in
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/config/notes.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/config/notes.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/config/notes.txt	(revision 22157)
@@ -0,0 +1,56 @@
+
+* current recipe load sequence:
+
+** load the basic config information.
+   (unless camera is explicitly specified, camera-specific recipes are not loaded)
+
+** save the generic command-line options (-D ..) on config->arguments:OPTIONS:(recipe)
+
+** supplement recipe with specific command-line options (-isdark...) to config->arguments:OPTIONS:(recipe)
+
+** SYSTEM
+** load all files from config->complete:RECIPES to config->recipes
+   (loadRecipeSystem)
+
+** CAMERA
+** load the recipes for the specified camera from the camera-specific
+   config file to config->recipes (overlay existing metadata)
+
+** CL:arguments
+** load recipe files defined on command line in the form -recipe-file (recipe) (filename)
+   from config->arguments:RECIPES
+   
+** CL:symbols
+** load symbolic recipes defined on command line in the form -recipe (recipe) (symbolic name)
+   from config->recipeSymbols
+
+** CL:options
+** load values defined on command line in the form -D key value
+   from config->arguments:OPTIONS:(recipe)
+
+---
+
+what we really want:
+
+** SYSTEM
+** load all files from config->complete:RECIPES to config->recipes
+   (loadRecipeSystem)
+
+** CAMERA
+** load the recipes for the specified camera from the camera-specific
+   config file to config->recipesCamera
+
+** CL:arguments
+** load recipe files defined on command line in the form -recipe-file (recipe) (filename)
+   from config->arguments:RECIPES
+   
+** CL:symbols
+** load symbolic recipes defined on command line in the form -recipe (recipe) (symbolic name)
+   from config->recipeSymbols
+
+** CL:options
+** load values defined on command line in the form -D key value
+   from config->arguments:OPTIONS:(recipe)
+
+metadata = psMetadataConfigRead (recipes/NAME.config)
+metadata = 
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfig.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfig.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfig.c	(revision 22157)
@@ -0,0 +1,1769 @@
+/** @file  pmConfig.h
+ *
+ *  @author PAP (IfA)
+ *  @author EAM (IfA)
+ *
+ *  Copyright 2007 Institute for Astronomy, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>            /* for strn?casecmp */
+#include <unistd.h>
+#include <libgen.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <glob.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmErrorCodes.h"
+#include "pmFPALevel.h"
+#include "pmConfigRecipes.h"
+#include "pmConfigCamera.h"
+
+#ifdef HAVE_NEBCLIENT
+#include <nebclient.h>
+#endif // ifdef HAVE_NEBCLIENT
+
+#define IPPRC_ENV "IPPRC"        // Name of the environment variable containing the top-level config file
+#define IPPRC_FILE ".ipprc"      // Default top-level config file
+
+#define DEFAULT_LOG STDERR_FILENO       // Default file descriptor for log messages
+#define DEFAULT_TRACE STDERR_FILENO     // Default file descriptor for trace messages
+
+static bool readCameraConfig = true;    // Read the camera config on startup (with pmConfigRead)?
+static psArray *configPath = NULL;      // Search path for configuration files
+
+static bool checkPath(const char *filename, bool create, bool trunc);
+
+bool pmConfigReadParamsSet(bool newReadCameraConfig)
+{
+    bool oldReadCameraConfig = readCameraConfig;
+    readCameraConfig = newReadCameraConfig;
+    return oldReadCameraConfig;
+}
+
+static void configFree(pmConfig *config)
+{
+    psFree(config->user);
+    psFree(config->site);
+    psFree(config->system);
+    psFree(config->complete);
+    psFree(config->files);
+    psFree(config->camera);
+    psFree(config->cameraName);
+    psFree(config->format);
+    psFree(config->formatName);
+    psFree(config->recipes);
+    psFree(config->recipesCamera);
+    psFree(config->recipeSymbols);
+    psFree(config->arguments);
+    psFree(config->database);
+
+    // Close log and trace files
+    if (config->logFD != STDOUT_FILENO && config->logFD != STDERR_FILENO) {
+        close(config->logFD);
+    }
+    if (config->traceFD != STDOUT_FILENO && config->traceFD != STDERR_FILENO) {
+        close(config->traceFD);
+    }
+
+    return;
+}
+
+// Check the end of a string for a word; return the length of the string without the ending word
+// Used to identify the camera base name (e.g., "MEGACAM" out of "_MEGACAM-CHIP")
+int checkEndForWord(const char *line,   // String to check for ending word
+                    const char *word    // Ending word to check for
+                    )
+{
+    int wlen = strlen(word);            // Length of word
+    int nlen = strlen(line);            // Length of line
+    if (nlen < wlen) {
+        return 0;
+    }
+
+    char *ptr = (char *)line + nlen - wlen; // Expected position of ending word
+    if (strcasecmp(ptr, word)) {
+        return 0;
+    }
+
+    return (nlen - wlen);
+}
+
+// the camera name is of the form: BASE, BASE_CHIP, or BASE_FPA.  pull out BASE:
+char *cameraBaseName(const char *name   // Name of meta-camera
+                     )
+{
+    char *answer;
+
+    int N = checkEndForWord (name, "-CHIP");
+    if (N && name[0] == '_') {
+        psString answer = psStringNCopy(name + 1, N - 1);
+        return answer;
+    }
+
+    N = checkEndForWord(name, "-FPA");
+    if (N && name[0] == '_') {
+        psString answer = psStringNCopy(name + 1, N - 1);
+        return answer;
+    }
+
+    N = checkEndForWord(name, "-SKYCELL");
+    if (N && name[0] == '_') {
+        psString answer = psStringNCopy(name + 1, N - 1);
+        return answer;
+    }
+
+    answer = psStringCopy(name);
+    return answer;
+}
+
+
+pmConfig *pmConfigAlloc()
+{
+    pmConfig *config = psAlloc(sizeof(pmConfig));
+    (void)psMemSetDeallocator(config, (psFreeFunc)configFree);
+
+    // Initialise
+    config->user = NULL;
+    config->site = NULL;
+    config->system = NULL;
+    config->complete = NULL;
+    config->camera = NULL;
+    config->cameraName = NULL;
+    config->format = NULL;
+    config->formatName = NULL;
+    config->recipes = psMetadataAlloc();
+    config->recipesRead = PM_RECIPE_SOURCE_NONE;
+    config->recipesCamera = psMetadataAlloc();
+    config->recipeSymbols = psMetadataAlloc();
+    config->arguments = psMetadataAlloc();
+    config->database = NULL;
+    config->defaultRecipe = NULL;
+
+    config->traceFD = DEFAULT_TRACE;
+    config->logFD = DEFAULT_LOG;
+
+    // the file structure is used to carry pmFPAfiles
+    config->files = psMetadataAlloc ();
+    return config;
+}
+
+// Resolve environment variables within a directory name; returns the resolved directory string.
+// The returned string is likely a new pointer; the old pointer should be freed by psStringSubstitute.
+static psString resolveEnvVar(psString dir // Directory to check for environment variables
+                             )
+{
+    char *envStart;                     // Start of any environment variable
+    while ((envStart = strchr(dir, '$'))) {
+        char *envName = envStart + 1;   // Start of the environment variable name
+        if (envName[0] == '\0') {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Path %s contains a bad environment variable.\n", dir);
+            return NULL;
+        }
+        if (envName[0] == '{') {
+            envName++;
+            if (envName[0] == '\0') {
+                psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                        "Path %s contains a bad environment variable.\n", dir);
+                return NULL;
+            }
+        }
+        char *envStop = strpbrk(envStart, "}/"); // End of the environment variable
+        ssize_t nameLength = envStop ? envStop - envName : strlen(envName); // Length of the name
+        psString name = psStringNCopy(envName, nameLength); // The environment variable name
+        char *value = getenv(name);     // Value of the environment variable
+        psFree(name);
+        psString valueSlash = NULL;    // Value with appended slash
+        psStringAppend(&valueSlash, "%s/", value);
+
+        ssize_t envvarLength = envStop ? envStop - envStart : strlen(envStart); // Length, w/o '}'
+        psString envvar = psStringNCopy(envStart, envvarLength + 1);  // Environment variable, with $, {, }
+
+        psTrace("psModules.config", 7, "Replacing %s with %s in directory %s\n", envvar, valueSlash, dir);
+        psStringSubstitute(&dir, valueSlash, envvar);
+        psFree(envvar);
+        psFree(valueSlash);
+    }
+
+    return dir;
+}
+
+
+void pmConfigSet(const char *path)
+{
+    PS_ASSERT_STRING_NON_EMPTY(path,);
+
+    assert (configPath == NULL);
+    // XXX why was this being called?  pmConfigSet should only be called once...
+    // pmConfigDone();
+
+    psList *list = psStringSplit(path, ":", false);
+    configPath = psListToArray(list);
+    // Resolve environment variables
+    for (long i = 0; i < configPath->n; i++) {
+        configPath->data[i] = resolveEnvVar(configPath->data[i]);
+        psTrace("psModules.config", 4, "Path %ld: %s\n", i, (char*)configPath->data[i]);
+    }
+    psFree(list);
+}
+
+void pmConfigDone(void)
+{
+    if (configPath) {
+        psFree(configPath);
+    }
+    configPath = NULL;
+
+    return;
+}
+
+bool pmConfigFileRead(psMetadata **config, const char *name, const char *description)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_STRING_NON_EMPTY(name, false);
+    PS_ASSERT_STRING_NON_EMPTY(description, false);
+
+    char *realName = NULL;
+    unsigned int numBadLines = 0;
+    struct stat filestat;
+
+    psTrace("psModules.config", 3, "Loading %s configuration from file %s\n",
+            description, name);
+
+    uid_t uid = getuid();
+    gid_t gid = getgid();
+
+    // we try: name, path[0]/name, path[1]/name, ...
+    // find the first existing entry in the path (starting with the bare name)
+    realName = psStringCopy (name);
+    psTrace ("psModules.config", 8, "trying %s\n", realName);
+
+    int status = stat (realName, &filestat);
+    if (status == 0) {
+        if ((uid == filestat.st_uid) && (filestat.st_mode & S_IRUSR)) {
+            goto found;
+        }
+        if ((gid == filestat.st_gid) && (filestat.st_mode & S_IRGRP)) {
+            goto found;
+        }
+        if (filestat.st_mode & S_IROTH) {
+            goto found;
+        }
+    }
+    psFree (realName);
+
+    if (configPath == NULL) {
+        psError(PS_ERR_IO, true, "Cannot find %s configuration file (%s) in path\n", description, name);
+        return false;
+    }
+
+    for (int i = 0; i < configPath->n; i++) {
+        realName = psStringCopy (configPath->data[i]);
+        psStringAppend (&realName, "/%s", name);
+        psTrace ("psModules.config", 8, "trying %s\n", realName);
+
+        status = stat (realName, &filestat);
+        if (status == 0) {
+            if ((uid == filestat.st_uid) && (filestat.st_mode & S_IRUSR)) {
+                goto found;
+            }
+            if ((gid == filestat.st_gid) && (filestat.st_mode & S_IRGRP)) {
+                goto found;
+            }
+            if (filestat.st_mode & S_IROTH) {
+                goto found;
+            }
+        }
+        psFree (realName);
+    }
+
+    psError(PS_ERR_IO, true, "Cannot find %s configuration file %s in path\n", description, name);
+    return false;
+
+found:
+    *config = psMetadataConfigRead(NULL, &numBadLines, realName, true);
+    if (numBadLines > 0) {
+        psError(PS_ERR_IO, false, "%d bad lines in %s configuration file (%s)",
+                numBadLines, description, realName);
+        psFree (realName);
+
+        return false;
+    }
+    if (!*config) {
+        psError(PS_ERR_IO, true, "Unable to read %s configuration from %s",
+                description, realName);
+        psFree (realName);
+        return false;
+    }
+
+    psFree (realName);
+    return true;
+}
+
+// Read a file into a metadataItem, if required
+static bool metadataItemReadFile(psMetadataItem *item, // Item into which to read file
+                                 const char *description // Description, for error messages
+    )
+{
+    assert(item);
+    assert(description);
+
+    if (item->type == PS_DATA_METADATA) {
+        return true;                    // We've already read it
+    }
+    if (item->type != PS_DATA_STRING) {
+        psTrace("config", 2, "Element %s in %s metadata is not of type STR.\n",
+                item->name, description);
+        return false;
+    }
+
+    psTrace("config", 2, "Reading %s %s: %s\n", description, item->name, item->data.str);
+    psMetadata *new = NULL;         // New metadata
+    if (!pmConfigFileRead(&new, item->data.str, item->name)) {
+        psError(PM_ERR_CONFIG, false, "Trouble reading reading %s %s.\n",
+                description, item->name);
+        psFree(new);
+        return false;
+    }
+
+    // Muck around under the hood to replace the filename with the metadata; don't try this at home, kids
+    item->type = PS_DATA_METADATA;
+    psFree(item->data.str);
+    item->data.md = new;
+
+    return true;
+}
+
+// Read metadata config files in a metadata
+// The metadata contains file names, which will be replaced with the metadata that are in the files.
+static bool metadataReadFiles(psMetadata *source, // Source metadata
+                              const char *description // Description, for error messages
+                             )
+{
+    assert(source);
+    psMetadataIterator *iter = psMetadataIteratorAlloc(source, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *item;               // Item from iteration
+    while ((item = psMetadataGetAndIncrement(iter))) {
+        if (!metadataItemReadFile(item, description)) {
+            psError(PM_ERR_CONFIG, false, "Unable to read %s %s.", description, item->name);
+            psFree(iter);
+            return false;
+        }
+    }
+    psFree(iter);
+
+    return true;
+}
+
+// Read the formats for a camera
+static bool cameraReadFormats(psMetadata *camera, // Camera for which to read the formats
+                              const char *name // Name of the camera, for error messages
+                             )
+{
+    assert(camera);
+    assert(name);
+
+    bool mdok;                          // Status of MD lookup
+    psMetadata *formats = psMetadataLookupMetadata(&mdok, camera, "FORMATS"); // Formats
+    if (!mdok || !formats) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find FORMATS in camera configuration %s.\n", name);
+        return false;
+    }
+    if (!metadataReadFiles(formats, "camera format")) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read formats within camera configuration %s.\n", name);
+        return false;
+    }
+
+    return true;
+}
+
+// Read the calibrations for a camera
+static bool cameraReadCalibrations(psMetadata *camera, // Camera for which to read the formats
+                                   const char *cameraName // Name of the camera, for error messages
+    )
+{
+    assert(camera);
+    assert(cameraName);
+
+    psMetadataItem *darkNorm = psMetadataLookup(camera, "DARK.NORM"); // The dark normalisation calibration
+    if (darkNorm) {
+        if (darkNorm->type == PS_DATA_STRING) {
+            const char *darkNormName = darkNorm->data.str; // The file name
+            psTrace("config", 2, "Reading %s dark normalisation: %s\n", cameraName, darkNormName);
+            psMetadata *new = NULL;         // New metadata
+            if (!pmConfigFileRead(&new, darkNormName, "Dark normalisation")) {
+                psError(PM_ERR_CONFIG, false, "Trouble reading reading %s dark normalisation %s --- "
+                        "ignored.\n", cameraName, darkNormName);
+                psFree(new);
+                return false;
+            }
+
+            // Muck around under the hood to replace the filename with the metadata;
+            // don't try this at home, kids
+            darkNorm->type = PS_DATA_METADATA;
+            psFree(darkNorm->data.str);
+            darkNorm->data.md = new;
+        } else if (darkNorm->type != PS_DATA_METADATA) {
+            psWarning("DARK.NORM in camera %s is not of type STR or METADATA (%x)",
+                      cameraName, darkNorm->type);
+        }
+    } else {
+        // Add a dummy entry
+        psPolynomial1D *poly = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1); // Dummy polynomial
+        poly->coeff[0] = 0.0;
+        poly->coeff[1] = 1.0;
+        psMetadata *polyMD = psMetadataAlloc(); // Container for the polynomial
+        (void)psPolynomial1DtoMetadata(polyMD, poly, "_DEFAULT"); // Metadata to insert
+        psFree(poly);
+        psMetadataAddMetadata(camera, PS_LIST_TAIL, "DARK.NORM", 0, "Dark normalisation polynomial",
+                              polyMD);
+        psMetadataAddStr(camera, PS_LIST_TAIL, "DARK.NORM.KEY", 0, "Key for dark normalisation", "_DEFAULT");
+        psFree(polyMD);
+    }
+
+    return true;
+}
+
+pmConfig *pmConfigRead(int *argc, char **argv, const char *defaultRecipe)
+{
+    PS_ASSERT_PTR_NON_NULL(argc, NULL);
+    PS_ASSERT_INT_POSITIVE(*argc, NULL);
+    PS_ASSERT_PTR_NON_NULL(argv, NULL);
+
+    pmConfig *config = pmConfigAlloc(); // The configuration, containing site, camera and recipes
+    config->defaultRecipe = defaultRecipe;
+
+    // The following section of code attempts to determine which file to use as the
+    // top-level the configuration file.  At the end of this code block, the configFile
+    // variable will contain the name of the configuration file.
+
+    char *configFile = NULL;
+    //
+    // First, try command line
+    //
+    psS32 argNum = psArgumentGet(*argc, argv, "-ipprc");
+    if (argNum != 0) {
+        // remove the "-ipprc" argument from argv, check and remove filename
+        psArgumentRemove(argNum, argc, argv);
+        if (argNum >= *argc) {
+            psWarning("-ipprc command-line switch provided without the required filename --- ignored.\n");
+        } else {
+            configFile = psStringCopy(argv[argNum]);
+            psArgumentRemove(argNum, argc, argv);
+        }
+    }
+    //
+    // Next, try environment variable
+    //
+    if (!configFile) {
+        configFile = getenv(IPPRC_ENV);
+        if (configFile) {
+            configFile = psStringCopy (configFile);
+        }
+    }
+
+    //
+    // Last chance is ~/.ipprc
+    //
+    if (!configFile) {
+        char *home = getenv("HOME");
+        configFile = psStringCopy(home);
+        psStringAppend(&configFile, "/%s", IPPRC_FILE);
+    }
+
+    // We have the configuration filename; now we read and parse the config
+    // file and store in psMetadata struct user.
+    // XXX move this section to pmConfigReadUser.c ?
+
+    if (!pmConfigFileRead(&config->user, configFile, "user")) {
+        psFree(config);
+        psFree(configFile);
+        return NULL;
+    }
+    psFree(configFile);
+
+    // XXX why was this being called here?  Is someone calling pmConfigRead multiple times?
+    // pmConfigDone();
+    assert (configPath == NULL);
+
+    // define the config-file search path (configPath).
+    psString path = psMetadataLookupStr(NULL, config->user, "PATH");
+    pmConfigSet (path);
+
+    // read the SITE file
+    psString siteFile = psMetadataLookupStr(NULL, config->user, "SITE");
+    if (!pmConfigFileRead(&config->site, siteFile, "site")) {
+        psFree(config);
+        return NULL;
+    }
+
+    // load the SYSTEM file
+    psString systemFile = psMetadataLookupStr(NULL, config->user, "SYSTEM");
+    if (!pmConfigFileRead(&config->system, systemFile, "system")) {
+        psFree(config);
+        return NULL;
+    }
+
+    // interpolate USER, SITE and SYSTEM into the config->complete metadata
+    config->complete = psMetadataCopy (NULL,             config->user);
+    config->complete = psMetadataCopy (config->complete, config->site);
+    config->complete = psMetadataCopy (config->complete, config->system);
+
+    // Set LOG and TRACE options based on the user configuration.  These must be set AFTER
+    // the SITE and SYSTEM config files are read so path:// entries here can be resolved.
+    {
+        bool mdok = true;   // Status of MD lookup result
+
+        // Set logging level
+        int logLevel = psMetadataLookupS32(&mdok, config->user, "LOGLEVEL");
+        if (mdok && logLevel >= 0)
+        {
+            psTrace("psModules.config", 7, "Setting log level to %d\n", logLevel);
+            psLogSetLevel(logLevel);
+        }
+
+
+        // Set logging format
+        psString logFormat = psMetadataLookupStr(&mdok, config->user, "LOGFORMAT");
+        if (mdok && logFormat)
+        {
+            psTrace("psModules.config", 7, "Setting log format to %s\n", logFormat);
+            psLogSetFormat(logFormat);
+        }
+
+        // Set logging destination first from command line, second from user configuration
+        psString logDest = NULL;        // Logging destination
+        argNum = psArgumentGet(*argc, argv, "-log");
+        if (argNum > 0) {
+            psArgumentRemove(argNum, argc, argv);
+            if (argNum >= *argc) {
+                psWarning("-log command-line switch provided without the required log destination "
+                          "--- ignored.");
+            } else {
+                logDest = psStringCopy(argv[argNum]);
+                psArgumentRemove(argNum, argc, argv);
+            }
+        }
+        if (!logDest) {
+            logDest = psMemIncrRefCounter(psMetadataLookupStr(&mdok, config->user, "LOGDEST"));
+        }
+        if (logDest) {
+            psString resolved = pmConfigConvertFilename(logDest, config, true, false); // Resolved filename
+            if (!resolved || strlen(resolved) == 0) {
+                psWarning("Unable to resolve log destination: %s --- ignored", logDest);
+            } else {
+                config->logFD = psMessageDestination(resolved);
+            }
+            psFree(resolved);
+            psFree(logDest);
+        }
+        if (!psLogSetDestination(config->logFD)) {
+            psError(PS_ERR_IO, false, "Unable to set log destination to file number %d --- ignored",
+                    config->logFD);
+            psFree(config);
+            return NULL;
+        }
+
+        // Set trace levels
+        psMetadata *trace = psMetadataLookupMetadata(&mdok, config->user, "TRACE");
+        if (mdok && trace) {
+            psMetadataIterator *traceIter = psMetadataIteratorAlloc(trace, PS_LIST_HEAD, NULL); // Iterator
+            psMetadataItem *traceItem = NULL; // Item from MD iteration
+            while ((traceItem = psMetadataGetAndIncrement(traceIter))) {
+                if (traceItem->type != PS_DATA_S32) {
+                    psWarning("The level for trace component %s is not of type S32 (%x)\n",
+                             traceItem->name, traceItem->type);
+                    continue;
+                }
+                psTrace("psModules.config", 7, "Setting trace level for %s to %d\n",
+                        traceItem->name, traceItem->data.S32);
+                (void)psTraceSetLevel(traceItem->name, traceItem->data.S32);
+            }
+            psFree(traceIter);
+        }
+
+        // Set trace formats
+        psString traceFormat = psMetadataLookupStr(&mdok, config->user, "TRACEFORMAT");
+        if (mdok && traceFormat) {
+            psTrace("psModules.config", 7, "Setting trace format to %s\n", traceFormat);
+            (void)psTraceSetFormat(traceFormat);
+        }
+
+        // Set trace destinations
+        psString traceDest = NULL;      // Trace destination
+        argNum = psArgumentGet(*argc, argv, "-tracedest");
+        if (argNum > 0) {
+            psArgumentRemove(argNum, argc, argv);
+            if (argNum >= *argc) {
+                psWarning("-tracedest command-line switch provided without the required trace destination "
+                          "--- ignored.\n");
+            } else {
+                traceDest = psStringCopy(argv[argNum]);
+                psArgumentRemove(argNum, argc, argv);
+            }
+        }
+        if (!traceDest) {
+            traceDest = psMemIncrRefCounter(psMetadataLookupStr(&mdok, config->user, "TRACEDEST"));
+        }
+        if (traceDest) {
+            psString resolved = pmConfigConvertFilename(traceDest, config, true, false); // Resolved filename
+            if (!resolved || strlen(resolved) == 0) {
+                psWarning("Unable to resolve trace destination: %s --- ignored", traceDest);
+            } else {
+                config->traceFD = psMessageDestination(resolved);
+            }
+            psFree(resolved);
+            psFree(traceDest);
+        }
+        if (!psTraceSetDestination(config->traceFD)) {
+            psError(PS_ERR_IO, false, "Unable to set trace destination to file number %d --- ignored",
+                    config->traceFD);
+            psFree(config);
+            return NULL;
+        }
+
+        // Allow command line options to override defaults for logging.
+        // XXX: Is it appropriate to use the ArgVerbosity function for this?
+        //   A: it removes the options from the command line.
+        //   B: will the pmConfigRead function always be called on initialization.
+        //
+        psArgumentVerbosity(argc, argv);
+        // XXX: substitute the string for the default log level for "2".
+    }
+
+    // XXX read TIME from SITE (or system?)
+    {
+        bool mdok = true;
+
+        // Initialise the psLib time handling
+        // XXX is this still needed / desired?
+        psString timeName = psMetadataLookupStr(&mdok, config->complete, "TIME");
+        if (mdok && timeName)
+        {
+            psTrace("psModules.config", 7, "Initialising psTime with file %s\n", timeName);
+            psTimeInit(timeName);
+        }
+    }
+
+    // Next, we do a similar thing for the camera configuration file.  The
+    // file is read and parsed into psMetadata struct "camera".
+    argNum = psArgumentGet(*argc, argv, "-camera");
+    if (argNum > 0) {
+        psArgumentRemove(argNum, argc, argv);
+        if (argNum >= *argc) {
+            psWarning("-camera command-line switch provided without the required camera or filename --- "
+                      "ignored.\n");
+        } else {
+            bool mdok = true;           // Status of MD lookup
+            char *cameraName = argv[argNum]; // symbolic name of the camera
+
+            // look for the CAMERAS list in config->complete
+            psMetadata *cameras = psMetadataLookupMetadata(&mdok, config->complete, "CAMERAS");
+            if (!cameras) {
+                psError(PS_ERR_IO, false, "Unable to find CAMERAS in site configuration.\n");
+                psFree(config);
+                return NULL;
+            }
+
+            // look for the symbolic camera name in the CAMERAS metadata
+            char *cameraFile = psMetadataLookupStr(&mdok, cameras, cameraName); // The filename
+            if (!cameraFile) {
+                psError(PS_ERR_IO, false, "%s is not listed in the site CAMERAS list\n", cameraName);
+                psFree(config);
+                return NULL;
+            }
+
+            // load this camera's configuration informatoin
+            if (!pmConfigFileRead(&config->camera, cameraFile, "camera")) {
+                psError(PM_ERR_CONFIG, false, "Problem reading %s", cameraName);
+                psFree(config);
+                return NULL;
+            }
+            // save the name for future uses
+            config->cameraName = psStringCopy (cameraName);
+
+            psArgumentRemove(argNum, argc, argv);
+
+            // Read in the formats
+            if (!cameraReadFormats(config->camera, cameraFile)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to read formats within camera configuration %s.\n",
+                        cameraFile);
+                psFree(config);
+                return NULL;
+            }
+
+            // Read in any camera-specific calibrations
+            if (!cameraReadCalibrations(config->camera, cameraName)) {
+                psError(PS_ERR_UNKNOWN, false,
+                        "Unable to read calibrations within camera configuration %s.\n",
+                        cameraName);
+                psFree(config);
+                return NULL;
+            }
+
+            psMetadataAddMetadata(cameras, PS_LIST_HEAD, cameraName, PS_META_REPLACE,
+                                  "Camera specified on command line", config->camera);
+
+            if (!pmConfigCameraSkycellVersion(config->complete, cameraName)) {
+                psError(PS_ERR_UNKNOWN, false,
+                        "Unable to generate skycell versions of specified camera %s.\n",
+                        cameraName);
+                psFree(config);
+                return NULL;
+            }
+
+            if (!pmConfigCameraMosaickedVersions(config->complete, cameraName)) {
+                psError(PS_ERR_UNKNOWN, false,
+                        "Unable to generate mosaicked versions of specified camera %s.\n",
+                        cameraName);
+                psFree(config);
+                return NULL;
+            }
+        }
+    }
+
+    // Read the camera configurations, if not already defined, and not turned off
+    if (!config->camera && readCameraConfig) {
+        bool mdok;                      // Status of MD lookup
+        psMetadata *cameras = psMetadataLookupMetadata(&mdok, config->complete, "CAMERAS"); // List of cameras
+        if (!mdok || !cameras) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find CAMERAS in the system configuration.\n");
+            return false;
+        }
+
+        if (!metadataReadFiles(cameras, "camera configuration")) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to read cameras within system configuration.\n");
+            psFree(config);
+            return NULL;
+        }
+
+        // Now fill in the formats and calibrations
+        psMetadataIterator *iter = psMetadataIteratorAlloc(cameras, PS_LIST_HEAD, NULL); // Iterator
+        psMetadataItem *item;           // Item from iteration
+        while ((item = psMetadataGetAndIncrement(iter))) {
+            assert(item->type == PS_DATA_METADATA);
+            if (!cameraReadFormats(item->data.md, item->name)) {
+                psWarning("Unable to read formats for camera %s: removed.\n", item->name);
+                psErrorStackPrint(stderr, "errors from read failure\n");
+                psErrorClear();
+                psMetadataRemoveKey(cameras, item->name);
+                continue;
+            }
+            if (!cameraReadCalibrations(item->data.md, item->name)) {
+                psWarning("Unable to read calibrations for camera %s: removed.\n", item->name);
+                psErrorStackPrint(stderr, "errors from read failure\n");
+                psErrorClear();
+                psMetadataRemoveKey(cameras, item->name);
+                continue;
+            }
+        }
+        psFree(iter);
+
+        if (!pmConfigCameraSkycellVersionsAll(config->complete)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to generate skycell versions of cameras.\n");
+            psFree(config);
+            return NULL;
+        }
+        if (!pmConfigCameraMosaickedVersionsAll(config->complete)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to generate mosaicked versions of cameras.\n");
+            psFree(config);
+            return NULL;
+        }
+    }
+
+    // Load the recipes from the camera file, if appropriate
+    if(!pmConfigReadRecipes(config, PM_RECIPE_SOURCE_SYSTEM | PM_RECIPE_SOURCE_CAMERA)) {
+        psError(PS_ERR_IO, false, "Failed to read recipes from camera file");
+        psFree(config);
+        return NULL;
+    }
+
+    // load command-line options of the form -recipe NAME RECIPE
+    pmConfigLoadRecipeArguments(argc, argv, config);
+
+    // read in command-line options to specific recipe values
+    pmConfigLoadRecipeOptions(argc, argv, config, "-D");
+    pmConfigLoadRecipeOptions(argc, argv, config, "-Di");
+    pmConfigLoadRecipeOptions(argc, argv, config, "-Df");
+    pmConfigLoadRecipeOptions(argc, argv, config, "-Db");
+
+    // Look for command-line options for files to replace
+    argNum = psArgumentGet(*argc, argv, "-F");
+    if (argNum > 0) {
+        psArgumentRemove(argNum, argc, argv);
+        if (argNum + 1 >= *argc) {
+            psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                    "Filerule switch (-F) provided without old and new filerule.");
+            psFree(config);
+            return NULL;
+        }
+
+        const char *old = argv[argNum]; // The old file, to be replaced
+        psArgumentRemove(argNum, argc, argv);
+        const char *new = argv[argNum]; // The new file, the replacement
+        psArgumentRemove(argNum, argc, argv);
+
+        psMetadata *cameras = psMetadataLookupMetadata(NULL, config->complete, "CAMERAS"); // List of cameras
+        if (!cameras) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find CAMERAS in the site configuration.\n");
+            return false;
+        }
+
+        psMetadataIterator *camerasIter = psMetadataIteratorAlloc(cameras, PS_LIST_HEAD, NULL); // Iterator
+        psMetadataItem *cameraItem;     // Item from iteration
+        while ((cameraItem = psMetadataGetAndIncrement(camerasIter))) {
+            // Silently ignore problems --- they will be caught later, because if the user wants the nominated
+            // file and it's not available for that camera, then they will know.
+
+            if (cameraItem->type != PS_DATA_METADATA) {
+                psTrace("psModules.config", 2,
+                        "Entry %s in CAMERAS is not of type METADATA --- ignored.", cameraItem->name);
+                continue;
+            }
+            psMetadata *camera = cameraItem->data.md; // Camera configuration
+
+            psMetadata *newRule = pmConfigFileRule(config, camera, new); // The rule of interest
+            if (!newRule) {
+                psTrace("psModules.config", 2,
+                        "Unable to find filerule %s in camera %s --- ignored.", new, cameraItem->name);
+                continue;
+            }
+
+            // By calling pmConfigFileRule, we've assured that the FILERULES is now a metadata
+            psMetadata *filerules = psMetadataLookupMetadata(NULL, camera, "FILERULES"); // File rules
+            if (!filerules) {
+                psTrace("psModules.config", 2,
+                        "Can't find FILERULES of type METADATA in camera %s --- ignored.", cameraItem->name);
+                continue;
+            }
+
+            psMetadataAddMetadata(filerules, PS_LIST_TAIL, old, PS_META_REPLACE,
+                                  "Original replaced by -F option", newRule);
+        }
+        psFree(camerasIter);
+    }
+
+    // check for values that override DB* keywords
+    argNum = psArgumentGet(*argc, argv, "-dbserver");
+    if (argNum > 0) {
+        psArgumentRemove(argNum, argc, argv);
+        if (argNum >= *argc) {
+            psWarning("-dbserver command-line switch provided without the required server name --- ");
+        } else {
+            char *dbserver = argv[argNum]; // The camera configuration file to read
+            if (!psMetadataAddStr(config->complete, PS_LIST_TAIL, "DBSERVER", PS_META_REPLACE,
+                                  NULL, dbserver)) {
+                psWarning("Failed to overwrite .ipprc DBSERVER value");
+            }
+
+            psArgumentRemove(argNum, argc, argv);
+        }
+    }
+
+    argNum = psArgumentGet(*argc, argv, "-dbname");
+    if (argNum > 0) {
+        psArgumentRemove(argNum, argc, argv);
+        if (argNum >= *argc) {
+            psWarning("-dbname command-line switch provided without the required database name");
+        } else {
+            char *dbname = argv[argNum]; // The camera configuration file to read
+            if (!psMetadataAddStr(config->complete, PS_LIST_TAIL, "DBNAME", PS_META_REPLACE, NULL, dbname)) {
+                psWarning("Failed to overwrite .ipprc DBNAME value");
+            }
+
+            psArgumentRemove(argNum, argc, argv);
+        }
+    }
+
+    argNum = psArgumentGet(*argc, argv, "-dbuser");
+    if (argNum > 0) {
+        psArgumentRemove(argNum, argc, argv);
+        if (argNum >= *argc) {
+            psWarning("-dbuser command-line switch provided without the required database name");
+        } else {
+            char *dbuser = argv[argNum]; // The camera configuration file to read
+            if (!psMetadataAddStr(config->complete, PS_LIST_TAIL, "DBUSER", PS_META_REPLACE, NULL, dbuser)) {
+                psWarning("Failed to overwrite .ipprc DBUSER value");
+            }
+
+            psArgumentRemove(argNum, argc, argv);
+        }
+    }
+
+    argNum = psArgumentGet(*argc, argv, "-dbpassword");
+    if (argNum > 0) {
+        psArgumentRemove(argNum, argc, argv);
+        if (argNum >= *argc) {
+            psWarning("-dbpassword command-line switch provided without the required password");
+        } else {
+            char *dbpassword = argv[argNum]; // The camera configuration file to read
+            if (!psMetadataAddStr(config->complete, PS_LIST_TAIL, "DBPASSWORD", PS_META_REPLACE,
+                                  NULL, dbpassword)) {
+                psWarning("Failed to overwrite .ipprc DBPASSWORD value");
+            }
+
+            psArgumentRemove(argNum, argc, argv);
+        }
+    }
+
+    argNum = psArgumentGet(*argc, argv, "-dbport");
+    if (argNum > 0) {
+        psArgumentRemove(argNum, argc, argv);
+        if (argNum >= *argc) {
+            psWarning("-dbpport command-line switch provided without the required port number");
+        } else {
+            char *dbport = argv[argNum]; // The camera configuration file to read
+            if (!psMetadataAddS32(config->complete, PS_LIST_TAIL, "DBPORT", PS_META_REPLACE, NULL,
+                                  (psS32)atoi(dbport))) {
+                psWarning("Failed to overwrite .ipprc DBPORT value");
+            }
+
+            psArgumentRemove(argNum, argc, argv);
+        }
+    }
+
+    psErrorClear();   // we may have failed to find some items in the metadata
+
+    return config;
+}
+
+
+// does this header match the specified camera format?  answer is supplied to 'valid' the
+// return value defines the error condition. error only on config errors
+bool pmConfigValidateCameraFormat(bool *valid, const psMetadata *cameraFormat, const psMetadata *header)
+{
+    PS_ASSERT_PTR_NON_NULL(cameraFormat, false);
+    PS_ASSERT_PTR_NON_NULL(header, false);
+
+    // Read the rule for that camera format
+    bool mdStatus = true;
+
+    psMetadata *rule = psMetadataLookupMetadata(&mdStatus, cameraFormat, "RULE");
+    if (! mdStatus || ! rule) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read rule for camera.");
+        *valid = false;
+        return false;
+    }
+
+    // grab the metadata items in sequence by key so we get the MULTI entry
+    psList *keyList = psMetadataKeys (rule);
+    psArray *keys = psListToArray (keyList);
+    if (! keys) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read rule for camera.");
+        *valid = false;
+        return false;
+    }
+
+    *valid = true;
+    for (int i = 0; *valid && (i < keys->n); i++) {
+
+        // get the ruleItem for this key
+        psMetadataItem *ruleItem = psMetadataLookup(rule, keys->data[i]);
+
+        // Check for the existence of the rule in the header
+        psMetadataItem *headerItem = psMetadataLookup(header, ruleItem->name);
+        if (! headerItem) {
+            // rule item not found in header
+            psTrace("psModules.config.format", 5, "Can't find %s", ruleItem->name);
+            *valid = false;
+            continue;
+        }
+
+        // if the RULE type is a primitive type (int, float, etc) or string compare directly
+        if (PS_DATA_IS_PRIMITIVE (ruleItem->type) || (ruleItem->type == PS_DATA_STRING)) {
+            // Check to see if the rule works
+            if (!psMetadataItemCompare(headerItem, ruleItem)) {
+                psTrace("psModules.config.format", 5, "%s doesn't match.", ruleItem->name);
+                *valid = false;
+            }
+            continue;
+        }
+
+        // for MULTI, try each one & succeed if any match (valid = true is default state)
+        if (ruleItem->type == PS_DATA_METADATA_MULTI) {
+            bool found = false;
+            for (int j = 0; j < ruleItem->data.list->n; j++) {
+                psMetadataItem *entry = psListGet (ruleItem->data.list, j);
+                assert (entry);
+                if (psMetadataItemCompare(headerItem, entry)) {
+                    found = true;
+                    psTrace("psModules.config.format", 5, "%s in multi list matches.", ruleItem->name);
+                    break;
+                }
+            }
+            if (!found) {
+                *valid = false;
+                psTrace("psModules.config.format", 5, "%s doesn't match.", ruleItem->name);
+            }
+            continue;
+        }
+
+        psError(PS_ERR_UNKNOWN, false, "Invalid type for RULE %s.", ruleItem->name);
+        *valid = false;
+        psFree (keyList);
+        psFree (keys);
+        return false;
+    }
+
+    psFree (keyList);
+    psFree (keys);
+    return true;
+}
+
+// Given a camera and a header, see if any of the camera formats match the header
+// if so, return the winning format and the name of the winning format (both allocated here)
+static bool formatFromHeader(bool *status,
+                             psMetadata **format, // Format to return
+                             psString *name, // Name to return
+                             psMetadata *camera, // Camera configuration
+                             const psMetadata *header, // FITS header
+                             const char *cameraName // Name of camera
+                            )
+{
+    assert(format);
+    assert(camera);
+    assert(header);
+    assert(cameraName);
+    assert(*cameraName);
+
+    *status = true;                     // error status
+    bool result = false;                // Did we find the first match?
+
+    // Read the list of formats
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *formats = psMetadataLookupMetadata(&mdok, camera, "FORMATS"); // List of formats
+    if (!mdok || !formats) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to find list of FORMATS in camera %s", cameraName);
+        *status = false;
+        return false;
+    }
+
+    if (!metadataReadFiles(formats, "camera format")) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read cameras formats within camera configuration.\n");
+        *status = false;
+        return false;
+    }
+
+    // Iterate over the formats
+    psMetadataIterator *formatsIter = psMetadataIteratorAlloc(formats, PS_LIST_HEAD, NULL);
+    psMetadataItem *formatsItem = NULL; // Item from formats
+    while ((formatsItem = psMetadataGetAndIncrement(formatsIter))) {
+        assert(formatsItem->type == PS_DATA_METADATA); // Since we have just read it in or deleted it
+        psMetadata *testFormat = formatsItem->data.md; // Format to test against
+
+        psTrace("psModules.config.format", 5, "trying format %s", formatsItem->name);
+
+        bool valid = false;
+        if (!pmConfigValidateCameraFormat(&valid, testFormat, header)) {
+            psError (PS_ERR_UNKNOWN, false, "Error in config scripts for camera %s, format %s\n",
+                     cameraName, formatsItem->name);
+            *status = false;
+            return false;
+        }
+        if (valid) {
+            if (!*format) {
+                psLogMsg("psModules.config.format", PS_LOG_INFO, "Camera %s, format %s matches header.\n",
+                         cameraName, formatsItem->name);
+                *format = psMemIncrRefCounter(testFormat);
+                *name = psStringCopy(formatsItem->name);
+                result = true;
+            } else {
+                psWarning("Camera %s, format %s also matches header --- ignored.\n",
+                         cameraName, formatsItem->name);
+            }
+        }
+    }
+    psFree(formatsIter);
+    *status = true;
+    return result;
+}
+
+// determine the camera format based on the keywords in the header.  If we have already chosen
+// a camera, then we select the formats only from that camera, and the meta-cameras for that
+// camera.  If we are discovering the camera (config->camera == NULL), then we also load the
+// recipe files for the camera.
+psMetadata *pmConfigCameraFormatFromHeader(psMetadata **camera, psString *formatName, pmConfig *config,
+                                           const psMetadata *header, bool readRecipes)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(header, NULL);
+
+    bool status = false;                // error status
+    psMetadata *format = NULL;          // The winning format
+    psString name = NULL;               // Name of the winning format
+
+    // If we don't know what sort of camera we have, we try all that we know
+    if (! config->camera) {
+        psAssert (!config->cameraName, "programming error: cameraName should be NULL if camera is undefined");
+        psAssert (!config->format,     "programming error: format should be NULL if camera is undefined");
+        psAssert (!config->formatName, "programming error: formatName should be NULL if camera is undefined");
+
+        bool mdok;                      // Metadata lookup status
+        psMetadata *cameras = psMetadataLookupMetadata(&mdok, config->complete, "CAMERAS");
+        if (! mdok || !cameras) {
+            psError(PS_ERR_IO, true, "Unable to find CAMERAS in the configuration.");
+            return NULL;
+        }
+
+        if (!metadataReadFiles(cameras, "camera configuration")) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to read cameras within site configuration.\n");
+            return NULL;
+        }
+
+        // Iterate over the cameras
+        psMetadataIterator *camerasIter = psMetadataIteratorAlloc(cameras, PS_LIST_HEAD, NULL);
+        psMetadataItem *camerasItem = NULL; // Item from the metadata
+        while ((camerasItem = psMetadataGetAndIncrement(camerasIter))) {
+            // Open the camera information
+            psTrace("psModules.config.format", 3, "Inspecting camera %s (%s)\n", camerasItem->name,
+                    camerasItem->comment);
+            assert(camerasItem->type == PS_DATA_METADATA); // It should be because we've read it in or deleted
+            psMetadata *testCamera = camerasItem->data.md; // Camera to test against what we've got:
+            if (formatFromHeader(&status, &format, &name, testCamera, header, camerasItem->name)) {
+                config->camera = psMemIncrRefCounter(testCamera);
+                config->cameraName = psStringCopy(camerasItem->name);
+                config->formatName = name;
+                config->format = format;
+                if (camera) {
+                    *camera = psMemIncrRefCounter(testCamera);  // view on value saved on config
+                }
+                if (formatName) {
+                    *formatName = psMemIncrRefCounter(name);    // view on value saved on config
+                }
+            } else {
+                if (!status) {
+                    psError(PS_ERR_IO, false, "Error reading camera config data for %s", camerasItem->name);
+                    return NULL;
+                }
+            }
+        }
+        psFree(camerasIter);
+
+        // Done looking at all cameras
+        if (!config->camera) {
+            psError(PS_ERR_IO, false, "Unable to find a camera that matches input FITS header!");
+            return NULL;
+        }
+
+        // Now we have the camera, we can read the recipes
+        if (readRecipes && !pmConfigReadRecipes(config, PM_RECIPE_SOURCE_CAMERA | PM_RECIPE_SOURCE_CL)) {
+            psError(PS_ERR_IO, false, "Error reading recipes from camera config for %s", config->cameraName);
+            return NULL;
+        }
+        return psMemIncrRefCounter(format); // a second copy, since the first copy sits on config->format
+    }
+
+    // we have a config with a specified camera.  However, the supplied header may not
+    // correspond to this mosaic level for the camera.  We need to try the CHIP and FPA mosaic
+    // versions as well as the base version
+
+    psAssert (config->cameraName, "programming error: cameraName should not be NULL if camera is defined");
+    psAssert (config->format,     "programming error: format should not be NULL if camera is defined");
+    psAssert (config->formatName, "programming error: formatName should not be NULL if camera is defined");
+
+    // the camera name is of the form: BASE, BASE_CHIP, or BASE_FPA.  pull out BASE:
+    char *baseName = cameraBaseName (config->cameraName);
+
+    bool found = false;
+
+    psMetadata *testCamera = NULL;
+    char *testName = NULL;
+
+    psMetadata *cameras = psMetadataLookupMetadata (NULL, config->complete, "CAMERAS");
+    psAssert (cameras, "missing CAMERAS in complete metadata");
+
+    // try the FPA metaCamera
+    if (!found) {
+        testName = NULL;
+        psStringAppend (&testName, "_%s-FPA", baseName);
+
+        testCamera = psMetadataLookupMetadata (NULL, cameras, testName);
+        psAssert (testCamera, "missing %s in CAMERAS in complete metadata", testName);
+
+        bool status;
+        found = formatFromHeader(&status, &format, &name, testCamera, header, testName);
+        if (!found) psFree (testName);
+    }
+
+    // try the CHIP metaCamera
+    if (!found) {
+        testName = NULL;
+        psStringAppend (&testName, "_%s-CHIP", baseName);
+
+        testCamera = psMetadataLookupMetadata (NULL, cameras, testName);
+        psAssert (testCamera, "missing %s in CAMERAS in complete metadata", testName);
+
+        bool status;
+        found = formatFromHeader(&status, &format, &name, testCamera, header, testName);
+        if (!found) psFree (testName);
+    }
+
+    // try the SKYCELL metaCamera
+    if (!found) {
+        testName = NULL;
+        psStringAppend (&testName, "_%s-SKYCELL", baseName);
+
+        testCamera = psMetadataLookupMetadata (NULL, cameras, testName);
+        psAssert (testCamera, "missing %s in CAMERAS in complete metadata", testName);
+
+        bool status;
+        found = formatFromHeader(&status, &format, &name, testCamera, header, testName);
+        if (!found) psFree (testName);
+    }
+
+    // try the base name
+    if (!found) {
+        testName = psMemIncrRefCounter (baseName);
+
+        testCamera = psMetadataLookupMetadata (NULL, cameras, testName);
+        psAssert (testCamera, "missing %s in CAMERAS in complete metadata", testName);
+
+        bool status;
+        found = formatFromHeader(&status, &format, &name, testCamera, header, testName);
+    }
+
+    if (!found) {
+        psError(PS_ERR_IO, true, "Unable to find a format with the specified camera (%s) that matches the "
+                "given header.\n", baseName);
+        psFree (baseName);
+        return NULL;
+    }
+
+    psFree (name); // winning format name (for metaCamera) returned by formatFromHeader
+
+    psFree (baseName);
+    if (formatName) {
+        *formatName = testName;
+    } else {
+        psFree (testName);
+    }
+    if (camera) {
+        *camera = psMemIncrRefCounter(testCamera);
+    }
+    return format; // we do NOT need to incr ref counter: this is the only copy
+}
+
+// Return the requested camera configuration
+psMetadata *pmConfigCameraByName(pmConfig *config, const char *cameraName)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(cameraName, NULL);
+
+    psMetadata *cameras = psMetadataLookupMetadata(NULL, config->complete, "CAMERAS");
+    if (!cameras) {
+        psError(PS_ERR_IO, true, "Unable to find CAMERAS in the configuration.");
+        return NULL;
+    }
+
+    char *cameraPath = psMetadataLookupStr(NULL, cameras, cameraName);
+    if (!cameraPath) {
+        psError(PS_ERR_IO, true, "Unable to find requested CAMERA in the configuration.");
+        return NULL;
+    }
+
+    psMetadata *camera = NULL; // Camera to test against what we've got:
+
+    if (!pmConfigFileRead(&camera, cameraPath, cameraName)) {
+        psWarning("Trouble reading reading camera configuration %s", cameraName);
+        psFree(camera);
+        return NULL;
+    }
+
+    return camera;
+}
+
+psDB *pmConfigDB(pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->complete, NULL);
+
+    #ifndef HAVE_PSDB
+
+    psError(PS_ERR_UNKNOWN, false,
+            "Cannot configure database: psModules was compiled without database support.");
+    return NULL;
+
+    #else
+
+    if (config->database) {
+        return config->database;
+    }
+
+    psBool mdStatus01 = false;
+    psBool mdStatus02 = false;
+    psBool mdStatus03 = false;
+    psBool mdStatus04 = false;
+    psBool mdStatus05 = false;
+
+    // XXX leaky strings
+    psString dbServer = psMetadataLookupStr(&mdStatus01, config->complete, "DBSERVER");
+    psString dbUsername = psMetadataLookupStr(&mdStatus02, config->complete, "DBUSER");
+    psString dbPassword = psMetadataLookupStr(&mdStatus03, config->complete, "DBPASSWORD");
+    psString dbName = psMetadataLookupStr(&mdStatus04, config->complete, "DBNAME");
+    psS32 dbPort = psMetadataLookupS32(&mdStatus05, config->complete, "DBPORT");
+    if (!mdStatus05) {
+        dbPort = 0;
+    }
+    if (!(mdStatus01 && mdStatus02 && mdStatus03 && mdStatus04)) {
+        psWarning("Could not determine database server, name, user, and password from site metadata.\n");
+        return NULL;
+    }
+
+    if (strcasecmp(dbName, "XXX") == 0 || strcasecmp(dbName, "NONE") == 0) {
+        psTrace("psModules.config", 1, "Database initialisation skipped: database is %s.", dbName);
+        return NULL;
+    }
+
+    config->database = psDBInit(dbServer, dbUsername, dbPassword, dbName, dbPort);
+    return config->database;
+    #endif
+}
+
+
+bool pmConfigConformHeader(psMetadata *header, const psMetadata *format)
+{
+    PS_ASSERT_PTR_NON_NULL(header, false);
+    PS_ASSERT_PTR_NON_NULL(format, false);
+
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *rules = psMetadataLookupMetadata(&mdok, format, "RULE"); // How to identify this format
+    if (!mdok || !rules) {
+        psError(PS_ERR_IO, true, "Unable to find RULE in camera format.\n");
+        return false;
+    }
+
+    psMetadataIterator *rulesIter = psMetadataIteratorAlloc(rules, PS_LIST_HEAD, NULL); // Iterator for rules
+    psMetadataItem *rulesItem = NULL;   // Item from iteration
+    while ((rulesItem = psMetadataGetAndIncrement(rulesIter))) {
+        // this will insert each of the MULTI entries, writing over the previous copies
+        if (PS_DATA_IS_PRIMITIVE (rulesItem->type) || (rulesItem->type == PS_DATA_STRING)) {
+            psMetadataItem *newItem = psMetadataItemCopy(rulesItem); // Copy of item
+            psMetadataAddItem(header, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+            psFree(newItem);                // Drop reference
+            continue;
+        }
+
+        # if (0) // XXX not needed
+        // for MULTI entries, supply the first one
+        if (rulesItem->type == PS_DATA_METADATA_MULTI) {
+            psMetadataItem *entry = psListGet (rulesItem->data.list, PS_LIST_HEAD);
+            if (!entry) {
+                psError(PS_ERR_UNKNOWN, true, "No entries for MULTI RULE item.");
+                return false;
+            }
+            psMetadataItem *newItem = psMetadataItemCopy(entry); // Copy of item
+            psMetadataAddItem(header, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+            psFree(newItem);                // Drop reference
+            continue;
+        }
+        # endif
+
+        psError(PS_ERR_UNKNOWN, false, "Invalid type for RULE %s.", rulesItem->name);
+        return false;
+    }
+    psFree(rulesIter);
+
+    return true;
+}
+
+psArray *pmConfigFileSets(int *argc, char **argv, const char *file, const char *list)
+{
+    PS_ASSERT_PTR_NON_NULL(argc, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(*argc, NULL);
+    PS_ASSERT_PTR_NON_NULL(argv, NULL);
+
+    int Narg;                           // Argument number
+
+    // we load all input files onto a psArray, to be parsed later
+    psArray *input = psArrayAllocEmpty(16);
+
+    // load the list of filenames the supplied file
+    // maybe a comma-separated list of words
+    // each word may be a glob: "file*.fits"
+    if (file && strlen(file) > 0 && (Narg = psArgumentGet (*argc, argv, file))) {
+
+        // select the word after 'file' and split by comma
+        psArgumentRemove (Narg, argc, argv);
+        psArray *words = psStringSplitArray (argv[Narg], ",", true);
+        psArgumentRemove (Narg, argc, argv);
+
+        // parse the word as a glob
+        glob_t globList;
+        for (int i = 0; i < words->n; i++) {
+            globList.gl_offs = 0;
+            glob (words->data[i], 0, NULL, &globList);
+
+            // if the glob does not match, save the literal word:
+            // otherwise save all glob matches
+            if (globList.gl_pathc == 0) {
+                psArrayAdd (input, 16, words->data[i]);
+            } else {
+                for (int j = 0; j < globList.gl_pathc; j++) {
+                    char *filename = psStringCopy (globList.gl_pathv[j]);
+                    psArrayAdd (input, 16, filename);
+                    psFree (filename);
+                }
+            }
+        }
+        psFree (words);
+    }
+
+    // load the list from the supplied text file
+    if (list && strlen(list) > 0 && (Narg = psArgumentGet(*argc, argv, list))) {
+        psArgumentRemove (Narg, argc, argv);
+        FILE *f = fopen(argv[Narg], "r");
+        if (!f) {
+            psError(PS_ERR_IO, true, "Unable to open specified list file");
+            psFree(input);
+            return NULL;
+        }
+
+        // XXX Reading the list should be reimplemented using psSlurp
+
+        char line[1024]; // XXX limits the list lines to 1024 chars
+        while (fgets(line, 1024, f) != NULL) {
+            char word[1024];
+            int nItems = sscanf(line, "%s", word);
+            switch (nItems) {
+              case 0:
+                break;
+              case 1: {
+                  psString filename = psStringCopy(word);
+                  psArrayAdd(input, 16, filename);
+                  psFree(filename);
+                  break;
+              }
+              default:
+                // rigid format, no comments allowed?
+                psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to parse file list: spaces detected.");
+                psFree(input);
+                fclose(f);
+                return NULL;
+            }
+        }
+        psArgumentRemove(Narg, argc, argv);
+        fclose(f);
+    }
+
+    return input;
+}
+
+// XXX this is a prime example of the failing of our error-handling system.  this function has
+// three possible outcomes: the argument was found, it was not found, or we raised an error.
+// returning only the bool does not distinguish failure to find the argument from a deeper
+// error.  requiring the calling function to both test the bool AND trap the error stack is
+// fragile: the error stack may not have been cleared, or they may not do both.  in some
+// places, we solve this by returning two types of boolean status values.  a better option
+// might be to return a psErrorCode value (as RHL proposed), which would be 0 on success and
+// any of several options on failure.
+
+bool pmConfigFileSetsMD(psMetadata *metadata, int *argc, char **argv, const char *name,
+                        const char *file, const char *list)
+{
+    PS_ASSERT_PTR_NON_NULL(metadata, false);
+    PS_ASSERT_STRING_NON_EMPTY(name, NULL);
+
+    psErrorClear();   // pmConfigFileSets may or may not call psError, so
+    // if files->n == 0 we'll want to call psError(..., false, ...)
+    psArray *files = pmConfigFileSets(argc, argv, file, list);
+    if (!files) {
+        psAbort("error parsing argument list");
+        psError(PS_ERR_IO, false, "error parsing argument list");
+        psFree (files);
+        return false;
+    }
+
+    // no files found: this is not really an error
+    if (files->n == 0) {
+        psFree (files);
+        return false;
+    }
+
+    psMetadataAddPtr(metadata, PS_LIST_TAIL, name,  PS_DATA_ARRAY, "", files);
+    psFree (files);
+    return true;
+}
+
+// convert the supplied name, create a new output psString
+psString pmConfigConvertFilename(const char *filename, const pmConfig *config, bool create, bool trunc)
+{
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    // strip file:// from front of name
+    if (!strncasecmp(filename, "file:", strlen("file:"))) {
+        psString newName = psStringCopy(filename);
+
+        char *point = newName + strlen("file:");
+        while (*point == '/')
+            point ++;
+        char *tmpName = NULL;
+        psStringAppend (&tmpName, "/%s", point);
+        psFree (newName);
+        newName = tmpName;
+
+        if (!checkPath(newName, create, trunc)) {
+            // let checkPath()'s psError() call float up
+            psFree (newName);
+            return NULL;
+        }
+
+        return newName;
+    }
+
+    // replace path://PATH with matched datapath
+    if (!strncasecmp(filename, "path://", strlen("path://"))) {
+        PS_ASSERT_METADATA_NON_NULL(config->complete, NULL);
+
+        psString newName = psStringCopy(filename);
+
+        // filename should be of the form: path://PATH/rest/of/file
+        // replace PATH with matching name from config->complete:DATAPATH
+        psMetadata *datapath = psMetadataLookupPtr (NULL, config->complete, "DATAPATH");
+        if (datapath == NULL) {
+            psError(PS_ERR_UNKNOWN, true, "DATAPATH is not defined in config.site");
+            psFree (newName);
+            return NULL;
+        }
+
+        char *point = newName + strlen("path://");
+        char *mark = strchr (point, '/');
+        if (mark == NULL) {
+            psError(PS_ERR_UNKNOWN, true, "syntax error in PATH-style name %s", newName);
+            psFree (newName);
+            return false;
+        }
+
+        psString path = psStringNCopy (point, mark - point);
+        char *realpath = psMetadataLookupStr (NULL, datapath, path);
+        if (realpath == NULL) {
+            psError(PS_ERR_UNKNOWN, true,
+                    "path (%s) not defined in config.site:DATAPATH for PATH-style name %s",
+                    path, newName);
+            psFree(newName);
+            psFree(path);
+            return false;
+        }
+        psFree(path);
+
+        char *tmpName = NULL;
+        psStringAppend(&tmpName, "%s/%s", realpath, mark + 1);
+        psFree(newName);
+        newName = tmpName;
+
+        if (!checkPath(newName, create, trunc)) {
+            // let checkPath()'s psError() call float up
+            psFree (newName);
+            return NULL;
+        }
+
+        return newName;
+    }
+
+    // substitute neb://name with matched nebulous name
+    if (!strncasecmp(filename, "neb://", strlen("neb://"))) {
+        #ifdef HAVE_NEBCLIENT
+
+        bool status = false;
+        psString neb_server = NULL;
+
+        // check the env first
+        neb_server = getenv("NEB_SERVER");
+
+        // if env isn't set, check the config system
+        if (!neb_server) {
+            neb_server = psMetadataLookupStr(&status, config->complete, "NEB_SERVER");
+            if (!status) {
+                psError(PM_ERR_CONFIG, true, "failed to lookup config value for NEB_SERVER.");
+                return NULL;
+            }
+        }
+
+        if (!neb_server) {
+            psError(PM_ERR_CONFIG, true, "Could not determine nebulous server URI.");
+            return NULL;
+        }
+
+        nebServer *server = nebServerAlloc(neb_server);
+        if (!server) {
+            psError(PM_ERR_SYS, true, "failed to create a nebServer object: %s", nebErr(server));
+            nebServerFree(server);
+            return NULL;
+        }
+
+        char *nebfile = NULL;
+        if (!(nebfile = nebFind(server, filename))) {
+            // object does not exist
+            if (create) {
+                nebfile = nebCreate(server, filename, NULL, NULL);
+                if (!nebfile) {
+                    psError(PM_ERR_SYS, true, "failed to create a new nebulous key: %s", nebErr(server));
+                    nebServerFree(server);
+                    return NULL;
+                }
+            } else {
+                // if the object does not exist and create isn't set, then we
+                // should puke
+                psError(PM_ERR_SYS, true, "Unable to access file %s", filename);
+                nebServerFree(server);
+                return NULL;
+            }
+        }
+
+        // convert nebfile into a psString
+        psString path = psStringCopy(nebfile);
+        nebFree(nebfile);
+        nebServerFree(server);
+
+        if (trunc) {
+            if(truncate(filename, 0) != 0) {
+                psError(PS_ERR_IO, true, "Failed to truncate file, %s\n", filename);
+                nebServerFree(server);
+                return NULL;
+            }
+        }
+
+        return path;
+
+        #else // ifdef HAVE_NEBCLIENT
+
+        psError(PM_ERR_PROG, true, "psModules was compiled without nebulous support.");
+        return NULL;
+        #endif // ifdef HAVE_NEBCLIENT
+
+    }
+
+    // if we go this far, do nothing
+    return psStringCopy(filename);
+}
+
+psMetadata *pmConfigFileRule(const pmConfig *config, const psMetadata *camera, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_METADATA_NON_NULL(camera, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(name, NULL);
+
+    psMetadataItem *item = psMetadataLookup(camera, "FILERULES"); // Item with the file rule of interest
+    if (!item) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find FILERULES in the camera configuration.");
+        return NULL;
+    }
+
+    if (!metadataItemReadFile(item, "file rules ")) {
+        psError(PM_ERR_CONFIG, false, "Unable to read file rules for camera.");
+        return NULL;
+    }
+
+    assert(item->type == PS_DATA_METADATA);
+    psMetadata *filerules = item->data.md; // File rules from the camera configuration
+
+    // select the name from the FILERULES
+    // check for alias name (type == STR, name is aliased name)
+    bool mdok;                          // Status of MD lookup
+    const char *realname = psMetadataLookupStr(&mdok, filerules, name); // Name of file rule to look up
+    if (!realname || strlen(realname) == 0) {
+        realname = name;
+    }
+
+    return psMetadataLookupMetadata(&mdok, filerules, realname);
+}
+
+psMetadata *pmConfigFitsType (const pmConfig *config, const psMetadata *camera, const char *fitsType)
+{
+    bool mdok;
+
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_METADATA_NON_NULL(camera, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(fitsType, NULL);
+
+    psMetadataItem *item = psMetadataLookup(camera, "FITSTYPES"); // Item with the file rule of interest
+    if (!item) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find FITSTYPES in the camera configuration.");
+        return NULL;
+    }
+
+    if (!metadataItemReadFile(item, "FITS Types")) {
+        psError(PM_ERR_CONFIG, false, "Unable to read fits types for camera.");
+        return NULL;
+    }
+
+    assert(item->type == PS_DATA_METADATA);
+    psMetadata *fitstypes = item->data.md; // FITS Types from the camera configuration
+
+    // select the name from the FITSTYPES
+    psMetadata *scheme = psMetadataLookupMetadata(&mdok, fitstypes, fitsType);
+    if (!scheme) {
+        psWarning("Unable to find specified FITS Type %s in camera configuration.", fitsType);
+        return NULL;
+    }
+
+    return scheme;
+}
+
+static bool checkPath(const char *filename, bool create, bool trunc)
+{
+    PS_ASSERT_PTR_NON_NULL(filename, false);
+
+    if (access(filename, R_OK) == 0) {
+        // file already exists
+        if (trunc) {
+            if(truncate(filename, 0) != 0) {
+                psError(PS_ERR_IO, true, "Failed to truncate file, %s\n", filename);
+                return false;
+            }
+        }
+    } else {
+        // file does not exist
+        if (create) {
+            int fd = open(filename, O_WRONLY|O_CREAT, 0666);
+            if (fd == 0) {
+                psError(PS_ERR_IO, true, "Failed to close file, %s\n", filename);
+                return false;
+            }
+            if (close(fd) != 0) {
+                psError(PS_ERR_IO, true, "Failed to close file, %s\n", filename);
+                return false;
+            }
+        } else {
+            // if the file does not exist and create isn't set, then we
+            // should puke
+            psError(PS_ERR_IO, true, "Unable to access file %s", filename);
+            return false;
+        }
+    }
+
+    return true;
+}
+
+
+#if 0
+bool pmConfigDump(const pmConfig *config, const pmFPA *source, const char *outroot)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_STRING_NON_EMPTY(outroot, false);
+
+    pmFPAview *view = source ? pmFPAviewTop(source) : pmFPAviewAlloc(0);// View to top level
+    if (!view) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to determine top view for FPA.");
+        return false;
+    }
+
+    psMetadata *dumpRules = psMetadataLookupMetadata(NULL, config->system, "CONFIG.RULES"); // Name rules
+    if (!dumpRules) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to find CONFIG.RULES in system configuration");
+        psFree(view);
+        return false;
+    }
+
+    const char *name =
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfig.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfig.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfig.h	(revision 22157)
@@ -0,0 +1,186 @@
+/*  @file pmConfig.h
+ *  @brief Configuration functions
+ *
+ *  @author Paul Price, IfA
+ *  @author Eugene Magnier, IfA
+ *
+ *  @version $Revision: 1.38 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-06-10 21:53:09 $
+ *  Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_CONFIG_H
+#define PM_CONFIG_H
+
+/// @addtogroup Config Configuration System
+/// @{
+
+/// Sources for recipes.
+///
+/// Defines what recipe sources have been read.  This allows us to read recipes from different sources as they
+/// become available.  For example, we may not have access to the camera configuration until we have read a
+/// FITS file.  We allow symbolic links, which means the user can specify on the command-line the name of a
+/// recipe that's defined elsewhere, instead of typing the entire filename.  This structure is private to
+/// psModules --- there is no need for the user to know about it.
+typedef enum {
+    PM_RECIPE_SOURCE_NONE        = 0x00, ///< None yet
+    PM_RECIPE_SOURCE_SYSTEM      = 0x01, ///< System configuration
+    PM_RECIPE_SOURCE_CAMERA      = 0x02, ///< Camera configuration
+    PM_RECIPE_SOURCE_CL          = 0x04, ///< Command-line
+    PM_RECIPE_SOURCE_SYMBOLIC    = 0x14, ///< Symbolic link, specified on command-line
+    PM_RECIPE_SOURCE_ALL         = 0xff  ///< All sources
+} pmRecipeSource;
+
+/// Configuration information
+///
+/// This structure stores the configuration information: user, site, system, camera and recipe configuration, the
+/// command-line arguments, the pmFPAfiles used, and the database handle.
+typedef struct {
+    psMetadata *user;                 	///< User configuration
+    psMetadata *site;                 	///< Site configuration
+    psMetadata *system;               	///< System configuration
+    psMetadata *complete;               ///< Full merged configuration
+    psMetadata *camera;                 ///< Camera specification
+    psString cameraName;                ///< Camera name
+    psMetadata *format;                 ///< Camera format description
+    psString formatName;                ///< Camera format name
+    psMetadata *recipes;                ///< Recipes for processing
+    psMetadata *recipesCamera;          ///< Recipes for processing
+    psMetadata *arguments;              ///< Processed command-line arguments
+    psMetadata *files;                  ///< pmFPAfiles used for analysis
+    psDB *database;                     ///< Database handle
+    const char *defaultRecipe;          ///< name of top-level recipe for this program
+    // Private members
+    pmRecipeSource recipesRead;         ///< Which recipe sources have been read
+    psMetadata *recipeSymbols;          ///< Where each recipe came from
+    int traceFD;                        ///< File descriptor for trace messages
+    int logFD;                          ///< File descriptor for log messages
+} pmConfig;
+
+/// Allocator for pmConfig
+pmConfig *pmConfigAlloc();
+
+/// Set static configuration information
+///
+/// The search path for the configuration files is a local static variable, set by this function.
+void pmConfigSet(const char *path ///< Search paths for configuration files; colon-delimited directories
+                );
+
+/// Free static memory used in the configuration system
+void pmConfigDone(void);
+
+/// Read configuration information from the command line.
+///
+/// pmConfigRead loads the user configuration (the file name is specified by "-ipprc FILE" on the
+/// command-line, the IPPRC environment variable, or it is $HOME/.ipprc).  The configuration search path is
+/// set. The camera configuration is loaded if it is specified on the command line ("-camera
+/// CAMERA_FILE"). Recipes specified on the command line ("-recipe RECIPE_NAME RECIPE_SOURCE") are also
+/// loaded.  These command-line arguments are removed from from the command-line, to simplify parsing.  The
+/// psLib log, trace and time setups are also performed if specified in the user configuration.
+pmConfig *pmConfigRead(int *argc,       ///< Number of command-line arguments
+                       char **argv, ///< Array of command-line arguments
+                       const char *defaultRecipe ///< name of top-level recipe for this program
+                      );
+
+/// Read a configuration file
+///
+/// Read a metadata configuration file into the supplied metadata.  Produce an error and return false if
+/// there's a problem.
+bool pmConfigFileRead(psMetadata **config, ///< Config to output
+                      const char *name, ///< Name of file
+                      const char *description ///< Description of file
+                     );
+
+/// Validate a header against the camera format
+///
+/// Given a FITS header (the PHU header), check it against the RULE metadata contained within the camera
+/// format; return found = true if it matches. return false on serious errors
+bool pmConfigValidateCameraFormat(bool *valid,
+                                  const psMetadata *cameraFormat, ///< Camera format containing the RULE
+                                  const psMetadata *header // FITS header for the PHU
+                                 );
+
+/// Determine the camera format (and camera if unknown) from examining the header
+///
+/// Given a FITS header, check it against all known cameras (unless we already know which camera, from
+/// pmConfigRead) and all known formats for those cameras in order to identify which is appropriate.  The
+/// first matching format is accepted; further matches produce warnings.  The accepted camera is saved in the
+/// configuration.  The accepted format is returned.
+psMetadata *pmConfigCameraFormatFromHeader(psMetadata **camera, // selected camera (or meta-camera)
+					   psString *formatName, // selected format name
+					   pmConfig *config, ///< The configuration
+                                           const psMetadata *header, ///< The FITS header
+                                           bool readRecipes ///< optionally read the recipes as well as the format
+    );
+
+/// Return the camera configuration specified by name
+///
+/// Given a camera name, returns the camera configuration metadata.
+psMetadata *pmConfigCameraByName(pmConfig *config, ///< The configuration
+                                 const char *cameraName ///< The camera name header
+                                );
+
+/// Setup the database
+///
+/// Initialise the database connection using the DBSERVER, DBNAME, DBUSER, DBPASSWORD values provided in the
+/// site configuration.  Stores the database handle in the configuration, and also returns it.
+psDB *pmConfigDB(pmConfig *config       ///< Configuration
+                );
+
+/// Make the supplied header conform to the nominated camera format.
+///
+/// Given a FITS header, make it conform to the RULE in the specified camera format.  This is useful for
+/// switching between formats, or generating fake data that must be recognised by
+/// pmConfigCameraFormatFromHeader.
+bool pmConfigConformHeader(psMetadata *header, ///< Header to conform
+                           const psMetadata *format ///< Camera format
+                          );
+
+/// Read the command-line for files (or a text file containing a list of files)
+///
+/// Given the 'file' and 'list' arguments (e.g., "-file" and "-list"), find the arguments associated with
+/// these words and interpret them as lists of files.  Return an array of the resulting filenames.
+psArray *pmConfigFileSets(int *argc,    ///< Number of arguments (I/O)
+                          char **argv,  ///< Array of arguments
+                          const char *file, ///< CL argument specifying a filename
+                          const char *list ///< CL argument specifying a text file with a list of filenames
+                         );
+
+/// Stuff associated files from the command-line into a metadata
+///
+/// Calls pmConfigFileSets to parse the command line for filenames (or a list which provides filenames), and
+/// stuffs the array of filenames into the metadata under "name".
+bool pmConfigFileSetsMD(psMetadata *metadata, ///< Metadata into which to stuff the array
+                        int *argc,    ///< Number of arguments (I/O)
+                        char **argv,  ///< Array of arguments
+                        const char *name, ///< Name for array in the metadata
+                        const char *file, ///< CL argument specifying a filename
+                        const char *list ///< CL argument specifying a text file with a list of filenames
+                       );
+
+/// Convert the supplied name, create a new output psString
+psString pmConfigConvertFilename(
+    const char *filename,               ///< file path/URI
+    const pmConfig *config,             ///< configuration
+    bool create,                        ///< create the file if it doesn't exist
+    bool trunc                          ///< truncate the file (if it exists)
+);
+
+/// Set whether all config parameters are read on startup
+bool pmConfigReadParamsSet(bool newReadCameraConfig // Desired mode for camera configuration reading
+                          );
+
+/// Get the file rule of interest
+///
+/// Look up the name of the set of file rules to use, get that set from the system configuration, and return the
+/// appropriate rule from the set.
+psMetadata *pmConfigFileRule(const pmConfig *config, ///< Configuration
+                             const psMetadata *camera, ///< Camera configuration of interest
+                             const char *name ///< Name of rule to read
+    );
+
+// look up the specified fitstype, interpolating the file if needed
+psMetadata *pmConfigFitsType (const pmConfig *config, const psMetadata *camera, const char *fitsType);
+
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfigCamera.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfigCamera.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfigCamera.c	(revision 22157)
@@ -0,0 +1,843 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>            /* for strn?casecmp */
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmVersion.h"
+#include "pmConcepts.h"
+#include "pmConfigCamera.h"
+
+#define TABLE_OF_CONTENTS "CONTENTS"    // Name for camera format metadata containing the contents
+#define CHIP_TYPES "CHIPS"              // Name for camera format metadata containing the chip types
+#define CELL_TYPES "CELLS"              // Name for camera format metadata containing the cell types
+
+// local helper functions defined below
+static void removeCellConceptsSources(psMetadata *source);
+static void removeChipConceptsSources(psMetadata *source);
+
+// Generate the skycell version of a named camera configuration
+bool pmConfigCameraSkycellVersion(psMetadata *system, // The system configuration
+                                  const char *name // Name of the un-mosaicked camera
+                                  )
+{
+    PS_ASSERT_METADATA_NON_NULL(system, false);
+    PS_ASSERT_STRING_NON_EMPTY(name, false);
+
+    bool mdok;                          // Status of MD lookup
+    psMetadata *cameras = psMetadataLookupMetadata(&mdok, system, "CAMERAS"); // List of cameras
+    if (!mdok || !cameras) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find CAMERAS in the system configuration.\n");
+        return false;
+    }
+    if (!pmConfigGenerateSkycellVersion(cameras, cameras, name, system)) {
+        psError(PS_ERR_UNKNOWN, true, "Failed to build skycell camera description for %s\n", name);
+        return false;
+    }
+    return true;
+}
+
+
+bool pmConfigCameraSkycellVersionsAll(psMetadata *system)
+{
+    PS_ASSERT_METADATA_NON_NULL(system, false);
+
+    bool mdok;                          // Status of MD lookup
+    psMetadata *cameras = psMetadataLookupMetadata(&mdok, system, "CAMERAS"); // List of cameras
+    if (!mdok || !cameras) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find CAMERAS in the system configuration.\n");
+        return false;
+    }
+
+    psMetadataIterator *camerasIter = psMetadataIteratorAlloc(cameras, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *camerasItem = NULL; // Item from iteration
+    psMetadata *new = psMetadataAlloc();// New cameras to add
+    while ((camerasItem = psMetadataGetAndIncrement(camerasIter))) {
+        assert(camerasItem->type == PS_DATA_METADATA); // Only metadata are allowed here!
+        if (!pmConfigGenerateSkycellVersion(cameras, new, camerasItem->name, system)) {
+            psError(PS_ERR_UNKNOWN, true, "Failed to build skycell camera description for %s\n",
+                    camerasItem->name);
+            return false;
+        }
+    }
+    psFree(camerasIter);
+
+    // Now put the new cameras at the top of the list of cameras, so they get recognised first
+    // Note: going from the top, and putting everything to the top as we get there, so that the last one on
+    // goes to the top.  This preserves the original order of the cameras, putting the skycell versions
+    // before the originals.
+    camerasIter = psMetadataIteratorAlloc(new, PS_LIST_HEAD, NULL); // Iterator
+    while ((camerasItem = psMetadataGetAndIncrement(camerasIter))) {
+        psMetadataAddItem(cameras, camerasItem, PS_LIST_HEAD, 0);
+    }
+    psFree(camerasIter);
+    psFree(new);
+
+    return true;
+}
+
+// Don't update these skycell concepts; last one MUST be 0 (i.e., NULL).
+const static char *skycellConceptsCell[] = { "CELL.BIASSEC", "CELL.TRIMSEC", "CELL.READDIR", "CELL.XPARITY",
+                                             "CELL.YPARITY", "CELL.X0", "CELL.Y0", 0 };
+const static char *skycellConceptsChip[] = { "CHIP.XPARITY", "CHIP.YPARITY", 0 };
+const static char *skycellConceptsFPA[] = { 0 };
+
+// What do we call the skycell concept in the FITS header?
+static const char *skycellConceptName(const char *name, // Name of concept
+                                      const char **concepts, // List of concepts NOT to update
+                                      const psMetadata *system // System configuration
+                                      )
+{
+    for (int i = 0; concepts[i]; i++) {
+        if (strcmp(name, concepts[i]) == 0) {
+            return NULL;
+        }
+    }
+
+    if (!system) {
+        return name;
+    }
+    bool mdok;                          // Status of MD lookup
+    psMetadata *skycells = psMetadataLookupMetadata(&mdok, system, "SKYCELLS"); // Skycell concept headers
+    if (!skycells) {
+        return name;
+    }
+    const char *keyword = psMetadataLookupStr(&mdok, skycells, name); // Keyword to use for this concept
+    if (!mdok || !keyword || strlen(keyword) == 0) {
+        return name;
+    }
+    return keyword;
+}
+
+
+// Generate a skycell version of a camera configuration
+bool pmConfigGenerateSkycellVersion(psMetadata *oldCameras, // Old list of camera configurations
+                                    psMetadata *newCameras, // New list of camera configurations
+                                    const char *name, // Name of original camera configuration
+                                    const psMetadata *system // System configuration
+                                    )
+{
+    assert(oldCameras);
+    assert(newCameras);
+    assert(name);
+
+    // See if the old one is there
+    psMetadata *camera = psMetadataLookupMetadata(NULL, oldCameras, name); // The camera configuration
+    if (!camera) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Can't find camera to be skycelled in camera list.");
+        return false;
+    }
+
+    // See if the new one is already there
+    psString newName = NULL;       // Name of skycelled camera
+    psStringAppend(&newName, "_%s-SKYCELL", name);
+    if (psMetadataLookup(oldCameras, newName)) {
+        return true;
+    }
+
+    psMetadata *new = psMetadataCopy(NULL, camera); // Copy of the camera description
+
+    // Fix the FPA description to contain a single chip with single cell
+    {
+        psMetadata *fpa = psMetadataAlloc();// The FPA description
+        psMetadataAddStr(fpa, PS_LIST_HEAD, "SkyChip", 0, "Single chip with single cell", "SkyCell");
+        psMetadataAddMetadata(new, PS_LIST_TAIL, "FPA", PS_META_REPLACE, "Description of FPA hierarchy", fpa);
+        psFree(fpa);
+    }
+
+    // Clear out the formats, replace them with the One True Format
+    psMetadata *formats = psMetadataLookupMetadata(NULL, new, "FORMATS"); // The list of formats
+    if (!formats) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Can't find FORMATS within camera configuration.");
+        psFree(new);
+        return false;
+    }
+    while (psListLength(formats->list) > 0) {
+        psMetadataRemoveIndex(formats, PS_LIST_HEAD);
+    }
+    psMetadata *format = psMetadataAlloc(); // The One True Format
+
+    {
+        psMetadata *rule = psMetadataAlloc(); // The RULE --- how to recognise the camera
+        psMetadataAddStr(rule, PS_LIST_TAIL, "PSCAMERA", 0, "Camera name", name);
+        psMetadataAddStr(rule, PS_LIST_TAIL, "PSFORMAT", 0, "Camera format", "SKYCELL");
+        psMetadataAddMetadata(format, PS_LIST_TAIL, "RULE", 0, "How to recognise this type of file", rule);
+        psFree(rule);
+    }
+
+    {
+        psMetadata *file = psMetadataAlloc(); // The FILE --- how to read the data
+        psMetadataAddStr(file, PS_LIST_TAIL, "PHU", 0, "What level the FITS file represents", "FPA");
+        psMetadataAddStr(file, PS_LIST_TAIL, "EXTENSIONS", 0, "What level the extensions represent", "NONE");
+        psMetadataAddStr(file, PS_LIST_TAIL, "FPA.OBS", 0, "PHU keyword for unique identifier", "FPA.OBS");
+        psMetadataAddMetadata(format, PS_LIST_TAIL, "FILE", 0, "How to read this type of file", file);
+        psFree(file);
+    }
+
+    psMetadataAddStr(format, PS_LIST_TAIL, "CONTENTS", 0, "What's in this type of file",
+                     "SkyChip:SkyCell:_skycell");
+
+    {
+        psMetadata *cells = psMetadataAlloc(); // The CELLS --- how to read the cells
+        psMetadata *skycell = psMetadataAlloc(); // How to read the skycell
+        psMetadataAddStr(skycell, PS_LIST_TAIL, "CELL.TRIMSEC", 0, "Trim section", "CELL.TRIMSEC");
+        psMetadataAddStr(skycell, PS_LIST_TAIL, "CELL.BIASSEC", 0, "Bias section", "CELL.BIASSEC");
+        psMetadataAddStr(skycell, PS_LIST_TAIL, "CELL.TRIMSEC.SOURCE", 0, "Source for trim section",
+                         "HEADER");
+        psMetadataAddStr(skycell, PS_LIST_TAIL, "CELL.BIASSEC.SOURCE", 0, "Source for bias section",
+                         "HEADER");
+        psMetadataAddMetadata(cells, PS_LIST_TAIL, "_skycell", 0, "Skycell specification", skycell);
+        psFree(skycell);
+        psMetadataAddMetadata(format, PS_LIST_TAIL, "CELLS", 0, "How to read the cells", cells);
+        psFree(cells);
+    }
+
+    // Stuffing all concepts into the header, by their PS concept name (e.g., "FPA.AIRMASS").
+    // (HIERARCH will take care of the long names, implemented in psLib.)
+    // Some people may not like this, but it's quick and easy and will do for now.
+    // An alternative may be provided later.
+    {
+        psMetadata *translation = psMetadataAlloc(); // The TRANSLATION --- how to read the FITS headers
+
+        psList *concepts;               // List of concepts for each level
+        psListIterator *iter;           // Iterator for concepts
+        psString name;                  // Concept name, from iteration
+
+        concepts = pmConceptsList(PM_FPA_LEVEL_FPA); // FPA-level concepts
+        iter = psListIteratorAlloc(concepts, PS_LIST_HEAD, false);
+        while ((name = psListGetAndIncrement(iter))) {
+            const char *new = skycellConceptName(name, skycellConceptsFPA, system); // Name for skycell
+            if (new) {
+                psMetadataAddStr(translation, PS_LIST_TAIL, name, 0, NULL, new);
+            }
+        }
+        psFree(iter);
+        psFree(concepts);
+
+        concepts = pmConceptsList(PM_FPA_LEVEL_CHIP);
+        iter = psListIteratorAlloc(concepts, PS_LIST_HEAD, false);
+        while ((name = psListGetAndIncrement(iter))) {
+            const char *new = skycellConceptName(name, skycellConceptsChip, system); // Name for skycell
+            if (new) {
+                psMetadataAddStr(translation, PS_LIST_TAIL, name, 0, NULL, new);
+            }
+        }
+        psFree(iter);
+        psFree(concepts);
+
+        concepts = pmConceptsList(PM_FPA_LEVEL_CELL);
+        iter = psListIteratorAlloc(concepts, PS_LIST_HEAD, false);
+        while ((name = psListGetAndIncrement(iter))) {
+            const char *new = skycellConceptName(name, skycellConceptsCell, system); // Name for skycell
+            if (new) {
+                psMetadataAddStr(translation, PS_LIST_TAIL, name, 0, NULL, new);
+            }
+        }
+        psFree(iter);
+        psFree(concepts);
+
+        psMetadataAddMetadata(format, PS_LIST_TAIL, "TRANSLATION", 0, "How to translate the FITS headers",
+                              translation);
+        psFree(translation);
+    }
+
+    {
+        psMetadata *defaults = psMetadataAlloc(); // Default values for concepts
+
+        psMetadataAddS32(defaults, PS_LIST_TAIL, "CELL.XPARITY", 0, NULL, 1);
+        psMetadataAddS32(defaults, PS_LIST_TAIL, "CELL.YPARITY", 0, NULL, 1);
+        psMetadataAddS32(defaults, PS_LIST_TAIL, "CHIP.XPARITY", 0, NULL, 1);
+        psMetadataAddS32(defaults, PS_LIST_TAIL, "CHIP.YPARITY", 0, NULL, 1);
+        psMetadataAddS32(defaults, PS_LIST_TAIL, "CELL.Y0", 0, NULL, 0);
+        psMetadataAddS32(defaults, PS_LIST_TAIL, "CELL.X0", 0, NULL, 0);
+        psMetadataAddS32(defaults, PS_LIST_TAIL, "CHIP.Y0", 0, NULL, 0);
+        psMetadataAddS32(defaults, PS_LIST_TAIL, "CHIP.X0", 0, NULL, 0);
+        psMetadataAddS32(defaults, PS_LIST_TAIL, "CELL.READDIR", 0, "Read direction (rows)", 1);
+
+        psMetadataAddMetadata(format, PS_LIST_TAIL, "DEFAULTS", 0, "Default values for concepts", defaults);
+        psFree(defaults);
+
+    }
+
+    {
+        psMetadata *database = psMetadataAlloc(); // Database values for concepts
+        psMetadataAddMetadata(format, PS_LIST_TAIL, "DATABASE", 0, "Database values for concepts", database);
+        psFree(database);
+    }
+
+    {
+        psMetadata *conceptFormats = psMetadataAlloc(); // Format peculiarities for various concepts
+        // These are the only essential formats
+        psMetadataAddStr(conceptFormats, PS_LIST_TAIL, "FPA.RA", 0, "Units for RA", "HOURS");
+        psMetadataAddStr(conceptFormats, PS_LIST_TAIL, "FPA.DEC", 0, "Units for RA", "DEGREES");
+        psMetadataAddStr(conceptFormats, PS_LIST_TAIL, "FPA.TIME", 0, "Format for time", "MJD");
+        psMetadataAddStr(conceptFormats, PS_LIST_TAIL, "CELL.TIME", 0, "Format for time", "MJD");
+
+        psMetadataAddMetadata(format, PS_LIST_TAIL, "FORMATS", 0, "Formats for various concepts",
+                              conceptFormats);
+        psFree(conceptFormats);
+    }
+
+    psMetadataAddMetadata(formats, PS_LIST_TAIL, "SKYCELL", 0, "The One True Format for skycells", format);
+    psFree(format);
+
+    // New camera MUST go to the head of the metadata, so that it will be recognised first
+    // The old camera doesn't contain the PSCAMERA and PSFORMAT headers, so it will match anything!
+    psMetadataAddMetadata(newCameras, PS_LIST_HEAD, newName, PS_META_REPLACE,
+                          "Automatically generated", new);
+    psFree(newName);
+    psFree(new);
+
+    return true;
+}
+
+// Generate the Chip and FPA mosaicked version of a named camera configuration
+bool pmConfigCameraMosaickedVersions(psMetadata *system, // The system configuration
+                                     const char *name // Name of the un-mosaicked camera
+                                    )
+{
+    PS_ASSERT_METADATA_NON_NULL(system, false);
+    PS_ASSERT_STRING_NON_EMPTY(name, false);
+
+    bool mdok;                          // Status of MD lookup
+    psMetadata *cameras = psMetadataLookupMetadata(&mdok, system, "CAMERAS"); // List of cameras
+    if (!mdok || !cameras) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find CAMERAS in the system configuration.\n");
+        return false;
+    }
+    if (!pmConfigGenerateMosaickedVersion(cameras, cameras, name, PM_FPA_LEVEL_CHIP)) {
+        psError(PS_ERR_UNKNOWN, true, "Failed to build Chip mosaic camera description for %s\n", name);
+        return false;
+    }
+    if (!pmConfigGenerateMosaickedVersion(cameras, cameras, name, PM_FPA_LEVEL_FPA)) {
+        psError(PS_ERR_UNKNOWN, true, "Failed to build FPA mosaic camera description for %s\n", name);
+        return false;
+    }
+    return true;
+}
+
+// the operation putting the new entries first is now implemented in pmConfigGenerateMosaickedVersion
+// Generate the Chip and FPA mosaicked version of a named camera configuration
+bool pmConfigCameraMosaickedVersionsAll(psMetadata *system)
+{
+    PS_ASSERT_METADATA_NON_NULL(system, false);
+
+    bool mdok;                          // Status of MD lookup
+    psMetadata *cameras = psMetadataLookupMetadata(&mdok, system, "CAMERAS"); // List of cameras
+    if (!mdok || !cameras) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find CAMERAS in the system configuration.\n");
+        return false;
+    }
+
+    psMetadataIterator *camerasIter = psMetadataIteratorAlloc(cameras, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *camerasItem = NULL; // Item from iteration
+    psMetadata *new = psMetadataAlloc();// New cameras to add
+    while ((camerasItem = psMetadataGetAndIncrement(camerasIter))) {
+        assert(camerasItem->type == PS_DATA_METADATA); // Only metadata are allowed here!
+        if (!pmConfigGenerateMosaickedVersion(cameras, new, camerasItem->name, PM_FPA_LEVEL_CHIP)) {
+            psError(PS_ERR_UNKNOWN, true, "Failed to build Chip mosaic camera description for %s\n", camerasItem->name);
+            return false;
+        }
+        if (!pmConfigGenerateMosaickedVersion(cameras, new, camerasItem->name, PM_FPA_LEVEL_FPA)) {
+            psError(PS_ERR_UNKNOWN, true, "Failed to build FPA mosaic camera description for %s\n", camerasItem->name);
+            return false;
+        }
+    }
+    psFree(camerasIter);
+
+    // Now put the new cameras at the top of the list of cameras, so they get recognised first
+    // Note: going from the top, and putting everything to the top as we get there, so that the last one on
+    // goes to the top.  This preserves the original order of the cameras, putting the mosaicked versions
+    // before the originals.
+    camerasIter = psMetadataIteratorAlloc(new, PS_LIST_HEAD, NULL); // Iterator
+    while ((camerasItem = psMetadataGetAndIncrement(camerasIter))) {
+        psMetadataAddItem(cameras, camerasItem, PS_LIST_HEAD, 0);
+    }
+    psFree(camerasIter);
+    psFree(new);
+
+    return true;
+}
+
+// Generate a mosaicked version of a camera configuration
+bool pmConfigGenerateMosaickedVersion(psMetadata *oldCameras, // Old list of camera configurations
+                                      psMetadata *newCameras, // New list of camera configurations
+                                      const char *name, // Name of original camera configuration
+                                      pmFPALevel mosaicLevel // Level to which we are mosaicking
+    )
+{
+    assert(oldCameras);
+    assert(newCameras);
+    assert(name);
+    assert(mosaicLevel == PM_FPA_LEVEL_CHIP || mosaicLevel == PM_FPA_LEVEL_FPA);
+
+    // Don't generate a mosaicked version of a skycell
+    if (name[0] == '_' && strlen(name) > 9 && strcmp(name + strlen(name) - 8, "-SKYCELL") == 0) {
+        return true;
+    }
+
+    // See if the old one is there
+    psMetadata *camera = psMetadataLookupMetadata(NULL, oldCameras, name); // The camera configuration
+    if (!camera) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Can't find camera to be mosaicked in camera list.");
+        return false;
+    }
+
+    // See if the new one is already there
+    psString newName = NULL;       // Name of mosaicked camera
+    psStringAppend(&newName, "_%s-%s", name, mosaicLevel == PM_FPA_LEVEL_CHIP ? "CHIP" : "FPA");
+    if (psMetadataLookup(oldCameras, newName)) {
+        return true;
+    }
+
+    psMetadata *new = psMetadataCopy(NULL, camera); // Copy of the camera description
+    bool mdok;                          // Status of MD lookups
+
+    // ** Fix up the contents of the FPA description to match the mosaicked camera **
+    // select the FPA description
+    psMetadata *fpa = psMetadataLookupMetadata(NULL, new, "FPA"); // FPA in the camera configuration
+    if (!fpa) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Can't find FPA within camera configuration.");
+        psFree(new);
+        return false;
+    }
+    switch (mosaicLevel) {
+        // For CHIP mosaic, replace the contents of each chip with a single cell
+      case PM_FPA_LEVEL_CHIP: {
+          psMetadataIterator *fpaIter = psMetadataIteratorAlloc(fpa, PS_LIST_HEAD, NULL); // Iterator
+          psMetadataItem *fpaItem = NULL;     // Item from iteration
+          while ((fpaItem = psMetadataGetAndIncrement(fpaIter))) {
+              if (fpaItem->type != PS_DATA_STRING) {
+                  psError(PS_ERR_UNKNOWN, true,
+                          "Element %s within FPA in camera configuration is not of type STR.",
+                          fpaItem->name);
+                  psFree(new);
+                  return false;
+              }
+
+              psFree(fpaItem->data.str);
+              fpaItem->data.str = psStringCopy("MosaickedCell");
+              psFree(fpaItem->comment);
+              fpaItem->comment = psStringCopy("Mosaicked cell; automatically generated");
+          }
+          psFree(fpaIter);
+          break;
+      }
+        // For FPA mosaic, replace the contents of the FPA with a single chip containing a single cell
+      case PM_FPA_LEVEL_FPA: {
+          while (psListLength(fpa->list) > 0) {
+              psMetadataRemoveIndex(fpa, PS_LIST_TAIL);
+          }
+
+          psMetadataAddStr(fpa, PS_LIST_HEAD, "MosaickedChip", 0,
+                           "Mosaicked chip with mosaicked cell; automatically generated",
+                           "MosaickedCell");
+          break;
+      }
+    default:
+        psAbort("Should never get here.\n");
+    }
+
+    // ** Update the camera formats : add a new (mosaicked) format for each existing camera format **
+    // select the list of all camera formats
+    psMetadata *formats = psMetadataLookupMetadata(NULL, new, "FORMATS"); // FORMATS in the configuration
+    assert(formats);            // It had better be there --- we've already read them in
+    // loop over each of the formats
+    psMetadataIterator *formatsIter = psMetadataIteratorAlloc(formats, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *formatsItem = NULL; // Item from iteration
+    while ((formatsItem = psMetadataGetAndIncrement(formatsIter))) {
+        assert(formatsItem->type == PS_DATA_METADATA); // We should have read it by now!
+        psMetadata *format = formatsItem->data.md; // The camera format
+
+        // Add a new RULE which uniquely describes the mosaicked format.  this is needed so
+        // that when a mosaic is written to a FITS file, it can be recognised again when read.
+        psMetadata *rule = psMetadataLookupMetadata(NULL, format, "RULE"); // Way to identify format from PHU
+        if (!rule) {
+            // a camera format without a rule is not allowed.
+            psError(PS_ERR_UNKNOWN, false, "Camera format %s has no RULE", formatsItem->name);
+            return false;
+        }
+
+        // the new rule is supplemented by the mosaicLevel
+        switch (mosaicLevel) {
+        case PM_FPA_LEVEL_CHIP:
+            psMetadataAddStr(rule, PS_LIST_TAIL, "PSMOSAIC", 0, "Mosaicked level", "CHIP");
+            break;
+        case PM_FPA_LEVEL_FPA:
+            psMetadataAddStr(rule, PS_LIST_TAIL, "PSMOSAIC", 0, "Mosaicked level", "FPA");
+            break;
+        default:
+            psAbort("Should never get here.\n");
+        }
+
+        // Fix the FILE information: need to fix the levels for the PHU and EXTENSIONS.
+        // both of these elements are required in the format; we raise an error if they are not found
+        // If EXTENSIONS is NONE, then we need to change the CONTENT specifier to point to the chip name.
+        psMetadata *file = psMetadataLookupMetadata(NULL, format, "FILE"); // File information
+        if (!file) {
+            psError(PS_ERR_UNKNOWN, false, "Camera format %s has no FILE", formatsItem->name);
+            return false;
+        }
+        psMetadataItem *phuItem = psMetadataLookup(file, "PHU"); // PHU level
+        if (!phuItem || phuItem->type != PS_DATA_STRING) {
+            psError(PS_ERR_UNKNOWN, false, "Camera format %s is missing PHU in the FILE information", formatsItem->name);
+            return false;
+        }
+        psMetadataItem *extensionsItem = psMetadataLookup(file, "EXTENSIONS"); // Extensions level
+        if (!extensionsItem || extensionsItem->type != PS_DATA_STRING) {
+            psError(PS_ERR_UNKNOWN, false, "Camera format %s is missing EXTENSIONS in the FILE information", formatsItem->name);
+            return false;
+        }
+
+        // mosaicLevel == CHIP:
+        // Case    PHU     EXTENSIONS     Modifications
+        // ====    ===     ==========     ===========
+        // 1.      FPA     CHIP           NONE
+        // 2.      FPA     CELL           EXT->CHIP
+        // 3.      FPA     NONE           NONE
+        // 4.      CHIP    CELL           EXT->NONE
+        // 5.      CHIP    NONE           NONE
+        // 6.      CELL    NONE           PHU->CHIP
+        // possible outcomes:
+        //         FPA     CHIP
+        //         FPA     NONE
+        //         CHIP    NONE
+
+        // mosaicLevel == FPA:
+        // Case    PHU     EXTENSIONS     Modifications
+        // ====    ===     ==========     ===========
+        // 1.      FPA     CHIP           EXT->NONE
+        // 2.      FPA     CELL           EXT->NONE
+        // 3.      FPA     NONE           NONE
+        // 4.      CHIP    CELL           PHU->FPA, EXT->NONE
+        // 5.      CHIP    NONE           PHU->FPA
+        // 6.      CELL    NONE           PHU->FPA
+        // possible outcomes:
+        //         FPA     NONE
+
+        // modify the values of phuItem and extensionsItem
+        switch (mosaicLevel) {
+          case PM_FPA_LEVEL_CHIP:
+            if (!strcasecmp(phuItem->data.str, "FPA") && !strcasecmp(extensionsItem->data.str, "CHIP")) {
+                break;
+            }
+            if (!strcasecmp(phuItem->data.str, "FPA") && !strcasecmp(extensionsItem->data.str, "CELL")) {
+                psFree(extensionsItem->data.str);
+                extensionsItem->data.str = psStringCopy("CHIP");
+                break;
+            }
+            if (!strcasecmp(phuItem->data.str, "FPA") && !strcasecmp(extensionsItem->data.str, "NONE")) {
+                break;
+            }
+            if (!strcasecmp(phuItem->data.str, "CHIP") && !strcasecmp(extensionsItem->data.str, "CELL")) {
+                psFree(extensionsItem->data.str);
+                extensionsItem->data.str = psStringCopy("NONE");
+                break;
+            }
+            if (!strcasecmp(phuItem->data.str, "CHIP") && !strcasecmp(extensionsItem->data.str, "NONE")) {
+                break;
+            }
+            if (!strcasecmp(phuItem->data.str, "CELL") && !strcasecmp(extensionsItem->data.str, "NONE")) {
+                psFree(phuItem->data.str);
+                phuItem->data.str = psStringCopy("CHIP");
+                break;
+            }
+            psAbort ("should not reach here");
+
+          case PM_FPA_LEVEL_FPA:
+            if (!strcasecmp(phuItem->data.str, "FPA") && !strcasecmp(extensionsItem->data.str, "CHIP")) {
+                psFree(extensionsItem->data.str);
+                extensionsItem->data.str = psStringCopy("NONE");
+                break;
+            }
+            if (!strcasecmp(phuItem->data.str, "FPA") && !strcasecmp(extensionsItem->data.str, "CELL")) {
+                psFree(extensionsItem->data.str);
+                extensionsItem->data.str = psStringCopy("NONE");
+                break;
+            }
+            if (!strcasecmp(phuItem->data.str, "FPA") && !strcasecmp(extensionsItem->data.str, "NONE")) {
+                break;
+            }
+            if (!strcasecmp(phuItem->data.str, "CHIP") && !strcasecmp(extensionsItem->data.str, "CELL")) {
+                psFree(phuItem->data.str);
+                phuItem->data.str = psStringCopy("FPA");
+                psFree(extensionsItem->data.str);
+                extensionsItem->data.str = psStringCopy("NONE");
+                break;
+            }
+            if (!strcasecmp(phuItem->data.str, "CHIP") && !strcasecmp(extensionsItem->data.str, "NONE")) {
+                psFree(phuItem->data.str);
+                phuItem->data.str = psStringCopy("FPA");
+                break;
+            }
+            if (!strcasecmp(phuItem->data.str, "CELL") && !strcasecmp(extensionsItem->data.str, "NONE")) {
+                psFree(phuItem->data.str);
+                phuItem->data.str = psStringCopy("FPA");
+                break;
+            }
+            psAbort ("should not reach here");
+
+          default:
+            psAbort("Should never get here.\n");
+        }
+
+        // Fix up the CONTENTS to contain only the mosaicked cell for each chip
+        switch (mosaicLevel) {
+          case PM_FPA_LEVEL_FPA:
+            psMetadataAddStr(format, PS_LIST_TAIL, TABLE_OF_CONTENTS, PS_META_REPLACE, NULL,
+                             "MosaickedChip:MosaickedCell:_mosaic");
+            break;
+          case PM_FPA_LEVEL_CHIP:
+            if (!strcasecmp(phuItem->data.str, "FPA") && !strcasecmp(extensionsItem->data.str, "CHIP")) {
+                // ensure the value of CONTENT in the FILE section has the right value
+                psMetadataAddStr(file, PS_LIST_TAIL, "CONTENT", PS_META_REPLACE, "Key to CONTENTS menu",
+                                 "PS_CNTNT");
+                psMetadataAddStr(file, PS_LIST_TAIL, "CONTENT.RULE", PS_META_REPLACE,
+                                 "Rule to generate CONTENTS", "{CHIP.NAME}");
+
+                // List the chipName:chipType for each chip.
+                psMetadata *contents = psMetadataAlloc(); // List of contents, with chipName:chipType
+
+                // XXX this is using the fpaItem->name not the chipName
+                psMetadataIterator *fpaIter = psMetadataIteratorAlloc(fpa, PS_LIST_HEAD, NULL); // Iteratr
+                psMetadataItem *fpaItem;    // Item from iteration
+                while ((fpaItem = psMetadataGetAndIncrement(fpaIter))) {
+                    assert (fpaItem->type == PS_DATA_STRING);
+                    psString content = NULL; // Content to add
+                    psStringAppend(&content, "%s:_mosaicChip ", fpaItem->name);
+                    psMetadataAddStr(contents, PS_LIST_TAIL, fpaItem->name, 0, NULL, content);
+                    psFree(content);
+                }
+                psFree(fpaIter);
+                psMetadataAddMetadata(format, PS_LIST_TAIL, TABLE_OF_CONTENTS, PS_META_REPLACE,
+                                      "List of contents", contents);
+                psFree(contents);
+
+                psMetadata *chips = psMetadataAlloc(); // List of chip types, with cellName:cellType
+                psMetadataAddStr(chips, PS_LIST_TAIL, "_mosaicChip", 0, NULL,
+                                 "MosaickedCell:_mosaic");
+                psMetadataAddMetadata(format, PS_LIST_TAIL, CHIP_TYPES, PS_META_REPLACE,
+                                      "List of chip types", chips);
+                psFree(chips);
+                break;
+            }
+            if (!strcasecmp(phuItem->data.str, "FPA") && !strcasecmp(extensionsItem->data.str, "NONE")) {
+                // List the contents on a single line
+                psString contentsLine = NULL; // Contents of the PHU
+                psMetadataIterator *fpaIter = psMetadataIteratorAlloc(fpa, PS_LIST_HEAD, NULL); // Iteratr
+                psMetadataItem *fpaItem;    // Item from iteration
+                while ((fpaItem = psMetadataGetAndIncrement(fpaIter))) {
+                    assert (fpaItem->type == PS_DATA_STRING);
+                    psStringAppend(&contentsLine, "%s:MosaickedCell:_mosaic ", fpaItem->name);
+                }
+                psFree(fpaIter);
+                psMetadataAddStr(format, PS_LIST_TAIL, TABLE_OF_CONTENTS, PS_META_REPLACE,
+                                 NULL, contentsLine);
+                psFree(contentsLine);
+                break;
+            }
+            if (!strcasecmp(phuItem->data.str, "CHIP") && !strcasecmp(extensionsItem->data.str, "NONE")) {
+                // XXX recode this to match the structure above (FPA/CHIP)?
+                // select and remove the old contents
+                psMetadata *contents = psMetadataLookupMetadata(NULL, format, TABLE_OF_CONTENTS); // File contents
+                if (!contents) {
+                    psError(PS_ERR_UNKNOWN, false, "Couldn't find %s in the camera format %s.\n",
+                            TABLE_OF_CONTENTS, formatsItem->name);
+                    return false;
+                }
+
+                // replace chip type with _mosaicChip
+                psMetadataIterator *contentIter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL); // Iterator
+                psMetadataItem *contentItem;    // Item from iteration
+                while ((contentItem = psMetadataGetAndIncrement(contentIter))) {
+                    assert (contentItem->type == PS_DATA_STRING);
+                    char *ptr = strchr (contentItem->data.str, ':');
+                    assert (ptr);
+                    psString content = psStringNCopy (contentItem->data.str, ptr - contentItem->data.str);
+                    psStringAppend(&content, ":_mosaicChip ");
+                    psFree (contentItem->data.str);
+                    contentItem->data.str = content;
+                }
+                psFree(contentIter);
+
+                # if (0)
+                while (psListLength(contents->list) > 0) {
+                    psMetadataRemoveIndex(contents, PS_LIST_TAIL);
+                }
+
+                // update with the new contents
+                psMetadataIterator *fpaIter = psMetadataIteratorAlloc(fpa, PS_LIST_HEAD, NULL); // Iterator
+                psMetadataItem *fpaItem;    // Item from iteration
+                while ((fpaItem = psMetadataGetAndIncrement(fpaIter))) {
+                    assert (fpaItem->type == PS_DATA_STRING);
+                    psString content = NULL; // Content to add
+                    psStringAppend(&content, "%s:_mosaicChip ", fpaItem->name);
+                    psMetadataAddStr(contents, PS_LIST_TAIL, fpaItem->name, 0, NULL, content);
+                    psFree(content);
+                }
+                psFree(fpaIter);
+                # endif
+
+                psMetadata *chips = psMetadataAlloc(); // List of chip types, with cellName:cellType
+                psMetadataAddStr(chips, PS_LIST_TAIL, "_mosaicChip", 0, NULL,
+                                 "MosaickedCell:_mosaic");
+                psMetadataAddMetadata(format, PS_LIST_TAIL, CHIP_TYPES, PS_META_REPLACE,
+                                      "List of chip types", chips);
+                psFree(chips);
+                break;
+            }
+        default:
+            psAbort("Should never get here.\n");
+        }
+
+        // Fix the cell type
+        psMetadata *cells = psMetadataLookupMetadata(NULL, format, CELL_TYPES); // CELLS information
+        if (!cells) {
+            psError(PS_ERR_UNKNOWN, false, "Couldn't find CELLS of type METADATA in the camera format %s.\n", formatsItem->name);
+            return false;
+        }
+        psMetadata *cell = psMetadataAlloc(); // Cell information
+        psMetadataAddStr(cell, PS_LIST_TAIL, "CELL.TRIMSEC", 0, "Trim section", "TRIMSEC");
+        psMetadataAddStr(cell, PS_LIST_TAIL, "CELL.BIASSEC", 0, "Bias section", "BIASSEC");
+        psMetadataAddStr(cell, PS_LIST_TAIL, "CELL.TRIMSEC.SOURCE", 0, "Trim section source", "HEADER");
+        psMetadataAddStr(cell, PS_LIST_TAIL, "CELL.BIASSEC.SOURCE", 0, "Bias section source", "HEADER");
+        psMetadataAddMetadata(cells, PS_LIST_HEAD, "_mosaic", PS_META_REPLACE, "Mosaic cell information", cell);
+        psFree(cell);                   // Drop reference
+
+        // Update the concepts, so that they are all stored in the FITS headers, under headers of the same
+        // name as the concept
+        psMetadata *database = psMetadataLookupMetadata(&mdok, format, "DATABASE"); // DATABASE concepts
+        psMetadata *defaults = psMetadataLookupMetadata(&mdok, format, "DEFAULTS"); // DEFAULTS concepts
+        if (!mdok || !defaults) {
+            psWarning("Couldn't find DEFAULTS of type METADATA in the camera format %s.\n",
+                      formatsItem->name);
+            continue;
+        }
+        psMetadata *translation = psMetadataLookupMetadata(&mdok, format, "TRANSLATION"); // TRANSLATION info
+        if (!mdok || !translation) {
+            psWarning("Couldn't find TRANSLATION of type METADATA in the camera format %s.\n",
+                      formatsItem->name);
+            continue;
+        }
+
+        removeCellConceptsSources(translation);
+        removeCellConceptsSources(database);
+        removeCellConceptsSources(defaults);
+
+        psMetadata *conceptFormats = psMetadataLookupMetadata(&mdok, format, "FORMATS"); // Concepts formats
+
+        // Add in the positioning concepts
+        psMetadataAddS32(defaults, PS_LIST_TAIL, "CELL.XPARITY", 0, NULL, 1);
+        psMetadataAddS32(defaults, PS_LIST_TAIL, "CELL.YPARITY", 0, NULL, 1);
+        psMetadataAddS32(defaults, PS_LIST_TAIL, "CELL.X0",      0, NULL, 0);
+        psMetadataAddS32(defaults, PS_LIST_TAIL, "CELL.Y0",      0, NULL, 0);
+        if (conceptFormats) {
+            if (psMetadataLookup(conceptFormats, "CELL.X0")) {
+                psMetadataRemoveKey(conceptFormats, "CELL.X0");
+            }
+            if (psMetadataLookup(conceptFormats, "CELL.Y0")) {
+                psMetadataRemoveKey(conceptFormats, "CELL.Y0");
+            }
+        }
+
+        if (mosaicLevel == PM_FPA_LEVEL_FPA) {
+            removeChipConceptsSources(translation);
+            removeChipConceptsSources(database);
+            removeChipConceptsSources(defaults);
+            psMetadataAddS32(defaults, PS_LIST_TAIL, "CHIP.XPARITY", 0, NULL, 1);
+            psMetadataAddS32(defaults, PS_LIST_TAIL, "CHIP.YPARITY", 0, NULL, 1);
+            psMetadataAddS32(defaults, PS_LIST_TAIL, "CHIP.X0", 0, NULL, 0);
+            psMetadataAddS32(defaults, PS_LIST_TAIL, "CHIP.Y0", 0, NULL, 0);
+            if (conceptFormats) {
+                if (psMetadataLookup(conceptFormats, "CHIP.X0")) {
+                    psMetadataRemoveKey(conceptFormats, "CHIP.X0");
+                }
+                if (psMetadataLookup(conceptFormats, "CHIP.Y0")) {
+                    psMetadataRemoveKey(conceptFormats, "CHIP.Y0");
+                }
+            }
+        }
+
+    }
+    psFree(formatsIter);
+
+    // New camera MUST go to the head of the metadata, so that it will be recognised first
+    // The old camera doesn't contain the PSMOSAIC header, so it will match anything!
+    psMetadataAddMetadata(newCameras, PS_LIST_HEAD, newName, PS_META_REPLACE,
+                          "Automatically generated", new);
+    psFree(newName);
+    psFree(new);
+
+    return true;
+}
+
+/*** Helper Functions ***/
+
+// Remove a concept from the list of sources.  Need to check to see if it exists first, to avoid a warning.
+static void removeConcept(psMetadata *source, // Source from which to remove concept
+                          const char *concept // Concept name to remove
+                         )
+{
+    assert(source);
+    assert(concept && strlen(concept) > 0);
+
+    if (psMetadataLookup(source, concept)) {
+        psMetadataRemoveKey(source, concept);
+    }
+
+    return;
+}
+
+// Remove certain concepts from the list of sources.  These concepts are important in the mosaicking process,
+// and are added explicitly to the defaults (elsewhere) so that the user can't get them wrong.
+static void removeCellConceptsSources(psMetadata *source // Source for concepts
+    )
+{
+    if (!source) {
+        return;
+    }
+
+    removeConcept(source, "CELL.BIASSEC");
+    removeConcept(source, "CELL.TRIMSEC");
+    removeConcept(source, "CELL.XPARITY");
+    removeConcept(source, "CELL.YPARITY");
+    removeConcept(source, "CELL.X0");
+    removeConcept(source, "CELL.Y0");
+
+    // For the sake of the defaults, include the .DEPEND
+    removeConcept(source, "CELL.XPARITY.DEPEND");
+    removeConcept(source, "CELL.YPARITY.DEPEND");
+    removeConcept(source, "CELL.X0.DEPEND");
+    removeConcept(source, "CELL.Y0.DEPEND");
+
+    return;
+}
+
+// Remove certain concepts from the list of sources.  These concepts are important in the mosaicking process,
+// and are added explicitly to the defaults (elsewhere) so that the user can't get them wrong.
+static void removeChipConceptsSources(psMetadata *source // Source for concepts
+    )
+{
+    if (!source) {
+        return;
+    }
+
+    removeConcept(source, "CHIP.XPARITY");
+    removeConcept(source, "CHIP.YPARITY");
+    removeConcept(source, "CHIP.X0");
+    removeConcept(source, "CHIP.Y0");
+
+    // For the sake of the defaults, include the .DEPEND
+    removeConcept(source, "CHIP.XPARITY.DEPEND");
+    removeConcept(source, "CHIP.YPARITY.DEPEND");
+    removeConcept(source, "CHIP.X0.DEPEND");
+    removeConcept(source, "CHIP.Y0.DEPEND");
+
+    return;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfigCamera.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfigCamera.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfigCamera.h	(revision 22157)
@@ -0,0 +1,52 @@
+/*  @file pmConfigCamera.h
+ *  @brief Camera Configuration functions
+ *
+ *  @author Paul Price, IfA
+ *  @author Eugene Magnier, IfA
+ *
+ *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-08-14 00:17:40 $
+ *  Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_CONFIG_CAMERA_H
+#define PM_CONFIG_CAMERA_H
+
+/// @addtogroup Config Configuration System
+/// @{
+
+// Generate a skycell version of a camera configuration
+bool pmConfigGenerateSkycellVersion(psMetadata *oldCameras, // Old list of camera configurations
+                                    psMetadata *newCameras, // New list of camera configurations
+                                    const char *name, // Name of original camera configuration
+                                    const psMetadata *site // The site configuration
+    );
+
+/// Generate the skycell version of a particular camera configuration
+bool pmConfigCameraSkycellVersion(psMetadata *site, // The site configuration
+                                  const char *name // Name of the un-mosaicked camera
+    );
+
+
+/// Generate skycell versions of all the camera configurations
+bool pmConfigCameraSkycellVersionsAll(psMetadata *site // Site configuration
+    );
+
+// Generate a mosaicked version of a camera configuration
+bool pmConfigGenerateMosaickedVersion(psMetadata *oldCameras, // Old list of camera configurations
+                                      psMetadata *newCameras, // New list of camera configurations
+                                      const char *name, // Name of original camera configuration
+                                      pmFPALevel mosaicLevel // Level to which we are mosaicking
+    );
+
+/// Generate the chip mosaicked version of a particular camera configuration
+bool pmConfigCameraMosaickedVersions(psMetadata *site, // Site configuration
+                                     const char *name // Name of the un-mosaicked camera
+    );
+
+/// Generate chip- and fpa-mosaicked versions of all the camera configurations
+bool pmConfigCameraMosaickedVersionsAll(psMetadata *site // Site configuration
+    );
+
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfigCommand.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfigCommand.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfigCommand.c	(revision 22157)
@@ -0,0 +1,61 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmConfig.h"
+#include "pmConfigCommand.h"
+
+bool pmConfigDatabaseCommand(psString *command, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(command, false);
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(config->complete, false);
+
+    bool mdok;                          // Status of MD lookup
+    const char *dbserver = psMetadataLookupStr(&mdok, config->complete, "DBSERVER"); // Database server
+    if (!mdok || strlen(dbserver) == 0) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find DBSERVER in site configuration.\n");
+        return false;
+    }
+    const char *dbname = psMetadataLookupStr(&mdok, config->complete, "DBNAME"); // Database name
+    if (!mdok || strlen(dbname) == 0) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find DBNAME in site configuration.\n");
+        return false;
+    }
+    const char *dbuser = psMetadataLookupStr(&mdok, config->complete, "DBUSER"); // Database user
+    if (!mdok || strlen(dbuser) == 0) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find DBUSER in site configuration.\n");
+        return false;
+    }
+    const char *dbpassword = psMetadataLookupStr(&mdok, config->complete, "DBPASSWORD"); // Database password
+    if (!mdok || strlen(dbpassword) == 0) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find DBPASSWORD in site configuration.\n");
+        return false;
+    }
+
+    psStringAppend(command, " -dbserver %s -dbname %s -dbuser %s -dbpassword %s",
+                   dbserver, dbname, dbuser, dbpassword);
+
+    return true;
+}
+
+
+bool pmConfigTraceCommand(psString *command)
+{
+    PS_ASSERT_PTR_NON_NULL(command, false);
+
+    psMetadata *levels = psTraceLevels(); // Metadata levels
+    psMetadataIterator *iter = psMetadataIteratorAlloc(levels, PS_LIST_HEAD, NULL); // Iterator for levels
+    psMetadataItem *item;               // Item from iteration
+    while ((item = psMetadataGetAndIncrement(iter))) {
+        assert(item->type == PS_TYPE_S32);
+        psStringAppend(command, " -trace %s %d", item->name, item->data.S32);
+    }
+    psFree(iter);
+    psFree(levels);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfigCommand.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfigCommand.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfigCommand.h	(revision 22157)
@@ -0,0 +1,16 @@
+#ifndef PM_CONFIG_COMMAND_H
+#define PM_CONFIG_COMMAND_H
+
+/// Extend a command-line to include the necessary database flags
+///
+/// The command-line is extended with -dbserver, -dbname, -dbuser, -dbpassword
+bool pmConfigDatabaseCommand(psString *command, ///< Command to extend
+                             const pmConfig *config ///< Configuration
+                            );
+
+/// Extend a command-line to propagate the trace flags
+///
+/// The command-line is extended with -trace
+bool pmConfigTraceCommand(psString *command ///< Command to extend
+                         );
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfigMask.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfigMask.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfigMask.c	(revision 22157)
@@ -0,0 +1,240 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmConfigMask.h"
+
+psMaskType pmConfigMaskGet(const char *masks, const pmConfig *config)
+{
+    assert (config);
+    PS_ASSERT_STRING_NON_EMPTY(masks, 0);
+
+    psMetadata *recipe = psMetadataLookupMetadata(NULL, config->recipes, "MASKS"); // The recipe
+    if (!recipe) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find MASKS recipe.");
+        return 0;
+    }
+
+    psMaskType mask = 0;                // Mask value, to return
+
+    psArray *names = psStringSplitArray(masks, " ,;", false); // Array of symbolic names
+    for (int i = 0; i < names->n; i++) {
+        const char *name = names->data[i]; // Symbolic name of interest
+        bool mdok;                      // Status of MD lookup
+        psMaskType value = psMetadataLookupU8(&mdok, recipe, name);
+        if (!mdok) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
+                    "Unable to find mask value for %s in MASK recipe", name);
+            psFree(names);
+            return 0;
+        }
+        mask |= value;
+    }
+    psFree(names);
+
+    return mask;
+}
+
+bool pmConfigMaskSet(const pmConfig *config, const char *maskName, psMaskType maskValue)
+{
+    assert (config);
+    PS_ASSERT_STRING_NON_EMPTY(maskName, false);
+
+    psMetadata *recipe = psMetadataLookupMetadata(NULL, config->recipes, "MASKS"); // The recipe
+    if (!recipe) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find MASKS recipe.");
+        return false;
+    }
+
+    psMetadataAddU8 (recipe, PS_LIST_TAIL, maskName, PS_META_REPLACE, "user-defined mask", maskValue);
+
+    return true;
+}
+
+// replace the named masks in the recipe with values in the header:
+// replace only the names in the header in the recipe
+bool pmConfigMaskReadHeader (pmConfig *config, psMetadata *header) {
+
+    bool status = false;
+    char namekey[80];
+    char valuekey[80];
+
+    psMetadata *recipe = psMetadataLookupMetadata(NULL, config->recipes, "MASKS"); // The recipe
+    if (!recipe) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find MASKS recipe.");
+        return false;
+    }
+
+    int nMask = psMetadataLookupS32 (&status, header, "MSKNUM");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find MSKNUM in header.");
+        return false;
+    }
+
+    for (int i = 0; i < nMask; i++) {
+
+	snprintf (namekey,  64, "MSKNAM%02d", i);
+	snprintf (valuekey, 64, "MSKVAL%02d", i);
+
+	char *name = psMetadataLookupStr (&status, header, namekey);
+	psU8 bit = psMetadataLookupU8 (&status, header, valuekey);
+
+	// XXX validate that bit is a 2^n value?
+
+	psMetadataItem *item = psMetadataLookup (header, name);
+	if (!item) {
+	    psWarning("mask recipe entry %s not in recipe\n", name);
+	    psMetadataAddU8 (recipe, PS_LIST_TAIL, name, 0, "Bitmask bit value", bit);
+	} else {
+	    item->data.U8 = bit;
+	}
+    }
+    
+    return true;
+}
+
+// write the named mask bits to the header
+bool pmConfigMaskWriteHeader (pmConfig *config, psMetadata *header) {
+
+    char namekey[80];
+    char valuekey[80];
+
+    psMetadata *recipe = psMetadataLookupMetadata(NULL, config->recipes, "MASKS"); // The recipe
+    if (!recipe) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find MASKS recipe.");
+        return false;
+    }
+
+    int nMask = 0;
+
+    psMetadataIterator *iter = psMetadataIteratorAlloc(recipe, PS_LIST_HEAD, NULL); // Iterator
+
+    psMetadataItem *item;               // Item from iteration
+    while ((item = psMetadataGetAndIncrement(iter))) {
+
+	if (item->type != PS_DATA_U8) {
+	    psWarning("mask recipe entry %s is not a bit value\n", item->name);
+	    continue;
+	}
+
+	snprintf (namekey,  64, "MSKNAM%02d", nMask);
+	snprintf (valuekey, 64, "MSKVAL%02d", nMask);
+
+	psMetadataAddStr (header, PS_LIST_TAIL, namekey, 0, "Bitmask bit name", item->name);
+	psMetadataAddU8 (header, PS_LIST_TAIL, valuekey, 0, "Bitmask bit value", item->data.U8);
+	nMask ++;
+    }
+    
+    psMetadataAddS32 (header, PS_LIST_TAIL, "MSKNUM", 0, "Bitmask bit count", nMask);
+    return true;
+}
+
+// examine named mask values in mask recipe and set the bits for maskValue and markValue
+// this function sets an appropriate value for the following required named mask concepts: 
+// FLAT (used to mark out-of-range corrections in the flat-fielding)
+// BLANK (used to mark non-existent pixels)
+// SAT (used to mark pixels with values out-of-range on the high end)
+// BAD (used to mark pixels with values out-of-range on the low end)
+// If there is no explicit value for the above, the 'DETECTOR' and 'RANGE' bits are used
+// If these latter do not exist, the value 0x01 is used.
+// The values actually used for these names are written back to the config file
+bool pmConfigMaskSetBits (psMaskType *outMaskValue, psMaskType *outMarkValue, pmConfig *config) {
+
+    psMaskType maskValue = 0;
+
+    // mask for generic detector defect
+    psMaskType detectorMask = pmConfigMaskGet("DETECTOR", config); 
+    maskValue |= detectorMask;
+
+    // mask for dark structures
+    psMaskType darkMask = pmConfigMaskGet("DARK", config);
+    maskValue |= darkMask;
+
+    // mask for non-linear flat regions (default to DETECTOR if not defined)
+    psMaskType flatMask = pmConfigMaskGet("FLAT", config); 
+    if (!flatMask) {
+	flatMask = detectorMask;
+	pmConfigMaskSet (config, "FLAT", flatMask);
+    }
+    if (!flatMask) {
+	flatMask = 0x01;
+	pmConfigMaskSet (config, "FLAT", flatMask);
+    }
+    maskValue |= flatMask;
+
+    // mask for non-existent data  (default to DETECTOR if not defined)
+    psMaskType blankMask = pmConfigMaskGet("BLANK", config); 
+    if (!blankMask) {
+	blankMask = detectorMask;
+	pmConfigMaskSet (config, "BLANK", blankMask);
+    }
+    if (!blankMask) {
+	blankMask = 0x01;
+	pmConfigMaskSet (config, "BLANK", blankMask);
+    }
+    maskValue |= blankMask;
+
+    // mask for generic data range errors
+    psMaskType rangeMask = pmConfigMaskGet("RANGE", config);
+    maskValue |= rangeMask;
+
+    // mask for saturated data  (default to RANGE if not defined)
+    psMaskType satMask = pmConfigMaskGet("SAT", config);
+    if (!satMask) {
+	satMask = rangeMask;
+	pmConfigMaskSet (config, "SAT", satMask);
+    }
+    if (!satMask) {
+	satMask = 0x01;
+	pmConfigMaskSet (config, "SAT", satMask);
+    }
+    maskValue |= satMask;
+    
+    // mask for below-range data  (default to RANGE if not defined)
+    psMaskType badMask = pmConfigMaskGet("BAD", config);
+    if (!badMask) {
+	badMask = rangeMask;
+	pmConfigMaskSet (config, "BAD", badMask);
+    }
+    if (!badMask) {
+	badMask = 0x01;
+	pmConfigMaskSet (config, "BAD", badMask);
+    }
+    maskValue |= badMask;
+
+    // XXX not sure what to do with these
+    psMaskType crMask = pmConfigMaskGet("CR", config);
+    maskValue |= crMask;
+
+    psMaskType ghostMask = pmConfigMaskGet("GHOST", config);
+    maskValue |= ghostMask;
+
+    // search for an unset bit to use for MARK:
+    psMaskType markValue = 0x80;
+
+    int nBits = sizeof(psMaskType) * 8;
+    for (int i = 0; !markValue && (i < nBits); i++) {
+	if (maskValue & markValue) {
+	    markValue >>= 1;
+	} else {
+	    markValue = markValue;
+	}
+    }
+    if (!markValue) {
+	psError (PS_ERR_UNKNOWN, true, "Unable to define the MARK bit mask: all bits taken!");
+	return false;
+    }
+
+    // update the config table
+    pmConfigMaskSet (config, "MASK.VALUE", maskValue);
+    pmConfigMaskSet (config, "MARK.VALUE", markValue);
+
+    *outMaskValue = maskValue;
+    *outMarkValue = markValue;
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfigMask.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfigMask.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfigMask.h	(revision 22157)
@@ -0,0 +1,40 @@
+/*  @file pmConfigMask.h
+ *  @brief Mask configuration functions
+ *
+ *  @author Paul Price, IfA
+ *
+ *  @version $Revision: 1.2.14.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-07-11 18:35:04 $
+ *  Copyright 2007 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_CONFIG_MASK_H
+#define PM_CONFIG_MASK_H
+
+#include <pslib.h>
+#include <pmConfig.h>
+
+#define PM_MASKS_RECIPE "MASKS"
+
+/// @addtogroup Config Configuration System
+/// @{
+
+/// Return a mask value given a list of symbolic names
+///
+/// The mask values are derived from the MASKS recipe
+psMaskType pmConfigMaskGet(const char *masks, ///< List of symbolic names, space/comma delimited
+                        const pmConfig *config ///< Configuration
+                        );
+
+bool pmConfigMaskSet(const pmConfig *config, const char *maskName, psMaskType maskValue);
+
+// replace the named masks in the recipe with values in the header:
+// replace only the names in the header in the recipe
+bool pmConfigMaskReadHeader (pmConfig *config, psMetadata *header);
+
+// write the named mask bits to the header
+bool pmConfigMaskWriteHeader (pmConfig *config, psMetadata *header);
+
+bool pmConfigMaskSetBits (psMaskType *outMaskValue, psMaskType *outMarkValue, pmConfig *config);
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfigRecipes.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfigRecipes.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfigRecipes.c	(revision 22157)
@@ -0,0 +1,628 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>            /* for strn?casecmp */
+#include <assert.h>
+#include <pslib.h>
+#include "pmConfig.h"
+#include "pmConfigRecipes.h"
+
+static bool loadRecipeSystem(bool *status, pmConfig *config, psMetadata *source);
+static bool loadRecipeCamera(bool *status, pmConfig *config, psMetadata *source);
+static bool loadRecipeSymbols(bool *status, pmConfig *config, pmRecipeSource source);
+static bool loadRecipeFromArguments(bool *status, pmConfig *config);
+static bool loadRecipeOptions(bool *status, pmConfig *config);
+static bool mergeRecipeCamera(bool *status, pmConfig *config);
+
+// use this function to select the options structure for the specified recipe
+// add additional command-line options to this metadata (before parsing the camera)
+psMetadata *pmConfigRecipeOptions (pmConfig *config, char *recipeName)
+{
+    bool success;
+
+    // select or create the OPTIONS folder
+    psMetadata *options = psMetadataLookupMetadata(&success, config->arguments, "OPTIONS");
+    if (!options) {
+        options = psMetadataAlloc ();
+        success = psMetadataAddPtr (config->arguments, PS_LIST_TAIL, "OPTIONS",  PS_DATA_METADATA, "", options);
+        assert (success); // type mismatch : OPTIONS already defined but wrong type
+        psFree (options); // drop extra reference
+    }
+
+    // look for the recipe defined in recipes
+    // if the recipe is already defined in config->arguments:OPTIONS, supplement
+    // save the recipe options onto config->arguments:RECIPES
+    psMetadata *recipe = psMetadataLookupMetadata(&success, options, recipeName);
+    if (!recipe) {
+        recipe = psMetadataAlloc();
+        success = psMetadataAddPtr(options, PS_LIST_TAIL, recipeName,  PS_DATA_METADATA, "", recipe);
+        assert (success); // type mismatch : OPTIONS already defined but wrong type
+        psFree (recipe);  // drop extra reference
+    }
+    return recipe;
+}
+
+// this function may be called several times.  it attempts to load the recipe data from one of three
+// locations: config->complete, config->camera, and argv.  We cannot read the recipes from
+// config->camera until a camera has been read BUT, the argv recipes must override the camera and
+// system recipes.  This command strips the argv elements it uses from the argv list.
+bool pmConfigReadRecipes(pmConfig *config, pmRecipeSource source)
+{
+    bool status;
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    if (!config->recipes) {
+        config->recipes = psMetadataAlloc();
+    }
+
+    // Read the recipe file names from the system configuration and camera configuration
+    // It is an error for config->complete:recipes not to exist.  all programs install their
+    // master recipe files in the system:recipe location when they are built.
+    psAssert (config->complete, "base config data defined");
+    if (source & PM_RECIPE_SOURCE_SYSTEM) {
+        if (!loadRecipeSystem(&status, config, config->complete)) {
+            psError(PS_ERR_IO, false, "Failed to read recipes from system config");
+            return false;
+        }
+        psTrace ("psModules.config", 3, "read recipes from system config");
+    }
+
+    // camera-specific recipes are not required : note the absence with a message
+    // camera-specific recipes may be read for a specified camera (in pmConfigRead) or
+    // for an identified camera (in pmConfigCameraFormatFromHeader).  the second
+    // set should not override the first set
+    if (config->camera && (source & PM_RECIPE_SOURCE_CAMERA) && !(config->recipesRead & PM_RECIPE_SOURCE_CAMERA)) {
+        if (!loadRecipeCamera(&status, config, config->camera)) {
+            psError(PS_ERR_IO, false, "Failed to read recipes from camera config");
+            return false;
+        }
+        if (status) {
+            psTrace ("psModules.config", 3, "read recipes from camera config");
+        } else {
+            psLogMsg ("psModules.config", PS_LOG_DETAIL, "no recipe supplied by camera config");
+        }
+    }
+
+    // merge camera and sytem recipes, apply recipes loaded into config->arguments based on command-line arguments
+    if (config->arguments && (source & PM_RECIPE_SOURCE_CL)) {
+
+	// update the system-level recipes with the symbolically-defined recipes 
+        if (!loadRecipeSymbols(&status, config, PM_RECIPE_SOURCE_SYSTEM)) {
+            psError(PS_ERR_IO, false, "Failed to read recipes from symbolic references");
+            return false;
+        }
+        if (status) {
+            psTrace ("psModules.config", 3, "read recipes from symbolic references");
+        } else {
+            psLogMsg ("psModules.config", PS_LOG_DETAIL, "no recipe supplied by symbolic reference");
+        }
+
+	// merge the SYSTEM and CAMERA recipes
+        if (!mergeRecipeCamera(&status, config)) {
+            psError(PS_ERR_IO, false, "Failed to read recipes from symbolic references");
+            return false;
+        }
+	psLogMsg ("psModules.config", PS_LOG_DETAIL, "merged camera recipes with system recipes");
+
+	// load recipe-files specified on the command line
+        if (!loadRecipeFromArguments(&status, config)) {
+            psError(PS_ERR_IO, false, "Failed to read recipes from command-line arguments");
+            return false;
+        }
+        if (status) {
+            psTrace ("psModules.config", 3, "read recipes from command-line arguments");
+        } else {
+            psLogMsg ("psModules.config", PS_LOG_DETAIL, "no recipe supplied on command-line arguments");
+        }
+
+	// update the system-level recipes with the symbolically-defined recipes 
+        if (!loadRecipeSymbols(&status, config, PM_RECIPE_SOURCE_CAMERA)) {
+            psError(PS_ERR_IO, false, "Failed to read recipes from symbolic references");
+            return false;
+        }
+        if (status) {
+            psTrace ("psModules.config", 3, "read recipes from symbolic references");
+        } else {
+            psLogMsg ("psModules.config", PS_LOG_DETAIL, "no recipe supplied by symbolic reference");
+        }
+
+	// override any specific values with values from the command line
+        if (!loadRecipeOptions(&status, config)) {
+            psError(PS_ERR_IO, false, "Failed to read recipes from symbolic references");
+            return false;
+        }
+        if (status) {
+            psTrace ("psModules.config", 3, "read recipes from command-line arguments");
+        } else {
+            psLogMsg ("psModules.config", PS_LOG_DETAIL, "no recipe supplied on command-line arguments");
+        }
+    }
+    return true;
+}
+
+// search for options of the form -D KEY VALUE or -D RECIPE:KEY VALUE
+bool pmConfigLoadRecipeOptions (int *argc, char **argv, pmConfig *config, char *flag)
+{
+    bool success;
+    int argNum;
+
+    // save the recipe options onto config->arguments:OPTIONS
+    // increment so we can free below (is a NOP if 'options' is NULL)
+    psMetadata *options = psMetadataLookupMetadata(&success, config->arguments, "OPTIONS");
+    if (!options) {
+        options = psMetadataAlloc();
+        success = psMetadataAddPtr(config->arguments, PS_LIST_TAIL, "OPTIONS",  PS_DATA_METADATA, "Command-line options specified with -D", options);
+        assert (success); // type mismatch : OPTIONS already defined but wrong type
+        psFree (options); // drop extra reference
+    }
+
+    // -D key value (all added as string)
+    while ((argNum = psArgumentGet (*argc, argv, flag))) {
+        psArgumentRemove (argNum, argc, argv);
+
+        // do we have enough arguments?
+        if (argNum + 1 >= *argc) {
+            psError(PS_ERR_IO, true, "insufficient parameters for command-line argument -D");
+            return false;
+        }
+
+        // is a target recipe specified?
+        const char *recipeName = NULL;
+        char *key;
+        psArray *words = psStringSplitArray(argv[argNum], ":", false);
+        switch (words->n) {
+        case 1:
+            recipeName = config->defaultRecipe;
+            if (!config->defaultRecipe) {
+                psError(PS_ERR_IO, true, "syntax error in parameter: no default recipe available; must specify recipe");
+                return false;
+            }
+            key = words->data[0];
+            break;
+        case 2:
+            recipeName = words->data[0];
+            key = words->data[1];
+            break;
+        default:
+            psError(PS_ERR_IO, true, "syntax error in parameter");
+            return false;
+        }
+
+        // if this recipe is already defined in options, supplement
+        psMetadata *recipe = psMetadataLookupMetadata(&success, options, recipeName);
+        if (!recipe) {
+            recipe = psMetadataAlloc();
+            success = psMetadataAddPtr(options, PS_LIST_TAIL, recipeName,  PS_DATA_METADATA, "", recipe);
+            assert (success); // type mismatch : recipe already defined but wrong type
+            psFree (recipe); // drop extra reference
+        }
+
+        bool valid = false;
+        if (!strcmp (flag, "-D")) {
+            psMetadataAddStr (recipe, PS_LIST_TAIL, key, PS_META_REPLACE, "", argv[argNum+1]);
+            valid = true;
+        }
+        if (!strcmp (flag, "-Di")) {
+            psMetadataAddS32 (recipe, PS_LIST_TAIL, key, PS_META_REPLACE, "", atoi(argv[argNum+1]));
+            valid = true;
+        }
+        if (!strcmp (flag, "-Df")) {
+            psMetadataAddF32 (recipe, PS_LIST_TAIL, key, PS_META_REPLACE, "", atof(argv[argNum+1]));
+            valid = true;
+        }
+        if (!strcmp (flag, "-Db")) {
+            if (!strcasecmp (argv[argNum+1], "true")) {
+                psMetadataAddBool (recipe, PS_LIST_TAIL, key, PS_META_REPLACE, "", true);
+            } else {
+                psMetadataAddBool (recipe, PS_LIST_TAIL, key, PS_META_REPLACE, "", false);
+            }
+            valid = true;
+        }
+        psFree (words);
+        assert (valid);  // flag may be: -D, -Df, -Di, -Db
+
+        psArgumentRemove (argNum, argc, argv);
+        psArgumentRemove (argNum, argc, argv);
+    }
+    return true;
+}
+
+// examine command-line arguments for -recipe RECIPE SYMBOLIC-NAME or -recipe-file RECIPE FILENAME
+// in the first case, the symbolic lookup is saved on config->recipeSymbols
+//   for later interpolation (pmConfigReadRecipes with option CL)
+// in the second case, read as metadata and save on config->arguments with name = KEY
+bool pmConfigLoadRecipeArguments (int *argc, char **argv, pmConfig *config)
+{
+    bool success;
+
+    psMetadata *recipes = psMetadataLookupMetadata(&success, config->arguments, "RECIPES");
+    if (!recipes) {
+        recipes = psMetadataAlloc();
+        success = psMetadataAddPtr (config->arguments, PS_LIST_TAIL, "RECIPES",  PS_DATA_METADATA, "", recipes);
+        assert (success);
+        psFree (recipes);
+    }
+
+    // Go through the command-line arguments
+    int argNum;                         // Argument number
+
+    // -recipe-file: read recipe from file
+    while ((argNum = psArgumentGet(*argc, argv, "-recipe-file")) > 0) {
+        psArgumentRemove(argNum, argc, argv);
+        if (argNum + 1 >= *argc) {
+            psError(PS_ERR_IO, false,
+                    "-recipe command-line switch provided without the required recipe and filename\n");
+            return false;
+        }
+
+        psString recipeName = psStringCopy(argv[argNum]); // Name of the recipe
+        psArgumentRemove(argNum, argc, argv);
+        psString filename = psStringCopy(argv[argNum]); // Filename for the recipe
+        psArgumentRemove(argNum, argc, argv);
+
+        psMetadata *recipe = NULL;      // Recipe from file
+        if (!pmConfigFileRead(&recipe, filename, "recipe")) {
+            psError(PS_ERR_IO, "Error reading config file %s\n", filename);
+            psFree(recipeName);
+            psFree(filename);
+            return false;
+        }
+
+        psString comment = NULL;
+        psStringAppend(&comment, "Recipe added at command line from file %s", filename);
+        psMetadataAdd(recipes, PS_LIST_TAIL, recipeName, PS_DATA_METADATA | PS_META_REPLACE,
+                      comment, recipe);
+        psFree(comment);
+        psFree(recipe);                 // Drop reference
+        psFree(recipeName);
+        psFree(filename);
+    }
+
+    // -recipe: read recipe from symbolic link
+    while ((argNum = psArgumentGet(*argc, argv, "-recipe")) > 0) {
+        psArgumentRemove(argNum, argc, argv);
+        if (argNum + 1 >= *argc) {
+            psError(PS_ERR_IO, false,
+                    "-recipe command-line switch provided without the required recipe and source\n");
+            return false;
+        }
+
+        char *recipeName = psStringCopy(argv[argNum]); // Name of the recipe
+        psArgumentRemove(argNum, argc, argv);
+        char *recipeSource = psStringCopy(argv[argNum]); // Source of the recipe
+        psArgumentRemove(argNum, argc, argv);
+
+        // Assume it's a symbolic reference to something that's not yet read in.
+        // it will be loaded later by pmConfigReadRecipes with option CL
+        psMetadataAddStr(config->recipeSymbols, PS_LIST_TAIL, recipeName, PS_META_REPLACE, NULL, recipeSource);
+
+        psTrace ("psModules.config", 3, "read recipe %s from %s", recipeName, recipeSource);
+        psFree(recipeName);
+        psFree(recipeSource);
+    }
+
+    return true;
+}
+
+// Load the recipe files for SYSTEM : REQUIRED
+static bool loadRecipeSystem(bool *status,
+                           pmConfig *config, // The configuration into which to read the recipes
+                           psMetadata *source // The source configuration, from which to read the filenames
+    )
+{
+    assert(status);
+    assert(config);
+    *status = false;
+
+    if (!source) {
+        psError(PS_ERR_IO, true, "The system configuration has not been read --- cannot read recipes from this location.\n");
+        config->recipesRead &= ~PM_RECIPE_SOURCE_SYSTEM;
+        return false;
+    }
+
+    psMetadata *recipes = psMetadataLookupMetadata(NULL, source, "RECIPES"); // The list of recipes
+    if (!recipes) {
+        psError(PS_ERR_IO, false, "RECIPES not found in the system configuration\n");
+        return false;
+    }
+
+    // Copy contents of the filenames to config->recipes from the "RECIPES" metadata in the source.
+    // We could use psMetadataCopy for this, but it's better to check that everything's of the correct type.
+    // If it's not of the correct type, we can tell the user which file it's in, so they can find it easier.
+    psMetadataIterator *recipesIter = psMetadataIteratorAlloc(recipes, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *fileItem = NULL;    // MD item containing the filename, from recipe iteration
+    while ((fileItem = psMetadataGetAndIncrement(recipesIter))) {
+        char *recipeName = fileItem->name;
+        char *recipeFile = fileItem->data.str;
+
+        // type mismatch is a serious error
+        if (fileItem->type != PS_DATA_STRING) {
+            psAbort("%s in system configuration RECIPES is not of type STR", recipeName);
+        }
+
+        // Read the recipe file. if we fail on a file, give a warning, but continue
+        psMetadata *recipe = NULL;
+        if (!pmConfigFileRead(&recipe, recipeFile, "recipe")) {
+            psError(PS_ERR_IO, false, "Failed to read recipe file %s listed in system configuration\n", recipeFile);
+            return false;
+        }
+
+        // and the contents of this recipe file to config->recipes
+        psMetadataAdd(config->recipes, PS_LIST_TAIL, fileItem->name, PS_DATA_METADATA | PS_META_REPLACE, fileItem->comment, recipe);
+        psFree(recipe);  // Drop reference
+    }
+    psFree(recipesIter);
+    config->recipesRead |= PM_RECIPE_SOURCE_SYSTEM;
+
+    *status = true;
+    return true;
+}
+
+// Load the recipe files for a specific CAMERA.  these are saved on recipesCamera
+static bool loadRecipeCamera(bool *status, // status variable
+                             pmConfig *config, // The configuration into which to read the recipes
+                             psMetadata *source // The source configuration, from which to read the filenames
+    )
+{
+    bool success;
+
+    assert(status);
+    assert(config);
+    *status = false;
+
+    if (!source) {
+        psError(PS_ERR_IO, true, "The camera configuration has not been read --- cannot read recipes from this location.\n");
+        config->recipesRead &= ~PM_RECIPE_SOURCE_CAMERA;
+        return false;
+    }
+
+    // it is not necessary to define any local recipes in the camera config; it this entry is missing,
+    // just return true
+    psMetadata *recipes = psMetadataLookupMetadata(&success, source, "RECIPES"); // The list of recipes
+    if (!recipes) {
+        psTrace ("psModules.config", 3, "RECIPES not found in the camera configuration\n");
+        return true;
+    }
+
+    // Copy contents of the filenames to config->recipes from the "RECIPES" metadata in the source.
+    // We could use psMetadataCopy for this, but it's better to check that everything's of the correct type.
+    // If it's not of the correct type, we can tell the user which file it's in, so they can find it easier.
+    psMetadataIterator *recipesIter = psMetadataIteratorAlloc(recipes, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *fileItem = NULL;    // MD item containing the filename, from recipe iteration
+    while ((fileItem = psMetadataGetAndIncrement(recipesIter))) {
+        char *recipeName = fileItem->name;
+        char *recipeFile = fileItem->data.str;
+
+        psTrace("psModules.config", 3, "loading %s from %s within camera configuration.\n", recipeName, recipeFile);
+
+        // type mismatch is a serious error
+        if (fileItem->type != PS_DATA_STRING) {
+            psAbort("%s in camera configuration RECIPES is not of type STR", recipeName);
+        }
+
+        // Read the recipe file. if we fail on a file, give a warning, but continue
+        psMetadata *recipe = NULL;
+        if (!pmConfigFileRead(&recipe, recipeFile, "recipe")) {
+            psError(PS_ERR_IO, false, "Failed to read recipe file %s listed in camera configuration\n", recipeFile);
+            return false;
+        }
+
+        // the named recipe must exist at the system level
+        psMetadata *current = psMetadataLookupMetadata(NULL, config->recipes, recipeName);
+        if (!current) {
+            psError(PS_ERR_IO, false, "Failed to find recipe for %s in master recipe list", recipeName);
+            psFree(recipe);  // Drop reference
+            return false;
+        }
+
+        // add the contents of this recipe file to config->recipesCamera
+        psMetadataAdd(config->recipesCamera, PS_LIST_TAIL, recipeName, PS_DATA_METADATA | PS_META_REPLACE, fileItem->comment, recipe);
+        psFree(recipe);  // Drop reference
+    }
+    psFree(recipesIter);
+    config->recipesRead |= PM_RECIPE_SOURCE_CAMERA;
+    *status = true;
+    return true;
+}
+
+// Merge the CAMERA recipes into the SYSTEM recipes
+static bool mergeRecipeCamera(bool *status, // status variable
+                             pmConfig *config // The configuration into which to read the recipes
+    )
+{
+    assert(status);
+    assert(config);
+    *status = false;
+
+    // Copy contents of config->recipesCamera to config->recipes
+    // We could use psMetadataCopy for this, but it's better to check that everything's of the correct type.
+    // If it's not of the correct type, we can tell the user which file it's in, so they can find it easier.
+    psMetadataIterator *recipesIter = psMetadataIteratorAlloc(config->recipesCamera, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *folderItem = NULL;    // MD item containing the filename, from recipe iteration
+    while ((folderItem = psMetadataGetAndIncrement(recipesIter))) {
+        char *recipeName = folderItem->name;
+        psMetadata *recipe = folderItem->data.md;
+
+        psTrace("psModules.config", 3, "merging %s from camera with system recipes.\n", recipeName);
+
+        // type mismatch is a serious error
+        if (folderItem->type != PS_DATA_METADATA) {
+            psAbort("%s not of type METADATA", recipeName);
+        }
+
+        // the select the named recipe from the system level
+        psMetadata *current = psMetadataLookupMetadata(NULL, config->recipes, recipeName);
+        psAssert (current, "Failed to find recipe for %s in system recipe list", recipeName);
+
+        // update the contents of this recipe from the one on config->recipesCamera
+	if (!psMetadataUpdate(current, recipe)) {
+	    psError(PS_ERR_IO, false, "Failed to update recipe for %s from camera recipe", recipeName);
+	    return false;
+	}
+    }
+    psFree(recipesIter);
+    *status = true;
+    return true;
+}
+
+// Load the recipes from config->arguments (CL)
+// Load the recipes: each time we load a specific recipe, it overrides the metadata
+// entries for an existing recipe metadata
+static bool loadRecipeFromArguments(bool *status,
+                                    pmConfig *config // The configuration into which to read the recipes
+    )
+{
+    assert(status);
+    assert(config);
+    *status = false;
+
+    if (!config->arguments) {
+        psTrace("psModules.config", 4, "no config->arguments metadata, nothing to read here");
+        return true;
+    }
+
+    psMetadata *recipes = psMetadataLookupMetadata(NULL, config->arguments, "RECIPES"); // The list of recipes
+    if (!recipes) {
+        psTrace("psModules.config", 4, "no RECIPES in config->arguments, nothing to read here");
+        return true;
+    }
+
+    // Copy the recipes from config->arguments:RECIPES to config->recipes.  supplement existing recipes.
+    psMetadataIterator *recipesIter = psMetadataIteratorAlloc(recipes, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *item = NULL;    // MD item containing the filename, from recipe iteration
+    while ((item = psMetadataGetAndIncrement(recipesIter))) {
+        // type mismatch is a serious error
+        if (item->type != PS_DATA_METADATA) {
+            psAbort("%s in config arguments RECIPES is not of type METADATA", item->name);
+        }
+        // increment the ref counter to protect the data
+
+        psMetadata *recipe = item->data.md; // Recipe of interest
+
+        // if this named recipe exists, supplement it
+        psMetadata *current = psMetadataLookupMetadata(NULL, config->recipes, item->name);
+        if (!current) {
+            psError(PS_ERR_IO, false, "Failed to find recipe for %s in master recipe list", item->name);
+            psFree(recipe);  // Drop reference
+            return false;
+        }
+        psTrace("psModules.config", 3, "Supplementing %s from arguments.\n", item->name);
+
+        if (!psMetadataUpdate (current, recipe)) {
+            psError(PS_ERR_IO, false, "Failed to update recipe for %s from camera recipe", item->name);
+            return false;
+        }
+    }
+    psFree(recipesIter);
+    *status = true;
+    return true;
+}
+
+// Load the recipes: each time we load a specific recipe, it overrides the metadata
+// entries for an existing recipe metadata
+// The configuration into which to read the recipes
+static bool loadRecipeSymbols(bool *status, pmConfig *config, pmRecipeSource source) {
+
+    bool found = false;
+
+    assert(status);
+    assert(config);
+    *status = false;
+
+    // check to see if any symbolic names need to be resolved
+    // each entry in recipeSymbols are of the form TARGET=SOURCE where TARGET is an existing
+    // recipe MD and REF is a MD to load over that recipe
+    psMetadataIterator *iter = psMetadataIteratorAlloc(config->recipeSymbols, PS_LIST_HEAD, NULL);
+    psMetadataItem *item = NULL;  // Item containing source, from iteration
+    while ((item = psMetadataGetAndIncrement(iter))) {
+        assert(item->type == PS_DATA_STRING); // It should be this type: we put it in ourselves
+        const char *sourceName = item->data.str; // The name of the symbolic reference
+        const char *targetName = item->name;
+        psTrace("psModules.config", 3, "Supplementing %s from %s.\n", targetName, sourceName);
+
+        // the target recipe must exist; select it
+        psMetadata *targetMD = psMetadataLookupMetadata(&found, config->recipes, targetName);
+        if (!targetMD) {
+            psError(PS_ERR_IO, true, "Failed to find recipe for %s in master recipe list", targetName);
+            return false;
+        }
+
+        // search for sourceName in config->recipes (folder name is targetName)
+	psMetadata *folder = psMetadataLookupMetadata(&found, config->recipes, targetName);
+	psMetadata *sourceMD = psMetadataLookupMetadata(&found, folder, sourceName);
+
+	// if we find the desired symbolic name at this level, set the item comment to say "FOUND"
+        if (sourceMD) {
+	  if (!psMetadataUpdate(targetMD, sourceMD)) {
+            psError(PS_ERR_IO, false, "Failed to update recipe for %s from camera recipe", targetName);
+            return false;
+	  }
+	  psStringAppend (&item->comment, "(FOUND)");
+	}	  
+
+	// if we have not found it by the camera level, we have a problem
+	if (source == PM_RECIPE_SOURCE_CAMERA) {
+	  if (strstr (item->comment, "(FOUND)") == NULL) {
+	    psError(PS_ERR_IO, false, "Selected symbolic name %s does not exist in recipes", sourceName);
+	    return false;
+	  }
+	}
+    }
+    psFree(iter);
+    *status = true;
+    return true;
+}
+
+// Load the recipe options
+// Load the recipes: each time we load a specific recipe, it overrides the metadata
+// entries for an existing recipe metadata
+static bool loadRecipeOptions(bool *status,
+                              pmConfig *config // The configuration into which to read the recipes
+    )
+{
+    bool found;
+    assert(status);
+    assert(config);
+    *status = false;
+
+    if (!config->arguments) {
+        psTrace("psModules.config", 4, "no config->arguments metadata, nothing to read here");
+        return true;
+    }
+
+    psMetadata *recipes = psMetadataLookupMetadata(&found, config->arguments, "OPTIONS"); // The list of recipes
+    if (!recipes) {
+        psTrace("psModules.config", 4, "no OPTIONS in config->arguments, nothing to read here");
+        return true;
+    }
+
+    // Copy the recipes from config->arguments:OPTIONS to config->recipes.  supplement existing recipes.
+    psMetadataIterator *recipesIter = psMetadataIteratorAlloc(recipes, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *item = NULL;    // MD item containing the filename, from recipe iteration
+    while ((item = psMetadataGetAndIncrement(recipesIter))) {
+        char *recipeName = item->name;
+        psMetadata *recipe = item->data.md;
+
+        // type mismatch is a serious error
+        if (item->type != PS_DATA_METADATA) {
+            psAbort("%s in config arguments OPTIONS is not of type METADATA", recipeName);
+        }
+
+        // if this named recipe exists, supplement it
+        psMetadata *current = psMetadataLookupMetadata(NULL, config->recipes, recipeName);
+        if (!current) {
+            psError(PS_ERR_IO, false, "Selected recipe %s is not found in camera recipe", recipeName);
+            return false;
+        }
+        if (!psMetadataUpdate (current, recipe)) {
+            psError(PS_ERR_IO, false, "Failed to update recipe for %s from camera recipe", recipeName);
+            return false;
+        }
+    }
+    psFree(recipesIter);
+    *status = true;
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfigRecipes.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfigRecipes.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmConfigRecipes.h	(revision 22157)
@@ -0,0 +1,33 @@
+/*  @file pmConfigRecipes.h
+ *  @brief Configuration Recipe functions
+ * 
+ *  @author ?, MHPCC
+ *  @author Paul Price, IfA
+ *  @author Eugene Magnier, IfA
+ * 
+ *  @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-04-19 02:10:12 $
+ *  Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_CONFIG_RECIPES_H
+#define PM_CONFIG_RECIPES_H
+
+/// @addtogroup Config Configuration System
+/// @{
+
+/// Read recipes
+///
+/// Attempt to read recipes from the sources that are available but have not already been read.  Having read a
+/// recipe, attempt to resolve symbolic links that were specified on the command line.
+bool pmConfigReadRecipes(pmConfig *config, ///< Configuration
+                         pmRecipeSource source ///< desired sources for recipes
+                        );
+
+
+bool pmConfigLoadRecipeArguments (int *argc, char **argv, pmConfig *config);
+bool pmConfigLoadRecipeOptions (int *argc, char **argv, pmConfig *config, char *flag);
+psMetadata *pmConfigRecipeOptions (pmConfig *config, char *recipe);
+
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmErrorCodes.c.in
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmErrorCodes.c.in	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmErrorCodes.c.in	(revision 22157)
@@ -0,0 +1,25 @@
+/*
+ * The line
+    { PM_ERR_$X{ErrorCode}, "$X{ErrorDescription}"},
+ * (without the Xs)
+ * will be replaced by values from errorCodes.dat
+ */
+#include <pslib.h>
+#include "pmErrorCodes.h"
+
+void pmErrorRegister(void)
+{
+    static psErrorDescription errors[] = {
+       { PM_ERR_BASE, "First value we use; lower values belong to psLib" },
+       { PM_ERR_${ErrorCode}, "${ErrorDescription}"},
+    };
+    static int nerror = PM_ERR_NERROR - PM_ERR_BASE; // number of values in enum
+
+    for (int i = 0; i < nerror; i++) {
+       psErrorDescription *tmp = psAlloc(sizeof(psErrorDescription));
+       *tmp = errors[i];
+       psErrorRegister(tmp, 1);
+       psFree(tmp);			/* it's on the internal list */
+    }
+    nerror = 0;			                // don't register more than once
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmErrorCodes.dat
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmErrorCodes.dat	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmErrorCodes.dat	(revision 22157)
@@ -0,0 +1,18 @@
+#
+# This file is used to generate pmErrorCodes.h
+#
+UNKNOWN			Unknown psModules error code
+PHOTOM			Problem in photometry
+PSF			Problem in PSF
+ASTROM			Problem in astrometry
+CAMERA			Problem in camera
+CONCEPTS		Problem in concepts
+IMCOMBINE		Problem in imcombine
+OBJECTS			Problem in objects
+SKY			Problem in sky
+# these errors correspond to standard exit conditions
+ARGUMENTS               Incorrect arguments
+SYS                     System error
+CONFIG                  Problem in configure files
+PROG                    Programming error
+DATA                    invalid data
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmErrorCodes.h.in
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmErrorCodes.h.in	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmErrorCodes.h.in	(revision 22157)
@@ -0,0 +1,18 @@
+#if !defined(PM_ERROR_CODES_H)
+#define PM_ERROR_CODES_H
+/*
+ * The line
+ *  PM_ERR_$X{ErrorCode},
+ * (without the X)
+ *
+ * will be replaced by values from errorCodes.dat
+ */
+typedef enum {
+    PM_ERR_BASE = 1200,
+    PM_ERR_${ErrorCode},
+    PM_ERR_NERROR
+} pmErrorCode;
+
+void pmErrorRegister(void);
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmVersion.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmVersion.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmVersion.c	(revision 22157)
@@ -0,0 +1,28 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmVersion.h"
+
+static const char *cvsTag = "$Name: not supported by cvs2svn $";// CVS tag name
+
+psString psModulesVersion(void)
+{
+    psString version = NULL;            // Version, to return
+    psStringAppend(&version, "%s-%s",PACKAGE_NAME,PACKAGE_VERSION);
+    return version;
+}
+
+psString psModulesVersionLong(void)
+{
+    psString version = psModulesVersion(); // Version, to return
+    psString tag = psStringStripCVS(cvsTag, "Name"); // CVS tag
+
+    psStringAppend(&version, " (cvs tag %s) %s, %s", tag, __DATE__, __TIME__);
+
+    psFree(tag);
+    return version;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmVersion.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmVersion.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/config/pmVersion.h	(revision 22157)
@@ -0,0 +1,37 @@
+/*  @file pmVersion.h
+ *  @brief Version functions
+ * 
+ *  @author Paul Price, IfA
+ *  @author Eugene Magnier, IfA
+ * 
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-03-30 21:12:56 $
+ *  Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_VERSION_H
+#define PM_VERSION_H
+
+/// @addtogroup Config Configuration System
+/// @{
+
+/** Get current psModules version
+ *
+ *  Returns the current psModules version name as a string.
+ *
+ *  @return psString: String with version name.
+ */
+psString psModulesVersion(
+    void
+);
+
+/** Get current psModules version (full identification)
+ *
+ *  Returns the current psModules version name and other information identifying the compilation.
+ *
+ *  @return psString: String with identity.
+ */
+psString psModulesVersionLong(void);
+
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/.cvsignore	(revision 22157)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.la
+*.lo
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/Makefile.am	(revision 22157)
@@ -0,0 +1,35 @@
+noinst_LTLIBRARIES = libpsmodulesdetrend.la
+
+libpsmodulesdetrend_la_CPPFLAGS = $(SRCINC) $(PSMODULES_CFLAGS)
+libpsmodulesdetrend_la_LDFLAGS  = -release $(PACKAGE_VERSION)
+libpsmodulesdetrend_la_SOURCES  = \
+	pmFlatField.c \
+	pmFlatNormalize.c \
+	pmFringeStats.c \
+	pmMaskBadPixels.c \
+	pmNonLinear.c \
+	pmBias.c \
+	pmOverscan.c \
+	pmDetrendDB.c \
+	pmShutterCorrection.c \
+	pmShifts.c \
+	pmDark.c
+
+#	pmSkySubtract.c
+
+pkginclude_HEADERS = \
+	pmFlatField.h \
+	pmFlatNormalize.h \
+	pmFringeStats.h \
+	pmMaskBadPixels.h \
+	pmNonLinear.h \
+	pmBias.h \
+	pmOverscan.h \
+	pmDetrendDB.h \
+	pmShutterCorrection.h \
+	pmShifts.h \
+	pmDark.h
+
+#	pmSkySubtract.h
+
+CLEANFILES = *~
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmBias.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmBias.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmBias.c	(revision 22157)
@@ -0,0 +1,194 @@
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmHDUUtils.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPACalibration.h"
+
+#include "pmOverscan.h"
+#include "pmBias.h"
+
+// pmBiasSubtractFrame(): this routine will take as input a readout for the input image and a readout for the bias
+// image.  The bias image is subtracted in place from the input image.
+bool pmBiasSubtractFrame(pmReadout *in, // Input readout
+                          const pmReadout *sub, // Readout to be subtracted from input
+                          float scale   // Scale to apply before subtracting
+                         )
+{
+    PS_ASSERT_PTR_NON_NULL(in, false);
+    PS_ASSERT_PTR_NON_NULL(in->image, false);
+    PS_ASSERT_IMAGE_TYPE(in->image, PS_TYPE_F32, false);
+    PS_ASSERT_IMAGE_NON_EMPTY(in->image, false);
+    PS_ASSERT_PTR_NON_NULL(sub, false);
+    PS_ASSERT_PTR_NON_NULL(sub->image, false);
+    PS_ASSERT_IMAGE_TYPE(sub->image, PS_TYPE_F32, false);
+    PS_ASSERT_IMAGE_NON_EMPTY(sub->image, false);
+    PS_ASSERT_IMAGES_SIZE_EQUAL(in->image, sub->image, false);
+
+    psImage *inImage  = in->image;      // The input image
+    psImage *inMask   = in->mask;       // The input mask
+    psImage *subImage = sub->image;     // The image to be subtracted
+    psImage *subMask  = sub->mask;      // The mask for the subtraction image
+
+    // Check parities
+    int xIpar = psMetadataLookupS32(NULL, in->parent->concepts, "CELL.XPARITY");
+    int xSpar = psMetadataLookupS32(NULL, sub->parent->concepts, "CELL.XPARITY");
+    if (xIpar != xSpar) {
+        psError(PS_ERR_UNKNOWN, true, "images for subtraction do not have the same "
+                "CELL.XPARITY (%d vs %d).\n    pmSubtractBias must be upgraded to handle this situation\n",
+                xIpar, xSpar);
+        return false;
+    }
+
+    int yIpar = psMetadataLookupS32(NULL, in->parent->concepts, "CELL.YPARITY");
+    int ySpar = psMetadataLookupS32(NULL, sub->parent->concepts, "CELL.YPARITY");
+    if (yIpar != ySpar) {
+        psError(PS_ERR_UNKNOWN, true, "images for subtraction do not have the same "
+                "CELL.YPARITY (%d vs %d).\n    pmSubtractBias must be upgraded to handle this situation\n",
+                xIpar, xSpar);
+        return false;
+    }
+
+    // Offsets of the cells
+    int x0in = psMetadataLookupS32(NULL, in->parent->concepts, "CELL.X0");
+    int y0in = psMetadataLookupS32(NULL, in->parent->concepts, "CELL.Y0");
+    int x0sub = psMetadataLookupS32(NULL, sub->parent->concepts, "CELL.X0");
+    int y0sub = psMetadataLookupS32(NULL, sub->parent->concepts, "CELL.Y0");
+
+    if ((inImage->numCols + x0in - x0sub) > subImage->numCols) {
+        psError(PS_ERR_UNKNOWN, true, "Image does not have enough columns for subtraction (%d vs %d).\n",
+                inImage->numCols + x0in - x0sub, subImage->numCols);
+        return false;
+    }
+    if ((inImage->numRows + y0in - y0sub) > subImage->numRows) {
+        psError(PS_ERR_UNKNOWN, true, "Image does not have enough rows for subtraction (%d vs %d).\n",
+                inImage->numRows + y0in - y0sub, subImage->numRows);
+        return false;
+    }
+
+    if (scale == 1.0) {
+        for (int i = 0; i < inImage->numRows; i++) {
+            for (int j = 0; j < inImage->numCols; j++) {
+                inImage->data.F32[i][j] -= subImage->data.F32[i+y0in-y0sub][j+x0in-x0sub];
+                if (inMask && subMask) {
+                    inMask->data.U8[i][j] |= subMask->data.U8[i+y0in-y0sub][j+x0in-x0sub];
+                }
+            }
+        }
+    } else {
+        for (int i = 0; i < inImage->numRows; i++) {
+            for (int j = 0; j < inImage->numCols; j++) {
+                inImage->data.F32[i][j] -= subImage->data.F32[i+y0in-y0sub][j+x0in-x0sub] * scale;
+                if (inMask && subMask) {
+                    inMask->data.U8[i][j] |= subMask->data.U8[i+y0in-y0sub][j+x0in-x0sub];
+                }
+            }
+        }
+    }
+
+    return true;
+}
+
+bool pmBiasSubtract(pmReadout *in, pmOverscanOptions *overscanOpts,
+                    const pmReadout *bias, const pmReadout *dark, const pmFPAview *view)
+{
+    psTrace("psModules.detrend", 4,
+            "---- pmBiasSubtract() begin ----\n");
+
+    PS_ASSERT_PTR_NON_NULL(in, false);
+    PS_ASSERT_IMAGE_NON_NULL(in->image, false);
+    PS_ASSERT_IMAGE_TYPE(in->image, PS_TYPE_F32, false);
+    if (bias) {
+        PS_ASSERT_IMAGE_NON_NULL(bias->image, false);
+        PS_ASSERT_IMAGE_TYPE(bias->image, PS_TYPE_F32, false);
+    }
+    if (dark) {
+        psWarning("Dark processing is now available using pmDark --- perhaps you should use that instead?");
+        PS_ASSERT_PTR_NON_NULL(view, false);
+        PS_ASSERT_IMAGE_NON_NULL(dark->image, false);
+        PS_ASSERT_IMAGE_TYPE(dark->image, PS_TYPE_F32, false);
+    }
+
+    pmHDU *hdu = pmHDUFromReadout(in);  // HDU of interest
+
+    if (!pmOverscanSubtract (in, overscanOpts)) {
+        return false;
+    }
+
+    // Bias frame subtraction
+    if (bias) {
+        psVector *md5 = psImageMD5(bias->image); // md5 hash
+        psString md5string = psMD5toString(md5); // String
+        psFree(md5);
+        psStringPrepend(&md5string, "BIAS image MD5: ");
+        psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK,
+                         md5string, "");
+        psFree(md5string);
+
+        if (!pmBiasSubtractFrame(in, bias, 1.0)) {
+            return false;
+        }
+    }
+
+    if (dark) {
+        // Get the scaling
+        float inTime = psMetadataLookupF32(NULL, in->parent->concepts, "CELL.DARKTIME");
+        float darkTime = psMetadataLookupF32(NULL, dark->parent->concepts, "CELL.DARKTIME");
+        if (isnan(inTime) || isnan(darkTime)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to determine dark scaling.");
+            return false;
+        }
+
+        float darkNorm = 1.0;
+        float inNorm = pmFPADarkNorm(in->parent->parent->parent, view, inTime);
+
+        // if we have a normalized dark exposure, we simply multiply the master by inNorm.  if
+        // we do not have a normalized exposure, we have to scale the master as well.  XXX do
+        // we need to explicitly identify the master as normalized?
+
+        if (darkTime != 1.0) {
+            darkNorm = pmFPADarkNorm(dark->parent->parent->parent, view, darkTime);
+        }
+
+        if (isnan(inNorm) || isnan(darkNorm)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to determine dark normalisations.");
+            return false;
+        }
+
+        float scale = inNorm / darkNorm;// Scaling to apply to dark exposure
+
+        psVector *md5 = psImageMD5(dark->image); // md5 hash
+        psString md5string = psMD5toString(md5); // String
+        psFree(md5);
+        psStringPrepend(&md5string, "DARK image (scale %.3f) MD5: ", scale);
+        psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK,
+                         md5string, "");
+        psFree(md5string);
+
+        if (!pmBiasSubtractFrame(in, dark, scale)) {
+            return false;
+        }
+    }
+
+    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now, used for reporting
+    psString timeString = psTimeToISO(time); // String with time
+    psFree(time);
+    psStringPrepend(&timeString, "Overscan/bias/dark processing completed at ");
+    psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK,
+                     timeString, "");
+    psFree(timeString);
+
+
+    return true;
+}
+
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmBias.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmBias.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmBias.h	(revision 22157)
@@ -0,0 +1,37 @@
+/* @file pmBias.h
+ * @brief Subtract the overscan, bias and dark
+ *
+ * @author George Gusciora, MHPCC
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.7 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-08-15 20:21:18 $
+ * Copyright 2004--2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_BIAS_H
+#define PM_BIAS_H
+
+/// @addtogroup detrend Detrend Creation and Application
+/// @{
+
+/// Subtract the overscan, bias and/or dark
+///
+/// Subtracts the overscan, as measured from the bias member of the input readout (if options are non-NULL),
+/// bias (if non-NULL) and dark (if non-NULL) scaled by the CELL.DARKTIME concept.
+bool pmBiasSubtract(pmReadout *in,      ///< Input readout, to be overscan/bias/dark corrected
+                    pmOverscanOptions *overscanOpts, ///< Options for overscan subtraction, or NULL
+                    const pmReadout *bias, ///< Bias to subtract, or NULL
+                    const pmReadout *dark, ///< Dark to scale and subtract, or NULL
+                    const pmFPAview *view ///< View for readout of interest
+                   );
+
+// pmBiasSubtractFrame(): this routine will take as input a readout for the input image and a readout for the bias
+// image.  The bias image is subtracted in place from the input image.
+bool pmBiasSubtractFrame(pmReadout *in, // Input readout
+                          const pmReadout *sub, // Readout to be subtracted from input
+                          float scale   // Scale to apply before subtracting
+    );
+
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmDark.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmDark.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmDark.c	(revision 22157)
@@ -0,0 +1,786 @@
+#include <stdio.h>
+#include <pslib.h>
+#include <string.h>
+
+#include "psPolynomialMD.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmHDUUtils.h"
+#include "pmFPARead.h"
+#include "pmFPAWrite.h"
+#include "pmReadoutStack.h"
+
+#include "pmDark.h"
+
+#define PM_DARK_FITS_EXTNAME "PS_DARK"  // FITS extension name for ordinates table
+#define PM_DARK_FITS_NAME    "NAME"     // Column name for concept name in ordinates table
+#define PM_DARK_FITS_ORDER   "ORDER"    // Column name for polynomial order in ordinates table
+#define PM_DARK_FITS_SCALE   "SCALE"    // Column name for scaling option in ordinates table
+#define PM_DARK_FITS_MIN     "MIN"      // Column name for minimum value in ordinates table
+#define PM_DARK_FITS_MAX     "MAX"      // Column name for maximum value in ordinates table
+
+
+
+// Look up the value of an ordinate in a readout
+static bool ordinateLookup(float *value, // Value of ordinate, to return
+                           bool *inRange, // is value within min : max range?
+                           const char *name, // Name of ordinate (concept name)
+                           bool scale,  // Scale the value?
+                           float min, float max, // Minimum and maximum values for scaling
+                           const pmReadout *ro // Readout of interest
+                           )
+{
+    assert(value);
+    assert(name);
+    assert(ro);
+
+    *inRange = true;
+
+    pmCell *cell = ro->parent; // Parent cell
+    if (!cell) {
+        return false;
+    }
+    psMetadataItem *item = psMetadataLookup(cell->concepts, name);
+    if (!item) {
+        pmChip *chip = cell->parent; // Parent chip
+        if (!chip) {
+            return false;
+        }
+        item = psMetadataLookup(chip->concepts, name);
+        if (!item) {
+            pmFPA *fpa = chip->parent; // Parent FPA
+            if (!fpa) {
+                return false;
+            }
+            item = psMetadataLookup(fpa->concepts, name);
+            if (!item) {
+                psWarning("Unable to find concept %s in readout", name);
+                return false;
+            }
+        }
+    }
+
+    *value = psMetadataItemParseF32(item); // Value of interest
+    if (!isfinite(*value)) {
+        psWarning("Non-finite value (%f) of concept %s in readout", *value, name);
+        return false;
+    }
+    if (scale) {
+        if (*value < min || *value > max) {
+            psWarning("Value of concept %s (%f) outside range (%f:%f)", name, *value, min, max);
+            *inRange = false;
+        }
+        *value = 2.0 * (*value - min) / (max - min) - 1.0;
+    }
+
+    return true;
+}
+
+static void darkOrdinateFree(pmDarkOrdinate *ord)
+{
+    psFree(ord->name);
+    return;
+}
+
+pmDarkOrdinate *pmDarkOrdinateAlloc(const char *name, int order)
+{
+    pmDarkOrdinate *ord = psAlloc(sizeof(pmDarkOrdinate)); // Ordinate data, to return
+    psMemSetDeallocator(ord, (psFreeFunc)darkOrdinateFree);
+
+    ord->name = psStringCopy(name);
+    ord->order = order;
+    ord->scale = false;
+    ord->min = NAN;
+    ord->max = NAN;
+
+    return ord;
+}
+
+
+bool pmDarkCombine(pmCell *output, const psArray *inputs, psArray *ordinates, const char *normConcept,
+                   int iter, float rej, psMaskType maskVal)
+{
+    PS_ASSERT_PTR_NON_NULL(output, false);
+    PS_ASSERT_ARRAY_NON_NULL(inputs, false);
+    PS_ASSERT_ARRAY_NON_NULL(ordinates, false);
+    PS_ASSERT_INT_NONNEGATIVE(iter, false);
+    PS_ASSERT_FLOAT_LARGER_THAN(rej, 0.0, false);
+
+    bool inRange = true;
+
+    // Extract fitting orders
+    int numOrdinates = ordinates->n;    // Number of ordinates
+    int numInputs = inputs->n;          // Number of inputs
+    int numBadInputs = 0;               // Number of bad inputs
+    psArray *values = psArrayAlloc(numInputs);
+    psVector *roMask = psVectorAlloc(numInputs, PS_TYPE_U8); // Mask for bad readouts
+    psVectorInit(roMask, 0);
+    psVector *norm = normConcept ? psVectorAlloc(numInputs, PS_TYPE_F32) : NULL; // Normalisations for each
+    for (int i = 0; i < numInputs; i++) {
+        values->data[i] = psVectorAlloc(numOrdinates, PS_TYPE_F32);
+        if (norm) {
+            pmReadout *ro = inputs->data[i]; // Readout of interest
+            float normValue;            // Normalisation value
+            if (!ordinateLookup(&normValue, &inRange, normConcept, false, NAN, NAN, ro)) {
+                psWarning("Unable to find value of %s for readout %d", normConcept, i);
+                roMask->data.U8[i] = 0xff;
+                norm->data.F32[i] = NAN;
+                numBadInputs++;
+                continue;
+            }
+            if (normValue == 0.0) {
+                psWarning("Normalisation value (%s) for readout %d is zero", normConcept, i);
+                roMask->data.U8[i] = 0xff;
+                norm->data.F32[i] = NAN;
+                numBadInputs++;
+                continue;
+            }
+            norm->data.F32[i] = 1.0 / normValue;
+        }
+    }
+    psVector *orders = psVectorAlloc(numOrdinates, PS_TYPE_U8); // Orders for each concept
+    for (int i = 0; i < numOrdinates; i++) {
+        pmDarkOrdinate *ord = ordinates->data[i]; // Ordinate information
+        if (ord->order <= 0) {
+            psError(PS_ERR_UNKNOWN, true, "Bad order for concept %s (%d) --- ignored", ord->name, ord->order);
+            psFree(values);
+            psFree(roMask);
+            psFree(orders);
+            return false;
+        }
+        orders->data.U8[i] = ord->order;
+
+        // Mask the readout and move on
+        #define MASK_READOUT_VALUE { \
+            roMask->data.U8[j] = 0xff; \
+            val->data.F32[i] = NAN; \
+            numBadInputs++; \
+            continue; \
+        }
+
+        for (int j = 0; j < numInputs; j++) {
+            psVector *val = values->data[j]; // Value vector for readout
+            if (roMask->data.U8[j]) {
+                val->data.F32[i] = NAN;
+                continue;
+            }
+
+            pmReadout *ro = inputs->data[j]; // Readout of interest
+            float value = NAN;          // Value of ordinate
+            if (!ordinateLookup(&value, &inRange, ord->name, ord->scale, ord->min, ord->max, ro)) {
+                roMask->data.U8[j] = 0xff;
+                val->data.F32[i] = NAN;
+                numBadInputs++;
+                continue;
+            }
+            if (!inRange) {
+                roMask->data.U8[j] = 0xff;
+                val->data.F32[i] = NAN;
+                numBadInputs++;
+                continue;
+            }
+            val->data.F32[i] = value;
+        }
+    }
+
+    if (psTraceGetLevel("psModules.detrend") > 9) {
+        for (int i = 0; i < numInputs; i++) {
+            psVector *val = values->data[i];
+            (void) val; // avoid unused variable message when tracing is compiled out
+            for (int j = 0; j < numOrdinates; j++) {
+                psTrace("psModules.detrend", 9, "Image %d, ordinate %d: %f\n", i, j, val->data.F32[j]);
+            }
+        }
+    }
+
+    psPolynomialMD *poly = psPolynomialMDAlloc(orders); // Polynomial for fitting
+    psFree(orders);
+    int numTerms = poly->coeff->n;      // Number of terms in polynomial
+    if (numTerms > numInputs - numBadInputs) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Insufficient inputs (%d) to fit polynomial terms (%d).",
+                numInputs - numBadInputs, numTerms);
+        psFree(values);
+        psFree(roMask);
+        psFree(norm);
+        return false;
+    }
+
+    // Set up output
+    int minInputCols, maxInputCols, minInputRows, maxInputRows; // Smallest and largest values to combine
+    int xSize, ySize;                   // Size of the output image
+    if (!pmReadoutStackValidate(&minInputCols, &maxInputCols, &minInputRows, &maxInputRows, &xSize, &ySize,
+                                inputs)) {
+        psError(PS_ERR_UNKNOWN, false, "No valid input readouts.");
+        psFree(values);
+        psFree(roMask);
+        psFree(norm);
+        return false;
+    }
+    if (output->readouts->n != numTerms) {
+        output->readouts = psArrayRealloc(output->readouts, numTerms);
+    }
+    int outRow0 = 0, outCol0 = 0;       // Output row0 and col0
+    for (int i = 0; i < numTerms; i++) {
+        pmReadout *ro = output->readouts->data[i]; // Readout to update
+        if (!ro) {
+            ro = output->readouts->data[i] = pmReadoutAlloc(output);
+        }
+
+        pmReadoutUpdateSize(ro, minInputCols, minInputRows, xSize, ySize, false, false, 0);
+        if (i == 0) {
+            outRow0 = ro->row0;
+            outCol0 = ro->col0;
+        } else {
+            assert(ro->row0 == outRow0);
+            assert(ro->col0 == outCol0);
+        }
+    }
+
+    psImage *counts = pmReadoutAnalysisImage(output->readouts->data[0], PM_READOUT_STACK_ANALYSIS_COUNT,
+                                             xSize, ySize, PS_TYPE_U16, 0);
+    if (!counts) {
+        return false;
+    }
+    psImage *sigma = pmReadoutAnalysisImage(output->readouts->data[0], PM_READOUT_STACK_ANALYSIS_SIGMA,
+                                            xSize, ySize, PS_TYPE_F32, NAN);
+    if (!sigma) {
+        psFree(counts);
+        return false;
+    }
+
+    // Iterate over pixels, fitting polynomial
+    psVector *pixels = psVectorAlloc(numInputs, PS_TYPE_F32); // Stack of pixels
+    psVector *mask   = psVectorAlloc(numInputs, PS_TYPE_MASK); // Mask for stack
+    for (int i = minInputRows; i < maxInputRows; i++) {
+        int yOut = i - outRow0; // y position on output readout
+#ifdef SHOW_BUSY
+        if (psTraceGetLevel("psModules.detrend") > 9) {
+            printf("Processing row %d\r", i);
+            fflush(stdout);
+        }
+#endif
+
+        for (int j = minInputCols; j < maxInputCols; j++) {
+            int xOut = j - outCol0; // x position on output readout
+
+            psVectorInit(mask, 0);
+            for (int r = 0; r < inputs->n; r++) {
+                if (roMask->data.U8[r]) {
+                    mask->data.PS_TYPE_MASK_DATA[r] = 0xff;
+                    continue;
+                }
+                pmReadout *readout = inputs->data[r]; // Input readout
+                int yIn = i - readout->row0; // y position on input readout
+                int xIn = j - readout->col0; // x position on input readout
+
+                pixels->data.F32[r] = readout->image->data.F32[yIn][xIn];
+                if (norm) {
+                    pixels->data.F32[r] *= norm->data.F32[r];
+                }
+                if (readout->mask) {
+                    mask->data.PS_TYPE_MASK_DATA[r] = readout->mask->data.PS_TYPE_MASK_DATA[yIn][xIn];
+                }
+
+            }
+
+            // XXX test
+            if (0 && (i == 377) && (j == 80)) {
+                FILE *f = fopen ("test.dat", "w");
+                for (int r = 0; r < inputs->n; r++) {
+                    fprintf (f, "%d %d  %d  ", i, j, mask->data.U8[r]);
+                    psVector *value = values->data[r];
+                    for (int tmpj = 0; tmpj < value->n; tmpj++) {
+                        fprintf (f, "%f ", value->data.F32[tmpj]);
+                    }
+                    fprintf (f, "%f\n", pixels->data.F32[r]);
+                }
+                fclose (f);
+            }
+
+            if (!psPolynomialMDClipFit(poly, pixels, NULL, mask, maskVal, values, iter, rej)) {
+                psErrorClear();         // Nothing we can do about it
+                psVectorInit(poly->coeff, NAN);
+            }
+            for (int k = 0; k < numTerms; k++) {
+                pmReadout *ro = output->readouts->data[k]; // Readout of interest
+                ro->image->data.F32[yOut][xOut] = poly->coeff->data.F64[k];
+            }
+            counts->data.U16[yOut][xOut] = poly->numFit;
+            sigma->data.F32[yOut][xOut] = poly->stdevFit;
+        }
+    }
+
+    psFree(norm);
+    psFree(roMask);
+    psFree(poly);
+    psFree(pixels);
+    psFree(mask);
+    psFree(values);
+
+    psMetadataAddPtr(output->analysis, PS_LIST_TAIL, PM_DARK_ANALYSIS_ORDINATES,
+                     PS_DATA_ARRAY | PS_META_REPLACE, "Dark ordinates", ordinates);
+    psMetadataAddStr(output->analysis, PS_LIST_TAIL, PM_DARK_ANALYSIS_NORM,
+                     PS_META_REPLACE, "Dark normalisation", normConcept);
+
+    for (int i = 0; i < numTerms; i++) {
+        pmReadout *ro = output->readouts->data[i]; // Readout to update
+        ro->data_exists = true;
+    }
+    output->data_exists = true;
+    output->parent->data_exists = true;
+
+    return true;
+}
+
+
+
+bool pmDarkApply(pmReadout *readout, const pmCell *dark, psMaskType bad)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(dark, false);
+    PS_ASSERT_IMAGE_NON_NULL(readout->image, false);
+    PS_ASSERT_IMAGE_TYPE(readout->image, PS_TYPE_F32, false);
+    int numCols = readout->image->numCols, numRows = readout->image->numRows; // Size of image
+    if (readout->mask) {
+        PS_ASSERT_IMAGE_NON_NULL(readout->mask, false);
+        PS_ASSERT_IMAGES_SIZE_EQUAL(readout->mask, readout->image, false);
+        PS_ASSERT_IMAGE_TYPE(readout->mask, PS_TYPE_MASK, false);
+    }
+    int numTerms = dark->readouts->n;   // Number of polynomial terms
+    for (int i = 0; i < numTerms; i++) {
+        pmReadout *ro = dark->readouts->data[i]; // Dark readout
+        PS_ASSERT_PTR_NON_NULL(ro, false);
+        PS_ASSERT_IMAGE_NON_NULL(ro->image, false);
+        PS_ASSERT_IMAGE_SIZE(ro->image, numCols, numRows, false);
+        PS_ASSERT_IMAGE_TYPE(ro->image, PS_TYPE_F32, false);
+    }
+    psArray *ordinates = psMetadataLookupPtr(NULL, dark->analysis, PM_DARK_ANALYSIS_ORDINATES); // Ordinates
+    if (!ordinates) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find dark ordinates.");
+        return false;
+    }
+    bool mdok;                          // Status of MD lookup
+    psString normConcept = psMetadataLookupStr(&mdok, dark->analysis, PM_DARK_ANALYSIS_NORM); // Normalisation
+    bool inRange = false;
+
+    int numOrdinates = ordinates->n;    // Number of ordinates
+    psVector *values = psVectorAlloc(numOrdinates, PS_TYPE_F32); // Values of ordinates
+    for (int i = 0; i < numOrdinates; i++) {
+        pmDarkOrdinate *ord = ordinates->data[i]; // Ordinate of interest
+        float value = NAN;              // Value for ordinate
+        if (!ordinateLookup(&value, &inRange, ord->name, ord->scale, ord->min, ord->max, readout)) {
+            psError(PS_ERR_UNKNOWN, true, "Unable to find value for %s", ord->name);
+            psFree(values);
+            return false;
+        }
+        values->data.F32[i] = value;
+    }
+    float norm = NAN;                   // Normalisation value
+    bool doNorm = false;                // Do normalisation?
+    if (normConcept && strlen(normConcept) > 0) {
+        if (!ordinateLookup(&norm, &inRange, normConcept, false, NAN, NAN, readout)) {
+            psError(PS_ERR_UNKNOWN, true, "Unable to find value for %s", normConcept);
+            psFree(values);
+            return false;
+        }
+        doNorm = true;
+    }
+
+    psVector *orders = psVectorAlloc(numOrdinates, PS_TYPE_U8); // Order for each polynomial
+    for (int i = 0; i < numOrdinates; i++) {
+        pmDarkOrdinate *ord = ordinates->data[i]; // Ordinate information
+        if (ord->order <= 0) {
+            psError(PS_ERR_UNKNOWN, true, "Bad order for concept %s (%d) --- ignored", ord->name, ord->order);
+            psFree(values);
+            psFree(orders);
+            return false;
+        }
+        orders->data.U8[i] = ord->order;
+    }
+
+    psPolynomialMD *poly = psPolynomialMDAlloc(orders); // Polynomial to apply
+    psFree(orders);
+
+    for (int y = 0; y < numRows; y++) {
+        for (int x = 0; x < numCols; x++) {
+            for (int i = 0; i < numTerms; i++) {
+                pmReadout *ro = dark->readouts->data[i]; // Dark readout
+                poly->coeff->data.F64[i] = ro->image->data.F32[y][x];
+            }
+            float value = psPolynomialMDEval(poly, values); // Value of dark current
+            if (doNorm) {
+                value *= norm;
+            }
+            readout->image->data.F32[y][x] -= value;
+            if (readout->mask && !isfinite(value)) {
+                readout->mask->data.PS_TYPE_MASK_DATA[y][x] = bad;
+            }
+        }
+    }
+
+    psFree(poly);
+    psFree(values);
+
+    return true;
+}
+
+bool pmFPAWriteDark(pmFPA *fpa, psFits *fits, pmConfig *config, bool blank, bool recurse)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    if (!pmFPAWrite(fpa, fits, config, blank, recurse)) {
+        psError(PS_ERR_IO, false, "Unable to write FPA dark images");
+        return false;
+    }
+
+    pmHDU *hdu = fpa->hdu;
+    if (blank || !hdu) {
+        // No more to do
+        return true;
+    }
+
+    psArray *ordinates = NULL;      // Dark ordinates, to write
+    const char *normConcept = NULL;     // Normalisation concept
+    psArray *chips = fpa->chips;    // Component chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i]; // Chip of interest
+        psArray *cells = chip->cells; // Component cells
+        for (int j = 0; j < cells->n; j++) {
+            pmCell *cell = cells->data[j]; // Cell of interest
+            bool mdok;              // Status of MD lookup
+            psArray *newOrd = psMetadataLookupPtr(&mdok, cell->analysis, PM_DARK_ANALYSIS_ORDINATES); // Ords
+            if (!mdok) {
+                continue;
+            }
+            psString newNorm = psMetadataLookupPtr(&mdok, cell->analysis, PM_DARK_ANALYSIS_NORM); // Norm
+            if (ordinates) {
+                if (newOrd != ordinates) {
+                    psError(PS_ERR_UNKNOWN, true, "Dark ordinates differ across cells.");
+                    return false;
+                }
+                if ((normConcept && (!newNorm || strcmp(normConcept, newNorm) != 0)) ||
+                    (!normConcept && newNorm)) {
+                    psError(PS_ERR_UNKNOWN, true, "Dark normalisations differ across cells.");
+                    return false;
+                }
+            } else {
+                ordinates = newOrd;
+                normConcept = newNorm;
+            }
+        }
+    }
+
+    if (!ordinates) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find dark ordinates.");
+        return false;
+    }
+
+    return pmDarkWrite(fits, hdu->header, ordinates, normConcept);
+}
+
+bool pmChipWriteDark(pmChip *chip, psFits *fits, pmConfig *config, bool blank, bool recurse)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    if (!pmChipWrite(chip, fits, config, blank, recurse)) {
+        psError(PS_ERR_IO, false, "Unable to write chip dark images");
+        return false;
+    }
+
+    pmHDU *hdu = pmHDUFromChip(chip);
+    if (blank || !hdu) {
+        // No more to do
+        return true;
+    }
+
+    psArray *ordinates = NULL;          // Dark ordinates, to write
+    const char *normConcept = NULL;     // Normalisation concept
+    psArray *cells = chip->cells;       // Component cells
+    for (int j = 0; j < cells->n; j++) {
+        pmCell *cell = cells->data[j]; // Cell of interest
+        bool mdok;                      // Status of MD lookup
+        psArray *newOrd = psMetadataLookupPtr(&mdok, cell->analysis, PM_DARK_ANALYSIS_ORDINATES); // Ordinates
+        if (!mdok) {
+            continue;
+        }
+        psString newNorm = psMetadataLookupPtr(&mdok, cell->analysis, PM_DARK_ANALYSIS_NORM); // Normalisation
+        if (ordinates) {
+            if (newOrd != ordinates) {
+                psError(PS_ERR_UNKNOWN, true, "Dark ordinates differ across cells.");
+                return false;
+            }
+            if ((normConcept && (!newNorm || strcmp(normConcept, newNorm) != 0)) ||
+                (!normConcept && newNorm)) {
+                psError(PS_ERR_UNKNOWN, true, "Dark normalisations differ across cells.");
+                return false;
+            }
+        } else {
+            ordinates = newOrd;
+            normConcept = newNorm;
+        }
+    }
+
+    if (!ordinates) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find dark ordinates.");
+        return false;
+    }
+
+    return pmDarkWrite(fits, hdu->header, ordinates, normConcept);
+}
+
+bool pmCellWriteDark(pmCell *cell, psFits *fits, pmConfig *config, bool blank)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    // Allow the usual pmFPAWrite functions to handle the heavy lifting for the images
+    if (!pmCellWrite(cell, fits, config, blank)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to write dark cell.");
+        return false;
+    }
+
+    pmHDU *hdu = pmHDUFromCell(cell);
+    if (blank || !hdu) {
+        // No more to do
+        return true;
+    }
+
+    psArray *ordinates = psMetadataLookupPtr(NULL, cell->analysis, PM_DARK_ANALYSIS_ORDINATES);
+    if (!ordinates) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find dark ordinates.");
+        return false;
+    }
+    bool mdok;                          // Status of MD lookup
+    psString normConcept = psMetadataLookupPtr(&mdok, cell->analysis, PM_DARK_ANALYSIS_NORM); // Normalisation
+
+    return pmDarkWrite(fits, hdu->header, ordinates, normConcept);
+}
+
+bool pmDarkWrite(psFits *fits, psMetadata *header, const psArray *ordinates, const char *normConcept)
+{
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+    PS_ASSERT_FITS_WRITABLE(fits, false);
+    PS_ASSERT_ARRAY_NON_NULL(ordinates, false);
+
+    // Only write table once per FITS file
+    bool write = false;             // Write table?
+    if (psFitsGetSize(fits) <= 1) {
+        write = true;
+    } else {
+        psMetadata *headers = psFitsReadHeaderSet(NULL, fits); // FITS headers
+        if (!psMetadataLookup(headers, PM_DARK_FITS_EXTNAME)) {
+            write = true;
+        }
+        psFree(headers);
+    }
+    if (!write) {
+        return true;
+    }
+
+    if (!psMemIncrRefCounter((psMetadata*)header)) {
+        header = psMetadataAlloc();
+    }
+    psMetadataAddStr(header, PS_LIST_TAIL, PM_DARK_HEADER_NORM, PS_META_REPLACE,
+                     "Dark normalisation concept", normConcept);
+
+    if (ordinates->n > 0) {
+        // Format ordinates into FITS table
+        int numOrdinates = ordinates->n;// Number of ordinates
+        psArray *table = psArrayAlloc(numOrdinates); // FITS table, constructed from ordinates
+        for (int i = 0; i < ordinates->n; i++) {
+            pmDarkOrdinate *ord = ordinates->data[i]; // Ordinate of interest
+            psMetadata *row = psMetadataAlloc(); // FITS table row
+            psMetadataAddStr(row, PS_LIST_TAIL, PM_DARK_FITS_NAME, 0, "Concept name", ord->name);
+            psMetadataAddS32(row, PS_LIST_TAIL, PM_DARK_FITS_ORDER, 0, "Polynomial order", ord->order);
+            psMetadataAddBool(row, PS_LIST_TAIL, PM_DARK_FITS_SCALE, 0, "Scale values?", ord->scale);
+            psMetadataAddF32(row, PS_LIST_TAIL, PM_DARK_FITS_MIN, 0, "Minimum value", ord->min);
+            psMetadataAddF32(row, PS_LIST_TAIL, PM_DARK_FITS_MAX, 0, "Maximum value", ord->max);
+            table->data[i] = row;
+        }
+
+        if (!psFitsWriteTable(fits, header, table, PM_DARK_FITS_EXTNAME)) {
+            psError(PS_ERR_IO, false, "Unable to write dark ordinates.");
+            psFree(table);
+            psFree(header);
+            return false;
+        }
+        psFree(table);
+    } else {
+        // No ordinates to write to a table, so write to a blank header.
+        if (!psFitsWriteBlank(fits, header, PM_DARK_FITS_EXTNAME)) {
+            psError(PS_ERR_IO, false, "Unable to write dark header.");
+            psFree(header);
+            return false;
+        }
+    }
+    psFree(header);
+
+    return true;
+}
+
+
+bool pmFPAReadDark(pmFPA *fpa, psFits *fits, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    if (!pmFPARead(fpa, fits, config)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read dark FPA.");
+        return false;
+    }
+
+    psString normConcept = NULL;        // Normalisation concept
+    psArray *ordinates = pmDarkRead(&normConcept, fits); // Dark ordinates
+    if (!ordinates) {
+        psError(PS_ERR_IO, false, "Unable to read dark ordinates.");
+        return false;
+    }
+
+    psArray *chips = fpa->chips;        // Component chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // Chip of interest
+        psArray *cells = chip->cells;   // Component cells
+        for (int j = 0; j < cells->n; j++) {
+            pmCell *cell = cells->data[j]; // Cell of interest
+            psMetadataAddPtr(cell->analysis, PS_LIST_TAIL, PM_DARK_ANALYSIS_ORDINATES,
+                             PS_DATA_ARRAY | PS_META_REPLACE, "Dark ordinates", ordinates);
+            psMetadataAddStr(cell->analysis, PS_LIST_TAIL, PM_DARK_ANALYSIS_NORM, PS_META_REPLACE,
+                             "Dark normalisation", normConcept);
+        }
+    }
+    psFree(ordinates);
+    psFree(normConcept);
+
+    return true;
+}
+
+bool pmChipReadDark(pmChip *chip, psFits *fits, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    if (!pmChipRead(chip, fits, config)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read dark chip.");
+        return false;
+    }
+
+    psString normConcept = NULL;        // Normalisation concept
+    psArray *ordinates = pmDarkRead(&normConcept, fits); // Dark ordinates
+    if (!ordinates) {
+        psError(PS_ERR_IO, false, "Unable to read dark ordinates.");
+        return false;
+    }
+
+    psArray *cells = chip->cells;       // Component cells
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // Cell of interest
+        psMetadataAddPtr(cell->analysis, PS_LIST_TAIL, PM_DARK_ANALYSIS_ORDINATES,
+                         PS_DATA_ARRAY | PS_META_REPLACE, "Dark ordinates", ordinates);
+        psMetadataAddStr(cell->analysis, PS_LIST_TAIL, PM_DARK_ANALYSIS_NORM, PS_META_REPLACE,
+                         "Dark normalisation", normConcept);
+    }
+    psFree(ordinates);
+    psFree(normConcept);
+
+    return true;
+}
+
+
+bool pmCellReadDark(pmCell *cell, psFits *fits, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    // Allow the usual pmFPARead functions to handle the heavy lifting for the images
+    if (!pmCellRead(cell, fits, config)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read dark cell.");
+        return false;
+    }
+
+    psString normConcept = NULL;        // Normalisation concept
+    psArray *ordinates = pmDarkRead(&normConcept, fits); // Dark ordinates
+    if (!ordinates) {
+        psError(PS_ERR_IO, false, "Unable to read dark ordinates.");
+        return false;
+    }
+    psMetadataAddPtr(cell->analysis, PS_LIST_TAIL, PM_DARK_ANALYSIS_ORDINATES,
+                     PS_DATA_ARRAY | PS_META_REPLACE, "Dark ordinates", ordinates);
+    psMetadataAddStr(cell->analysis, PS_LIST_TAIL, PM_DARK_ANALYSIS_NORM, PS_META_REPLACE,
+                     "Dark normalisation", normConcept);
+    psFree(ordinates);
+    psFree(normConcept);
+
+    return true;
+}
+
+psArray *pmDarkRead(psString *normConcept, psFits *fits)
+{
+    PS_ASSERT_PTR_NON_NULL(normConcept, NULL);
+    PS_ASSERT_PTR_NULL(*normConcept, NULL);
+    PS_ASSERT_FITS_NON_NULL(fits, NULL);
+
+    if (!psFitsMoveExtName(fits, PM_DARK_FITS_EXTNAME)) {
+        psError(PS_ERR_IO, false, "Unable to find extension with dark ordinates table (%s).",
+                PM_DARK_FITS_EXTNAME);
+        return false;
+    }
+
+    psMetadata *header = psFitsReadHeader(NULL, fits); // Header
+    bool mdok;                          // Status of MD lookup
+    *normConcept = psMemIncrRefCounter(psMetadataLookupStr(&mdok, header, PM_DARK_HEADER_NORM));
+
+    psArray *ordinates = NULL;          // Dark ordinates to return
+
+    psFitsType type = psFitsGetExtType(fits); // Type of FITS extension
+    switch (type) {
+      case PS_FITS_TYPE_IMAGE: {
+          // Check that it's of zero size; otherwise we might have some conflict
+          int numCols = psMetadataLookupS32(&mdok, header, "NAXIS1");
+          int numRows = psMetadataLookupS32(&mdok, header, "NAXIS2");
+          if (numCols != 0 || numRows != 0) {
+              psError(PS_ERR_UNKNOWN, true, "Extension %s is not a DARK table.", PM_DARK_FITS_EXTNAME);
+              psFree(header);
+              return NULL;
+          }
+          // No ordinates fit --- only a constant term
+          ordinates = psArrayAlloc(0);
+          break;
+      }
+      case PS_FITS_TYPE_BINARY_TABLE:
+      case PS_FITS_TYPE_ASCII_TABLE: {
+          psArray *table = psFitsReadTable(fits); // FITS Table with ordinates
+          int numOrdinates = table->n;        // Number of ordinates
+          ordinates = psArrayAlloc(numOrdinates);
+
+          for (int i = 0; i < numOrdinates; i++) {
+              psMetadata *row = table->data[i]; // Row of interest
+              const char *name = psMetadataLookupStr(NULL, row, PM_DARK_FITS_NAME); // Concept name
+              int order = psMetadataLookupS32(NULL, row, PM_DARK_FITS_ORDER); // Polynomial order
+              if (!name || order <= 0) {
+                  psError(PS_ERR_UNKNOWN, false, "Bad value reading dark ordinates table.");
+                  psFree(table);
+                  psFree(ordinates);
+                  return false;
+              }
+              pmDarkOrdinate *ord = pmDarkOrdinateAlloc(name, order); // Ordinate data
+              ord->scale = psMetadataLookupBool(NULL, row, PM_DARK_FITS_SCALE);
+              ord->min = psMetadataLookupF32(NULL, row, PM_DARK_FITS_MIN);
+              ord->max = psMetadataLookupF32(NULL, row, PM_DARK_FITS_MAX);
+
+              ordinates->data[i] = ord;
+          }
+          psFree(table);
+          break;
+      }
+      default:
+        psError(PS_ERR_UNKNOWN, true, "Unrecognised FITS extension type.");
+        return NULL;
+    }
+    psFree(header);
+
+    return ordinates;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmDark.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmDark.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmDark.h	(revision 22157)
@@ -0,0 +1,99 @@
+#ifndef PM_DARK_H
+#define PM_DARK_H
+
+#include <pslib.h>
+#include <pmHDU.h>
+#include <pmFPA.h>
+#include <pmConfig.h>
+
+#define PM_DARK_ANALYSIS_ORDINATES "DARK.ORDINATES" // Name for dark ordinates in the cell analysis metadata
+#define PM_DARK_ANALYSIS_NORM "DARK.NORM" // Name for dark normalisation concept in cell analysis metadata
+#define PM_DARK_HEADER_NORM "PSDRKNRM"  // Header keyword for dark normalisation concept
+
+// An ordinate for fitting darks
+typedef struct {
+    psString name;                      // Name of concept to fit
+    int order;                          // Polynomial order to fit
+    bool scale;                         // Rescale values?
+    float min, max;                     // Minimum and maximum values for rescaling
+} pmDarkOrdinate;
+
+// Allocator
+pmDarkOrdinate *pmDarkOrdinateAlloc(const char *name, // Name for ordinate
+                                    int order // Order for ordinate
+    );
+
+
+// Combine darks
+bool pmDarkCombine(pmCell *output,      // Output cell; readouts will be attached
+                   const psArray *inputs, // Input readouts for combination
+                   psArray *ordinates,  // Ordinates for fitting
+                   const char *normConcept, // Concept name to use to divide input pixel values
+                   int iter,            // Number of rejection iterations
+                   float rej,           // Rejection threshold (standard deviations)
+                   psMaskType maskVal   // Value to mask
+    );
+
+// Apply dark
+bool pmDarkApply(pmReadout *readout,    // Readout to which to apply dark
+                 const pmCell *dark,    // Dark to apply
+                 psMaskType bad         // Mask value to give bad pixels
+    );
+
+// I/O functions for darks
+
+// Write all darks within an FPA
+bool pmFPAWriteDark(pmFPA *fpa,         // FPA to write
+                    psFits *fits,       // FITS file to which to write
+                    pmConfig *config,   // Configuration
+                    bool blank,         // Write a blank only?
+                    bool recurse        // Recurse to lower levels?
+    );
+
+// Write all darks within a chip
+bool pmChipWriteDark(pmChip *chip,      // Chip to write
+                     psFits *fits,      // FITS file to which to write
+                     pmConfig *config,  // Configuration
+                     bool blank,        // Write a blank only?
+                     bool recurse       // Recurse to lower levels?
+    );
+
+// Write a dark to a FITS file
+bool pmCellWriteDark(pmCell *cell,      // Cell containing dark information
+                     psFits *fits,      // FITS file to which to write
+                     pmConfig *config,  // Configuration
+                     bool blank         // Write a blank only?
+    );
+
+// Read dark for all FPA from a FITS file
+bool pmFPAReadDark(pmFPA *fpa,          // FPA for which to read
+                   psFits *fits,        // FITS file to read
+                   pmConfig *config     // Configuration
+    );
+
+// Read dark for all chip from a FITS file
+bool pmChipReadDark(pmChip *chip,       // Chip for which to read
+                    psFits *fits,       // FITS file to read
+                    pmConfig *config    // Configuration
+    );
+
+// Read dark for a cell from a FITS file
+bool pmCellReadDark(pmCell *cell,       // Cell for which to read
+                    psFits *fits,       // FITS file to read
+                    pmConfig *config    // Configuration
+    );
+
+// Write dark table to FITS file
+bool pmDarkWrite(psFits *fits,          // FITS file to which to write
+                 psMetadata *header,    // Header to write
+                 const psArray *ordinates, // Dark ordinates to write
+                 const char *normConcept // Normalisation concept name
+    );
+
+// Read dark table from FITS file
+psArray *pmDarkRead(psString *normConcept, // Normalisation concept name
+                    psFits *fits        // FITS file to read
+    );
+
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmDetrendDB.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmDetrendDB.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmDetrendDB.c	(revision 22157)
@@ -0,0 +1,337 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmConfigCommand.h"
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmDetrendDB.h"
+#include "psPipe.h"
+#include "psIOBuffer.h"
+
+// ************* detrend select functions **************
+
+//
+static void pmDetrendSelectOptionsFree (pmDetrendSelectOptions *options)
+{
+
+    if (!options)
+        return;
+
+    psFree (options->camera);
+    psFree (options->filter);
+    psFree (options->dettype);
+    psFree (options->version);
+
+    return;
+}
+
+// define basic options for a new detrend database query
+pmDetrendSelectOptions *pmDetrendSelectOptionsAlloc(const char *camera, psTime time, pmDetrendType type)
+{
+
+    pmDetrendSelectOptions *options = psAlloc(sizeof(pmDetrendSelectOptions));
+    psMemSetDeallocator(options, (psFreeFunc) pmDetrendSelectOptionsFree);
+
+    // basic options required by every query
+    options->camera = psStringCopy (camera);
+    options->time   = time;
+    options->type   = type;
+
+    // these other options depend on the type of detrend data
+    options->filter   = NULL;
+    options->version  = NULL;
+    options->dettype  = NULL;
+    options->exptime  = 0.0;
+    options->airmass  = 0.0;
+    options->dettemp  = 0.0;
+    options->twilight = 0.0;
+
+    options->exptimeSet  = false; // not selected
+    options->airmassSet  = false; // not selected
+    options->dettempSet  = false; // not selected
+    options->twilightSet = false; // not selected
+
+    return options;
+}
+
+static void pmDetrendSelectResultsFree (pmDetrendSelectResults *results)
+{
+
+    if (!results)
+        return;
+
+    psFree (results->detID);
+    psFree(results->level);
+
+    return;
+}
+
+pmDetrendSelectResults *pmDetrendSelectResultsAlloc ()
+{
+
+    pmDetrendSelectResults *results = psAlloc(sizeof(pmDetrendSelectResults));
+    psMemSetDeallocator(results, (psFreeFunc) pmDetrendSelectResultsFree);
+
+    results->detID = NULL;
+    results->level = NULL;
+
+    return results;
+}
+
+psString pmDetrendTypeToString (pmDetrendType type)
+{
+
+    # define DETREND_STRING_CASE(TTT) \
+case PM_DETREND_TYPE_##TTT: \
+    return psStringCopy (#TTT);
+
+    switch (type) {
+        DETREND_STRING_CASE (MASK);
+        DETREND_STRING_CASE (BIAS);
+        DETREND_STRING_CASE (DARK);
+        DETREND_STRING_CASE (FLAT);
+        DETREND_STRING_CASE (FLAT_CORRECTION);
+        DETREND_STRING_CASE (SHUTTER);
+        DETREND_STRING_CASE (FRINGE);
+        DETREND_STRING_CASE (ASTROM);
+    default:
+        return NULL;
+    }
+    return NULL;
+}
+
+// detselect -camera (camera) -time (time) -type (type) [others]
+// returns: (type) (class) (exp_flag) DONE
+pmDetrendSelectResults *pmDetrendSelect (const pmDetrendSelectOptions *options,
+        const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(options, NULL);
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    int status, exit_status;
+    psString line = NULL;
+    psString time = psTimeToISO (&options->time);
+
+    char *type = NULL;
+    if (options->dettype) {
+        type = psMemIncrRefCounter (options->dettype);
+    } else {
+        type = pmDetrendTypeToString (options->type);
+    }
+    unsigned int nFail;
+
+    pmDetrendSelectResults *results = pmDetrendSelectResultsAlloc();
+    psStringAppend(&line, "detselect -search -inst %s -det_type %s -time %s", options->camera, type, time);
+
+    if (options->filter) {
+        psStringAppend(&line, " -filter %s", options->filter);
+    }
+    if (options->version) {
+        psStringAppend(&line, " -version %s", options->version);
+    }
+    if (options->exptimeSet) {
+        psStringAppend(&line, " -exp_time %f", options->exptime);
+    }
+    if (options->airmassSet) {
+        psStringAppend(&line, " -airmass %f", options->airmass);
+    }
+    if (options->dettempSet) {
+        psStringAppend(&line, " -airmass %f", options->dettemp);
+    }
+    if (options->twilightSet) {
+        psStringAppend(&line, " -airmass %f", options->twilight);
+    }
+
+    psIOBuffer *buffer = NULL;
+    psPipe *pipe = NULL;
+    psMetadata *answer = NULL;
+
+    if (!pmConfigDatabaseCommand(&line, config)) {
+        psError (PS_ERR_IO, false, "error building detrend command %s", line);
+        goto failure;
+    }
+
+    if (!pmConfigTraceCommand(&line)) {
+        psError (PS_ERR_IO, false, "error building detrend command %s", line);
+        goto failure;
+    }
+
+    psTrace("psModules.detrend", 5, "running %s", line);
+
+    // use psPipe to exec the command, wait for response
+    buffer = psIOBufferAlloc (512);
+    pipe = psPipeOpen (line);
+    if (!pipe) {
+        psError (PS_ERR_IO, false, "error calling command %s", line);
+        goto failure;
+    }
+
+    status = psIOBufferReadEmpty (buffer, 2000, pipe->fd_stdout);
+    if (!status) {
+        psError (PS_ERR_IO, false, "detselect is not responding");
+        psLogMsg ("psModule.detrend", PS_LOG_ERROR, "detselect command:\n %s\n", line);
+        goto failure;
+    }
+    exit_status = psPipeClose (pipe);
+    if (exit_status) {
+        psError (PS_ERR_IO, false, "error running detselect");
+        psLogMsg ("psModule.detrend", PS_LOG_ERROR, "detselect command:\n %s\n", line);
+        goto failure;
+    }
+
+    psTrace("psModules.detrend", 5, "got answer: %s\n", buffer->data);
+
+    nFail = 0;
+    answer = psMetadataConfigParse (NULL, &nFail, buffer->data, false);
+    if (!answer) {
+        psError(PS_ERR_IO, false, "failed to parse response from detselect\n");
+        psLogMsg ("psModule.detrend", PS_LOG_ERROR, "detselect response (%d bytes):\n %s\n", buffer->n, buffer->data);
+        psLogMsg ("psModule.detrend", PS_LOG_ERROR, "detselect command:\n %s\n", line);
+        goto failure;
+    }
+
+    psMetadata *md = psMetadataLookupPtr (NULL, answer, "detExp");
+    if (!md) {
+        psError(PS_ERR_IO, false, "detselect response is missing 'detExp' Metadata\n");
+        psLogMsg ("psModule.detrend", PS_LOG_ERROR, "detselect response:\n %s\n", buffer->data);
+        goto failure;
+    }
+
+    bool mdstatus;
+    int detID = psMetadataLookupS32 (&mdstatus, md, "det_id");
+    if (!mdstatus) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find det_id in output from detselect.");
+        psLogMsg ("psModule.detrend", PS_LOG_ERROR, "detselect response:\n %s\n", buffer->data);
+        goto failure;
+    }
+    int iteration  = psMetadataLookupS32 (&mdstatus, md, "iteration");
+    if (!mdstatus) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find iteration in output from detselect.");
+        psLogMsg ("psModule.detrend", PS_LOG_ERROR, "detselect response:\n %s\n", buffer->data);
+        goto failure;
+    }
+    psString fileLevel = psMetadataLookupStr(&mdstatus, md, "filelevel");
+    if (!mdstatus || !fileLevel || strlen(fileLevel) == 0) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find filelevel in output from detselect.");
+        psLogMsg ("psModule.detrend", PS_LOG_ERROR, "detselect response:\n %s\n", buffer->data);
+        goto failure;
+    }
+
+    results->detID = NULL; // it should be NULL already from the Alloc above
+    psStringAppend (&results->detID, " -det_id %d -iteration %d ", detID, iteration);
+    results->level = psMemIncrRefCounter(fileLevel);
+
+    psTrace("psModules.detrend", 5, "generated detID %s\n", results->detID);
+
+    psFree (answer);
+    psFree (buffer);
+    psFree (pipe);
+    psFree (line);
+    psFree (time);
+    psFree (type);
+    return results;
+
+failure:
+    psFree (answer);
+    psFree (results);
+    psFree (pipe);
+    psFree (buffer);
+    psFree (line);
+    psFree (type);
+    psFree (time);
+    return NULL;
+}
+
+// ************* detrend file functions **************
+
+// detselect -select -detID (detID) -classID (classID)
+// returns: (detID) (classID) (filename) DONE
+char *pmDetrendFile (const char *detID, const char *classID, const pmConfig *config)
+{
+    unsigned int nFail;
+
+    PS_ASSERT_PTR_NON_NULL(detID, NULL);
+
+    bool status;
+    psString line = NULL;
+    psArray *array = NULL;
+
+    // generate the detselect command
+    psStringAppend (&line, "detselect -select %s", detID);
+    if (classID && strlen(classID) > 0) {
+        psStringAppend(&line, " -class_id %s", classID);
+    }
+    pmConfigDatabaseCommand(&line, config);
+    pmConfigTraceCommand(&line);
+    psTrace("psModules.detrend", 5, "running %s", line);
+
+    // use psPipe to exec the command, wait for response
+    psIOBuffer *buffer = psIOBufferAlloc (512);
+    psPipe *pipe = psPipeOpen (line);
+    if (!pipe) {
+        psError (PS_ERR_IO, false, "error calling command %s", line);
+        goto failure;
+    }
+
+    // timeout somewhat longer than 2sec.  this could still be too short....
+    status = psIOBufferReadEmpty (buffer, 2000, pipe->fd_stdout);
+    if (!status) {
+        psError (PS_ERR_IO, false, "detselect is not responding");
+        goto failure;
+    }
+    status = psPipeClose (pipe);
+    if (status) {
+        psError (PS_ERR_IO, false, "error running detselect");
+        goto failure;
+    }
+
+    psTrace("psModules.detrend", 5, "got answer: %s\n", buffer->data);
+
+    if (!buffer->n) {
+        psLogMsg ("psModule.detrend", PS_LOG_ERROR, "detselect response (%d bytes):\n %s\n", buffer->n, buffer->data);
+        psError(PS_ERR_IO, true, "no matching detrend data in database\n");
+        goto failure;
+    }
+
+    psMetadata *answer = psMetadataConfigParse (NULL, &nFail, buffer->data, false);
+    if (!answer) {
+        psError(PS_ERR_IO, false, "failed to parse response from detselect\n");
+        psLogMsg ("psModule.detrend", PS_LOG_ERROR, "detselect response (%d bytes):\n %s\n", buffer->n, buffer->data);
+        goto failure;
+    }
+    psMetadataItem *item = psMetadataLookup (answer, "detNormalizedImfile");
+    if ((item->type == PS_DATA_METADATA_MULTI) && (item->data.list->n > 1)) {
+        psError(PS_ERR_IO, false, "detselect returned too many files\n");
+        goto failure;
+    }
+
+    psMetadata *md = psMetadataLookupPtr (NULL, answer, "detNormalizedImfile");
+    if (!md) {
+        psError(PS_ERR_IO, false, "detselect response is missing 'detNormalizedImfile' Metadata\n");
+        psLogMsg ("psModule.detrend", PS_LOG_ERROR, "detselect response:\n %s\n", buffer->data);
+        goto failure;
+    }
+
+    char *result = psStringCopy (psMetadataLookupStr (NULL, md, "uri"));
+    psTrace("psModules.detrend", 5, "detrend file: %s\n", result);
+
+    psFree (answer);
+    psFree (pipe);
+    psFree (buffer);
+    psFree (line);
+    return result;
+
+failure:
+    psFree (array);
+    psFree (pipe);
+    psFree (buffer);
+    psFree (line);
+    return NULL;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmDetrendDB.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmDetrendDB.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmDetrendDB.h	(revision 22157)
@@ -0,0 +1,65 @@
+/* @file  pmDetrendDB.h
+ * @brief Tools to query the detrend database system
+ *
+ * the functions in here do not perform the detrend database queries directly.  all interfaces
+ * to the detrend database go through the external dettools functions.  this allows the modules
+ * and directly dependent program to be sufficiently independent of the database schema that it
+ * can be used with any properly defined detrend database tables.
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.14 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-11-10 01:09:20 $
+ * Copyright 2004-2005 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_DETREND_DB_H
+#define PM_DETREND_DB_H
+
+/// @addtogroup detrend Detrend Creation and Application
+/// @{
+
+typedef enum {
+    PM_DETREND_TYPE_MASK,
+    PM_DETREND_TYPE_BIAS,
+    PM_DETREND_TYPE_DARK,
+    PM_DETREND_TYPE_FLAT,
+    PM_DETREND_TYPE_FLAT_CORRECTION,
+    PM_DETREND_TYPE_SHUTTER,
+    PM_DETREND_TYPE_FRINGE,
+    PM_DETREND_TYPE_BACKGROUND,
+    PM_DETREND_TYPE_ASTROM,
+} pmDetrendType;
+
+typedef struct {
+    char *camera;                       // name of camera
+    char *version;                      // optional version string
+    char *filter;                       // name of filter
+    char *dettype;                      // actual detrend type name
+    float exptime;                      // exposure time (for dark, maybe flat & fringe)
+    float airmass;                      // for fringe
+    float dettemp;                      // for fringe
+    float twilight;                     // hours (or seconds?) since/before nearest twilight
+    psTime time;                        // time of input data
+    pmDetrendType type;                 // type of detrend data
+
+    bool  exptimeSet;
+    bool  airmassSet;
+    bool  dettempSet;
+    bool  twilightSet;
+} pmDetrendSelectOptions;
+
+typedef struct {
+    char *detID;                        // identifier of detrend run
+    psString level;                     // level in FPA hierarchy of individual file
+} pmDetrendSelectResults;
+
+psString pmDetrendTypeToString (pmDetrendType type);
+
+pmDetrendSelectOptions *pmDetrendSelectOptionsAlloc(const char *camera, psTime time, pmDetrendType type);
+pmDetrendSelectResults *pmDetrendSelectResultsAlloc();
+pmDetrendSelectResults *pmDetrendSelect (const pmDetrendSelectOptions *options, const pmConfig *config);
+char *pmDetrendFile (const char *detID, const char *classID, const pmConfig *config);
+
+/// @}
+# endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmFlatField.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmFlatField.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmFlatField.c	(revision 22157)
@@ -0,0 +1,119 @@
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmHDUUtils.h"
+#include "pmFPAMaskWeight.h"
+#include "pmFlatField.h"
+
+bool pmFlatField(pmReadout *in, const pmReadout *flat, psMaskType badFlat)
+{
+    PS_ASSERT_PTR_NON_NULL(in, false);
+    PS_ASSERT_PTR_NON_NULL(in->image, false);
+    PS_ASSERT_IMAGE_NON_EMPTY(in->image, false);
+    PS_ASSERT_PTR_NON_NULL(flat, false);
+    PS_ASSERT_PTR_NON_NULL(flat->image, false);
+    PS_ASSERT_IMAGE_NON_EMPTY(flat->image, false);
+    if (in->mask) {
+        PS_ASSERT_IMAGE_TYPE(in->mask, PS_TYPE_MASK, false);
+        PS_ASSERT_IMAGES_SIZE_EQUAL(in->mask, in->image, false);
+    }
+    PS_ASSERT_IMAGE_TYPE(flat->image, in->image->type.type, false);
+    if (flat->mask) {
+        PS_ASSERT_IMAGE_TYPE(flat->mask, PS_TYPE_MASK, false);
+        PS_ASSERT_IMAGES_SIZE_EQUAL(flat->mask, flat->image, false);
+    }
+
+    psImage *inImage   = in->image;     // Input image
+    psImage *inMask    = in->mask;      // Mask for input image
+    psImage *flatImage = flat->image;   // Flat-field image
+    psImage *flatMask  = flat->mask;    // Mask for flat-field image
+
+    // Add flat-field MD5 to header
+    pmHDU *hdu = pmHDUFromReadout(in);  // HDU of interest
+    psVector *md5 = psImageMD5(flat->image); // md5 hash
+    psString md5string = psMD5toString(md5); // String
+    psFree(md5);
+    psStringPrepend(&md5string, "FLAT image MD5: ");
+    psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK,
+                     md5string, "");
+    psFree(md5string);
+
+    // Check input image is not larger than flat image; mask is the same size as the input
+    if (inImage->numRows > flatImage->numRows || inImage->numCols > flatImage->numCols) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Input image (%dx%d) is larger than flat-field image "
+                "(%dx%d).\n", inImage->numCols, inImage->numRows, flatImage->numCols, flatImage->numRows);
+        return false;
+    }
+
+    // Offsets on the chip
+    int x0in = psMetadataLookupS32(NULL, in->parent->concepts, "CELL.X0");
+    int y0in = psMetadataLookupS32(NULL, in->parent->concepts, "CELL.Y0");
+    int x0flat = psMetadataLookupS32(NULL, flat->parent->concepts, "CELL.X0");
+    int y0flat = psMetadataLookupS32(NULL, flat->parent->concepts, "CELL.Y0");
+
+    // Determine offset based on image offset with chip offset: input frame to flat frame
+    int yOffset = in->row0 + y0in - flat->row0 - y0flat;
+    int xOffset = in->col0 + x0in - flat->col0 - x0flat;
+
+    // Check that offsets are within image limits
+    if (inImage->numRows + yOffset > flatImage->numRows ||
+            inImage->numCols + xOffset > flatImage->numCols) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Input image (%dx%d) with offsets (%d,%d) is larger than "
+                "flat-field image (%dx%d).\n", inImage->numCols, inImage->numRows, xOffset, yOffset,
+                flatImage->numCols, flatImage->numRows);
+        return false;
+    }
+
+    // Macro for all PS types
+    #define FLAT_DIVISION_CASE(TYPE, SPECIAL) \
+case PS_TYPE_##TYPE: \
+    for (int j = 0; j < inImage->numRows; j++) { \
+        for (int i = 0; i < inImage->numCols; i++) { \
+            ps##TYPE flatValue = flatImage->data.TYPE[j + yOffset][i + xOffset]; \
+            if (!isfinite(flatValue) || flatValue <= 0.0 || \
+                (flatMask && flatMask->data.U8[j + yOffset][i + xOffset])) { \
+                if (inMask) { \
+                    inMask->data.PS_TYPE_MASK_DATA[j][i] |= badFlat; \
+                } \
+                inImage->data.TYPE[j][i] = SPECIAL; \
+            } else { \
+                inImage->data.TYPE[j][i] /= flatValue; \
+            } \
+        } \
+    } \
+    break;
+
+    switch (inImage->type.type) {
+        FLAT_DIVISION_CASE(U8,  0);
+        FLAT_DIVISION_CASE(U16, 0);
+        FLAT_DIVISION_CASE(U32, 0);
+        FLAT_DIVISION_CASE(U64, 0);
+        FLAT_DIVISION_CASE(S8,  0);
+        FLAT_DIVISION_CASE(S16, 0);
+        FLAT_DIVISION_CASE(S32, 0);
+        FLAT_DIVISION_CASE(S64, 0);
+        FLAT_DIVISION_CASE(F32, NAN);
+        FLAT_DIVISION_CASE(F64, NAN);
+    default:
+        psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Unsupported type for input image: %x\n",
+                inImage->type.type);
+        return false;
+    }
+
+    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now, used for reporting
+    psString timeString = psTimeToISO(time); // String with time
+    psFree(time);
+    psStringPrepend(&timeString, "Flat-field processing completed at ");
+    psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK,
+                     timeString, "");
+    psFree(timeString);
+
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmFlatField.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmFlatField.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmFlatField.h	(revision 22157)
@@ -0,0 +1,30 @@
+/* @file pmFlatField.h
+ * @brief Apply flat field calibration
+ *
+ * @author Ross Harman, MHPCC
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.10 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-06-02 03:51:03 $
+ * Copyright 2004-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FLAT_FIELD_H
+#define PM_FLAT_FIELD_H
+
+/// @addtogroup detrend Detrend Creation and Application
+/// @{
+
+/// Apply flat field calibration to a readout
+///
+/// This function applies the flat field calibration to the input readout.  Support is available for different
+/// image types, though the input and flat images must have the same type.  The relative offsets between the
+/// input and flat images is determined from the readout row0,col0 and the CELL.X0 and CELL.Y0 concepts.
+/// Normalisation of the flat is left as the responsibility of the caller.  Non-positive pixels in the flat
+/// are masked, if there is a mask present in the input readout.
+bool pmFlatField(pmReadout *in,         ///< Readout with input image
+                 const pmReadout *flat,  ///< Readout with flat image
+                 psMaskType badFlat     ///< Mask value to give bad flat pixels
+                );
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmFlatNormalize.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmFlatNormalize.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmFlatNormalize.c	(revision 22157)
@@ -0,0 +1,189 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <math.h>
+#include <pslib.h>
+
+#include "pmFlatNormalize.h"
+
+// I'm not sure that many many iterations are required, but rather suspect that the system converges within a
+// few with absolutely no trouble (it *is* over-constrained).  For this reason, I'm putting the maximum number
+// of iterations and tolerance as preset values.
+#define MAXITER 10                      // Maximum number of iterations
+#define TOLERANCE 1e-3                  // Minimum tolerance for convergance
+
+
+bool pmFlatNormalize(psVector **expFluxesPtr, psVector **chipGainsPtr, const psImage *bgMatrix)
+{
+    PS_ASSERT_PTR_NON_NULL(bgMatrix, false);
+    PS_ASSERT_IMAGE_NON_NULL(bgMatrix, false);
+
+    int numExps = bgMatrix->numRows; // Number of exposures
+    int numChips = bgMatrix->numCols; // Number of chips with which each exposure is made
+
+    psVector *expFluxes;                // Dereferenced version of expFluxesPtr
+    if (expFluxesPtr) {
+        if (*expFluxesPtr) {
+            PS_ASSERT_VECTOR_TYPE(*expFluxesPtr, PS_TYPE_F32, false);
+            PS_ASSERT_VECTOR_SIZE(*expFluxesPtr, (long)numExps, false);
+        } else {
+            *expFluxesPtr = psVectorAlloc(numExps, PS_TYPE_F32);
+        }
+        expFluxes = psMemIncrRefCounter(*expFluxesPtr);
+    } else {
+        expFluxes = psVectorAlloc(numExps, PS_TYPE_F32);
+    }
+
+    psVector *chipGains;                // Dereferenced version of chipGainsPtr
+    if (chipGainsPtr) {
+        if (*chipGainsPtr) {
+            PS_ASSERT_VECTOR_TYPE(*chipGainsPtr, PS_TYPE_F32, false);
+            PS_ASSERT_VECTOR_SIZE(*chipGainsPtr, (long)numChips, false);
+        } else {
+            *chipGainsPtr = psVectorAlloc(numChips, PS_TYPE_F32);
+            psVectorInit(*chipGainsPtr, 1.0);
+        }
+        chipGains = psMemIncrRefCounter(*chipGainsPtr);
+    } else {
+        chipGains = psVectorAlloc(numChips, PS_TYPE_F32);
+        psVectorInit(chipGains, 1.0);
+    }
+
+    // Take the logarithms
+    psImage *flux = psImageCopy(NULL, bgMatrix, PS_TYPE_F32); // Copy of the input flux levels matrix
+    psImage *fluxMask = psImageAlloc(numChips, numExps, PS_TYPE_U8); // Mask for bad measurements
+    psImageInit(fluxMask, 0);
+    psVector *gainMask = psVectorAlloc(numChips, PS_TYPE_U8); // Mask for bad gains
+    psVectorInit(gainMask, 0);
+    psVector *expMask = psVectorAlloc(numExps, PS_TYPE_U8); // Mask for bad exposures
+    psVectorInit(expMask, 0);
+    for (int i = 0; i < numChips; i++) {
+        // Note: the input gains are in e/ADU; we want to work with ADU/e (bg [ADU] = g [ADU/e] * f [e])
+        // Hence the minus sign
+        if (isfinite(chipGains->data.F32[i]) && chipGains->data.F32[i] > 0) {
+            chipGains->data.F32[i] = -logf(chipGains->data.F32[i]);
+        } else {
+            chipGains->data.F32[i] = 0.0; // Take a wild guess, gain ~ 1 e/ADU
+        }
+
+        for (int j = 0; j < numExps; j++) {
+            if (isfinite(flux->data.F32[j][i]) && flux->data.F32[j][i] > 0) {
+                flux->data.F32[j][i] = logf(flux->data.F32[j][i]);
+            } else {
+                // Blank out this measurement
+                fluxMask->data.U8[j][i] = 1;
+                flux->data.F32[j][i] = NAN;
+            }
+        }
+    }
+
+    // Not really sure that we need to iterate, but here we go anyway...
+
+    float diff = INFINITY;              // Difference from previous iteration
+    psVector *oldExpFluxes = NULL;      // The fluxes in the previous iteration
+    psVector *oldChipGains = NULL;      // Chip gains in the previous iteration
+    for (int iter = 0; iter < MAXITER && diff > TOLERANCE; iter++) {
+        // Improve on the exposure fluxes
+        int numFluxes = 0;              // Number of fluxes
+        for (int i = 0; i < numExps; i++) {
+            if (expMask->data.U8[i]) {
+                psTrace("psModules.detrend", 7, "Flux for exposure %d is masked.\n", i);
+                continue;
+            }
+            numFluxes++;
+            float sum = 0.0;            // Sum of F_ij - G_j
+            int number = 0;             // Number of chips contributing
+            for (int j = 0; j < numChips; j++) {
+                if (!gainMask->data.U8[j] && !fluxMask->data.U8[i][j]) {
+                    sum += flux->data.F32[i][j] - chipGains->data.F32[j];
+                    number++;
+                }
+            }
+            if (number > 0) {
+                expFluxes->data.F32[i] = sum / (float)number;
+            } else {
+                expMask->data.U8[i] = 1;
+                expFluxes->data.F32[i] = NAN;
+            }
+            psTrace("psModules.detrend", 7, "Flux for exposure %d is %lf\n", i, expf(expFluxes->data.F32[i]));
+        }
+
+        // Improve on the gains
+        float meanGain = 0.0;           // Mean gain
+        int numGains = 0;               // Number of gains
+        for (int i = 0; i < numChips; i++) {
+            if (gainMask->data.U8[i]) {
+                continue;
+            }
+            float sum = 0.0;           // Sum of F_ji - S_j
+            int number = 0;             // Numer of sources contributing
+            for (int j = 0; j < numExps; j++) {
+                if (!fluxMask->data.U8[j][i]) {
+                    sum += flux->data.F32[j][i] - expFluxes->data.F32[j];
+                    number++;
+                }
+            }
+            if (number > 0) {
+                chipGains->data.F32[i] = sum / (float)number;
+            } else {
+                gainMask->data.U8[i] = 1;
+                chipGains->data.F32[i] = NAN;
+            }
+            psTrace("psModules.detrend", 7, "Gain for chip %d is %lf\n", i, expf(-chipGains->data.F32[i]));
+            meanGain += expf(chipGains->data.F32[i]);
+            numGains++;
+        }
+
+        // Normalise the mean gain to unity, and measure the difference
+        meanGain /= (float)numGains;
+        meanGain = logf(meanGain);
+        if (iter > 0) {
+            diff = 0.0;
+            for (int i = 0; i < numChips; i++) {
+                if (gainMask->data.U8[i]) {
+                    continue;
+                }
+                chipGains->data.F32[i] -= meanGain;
+                diff += abs((chipGains->data.F32[i] - oldChipGains->data.F32[i]) / chipGains->data.F32[i]);
+            }
+            for (int i = 0; i < numExps; i++) {
+                if (expMask->data.U8[i]) {
+                    continue;
+                }
+                diff += abs((expFluxes->data.F32[i] - oldExpFluxes->data.F32[i]) / expFluxes->data.F32[i]);
+            }
+        }
+
+        psTrace("psModules.detrend", 2, "Iteration %d: difference is %e\n", iter, diff);
+
+        // Copy the new to the old
+        oldChipGains = psVectorCopy(oldChipGains, chipGains, PS_TYPE_F32);
+        oldExpFluxes = psVectorCopy(oldExpFluxes, expFluxes, PS_TYPE_F32);
+    }
+    psFree(flux);
+    psFree(fluxMask);
+    psFree(oldChipGains);
+    psFree(oldExpFluxes);
+
+    // Un-log the vectors
+    for (int i = 0; i < numChips; i++) {
+        if (!gainMask->data.U8[i]) {
+            chipGains->data.F32[i] = expf(chipGains->data.F32[i]);
+        }
+    }
+    for (int i = 0; i < numExps; i++) {
+        if (!expMask->data.U8[i]) {
+            expFluxes->data.F32[i] = expf(expFluxes->data.F32[i]);
+        }
+    }
+    psFree(gainMask);
+    psFree(expMask);
+
+    psFree(chipGains);
+    psFree(expFluxes);
+
+    return (diff < TOLERANCE); // Did we converge?
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmFlatNormalize.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmFlatNormalize.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmFlatNormalize.h	(revision 22157)
@@ -0,0 +1,31 @@
+/* @file pmFlatNormalize.h
+ * @brief Normalize flat-field measurements
+ *
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.7 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-03-30 21:12:56 $
+ * Copyright 2004-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FLAT_NORMALIZE_H
+#define PM_FLAT_NORMALIZE_H
+
+/// @addtogroup detrend Detrend Creation and Application
+/// @{
+
+/// Normalize flat-field measurements
+///
+/// We have f_ij = g_i s_j where f_ij is the flux recorded for chip i and integration j, g_i is the gain for
+/// the i-th chip, s_j is the flux of the source in the j-th integration.  An initial guess for the chip gains
+/// might be helpful, but is not necessary.  The matrix of background measurements contains the background for
+/// the flat fields used in the combination, as a function of exposure (rows) and chip (columns).  The
+/// exposure fluxes and chip gains are modified upon return with the solved values.  Returns true if the
+/// solution converged.
+bool pmFlatNormalize(psVector **expFluxesPtr, ///< Flux in each exposure, or NULL; modified
+                     psVector **chipGainsPtr, ///< Initial guess of the chip gains or NULL; modified
+                     const psImage *bgMatrix ///< Background measurements: rows are exposures, cols are chips
+                    );
+
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmFringeStats.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmFringeStats.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmFringeStats.c	(revision 22157)
@@ -0,0 +1,1071 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFringeStats.h"
+
+// Future optimisations for speed:
+//
+// 1. Clipping --- don't re-do the matrix setup again, but carry matrix and vector around, subtract
+// contributions from clipped data points.
+// 2. Faster psImageStats (use memcpy?)
+
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// pmFringeRegions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static void fringeRegionsFree(pmFringeRegions *fringe)
+{
+    psFree(fringe->x);
+    psFree(fringe->y);
+    psFree(fringe->mask);
+    return;
+}
+
+pmFringeRegions *pmFringeRegionsAlloc(int nPts, int dX, int dY, int nX, int nY)
+{
+    pmFringeRegions *fringe = psAlloc(sizeof(pmFringeRegions));
+    (void)psMemSetDeallocator(fringe, (psFreeFunc)fringeRegionsFree);
+
+    fringe->x = NULL;
+    fringe->y = NULL;
+    fringe->mask = NULL;
+
+    fringe->nRequested = nPts;
+    fringe->nAccepted = 0;
+
+    fringe->dX = dX;
+    fringe->dY = dY;
+    fringe->nX = nX;
+    fringe->nY = nY;
+
+    return fringe;
+}
+
+bool pmFringeRegionsCreatePoints(pmFringeRegions *fringe, const psImage *image, psRandom *random)
+{
+    PS_ASSERT_PTR_NON_NULL(fringe, false);
+    PS_ASSERT_PTR_NON_NULL(image, false);
+    PS_ASSERT_IMAGE_NON_EMPTY(image, false);
+
+    double frnd;
+    // create fringe->nRequested
+
+    psRandom *rng;
+    if (random) {
+        rng = psMemIncrRefCounter(random);
+    } else {
+        rng = psRandomAlloc(PS_RANDOM_TAUS, 0);
+    }
+
+    fringe->x = psVectorRecycle(fringe->x, fringe->nRequested, PS_TYPE_F32);
+    fringe->y = psVectorRecycle(fringe->y, fringe->nRequested, PS_TYPE_F32);
+    fringe->mask = psVectorRecycle(fringe->mask, fringe->nRequested, PS_TYPE_U8);
+    fringe->x->n = fringe->y->n = fringe->mask->n = fringe->nRequested;
+    psVectorInit(fringe->mask, 0);
+
+    int nX = image->numCols;
+    int nY = image->numRows;
+
+    psF32 *xPt = fringe->x->data.F32;
+    psF32 *yPt = fringe->y->data.F32;
+
+    int dX = fringe->dX;
+    int dY = fringe->dY;
+
+    // generate random points located within image bounds
+    for (int i = 0; i < fringe->nRequested; i++) {
+        frnd = psRandomUniform(rng);
+        xPt[i] = (nX - 2*dX)* frnd + dX;
+        frnd = psRandomUniform(rng);
+        yPt[i] = (nY - 2*dY)* frnd + dY;
+    }
+
+    psFree(rng);
+
+    return true;
+}
+
+bool pmFringeRegionsWriteFits(psFits *fits, psMetadata *header,
+                              const pmFringeRegions *regions, const char *extname)
+{
+    // Make sure the input is well-behaved
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    PS_ASSERT_PTR_NON_NULL(regions, false);
+    psVector *x = regions->x;           // The x positions
+    psVector *y = regions->y;           // The y positions
+    psVector *mask = regions->mask;     // The region mask
+    int numRows = regions->nRequested;  // Number of rows in the table
+    PS_ASSERT_INT_POSITIVE(numRows, false);
+    PS_ASSERT_VECTOR_NON_NULL(x, false);
+    PS_ASSERT_VECTOR_NON_NULL(y, false);
+    PS_ASSERT_VECTOR_TYPE(x, PS_TYPE_F32, false);
+    PS_ASSERT_VECTOR_TYPE(y, PS_TYPE_F32, false);
+    PS_ASSERT_VECTOR_SIZE(x, (long)numRows, false);
+    PS_ASSERT_VECTOR_SIZE(y, (long)numRows, false);
+    if (mask) {
+        PS_ASSERT_VECTOR_NON_NULL(mask, false);
+        PS_ASSERT_VECTOR_TYPE(mask, PS_TYPE_U8, false);
+        PS_ASSERT_VECTOR_SIZE(mask, (long)numRows, false);
+    }
+
+    // We need to write:
+    // Scalars: dX, dY, nX, nY
+    // Vectors: x, y, mask
+
+    psMetadata *scalars = psMemIncrRefCounter(header); // Metadata to hold the scalars; will be the header
+    if (!scalars) {
+        scalars = psMetadataAlloc();
+    }
+    psMetadataAddS32(scalars, PS_LIST_TAIL, "PSFRNGDX", PS_META_REPLACE, "Median box half-width",
+                     regions->dX);
+    psMetadataAddS32(scalars, PS_LIST_TAIL, "PSFRNGDY", PS_META_REPLACE, "Median box half-height",
+                     regions->dY);
+    psMetadataAddS32(scalars, PS_LIST_TAIL, "PSFRNGNX", PS_META_REPLACE, "Large-scale smoothing in x",
+                     regions->nX);
+    psMetadataAddS32(scalars, PS_LIST_TAIL, "PSFRNGNY", PS_META_REPLACE, "Large-scale smoothing in y",
+                     regions->nY);
+
+    psArray *table = psArrayAlloc(numRows); // The table
+    // Translate the vectors into the required format for psFitsWriteTable()
+    for (long i = 0; i < numRows; i++) {
+        psMetadata *row = psMetadataAlloc();
+        psMetadataAddF32(row, PS_LIST_TAIL, "x", PS_META_REPLACE, "Fringe position in x", x->data.F32[i]);
+        psMetadataAddF32(row, PS_LIST_TAIL, "y", PS_META_REPLACE, "Fringe position in y", y->data.F32[i]);
+        psU8 maskValue = 0;
+        if (mask && mask->data.U8[i]) {
+            maskValue = 0xff;
+        }
+        psMetadataAddU8(row, PS_LIST_TAIL, "mask", PS_META_REPLACE, "Mask", maskValue);
+        table->data[i] = row;
+    }
+
+    bool success;                       // Success of operation
+    if (!(success = psFitsWriteTable(fits, scalars, table, extname))) {
+        psError(PS_ERR_IO, false, "Unable to write fringe data to extension %s\n", extname);
+    }
+    psFree(scalars);
+    psFree(table);
+
+    return success;
+}
+
+pmFringeRegions *pmFringeRegionsReadFits(psMetadata *header, const psFits *fits, const char *extname)
+{
+    PS_ASSERT_PTR_NON_NULL(fits, NULL);
+
+    if (extname && strlen(extname) > 0) {
+        if (!psFitsMoveExtName(fits, extname)) {
+            psError(PS_ERR_IO, false, "Unable to move to extension %s\n", extname);
+            return NULL;
+        }
+    } else if (!psFitsMoveExtNum(fits, 0, false)) {
+        psError(PS_ERR_IO, false, "Unable to move to PHU\n");
+        return NULL;
+    }
+
+    psMetadata *headerCopy = psMemIncrRefCounter(header); // Copy of the header, or NULL
+
+    headerCopy = psFitsReadHeader(headerCopy, fits); // The FITS header
+    if (!headerCopy) {
+        psError(PS_ERR_IO, false, "Unable to read header for extension %s\n", extname);
+        psFree(header);
+        return NULL;
+    }
+
+    // Read the scalars from the header
+    #define READ_SCALAR(SCALAR, NAME) \
+    int SCALAR = psMetadataLookupS32(&mdok, headerCopy, NAME); \
+    if (!mdok || SCALAR <= 0) { \
+        psError(PS_ERR_IO, true, "Unable to find " NAME " in header of extension %s.\n", extname); \
+        psFree(headerCopy); \
+        return NULL; \
+    }
+
+    // Need to retrieve the scalars: dX, dY, nX, nY
+    bool mdok = true;                   // Status of MD lookup
+    READ_SCALAR(dX, "PSFRNGDX");
+    READ_SCALAR(dY, "PSFRNGDY");
+    READ_SCALAR(nX, "PSFRNGNX");
+    READ_SCALAR(nY, "PSFRNGNY");
+    psFree(headerCopy);
+
+    // Now the vectors: x, y, mask
+    psArray *table = psFitsReadTable(fits); // The table
+    long numRows = table->n;            // Number of rows
+
+    pmFringeRegions *regions = pmFringeRegionsAlloc(numRows, dX, dY, nX, nY); // The fringe regions
+    psVector *x = psVectorAlloc(numRows, PS_TYPE_F32); // x position
+    psVector *y = psVectorAlloc(numRows, PS_TYPE_F32); // y position
+    psVector *mask = psVectorAlloc(numRows, PS_TYPE_U8); // mask
+    regions->x = x;
+    regions->y = y;
+    regions->mask = mask;
+
+    #define READ_REGIONS_ROW(VECTOR, TYPE, NAME, DESCRIPTION) \
+    VECTOR->data.TYPE[i] = psMetadataLookup##TYPE(&mdok, row, NAME); \
+    if (!mdok) { \
+        psError(PS_ERR_IO, true, "Unable to find " #DESCRIPTION " .\n"); \
+        psFree(table); \
+        psFree(regions); \
+        return NULL; \
+    }
+
+    // Translate the table into vectors
+    for (long i = 0; i < numRows; i++) {
+        psMetadata *row = table->data[i]; // Table row
+        READ_REGIONS_ROW(x, F32, "x", "x position");
+        READ_REGIONS_ROW(y, F32, "y", "y position");
+        READ_REGIONS_ROW(mask, U8, "mask", "mask");
+    }
+    psFree(table);
+
+    return regions;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// pmFringeStats
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static void fringeStatsFree(pmFringeStats *stats)
+{
+    psFree(stats->regions);
+    psFree(stats->f);
+    psFree(stats->df);
+}
+
+pmFringeStats *pmFringeStatsAlloc(pmFringeRegions *regions)
+{
+    PS_ASSERT_PTR_NON_NULL(regions, false);
+
+    pmFringeStats *stats = psAlloc(sizeof(pmFringeStats));
+    (void)psMemSetDeallocator(stats, (psFreeFunc)fringeStatsFree);
+
+    int numRegions = regions->nRequested; // Number of regions
+    stats->regions = psMemIncrRefCounter(regions);
+    stats->f = psVectorAlloc(numRegions, PS_TYPE_F32);
+    stats->df = psVectorAlloc(numRegions, PS_TYPE_F32);
+
+    return stats;
+}
+
+pmFringeStats *pmFringeStatsMeasure(pmFringeRegions *fringe, const pmReadout *readout, psMaskType maskVal)
+{
+    PS_ASSERT_PTR_NON_NULL(fringe, NULL);
+    PS_ASSERT_PTR_NON_NULL(readout, NULL);
+    PS_ASSERT_PTR_NON_NULL(readout->image, NULL);
+    PS_ASSERT_IMAGE_NON_EMPTY(readout->image, NULL);
+
+    if (!fringe->x || !fringe->y) {
+        // create the fringe vectors for this image
+        pmFringeRegionsCreatePoints(fringe, readout->image, NULL);
+    }
+
+    PS_ASSERT_PTR_NON_NULL(fringe->x, false);
+    PS_ASSERT_PTR_NON_NULL(fringe->y, false);
+
+    pmFringeStats *measurements = pmFringeStatsAlloc(fringe);
+
+    psF32 *xPt = fringe->x->data.F32;
+    psF32 *yPt = fringe->y->data.F32;
+    psF32 *fPt = measurements->f->data.F32;
+    psF32 *dfPt = measurements->df->data.F32;
+
+    int dX = fringe->dX;
+    int dY = fringe->dY;
+
+    psImage *image = readout->image;
+    psImage *mask  = readout->mask;
+
+    psStats *median = psStatsAlloc(PS_STAT_SAMPLE_MEDIAN); // Median statistics only
+    psStats *medianSd = psStatsAlloc(PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV); // Median and SD
+
+    // Measure the sky in each smoothing box
+    psImage *sky = psImageAlloc(fringe->nX, fringe->nY, PS_TYPE_F32);
+    for (int i = 0; i < fringe->nY; i++) {
+        int y0 = image->row0 + (float)i * (float)image->numRows / (float)fringe->nY;
+        int y1 = image->row0 + (float)(i + 1) * (float)image->numRows / (float)fringe->nY;
+        for (int j = 0; j < fringe->nX; j++) {
+            int x0 = image->col0 + (float)j * (float)image->numCols / (float)fringe->nX;
+            int x1 = image->col0 + (float)(j + 1) * (float)image->numCols / (float)fringe->nX;
+            psRegion region = psRegionSet(x0, x1, y0, y1);
+            psImage *subImage = psImageSubset(image, region); // Subimage of the sky region
+            psImage *subMask = NULL;
+            if (mask) {
+                subMask = psImageSubset(mask, region); // Subimage of the sky region
+            }
+            psImageStats(median, subImage, subMask, maskVal);
+            sky->data.F32[i][j] = median->sampleMedian;
+            psFree(subImage);
+            psFree(subMask);
+        }
+    }
+
+    for (int i = 0; i < fringe->x->n; i++) {
+        psRegion region = psRegionSet(image->col0 + xPt[i] - dX,
+                                      image->col0 + xPt[i] + dX + 1,
+                                      image->row0 + yPt[i] - dY,
+                                      image->row0 + yPt[i] + dY + 1);
+        psImage *subImage = psImageSubset(image, region);
+        psImage *subMask = NULL;
+        if (mask) {
+            subMask = psImageSubset(mask, region);
+        }
+        psImageStats(medianSd, subImage, subMask, maskVal);
+        psFree(subImage);
+        psFree(subMask);
+
+        int xSky = xPt[i] / (float)image->numCols * (float)sky->numCols;
+        int ySky = yPt[i] / (float)image->numRows * (float)sky->numRows;
+
+        fPt[i] = medianSd->sampleMedian - sky->data.F32[ySky][xSky];
+        dfPt[i] = 1.0 / medianSd->sampleStdev;
+
+        psTrace("psModules.detrend", 7, "[%d:%d,%d:%d]: %f %f\n", (int)region.x0, (int)region.x1,
+                (int)region.y0, (int)region.y1, fPt[i], dfPt[i]);
+    }
+    psFree(sky);
+    psFree(median);
+    psFree(medianSd);
+
+    return measurements;
+}
+
+bool pmFringeStatsWriteFits(psFits *fits,
+                            psMetadata *header,
+                            const pmFringeStats *fringe,
+                            const char *extname
+                           )
+{
+    // Make sure the input is well-behaved
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    PS_ASSERT_PTR_NON_NULL(fringe, false);
+    pmFringeRegions *regions = fringe->regions; // The fringe regions
+    PS_ASSERT_PTR_NON_NULL(regions, false);
+    int numRows = regions->nRequested;  // Number of rows in the table
+    PS_ASSERT_INT_POSITIVE(numRows, false);
+    psVector *f = fringe->f;            // The fringe measurements
+    psVector *df = fringe->df;      // The fringe standard deviatiations
+    PS_ASSERT_VECTOR_NON_NULL(f, false);
+    PS_ASSERT_VECTOR_NON_NULL(df, false);
+    PS_ASSERT_VECTOR_TYPE(f, PS_TYPE_F32, false);
+    PS_ASSERT_VECTOR_TYPE(df, PS_TYPE_F32, false);
+    PS_ASSERT_VECTOR_SIZE(f, (long)numRows, false);
+    PS_ASSERT_VECTOR_SIZE(df, (long)numRows, false);
+
+    // We need to write:
+    // Vectors: f, df
+    psArray *table = psArrayAlloc(numRows); // The table
+    // Translate the vectors into the required format for psFitsWriteTable()
+    for (long i = 0; i < numRows; i++) {
+        psMetadata *row = psMetadataAlloc();
+        psMetadataAddF32(row, PS_LIST_TAIL, "f", PS_META_REPLACE, "Fringe measurement", f->data.F32[i]);
+        psMetadataAddF32(row, PS_LIST_TAIL, "df", PS_META_REPLACE, "Fringe stdev", df->data.F32[i]);
+        table->data[i] = row;
+    }
+
+    if (!psFitsWriteTable(fits, header, table, extname)) {
+        psError(PS_ERR_IO, false, "Unable to write fringe data to extension %s\n", extname);
+        psFree(table);
+        return false;
+    }
+
+    psFree(table);
+    return true;
+}
+
+pmFringeStats *pmFringeStatsReadFits(psMetadata *header, const psFits *fits, const char *extname,
+                                     pmFringeRegions *regions)
+{
+    PS_ASSERT_PTR_NON_NULL(fits, NULL);
+    PS_ASSERT_PTR_NON_NULL(regions, NULL);
+    PS_ASSERT_INT_POSITIVE(regions->nRequested, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(regions->x, regions->y, NULL);
+    PS_ASSERT_VECTOR_SIZE(regions->x, (long)regions->nRequested, NULL);
+
+    if (extname && strlen(extname) > 0) {
+        if (!psFitsMoveExtName(fits, extname)) {
+            psError(PS_ERR_IO, false, "Unable to move to extension %s\n", extname);
+            return NULL;
+        }
+    } else if (!psFitsMoveExtNum(fits, 0, false)) {
+        psError(PS_ERR_IO, false, "Unable to move to PHU\n");
+        return NULL;
+    }
+
+    psMetadata *headerCopy = psMemIncrRefCounter(header); // Copy of the header, or NULL
+
+    headerCopy = psFitsReadHeader(headerCopy, fits); // The FITS header
+    if (!headerCopy) {
+        psError(PS_ERR_IO, false, "Unable to read header for extension %s\n", extname);
+        psFree(headerCopy);
+        return NULL;
+    }
+    psFree(headerCopy);
+
+    // Now the vectors: f, df
+    psArray *table = psFitsReadTable(fits); // The table
+    long numRows = table->n;            // Number of rows
+
+    pmFringeStats *fringes = pmFringeStatsAlloc(regions); // The fringe measurements
+    psVector *f = fringes->f;           // fringe measurement
+    psVector *df = fringes->df;         // fringe stdev
+
+    #define READ_STATS_ROW(VECTOR, TYPE, NAME, DESCRIPTION) \
+    VECTOR->data.TYPE[i] = psMetadataLookup##TYPE(&mdok, row, NAME); \
+    if (!mdok) { \
+        psError(PS_ERR_IO, true, "Unable to find " #DESCRIPTION " .\n"); \
+        psFree(table); \
+        psFree(fringes); \
+        return NULL; \
+    }
+
+    // Translate the table into vectors
+    bool mdok;                          // Status of MD lookup
+    for (long i = 0; i < numRows; i++) {
+        psMetadata *row = table->data[i]; // Table row
+        READ_STATS_ROW(f, F32, "f", "fringe measurement");
+        READ_STATS_ROW(df, F32, "df", "fringe standard deviation");
+    }
+    psFree(table);
+
+    return fringes;
+}
+
+
+pmFringeStats *pmFringeStatsConcatenate(const psArray *fringes, const psVector *x0, const psVector *y0)
+{
+    PS_ASSERT_PTR_NON_NULL(fringes, NULL);
+    PS_ASSERT_PTR_NON_NULL(fringes->data, NULL);
+    PS_ASSERT_INT_POSITIVE(fringes->n, NULL);
+    if (x0 && y0) {
+        PS_ASSERT_VECTOR_NON_NULL(x0, NULL);
+        PS_ASSERT_VECTOR_NON_NULL(y0, NULL);
+        PS_ASSERT_VECTOR_TYPE(x0, PS_TYPE_S32, NULL);
+        PS_ASSERT_VECTOR_TYPE(y0, PS_TYPE_S32, NULL);
+        PS_ASSERT_VECTORS_SIZE_EQUAL(x0, y0, NULL);
+        PS_ASSERT_VECTOR_SIZE(x0, fringes->n, NULL);
+        PS_ASSERT_VECTOR_SIZE(y0, fringes->n, NULL);
+    }
+
+    // Get the measurement parameters, and check they are consistent
+    int numPoints = 0;                  // Number of fringe points
+    int dX = 0, dY = 0;                 // Half-width and -height of fringe boxes
+    int nX = 0, nY = 0;                 // Smoothing scales
+    for (long i = 0; i < fringes->n; i++) {
+        pmFringeStats *fringe = fringes->data[i]; // The fringe of interest
+        pmFringeRegions *regions = fringe->regions; // The fringe regions
+        if (numPoints == 0) {
+            dX = regions->dX;
+            dY = regions->dY;
+            nX = regions->nX;
+            nY = regions->nY;
+        } else if (regions->dX != dX || regions->dY != dY || regions->nX != nX || regions->nY != nY) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Fringe %ld has different parameters (%d,%d,%d,%d) "
+                    "from the first (%d,%d,%d,%d).\n", i,
+                    regions->dX, regions->dY, regions->nX, regions->nY, dX, dY, nX, nY);
+            return NULL;
+        }
+        int num = regions->nRequested;  // Number of fringe points
+        if (regions->x->n != num || regions->y->n != num || regions->mask->n != num ||
+                fringe->f->n != num || fringe->df->n != num) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Length of region (%ld,%ld,%ld) and fringe vectors "
+                    "do not match (%ld,%ld) with the official value (%d).\n", regions->x->n, regions->y->n,
+                    regions->mask->n, fringe->f->n, fringe->df->n, num);
+            return NULL;
+        }
+        numPoints += regions->nRequested;
+    }
+
+    pmFringeRegions *newRegions = pmFringeRegionsAlloc(numPoints, dX, dY, nX, nY); // The new list of regions
+    newRegions->x = psVectorAlloc(numPoints, PS_TYPE_F32);
+    newRegions->y = psVectorAlloc(numPoints, PS_TYPE_F32);
+    newRegions->mask = psVectorAlloc(numPoints, PS_TYPE_U8);
+    pmFringeStats *newStats = pmFringeStatsAlloc(newRegions); // The new list of statistics
+
+    long offset = 0;                    // Offset from start of the list
+    for (long i = 0; i < fringes->n; i++) {
+        pmFringeStats *fringe = fringes->data[i]; // The fringe of interest
+        pmFringeRegions *regions = fringe->regions; // The fringe regions
+        // Copy the data over
+        memcpy(&newRegions->x->data.F32[offset], regions->x->data.F32, regions->x->n * sizeof(psF32));
+        memcpy(&newRegions->y->data.F32[offset], regions->y->data.F32, regions->y->n * sizeof(psF32));
+        memcpy(&newRegions->mask->data.U8[offset], regions->mask->data.U8, regions->mask->n * sizeof(psU8));
+        memcpy(&newStats->f->data.F32[offset], fringe->f->data.F32, fringe->f->n * sizeof(psF32));
+        memcpy(&newStats->df->data.F32[offset], fringe->df->data.F32, fringe->df->n * sizeof(psF32));
+        if (x0 && y0) {
+            for (long j = offset; j < offset + regions->x->n; j++) {
+                newRegions->x->data.F32[j] += x0->data.S32[i];
+                newRegions->y->data.F32[j] += y0->data.S32[i];
+            }
+        }
+        offset += regions->nRequested;
+    }
+
+    psFree(newRegions);                 // Drop reference
+    return newStats;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// pmFringeIO
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmFringesFormat(pmCell *cell, psMetadata *header, const psArray *fringes)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_ARRAY_NON_NULL(fringes, false);
+
+    // Check the regions are all identical
+    pmFringeRegions *regions = ((pmFringeStats*)fringes->data[0])->regions; // First region
+    for (int i = 1; i < fringes->n; i++) {
+        pmFringeStats *stats = fringes->data[i];
+        if (stats->regions != regions) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Regions for fringe statistics are not identical.\n");
+            return false;
+        }
+    }
+
+    // Ensure the region is legit
+    psVector *x = regions->x;           // The x positions
+    psVector *y = regions->y;           // The y positions
+    psVector *mask = regions->mask;     // The region mask
+    int numRows = regions->nRequested;  // Number of rows in the table
+    PS_ASSERT_INT_POSITIVE(numRows, false);
+    PS_ASSERT_VECTOR_NON_NULL(x, false);
+    PS_ASSERT_VECTOR_NON_NULL(y, false);
+    PS_ASSERT_VECTOR_TYPE(x, PS_TYPE_F32, false);
+    PS_ASSERT_VECTOR_TYPE(y, PS_TYPE_F32, false);
+    PS_ASSERT_VECTOR_SIZE(x, (long)numRows, false);
+    PS_ASSERT_VECTOR_SIZE(y, (long)numRows, false);
+    if (mask) {
+        PS_ASSERT_VECTOR_NON_NULL(mask, false);
+        PS_ASSERT_VECTOR_TYPE(mask, PS_TYPE_U8, false);
+        PS_ASSERT_VECTOR_SIZE(mask, (long)numRows, false);
+    }
+
+    // We need to write:
+    // Scalars: dX, dY, nX, nY
+    // Vectors: x, y, mask, f, df
+
+    psMetadata *scalars = psMemIncrRefCounter(header); // Metadata to hold the scalars; will be the header
+    if (!scalars) {
+        scalars = psMetadataAlloc();
+    }
+    psMetadataAddS32(scalars, PS_LIST_TAIL, "PSFRNGDX", PS_META_REPLACE, "Median box half-width",
+                     regions->dX);
+    psMetadataAddS32(scalars, PS_LIST_TAIL, "PSFRNGDY", PS_META_REPLACE, "Median box half-height",
+                     regions->dY);
+    psMetadataAddS32(scalars, PS_LIST_TAIL, "PSFRNGNX", PS_META_REPLACE, "Large-scale smoothing in x",
+                     regions->nX);
+    psMetadataAddS32(scalars, PS_LIST_TAIL, "PSFRNGNY", PS_META_REPLACE, "Large-scale smoothing in y",
+                     regions->nY);
+    psMetadataAdd(cell->analysis, PS_LIST_TAIL, "FRINGE.HEADER", PS_DATA_METADATA,
+                  "Header for fringe data", scalars);
+    psFree(scalars);
+
+
+    psArray *table = psArrayAlloc(numRows); // The table
+    // Translate the vectors into the required format for psFitsWriteTable()
+    for (long i = 0; i < numRows; i++) {
+        psMetadata *row = psMetadataAlloc();
+        psMetadataAddF32(row, PS_LIST_TAIL, "x", PS_META_REPLACE, "Fringe position in x", x->data.F32[i]);
+        psMetadataAddF32(row, PS_LIST_TAIL, "y", PS_META_REPLACE, "Fringe position in y", y->data.F32[i]);
+        psU8 maskValue = 0;             // Mask value
+        if (mask && mask->data.U8[i]) {
+            maskValue = 0xff;
+        }
+
+        psVector *f = psVectorAlloc(fringes->n, PS_TYPE_F32); // Measurements for each fringe component
+        psVector *df = psVectorAlloc(fringes->n, PS_TYPE_F32); // Errors in measurements
+        for (long j = 0; j < fringes->n; j++) {
+            pmFringeStats *stats = fringes->data[j]; // Fringe statistics of interest
+            f->data.F32[j] = stats->f->data.F32[i];
+            df->data.F32[j] = stats->df->data.F32[i];
+            if (!isfinite(f->data.F32[j]) || !isfinite(df->data.F32[j])) {
+                maskValue = 0xff;
+            }
+        }
+        psMetadataAdd(row, PS_LIST_TAIL, "f", PS_DATA_VECTOR | PS_META_REPLACE, "Fringe measurements", f);
+        psMetadataAdd(row, PS_LIST_TAIL, "df", PS_DATA_VECTOR | PS_META_REPLACE, "Fringe errors", df);
+        // Drop references
+        psFree(f);
+        psFree(df);
+
+        psMetadataAddU8(row, PS_LIST_TAIL, "mask", PS_META_REPLACE, "Mask", maskValue);
+        table->data[i] = row;
+    }
+
+    psMetadataAdd(cell->analysis, PS_LIST_TAIL, "FRINGE", PS_DATA_ARRAY, "Fringe data", table);
+
+    return true;
+}
+
+
+psArray *pmFringesParse(pmCell *cell)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+
+    bool mdok;                          // Status of MD lookup
+    psMetadata *header = psMetadataLookupMetadata(&mdok, cell->analysis, "FRINGE.HEADER"); // Header
+    if (!mdok || !header) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to find header for fringe data.\n");
+        return NULL;
+    }
+
+    psArray *table = psMetadataLookupPtr(NULL, cell->analysis, "FRINGE"); // FITS table
+    if (!table) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to find table for fringe data.\n");
+        return NULL;
+    }
+
+
+    // Read the scalars from the header
+    #define READ_FRINGES_SCALAR(SCALAR, NAME) \
+    int SCALAR = psMetadataLookupS32(&mdok, header, NAME); \
+    if (!mdok || SCALAR <= 0) { \
+        psError(PS_ERR_IO, true, "Unable to find " NAME " in fringe header.\n"); \
+        return NULL; \
+    }
+
+    // Need to retrieve the scalars: dX, dY, nX, nY
+    READ_FRINGES_SCALAR(dX, "PSFRNGDX");
+    READ_FRINGES_SCALAR(dY, "PSFRNGDY");
+    READ_FRINGES_SCALAR(nX, "PSFRNGNX");
+    READ_FRINGES_SCALAR(nY, "PSFRNGNY");
+
+    // Now the vectors: x, y, mask, f, df
+    long numRows = table->n;            // Number of rows
+    pmFringeRegions *regions = pmFringeRegionsAlloc(numRows, dX, dY, nX, nY); // The fringe regions
+    psVector *x = psVectorAlloc(numRows, PS_TYPE_F32); // x position
+    psVector *y = psVectorAlloc(numRows, PS_TYPE_F32); // y position
+    psVector *mask = psVectorAlloc(numRows, PS_TYPE_U8); // mask
+    regions->x = x;
+    regions->y = y;
+    regions->mask = mask;
+    psArray *f = psArrayAlloc(numRows); // Array of fringe measurements
+    psArray *df = psArrayAlloc(numRows);// Array of errors
+    psArray *fringes = NULL; // Array of fringes, to return
+
+    #define READ_FRINGES_VECTOR_ROW(VECTOR, TYPE, NAME, DESCRIPTION) \
+    { \
+        VECTOR->data.TYPE[i] = psMetadataLookup##TYPE(&mdok, row, NAME); \
+        if (!mdok) { \
+            psError(PS_ERR_IO, true, "Unable to find " #DESCRIPTION " for row %ld.\n", i); \
+            goto READ_FRINGES_DONE; \
+        } \
+    }
+
+    // Some values may be either a vector or a value --- need to check
+    #define READ_FRINGES_ARRAY_ROW(ARRAY, TYPE, NAME, DESCRIPTION) \
+    { \
+        psMetadataItem *item = psMetadataLookup(row, NAME); \
+        if (!item) { \
+            psError(PS_ERR_IO, true, "Unable to find " #DESCRIPTION " for row %ld.\n", i); \
+            goto READ_FRINGES_DONE; \
+        } \
+        if (item->type == PS_DATA_VECTOR) { \
+            ARRAY->data[i] = psMemIncrRefCounter(item->data.V); \
+        } else if (item->type == PS_TYPE_##TYPE) { \
+            psVector *vector = psVectorAlloc(1, PS_TYPE_##TYPE); \
+            vector->data.TYPE[0] = item->data.TYPE; \
+            ARRAY->data[i] = vector; \
+        } else { \
+            psError(PS_ERR_IO, true, "Found " #DESCRIPTION " for row %ld, but it's of an " \
+                    "unsupported type (%x).\n", i, item->type); \
+            goto READ_FRINGES_DONE; \
+        } \
+    }
+
+    // Translate the table into vectors
+    for (long i = 0; i < numRows; i++) {
+        psMetadata *row = table->data[i]; // Table row
+        READ_FRINGES_VECTOR_ROW(x, F32, "x", "x position");
+        READ_FRINGES_VECTOR_ROW(y, F32, "y", "y position");
+        READ_FRINGES_VECTOR_ROW(mask, U8, "mask", "mask");
+        READ_FRINGES_ARRAY_ROW(f, F32, "f", "fringe measurement");
+        READ_FRINGES_ARRAY_ROW(df, F32, "df", "fringe error");
+    }
+
+    // Get f,df into pmFringeStats
+    long numFringes = ((psVector*)(f->data[0]))->n; // Number of fringe components
+    fringes = psArrayAlloc(numFringes);
+    for (int j = 0; j < numFringes; j++) {
+        fringes->data[j] = pmFringeStatsAlloc(regions);
+    }
+
+    for (long i = 0; i < numRows; i++) {
+        psVector *measurements = f->data[i]; // Vector of measurements
+        psVector *errors = df->data[i]; // Vector of errors
+        for (int j = 0; j < numFringes; j++) {
+            pmFringeStats *fringe = fringes->data[j];
+            fringe->f->data.F32[i] = measurements->data.F32[j];
+            fringe->df->data.F32[i] = errors->data.F32[j];
+        }
+    }
+
+READ_FRINGES_DONE:
+    psFree(regions);
+    psFree(f);
+    psFree(df);
+
+    return fringes;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// pmFringeScale
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static void fringeScaleFree(pmFringeScale *scale)
+{
+    psFree(scale->coeff);
+    psFree(scale->coeffErr);
+    return;
+}
+
+pmFringeScale *pmFringeScaleAlloc(int nFringeFrames)
+{
+    pmFringeScale *scale = psAlloc(sizeof(pmFringeScale));
+    (void)psMemSetDeallocator(scale, (psFreeFunc)fringeScaleFree);
+
+    scale->nFringeFrames = nFringeFrames;
+    scale->coeff = psVectorAlloc(nFringeFrames + 1, PS_TYPE_F32);
+    scale->coeffErr = psVectorAlloc(nFringeFrames + 1, PS_TYPE_F32);
+
+    return scale;
+}
+
+// Determine the fringe scales through solving the least-squares problem
+static bool scaleMeasure(pmFringeScale *scale, // Scale to return
+                         pmFringeStats *science, // The fringe measurements for the science image
+                         psArray *fringes // Array of fringe measurements for the templates
+                        )
+{
+    assert(scale);
+    assert(science);
+    assert(fringes);
+    assert(scale->nFringeFrames == fringes->n);
+
+    psVector *mask = science->regions->mask; // The region mask
+
+    int numCoeffs = fringes->n + 1;     // Number of coefficients: scales for the templates plus a background
+    int numPoints = science->regions->nRequested; // Number of points (i.e., fringe measurements)
+
+    psImage *A = psImageAlloc(numCoeffs, numCoeffs, PS_TYPE_F64); // The least-squares matrix
+    psVector *B = psVectorAlloc(numCoeffs, PS_TYPE_F64); // The least-squares vector
+
+    // Generate the least-squares matrix and vector
+    for (int i = 0; i < numCoeffs; i++) {
+        psVector *fringe1 = NULL;       // A fringe measurement
+        if (i != 0) {
+            pmFringeStats *fringe = fringes->data[i - 1];
+            fringe1 = fringe->f;
+        }
+
+        // Fill in the upper part of the matrix
+        for (int j = i; j < numCoeffs; j++) {
+            psVector *fringe2 = NULL;   // Another fringe measurement
+            if (j != 0) {
+                pmFringeStats *fringe = fringes->data[j - 1];
+                fringe2 = fringe->f;
+            }
+
+            double matrix = 0.0;        // The matrix sum
+            for (int k = 0; k < numPoints; k++) {
+                if (!mask->data.U8[k]) {
+                    psF32 f1 = (fringe1) ? fringe1->data.F32[k] : 1.0; // Contribution from i fringe
+                    psF32 f2 = (fringe2) ? fringe2->data.F32[k] : 1.0; // Contribution from j fringe
+                    psF32 dsInv = science->df->data.F32[k]; // 1 / sigma
+                    matrix += f1 * f2 * dsInv * dsInv;
+                }
+            }
+            A->data.F64[i][j] = matrix;
+        }
+
+        // Use symmetry to fill in the lower part of the matrix
+        for (int j = 0; j < i; j++) {
+            A->data.F64[i][j] = A->data.F64[j][i];
+        }
+
+        double vector = 0.0;            // The vector sum
+        for (int k = 0; k < numPoints; k++) {
+            if (!mask->data.U8[k]) {
+                psF32 f1 = (fringe1) ? fringe1->data.F32[k] : 1.0; // Contribution from fringe 1
+                psF32 s = science->f->data.F32[k]; // Contribution from science measurement
+                psF32 dsInv = science->df->data.F32[k]; // 1 / sigma
+                vector += f1 * s * dsInv * dsInv;
+            }
+        }
+        B->data.F64[i] = vector;
+    }
+
+    if (psTraceGetLevel("psModules.detrend") >= 5) {
+        printf("From %d points:\n", numPoints);
+        for (int i = 0; i < numCoeffs; i++) {
+            for (int j = 0; j < numCoeffs; j++) {
+                printf("%.2e ", A->data.F64[i][j]);
+            }
+            printf("\n");
+        }
+    }
+
+    // Solve the least-squares equation
+    if (! psMatrixGJSolve(A, B)) {
+        psError(PS_ERR_UNKNOWN, false, "Could not solve linear equations.  Returning NULL.\n");
+        return false;
+    }
+
+    // Copy the results over
+    for (int i = 0; i < numCoeffs; i++) {
+        scale->coeff->data.F32[i] = B->data.F64[i];
+        scale->coeffErr->data.F32[i] = sqrt(A->data.F64[i][i]);
+    }
+
+    psFree(A);
+    psFree(B);
+
+    return true;
+}
+
+// Measure the fringe differences for each region
+static bool fringeScaleDiffs(psVector *diff, // Vector of differences
+                             pmFringeStats *science, // Science fringe measurements
+                             psArray *fringes, // Template fringe measurements
+                             pmFringeScale *scale // Fringe scales
+                            )
+{
+    assert(diff);
+    assert(diff->type.type == PS_TYPE_F32);
+    assert(science);
+    assert(fringes);
+    assert(scale);
+    assert(diff->n == science->regions->nRequested);
+    assert(fringes->n == scale->nFringeFrames);
+
+    psVector *mask = science->regions->mask; // The region mask
+
+    for (int i = 0; i < diff->n; i++) {
+        if (!mask->data.U8[i]) {
+            float difference = science->f->data.F32[i] - scale->coeff->data.F32[0];
+            for (int j = 0; j < fringes->n; j++) {
+                pmFringeStats *fringe = fringes->data[j]; // The fringe of interest
+                difference -= scale->coeff->data.F32[j + 1] * fringe->f->data.F32[i];
+            }
+            diff->data.F32[i] = difference * difference * science->df->data.F32[i] * science->df->data.F32[i];
+        }
+    }
+
+    return true;
+}
+
+// Clip regions based on the differences; return the number masked
+static int clipRegions(psVector *diffs, // Differences
+                       psVector *mask,  // Region mask
+                       float rej        // Rejection limit in standard deviations
+                      )
+{
+    assert(diffs);
+    assert(diffs->type.type == PS_TYPE_F32);
+    assert(mask);
+    assert(mask->type.type == PS_TYPE_U8);
+    assert(diffs->n == mask->n);
+
+    psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_QUARTILE); // Statistics
+    psVectorStats(stats, diffs, NULL, mask, 1);
+    float middle = stats->sampleMedian; // The middle of the distribution
+    float thresh = rej * 0.74 * (stats->sampleUQ - stats->sampleLQ); // The rejection threshold
+    psFree(stats);
+
+    int numClipped = 0;                 // Number clipped
+    for (int i = 0; i < diffs->n; i++) {
+        psTrace("psModules.detrend", 10, "Region %d (%d): %f\n", i, mask->data.U8[i], diffs->data.F32[i]);
+        if (!mask->data.U8[i] && fabs(diffs->data.F32[i]) > middle + thresh) {
+            psTrace("psModules.detrend", 5, "Masking %d: %f\n", i, diffs->data.F32[i]);
+            mask->data.U8[i] = 1;
+            numClipped++;
+        }
+    }
+
+    return numClipped;
+}
+
+
+// XXX include the fringe error (fringe->df) in the fit?
+pmFringeScale *pmFringeScaleMeasure(pmFringeStats *science, psArray *fringes, float rej,
+                                    unsigned int nIter, float keepFrac)
+{
+    PS_ASSERT_PTR_NON_NULL(science, NULL);
+    PS_ASSERT_PTR_NON_NULL(fringes, NULL);
+    PS_ASSERT_INT_POSITIVE(fringes->n, NULL);
+    PS_ASSERT_INT_POSITIVE(nIter, NULL);
+
+    pmFringeRegions *regions = science->regions; // The fringe regions
+    int numRegions = regions->nRequested; // Number of regions
+
+    // Ensure we are dealing with the SAME fringe points for all the inputs.
+    // Otherwise, we're going to get crazy results.
+    for (long i = 0; i < numRegions; i++) {
+        float xScience = regions->x->data.F32[i]; // The x position for the science image
+        float yScience = regions->y->data.F32[i]; // The y position for the science image
+        for (long j = 0; j < fringes->n; j++) {
+            pmFringeStats *fringe = fringes->data[j]; // The fringe statistics from a fringe image
+            pmFringeRegions *fringeRegions = fringe->regions; // The fringe regions for that fringe image
+            if (fringeRegions->x->data.F32[i] != xScience ||
+                    fringeRegions->y->data.F32[i] != yScience) {
+                psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Science and fringe measurement regions "
+                        "don't match.\n");
+                return NULL;
+            }
+        }
+    }
+
+    // Set up the mask
+    if (!regions->mask) {
+        regions->mask = psVectorAlloc(numRegions, PS_TYPE_U8);
+        psVectorInit(regions->mask, 0);
+    }
+    psVector *mask = regions->mask;     // The region mask
+    psStats *median = psStatsAlloc(PS_STAT_SAMPLE_MEDIAN); // Median statistics
+    unsigned int numClipped = 0;        // Total number clipped
+    psVector *diff = psVectorAlloc(numRegions, PS_TYPE_F32); // The differences between obs. and pred.
+
+    pmFringeScale *scale = pmFringeScaleAlloc(fringes->n); // The fringe scales
+
+    // Get rid of bad data points
+    for (int i = 0; i < fringes->n; i++) {
+        pmFringeStats *fringe = fringes->data[i]; // The fringe of interest
+        for (int j = 0; j < numRegions; j++) {
+            if (!isfinite(fringe->f->data.F32[j])) {
+                mask->data.U8[j] = 1;
+                psTrace("psModules.detrend", 9, "Masking region %d because not finite in fringe %d.\n", j, i);
+            }
+        }
+    }
+
+# if (0)
+    // Write fringe data to file for a test
+    FILE *f = fopen ("fringe.dat", "w");
+    for (int j = 0; j < numRegions; j++) {
+	if (mask->data.U8[j]) continue;
+	fprintf (f, "%d %f %f ", j, science->f->data.F32[j], science->df->data.F32[j]);
+	for (int i = 0; i < fringes->n; i++) {
+	    pmFringeStats *fringe = fringes->data[i]; // The fringe of interest
+            fprintf (f, "%f  ", fringe->f->data.F32[j]);
+        }
+	fprintf (f, "\n");
+    }
+    fclose (f);
+# endif
+
+    // Get rid of the extreme outliers by assuming most of the points are somewhat clustered
+    psVectorStats(median, science->f, NULL, NULL, 0);
+    scale->coeff->data.F32[0] = median->sampleMedian;
+    for (int i = 0; i < fringes->n; i++) {
+        pmFringeStats *fringe = fringes->data[i]; // The fringe of interest
+        psVectorStats(median, fringe->f, NULL, NULL, 0);
+        scale->coeff->data.F32[0] -= median->sampleMedian;
+        scale->coeff->data.F32[i] = 0.0;
+    }
+    psFree(median);
+    fringeScaleDiffs(diff, science, fringes, scale);
+    numClipped = clipRegions(diff, mask, 3.0*rej);
+    psTrace("psModules.detrend", 4, "%d regions clipped in initial pass.\n", numClipped);
+
+    unsigned int iter = 0;              // Iteration number
+    unsigned int iterClip = 0;          // Number clipped in this iteration
+    do {
+        iter++;
+        scaleMeasure(scale, science, fringes); // The scales
+        psTrace("psModules.detrend", 1, "Fringe scales after iteration %d:\n", iter);
+        psTrace("psModules.detrend", 1, "Background: %f %f\n", scale->coeff->data.F32[0],
+                scale->coeffErr->data.F32[0]);
+        for (int i = 0; i < scale->nFringeFrames; i++) {
+            psTrace("psModules.detrend", 1, "%d: %f %f\n", i, scale->coeff->data.F32[i + 1],
+                    scale->coeffErr->data.F32[i + 1]);
+        }
+
+        fringeScaleDiffs(diff, science, fringes, scale);
+        iterClip = clipRegions(diff, mask, rej); // Number clipped
+        numClipped += iterClip;
+        psTrace("psModules.detrend", 9, "Clipped: %d\tFrac: %f\n", iterClip,
+                (float)numClipped/(float)numRegions);
+    } while (iterClip > 0 && iter < nIter && (float)numClipped/(float)numRegions <= 1.0 - keepFrac);
+    psFree(diff);
+
+    // A final iteration with the last clipping
+    scaleMeasure(scale, science, fringes);
+
+    return scale;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Fringe correction
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// XXX note that this modifies the input fringe images
+psImage *pmFringeCorrect(pmReadout *readout, pmFringeRegions *fringes, psArray *fringeImages,
+                         psArray *fringeStats, psMaskType maskVal, float rej,
+                         unsigned int nIter, float keepFrac)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, NULL);
+    PS_ASSERT_PTR_NON_NULL(readout->image, NULL);
+    PS_ASSERT_IMAGE_NON_EMPTY(readout->image, NULL);
+    PS_ASSERT_PTR_NON_NULL(fringes, NULL);
+    PS_ASSERT_PTR_NON_NULL(fringeImages, NULL);
+    PS_ASSERT_PTR_NON_NULL(fringeStats, NULL);
+    PS_ASSERT_INT_EQUAL(fringeImages->n, fringeStats->n, NULL);
+    PS_ASSERT_INT_POSITIVE(nIter, NULL);
+
+    // measure the fringe stats for the science frame and solve for the scales
+    pmFringeStats *scienceStats = pmFringeStatsMeasure(fringes, readout, maskVal);
+
+    if (psTraceGetLevel("psModules.detrend") > 9) {
+        for (int i = 0; i < fringes->nRequested; i++) {
+            printf("%f", scienceStats->f->data.F32[i]);
+            for (int j = 0; j < fringeStats->n; j++) {
+                pmFringeStats *fringe = fringeStats->data[j];
+                printf("\t%f", fringe->f->data.F32[i]);
+            }
+            printf("\n");
+        }
+    }
+
+    pmFringeScale *scale = pmFringeScaleMeasure(scienceStats, fringeStats, rej, nIter, keepFrac);
+    psFree(scienceStats);
+
+    psTrace("psModules.detrend", 7, "Fringe solution:\n");
+    for (int i = 0; i < fringeImages->n + 1; i++) {
+        psTrace("psModules.detrend", 7, "%d: %f %f\n", i, scale->coeff->data.F32[i],
+                scale->coeffErr->data.F32[i]);
+    }
+
+    // build the fringe correction image
+    // XXX we could save data space by making the first image the output image
+    psImage *sumFringe = psImageAlloc(readout->image->numCols, readout->image->numRows, PS_TYPE_F32);
+    //psBinaryOp(sumFringe, sumFringe, "+", psScalarAlloc(scale->coeff->data.F32[0], PS_TYPE_F32));
+    for (int i = 0; i < fringeImages->n; i++) {
+
+        // rescale the fringe image
+        psBinaryOp(fringeImages->data[i], fringeImages->data[i], "*",
+                   psScalarAlloc(scale->coeff->data.F32[i+1], PS_TYPE_F32));
+
+        // sum together
+        sumFringe = (psImage*)psBinaryOp(sumFringe, sumFringe, "+", fringeImages->data[i]);
+    }
+    psFree(scale);
+
+    // subtract the resulting fringe frame
+    readout->image = (psImage*)psBinaryOp(readout->image, readout->image, "-", sumFringe);
+
+    return sumFringe;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmFringeStats.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmFringeStats.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmFringeStats.h	(revision 22157)
@@ -0,0 +1,214 @@
+/* @file pmFringeStats.h
+ * @brief Measure fringe statistics, and apply correction
+ *
+ * @author Eugene Magnier, IfA
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.12 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-01-24 02:54:15 $
+ * Copyright 2004-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_FRINGE_STATS
+#define PM_FRINGE_STATS
+
+/// @addtogroup detrend Detrend Creation and Application
+/// @{
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// pmFringeRegions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/// Fringe measurement regions.
+///
+/// Fringes are measured within a box of size dX,dY.  A large scale smoothing is performed by subtracting the
+/// background within large divisions of the image.  The coordinates of the fringe points and the mask may be
+/// NULL, which means that they will be generated when required.
+typedef struct
+{
+    int nRequested;                     // Number of fringe points selected
+    int nAccepted;                      // Number of fringe points not masked
+    int dX;                             // Median box half-width
+    int dY;                             // Median box half-height
+    int nX;                             // Number of large-scale smoothing divisions in x (col)
+    int nY;                             // Number of large-scale smoothing divisions in y (row)
+    psVector *x;                        // Fringe point coordinates (col), or NULL
+    psVector *y;                        // Fringe point coordinates (row), or NULL
+    psVector *mask;                     // Fringe point on/off mask, or NULL
+}
+pmFringeRegions;
+
+/// Allocate fringe regions
+pmFringeRegions *pmFringeRegionsAlloc (int nPts, ///< Number of fringe points to create
+                                       int dX, ///< Half-width of fringe boxes
+                                       int dY, ///< Half-height of fringe boxes
+                                       int nX, ///< Smoothing scale in x
+                                       int nY ///< Smoothing scale in y
+                                      );
+
+/// Generate the fringe points
+///
+/// Fringe points are generated randomly over the image.  No effort is made to avoid masked regions (indeed,
+/// the function knows nothing about masks).  If the random number generator is NULL, then a new one will be
+/// used.
+bool pmFringeRegionsCreatePoints(pmFringeRegions *fringe, ///< Fringe regions to generate
+                                 const psImage *image, ///< Image for the regions (defines the size)
+                                 psRandom *random ///< Random number generator, or NULL
+                                );
+
+/// Write the regions to a FITS file
+///
+/// The fringe regions are written to the FITS file, with the given extension name.  The header is
+/// supplemented with scalar values dX, dY, nX and nY (as PSFRNGDX, PSFRNGDY, PSFRNGNX, PSFRNGNY) from the
+/// fringe regions, while the fringe coordinates and mask are written as a FITS table (as x, y, mask).
+bool pmFringeRegionsWriteFits(psFits *fits, ///< Output FITS file
+                              psMetadata *header, ///< Additional headers to write, or NULL
+                              const pmFringeRegions *regions, ///< Regions to write
+                              const char *extname ///< Extension name, or NULL
+                             );
+
+/// Read the regions from a FITS file
+///
+/// The fringe regions are read from the FITS file, at the given extension name.  The scalars are retrieved
+/// from the header, while the table provides the fringe coordinates and mask.
+pmFringeRegions *pmFringeRegionsReadFits(psMetadata *header, ///< Header to read, or NULL
+        const psFits *fits, ///< Input FITS file
+        const char *extname ///< Extension name, or NULL
+                                        );
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// pmFringeStats
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/// Fringe measurements for a particular image
+///
+/// Measurements of the median and stdev are made at each of the fringe regions.
+typedef struct
+{
+    pmFringeRegions *regions;           ///< Fringe regions
+    psVector *f;                        ///< Fringe point median
+    psVector *df;                       ///< Fringe point stdev
+}
+pmFringeStats;
+
+/// Allocate fringe statistics
+pmFringeStats *pmFringeStatsAlloc(pmFringeRegions *regions // The fringe regions which will be measured
+                                 );
+
+/// Measure the fringe statistics for an image
+///
+/// Given an input image and fringe regions at which to measure, measures the median and stdev at each of the
+/// fringe points.  If the fringe points are undefined, they are generated.
+pmFringeStats *pmFringeStatsMeasure(pmFringeRegions *fringe, ///< Fringe regions at which to measure
+                                    const pmReadout *readout, ///< Readout for which to measure
+                                    psMaskType maskVal ///< Mask value for image
+                                   );
+
+/// Write the fringe stats for an image to a FITS table
+///
+/// The fringe measurements are written to the FITS file with the given extension name.  The median and stdev
+/// measurements are written as a FITS table (as f and df).
+bool pmFringeStatsWriteFits(psFits *fits, ///< FITS file to which to write
+                            psMetadata *header, ///< Additional headers to write, or NULL
+                            const pmFringeStats *fringe, ///< Fringe statistics to be written
+                            const char *extname ///< Extension name for table
+                           );
+
+/// Read the fringe stats for an image from a FITS table
+///
+/// The fringe measurements are read from the FITS file, at the given extension name.  The table provides the
+/// median and stdev measurements.  It is assumed that the fringe measurements correspond to the regions
+/// provided.
+pmFringeStats *pmFringeStatsReadFits(psMetadata *header, ///< Header to read, or NULL
+                                     const psFits *fits, ///< FITS file from which to read
+                                     const char *extname, ///< Extension name to read
+                                     pmFringeRegions *regions ///< Corresponding regions
+                                    );
+
+/// Concatenate the fringe stats for several readouts into a single fringe stats.
+///
+/// Each readout of each chip must be measured separately (so as to avoid any gaps between the cells, as in
+/// the case for GPC).  But the fit must be performed with all the readouts belonging to a chip (in order to
+/// get a secure measurement of the fringe amplitudes).  To do so, we need to concatenate the fringe
+/// measurements for each of the chip components.  This function generates a new pmFringeStats from
+/// concatenating those in the array.  The corresponding pmFringeRegions is also generated.
+pmFringeStats *pmFringeStatsConcatenate(const psArray *fringes, ///< Array of pmFringeStats for the readouts
+                                        const psVector *x0, ///< Offset in x for the readout
+                                        const psVector *y0 ///< Offset in y for the readout
+                                       );
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Input/output for multiple pmFringeStats
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/// Write an array of fringes measurements to a FITS table.
+///
+/// Writes an array of fringe measurements for a cell as a FITS table in the analysis metadata.  The array of
+/// fringe statistics must all use the same fringe regions (or there is no point in storing them all
+/// together).  The header is supplemented with scalar values dX, dY, nX and nY (as PSFRNGDX, PSFRNGDY,
+/// PSFRNGNX, PSFRNGNY) from the fringe regions, while the fringe coordinates and mask are written as a FITS
+/// table (as x, y, mask, f, df; f and df are vectors).
+bool pmFringesFormat(pmCell *cell,   ///< Cell for which to write
+                     psMetadata *header, ///< Header, or NULL
+                     const psArray *fringes ///< Array of pmFringeStats, all for the same pmFringeRegion
+                    );
+
+/// Parses an array of fringes measurements from a FITS table.
+///
+/// The fringes for the cell are read from the FITS table in the analysis metadata.  The table provides the
+/// region and the (possibly multiple) fringe statistics for that region.  The current extension is used if
+/// the extension name is not provided.
+psArray *pmFringesParse(pmCell *cell ///< Cell for which to read fringes
+                       );
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// pmFringeScale
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/// The fringe correction solution
+typedef struct
+{
+    int nFringeFrames;                  ///< Number of fringe frames
+    psVector *coeff;                    ///< Fringe coefficients; size = nFringeFrames
+    psVector *coeffErr;                 ///< Error in fringe coefficients; size = nFringeFrames
+}
+pmFringeScale;
+
+/// Measure the scales for the fringe correction
+///
+/// Given a fringe measurement for a science image, and an array of template fringe measurements, this
+/// function measures the contribution of each of the templates to the input.  Rejection is performed on the
+/// fringe regions, to weed out stars etc.
+pmFringeScale *pmFringeScaleMeasure(pmFringeStats *science, ///< Fringe measurements from science image
+                                    psArray *fringes, ///< Array of fringe measurements from templates
+                                    float rej, ///< Rejection threshold (in standard deviations)
+                                    unsigned int nIter, ///< Maximum number of iterations
+                                    float keepFrac ///< Minimum fraction of regions to keep
+                                   );
+
+/// Allocate fringe scales
+pmFringeScale *pmFringeScaleAlloc(int nFringeFrames ///< Number of fringe frames
+                                 );
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Fringe correction
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/// Solve for and apply the fringe correction
+///
+/// This is a wrapper around each of the fringe correction components to measure the fringe points, solve for
+/// the fringe correction, and apply the fringe correction.  The input fringe images are modified (scaled by
+/// the solution coefficients in order to correct the science image).  Returns the summed fringe image.
+psImage *pmFringeCorrect(pmReadout *in, ///< Input science image
+                         pmFringeRegions *fringes, ///< The fringe regions used
+                         psArray *fringeImages, ///< Fringe template images to use in correction
+                         psArray *fringeStats, ///< Fringe stats (for templates) to use in correction
+                         psMaskType maskVal, ///< Value to mask for science image
+                         float rej,     ///< Rejection threshold, for pmFringeScaleMeasure
+                         unsigned int nIter, ///< Maximum number of iterations, for pmFringeScaleMeasure
+                         float keepFrac ///< Minimum fraction of regions to keep, for pmFringeScaleMeasure
+                        );
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmMaskBadPixels.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmMaskBadPixels.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmMaskBadPixels.c	(revision 22157)
@@ -0,0 +1,271 @@
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <strings.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmHDUUtils.h"
+#include "pmFPAMaskWeight.h"
+#include "pmMaskBadPixels.h"
+
+bool pmMaskBadPixels(pmReadout *input, const pmReadout *mask, psMaskType maskVal)
+{
+    PS_ASSERT_PTR_NON_NULL(input, false);
+    PS_ASSERT_PTR_NON_NULL(input->mask, false);
+    PS_ASSERT_IMAGE_TYPE(input->mask, PS_TYPE_MASK, false);
+
+    PS_ASSERT_PTR_NON_NULL(mask, false);
+    PS_ASSERT_PTR_NON_NULL(mask->mask, false);
+    PS_ASSERT_IMAGE_TYPE(mask->mask, PS_TYPE_MASK, false);
+
+    psImage *inMask = input->mask;
+    psImage *exMask = mask->mask;
+
+    // Add mask MD5 to header
+    pmHDU *hdu = pmHDUFromReadout(input);  // HDU of interest
+    psVector *md5 = psImageMD5(mask->mask); // md5 hash
+    psString md5string = psMD5toString(md5); // String
+    psFree(md5);
+    psStringPrepend(&md5string, "MASK image MD5: ");
+    psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK,
+                     md5string, "");
+    psFree(md5string);
+
+    int rowMax = input->row0 + inMask->numRows;
+    int colMax = input->col0 + inMask->numCols;
+
+    if (mask->row0 > input->row0 || mask->col0 > input->col0 ||
+            mask->row0 + exMask->numRows < rowMax || mask->col0 + exMask->numCols < colMax) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                "Input image size exceeds that of mask image: (%d, %d) vs (%d, %d)",
+                inMask->numRows, inMask->numCols, exMask->numRows, exMask->numCols);
+        return false;
+    }
+
+    // Determine total offset based on image offset with chip offset
+    // XXX if we choose to correct for the readout location, apply input->col0,row0 here
+    int offCol = input->col0 - mask->col0;
+    int offRow = input->row0 - mask->row0;
+
+    // masks are both of type PS_TYPE_MASK
+    psMaskType **exVal = exMask->data.U8;
+    psMaskType **inVal = inMask->data.U8;
+
+    // apply exMask values
+    if (maskVal) {
+        // set raised pixels in exMask which are selected by maskVal
+        for (int j = 0; j < inMask->numRows; j++) {
+            int xJ = j - offRow;
+            for (int i = 0; i < inMask->numCols; i++) {
+                int xI = i - offCol;
+                inVal[j][i] |= (maskVal & exVal[xJ][xI]);
+            }
+        }
+    }
+
+    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now, used for reporting
+    psString timeString = psTimeToISO(time); // String with time
+    psFree(time);
+    psStringPrepend(&timeString, "Static mask (selecting %x) applied at ", maskVal);
+    psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK,
+                     timeString, "");
+    psFree(timeString);
+
+    return true;
+}
+
+
+bool pmMaskFlagSuspectPixels(pmReadout *output, const pmReadout *readout, float median, float stdev,
+                             float rej, psMaskType maskVal)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_FLOAT_LARGER_THAN(rej, 0.0, false);
+    PS_ASSERT_IMAGE_NON_NULL(readout->image, false);
+    PS_ASSERT_IMAGE_NON_EMPTY(readout->image, false);
+    PS_ASSERT_IMAGE_TYPE(readout->image, PS_TYPE_F32, false);
+    if (readout->mask) {
+        PS_ASSERT_IMAGE_NON_EMPTY(readout->mask, false);
+        PS_ASSERT_IMAGES_SIZE_EQUAL(readout->image, readout->mask, false);
+        PS_ASSERT_IMAGE_TYPE(readout->mask, PS_TYPE_MASK, false);
+    }
+    PS_ASSERT_PTR_NON_NULL(output, false);
+
+    bool mdok;                          // Status of MD lookup
+    psImage *suspect = psMetadataLookupPtr(&mdok, output->analysis, PM_MASK_ANALYSIS_SUSPECT); // Suspect img
+    if (suspect) {
+        PS_ASSERT_IMAGE_NON_EMPTY(suspect, false);
+        PS_ASSERT_IMAGE_TYPE(suspect, PS_TYPE_F32, false);
+        PS_ASSERT_IMAGES_SIZE_EQUAL(readout->image, suspect, false);
+        psMemIncrRefCounter(suspect);
+    } else {
+        suspect = psImageAlloc(readout->image->numCols, readout->image->numRows, PS_TYPE_F32);
+        psImageInit(suspect, 0);
+        psMetadataAddImage(output->analysis, PS_LIST_TAIL, PM_MASK_ANALYSIS_SUSPECT, PS_META_REPLACE,
+                           "Suspect pixels", suspect);
+        psMetadataAddS32(output->analysis, PS_LIST_TAIL, PM_MASK_ANALYSIS_NUM, PS_META_REPLACE,
+                         "Number of input images", 0);
+    }
+
+    if (!isfinite(median) || !isfinite(stdev)) {
+        // If we get down here and the statistics are missing, then we should go and mask the entire image
+        psWarning("Missing statistics --- flagging entire image as suspect.");
+        return (psImage*)psBinaryOp(suspect, suspect, "+", psScalarAlloc(1.0, PS_TYPE_F32));
+    }
+
+    psImage *image = readout->image;    // Image of interest
+    psImage *mask = readout->mask;      // Corresponding mask
+
+    psTrace ("psModules.detrend", 3, "suspect: %f +/- %f\n", median, stdev);
+
+    // XXX this loop could result in pixels with suspect = 0.0 but no valid input pixels (all
+    // masked).  need to track the number of good as well as suspect pixels?
+    for (int y = 0; y < image->numRows; y++) {
+        for (int x = 0; x < image->numCols; x++) {
+            if (fabs((image->data.F32[y][x] - median) / stdev) < rej) continue;
+	    if (mask && (mask->data.PS_TYPE_MASK_DATA[y][x] & maskVal)) continue;
+	    suspect->data.F32[y][x] += 1.0;
+        }
+    }
+    psFree(suspect);                    // Drop reference
+
+    psMetadataItem *numItem = psMetadataLookup(output->analysis, PM_MASK_ANALYSIS_NUM); // Item with number
+    assert(numItem);
+    numItem->data.S32++;
+
+    return true;
+}
+
+// the maskVal supplied here is the value SET for this mask (ie, it is not used to avoid pixels)
+bool pmMaskIdentifyBadPixels(pmReadout *output, psMaskType maskVal, float thresh, pmMaskIdentifyMode mode)
+{
+    PS_ASSERT_PTR_NON_NULL(output, false);
+    psImage *suspects = psMetadataLookupPtr(NULL, output->analysis, PM_MASK_ANALYSIS_SUSPECT); // Suspect img
+    if (!suspects) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to find image with suspected bad pixels.");
+        return false;
+    }
+    PS_ASSERT_IMAGE_NON_EMPTY(suspects, false);
+    PS_ASSERT_IMAGE_TYPE(suspects, PS_TYPE_F32, false);
+    if (output->mask) {
+        PS_ASSERT_IMAGE_NON_EMPTY(output->mask, false);
+        PS_ASSERT_IMAGES_SIZE_EQUAL(output->mask, suspects, false);
+        PS_ASSERT_IMAGE_TYPE(output->mask, PS_TYPE_MASK, false);
+    } else {
+        output->mask = psImageAlloc(suspects->numCols, suspects->numRows, PS_TYPE_MASK);
+    }
+    int num = psMetadataLookupS32(NULL, output->analysis, PM_MASK_ANALYSIS_NUM); // Number of inputs
+    PS_ASSERT_INT_POSITIVE(num, false);
+
+    float limit = NAN;                  // Limit for masking
+    switch (mode) {
+      case PM_MASK_ID_VALUE:
+        limit = thresh;
+        break;
+
+      case PM_MASK_ID_FRACTION:
+        limit = thresh * num;
+        break;
+
+      case PM_MASK_ID_SIGMA: {
+        psStats *stats = psStatsAlloc(PS_STAT_CLIPPED_STDEV); // Statistics
+        stats->clipSigma = 5.0;
+        stats->clipIter = 1;
+        if (!psImageStats(stats, suspects, NULL, 0) || !isfinite(stats->clippedStdev)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to perform statistics.\n");
+            psFree(stats);
+            return NULL;
+        }
+        limit = thresh * stats->clippedStdev;
+        psTrace ("psModules.detrend", 3, "bad: %f -> %f\n", stats->clippedStdev, limit);
+        psFree(stats);
+        break;
+      }
+
+      case PM_MASK_ID_POISSON: {
+        psStats *stats = psStatsAlloc(PS_STAT_MAX); // Statistics
+        if (!psImageStats(stats, suspects, NULL, 0)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to perform statistics.\n");
+            psFree(stats);
+            return NULL;
+        }
+        psHistogram *histo = psHistogramAlloc(-0.5, stats->max + 0.5, stats->max + 1);
+        psFree(stats);
+        if (!psImageHistogram(histo, suspects, NULL, 0)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to generate histogram.\n");
+            psFree(histo);
+            return NULL;
+        }
+
+        // Find the mode.  Since this is a Poisson distribution (more or less), this should also be the mean
+        // and variance.
+        int max = 0;                    // Index of the mode
+        for (int i = 0; i < histo->nums->n; i++) {
+            if (histo->nums->data.F32[i] > histo->nums->data.F32[max]) {
+                max = i;
+            }
+        }
+
+        // Since the mode is most likely zero, we add one to get something realistic.  Then "thresh" is
+        // negative, so we subtract instead of add.
+        limit = max + 1.0 - thresh * sqrtf((float)max + 1.0);
+
+        psTrace ("psModules.detrend", 3, "bad: mode: %d, stdev: %f, limit: %f\n",
+                 max, sqrtf((float)max + 1.0), limit);
+        break;
+      }
+      default:
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Invalid mask identify mode");
+        return NULL;
+    }
+
+    if (psTraceGetLevel("psModules.detrend") > 9) {
+        psStats *stats = psStatsAlloc(PS_STAT_MIN | PS_STAT_MAX); // Statistics
+        psImageStats(stats, suspects, NULL, 0);
+        psHistogram *histo = psHistogramAlloc(-0.5, stats->max + 0.5, stats->max + 1);
+        psImageHistogram(histo, suspects, NULL, 0);
+        for (int i = 0; i < histo->nums->n; i++) {
+            printf("%f --> %f : %f\n", histo->bounds->data.F32[i], histo->bounds->data.F32[i + 1],
+                   histo->nums->data.F32[i]);
+        }
+        psFree(stats);
+        psFree(histo);
+        printf("Threshold: %f\n", limit);
+    }
+
+    psTrace ("psModules.detrend", 3, "bad pixel threshold: %f", limit);
+
+    psImage *badpix = output->mask;     // Bad pixel mask
+    psImageInit(badpix, 0);
+
+    for (int y = 0; y < suspects->numRows; y++) {
+        for (int x = 0; x < suspects->numCols; x++) {
+            if (suspects->data.F32[y][x] >= limit) {
+                badpix->data.PS_TYPE_MASK_DATA[y][x] = maskVal;
+            }
+        }
+    }
+
+    return true;
+}
+
+pmMaskIdentifyMode pmMaskIdentifyModeFromString (const char *string) {
+
+    if (!strcasecmp(string, "VALUE")) {
+      return PM_MASK_ID_VALUE;
+    }
+    if (!strcasecmp(string, "FRACTION")) {
+      return PM_MASK_ID_FRACTION;
+    }
+    if (!strcasecmp(string, "SIGMA")) {
+      return PM_MASK_ID_SIGMA;
+    }
+    if (!strcasecmp(string, "POISSON")) {
+      return PM_MASK_ID_POISSON;
+    }
+    return PM_MASK_ID_NONE;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmMaskBadPixels.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmMaskBadPixels.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmMaskBadPixels.h	(revision 22157)
@@ -0,0 +1,71 @@
+/* @file pmMaskBadPixels.h
+ * @brief Mask bad pixels
+ *
+ * @author Ross Harman, MHPCC
+ * @author Eugene Magnier, IfA
+ *
+ * @version $Revision: 1.16 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-03-29 03:10:17 $
+ * Copyright 2004 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_MASK_BAD_PIXELS_H
+#define PM_MASK_BAD_PIXELS_H
+
+/// @addtogroup detrend Detrend Creation and Application
+/// @{
+
+#define PM_MASK_ANALYSIS_SUSPECT "MASK.SUSPECT" // Readout analysis metadata keyword for suspect image
+#define PM_MASK_ANALYSIS_NUM "MASK.NUM" // Readout analysis metadata keyword for number of inputs
+
+
+typedef enum {
+  PM_MASK_ID_NONE,
+  PM_MASK_ID_VALUE,
+  PM_MASK_ID_FRACTION,
+  PM_MASK_ID_SIGMA,
+  PM_MASK_ID_POISSON,
+} pmMaskIdentifyMode;
+
+pmMaskIdentifyMode pmMaskIdentifyModeFromString (const char *string);
+
+/// Applies the bad pixel mask to the input
+///
+/// Pixels marked as bad within the mask are marked as bad within the input image's mask.  If maskVal is
+/// non-zero, all pixels in the mask have any of the same bits sets as maskVal shall have the corresponding
+/// bits raised.  If maskVal is zero, any zero pixels in the mask are OR-ed with PM_MASK_BAD.  Position
+/// offsets (such as due to trimming) between the input and mask are applied so that the same pixels are
+/// referred to.  The science readout must already have a supplied mask element (use eg. pmReadoutSetMask).
+/// The supplied mask image must be of MASK type
+bool pmMaskBadPixels(pmReadout *input,  ///< Input science image
+                     const pmReadout *mask, ///< Mask image to apply
+                     psMaskType maskVal ///< Mask value to apply
+                    );
+
+/// Find pixels outlying from the background, flagging suspect pixels
+///
+/// Pixels more than "rej" standard deviations from the background level (in flat-fielded,
+/// background-subtracted images) have the corresponding pixel in the "suspect pixels" image
+/// incremented.  After accumulating over a suitable sample of images, bad pixels should have a
+/// high value in the suspect pixels image, allowing them to be identified.  The suspect pixels
+/// image is of type S32.  The relevant median and standard deviation must be supplied in the
+/// readout->analysis metadata as READOUT.MEDIAN, READOUT.STDEVe
+bool pmMaskFlagSuspectPixels(pmReadout *output, ///< Output readout, optionally with suspect pixels image
+                             const pmReadout *readout, ///< Readout to inspect
+                             float median, ///< Image median
+                             float stdev, ///< Image standard deviation
+                             float rej, ///< Rejection threshold (standard deviations)
+                             psMaskType maskVal ///< Mask value for statistics
+    );
+
+/// Identify bad pixels from the suspect pixels image
+///
+/// Bad pixels are identified from the suspect pixels image (accumulated over a large number of images),
+/// according to the chosen mode.
+bool pmMaskIdentifyBadPixels(pmReadout *output, ///< Output readout, with suspect pixels imageOut
+                             psMaskType maskVal, ///< Value to set for bad pixels
+                             float thresh, ///< Threshold for bad pixel
+                             pmMaskIdentifyMode mode ///< Mode for identifying bad pixels
+    );
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmNonLinear.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmNonLinear.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmNonLinear.c	(revision 22157)
@@ -0,0 +1,93 @@
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmNonLinear.h"
+
+pmReadout *pmNonLinearityPolynomial(pmReadout *inputReadout, const psPolynomial1D *input1DPoly)
+{
+    PS_ASSERT_PTR_NON_NULL(inputReadout, NULL);
+    PS_ASSERT_PTR_NON_NULL(inputReadout->image, NULL);
+    PS_ASSERT_IMAGE_TYPE(inputReadout->image, PS_TYPE_F32, NULL);
+    PS_ASSERT_PTR_NON_NULL(input1DPoly, NULL);
+
+    psImage *image = inputReadout->image; // Image to correct
+    for (int i = 0; i < image->numRows; i++) {
+        for (int j = 0; j < image->numCols; j++) {
+            image->data.F32[i][j] = psPolynomial1DEval(input1DPoly, image->data.F32[i][j]);
+        }
+    }
+    return inputReadout;
+}
+
+// set the bin closest to the corresponding value.  
+#define PS_BIN_FOR_VALUE(RESULT, VECTOR, VALUE) { \
+       	psVectorBinaryDisectResult result; \
+       	psScalar tmpScalar; \
+       	tmpScalar.type.type = PS_TYPE_F32; \
+	tmpScalar.data.F32 = (VALUE); \
+	RESULT = psVectorBinaryDisect (&result, VECTOR, &tmpScalar); \
+	switch (result) { \
+	  case PS_BINARY_DISECT_PASS: \
+            break; \
+	  case PS_BINARY_DISECT_OUTSIDE_RANGE: \
+            numPixels ++; \
+	    break; \
+	  case PS_BINARY_DISECT_INVALID_INPUT: \
+	  case PS_BINARY_DISECT_INVALID_TYPE: \
+	    psAbort ("programming error"); \
+	    break; \
+        } }
+
+pmReadout *pmNonLinearityLookup(pmReadout *inputReadout, const psVector *inFlux, const psVector *outFlux)
+{
+    PS_ASSERT_PTR_NON_NULL(inputReadout, NULL);
+    PS_ASSERT_PTR_NON_NULL(inputReadout->image, NULL);
+    PS_ASSERT_IMAGE_TYPE(inputReadout->image, PS_TYPE_F32, NULL);
+    PS_ASSERT_PTR_NON_NULL(inFlux, NULL);
+    if (inFlux->n < 2) {
+        psError(PS_ERR_UNKNOWN, true,
+                "pmNonLinearityLookup(): input vector less than 2 elements.  Returning inputReadout image.");
+        return(inputReadout);
+    }
+    PS_ASSERT_PTR_NON_NULL(outFlux,NULL);
+    psS32 tableSize = inFlux->n;
+    if (inFlux->n != outFlux->n) {
+        tableSize = PS_MIN(inFlux->n, outFlux->n);
+        psLogMsg(__func__, PS_LOG_WARN,
+                 "WARNING: pmNonLinear.c: pmNonLinearityLookup(): "
+                 "input vectors have different sizes (%ld, %ld)\n",
+                 inFlux->n, outFlux->n);
+    }
+    PS_ASSERT_VECTOR_TYPE(inFlux, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTOR_TYPE(outFlux, PS_TYPE_F32, NULL);
+
+    psImage *image = inputReadout->image; // Input image
+    int numPixels = 0;                  // Number of pixels outside the range
+    int binNum;
+
+    for (int i = 0; i < image->numRows; i++) {
+        for (int j = 0; j < image->numCols; j++) {
+	    float value = image->data.F32[i][j];
+            PS_BIN_FOR_VALUE(binNum, inFlux, value);
+
+	    // Perform linear interpolation.
+	    // XXX this will result in non-sensical results if inFlux contains equal-value
+	    // bins.  either enforce d(inFlux)/d(binNum) > 0 or see psStats.c PS_BIN_INTERPOLATE
+	    float slope = 
+		(outFlux->data.F32[binNum + 1] - outFlux->data.F32[binNum]) /
+		(inFlux->data.F32[binNum + 1] - inFlux->data.F32[binNum]);
+	    image->data.F32[i][j] = slope*(value - inFlux->data.F32[binNum]) + outFlux->data.F32[binNum];
+        }
+    }
+    if (numPixels > 0) {
+        psLogMsg(__func__, PS_LOG_WARN,
+                 "WARNING: pmNonLinear.c: pmNonLinearityLookup(): %d pixels outside table.", numPixels);
+    }
+    return inputReadout;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmNonLinear.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmNonLinear.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmNonLinear.h	(revision 22157)
@@ -0,0 +1,35 @@
+/* @file pmNonLinear.h
+ * @brief Perform non-linear correction through polynomial or table lookup
+ *
+ * @author George Gusciora, MHPCC
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.6 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-03-30 21:12:56 $
+ * Copyright 2004 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_NON_LINEAR_H
+#define PM_NON_LINEAR_H
+
+/// @addtogroup detrend Detrend Creation and Application
+/// @{
+
+/// Correct non-linearity through polynomial
+///
+/// Applies a polynomial to the flux of each pixel in the input image to determine the corrected flux.
+pmReadout *pmNonLinearityPolynomial(pmReadout *in, ///< Input image, to correct
+                                    const psPolynomial1D *coeff ///< Polynomial for non-linearity correction
+                                   );
+
+/// Correct non-linearity through table lookup
+///
+/// For each pixel in the input image, performs linear interpolation on the table (from the two vectors) to
+/// determine the corrected flux.
+pmReadout *pmNonLinearityLookup(pmReadout *in, ///< Input image, to correct
+                                const psVector *inFlux, ///< Table column with input fluxes
+                                const psVector *outFlux ///< Table column with output fluxes
+                               );
+
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmOverscan.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmOverscan.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmOverscan.c	(revision 22157)
@@ -0,0 +1,401 @@
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmHDUUtils.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPACalibration.h"
+
+#include "pmOverscan.h"
+
+#define SMOOTH_NSIGMA 4.0               // Number of Gaussian sigma the smoothing kernel extends
+
+static void pmOverscanOptionsFree(pmOverscanOptions *options)
+{
+    psFree(options->stat);
+    psFree(options->poly);
+    psFree(options->spline);
+}
+
+pmOverscanOptions *pmOverscanOptionsAlloc(bool single, pmFit fitType, unsigned int order, psStats *stat,
+                                          int boxcar, float gauss)
+{
+    pmOverscanOptions *opts = psAlloc(sizeof(pmOverscanOptions));
+    psMemSetDeallocator(opts, (psFreeFunc)pmOverscanOptionsFree);
+
+    // Inputs
+    opts->single = single;
+    opts->constant = false;
+    opts->fitType = fitType;
+    opts->order = order;
+    opts->stat = psMemIncrRefCounter(stat);
+
+    // Smoothing
+    opts->boxcar = boxcar;
+    opts->gauss = gauss;
+
+    // Outputs
+    opts->poly = NULL;
+    opts->spline = NULL;
+
+    return opts;
+}
+
+// Produce an overscan vector from an array of pixels
+psVector *pmOverscanVector(float *chi2, // chi^2 from fit
+			   pmOverscanOptions *overscanOpts, // Overscan options
+			   const psArray *pixels, // Array of vectors containing the pixel values
+			   psStats *myStats // Statistic to use in reducing the overscan
+    )
+{
+    assert(overscanOpts);
+    assert(pixels);
+    assert(myStats);
+
+    psStatsOptions statistic = psStatsSingleOption(myStats->options); // Statistic to use
+    assert(statistic != 0);
+
+    // Reduce the overscans
+    psVector *reduced = psVectorAlloc(pixels->n, PS_TYPE_F32); // Overscan for each row
+    psVector *ordinate = psVectorAlloc(pixels->n, PS_TYPE_F32); // Ordinate
+    psVector *mask = psVectorAlloc(pixels->n, PS_TYPE_U8); // Mask for fitting
+
+    for (int i = 0; i < pixels->n; i++) {
+        psVector *values = pixels->data[i]; // Vector with overscan values
+        if (values->n > 0) {
+            mask->data.U8[i] = 0;
+            ordinate->data.F32[i] = 2.0*(float)i/(float)pixels->n - 1.0; // Scale to [-1,1]
+            psVectorStats(myStats, values, NULL, NULL, 0);
+            reduced->data.F32[i] = psStatsGetValue(myStats, statistic);
+        } else if (overscanOpts->fitType == PM_FIT_NONE) {
+            psError(PS_ERR_UNKNOWN, true, "The overscan is not supplied for all points on the "
+                    "image, and no fit is requested.\n");
+            return NULL;
+        } else {
+            // We'll fit this one out
+            mask->data.U8[i] = 1;
+        }
+    }
+
+    // Smooth the reduced vector
+    if (overscanOpts->boxcar > 0) {
+        psVector *smoothed = psVectorBoxcar(NULL, reduced, overscanOpts->boxcar); // Smoothed vector
+        psFree(reduced);
+        reduced = smoothed;
+    }
+    if (isfinite(overscanOpts->gauss) && overscanOpts->gauss > 0) {
+        if (overscanOpts->boxcar > 0) {
+            psWarning("Gaussian smoothing the boxcar smoothed overscan --- you asked for it.");
+        }
+        psVector *smoothed = psVectorSmooth(NULL, reduced, overscanOpts->gauss, SMOOTH_NSIGMA);
+        psFree(reduced);
+        reduced = smoothed;
+    }
+
+    // Fit the overscan, if required
+    psVector *fitted;                   // Fitted overscan values
+    switch (overscanOpts->fitType) {
+      case PM_FIT_NONE:
+        // No fitting --- that's easy.
+        fitted = psMemIncrRefCounter(reduced);
+        break;
+      case PM_FIT_POLY_ORD:
+        if (overscanOpts->poly && (overscanOpts->poly->nX != overscanOpts->order ||
+                                   overscanOpts->poly->type != PS_POLYNOMIAL_ORD)) {
+            psFree(overscanOpts->poly);
+            overscanOpts->poly = NULL;
+        }
+        if (! overscanOpts->poly) {
+            overscanOpts->poly = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, overscanOpts->order);
+        }
+        psVectorFitPolynomial1D(overscanOpts->poly, mask, 1, reduced, NULL, ordinate);
+        fitted = psPolynomial1DEvalVector(overscanOpts->poly, ordinate);
+        break;
+      case PM_FIT_POLY_CHEBY:
+        if (overscanOpts->poly && (overscanOpts->poly->nX != overscanOpts->order ||
+                                   overscanOpts->poly->type != PS_POLYNOMIAL_CHEB)) {
+            psFree(overscanOpts->poly);
+            overscanOpts->poly = NULL;
+        }
+        if (! overscanOpts->poly) {
+            overscanOpts->poly = psPolynomial1DAlloc(PS_POLYNOMIAL_CHEB, overscanOpts->order);
+        }
+        psVectorFitPolynomial1D(overscanOpts->poly, mask, 1, reduced, NULL, ordinate);
+        fitted = psPolynomial1DEvalVector(overscanOpts->poly, ordinate);
+        break;
+      case PM_FIT_SPLINE:
+        // XXX I don't think psSpline1D is up to scratch yet --- it has no mask, and requires an
+        // input spline
+        overscanOpts->spline = psVectorFitSpline1D(reduced, ordinate);
+        fitted = psSpline1DEvalVector(overscanOpts->spline, ordinate);
+        break;
+      default:
+        psError(PS_ERR_UNKNOWN, true, "Unknown value for the fitting type: %d\n", overscanOpts->fitType);
+        return NULL;
+        break;
+    }
+
+    if (chi2) {
+        *chi2 = 0.0;                    // chi^2 (sort of)
+        for (int i = 0; i < reduced->n; i++) {
+            *chi2 += PS_SQR(fitted->data.F32[i] - reduced->data.F32[i]);
+        }
+    }
+
+    psFree(reduced);
+    psFree(ordinate);
+    psFree(mask);
+
+    return fitted;
+}
+
+bool pmOverscanUpdateHeader (pmHDU *hdu, pmOverscanOptions *overscanOpts, float chi2) {
+
+    psString comment = NULL;    // Comment to add
+
+    switch (overscanOpts->fitType) {
+      case PM_FIT_POLY_ORD:
+      case PM_FIT_POLY_CHEBY: {
+	  psStringAppend(&comment, "Overscan fit (chi2: %.2f): ", chi2);
+	  psPolynomial1D *poly = overscanOpts->poly; // The polynomial
+	  for (int i = 0; i < poly->nX; i++) {
+	      psStringAppend(&comment, "%.1f ", poly->coeff[i]);
+	  }
+	  psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, comment, "");
+	  psFree(comment);
+	  comment = NULL;
+
+	  // write metadata header value
+	  psMetadataAddF32(hdu->header, PS_LIST_TAIL, "OVER_VAL", PS_META_REPLACE,
+			   "Overscan value", poly->coeff[0]);
+	  psMetadataAddF32(hdu->header, PS_LIST_TAIL, "OVER_SIG", PS_META_REPLACE,
+			   "Overscan stdev", poly->coeffErr[0]);
+	  break;
+      }
+      case PM_FIT_SPLINE: {
+	  psSpline1D *spline = overscanOpts->spline; // The spline
+	  for (int i = 0; i < spline->n; i++) {
+	      psStringAppend(&comment, "Overscan fit (chi2: %.2f) %d:", chi2, i);
+	      psPolynomial1D *poly = spline->spline[i]; // i-th polynomial
+	      for (int j = 0; j < poly->nX; j++) {
+		  psStringAppend(&comment, "%.1f ", poly->coeff[i]);
+	      }
+	      psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK,
+			       comment, "");
+	      psFree(comment);
+	      comment = NULL;
+	  }
+	  // write metadata header value
+	  psMetadataAddF32(hdu->header, PS_LIST_TAIL, "OVER_VAL", PS_META_REPLACE,
+			   "Overscan value", NAN);
+	  psMetadataAddF32(hdu->header, PS_LIST_TAIL, "OVER_SIG", PS_META_REPLACE,
+			   "Overscan stdev", NAN);
+	  break;
+      }
+      case PM_FIT_NONE:
+	break;
+      default:
+	psAbort("Should never get here!!!\n");
+    }
+    return true;
+}
+
+bool pmOverscanSubtract (pmReadout *input, pmOverscanOptions *overscanOpts) {
+
+    assert (input);
+
+    if (overscanOpts == NULL) return true; // no overscan subtraction requested
+
+    pmHDU *hdu = pmHDUFromReadout(input);  // HDU of interest
+    psImage *image = input->image;
+
+    // check for 'soft bias' (simple, fixed offset to be subtracted)
+    if (overscanOpts->constant) {
+	// write metadata header value
+	psMetadataAddF32(hdu->header, PS_LIST_TAIL, "OVER_VAL", PS_META_REPLACE, "Overscan value",
+			 overscanOpts->value);
+	psMetadataAddF32(hdu->header, PS_LIST_TAIL, "OVER_SIG", PS_META_REPLACE, "Overscan stdev", NAN);
+
+	(void)psBinaryOp(input->image, input->image, "-", psScalarAlloc((float)overscanOpts->value, PS_TYPE_F32));
+
+	return true;
+    }
+
+    // we are performing a statitical analysis of the overscan region
+
+    // Check for an unallowable pmFit.
+    if (overscanOpts->fitType != PM_FIT_NONE && overscanOpts->fitType != PM_FIT_POLY_ORD &&
+	overscanOpts->fitType != PM_FIT_POLY_CHEBY && overscanOpts->fitType != PM_FIT_SPLINE) {
+	psError(PS_ERR_UNKNOWN, true, "Invalid fit type (%d).  Returning original image.\n",
+		overscanOpts->fitType);
+	return false;
+    }
+
+    psList *overscans = input->bias; // List of the overscan images
+
+    psStatsOptions statistic = psStatsSingleOption(overscanOpts->stat->options); // Statistic to use
+    if (statistic == 0) {
+	psError(PS_ERR_BAD_PARAMETER_VALUE, false, "Multiple or no statistics options set: %p\n",
+		overscanOpts->stat);
+	return false;
+    }
+    psStats *stats = psStatsAlloc(statistic); // A new psStats, to avoid clobbering original
+
+    psString comment = NULL;    // Comment to add
+    psStringAppend(&comment, "Subtracting overscan (stat %x; type %x; order %d)",
+		   statistic, overscanOpts->fitType, overscanOpts->order);
+    psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK,
+		     comment, "");
+    psFree(comment);
+
+    // Reduce all overscan pixels to a single value
+    if (overscanOpts->single) {
+	psVector *pixels = psVectorAlloc(0, PS_TYPE_F32);
+	psListIterator *iter = psListIteratorAlloc(overscans, PS_LIST_HEAD, false); // Iterator
+	psImage *overscan = NULL;   // Overscan image from iterator
+	while ((overscan = psListGetAndIncrement(iter))) {
+	    int index = pixels->n;  // Index
+	    pixels = psVectorRealloc(pixels, pixels->n + overscan->numRows * overscan->numCols);
+	    pixels->n += overscan->numRows * overscan->numCols;
+	    for (int i = 0; i < overscan->numRows; i++) {
+		memcpy(&pixels->data.F32[index], overscan->data.F32[i],
+		       overscan->numCols * sizeof(psF32));
+		index += overscan->numCols;
+	    }
+	}
+	psFree(iter);
+
+	(void)psVectorStats(stats, pixels, NULL, NULL, 0);
+	psFree(pixels);
+	double reduced = psStatsGetValue(stats, statistic); // Result of statistics
+
+	psString comment = NULL;    // Comment to add
+	psStringAppend(&comment, "Overscan value: %f", reduced);
+	psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, comment, "");
+	psFree(comment);
+
+	// write metadata header value
+	psMetadataAddF32(hdu->header, PS_LIST_TAIL, "OVER_VAL", PS_META_REPLACE, "Overscan value",
+			 reduced);
+	psMetadataAddF32(hdu->header, PS_LIST_TAIL, "OVER_SIG", PS_META_REPLACE, "Overscan stdev", NAN);
+
+	(void)psBinaryOp(image, image, "-", psScalarAlloc((float)reduced, PS_TYPE_F32));
+	psFree(stats);
+	return true;
+    } 
+
+    // We are performing a row-by-row overscan subtraction
+    bool readRows = psMetadataLookupBool(NULL, input->parent->concepts, "CELL.READDIR"); // Read direction
+    float chi2 = NAN;           // chi^2 from fit
+
+    // adjust operation depending on the read direction : need to re-org pixels for columns
+    if (readRows) {
+	// The read direction is rows
+	psArray *pixels = psArrayAlloc(image->numRows); // Array of vectors containing pixels
+	for (int i = 0; i < pixels->n; i++) {
+	    pixels->data[i] = psVectorAlloc(0, PS_TYPE_F32);
+	}
+
+	// Pull the pixels out into the vectors
+	psListIterator *iter = psListIteratorAlloc(overscans, PS_LIST_HEAD, false); // Iterator
+	psImage *overscan = NULL; // Overscan image from iterator
+	while ((overscan = psListGetAndIncrement(iter))) {
+	    // the overscan and image might not be aligned.  pixels->data represents
+	    // the image row pixels.
+	    int diff = overscan->row0 - image->row0; // Offset between the two regions
+	    for (int i = PS_MAX(0,diff); i < PS_MIN(image->numRows, overscan->numRows + diff); i++) {
+		int j = i - diff;
+		// i is row on image
+		// j is row on overscan
+		psVector *values = pixels->data[i];
+		int index = values->n; // Index in the vector
+		values = psVectorRealloc(values, values->n + overscan->numCols);
+		values->n += overscan->numCols;
+		memcpy(&values->data.F32[index], overscan->data.F32[j],
+		       overscan->numCols * PSELEMTYPE_SIZEOF(PS_TYPE_F32));
+		index += overscan->numCols;
+		pixels->data[i] = values; // Update the pointer in case it's moved
+	    }
+	}
+	psFree(iter);
+
+	// Reduce the overscans
+	psVector *reduced = pmOverscanVector(&chi2, overscanOpts, pixels, stats);
+	psFree(pixels);
+	if (! reduced) {
+	    psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to generate overscan vector.\n");
+	    psFree(stats);
+	    return false;
+	}
+
+	// Subtract row by row
+	for (int i = 0; i < image->numRows; i++) {
+	    for (int j = 0; j < image->numCols; j++) {
+		image->data.F32[i][j] -= reduced->data.F32[i];
+	    }
+	}
+	psFree(reduced);
+    } else {
+	// The read direction is columns
+	psArray *pixels = psArrayAlloc(image->numCols); // Array of vectors containing pixels
+	for (int i = 0; i < pixels->n; i++) {
+	    psVector *values = psVectorAlloc(0, PS_TYPE_F32);
+	    pixels->data[i] = values;
+	}
+
+	// Pull the pixels out into the vectors
+	psListIterator *iter = psListIteratorAlloc(overscans, PS_LIST_HEAD, false); // Iterator
+	psImage *overscan = NULL; // Overscan image from iterator
+	while ((overscan = psListGetAndIncrement(iter))) {
+	    // the overscan and image might not be aligned.  pixels->data represents
+	    // the image row pixels.
+	    int diff = overscan->col0 - image->col0; // Offset between the two regions
+	    for (int i = PS_MAX(0,diff); i < PS_MIN(image->numCols, overscan->numCols + diff); i++) {
+		int iFixed = i - diff;
+		// i is column on image
+		// iFixed is column on overscan
+		psVector *values = pixels->data[i];
+		int index = values->n; // Index in the vector
+		values = psVectorRealloc(values, values->n + overscan->numRows);
+		for (int j = 0; j < overscan->numRows; j++) {
+		    values->data.F32[index++] = overscan->data.F32[j][iFixed];
+		}
+		values->n += overscan->numRows;
+		pixels->data[i] = values; // Update the pointer in case it's moved
+	    }
+	}
+	psFree(iter);
+
+	// Reduce the overscans
+	psVector *reduced = pmOverscanVector(&chi2, overscanOpts, pixels, stats);
+	psFree(pixels);
+	if (! reduced) {
+	    psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to generate overscan vector.\n");
+	    psFree(stats);
+	    return false;
+	}
+
+	// Subtract column by column
+	for (int i = 0; i < image->numCols; i++) {
+	    for (int j = 0; j < image->numRows; j++) {
+		image->data.F32[j][i] -= reduced->data.F32[i];
+	    }
+	}
+	psFree(reduced);
+    }
+
+    pmOverscanUpdateHeader (hdu, overscanOpts, chi2);
+    psFree(stats);
+
+    return true;
+
+} // End of overscan subtraction
+    
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmOverscan.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmOverscan.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmOverscan.h	(revision 22157)
@@ -0,0 +1,72 @@
+/* @file pmOverscan.h
+ * @brief Functions to subtract the overscan, used by pmBiasSubtract
+ *
+ * @author George Gusciora, MHPCC
+ * @author Paul Price, IfA
+ * @author Eugene Magnier, IfA
+ *
+ * @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-08-15 20:21:18 $
+ * Copyright 2004--2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_OVERSCAN_H
+#define PM_OVERSCAN_H
+
+/// @addtogroup detrend Detrend Creation and Application
+/// @{
+
+/// Type of fit to perform
+typedef enum {
+    PM_FIT_NONE,                        ///< No fit
+    PM_FIT_POLY_ORD,                    ///< Fit ordinary polynomial
+    PM_FIT_POLY_CHEBY,                  ///< Fit Chebyshev polynomial
+    PM_FIT_SPLINE                       ///< Fit cubic splines
+} pmFit;
+
+/// Options for overscan subtraction
+///
+/// The overscan subtraction may be performed by reducing all overscan regions to a single value (e.g., if
+/// there is no structure); or the overscan may be fit perpendicular to the read direction (usually the
+/// columns) with a particular functional form; or a single value may be subtracted for each read/scan without
+/// fitting (if the structure defies characterisation).  In any case, statistics are required to reduce
+/// multiple values to a single value (either for the scan, or for the entire overscan regions).
+typedef struct
+{
+    // Inputs
+    bool single;                        ///< Reduce all overscan regions to a single value?
+    bool constant;			///< use a supplied constant value (do not measure region)
+    float value;			///< supplied value if needed (per above)
+    pmFit fitType;                      ///< Type of fit to overscan
+    unsigned int order;                 ///< Order of polynomial, or number of spline pieces
+    psStats *stat;                      ///< Statistic to use when reducing the minor direction
+    int boxcar;                         ///< Boxcar smoothing radius
+    float gauss;                        ///< Gaussian smoothing sigma
+    // Outputs
+    psPolynomial1D *poly;               ///< Result of polynomial fit
+    psSpline1D *spline;                 ///< Result of spline fit
+}
+pmOverscanOptions;
+
+/// Allocator for overscan options
+pmOverscanOptions *pmOverscanOptionsAlloc(bool single, ///< Reduce all overscan regions to a single value?
+                                          pmFit fitType, ///< Type of fit to overscan
+                                          unsigned int order, ///< Order of polynomial, or number of splines
+                                          psStats *stat, ///< Statistic to use
+                                          int boxcar, ///< Boxcar smoothing radius
+                                          float gauss ///< Gaussian smoothing sigma
+                                         );
+
+psVector *pmOverscanVector(float *chi2, // chi^2 from fit
+			   pmOverscanOptions *overscanOpts, // Overscan options
+			   const psArray *pixels, // Array of vectors containing the pixel values
+			   psStats *myStats // Statistic to use in reducing the overscan
+    );
+
+bool pmOverscanUpdateHeader (pmHDU *hdu, pmOverscanOptions *overscanOpts, float chi2);
+
+bool pmOverscanSubtract (pmReadout *input, pmOverscanOptions *overscanOpts);
+
+/// @}
+#endif
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmShifts.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmShifts.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmShifts.c	(revision 22157)
@@ -0,0 +1,477 @@
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAUtils.h"
+#include "pmShifts.h"
+
+#define SHIFTS_BUFFER 100               // Buffer size for shifts
+#define TRACE "psModules.detrend"       // Trace facility
+#define FFT_SIZE 25                     // Size at which we use FFT instead of direct convolution
+
+// XXX To do:
+// * Make the table column names configurable, by having a SHIFTS metadata in the camera format, with entries
+//   specifying the column names.
+
+
+static void shiftsFree(pmShifts *shifts)
+{
+    psFree(shifts->x);
+    psFree(shifts->y);
+    psFree(shifts->t);
+    return;
+}
+
+pmShifts *pmShiftsAlloc(bool tRel, bool xyRel)
+{
+    pmShifts *shifts = psAlloc(sizeof(pmShifts));
+    psMemSetDeallocator(shifts, (psFreeFunc)shiftsFree);
+
+    shifts->x = psVectorAllocEmpty(SHIFTS_BUFFER, PS_TYPE_S32);
+    shifts->y = psVectorAllocEmpty(SHIFTS_BUFFER, PS_TYPE_S32);
+    shifts->t = psVectorAllocEmpty(SHIFTS_BUFFER, PS_TYPE_F32);
+    shifts->num = 0;
+
+    // Suitable defaults
+    shifts->tRelative = tRel;
+    shifts->xyRelative = xyRel;
+
+    return shifts;
+}
+
+// Look up the cell within a hash; supplement the hash with a new value if it doesn't exist
+static pmShifts *cellVectors(psHash *shifts, // Hash of shifts
+                             const char *cellName, // Key for hash
+                             bool tRel, bool xyRel // Are the shifts relative?
+    )
+{
+    assert(shifts);
+    assert(cellName);
+
+    // Find the appropriate cell
+    pmShifts *vectors = psHashLookup(shifts, cellName);
+    if (!vectors) {
+        vectors = pmShiftsAlloc(tRel, xyRel);
+        psHashAdd(shifts, cellName, vectors);
+        psFree(vectors);            // Drop reference
+    }
+    return vectors;
+}
+
+
+bool pmShiftsRead(const pmCell *cell, psFits *fits)
+{
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+
+    bool mdok;                          // Status of MD lookup
+    pmShifts *check = psMetadataLookupPtr(&mdok, cell->analysis, PM_SHIFTS_TABLE_NAME); // Table, or NULL
+    if (check) {
+        psTrace(TRACE, 2, "Cell already has OT kernel present.\n");
+        return true;                    // No error
+    }
+    psTrace(TRACE, 2, "Reading FITS file for OT kernels.\n");
+
+    // Determine camera layout
+    pmChip *chip = cell->parent;        // The parent chip
+    pmFPA *fpa = chip->parent;          // The parent FPA
+    pmHDU *phu = NULL;                  // The primary header
+    pmFPALevel phuLevel = PM_FPA_LEVEL_NONE; // Level of the PHU
+    long numChips = 0;                  // Number of chips below the PHU; for setting efficient hash size
+    long numCells = 0;                  // Number of cells below the PHU; for setting efficient hash size
+    if (fpa->hdu) {
+        phu = fpa->hdu;
+        // Count the cells
+        psArray *chips = fpa->chips;    // Array of chips
+        numChips = chips->n;
+        for (int i = 0; i < chips->n; i++) {
+            pmChip *chip = chips->data[i]; // Chip of interest
+            numCells += chip->cells->n;
+        }
+        numCells = (float)numCells / (float)numChips + 0.5; // Average number of cells per chip
+        phuLevel = PM_FPA_LEVEL_FPA;
+    }
+    if (!phu && chip->hdu) {
+        phu = chip->hdu;
+        numChips = 1;
+        numCells = chip->cells->n;
+        phuLevel = PM_FPA_LEVEL_CHIP;
+    }
+    if (!phu && cell->hdu) {
+        phu = cell->hdu;
+        numChips = 0;
+        numCells = 1;
+        phuLevel = PM_FPA_LEVEL_CELL;
+    }
+    if (!phu || phuLevel == PM_FPA_LEVEL_NONE) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Can't find the PHU.\n");
+        return false;
+    }
+
+
+    // Find out what to read
+    psMetadata *shiftsInfo = psMetadataLookupMetadata(&mdok, phu->format, "SHIFTS"); // Shifts information
+    if (!mdok || !shiftsInfo) {
+        // We have read all the shifts that we have been told about --- which is none.
+        return true;
+    }
+    const char *shiftsExt = psMetadataLookupStr(&mdok, shiftsInfo, "EXTENSION"); // Extension name for shifts
+    if (!mdok || !shiftsExt) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "Can't find EXTENSION name in SHIFTS information from camera format.");
+        return false;
+    }
+    const char *chipCol = NULL;         // Column name for chip
+    if (phuLevel == PM_FPA_LEVEL_FPA) {
+        chipCol = psMetadataLookupStr(&mdok, shiftsInfo, "CHIP");
+        if (!mdok || !chipCol) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                    "Can't find CHIP column name in SHIFTS information from camera format.");
+            return false;
+        }
+    }
+    const char *cellCol = NULL;         // Column name for cell
+    if (phuLevel <= PM_FPA_LEVEL_CHIP) {
+        cellCol = psMetadataLookupStr(&mdok, shiftsInfo, "CELL");
+        if (!mdok || !chipCol) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                    "Can't find CELL column name in SHIFTS information from camera format.");
+            return false;
+        }
+    }
+    const char *tCol = psMetadataLookupStr(&mdok, shiftsInfo, "T");
+    if (!mdok || !tCol) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "Can't find T column name in SHIFTS information from camera format.");
+        return false;
+    }
+    const char *xCol = psMetadataLookupStr(&mdok, shiftsInfo, "X");
+    if (!mdok || !xCol) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "Can't find X column name in SHIFTS information from camera format.");
+        return false;
+    }
+    const char *yCol = psMetadataLookupStr(&mdok, shiftsInfo, "Y");
+    if (!mdok || !yCol) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "Can't find Y column name in SHIFTS information from camera format.");
+        return false;
+    }
+
+    bool tRel = psMetadataLookupBool(&mdok, shiftsInfo, "TRELATIVE");
+    if (!mdok) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "Can't find TRELATIVE in SHIFTS information from camera format.");
+        return false;
+    };
+    bool xyRel = psMetadataLookupStr(&mdok, shiftsInfo, "XYRELATIVE");
+    if (!mdok) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "Can't find XYRELATIVE in SHIFTS information from camera format.");
+        return false;
+    };
+
+    // Read the FITS file
+    int origExt = psFitsGetExtNum(fits); // Original extension number; to preserve position
+    if (!psFitsMoveExtName(fits, shiftsExt)) {
+        psError(PS_ERR_IO, false, "Unable to move to shifts extension %s", shiftsExt);
+        return false;
+    }
+    psArray *table = psFitsReadTable(fits); // The table of shifts
+    if (!table) {
+        psError(PS_ERR_IO, false, "Unable to read shifts table.\n");
+        return false;
+    }
+
+    // More sensible storage
+    pmShifts *singleShifts = NULL;      // Shifts for a single cell
+    psHash *multipleShifts = NULL;      // Shifts for multiple cells, stored by cell name
+    switch (phuLevel) {
+      case PM_FPA_LEVEL_FPA:
+        multipleShifts = psHashAlloc(2 * numChips);
+        break;
+      case PM_FPA_LEVEL_CHIP:
+        multipleShifts = psHashAlloc(2 * numCells);
+        break;
+      case PM_FPA_LEVEL_CELL:
+        singleShifts = pmShiftsAlloc(tRel, xyRel);
+        break;
+      default:
+        psAbort("Should never get here.\n");
+    }
+
+    // Pull values out of the table into something a bit more sensible
+    for (int i = 0; i < table->n; i++) {
+        psMetadata *row = table->data[i]; // The row of interest
+
+        const char *chipName = NULL;    // Name of chip
+        if (phuLevel == PM_FPA_LEVEL_FPA) {
+            // Only care about the chip name if there's more than one chip
+            chipName = psMetadataLookupStr(&mdok, row, chipCol);
+            if (!mdok || !chipName) {
+                psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find column %s in row %d of shifts table\n",
+                        chipCol, i);
+                psFree(multipleShifts);
+                psFree(table);
+                return false;
+            }
+        }
+        const char *cellName = NULL;    // Name of cell
+        if (phuLevel <= PM_FPA_LEVEL_CHIP) {
+            // Only care about the cell name if there's a chip
+            cellName = psMetadataLookupStr(&mdok, row, cellCol);
+            if (!mdok || !cellName) {
+                psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find column %s in row %d of shifts table\n",
+                        cellCol, i);
+                psFree(multipleShifts);
+                psFree(table);
+                return false;
+            }
+        }
+        float x = psMetadataLookupS32(&mdok, row, xCol); // Shift in x
+        if (!mdok) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find column %s in row %d of shifts table\n",
+                    xCol, i);
+            psFree(multipleShifts);
+            psFree(table);
+            return false;
+        }
+        float y = psMetadataLookupF32(&mdok, row, yCol); // Shift in y
+        if (!mdok) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find column %s in row %d of shifts table\n",
+                    yCol, i);
+            psFree(multipleShifts);
+            psFree(table);
+            return false;
+        }
+        float t = psMetadataLookupF32(&mdok, row, tCol); // Time of shift
+        if (!mdok) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find column %s in row %d of shifts table\n",
+                    tCol, i);
+            psFree(multipleShifts);
+            psFree(table);
+            return false;
+        }
+
+        pmShifts *shifts = NULL;        // Shifts for the cell of interest
+        switch (phuLevel) {
+          case PM_FPA_LEVEL_FPA: {
+              psHash *cells = psHashLookup(multipleShifts, chipName); // Hash of cells
+              if (!cells) {
+                  cells = psHashAlloc(numCells);
+                  psHashAdd(multipleShifts, chipName, cells);
+                  psFree(cells);          // Drop reference
+              }
+              shifts = cellVectors(cells, cellName, tRel, xyRel);
+              break;
+          }
+          case PM_FPA_LEVEL_CHIP:
+            shifts = cellVectors(multipleShifts, cellName, tRel, xyRel);
+            break;
+          case PM_FPA_LEVEL_CELL:
+            shifts = singleShifts;
+            break;
+          default:
+            psAbort("Should never get here.\n");
+        }
+
+        // Add the shift
+        psVectorExtend(shifts->x, 1, SHIFTS_BUFFER);
+        psVectorExtend(shifts->y, 1, SHIFTS_BUFFER);
+        psVectorExtend(shifts->t, 1, SHIFTS_BUFFER);
+        shifts->x->data.S32[shifts->num] = (int)x;
+        shifts->y->data.S32[shifts->num] = (int)y;
+        shifts->t->data.F32[shifts->num] = t;
+        shifts->num++;
+    }
+    psFree(table);
+
+    // Put the kernels into their own cells
+    if (phuLevel == PM_FPA_LEVEL_CELL) {
+        // Only a single cell
+        psMetadataAddPtr(cell->analysis, PS_LIST_TAIL, PM_SHIFTS_TABLE_NAME, PS_DATA_KERNEL,
+                         "Orthogonal transfer shifts", singleShifts);
+        psFree(singleShifts);
+        return true;
+    } else {
+        psList *names = psHashKeyList(multipleShifts); // List of hash keys (chip/cell names)
+        psListIterator *namesIter = psListIteratorAlloc(names, PS_LIST_HEAD, false); // Iterator for names
+        const char *name;               // Name, from iteration
+        while ((name = psListGetAndIncrement(namesIter))) {
+            switch (phuLevel) {
+              case PM_FPA_LEVEL_FPA: {
+                  int chipNum = pmFPAFindChip(fpa, name); // Number of chip of interest
+                  if (chipNum < 0) {
+                      psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find chip %s", name);
+                      psFree(namesIter);
+                      psFree(names);
+                      psFree(multipleShifts);
+                      return false;
+                  }
+                  pmChip *chip = fpa->chips->data[chipNum]; // Chip of interest
+
+                  // Loop over component cells
+                  psHash *cells = psHashLookup(multipleShifts, name); // Hash of cells
+                  psList *cellNames = psHashKeyList(multipleShifts); // List of hash keys (cell names)
+                  psListIterator *cellNamesIter = psListIteratorAlloc(cellNames, PS_LIST_HEAD, false);
+                  const char *cellName;       // Cell name, from iteration
+                  while ((cellName = psListGetAndIncrement(cellNamesIter))) {
+                      int cellNum = pmChipFindCell(chip, cellName); // Number of cell of interest
+                      if (!cell) {
+                          psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find cell %s", cellName);
+                          psFree(cellNamesIter);
+                          psFree(cellNames);
+                          psFree(namesIter);
+                          psFree(names);
+                          psFree(multipleShifts);
+                          return false;
+                      }
+                      pmCell *cell = chip->cells->data[cellNum]; // Cell of interest
+                      if (psMetadataLookup(cell->analysis, PM_SHIFTS_TABLE_NAME)) {
+                          // Already has a shifts table, for some reason
+                          psWarning("Chip %s, cell %s already has a shifts table --- overwriting\n",
+                                    name, cellName);
+                      }
+
+                      pmShifts *vectors = psHashLookup(cells, cellName); // Shifts for the cell of interest
+                      psMetadataAddPtr(cell->analysis, PS_LIST_TAIL, PM_SHIFTS_TABLE_NAME,
+                                       PS_DATA_KERNEL | PS_META_REPLACE,
+                                       "Orthogonal transfer shifts", vectors);
+                  }
+                  psFree(cellNamesIter);
+                  psFree(cellNames);
+                  break;
+              }
+              case PM_FPA_LEVEL_CHIP: {
+                  int cellNum = pmChipFindCell(chip, name); // Number of cell of interest
+                  if (!cell) {
+                      psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find cell %s", name);
+                      psFree(namesIter);
+                      psFree(names);
+                      psFree(multipleShifts);
+                      return false;
+                  }
+                  pmCell *cell = chip->cells->data[cellNum]; // Cell of interest
+                  if (psMetadataLookup(cell->analysis, PM_SHIFTS_TABLE_NAME)) {
+                      // Already has a shifts table, for some reason
+                      psWarning("Cell %s already has a shifts table --- overwriting\n", name);
+                  }
+
+                  pmShifts *vectors = psHashLookup(multipleShifts, name); // Shifts for this cell
+                  psMetadataAddPtr(cell->analysis, PS_LIST_TAIL, PM_SHIFTS_TABLE_NAME,
+                                   PS_DATA_KERNEL | PS_META_REPLACE,
+                                   "Orthogonal transfer shifts", vectors);
+                  break;
+              }
+              default:
+                psAbort("Should never get here.\n");
+            }
+        }
+        psFree(namesIter);
+        psFree(names);
+        psFree(multipleShifts);
+    }
+
+    // Go back to the original position in the FITS file
+    return psFitsMoveExtNum(fits, origExt, false);
+}
+
+
+// Generate a kernel and stuff it on the cell metadata
+bool pmShiftsKernel(const pmCell *cell     // Cell to which the shifts belong
+    )
+{
+    PS_ASSERT_PTR(cell, false);
+
+    bool mdok;                          // Status of MD lookup
+    pmShifts *shifts = psMetadataLookupPtr(&mdok, cell->analysis, PM_SHIFTS_TABLE_NAME);
+    if (!shifts) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find shifts table for cell.\n");
+        return false;
+    }
+
+    psKernel *kernel = psKernelGenerate(shifts->t, shifts->x, shifts->y,
+                                        shifts->tRelative, shifts->xyRelative); // Shift kernel
+    if (!kernel) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to generate kernel from OT shifts");
+        return false;
+    }
+    psMetadataAddPtr(cell->analysis, PS_LIST_TAIL, PM_SHIFTS_KERNEL_NAME, PS_DATA_KERNEL,
+                     "Orthogonal transfer kernel, calculated from shifts", kernel);
+    psFree(kernel);
+
+    return true;
+}
+
+bool pmShiftsConvolve(pmReadout *detrend, const pmCell *source, psMaskType maskVal)
+{
+    PS_ASSERT_PTR(detrend, false);
+    PS_ASSERT_PTR(source, false);
+
+    bool mdok;                          // Status of MD lookup
+    psKernel *kernel = psMetadataLookupPtr(&mdok, source->analysis, PM_SHIFTS_KERNEL_NAME);
+    if (!kernel) {
+        // Maybe they just forgot to generate the kernel with pmShiftsKernel
+        if (psMetadataLookup(source->analysis, PM_SHIFTS_TABLE_NAME)) {
+            if (!pmShiftsKernel(source)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to generate shifts kernel.");
+                return false;
+            }
+            // Hopefully it's there now
+            kernel = psMetadataLookupPtr(&mdok, source->analysis, PM_SHIFTS_KERNEL_NAME);
+            if (!kernel) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to find shifts kernel.");
+                return false;
+            }
+        } else {
+            psError(PS_ERR_UNKNOWN, false, "Unable to find shifts kernel or shifts table.");
+            return false;
+        }
+    }
+
+    if (detrend->image) {
+#if 1
+        // Always do direct convolution (no fuss with edge effects)
+        psImage *convolved = psImageConvolveDirect(NULL, detrend->image, kernel);
+#else
+        // Kernel size-dependent convolution --- if it's big, use the FFT
+        int xSize = kernel->xMax - kernel->xMin; // Kernel size in x
+        int ySize = kernel->yMax - kernel->yMin; // Kernel size in y
+        psImage *convolved;
+        if (xSize * ySize < FFT_SIZE * FFT_SIZE) {
+            convolved = psImageConvolveDirect(NULL, detrend->image, kernel);
+        } else {
+            // This is a little dodgy --- making choices about parameters without the user's input
+            psStats *stats = psImageStats(PS_STAT_ROBUST_MEDIAN);
+            stats->nSubsample = 10000;
+            psImageBackground(stats, detrend->image, detrend->mask, maskVal, NULL);
+            convolved = psImageConvolveFFT(detrend->image, kernel, stats->robustMedian);
+        }
+#endif
+        if (!convolved) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to convolve detrend image.");
+            return false;
+        }
+        psFree(detrend->image);
+        detrend->image = convolved;
+    }
+
+    // Purposely ignoring the weight map --- don't care about the weight map for a detrend image
+
+    if (maskVal && detrend->mask && !psImageConvolveMaskDirect(detrend->mask, detrend->mask, maskVal, 0,
+                                                               kernel->xMin, kernel->xMax,
+                                                               kernel->yMin, kernel->yMax)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to convolve detrend mask.");
+        return false;
+    }
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmShifts.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmShifts.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmShifts.h	(revision 22157)
@@ -0,0 +1,51 @@
+#ifndef PM_SHIFTS_H
+#define PM_SHIFTS_H
+
+#define PM_SHIFTS_TABLE_NAME "SHIFTS.TABLE" ///< Name for table on the analysis metadata
+#define PM_SHIFTS_KERNEL_NAME "SHIFTS.KERNEL" ///< Name for kernel on the analysis metadata
+
+/// Shifts due to orthogonal transfer
+typedef struct {
+    psVector *x;                        ///< Shifts in x
+    psVector *y;                        ///< Shifts in y
+    psVector *t;                        ///< Times of shifts
+    long num;                           ///< Number of values
+    bool tRelative;                     ///< Are the time values relative (durations)?
+    bool xyRelative;                    ///< Are the shift (x,y) values relative to the previous position?
+} pmShifts;
+
+/// Allocator for pmShifts
+pmShifts *pmShiftsAlloc(bool tRel,      ///< Are the time values relative (durations)?
+                        bool xyRel      ///< Are the shift (x,y) values relative to the previous position?
+                        );
+
+/// Read orthogonal transfer shifts table for a cell
+///
+/// Given a cell, this function searches for the orthogonal transfer shifts for this cell in the supplied FITS
+/// file.  The FITS extension containing the shifts table is specified by the SHIFTS keyword within the FILE
+/// information in the camera format.  If the extension is found, the shifts table is read and translated into
+/// kernels which are placed in the analysis metadata of each cell.  Note that this operation is performed on
+/// all cells within the FITS file (or at least, those contained within the shifts table), not just the cell
+/// provided --- if we have to read the whole table, we may as well translate the whole lot.  If a kernel is
+/// already present in the cell analysis metadata, the function returns true without doing any work.
+bool pmShiftsRead(const pmCell *cell,         ///< Cell for which to search for shifts
+                  psFits *fits          ///< FITS file in which to search for OT shifts extension
+                  );
+
+
+/// Generate a kernel for the cell from the orthogonal transfer shifts
+///
+/// The kernel is saved in the analysis metadata
+bool pmShiftsKernel(const pmCell *cell   ///< Cell for which to generate kernel
+                    );
+
+/// Convolve a detrend with the appropriate orthogonal transfer convolution kernel from a science exposure.
+///
+/// The kernel is generated (with pmShiftsKernel) if required.  The image and mask are convolved with the
+/// kernel (specified maskVal is smeared).  The weight map is not convolved.
+bool pmShiftsConvolve(pmReadout *detrend, ///< Detrend readout to convolve
+                      const pmCell *source, ///< Science exposure, containing a shifts kernel
+                      psMaskType maskVal ///< Mask value to smear
+                      );
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmShutterCorrection.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmShutterCorrection.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmShutterCorrection.c	(revision 22157)
@@ -0,0 +1,1080 @@
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <strings.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmHDUUtils.h"
+#include "psVectorBracket.h"
+#include "pmConceptsAverage.h"
+#include "pmReadoutStack.h"
+
+#include "pmShutterCorrection.h"
+
+/// Measure shutter correction:
+///
+/// input  : collection of shutter correction exposures (pre-processed)
+/// output : a shutter correction image
+///
+/// The measurement could be performed on any focal-plane unit at a time. for GPC, the obvious scale is to
+/// measure the effect on the entire focal plane at once, with a single reference point in the field.  this is
+/// a little more complex than just measuring the effect for a single 2D image array.  the reference point and
+/// the detailed analysis points need to be defined for the entire hierarchy rather than just as coordinate
+/// pairs or regions.  a pmFPAview would be a natural element with which to define these points, but at the
+/// moment, the pmFPAview structure defines a band in the CCD, not a coordinate.  An option is to instead
+/// specify the reference locations as a pmFPAview coupled with a psRegion, though we need to be careful not
+/// to over-specify the pixels (ie, conflict between pmFPAview and psRegion).
+///
+/// At each point in an image with exposure time T, we measure f(k;T) = F(k;T) / F(0;T) where k is the
+/// coordinate of the point of interest, 0 is the reference coordinate, and F(k;T) is the measured number of
+/// counts at the point of interest in this image.  given a collection of f(k;T) values, we need to determine
+/// the model f(k;T) = A(k) (T + dTk) / (T + dTo) where dTk is the shutter error at the given position, dTo is
+/// the shutter error at the reference position, and A(k) is the scaling factor for the given position.
+///
+/// The process for generating a shutter correction is as follows:
+/// - for each image
+/// -- measure the reference point counts
+/// - for each analysis region:
+/// -- measure shutter parameters (dTo, dTk, A):
+/// --- for each image:
+/// ---- measure counts at the region
+/// ---- divide by the reference counts
+/// --- linear extrapolation to find f(inf) = A(k)
+/// --- linear extrapolation to find f(0) = A(k) dTk / dTo
+/// --- linear interpolation to find coordinate where f(dTo) = A (1 + dTk/dTo) / 2
+/// --- non-linear fit of T, f(T) to f(k;T) = A(k) (T + dTk) / (T + dTo)
+/// - use the collection of dTo values to choose a best value for dTo (median)
+/// - for each image pixel
+/// -- divide by the reference counts
+/// -- generate the vectors T, f(T)
+/// -- linear fit of T, f(T) to f(k;T) = A(k) (T + dTk) / (T + dTo) using dTo above
+/// -- save dTk, A(k) in output image pixels
+/// -- apply dTk, A(k) to measure residual images
+/// -- generate residual FITS/JPEG images
+
+
+#define MEASURE_SAMPLES 4               // Number of samples to make over the image.  This should only be
+                                        // changed with great caution, since assumptions on its value are in
+                                        // the code (see pmShutterCorrectionDataAlloc).
+
+
+static void pmShutterCorrectionFree(pmShutterCorrection *pars)
+{
+    // Nothing to free
+    return;
+}
+
+pmShutterCorrection *pmShutterCorrectionAlloc()
+{
+    pmShutterCorrection *corr = (pmShutterCorrection*)psAlloc(sizeof(pmShutterCorrection));
+    psMemSetDeallocator(corr, (psFreeFunc)pmShutterCorrectionFree);
+
+    corr->scale  = 0.0;
+    corr->offset = 0.0;
+    corr->offref = 0.0;
+    corr->num = 0;
+    corr->stdev = NAN;
+    corr->valid = true;
+
+    return corr;
+}
+
+pmShutterCorrection *pmShutterCorrectionGuess(const psVector *exptime, const psVector *counts)
+{
+    // NOTE: vectors must be sorted on input.  It is expensive to sort or check this here, but
+    // it is easy to arrange by sorting the images before generating these vectors.
+
+    PS_ASSERT_VECTOR_NON_NULL(exptime, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(counts, NULL);
+    PS_ASSERT_VECTOR_TYPE(exptime, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTOR_TYPE(counts, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(exptime, counts, NULL);
+    if (exptime->n <= 2) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                "Require more than 2 exposures to guess shutter correction.\n");
+        return NULL;
+    }
+
+    long N = exptime->n;                // Number of exposures
+
+    // use interpolation to guess shutter correction parameters given a set of exposures times and normalized
+    // counts (divided by the reference counts for each image)
+
+    pmShutterCorrection *corr = pmShutterCorrectionAlloc(); // Shutter correction, to be returned
+    psPolynomial1D *line = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1); // Straight line, for extrapolation
+
+    // choose the highest exptime point as the guess for the scale:
+    // XXX we could examine the top 2 or 3 values and decide if we
+    // extended exptime enough or median clip.
+    corr->scale = counts->data.F32[N-1];
+
+    // fit a line to the lowest three points and extrapolate to 0.0
+    psVector *tmpX = psVectorAlloc(2, PS_TYPE_F32);
+    psVector *tmpY = psVectorAlloc(2, PS_TYPE_F32);
+
+    long index;
+
+    // Iterate only
+    for (index = 0; !isfinite(exptime->data.F32[index]) && index < N - 1; index++); 
+
+    if (index == N - 1) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "Not enough good values to guess shutter correction.\n");
+        goto GUESS_ERROR;
+    }
+    tmpX->data.F32[0] = exptime->data.F32[index];
+    tmpY->data.F32[0] = counts->data.F32[index];
+
+    for (index++;
+            (!isfinite(exptime->data.F32[index]) || exptime->data.F32[index] == exptime->data.F32[0]) &&
+            index < N; index++)
+        ; // Iterate only
+    if (index == N) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "Exposure times are all identical --- cannot guess shutter correction.\n");
+        goto GUESS_ERROR;
+    }
+    tmpY->data.F32[1] = counts->data.F32[index];
+    tmpX->data.F32[1] = exptime->data.F32[index];
+
+    // fit a line and extrapolate the fit to 0.0
+    if (!psVectorFitPolynomial1D(line, NULL, 0, tmpY, NULL, tmpX)) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to fit for the time offset.\n");
+        goto GUESS_ERROR;
+    }
+    float ratio = psPolynomial1DEval(line, 0.0) / corr->scale;
+
+    // XXX we need a sanity check:
+    // if the mean value of the three points is higher than corr->scale,
+    // then the slope should be negative.
+    // if the mean value of the three points is lower than corr->scale,
+    // then the slope should be positive.
+
+    // find two points bracketing the value counts = A (1 + dTk/dTo) / 2 = corr->scale (1 + ratio) / 2
+    float value = corr->scale * (1 + ratio) / 2.0;
+
+    int Np;                             // Index of the value above (positive side)
+    if (ratio < 1.0) {
+        Np = psVectorBracket(counts, value, true);
+    } else {
+        Np = psVectorBracketDescend(counts, value, true);
+    }
+    int Nm = (Np == 0) ? 1 : Np - 1;    // Index of the value below (negative side)
+
+    tmpX->data.F32[0] = counts->data.F32[Nm];
+    tmpX->data.F32[1] = counts->data.F32[Np];
+    tmpY->data.F32[0] = exptime->data.F32[Nm];
+    tmpY->data.F32[1] = exptime->data.F32[Np];
+
+    // fit a line and extrapolate the fit to counts = A (1 + dTk/dTo) : exptime = dTo
+    if (!psVectorFitPolynomial1D (line, NULL, 0, tmpY, NULL, tmpX)) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to fit for the reference offset.\n");
+        goto GUESS_ERROR;
+    }
+    corr->offref = psPolynomial1DEval(line, value);
+    corr->offset = ratio * corr->offref;
+
+    psFree(line);
+    psFree(tmpX);
+    psFree(tmpY);
+
+    return corr;
+
+GUESS_ERROR:
+    psFree(tmpX);
+    psFree(tmpY);
+    psFree(line);
+    psFree(corr);
+    return NULL;
+}
+
+// linear fit to the counts and exptime, given a value for offref
+pmShutterCorrection *pmShutterCorrectionLinFit(const psVector *exptime, const psVector *counts,
+                                               const psVector *cntError, const psVector *mask, float offref,
+                                               int nIter, float rej, psMaskType maskVal)
+{
+    PS_ASSERT_VECTOR_NON_NULL(exptime, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(counts, NULL);
+    PS_ASSERT_VECTOR_TYPE(exptime, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTOR_TYPE(counts, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(exptime, counts, NULL);
+    if (exptime->n <= 2) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                "Require more than 2 exposures to guess shutter correction.\n");
+        return NULL;
+    }
+    if (cntError) {
+        PS_ASSERT_VECTOR_TYPE(cntError, PS_TYPE_F32, NULL);
+        PS_ASSERT_VECTORS_SIZE_EQUAL(counts, cntError, NULL);
+    }
+    PS_ASSERT_FLOAT_LARGER_THAN(offref, 0.0, NULL);
+
+    // this step is identical for all pixels: do it once and save?
+    psVector *x = psVectorAlloc(exptime->n, PS_TYPE_F32);
+    psVector *y = psVectorAlloc(exptime->n, PS_TYPE_F32);
+
+    for (long i = 0; i < exptime->n; i++) {
+        // Should be safe (if expensive) to stick NaNs in --- the fitter deals with them
+        float value = 1.0 / (exptime->data.F32[i] + offref);
+        x->data.F32[i] = exptime->data.F32[i] * value;
+        y->data.F32[i] = value;
+    }
+
+    psPolynomial2D *line = psPolynomial2DAlloc (PS_POLYNOMIAL_ORD, 1, 1);
+
+    // mask out the terms we will not fit
+    line->coeffMask[0][0] = PS_POLY_MASK_SET;
+    line->coeffMask[1][1] = PS_POLY_MASK_SET;
+    line->coeff[0][0] = 0;
+    line->coeff[1][1] = 0;
+
+    // the stats structure determines how the clipping statistic is measured
+    // too few points to use the robust analysis method
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+    stats->clipSigma = rej;
+    stats->clipIter = nIter;
+
+    if (!psVectorClipFitPolynomial2D(line, stats, mask, maskVal, counts, cntError, x, y)) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to fit shutter correction.\n");
+        psFree(stats);
+        psFree(x);
+        psFree(y);
+        psFree(line);
+        return NULL;
+    }
+
+    pmShutterCorrection *corr = pmShutterCorrectionAlloc();
+    corr->offref = offref;
+    corr->scale  = line->coeff[1][0];
+    corr->offset = line->coeff[0][1] / line->coeff[1][0];
+    corr->num = stats->clippedNvalues;
+    corr->stdev = stats->clippedStdev;
+
+    psFree(stats);
+    psFree(x);
+    psFree(y);
+    psFree(line);
+
+    return corr;
+}
+
+static psF32 pmShutterCorrectionModel(psVector *deriv, const psVector *params, const psVector *x)
+{
+    // This is in a tight loop, so we won't assert here.
+
+    psF32 A = params->data.F32[0];
+    psF32 p = x->data.F32[0] + params->data.F32[1];
+    psF32 q = 1.0 / (x->data.F32[0] + params->data.F32[2]);
+    psF32 f = A * p * q;
+
+    if (deriv) {
+        deriv->data.F32[0] = p * q;
+        deriv->data.F32[1] = A * q;
+        deriv->data.F32[2] = - f * q;
+    }
+    return f;
+}
+
+// non-linear fit to the counts and exptime, given a guess for the three parameters
+pmShutterCorrection *pmShutterCorrectionFullFit(const psVector *exptime, const psVector *counts,
+                                                const psVector *cntError, const pmShutterCorrection *guess)
+{
+    PS_ASSERT_VECTOR_NON_NULL(exptime, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(counts, NULL);
+    PS_ASSERT_VECTOR_TYPE(exptime, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTOR_TYPE(counts, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(exptime, counts, NULL);
+    if (exptime->n <= 2) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                "Require more than 2 exposures to guess shutter correction.\n");
+        return NULL;
+    }
+    if (cntError) {
+        PS_ASSERT_VECTOR_TYPE(cntError, PS_TYPE_F32, NULL);
+        PS_ASSERT_VECTORS_SIZE_EQUAL(counts, cntError, NULL);
+    }
+    PS_ASSERT_PTR_NON_NULL(guess, NULL);
+
+    psMinimization *minInfo = psMinimizationAlloc(15, 0.1); // Minimization information
+
+    psVector *params = psVectorAlloc (3, PS_TYPE_F32); // Fitting parameters
+    params->data.F32[0] = guess->scale;
+    params->data.F32[1] = guess->offset;
+    params->data.F32[2] = guess->offref;
+
+    // XXX for the moment, don't set any constraints
+    // psMinConstraint *constraint = psMinConstraintAlloc();
+    // constrain->checkLimits = pmShutterParamLimits;
+    // constrain->paramMask   = NULL;
+    psMinConstraint *constraint = NULL;   // Constraints on the minimization
+
+    // XXX ignore covariance matrix for the moment
+    // psImage *covar = psImageAlloc (params->n, params->n, PS_TYPE_F64);
+    psImage *covar = NULL;              // Covariance matrix
+
+    // construct the coordinate and value entries (y is counts)
+    psArray *x = psArrayAlloc(exptime->n); // Coordinates
+
+    for (long i = 0; i < exptime->n; i++) {
+        psVector *coord = psVectorAlloc(1, PS_TYPE_F32);
+        coord->data.F32[0] = exptime->data.F32[i];
+        x->data[i] = coord;
+    }
+
+    if (!psMinimizeLMChi2(minInfo, covar, params, constraint, x, counts, cntError, pmShutterCorrectionModel)) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to fit for shutter correction.\n");
+        psFree(x);
+        psFree(minInfo);
+        psFree(params);
+        return NULL;
+    }
+
+    pmShutterCorrection *corr = pmShutterCorrectionAlloc(); // Shutter correction
+    corr->scale  = params->data.F32[0];
+    corr->offset = params->data.F32[1];
+    corr->offref = params->data.F32[2];
+
+    // apply the correction and measure the residual scatter
+    psVector *resid = psVectorAlloc (exptime->n, PS_TYPE_F32);
+    for (int i = 0; i < exptime->n; i++) {
+	float fitCounts = corr->scale * (exptime->data.F32[i] + corr->offset) / (exptime->data.F32[i] + corr->offref);
+	resid->data.F32[i] = counts->data.F32[i] - fitCounts;
+    }
+    
+    psStats *rawStats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+    psStats *resStats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+    psVectorStats (rawStats, counts, NULL, NULL, 0);
+    psVectorStats (resStats, resid, NULL, NULL, 0);
+
+    // XXX temporary hard-wired minimum stdev improvement factor 
+    psTrace("psModules.detrend", 3, "raw scatter %f vs res scatter %f\n", rawStats->sampleStdev, resStats->sampleStdev);
+    if (rawStats->sampleStdev / resStats->sampleStdev < 1.5) corr->valid = false;
+
+    psFree (rawStats);
+    psFree (resStats);
+    psFree (resid);
+
+    psFree(minInfo);
+    psFree(params);
+    psFree(x);
+
+    return corr;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmShutterCorrectionMeasure(pmReadout *output, const psArray *readouts, int size, psStatsOptions meanStat,
+                                psStatsOptions stdevStat, int nIter, float rej, psMaskType maskVal)
+{
+    PS_ASSERT_ARRAY_NON_NULL(readouts, NULL);
+    PS_ASSERT_ARRAY_NON_EMPTY(readouts, NULL);
+    PS_ASSERT_INT_POSITIVE(size, NULL);
+
+    long num = readouts->n;             // Number of readouts
+    PS_ASSERT_INT_POSITIVE(nIter, NULL);
+    PS_ASSERT_FLOAT_LARGER_THAN(rej, 0.0, NULL);
+
+    psArray *images = psArrayAlloc(num);// Array of images
+    psArray *masks = NULL; // Array of masks
+    psArray *weights = NULL; // Array of weights
+    psVector *exptimes = psVectorAlloc(num, PS_TYPE_F32); // Vector of exposure times
+
+    {
+        pmReadout *readout = readouts->data[0]; // Representative readout
+        if (readout->mask)
+        {
+            masks = psArrayAlloc(num);
+        }
+        if (readout->weight)
+        {
+            weights = psArrayAlloc(num);
+        }
+    }
+
+    // Check input sizes, generate first-pass statistics
+    psRegion refRegion;                 // Reference region
+    psVector *refs = psVectorAlloc(num, PS_TYPE_F32); // Reference measurements
+    psVectorInit(refs, 0);
+    psArray *regions = psArrayAlloc(MEASURE_SAMPLES); // Array of sample regions, made on each image
+    psImage *samplesMean = psImageAlloc(num, MEASURE_SAMPLES, PS_TYPE_F32); // Measurements for each file
+    psImage *samplesStdev = psImageAlloc(num, MEASURE_SAMPLES, PS_TYPE_F32); // Errors for each file
+    psStats *stats = psStatsAlloc(meanStat | stdevStat);
+    int numRows = 0, numCols = 0; // Size of images
+    for (long i = 0; i < images->n; i++) {
+        pmReadout *readout = readouts->data[i]; // Readout of interest
+        if (!readout) {
+            continue;
+        }
+
+        bool mdok;                      // Status of MD lookup
+        float exptime = psMetadataLookupF32(&mdok, readout->parent->concepts, "CELL.EXPOSURE"); // Exp. time
+        if (!mdok || !isfinite(exptime)) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Exposure time for readout %ld is not set.\n", i);
+            goto MEASURE_ERROR;
+        }
+        exptimes->data.F32[i] = exptime;
+
+        psImage *image = readout->image; // Image of interest
+        if (!image) {
+            continue;
+        }
+        images->data[i] = psMemIncrRefCounter(image);
+        if (image->type.type != PS_TYPE_F32) {
+            psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Bad type for image: %x\n", image->type.type);
+            goto MEASURE_ERROR;
+        }
+        if (numRows == 0 || numCols == 0) {
+            numRows = image->numRows;
+            numCols = image->numCols;
+	    // define the reference region : a box of size 'size' at the center
+            refRegion = psRegionForSquare(0.5 * numCols, 0.5 * numRows, size);
+            // Set up the sample regions : boxes of size 'size' at the 4 image corners
+            for (int j = 0; j < MEASURE_SAMPLES; j++) {
+                int x = (j % 2) ? size : image->numCols - size;
+                int y = (j > 1) ? size : image->numRows - size;
+                psRegion region = psRegionForSquare(x, y, size);
+                region = psRegionForImage(image, region);
+                regions->data[j] = psRegionAlloc(region.x0, region.x1, region.y0, region.y1);
+            }
+        } else if (numRows != image->numRows || numCols != image->numCols) {
+            psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                    "Image sizes don't match: %dx%d vs %dx%d\n", image->numCols, image->numRows,
+                    numCols, numRows);
+            goto MEASURE_ERROR;
+        }
+        psImage *mask = readout->mask; // Mask of interest
+        if (mask) {
+            if (!masks) {
+                psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Not all readouts have masks.\n");
+                goto MEASURE_ERROR;
+            }
+            masks->data[i] = psMemIncrRefCounter(mask);
+
+            if (mask->type.type != PS_TYPE_U8) {
+                psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Bad type for mask: %x\n", mask->type.type);
+                goto MEASURE_ERROR;
+            }
+            if (mask->numRows != numRows || mask->numCols != numCols) {
+                psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                        "Mask sizes don't match: %dx%d vs %dx%d\n", mask->numCols, mask->numRows,
+                        numCols, numRows);
+                goto MEASURE_ERROR;
+            }
+        } else if (masks) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Not all readouts have masks.\n");
+            goto MEASURE_ERROR;
+        }
+
+        psImage *weight = readout->weight; // Weight map of interest
+        if (weight) {
+            if (!weights) {
+                psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Not all readouts have weights.\n");
+                goto MEASURE_ERROR;
+            }
+            weights->data[i] = psMemIncrRefCounter(weight);
+
+            if (weight->type.type != PS_TYPE_F32) {
+                psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Bad type for weights: %x\n", weight->type.type);
+                goto MEASURE_ERROR;
+            }
+            if (weight->numRows != numRows || weight->numCols != numCols) {
+                psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                        "Weight sizes don't match: %dx%d vs %dx%d\n", weight->numCols, weight->numRows,
+                        numCols, numRows);
+                goto MEASURE_ERROR;
+            }
+        } else if (weights) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Not all readouts have weights.\n");
+            goto MEASURE_ERROR;
+        }
+
+
+        // Measure statistics
+        if (!psImageStats(stats, image, mask, maskVal)) {
+            psWarning("Unable to measure reference statistics.\n");
+        }
+        refs->data.F32[i] = psStatsGetValue(stats, meanStat);
+        psTrace("psModules.detrend", 3, "Reference value for image %ld = %f\n", i, refs->data.F32[i]);
+        if (refs->data.F32[i] <= 0.0) {
+            psError(PS_ERR_UNKNOWN, true, "Measured non-positive reference value.\n");
+            goto MEASURE_ERROR;
+        }
+        refs->data.F32[i] = 1.0 / refs->data.F32[i];
+        for (int j = 0; j < MEASURE_SAMPLES; j++) {
+            psRegion *region = regions->data[j]; // Region of interest
+            psImage *subImage = psImageSubset(image, *region); // Sub-image
+            psImage *subMask = NULL;
+            if (mask) {
+                subMask = psImageSubset(mask, *region);
+            }
+            if (!psImageStats(stats, subImage, subMask, maskVal)) {
+                psString regionString = psRegionToString(*region);
+                psWarning("Unable to measure sample statistics at %s in image %ld.\n",
+                          regionString, i);
+                psFree(regionString);
+            }
+            psFree(subImage);
+            samplesMean->data.F32[j][i] = psStatsGetValue(stats, meanStat) * refs->data.F32[i];
+            samplesStdev->data.F32[j][i] = psStatsGetValue(stats, stdevStat) * refs->data.F32[i];
+            psTrace("psModules.detrend", 5, "Image %ld, sample %d: %f +/- %f\n", i, j,
+                    samplesMean->data.F32[j][i], samplesStdev->data.F32[j][i]);
+        }
+    }
+    psFree(regions);
+    psFree(stats);
+
+    float meanRef = 0.0;                // Mean reference offset
+    int numGood = 0;                    // Number of good measurements
+    psVector *counts = psVectorAlloc(num, PS_TYPE_F32); // Mean for each image
+    psVector *errors = psVectorAlloc(num, PS_TYPE_F32); // Stdev for each image
+    for (int i = 0; i < MEASURE_SAMPLES; i++) {
+        counts = psImageRow(counts, samplesMean, i);
+        errors = psImageRow(errors, samplesStdev, i);
+        pmShutterCorrection *guess = pmShutterCorrectionGuess(exptimes, counts); // Guess at correction
+        pmShutterCorrection *corr = pmShutterCorrectionFullFit(exptimes, counts, errors, guess); // Correct'n
+        if (!corr) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to measure shutter reference correction.\n");
+            psFree(guess);
+            psFree(counts);
+            psFree(errors);
+            goto MEASURE_ERROR;
+        }
+        psTrace("psModules.detrend", 5, "Sample reference value: %f\n", corr->offref);
+        if (isfinite(corr->offref)) {
+            meanRef += corr->offref;
+            numGood++;
+        }
+        psFree(corr);
+        psFree(guess);
+    }
+    psFree(samplesMean);
+    psFree(samplesStdev);
+
+    if (numGood == 0) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to measure mean reference offset.\n");
+        psFree(counts);
+        psFree(errors);
+        goto MEASURE_ERROR;
+    }
+    meanRef /= (float)numGood;
+    psTrace("psModules.detrend", 3, "Mean reference value: %f\n", meanRef);
+
+    // Check the weights
+    if (weights && nIter > 1) {
+        for (int i = 0; i < weights->n && nIter > 1; i++) {
+            psImage *weight = weights->data[i]; // Weight image
+            if (!weight) {
+                // We don't have weights, so no realistic errors: turn off iteration
+                if (nIter > 0) {
+                    psWarning("Not all images have weights --- turning iteration off.\n");
+                }
+                nIter = 1;
+            }
+        }
+    }
+
+    psImage *shutter = psImageAlloc(numCols, numRows, PS_TYPE_F32); // Shutter correction image
+    psImage *pattern = psImageAlloc(numCols, numRows, PS_TYPE_F32); // Illumination pattern
+    psVector *mask = psVectorAlloc(num, PS_TYPE_U8); // Mask for each image
+    psVectorInit(mask, 0);
+    psTrace("psModules.detrend", 2, "Performing linear fit on individual pixels...\n");
+    for (int y = 0; y < numRows; y++) {
+        for (int x = 0; x < numCols; x++) {
+            for (int i = 0; i < num; i++) {
+                psImage *image = images->data[i]; // Image of interest
+                counts->data.F32[i] = image->data.F32[y][x] * refs->data.F32[i];
+                psImage *maskImage;     // Mask image
+                if (masks && (maskImage = masks->data[i])) {
+                    mask->data.U8[i] = maskImage->data.U8[y][x];
+                }
+                psImage *weight;        // Weight image
+                if (weights && (weight = weights->data[i])) {
+                    errors->data.F32[i] = sqrtf(weight->data.F32[y][x]) * refs->data.F32[i];
+                } else {
+                    errors->data.F32[i] = sqrtf(image->data.F32[y][x]) * refs->data.F32[i];
+                }
+            }
+
+            pmShutterCorrection *corr = pmShutterCorrectionLinFit(exptimes, counts, errors, mask, meanRef,
+                                        nIter, rej, maskVal);
+            shutter->data.F32[y][x] = corr->offset;
+            pattern->data.F32[y][x] = corr->scale;
+            psFree(corr);
+        }
+    }
+    psFree(mask);
+    psFree(counts);
+    psFree(errors);
+    psFree(refs);
+
+    if (psTraceGetLevel("psModules.detrend") > 5) {
+        psFits *fits = psFitsOpen("pattern.fits", "w");
+        psFitsWriteImage(fits, NULL, pattern, 0, NULL);
+        psFitsClose(fits);
+    }
+    psFree(pattern);
+
+    output->image = shutter;
+
+    // Update the "concepts"
+    psList *inputCells = psListAlloc(NULL); // List of cells
+    for (long i = 0; i < readouts->n; i++) {
+        pmReadout *readout = readouts->data[i]; // Readout of interest
+        psListAdd(inputCells, PS_LIST_TAIL, readout->parent);
+    }
+    bool success = pmConceptsAverageCells(output->parent, inputCells, NULL, NULL, true);
+    psFree(inputCells);
+
+    // Correct the exposure times --- they don't make sense any more.
+    psMetadataItem *item = psMetadataLookup(output->parent->concepts, "CELL.EXPOSURE");
+    item->data.F32 = NAN;
+    item = psMetadataLookup(output->parent->concepts, "CELL.DARKTIME");
+    item->data.F32 = NAN;
+
+    return success;
+
+
+MEASURE_ERROR:
+    // Clean up after error
+    psFree(exptimes);
+    psFree(images);
+    psFree(masks);
+    psFree(weights);
+    psFree(refs);
+    psFree(regions);
+    psFree(stats);
+    psFree(samplesMean);
+    psFree(samplesStdev);
+    return false;
+}
+
+
+bool pmShutterCorrectionApply(pmReadout *readout, const pmReadout *shutter, psMaskType blank)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(shutter, false);
+    PS_ASSERT_IMAGE_NON_NULL(readout->image, false);
+    PS_ASSERT_IMAGE_NON_NULL(shutter->image, false);
+    PS_ASSERT_IMAGE_TYPE(readout->image, PS_TYPE_F32, false);
+    PS_ASSERT_IMAGE_TYPE(shutter->image, PS_TYPE_F32, false);
+
+    psRegion region = psRegionSet(readout->col0, readout->col0 + readout->image->numCols,
+                                  readout->row0, readout->row0 + readout->image->numRows); // Detector region
+
+    pmCell *cell = readout->parent;     // Parent cell
+    if (!cell) {
+        psError(PS_ERR_BAD_PARAMETER_NULL, true,
+                "Parent cell is NULL --- unable to determine exposure time.\n");
+        return false;
+    }
+    float exptime = psMetadataLookupF32(NULL, cell->concepts, "CELL.EXPOSURE"); // Exposure time
+    if (!isfinite(exptime)) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Bad exposure time: %f.\n", exptime);
+        return false;
+    }
+
+    pmHDU *hdu = pmHDUFromCell(cell);// HDU  of interest
+
+    psVector *md5 = psImageMD5(shutter->image); // md5 hash
+    psString md5string = psMD5toString(md5); // String
+    psFree(md5);
+    psStringPrepend(&md5string, "Shutter image MD5: ");
+    psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, md5string, "");
+    psFree(md5string);
+
+
+    psImage *shutterImage = psImageSubset(shutter->image, region); // Subimage with shutter
+    if (!shutterImage) {
+        psString regionString = psRegionToString(region);
+        psError(PS_ERR_BAD_PARAMETER_VALUE, false, "Size mismatch: %s vs %dx%d\n",
+                regionString, shutter->image->numCols, shutter->image->numRows);
+        psFree(regionString);
+        psFree(shutterImage);
+        return false;
+    }
+    psImage *image = readout->image;    // Image to correct
+    psImage *mask = readout->mask;      // Corresponding mask
+
+    if (exptime <= 0.0) {
+        // In the extreme case that we have exptime <= 0.0, we correct the image to
+        // counts-per-second, rather than counts in the nominal exposure time
+        for (int y = 0; y < image->numRows; y++) {
+            for (int x = 0; x < image->numCols; x++) {
+                if (mask && !isfinite(shutterImage->data.F32[y][x])) {
+                    mask->data.PS_TYPE_MASK_DATA[y][x] |= blank;
+                    image->data.F32[y][x] = NAN;
+                    continue;
+                }
+                image->data.F32[y][x] *= 1.0 / (exptime + shutterImage->data.F32[y][x]);
+            }
+        }
+        psMetadataAddF32 (cell->concepts, PS_LIST_TAIL, "CELL.EXPOSURE", PS_META_REPLACE, "exposure time re-normalized to 1.0", 1.0); // Exposure time
+        psString line = NULL;
+        psStringAppend (&line, "extreme exposure time %f, re-normalized to 1.0", exptime);
+        psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, line, "");
+        psFree (line);
+    } else {
+        for (int y = 0; y < image->numRows; y++) {
+            for (int x = 0; x < image->numCols; x++) {
+                if (mask && !isfinite(shutterImage->data.F32[y][x])) {
+                    mask->data.PS_TYPE_MASK_DATA[y][x] |= blank;
+                    image->data.F32[y][x] = NAN;
+                    continue;
+                }
+                image->data.F32[y][x] *= exptime / (exptime + shutterImage->data.F32[y][x]);
+            }
+        }
+    }
+    psFree(shutterImage);
+
+    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now, used for reporting
+    psString timeString = psTimeToISO(time); // String with time
+    psFree(time);
+    psStringPrepend(&timeString, "Shutter correction completed at ");
+    psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK,
+                     timeString, "");
+    psFree(timeString);
+
+
+    return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+#define IMAGES_BUFFER 10                // Allocate space for this many images at a time
+
+static void shutterCorrectionDataFree(pmShutterCorrectionData *data)
+{
+    psFree(data->regions);
+    psFree(data->mean);
+    psFree(data->stdev);
+
+    psFree(data->exptimes);
+    psFree(data->refs);
+
+    return;
+}
+
+pmShutterCorrectionData *pmShutterCorrectionDataAlloc(int numCols, int numRows, int size)
+{
+    pmShutterCorrectionData *data = psAlloc(sizeof(pmShutterCorrectionData));
+    psMemSetDeallocator(data, (psFreeFunc)shutterCorrectionDataFree);
+
+    data->num = 0;
+    data->numCols = 0;
+    data->numRows = 0;
+
+    data->regions = psArrayAlloc(MEASURE_SAMPLES);
+    for (int j = 0; j < MEASURE_SAMPLES; j++) {
+        int x = (j % 2) ? size : numCols - size - 1;
+        int y = (j > 1) ? size : numRows - size - 1;
+        psRegion region = psRegionForSquare(x, y, size);
+        data->regions->data[j] = psRegionAlloc(region.x0, region.x1, region.y0, region.y1);
+    }
+
+    data->mean = psArrayAlloc(MEASURE_SAMPLES);
+    data->stdev = psArrayAlloc(MEASURE_SAMPLES);
+    for (int i = 0; i < MEASURE_SAMPLES; i++) {
+        data->mean->data[i] = psVectorAllocEmpty(IMAGES_BUFFER, PS_TYPE_F32);
+        data->stdev->data[i] = psVectorAllocEmpty(IMAGES_BUFFER, PS_TYPE_F32);
+    }
+
+    data->exptimes = psVectorAllocEmpty(IMAGES_BUFFER, PS_TYPE_F32);
+    data->refs = psVectorAllocEmpty(IMAGES_BUFFER, PS_TYPE_F32);
+
+    return data;
+}
+
+bool pmShutterCorrectionAddReadout(pmShutterCorrectionData *data,
+                                   const pmReadout *readout, ///< Readout to add
+                                   psStatsOptions meanStat, ///< Statistic to use for mean
+                                   psStatsOptions stdevStat, ///< Statistic to use for stdev
+                                   psMaskType maskVal, ///< Mask value
+                                   psRandom *rng ///< Random number generator
+    )
+{
+    PS_ASSERT_PTR_NON_NULL(data, NULL);
+    PS_ASSERT_PTR_NON_NULL(readout, NULL);
+    PS_ASSERT_IMAGE_NON_NULL(readout->image, NULL);
+    PS_ASSERT_IMAGE_TYPE(readout->image, PS_TYPE_F32, NULL);
+    if (data->num == 0) {
+        data->numCols = readout->image->numCols;
+        data->numRows = readout->image->numRows;
+    } else {
+        PS_ASSERT_IMAGE_SIZE(readout->image, data->numCols, data->numRows, NULL);
+    }
+    if (readout->mask) {
+        PS_ASSERT_IMAGE_NON_NULL(readout->mask, NULL);
+        PS_ASSERT_IMAGE_TYPE(readout->mask, PS_TYPE_MASK, NULL);
+        PS_ASSERT_IMAGE_SIZE(readout->mask, data->numCols, data->numRows, NULL);
+    }
+    if (readout->weight) {
+        PS_ASSERT_IMAGE_NON_NULL(readout->weight, NULL);
+        PS_ASSERT_IMAGE_TYPE(readout->weight, PS_TYPE_F32, NULL);
+        PS_ASSERT_IMAGE_SIZE(readout->weight, data->numCols, data->numRows, NULL);
+    }
+
+    // Add the exposure time
+    float exptime = psMetadataLookupF32(NULL, readout->parent->concepts, "CELL.EXPOSURE"); // Exp. time
+    if (!isfinite(exptime)) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Exposure time is not set.");
+        return false;
+    }
+    data->exptimes->data.F32[data->exptimes->n] = exptime;
+    data->exptimes = psVectorExtend(data->exptimes, IMAGES_BUFFER, 1);
+
+    // Add the statistics
+
+    // Add the reference value
+    psStats *stats = psStatsAlloc(meanStat | stdevStat); // Statistics to apply
+    if (!rng) {
+        rng = psRandomAlloc(PS_RANDOM_TAUS, 0);
+    } else {
+        psMemIncrRefCounter(rng);
+    }
+    if (!psImageBackground(stats, NULL, readout->image, readout->mask, maskVal, rng)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to measure reference statistics.\n");
+        psFree(stats);
+        psFree(rng);
+        return false;
+    }
+    psFree(rng);
+    float refValue = psStatsGetValue(stats, meanStat); // Reference value
+    psTrace("psModules.detrend", 3, "Reference value & exptime for shutter image : %f cnts %f sec\n", refValue, exptime);
+    if (refValue <= 0.0) {
+        psError(PS_ERR_UNKNOWN, true, "Measured non-positive reference value.\n");
+        psFree(stats);
+        return false;
+    }
+    refValue = 1.0 / refValue;
+    data->refs->data.F32[data->refs->n] = refValue;
+    data->refs = psVectorExtend(data->refs, IMAGES_BUFFER, 1);
+
+    // Add the region statistics
+    for (int j = 0; j < MEASURE_SAMPLES; j++) {
+        psRegion *region = data->regions->data[j]; // Region of interest
+        psRegion adjusted = *region;    // Adjusted region, compensating for offsets
+        adjusted.x0 += readout->image->col0;
+        adjusted.x1 += readout->image->col0;
+        adjusted.y0 += readout->image->row0;
+        adjusted.y1 += readout->image->row0;
+        psImage *subImage = psImageSubset(readout->image, adjusted); // Sub-image
+        psImage *subMask = NULL;        // Sub-image of mask
+        if (readout->mask) {
+            subMask = psImageSubset(readout->mask, adjusted);
+        }
+        if (!psImageStats(stats, subImage, subMask, maskVal)) {
+            psString regionString = psRegionToString(adjusted);
+            psWarning("Unable to measure sample statistics at %s in image.\n",
+                      regionString);
+            psFree(regionString);
+        }
+        psFree(subImage);
+        psFree(subMask);
+
+        psVector *mean = data->mean->data[j]; // Vector of means for this region
+        psVector *stdev = data->stdev->data[j]; // Vector of standard deviations for this region
+
+        mean->data.F32[mean->n] = psStatsGetValue(stats, meanStat) * refValue;
+        stdev->data.F32[stdev->n] = psStatsGetValue(stats, stdevStat) * refValue;
+
+	psTrace("psModules.detrend", 5, "input shutter image sample value %d: %f +/- %f  ->  %f +/- %f\n", j,
+		psStatsGetValue(stats, meanStat), psStatsGetValue(stats, stdevStat),
+		mean->data.F32[mean->n], stdev->data.F32[stdev->n]);
+
+        data->mean->data[j] = psVectorExtend(mean, IMAGES_BUFFER, 1);
+        data->stdev->data[j] = psVectorExtend(stdev, IMAGES_BUFFER, 1);
+    }
+
+    data->num++;
+
+    return true;
+}
+
+float pmShutterCorrectionReference(pmShutterCorrectionData *data)
+{
+    PS_ASSERT_PTR_NON_NULL(data, NAN);
+    PS_ASSERT_INT_POSITIVE(data->num, NAN);
+
+    // supply counts sorted by exptime
+
+    // generate the index for the exptimes vector
+    psVector *index = psVectorSortIndex (NULL, data->exptimes);
+    psVector *newtimes = psVectorAlloc (data->exptimes->n, PS_TYPE_F32);
+
+    // reshuffle exptimes to new sequence (this is only a local value)
+    for (int j = 0; j < newtimes->n; j++) {
+	newtimes->data.F32[j] = data->exptimes->data.F32[index->data.S32[j]];
+    }
+
+    float meanRef = 0.0;                // Mean reference offset
+    int numGood = 0;                    // Number of good measurements
+    for (int i = 0; i < MEASURE_SAMPLES; i++) {
+	psVector *newcounts = psVectorAlloc (data->exptimes->n, PS_TYPE_F32);
+	psVector *newerrors = psVectorAlloc (data->exptimes->n, PS_TYPE_F32);
+	psVector *counts = data->mean->data[i];
+	psVector *errors = data->stdev->data[i];
+
+	for (int j = 0; j < newcounts->n; j++) {
+	    newcounts->data.F32[j] = counts->data.F32[index->data.S32[j]];
+	    newerrors->data.F32[j] = errors->data.F32[index->data.S32[j]];
+	}
+
+	// use the sorted exptime, counts, and errors for the measurements
+        pmShutterCorrection *guess = pmShutterCorrectionGuess(newtimes, newcounts); // Guess at correction
+	psTrace("psModules.detrend", 5, "Shutter correction guess: scale: %f, offset: %f, offref: %f\n", guess->scale, guess->offset, guess->offref);
+
+        pmShutterCorrection *corr = pmShutterCorrectionFullFit(newtimes, newcounts, newerrors, guess); // The actual correction
+	psTrace("psModules.detrend", 5, "Shutter correction fit: scale: %f, offset: %f, offref: %f\n", corr->scale, corr->offset, corr->offref);
+
+        if (corr && isfinite(corr->offref) && corr->valid) {
+            psTrace("psModules.detrend", 5, "Sample reference value: %f\n", corr->offref);
+            meanRef += corr->offref;
+            numGood++;
+        }
+
+        psFree(corr);
+        psFree(guess);
+	psFree (newcounts);
+	psFree (newerrors);
+    }
+    psFree (newtimes);
+    psFree (index);
+
+    if (numGood == 0) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to measure mean reference offset.\n");
+        return false;
+    }
+    meanRef /= (float)numGood;
+    psTrace("psModules.detrend", 3, "Mean reference value: %f\n", meanRef);
+    return meanRef;
+}
+
+bool pmShutterCorrectionGenerate(pmReadout *shutter, pmReadout *pattern, const psArray *inputs,
+                                 float reference, const pmShutterCorrectionData *data,
+                                 int nIter, float rej, psMaskType maskVal)
+{
+    PS_ASSERT_PTR_NON_NULL(shutter, false);
+    PS_ASSERT_ARRAY_NON_NULL(inputs, false);
+    PS_ASSERT_INT_EQUAL(data->num, inputs->n, false);
+    PS_ASSERT_INT_NONNEGATIVE(nIter, false);
+    PS_ASSERT_FLOAT_LARGER_THAN(rej, 0.0, false);
+
+    int minInputCols, maxInputCols, minInputRows, maxInputRows; // Smallest and largest values to combine
+    int xSize, ySize;                   // Size of the output image
+    if (!pmReadoutStackValidate(&minInputCols, &maxInputCols, &minInputRows, &maxInputRows, &xSize, &ySize,
+                                inputs)) {
+        psError(PS_ERR_UNKNOWN, false, "No valid input readouts.");
+        return false;
+    }
+
+    pmReadoutUpdateSize(shutter, minInputCols, minInputRows, xSize, ySize, false, false, maskVal);
+    if (pattern) {
+        pmReadoutUpdateSize(pattern, minInputCols, minInputRows, xSize, ySize, false, false, maskVal);
+    }
+
+    psImage *nums = pmReadoutAnalysisImage(shutter, PM_READOUT_STACK_ANALYSIS_COUNT, xSize, ySize,
+                                           PS_TYPE_U16, 0); // Image with number fitted per pixel
+    if (!nums) {
+        return false;
+    }
+    psImage *sigma = pmReadoutAnalysisImage(shutter, PM_READOUT_STACK_ANALYSIS_SIGMA, xSize, ySize,
+                                            PS_TYPE_F32, NAN); // Image with stdev per pixel
+    if (!sigma) {
+        psFree(nums);
+        return false;
+    }
+
+    psImage *shutterImage = shutter->image; // Shutter correction image
+    psImage *patternImage; // Illumination pattern
+    if (pattern) {
+        patternImage = psMemIncrRefCounter(pattern->image);
+    } else {
+        patternImage = psImageAlloc(xSize, ySize, PS_TYPE_F32);
+    }
+
+    int num = data->num;                // Number of images
+    psVector *counts = psVectorAlloc(num, PS_TYPE_F32); // Counts in each image
+    psVector *errors = psVectorAlloc(num, PS_TYPE_F32); // Counts in each image
+    psVector *mask = psVectorAlloc(num, PS_TYPE_MASK); // Mask for each image
+    psTrace("psModules.detrend", 2, "Performing linear fit on individual pixels...\n");
+    for (int i = minInputRows; i < maxInputRows; i++) {
+        int yOut = i - shutter->row0; // y position on output readout
+        for (int j = minInputCols; j < maxInputCols; j++) {
+            int xOut = j - shutter->col0; // x position on output readout
+
+            psVectorInit(mask, 0);
+            for (int r = 0; r < num; r++) {
+                pmReadout *readout = inputs->data[r]; // Readout of interest
+                int yIn = i - readout->row0; // y position on input readout
+                int xIn = j - readout->col0; // x position on input readout
+                psImage *image = readout->image; // Image of interest
+                float ref = data->refs->data.F32[r]; // (Inverse) reference value
+                counts->data.F32[r] = image->data.F32[yIn][xIn] * ref;
+                if (readout->mask) {
+                    mask->data.PS_TYPE_MASK_DATA[r] = readout->mask->data.PS_TYPE_MASK_DATA[yIn][xIn];
+                }
+                if (readout->weight) {
+                    errors->data.F32[r] = sqrtf(readout->weight->data.F32[yIn][xIn]) * ref;
+                } else {
+                    errors->data.F32[r] = sqrtf(image->data.F32[yIn][xIn]) * ref;
+                }
+            }
+
+            pmShutterCorrection *corr = pmShutterCorrectionLinFit(data->exptimes, counts, errors, mask,
+                                                                  reference, nIter, rej, maskVal);
+            if (!corr) {
+                // Nothing we can do about it
+                psErrorClear();
+                shutterImage->data.F32[yOut][xOut] = NAN;
+                patternImage->data.F32[yOut][xOut] = NAN;
+                nums->data.U16[yOut][xOut] = 0;
+                sigma->data.F32[yOut][xOut] = NAN;
+                continue;
+            }
+            shutterImage->data.F32[yOut][xOut] = corr->offset;
+            patternImage->data.F32[yOut][xOut] = corr->scale;
+            nums->data.U16[yOut][xOut] = corr->num;
+            sigma->data.F32[yOut][xOut] = corr->stdev;
+            psFree(corr);
+        }
+    }
+    psFree(mask);
+    psFree(errors);
+    psFree(counts);
+    psFree(nums);
+    psFree(sigma);
+
+    // Update the "concepts"
+    psList *inputCells = psListAlloc(NULL); // List of cells
+    for (long i = 0; i < inputs->n; i++) {
+        pmReadout *readout = inputs->data[i]; // Readout of interest
+        psListAdd(inputCells, PS_LIST_TAIL, readout->parent);
+    }
+    bool success = pmConceptsAverageCells(shutter->parent, inputCells, NULL, NULL, true);
+    psFree(inputCells);
+
+    // Correct the exposure times --- they don't make sense any more.
+    psMetadataItem *item = psMetadataLookup(shutter->parent->concepts, "CELL.EXPOSURE");
+    item->data.F32 = NAN;
+    item = psMetadataLookup(shutter->parent->concepts, "CELL.DARKTIME");
+    item->data.F32 = NAN;
+
+    shutter->data_exists = true;
+    shutter->parent->data_exists = true;
+    shutter->parent->parent->data_exists = true;
+
+    if (pattern) {
+        pattern->data_exists = true;
+        pattern->parent->data_exists = true;
+        pattern->parent->parent->data_exists = true;
+    }
+
+    return success;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmShutterCorrection.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmShutterCorrection.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmShutterCorrection.h	(revision 22157)
@@ -0,0 +1,194 @@
+/* @file pmShutterCorrection.h
+ * @brief Functions to build and apply a shutter exposure-time correction.
+ *
+ * @author Eugene Magnier, IfA
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.16 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-06-09 00:39:37 $
+ * Copyright 2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_SHUTTER_CORRECTION_H
+#define PM_SHUTTER_CORRECTION_H
+
+/// @addtogroup detrend Detrend Creation and Application
+/// @{
+
+/*  A mechanical shutter may not yield uniform exposure times as a function of position on the
+ *  detector.  The typical error consists of a constant exposure-time offset relative to the
+ *  requested value, ie exposure time is T_o + dT(x,y).  The exposure error, dT, may be
+ *  measured with the following scheme.  Obtain a set of exposures with different exposures
+ *  times taken of the same flat-field source; the source must be spatially stable between the
+ *  exposures, but need not have a stable amplitude.  For an illuminating flux of intensity
+ *  F(x,y) = F_o f(x,y), the signal recorded by any pixel in the detector is given by: S(t,x,y)
+ *  = F_o(t) f(x,y) (T_o + dT(x,y)) where F_o(t) is the (variable) overall intensity of the
+ *  illuminating source and f(x,y) is the spatial illumination pattern times the flat-field
+ *  response.  Choose a reference location in the image (eg, the detector center) and divide by
+ *  the value of that region (ie, mean or median):
+ *
+ *  s(t,x,y) = S(t,x,y) / S(t,0,0)
+ *  s(t,x,y) = F_o(t) f(x,y) (T_o + dT(x,y)) / F_o(t) f(0,0) (T_o + dT(0,0))
+ *  s(t,x,y) = f(x,y) (T_o + dT(x,y)) / f(0,0) (T_o + dT(0,0))
+ *
+ *  we can absorb the term f(0,0) into f(x,y) as we have no motivation for the scale of f(x,y)
+ *  -- a normalization for the flat-field is not specified here.  For any single pixel, over
+ *  the set of exposures, we thus need to solve for dT(x,y), dT(0,0), and f'(x,y) in the
+ *  equation: s(t,x,y) = f'(x,y) (T_o + dT(x,y)) / (T_o + dT(0,0))
+ *
+ *  we avoid directly fitting these values as the process would be a non-linear
+ *  least-squares problem for every pixel in the image, and thus very time
+ *  consuming.  There are linear options which may be used instead.
+ *  First, as T_o goes to a large value, s() approaches the value of f'(x,y).
+ *  Next, as T_o goes to a very small value, s() approaches the value of
+ *  f'(x,y)*dT(x,y)/dT(0,0).  Finally, when s() has the value of
+ *  f'(x,y)*(1 + dT(x,y)/dT(0,0))/2, T_o has the value of dT(0,0).  with data
+ *  points covering a reasonable dynamic range, we can solve for these three
+ *  values by interpolation and/or extrapolation.
+ *
+ *  To take the strategy one step further, we could use the above recipe to
+ *  obtain a guess for the three parameters and then apply non-linear fitting to
+ *  solve more accurately for the parameters.  If we limit this operation to a
+ *  handful of positions in the image (user defined, but the obvious choice would
+ *  be positions near the center, edges, and corners), then we may determine a
+ *  good value for dT(0,0).  Since there is only one dT(0,0) for the image, we
+ *  can apply the resulting measurement to the rest of the pixels in the image.
+ *  If dT(0,0) is not a free parameter, then the fitting process is linear in
+ *  terms of dT(x,y) and f'(x,y)
+ */
+
+/// Shutter correction parameters, applicable for a single pixel
+typedef struct {
+    double scale;                       ///< The normalisation for an exposure, A(k) or f'(x,y) 
+    double offset;                      ///< The time offset, dTk
+    double offref;                      ///< The reference time offset, dTo
+    int num;                            ///< Number of points used
+    float stdev;                        ///< Standard deviation
+    bool valid;				// is the fitted shutter correction valid (produce a significant improvement?)
+} pmShutterCorrection;
+
+/// Allocator for shutter correction parameters
+pmShutterCorrection *pmShutterCorrectionAlloc();
+
+/// Guess a shutter correction, based on plot of counts vs exposure time
+///
+/// This function is used before doing the full non-linear fit, to get parameters close to the true.  Assumes
+/// exptime vector is sorted (ascending order; longest is last) prior to input.
+pmShutterCorrection *pmShutterCorrectionGuess(
+    const psVector *exptime,            ///< Exposure times for each exposure
+    const psVector *counts              ///< Counts for each exposure
+    );
+
+/// Generate shutter correction based on a linear fit
+///
+/// Performs a linear fit to counts as a function of exposure time, with the reference time offset fixed (so
+/// that the system is linear).  Performs iterative clipping, if nIter > 1.
+pmShutterCorrection *pmShutterCorrectionLinFit(
+    const psVector *exptime,            ///< Exposure times for each exposure
+    const psVector *counts,             ///< Counts for each exposure
+    const psVector *cntError,           ///< Error in the counts
+    const psVector *mask,               ///< Mask for each exposure
+    float offref,                       ///< Reference time offset
+    int nIter,                          ///< Number of iterations
+    float rej,                          ///< Rejection threshold (sigma)
+    psMaskType maskVal                  ///< Mask value
+    );
+
+/// Generate shutter correction based on a full non-linear fit
+///
+/// Performs a full non-linear fit to counts as a function of exposure time.  The main purpose is to solve for
+/// the reference time offset, so that future fits may be performed using linear fitting with the reference
+/// time offset fixed.
+pmShutterCorrection *pmShutterCorrectionFullFit(
+    const psVector *exptime,            ///< Exposure times for each exposure
+    const psVector *counts,             ///< Counts for each exposure
+    const psVector *cntError,           ///< Error in the counts
+    const pmShutterCorrection *guess    ///< Initial guess
+    );
+
+/// Measure a shutter correction image from an array of images
+///
+/// Given an array of readouts (with known exposure times from the cell concepts), this function measures the
+/// shutter correction (our principal concern is for the time offset, rather than the normalisation) by
+/// measuring the reference time offset using the full non-linear fit for a small number of representative
+/// regions (middle and corners), and then using that to perform a linear fit to each pixel.
+bool pmShutterCorrectionMeasure(
+    pmReadout *output,                  ///< Output readout
+    const psArray *readouts,            ///< Array of readouts
+    int size,                           ///< Size of samples for statistics for non-linear fit
+    psStatsOptions meanStat,            ///< Statistic to use for mean
+    psStatsOptions stdevStat,           ///< Statistic to use for stdev
+    int nIter,                          ///< Number of iterations
+    float rej,                          ///< Rejection threshold (sigma)
+    psMaskType maskVal                  ///< Mask value
+    );
+
+/// Apply a shutter correction
+///
+/// Given a shutter correction (with dT for each pixel), applies this correction to an input image.
+bool pmShutterCorrectionApply(
+    pmReadout *readout,                 ///< Readout to which to apply shutter correction
+    const pmReadout *shutter,           ///< Shutter correction readout, with dT for each pixel
+    psMaskType blank                    ///< Value to give blank pixels
+    );
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Functions for doing the shutter correction piece-meal (don't have to read entire image stack into memory at
+// once).  A single read run through the stack is required, calling pmShutterCorrectionAddReadout on each.
+// Then pmShutterCorrectionReference provides the required reference shutter time, so that
+// pmShutterCorrectionGenerate can generate a shutter correction piece by piece as overlapping pixels from
+// each input are read in.
+
+
+/// Data for measuring the shutter correction
+typedef struct {
+    int num;                            ///< Number of images
+    int numCols, numRows;               ///< Size of images
+    psArray *regions;                   ///< Regions at which to measure statistics
+    psArray *mean;                      ///< Vector of means at each region
+    psArray *stdev;                     ///< Vector of standard deviations at each region
+    psVector *exptimes;                 ///< Exposure times for each image
+    psVector *refs;                     ///< Reference fluxes
+} pmShutterCorrectionData;
+
+/// Allocator for pmShutterCorrectionData
+pmShutterCorrectionData *pmShutterCorrectionDataAlloc(int numCols, int numRows, ///< Size of images
+                                                      int size ///< Size of regions
+    );
+
+/// Add a readout to the correction data
+///
+/// Performs statistics on the readout, recording the data
+bool pmShutterCorrectionAddReadout(
+    pmShutterCorrectionData *data,      ///< Correction data
+    const pmReadout *readout,           ///< Readout to add
+    psStatsOptions meanStat,            ///< Statistic to use for mean
+    psStatsOptions stdevStat,           ///< Statistic to use for stdev
+    psMaskType maskVal,                 ///< Mask value
+    psRandom *rng                       ///< Random number generator
+    );
+
+/// Calculate the reference shutter time from the correction data
+float pmShutterCorrectionReference(
+    pmShutterCorrectionData *data ///< Correction data
+    );
+
+/// Generate a shutter correction
+///
+/// Performs the linear fit to each pixel in the stack.
+bool pmShutterCorrectionGenerate(
+    pmReadout *shutter,                 ///< Shutter correction
+    pmReadout *pattern,                 ///< Background pattern (or NULL)
+    const psArray *inputs,              ///< Stack of input pmReadouts
+    float reference,                    ///< Reference shutter time (from pmShutterCorrectionRef)
+    const pmShutterCorrectionData *data, ///< Correction data
+    int nIter,                          ///< Number of iterations
+    float rej,                          ///< Rejection threshold (sigma)
+    psMaskType maskVal                  ///< Mask value
+    );
+
+
+
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmSkySubtract.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmSkySubtract.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmSkySubtract.c	(revision 22157)
@@ -0,0 +1,738 @@
+/** @file  pmSubtractSky.c
+ *
+ *  This file will contain a module which will create a model of the
+ *  background sky and subtract that from the input image.
+ *
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-04-04 22:42:48 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmSubtractSky.h"
+
+// XXX: Get rid of the.  Create pmUtils.h
+psImage *p_psDetermineTrimmedImage(
+    pmReadout *in
+);
+
+/******************************************************************************
+DetermineNumBits(data): This routine takes an enum psStatsOptions as an
+argument and returns the number of non-zero bits.
+
+XXX: This code is duplicated in the ReadoutCombine file.
+ *****************************************************************************/
+static psS32 DetermineNumBits(psStatsOptions data)
+{
+    psTrace("psModules.detrend", 4, "Calling DetermineNumBits(0x%x)\n", data);
+
+    psS32 i;
+    psU64 tmpData = data;
+    psS32 numBits = 0;
+
+    for (i=0;i<(8 * sizeof(psStatsOptions));i++) {
+        if (0x0001 & tmpData) {
+            numBits++;
+        }
+        tmpData = tmpData >> 1;
+    }
+
+    psTrace("psModules.detrend", 4,
+            "Calling DetermineNumBits(0x%x) -> %d\n", data, numBits);
+    return(numBits);
+}
+
+/******************************************************************************
+getHighestPriorityStatOption(statOptions): this routine takes as input a
+psStats->options with multiple options set and returns one with a single
+option set according to the precedence set in the SDRS.
+ *****************************************************************************/
+static psU64 getHighestPriorityStatOption(psU64 statOptions)
+{
+    psTrace("psModules.detrend", 4,
+            "Calling getHighestPriorityStatOption(0x%x)\n", statOptions);
+
+    if (statOptions & PS_STAT_SAMPLE_MEAN) {
+        return(PS_STAT_SAMPLE_MEAN);
+    } else if (statOptions & PS_STAT_SAMPLE_MEDIAN) {
+        return(PS_STAT_SAMPLE_MEDIAN);
+    } else if (statOptions & PS_STAT_CLIPPED_MEAN) {
+        return(PS_STAT_CLIPPED_MEAN);
+    } else if (statOptions & PS_STAT_FITTED_MEAN) {
+        return(PS_STAT_FITTED_MEAN);
+    } else if (statOptions & PS_STAT_ROBUST_MEDIAN) {
+        return(PS_STAT_ROBUST_MEDIAN);
+    }
+    psError(PS_ERR_UNKNOWN, true, "Unallowable option requested for statistically binning image pixels.\n");
+    return(-1);
+    // XXX
+    //else if (statOptions & PS_STAT_ROBUST_MODE) {
+    //    return(PS_STAT_ROBUST_MODE);
+    //}
+}
+
+/******************************************************************************
+psImage *binImage(origImage, binFactor, statOptions): This routine takes an
+input psImage and scales it smaller by a factor of binFactor.  The statistic
+used in combining input pixels is specified in statOptions.
+
+XXX: use static vectors for myStats, binVector and binMask.
+XXX: I coded this before I was aware of a psLib reBin function.  I don't
+use this function in this module.  I'm keeping it here in the event that
+requirements change and we might need a custom reBin function.
+ *****************************************************************************/
+#if 0
+static psImage *binImage(psImage *origImage,
+                         int binFactor,
+                         psStatsOptions statOptions)
+{
+    psTrace("psModules.detrend", 4, "Calling binImage(%d)\n", binFactor);
+
+    if (binFactor <= 0) {
+        psLogMsg(__func__, PS_LOG_WARN,
+                 "WARNING: binImage(): binFactor is %d\n", binFactor);
+        return(origImage);
+    }
+    if (binFactor == 1) {
+        return(origImage);
+    }
+
+    psVector *binVector = psVectorAlloc(binFactor * binFactor, PS_TYPE_F32);
+    psVector *binMask = psVectorAlloc(binFactor * binFactor, PS_TYPE_U8);
+    psStats *myStats = psStatsAlloc(statOptions);
+
+    for (psS32 row = 0; row < origImage->numRows ; row+=binFactor) {
+        for (psS32 col = 0; col < origImage->numCols ; col+=binFactor) {
+            psS32 count = 0;
+            for (psS32 binRow = 0; binRow <= binFactor ; binRow++) {
+                for (psS32 binCol = 0; binCol <= binFactor ; binCol++) {
+                    if (((row + binRow) < origImage->numRows) &&
+                            ((col + binCol) < origImage->numCols)) {
+                        binVector->data.F32[count] =
+                            origImage->data.F32[row + binRow][col + binCol];
+                        binMask->data.U8[count] = 0;
+                    } else {
+                        binVector->data.F32[count] = 0.0;
+                        binMask->data.U8[count] = 1;
+                    }
+                    count++;
+                }
+            }
+            psStats *rc1 = psVectorStats(myStats, binVector, NULL, binMask, 1);
+            if (rc1 == NULL) {
+                psError(PS_ERR_UNKNOWN, false, "psVectorStats(): could not perform requested statistical operation.  Returning in image.\n");
+                return(origImage);
+            }
+            psF64 statValue;
+            psBool rc = p_psGetStatValue(rc1, &statValue);
+
+            if (rc == true) {
+                origImage->data.F32[row][col] = (psF32) statValue;
+            } else {
+                origImage->data.F32[row][col] = 0.0;
+                psLogMsg(__func__, PS_LOG_WARN,
+                         "WARNING: pmSubtractSky(), binImage(): p_psGetStatValue() was FALSE\n");
+            }
+        }
+    }
+    psFree(binVector);
+    psFree(binMask);
+    psFree(myStats);
+
+    psTrace("psModules.detrend", 4, "Exiting binImage(%d)\n", binFactor);
+    return(origImage);
+}
+#endif
+
+/******************************************************************************
+CalculatePolyTerms(xOrder, yOrder): this routine will calculate the number of
+coefficients (or terms) in a 2-D polynomial of order (xOrder, yOrder).
+
+XXX: Use your brain and figure out the analytical expression.
+
+XXX: Why isn't it simply (xOrder+1) * (yOrder+1)?
+ *****************************************************************************/
+static psS32 CalculatePolyTerms(psS32 xOrder, psS32 yOrder)
+{
+    psTrace("psModules.detrend", 4,
+            "Calling CalculatePolyTerms(%d, %d)\n", xOrder, yOrder);
+
+    psS32 maxOrder = PS_MAX(xOrder, yOrder);
+    psS32 localPolyTerms = 0;
+    psS32 order = 0;
+    psS32 num=0;
+
+    for (order=0;order<=maxOrder;order++) {
+        for (num=0;num<=order;num++) {
+            if (((order-num) <= xOrder) && (num <= yOrder)) {
+                localPolyTerms++;
+            }
+        }
+    }
+    psTrace("psModules.detrend", 4,
+            "Exiting CalculatePolyTerms(%d, %d) -> %d\n", xOrder, yOrder, localPolyTerms);
+    return(localPolyTerms);
+
+    //    return((xOrder+1) * (yOrder+1));
+}
+
+/******************************************************************************
+buildPolyTerms(): this routine computes a 2-D array polyTerms[][] that holds
+terms for the polynomial that is used to model the sky background.  We use
+this array primarily for convenience in computations involving sky model
+polynomials.  It is defined as:
+    polyTerms[i][0] = the power to which X is raised in the i-th term of in an
+    poly-order sky background polynomial.
+
+    polyTerms[i][1] = the power to which Y is raised in the i-th term of in an
+    poly-order sky background polynomial.
+ *****************************************************************************/
+static psS32 **buildPolyTerms(psS32 xOrder, psS32 yOrder)
+{
+    psTrace("psModules.detrend", 4,
+            "Calling buildPolyTerms(%d, %d)\n", xOrder, yOrder);
+
+    psS32 i=0;
+    psS32 order = 0;
+    psS32 num=0;
+    psS32 localPolyTerms = CalculatePolyTerms(xOrder, yOrder);
+    psS32 maxOrder = PS_MAX(xOrder, yOrder);
+
+    // Create the data structure which we hold the xy order of each coeff.
+    psS32 **polyTerms = (psS32 **) psAlloc(localPolyTerms * sizeof(psS32 *));
+    for (i=0; i < localPolyTerms ; i++) {
+        polyTerms[i] = (psS32 *) psAlloc(2 * sizeof(psS32));
+    }
+
+    i=0;
+    // This code segment loops through each term i in the polynomial and
+    // calculates the power to which x/y are raised in that i-th term.
+    // We first do the 0-order terms, then the 1-order terms, etc.
+    for (order=0;order<=maxOrder;order++) {
+        for (num=0;num<=order;num++) {
+            if (((order-num) <= xOrder) && (num <= yOrder)) {
+                polyTerms[i][0] = order-num;
+                polyTerms[i][1] = num;
+                i++;
+            }
+        }
+    }
+
+    if (psTraceGetLevel("psModules.detrend") >= 10) {
+        for (i=0; i < localPolyTerms ; i++) {
+            printf("x^%d * y^%d\n", polyTerms[i][0], polyTerms[i][1]);
+        }
+    }
+
+    psTrace("psModules.detrend", 4,
+            "Exiting buildPolyTerms(%d, %d)\n", xOrder, yOrder);
+    return(polyTerms);
+}
+
+/******************************************************************************
+This procedure calculates various combinations of powers of x and y and stores
+them in the data structure p_psPolySums[][].  After it completes:
+
+    p_psPolySums[i][j] == x^i * y^j
+
+XXX: Use a psImage for the p_psPolySums data structure?
+XXX: p_psPolySums: should this be a global?  Did you get the storage classifier
+     and name correct?
+XXX: Use variable size arrays for p_psPolySums[][].
+XXX: Must initialize p_psPolySums[][]?
+ *****************************************************************************/
+#define PS_MAX_POLYNOMIAL_ORDER 20
+
+psF64 p_psPolySums[PS_MAX_POLYNOMIAL_ORDER+1][PS_MAX_POLYNOMIAL_ORDER+1];
+static void buildSums(psF64 x,
+                      psF64 y,
+                      psS32 xOrder,
+                      psS32 yOrder)
+{
+    psTrace("psModules.detrend", 4,
+            "Calling buildPolyTerms(%d, %d)\n", xOrder, yOrder);
+
+    psS32 i = 0;
+    psS32 j = 0;
+    psF64 xSum = 0.0;
+    psF64 ySum = 0.0;
+
+    xSum = 1.0;
+    ySum = 1.0;
+    for(i=0;i<=xOrder;i++) {
+        ySum = xSum;
+        for(j=0;j<=yOrder;j++) {
+            p_psPolySums[i][j] = ySum;
+            ySum*= y;
+        }
+        xSum*= x;
+    }
+    psTrace("psModules.detrend", 4,
+            "Exiting buildPolyTerms(%d, %d)\n", xOrder, yOrder);
+}
+
+/******************************************************************************
+ImageFitPolynomial(myPoly, dataImage, maskImage): this private routine takes
+an input image along with a mask and fits a polynomial to it.  The degree of
+the polynomial is specified by input parameter myPoly, and need not be
+symmetrical in orders of X and Y.  The polynomial must be type
+PS_POLYNOMIAL_ORD.  If there are not enough rows or columns in the input image
+for the order of the polynomial, then that order is reduced.  The algorithm
+used in this routine is based on that of the pilot project ADD, but is not
+documented anywhere.
+
+XXX: Different trace message facilities in use here.
+ *****************************************************************************/
+static psPolynomial2D *ImageFitPolynomial(
+    psPolynomial2D *myPoly,
+    psImage *dataImage,
+    psImage *maskImage)
+{
+    psTrace("psModules.detrend", 4,
+            "Calling ImageFitPolynomial()\n");
+    PS_ASSERT_POLY_NON_NULL(myPoly, NULL);
+    PS_ASSERT_POLY_TYPE(myPoly, PS_POLYNOMIAL_ORD, NULL);
+    PS_ASSERT_IMAGE_NON_NULL(dataImage, NULL);
+    PS_ASSERT_IMAGE_NON_EMPTY(dataImage, NULL);
+    PS_ASSERT_IMAGE_TYPE(dataImage, PS_TYPE_F32, NULL);
+    PS_ASSERT_IMAGE_NON_NULL(maskImage, NULL);
+    PS_ASSERT_IMAGE_NON_EMPTY(maskImage, NULL);
+    PS_ASSERT_IMAGE_TYPE(maskImage, PS_TYPE_U8, NULL);
+    PS_ASSERT_IMAGES_SIZE_EQUAL(dataImage, maskImage, NULL);
+    psS32 oldPolyX = -1;
+    psS32 oldPolyY = -1;
+
+    // The matrix equations become singular if there are more powers of X
+    // in myPoly then there are rows of the image.  I think.  Similarly for
+    // powers of Y and columns.  So.  Here we reduce the complexity of the
+    // polynomial if there are not enough rows/columns in the input image.
+
+    if ((myPoly->nX + 1) > dataImage->numRows) {
+        psLogMsg(__func__, PS_LOG_WARN,
+                 "WARNING: ImageFitPolynomial(): Reducing polynomial complexity in x-dimension.\n");
+        oldPolyX = myPoly->nX;
+        myPoly->nX = dataImage->numRows - 1;
+    }
+    if ((myPoly->nY + 1) > dataImage->numCols) {
+        psLogMsg(__func__, PS_LOG_WARN,
+                 "WARNING: ImageFitPolynomial(): Reducing polynomial complexity in y-dimension.\n");
+        oldPolyY = myPoly->nY;
+        myPoly->nY = dataImage->numCols - 1;
+    }
+    psS32 i;
+    psS32 j;
+    psS32 x;
+    psS32 y;
+    psS32 aRow;
+    psS32 aCol;
+    psS32 **polyTerms = buildPolyTerms(myPoly->nX, myPoly->nY);
+    // We determine how many coefficients will be in the polynomial that we
+    // are fitting to this image.
+    psS32 localPolyTerms = CalculatePolyTerms(myPoly->nX, myPoly->nY);
+    psImage *A = psImageAlloc(localPolyTerms, localPolyTerms, PS_TYPE_F64);
+    psImage *Aout = psImageAlloc(localPolyTerms, localPolyTerms, PS_TYPE_F64);
+    psVector *B = psVectorAlloc(localPolyTerms, PS_TYPE_F64);
+    psVector *outPerm = NULL;
+
+    //
+    // Initialize A matrix and B vector.
+    //
+    psImageInit(A, 0.0);
+    psVectorInit(B, 0.0);
+
+    //
+    // We build the A matrix and B vector.
+    //
+    for (x=0;x<dataImage->numRows;x++) {
+        for (y=0;y<dataImage->numCols;y++) {
+            if (maskImage->data.U8[x][y] == 0) {
+                buildSums((psF64) x, (psF64) y, myPoly->nX, myPoly->nY);
+
+                /************************************************************
+                This code dervies from equation (7) of the pilot ADD.  However,
+                it is not exactly the same in that the order of the polynomial
+                may be different in X And Y.
+
+                Equation (7) from the pilot ADD describes 16 linear equations.
+                The i-th equation is simply the partial derivative of the
+                sky background polynomial (1) w.r.t. to the i-th term in
+                that polynomial.  The i-th equation is stored in row i of
+                matrix A[][] (matrix A[][] has origin (1,1), not (0,0)).  To
+                compute A[i][j] we simply multiply the j-th term of the Sky
+                Background Polynomial (SBP) by the i-th term of SBP.
+                ************************************************************/
+                for (aRow=0;aRow<localPolyTerms;aRow++) {
+                    for (aCol=0;aCol<localPolyTerms;aCol++) {
+                        A->data.F64[aRow][aCol]+=
+                            (p_psPolySums[ polyTerms[aCol][0] ][ polyTerms[aCol][1] ] *
+                             p_psPolySums[ polyTerms[aRow][0] ][ polyTerms[aRow][1] ]);
+                    }
+                }
+                // Build the B[] vector, which is the right-hand side of (7).
+                for (i=0;i<localPolyTerms;i++) {
+                    B->data.F64[i]+= dataImage->data.F32[x][y] *
+                                     p_psPolySums[ polyTerms[i][0] ][ polyTerms[i][1] ];
+                }
+            }
+        }
+    }
+
+    if (psTraceGetLevel(".psModule.pmSubtractSky.ImageFitPolynomial") >= 8) {
+        for (aRow=0;aRow<localPolyTerms;aRow++) {
+            for (aCol=0;aCol<localPolyTerms;aCol++) {
+                printf("A[%d][%d] is %f\n", aRow, aCol,
+                       A->data.F64[aRow][aCol]);
+            }
+        }
+
+        for (i=0;i<=localPolyTerms;i++) {
+            printf("B[%d] is %f\n", i, B->data.F64[i]);
+        }
+    }
+
+    //
+    // Solve the matrix equations for the polynomial coefficients C.
+    // XXX: How do we know if these matrix operations were successful?
+    //
+    Aout = psMatrixLUD(Aout, &outPerm, A);
+    PS_ASSERT_IMAGE_NON_NULL(Aout, NULL);
+    PS_ASSERT_IMAGE_NON_EMPTY(Aout, NULL);
+    psVector *C = psVectorAlloc(localPolyTerms, PS_TYPE_F64);
+    psMatrixLUSolve(C, Aout, B, outPerm);
+
+    //
+    // Set the appropriate coefficients in the myPoly structure.
+    //
+    for (i=0;i<localPolyTerms;i++) {
+        myPoly->coeff[ polyTerms[i][0] ][ polyTerms[i][1] ] = C->data.F64[i];
+        psTrace("psModules.detrend", 6,
+                "myPoly->coeff[%d][%d] is %f\n", polyTerms[i][0], polyTerms[i][1], myPoly->coeff[ polyTerms[i][0] ][ polyTerms[i][1] ]);
+    }
+
+    //
+    // Free data structures that were allocated in this module.
+    //
+    for (i=0;i<localPolyTerms;i++) {
+        psFree(polyTerms[i]);
+    }
+    psFree(polyTerms);
+    psFree(A);
+    psFree(Aout);
+    psFree(B);
+    psFree(C);
+    psFree(outPerm);
+
+    //
+    // We restore the original size of the polynomial and set remaining
+    // coefficients to 0.0, if necessary.
+    //
+    // XXX: Verify this works after poly nOrder/nTerm change.
+    //
+    if (oldPolyX != -1) {
+        myPoly->nX = oldPolyX;
+        for (i=oldPolyX ; i < (1 + myPoly->nX) ; i++) {
+            for (j=0;j<(1 + myPoly->nY) ; j++) {
+                myPoly->coeff[i][j] = 0.0;
+            }
+        }
+    }
+    if (oldPolyY != -1) {
+        myPoly->nY = oldPolyY;
+        for (i=0 ; i < (1 + myPoly->nX) ; i++) {
+            for (j=oldPolyY;j < (1 + myPoly->nY) ; j++) {
+                myPoly->coeff[i][j] = 0.0;
+            }
+        }
+    }
+
+    psTrace("psModules.detrend", 4,
+            "Exiting ImageFitPolynomial()\n");
+    //    psTrace("psModules.detrend", 4,
+    //            "---- ImageFitPolynomial() end successfully ----\n");
+    return(myPoly);
+}
+
+
+/******************************************************************************
+pmReadout pmSubtractSky():
+
+XXX: use static vectors for myStats, and the binned image
+
+XXX: The SDR is silent about types.  PS_TYPE_F32 is implemented here.
+
+XXX: Sync the psTrace message facilities.
+ *****************************************************************************/
+pmReadout *pmSubtractSky(pmReadout *in,
+                         void *fitSpec,
+                         psFit fit,
+                         psS32 binFactor,
+                         psStats *stats,
+                         psF32 clipSD)
+{
+    PS_ASSERT_READOUT_NON_NULL(in, NULL);
+    PS_ASSERT_READOUT_NON_EMPTY(in, NULL);
+    PS_ASSERT_READOUT_TYPE(in, PS_TYPE_F32, NULL);
+    PS_WARN_PTR_NON_NULL(in->parent);
+    if (in->parent != NULL) {
+        PS_WARN_PTR_NON_NULL(in->parent->concepts);
+    }
+    psTrace("psModules.detrend", 4,
+            "---- pmSubtractSky() begin ----\n");
+
+    if ((fit != PM_FIT_NONE) &&
+            (fit != PM_FIT_POLYNOMIAL) &&
+            (fit != PM_FIT_SPLINE)) {
+        psError(PS_ERR_UNKNOWN, true, "psFit is unallowable (%d).  Returning in image.\n", fit);
+        return(in);
+    }
+
+    psStatsOptions statOptions = 0;
+
+    //
+    // Return the original input readout if the fit specs are poorly defined.
+    // No warning or error messages should be generated.
+    //
+    if ((fitSpec == NULL) ||
+            ((fit == PM_FIT_NONE) || (fit == PM_FIT_SPLINE))) {
+        //        psLogMsg(__func__, PS_LOG_WARN, "Fit specs are poorly defined.  Returning in image.\n");
+        return(in);
+    }
+
+    //
+    // Determine trimmed image from metadata.
+    //
+
+    psImage *trimmedImg = p_psDetermineTrimmedImage(in);
+    psImage *binnedImage = NULL;
+    psPolynomial2D *myPoly = NULL;
+    psImage *binnedMaskImage = NULL;
+    psU32 oldStatOptions = 0;
+
+    //
+    // Determine which statistic to use when binning pixels, if any.
+    //
+    if (stats != NULL) {
+        statOptions = stats->options;
+        if (1 < DetermineNumBits(statOptions)) {
+            psLogMsg(__func__, PS_LOG_WARN, "WARNING: Multiple statistical options have been requested.\n");
+            statOptions = getHighestPriorityStatOption(statOptions);
+            if (statOptions == -1) {
+                psError(PS_ERR_UNKNOWN, true, "Not allowable stats->option was specified.  Returning in image.\n");
+                return(in);
+            }
+            // Save old input "stats" parameter.
+            oldStatOptions = stats->options;
+            stats->options = statOptions;
+        }
+        if (0 == DetermineNumBits(statOptions)) {
+            psLogMsg(__func__, PS_LOG_WARN,
+                     "WARNING: pmSubtractSky(): no stats->options was requested\n");
+        }
+    }
+
+    //
+    // Generate required warning messages.
+    //
+    if (binFactor <= 0) {
+        psLogMsg(__func__, PS_LOG_WARN,
+                 "WARNING: pmSubtractSky(): binFactor is %d\n", binFactor);
+    }
+    if (stats == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN,
+                 "WARNING: pmSubtractSky(): input parameter stats is NULL\n");
+    }
+
+    //
+    // Bin the input image according to input parameters.
+    // Create a new binned image mask.
+    //
+    if ((binFactor <= 1) || (stats == NULL) || (0 == DetermineNumBits(statOptions))) {
+        // No binning is required here.  Simply create a copy of the image
+        // and a mask.
+        binnedImage = psImageCopy(binnedImage, trimmedImg, PS_TYPE_F32);
+        if (binnedImage == NULL) {
+            psError(PS_ERR_UNKNOWN, false, "psImageCopy() returned NULL.  Returning in image.\n");
+            return(in);
+        }
+
+        if (in->mask != NULL) {
+            binnedMaskImage = psImageCopy(binnedMaskImage, in->mask, PS_TYPE_U8);
+            if (binnedMaskImage == NULL) {
+                psError(PS_ERR_UNKNOWN, false, "psImageCopy() returned NULL.  Returning in image.\n");
+                psFree(binnedImage);
+                return(in);
+            }
+        } else {
+            binnedMaskImage = psImageAlloc(binnedImage->numCols,
+                                           binnedImage->numRows,
+                                           PS_TYPE_U8);
+            psImageInit(binnedMaskImage, 0);
+        }
+    } else {
+        binnedImage = psImageRebin(NULL, trimmedImg, in->mask, 0, binFactor, stats);
+        if (binnedImage == NULL) {
+            psError(PS_ERR_UNKNOWN, false, "psImageRebin() returned NULL.  Returning in image.\n");
+            return(in);
+        }
+        binnedMaskImage = psImageAlloc(binnedImage->numCols,
+                                       binnedImage->numRows,
+                                       PS_TYPE_U8);
+        psImageInit(binnedMaskImage, 0);
+    }
+    psTrace("psModules.detrend", 4,
+            "binnedImage size is (%d, %d)\n", binnedImage->numRows, binnedImage->numCols);
+
+    //
+    // Clip pixels that are outside the acceptable range.
+    //
+    if (clipSD <= 0.0) {
+        psLogMsg(__func__, PS_LOG_WARN,
+                 "WARNING: pmSubtractSky(): clipSD is %f\n", clipSD);
+    } else {
+        // Determine the mean and standard deviation of the binned image.
+        psStats *myStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV);
+        if (!psImageStats(myStats, binnedImage, NULL, 0)) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Couldn't get statistics for image.\n");
+            return NULL;
+        }
+        psF64 binnedMean = myStats->sampleMean;
+        psF64 binnedStdev = myStats->sampleStdev;
+        psFree(myStats);
+        psTrace("psModules.detrend", 6,
+                "binned StDev is %f\n", binnedStdev);
+
+        // Clip all pixels which are more than clipSD sigmas from the mean.
+        psTrace("psModules.detrend", 6,
+                "clipSD is %f\n", clipSD);
+
+        for (psS32 row = 0; row < binnedImage->numRows ; row++) {
+            for (psS32 col = 0; col < binnedImage->numCols ; col++) {
+                if (fabs(binnedImage->data.F32[row][col] - binnedMean) >
+                        (clipSD * binnedStdev)) {
+                    binnedMaskImage->data.U8[row][col] = 1;
+                }
+            }
+        }
+    }
+
+    //
+    // Fit the polynomial to the binned image
+    //
+    if (fit == PM_FIT_POLYNOMIAL) {
+        myPoly = (psPolynomial2D *) fitSpec;
+        PS_ASSERT_POLY_NON_NULL(myPoly, NULL);
+        PS_ASSERT_POLY_TYPE(myPoly, PS_POLYNOMIAL_ORD, NULL);
+
+        myPoly = ImageFitPolynomial(myPoly, binnedImage, binnedMaskImage);
+
+        if (myPoly != NULL) {
+            // Set the pixels in the binned image to that of the polynomial.
+            binnedImage = psImageEvalPolynomial(binnedImage, myPoly);
+            if (binnedImage == NULL) {
+                psError(PS_ERR_UNKNOWN, false, "psImageEvalPolynomial() returned NULL.  Returning in image.\n");
+                psFree(binnedMaskImage);
+                if (!((binFactor <= 1) || (stats == NULL))) {
+                    psFree(binnedImage);
+                }
+                if (oldStatOptions != 0) {
+                    stats->options = statOptions;
+                }
+                return(in);
+            }
+        } else {
+            psLogMsg(__func__, PS_LOG_WARN,
+                     "WARNING: pmSubtractSky(): could not model sky with a polynomial.\n");
+            psFree(binnedMaskImage);
+            if (!((binFactor <= 1) || (stats == NULL))) {
+                psFree(binnedImage);
+            }
+            if (oldStatOptions != 0) {
+                stats->options = statOptions;
+            }
+            return(in);
+        }
+    } else {
+        // We shouldn't get here since we check this above.
+        psError(PS_ERR_UNKNOWN, true, "Unallowable fit type.  Returning in image.\n");
+        psFree(binnedMaskImage);
+        if (!((binFactor <= 1) || (stats == NULL))) {
+            psFree(binnedImage);
+        }
+        if (oldStatOptions != 0) {
+            stats->options = statOptions;
+        }
+        return(in);
+    }
+
+    //
+    //Subtract the polynomially fitted image from the original image
+    //
+    if (binFactor <= 1) {
+        // The binned image is the same size as the original image.
+        for (psS32 row = 0; row < trimmedImg->numRows ; row++) {
+            for (psS32 col = 0; col < trimmedImg->numCols ; col++) {
+                trimmedImg->data.F32[row][col]-= binnedImage->data.F32[row][col];
+            }
+        }
+    } else {
+
+        psImageInterpolateOptions *interp = psImageInterpolateOptionsAlloc(PS_INTERPOLATE_BILINEAR,
+                                                                           binnedImage, NULL, NULL, 0,
+                                                                           0.0, 0.0, 0, 0, 0.0);
+
+        for (psS32 row = 0; row < trimmedImg->numRows ; row++) {
+            for (psS32 col = 0; col < trimmedImg->numCols ; col++) {
+                // We calculate the F32 value of the pixel coordinates in the
+                // binned image and then use a pixel interpolation routine to
+                // determine the value of the pixel at that location.
+                psF32 binRowF64 = ((psF32) row) / ((psF32) binFactor);
+                psF32 binColF64 = ((psF32) col) / ((psF32) binFactor);
+
+                // We add 0.5 to the pixel locations since the pixel
+                // interpolation routine defines the location of pixel
+                // (i, j) as (i+0.5, j+0.5).
+                binRowF64+= 0.5;
+                binColF64+= 0.5;
+
+                double binPixel;
+                if (!psImagePixelInterpolate(&binPixel, NULL, NULL, binColF64, binRowF64, interp)) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to interpolate image.");
+                    psFree(interp);
+                    psFree(binnedImage);
+                    return NULL;
+                }
+                trimmedImg->data.F32[row][col] -= binPixel;
+
+                psTrace("psModules.detrend", 8,
+                        "image[%d][%d] <--> binnedImage[%.2f][%.2f]: %lf\n",
+                        row, col, binRowF64-0.5, binColF64-0.5, binPixel);
+            }
+        }
+        psFree(interp);
+
+    }
+    psFree(binnedMaskImage);
+    psFree(binnedImage);
+    if (oldStatOptions != 0) {
+        stats->options = statOptions;
+    }
+
+    psTrace("psModules.detrend", 4,
+            "---- pmSubtractSky() exit successfully ----\n");
+    return(in);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmSkySubtract.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmSkySubtract.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/detrend/pmSkySubtract.h	(revision 22157)
@@ -0,0 +1,34 @@
+/* @file  pmSubtractSky.h
+ *
+ * This file will contain a module which will create a model of the
+ * background sky and subtract that from the input image.
+ *
+ * @author GLG, MHPCC
+ *
+ * @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-03-30 21:12:56 $
+ * Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+#ifndef PM_SUBTRACT_SKY_H
+#define PM_SUBTRACT_SKY_H
+
+/// @addtogroup detrend Detrend Creation and Application
+/// @{
+
+// XXX: this is pmFit in pmSubtractBias.c, named psFit here.
+typedef enum {
+    PM_FIT_NONE,                              ///< No fit
+    PM_FIT_POLYNOMIAL,                        ///< Fit polynomial
+    PM_FIT_SPLINE                             ///< Fit cubic splines
+} psFit;
+
+pmReadout *pmSubtractSky(pmReadout *in,
+                         void *fitSpec,
+                         psFit fit,
+                         int binFactor,
+                         psStats *stats,
+                         float clipSD);
+
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/extras/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/extras/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/extras/.cvsignore	(revision 22157)
@@ -0,0 +1,9 @@
+.deps
+.libs
+Makefile
+Makefile.in
+libpsmodulesextras.la
+libpsmodulesextras_la-pmKapaPlots.lo
+libpsmodulesextras_la-psIOBuffer.lo
+libpsmodulesextras_la-psPipe.lo
+libpsmodulesextras_la-psVectorBracket.lo
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/extras/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/extras/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/extras/Makefile.am	(revision 22157)
@@ -0,0 +1,17 @@
+noinst_LTLIBRARIES = libpsmodulesextras.la
+
+libpsmodulesextras_la_CPPFLAGS = $(SRCINC) $(PSMODULES_CFLAGS) -I../pslib/
+libpsmodulesextras_la_LDFLAGS  = -release $(PACKAGE_VERSION)
+libpsmodulesextras_la_SOURCES  = \
+	psVectorBracket.c \
+	psPipe.c \
+	psIOBuffer.c \
+	pmKapaPlots.c
+
+pkginclude_HEADERS = \
+	psVectorBracket.h \
+	psPipe.h \
+	psIOBuffer.h \
+	pmKapaPlots.h
+
+CLEANFILES = *~
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/extras/pmKapaPlots.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/extras/pmKapaPlots.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/extras/pmKapaPlots.c	(revision 22157)
@@ -0,0 +1,234 @@
+/** @file  pmAstrometryWCS.c
+ *
+ *  @brief functions to convert FITS WCS keywords to / from pmFPA structures
+ *
+ *  @ingroup Astrometry
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.12 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-06-20 02:20:07 $
+ *
+ *  Copyright 2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pslib.h>
+#include "pmKapaPlots.h"
+
+// the top portion of this file defines plotting functions which use kapa for plotting.
+// if kapa is not available, these functions are defined in the bottom portion as stubs
+// which perform NOP and return false (XXX should this be 'true' ??)
+
+# if (HAVE_KAPA)
+
+    // XXX not thread safe (perhaps not needed)
+    // to make this thread safe, we could check the thread ID and tie to it
+    static int kapa_fd = -1;
+
+int pmKapaOpen (bool showWindow)
+{
+    char kapa[64];
+
+    if (showWindow) {
+        strcpy (kapa, "kapa");
+    } else {
+        strcpy (kapa, "kapa -noX");
+    }
+
+    if (kapa_fd == -1) {
+        // kapa_fd = KapaOpen (kapa, "psphot");
+        kapa_fd = KapaOpenNamedSocket (kapa, "psphot");
+    }
+    return kapa_fd;
+}
+
+bool pmKapaClose ()
+{
+
+    if (kapa_fd == -1)
+        return true;
+    KapaClose (kapa_fd);
+    kapa_fd = -1;
+    return true;
+}
+
+bool pmKapaPlotVectorPair_AutoLimits_OpenGraph (int kapa, Graphdata *graphdata, psVector *xVec, psVector *yVec)
+{
+
+    // set limits based on data values
+    graphdata->xmin = +FLT_MAX;
+    graphdata->xmax = -FLT_MAX;
+    graphdata->ymin = +FLT_MAX;
+    graphdata->ymax = +FLT_MAX;
+    for (int i = 0; i < xVec->n; i++) {
+        graphdata->xmin = PS_MIN (graphdata->xmin, xVec->data.F32[i]);
+        graphdata->xmax = PS_MAX (graphdata->xmax, xVec->data.F32[i]);
+        graphdata->ymin = PS_MIN (graphdata->ymin, yVec->data.F32[i]);
+        graphdata->ymax = PS_MAX (graphdata->ymax, yVec->data.F32[i]);
+    }
+    // add 5% to range
+    float range;
+
+    range = graphdata->xmax - graphdata->xmin;
+    graphdata->xmax += 0.05*range;
+    graphdata->xmin -= 0.05*range;
+
+    range = graphdata->ymax - graphdata->ymin;
+    graphdata->ymax += 0.05*range;
+    graphdata->ymin -= 0.05*range;
+
+    KapaSetLimits (kapa, graphdata);
+    KapaSetFont (kapa, "helvetica", 14);
+    KapaBox (kapa, graphdata);
+
+    KapaPrepPlot (kapa, xVec->n, graphdata);
+    KapaPlotVector (kapa, xVec->n, xVec->data.F32, "x");
+    KapaPlotVector (kapa, yVec->n, yVec->data.F32, "y");
+    return true;
+}
+
+bool pmKapaPlotVectorPair (psVector *xVec, psVector *yVec)
+{
+
+    Graphdata graphdata;
+
+    int kapa = pmKapaOpen (true);
+    if (kapa == -1) {
+        psError(PS_ERR_UNKNOWN, true, "failure to open kapa");
+        return false;
+    }
+
+    if (xVec->n != yVec->n)
+        return false;
+
+    KapaInitGraph (&graphdata);
+    KapaClearPlots (kapa);
+
+    // set limits based on data values
+    graphdata.xmin = +FLT_MAX;
+    graphdata.xmax = -FLT_MAX;
+    graphdata.ymin = +FLT_MAX;
+    graphdata.ymax = -FLT_MAX;
+    for (int i = 0; i < xVec->n; i++) {
+        graphdata.xmin = PS_MIN (graphdata.xmin, xVec->data.F32[i]);
+        graphdata.xmax = PS_MAX (graphdata.xmax, xVec->data.F32[i]);
+        graphdata.ymin = PS_MIN (graphdata.ymin, yVec->data.F32[i]);
+        graphdata.ymax = PS_MAX (graphdata.ymax, yVec->data.F32[i]);
+    }
+    // add 5% to range
+    float range;
+
+    range = graphdata.xmax - graphdata.xmin;
+    graphdata.xmax += 0.05*range;
+    graphdata.xmin -= 0.05*range;
+
+    range = graphdata.ymax - graphdata.ymin;
+    graphdata.ymax += 0.05*range;
+    graphdata.ymin -= 0.05*range;
+
+    KapaSetLimits (kapa, &graphdata);
+    KapaSetFont (kapa, "helvetica", 14);
+    KapaBox (kapa, &graphdata);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 0;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, xVec->n, &graphdata);
+    KapaPlotVector (kapa, xVec->n, xVec->data.F32, "x");
+    KapaPlotVector (kapa, yVec->n, yVec->data.F32, "y");
+
+    return true;
+}
+
+bool pmKapaPlotVectorTriple_AutoLimits_OpenGraph (int kapa, Graphdata *graphdata, psVector *xVec, psVector *yVec, psVector *zVec, bool increasing)
+{
+
+    // set limits based on data values
+    graphdata->xmin = +FLT_MAX;
+    graphdata->xmax = -FLT_MAX;
+    graphdata->ymin = +FLT_MAX;
+    graphdata->ymax = -FLT_MAX;
+    float zmin = +FLT_MAX;
+    float zmax = -FLT_MAX;
+    for (int i = 0; i < xVec->n; i++) {
+        graphdata->xmin = PS_MIN (graphdata->xmin, xVec->data.F32[i]);
+        graphdata->xmax = PS_MAX (graphdata->xmax, xVec->data.F32[i]);
+        graphdata->ymin = PS_MIN (graphdata->ymin, yVec->data.F32[i]);
+        graphdata->ymax = PS_MAX (graphdata->ymax, yVec->data.F32[i]);
+        zmin = PS_MIN (zmin, zVec->data.F32[i]);
+        zmax = PS_MAX (zmax, zVec->data.F32[i]);
+    }
+
+    // add 5% to range
+    float range;
+
+    psVector *zScale = psVectorAlloc (zVec->n, PS_DATA_F32);
+
+    range = zmax - zmin;
+    if (range == 0.0) {
+        psVectorInit (zScale, 1.0);
+    } else {
+        for (int i = 0; i < zVec->n; i++) {
+            if (increasing) {
+                zScale->data.F32[i] = PS_MIN (1.5, PS_MAX(0.05, 1.5*(zVec->data.F32[i] - zmin)/range));
+            } else {
+                zScale->data.F32[i] = PS_MIN (1.5, PS_MAX(0.05, 1.5*(zmax - zVec->data.F32[i])/range));
+            }
+        }
+    }
+
+    range = graphdata->xmax - graphdata->xmin;
+    graphdata->xmax += 0.05*range;
+    graphdata->xmin -= 0.05*range;
+
+    range = graphdata->ymax - graphdata->ymin;
+    graphdata->ymax += 0.05*range;
+    graphdata->ymin -= 0.05*range;
+
+    KapaSetLimits (kapa, graphdata);
+    KapaSetFont (kapa, "helvetica", 14);
+    KapaBox (kapa, graphdata);
+
+    // the point size will be scaled from the z vector
+    graphdata->size = -1;
+    KapaPrepPlot (kapa, xVec->n, graphdata);
+    KapaPlotVector (kapa, xVec->n, xVec->data.F32, "x");
+    KapaPlotVector (kapa, yVec->n, yVec->data.F32, "y");
+    KapaPlotVector (kapa, zVec->n, zScale->data.F32, "z");
+    psFree (zScale);
+    return true;
+}
+
+# else
+    # include "pmKapaPlots.h"
+
+    int pmKapaOpen (bool showWindow)
+{
+    return -1;
+}
+
+bool pmKapaClose ()
+{
+    return true;
+}
+
+bool pmKapaPlotVectorPair (psVector *xVec, psVector *yVec)
+{
+    return false;
+}
+
+bool pmKapaPlotVectorPair_AutoLimits_OpenGraph (int kapa, void *graphdata, psVector *xVec, psVector *yVec)
+{
+    return false;
+}
+
+bool pmKapaPlotVectorTriple_AutoLimits_OpenGraph (int kapa, void *graphdata, psVector *xVec, psVector *yVec, psVector *zVec, bool increasing)
+{
+    return false;
+}
+# endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/extras/pmKapaPlots.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/extras/pmKapaPlots.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/extras/pmKapaPlots.h	(revision 22157)
@@ -0,0 +1,35 @@
+/* @file  pmKapaPlots.h
+ * @brief functions to make plots with the external program 'kapa' 
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.7 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-03-30 21:12:56 $
+ * Copyright 2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_KAPA_PLOTS_H
+#define PM_KAPA_PLOTS_H
+
+/// @addtogroup Extras Miscellaneous Funtions
+/// @{
+
+// move to psLib or psModules
+int pmKapaOpen (bool showWindow);
+bool pmKapaClose ();
+bool pmKapaPlotVectorPair (psVector *xVec, psVector *yVec);
+
+# if (HAVE_KAPA)
+# include <kapa.h>
+
+// yes, this is an absurd name...
+bool pmKapaPlotVectorPair_AutoLimits_OpenGraph (int kapa, Graphdata *graphdata, psVector *xVec, psVector *yVec);
+bool pmKapaPlotVectorTriple_AutoLimits_OpenGraph (int kapa, Graphdata *graphdata, psVector *xVec, psVector *yVec, psVector *zVec, bool increasing);
+# else
+
+bool pmKapaPlotVectorPair_AutoLimits_OpenGraph (int kapa, void *graphdata, psVector *xVec, psVector *yVec);
+bool pmKapaPlotVectorTriple_AutoLimits_OpenGraph (int kapa, void *graphdata, psVector *xVec, psVector *yVec, psVector *zVec, bool increasing);
+# endif
+
+/// @}
+#endif // PM_KAPA_PLOTS_H
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/extras/psIOBuffer.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/extras/psIOBuffer.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/extras/psIOBuffer.c	(revision 22157)
@@ -0,0 +1,113 @@
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <pslib.h>
+#include "psIOBuffer.h"
+
+static void psIOBufferFree (psIOBuffer *buffer)
+{
+    if (buffer == NULL)
+        return;
+
+    psFree (buffer->data);
+    return;
+}
+
+psIOBuffer *psIOBufferAlloc (int nBuffer)
+{
+
+    psIOBuffer *buffer = (psIOBuffer *)psAlloc(sizeof(psIOBuffer));
+    psMemSetDeallocator(buffer, (psFreeFunc) psIOBufferFree);
+
+    buffer->data = (char *) psAlloc (nBuffer + 1);
+
+    buffer->nAlloc = nBuffer + 1;
+    buffer->nReset = nBuffer;
+    buffer->nBlock = nBuffer / 2;
+    buffer->n = 0;
+    return (buffer);
+}
+
+bool psIOBufferFlush (psIOBuffer *buffer)
+{
+
+    if (buffer == NULL)
+        return false;
+    buffer->n = 0;
+    buffer->nAlloc = buffer->nReset;
+    buffer->data = psRealloc (buffer->data, buffer->nAlloc);
+    memset(buffer->data, '\0', buffer->nAlloc);
+
+    return true;
+}
+
+int psIOBufferRead (psIOBuffer *buffer, int fd)
+{
+
+    int Nread, Nfree;
+
+    if (fd == 0) {
+        /* pipe is closed */
+        return (0);
+    }
+
+    Nfree = buffer->nAlloc - buffer->n - 1;
+
+    // extend the data block if needed
+    if (Nfree < buffer->nBlock) {
+        buffer->nAlloc += 2*buffer->nBlock + 1;
+        buffer->data = psRealloc (buffer->data, buffer->nAlloc);
+        Nfree = buffer->nAlloc - buffer->n;
+        memset(buffer->data + buffer->n, '\0', Nfree);
+    }
+
+    // attempt to read from the fd into the buffer
+    Nread = read (fd, &buffer->data[buffer->n], buffer->nBlock);
+
+    if (Nread >= 0) {
+        buffer->n += Nread;
+        buffer->data[buffer->n] = 0;
+        return (Nread);
+    }
+
+    // check on exit status (try again if waiting for non-blocking fd)
+    if (Nread == -1) {
+        switch (errno) {
+        case EAGAIN:
+        case EIO:
+            /** no data available in pipe **/
+            return (-1);
+        default:
+            /** error reading from pipe **/
+            psError (PS_ERR_IO, true, "error on psIOBufferRead");
+            return (-2);
+        }
+    }
+    return (Nread);
+}
+
+/* read until buffer is empty (Nmax retries) */
+int psIOBufferReadEmpty (psIOBuffer *buffer, int Nmax, int fd)
+{
+
+    int i, status;
+
+    status = -1;
+    for (i = 0; (status != 0) && (i < Nmax); i++) {
+        status = psIOBufferRead (buffer, fd);
+        if (status == -2)
+            return false;
+        if (status == -1)
+            usleep (10000);
+        if (status > 0)
+            i = 0;
+    }
+    if (status == -1)
+        return false;
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/extras/psIOBuffer.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/extras/psIOBuffer.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/extras/psIOBuffer.h	(revision 22157)
@@ -0,0 +1,34 @@
+/* @file  IOBuffer.h
+ * @brief input/output character buffer
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-01-24 02:54:15 $
+ * Copyright 2004-2005 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PS_IO_BUFFER_H
+#define PS_IO_BUFFER_H
+
+/// @addtogroup Extras Miscellaneous Funtions
+/// @{
+
+typedef struct
+{
+    char *data;
+    int nAlloc;    // current size of allocated buffer
+    int nReset;    // size to set buffer after flush
+    int nBlock;    // number of bytes to try to read at a time
+    int n;    // current size of filled data
+}
+psIOBuffer;
+
+// psIOBuffer functions
+psIOBuffer *psIOBufferAlloc (int nBuffer);
+bool psIOBufferFlush (psIOBuffer *buffer);
+int psIOBufferRead (psIOBuffer *buffer, int fd);
+int psIOBufferReadEmpty (psIOBuffer *buffer, int maxRetries, int fd);
+
+/// @}
+# endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/extras/psPipe.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/extras/psPipe.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/extras/psPipe.c	(revision 22157)
@@ -0,0 +1,210 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <pslib.h>
+#include <psPipe.h>
+
+void closePipes (int *stdin_fd, int *stdout_fd, int *stderr_fd)
+{
+
+    if (stdin_fd[0]  != 0)
+        close (stdin_fd[0]);
+    if (stdin_fd[1]  != 0)
+        close (stdin_fd[0]);
+    if (stdout_fd[0] != 0)
+        close (stdout_fd[0]);
+    if (stdout_fd[1] != 0)
+        close (stdout_fd[1]);
+    if (stderr_fd[0] != 0)
+        close (stderr_fd[0]);
+    if (stderr_fd[1] != 0)
+        close (stderr_fd[1]);
+}
+
+static void psPipeFree (psPipe *pipe)
+{
+    return;
+}
+
+psPipe *psPipeAlloc ()
+{
+    psPipe *pipe = (psPipe *)psAlloc(sizeof(psPipe));
+    psMemSetDeallocator(pipe, (psFreeFunc) psPipeFree);
+
+    pipe->fd_stdin  = 0;
+    pipe->fd_stdout = 0;
+    pipe->fd_stderr = 0;
+    return (pipe);
+}
+
+psPipe *psPipeOpen (char *command)
+{
+
+    int stdin_fd[2], stdout_fd[2], stderr_fd[2], status;
+    pid_t pid;
+
+    memset(stdin_fd,  '\0', 2*sizeof(int));
+    memset(stdout_fd, '\0', 2*sizeof(int));
+    memset(stderr_fd, '\0', 2*sizeof(int));
+
+    if (pipe (stdin_fd)  < 0) {
+        psError (PS_ERR_UNKNOWN, true, "cannot create pipe file descriptor");
+        closePipes (stdin_fd, stdout_fd, stderr_fd);
+        return NULL;
+    }
+    if (pipe (stdout_fd) < 0) {
+        psError (PS_ERR_UNKNOWN, true, "cannot create pipe file descriptor");
+        closePipes (stdin_fd, stdout_fd, stderr_fd);
+        return NULL;
+    }
+    if (pipe (stderr_fd) < 0) {
+        psError (PS_ERR_UNKNOWN, true, "cannot create pipe file descriptor");
+        closePipes (stdin_fd, stdout_fd, stderr_fd);
+        return NULL;
+    }
+
+    psArray *cmd = psStringSplitArray (command, " ", false);
+    if (cmd->n <= 0) {
+        psError (PS_ERR_UNKNOWN, true, "empty command for pipe");
+        psFree (cmd);
+        closePipes (stdin_fd, stdout_fd, stderr_fd);
+        return NULL;
+    }
+
+    // create the command line array needed by execvp
+    char **argv = (char **)psAlloc((cmd->n+1)*sizeof(char *));
+    for (int i = 0; i < cmd->n; i++) {
+        argv[i] = cmd->data[i];
+    }
+    argv[cmd->n] = NULL;
+
+    pid = fork ();
+    if (!pid) { /* must be child process */
+        /* close the other ends of the pipes */
+        close (stdin_fd[1]);
+        close (stdout_fd[0]);
+        close (stderr_fd[0]);
+
+        /* tie our ends of the pipes to stdin, stdout, stderr */
+        dup2 (stdin_fd[0],  STDIN_FILENO);
+        dup2 (stdout_fd[1], STDOUT_FILENO);
+        dup2 (stderr_fd[1], STDERR_FILENO);
+
+        /* set all three unblocking */
+        setvbuf (stdin,  (char *) NULL, _IONBF, BUFSIZ);
+        setvbuf (stdout, (char *) NULL, _IONBF, BUFSIZ);
+        setvbuf (stderr, (char *) NULL, _IONBF, BUFSIZ);
+
+        status = execvp (argv[0], argv);
+
+        // this statement exits the child, not the parent, process
+        exit (1);
+    }
+    psFree (cmd);
+    psFree (argv);
+
+    if (pid == -1) {
+        psError (PS_ERR_UNKNOWN, true, "unable to create child process");
+        closePipes (stdin_fd, stdout_fd, stderr_fd);
+        return NULL;
+    }
+
+    /* close the other ends of the pipes */
+    close (stdin_fd[0]);
+    stdin_fd[0]  = 0;
+    close (stdout_fd[1]);
+    stdout_fd[1] = 0;
+    close (stderr_fd[1]);
+    stderr_fd[1] = 0;
+
+    /* make the pipes non-blocking */
+    fcntl (stdin_fd[1],  F_SETFL, O_NONBLOCK);
+    fcntl (stdout_fd[0], F_SETFL, O_NONBLOCK);
+    fcntl (stderr_fd[0], F_SETFL, O_NONBLOCK);
+
+    psPipe *pipe = psPipeAlloc();
+
+    pipe->pid    = pid;
+    pipe->fd_stdin  = stdin_fd[1];
+    pipe->fd_stdout = stdout_fd[0];
+    pipe->fd_stderr = stderr_fd[0];
+
+    return (pipe);
+}
+
+// this function returns the exit status of the called function
+// or a value > 255 on an error
+int psPipeClose (psPipe *pipe)
+{
+    int close_status;
+    int exit_status;
+    int wait_status;
+    int result;
+
+    PS_ASSERT_PTR_NON_NULL(pipe, false);
+
+    close_status = true;
+    if (close (pipe->fd_stdin) != 0) {
+        psError(PS_ERR_IO, true, "error closing the pipe stdin (pid %d, error %s)\n", pipe->pid, strerror(errno));
+        close_status = false;
+    }
+    if (close (pipe->fd_stdout) != 0) {
+        psError(PS_ERR_IO, true, "error closing the pipe stdout (pid %d, error %s)\n", pipe->pid, strerror(errno));
+        close_status = false;
+    }
+    if (close (pipe->fd_stderr) != 0) {
+        psError(PS_ERR_IO, true, "error closing the pipe sterr (pid %d, error %s)\n", pipe->pid, strerror(errno));
+        close_status = false;
+    }
+
+    // we expect the child process to have exited.
+    // wait for the exit condition, but no longer than 100ms
+    for (int i = 0; i < 10; i++) {
+        result = waitpid (pipe->pid, &wait_status, WNOHANG);
+        switch (result) {
+        case -1:   // error on waitpid
+            switch (errno) {
+            case ECHILD:
+                psError(PS_ERR_IO, true, "unknown PID, not a child process: %d\n", pipe->pid);
+                return 0x100;
+            default:
+                psAbort("unexpected response to waitpid: %d\n", result);
+            }
+            break;
+
+        case 0:   // child not yet exited
+            usleep (10000);
+            continue;
+
+        default:
+            if (result != pipe->pid) {
+                psAbort("waitpid error: mis-matched PID (%d vs %d).  programming error\n", result, pipe->pid);
+            }
+            if (WIFEXITED(wait_status)) {
+                exit_status = WEXITSTATUS(wait_status);
+                if (close_status) {
+                    return exit_status;
+                } else {
+                    return (0x100);
+                }
+            }
+            if (WIFSIGNALED(wait_status)) {
+                psError(PS_ERR_IO, true, "job %d exited on signal %d\n", pipe->pid, WTERMSIG(wait_status));
+                return (0x100 + WTERMSIG(wait_status));
+            }
+            if (WIFSTOPPED(wait_status)) {
+                psAbort("waitpid returns 'stopped' programming error\n");
+            }
+        }
+    }
+    psError(PS_ERR_IO, true, "child process pid %d did not exit\n", pipe->pid);
+    return 0x100;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/extras/psPipe.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/extras/psPipe.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/extras/psPipe.h	(revision 22157)
@@ -0,0 +1,33 @@
+/* @file  psPipe.h
+ * @brief 3-stream pipe 
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-01-24 02:54:15 $
+ * Copyright 2004-2005 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PS_PIPE_H
+#define PS_PIPE_H
+
+/// @addtogroup Extras Miscellaneous Funtions
+/// @{
+
+// move these to pslib??
+typedef struct
+{
+    int pid;
+    int fd_stdin;
+    int fd_stdout;
+    int fd_stderr;
+}
+psPipe;
+
+// psPipe functions
+psPipe *psPipeAlloc ();
+psPipe *psPipeOpen (char *command);
+int     psPipeClose (psPipe *pipe);
+
+/// @}
+# endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/extras/psVectorBracket.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/extras/psVectorBracket.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/extras/psVectorBracket.c	(revision 22157)
@@ -0,0 +1,137 @@
+/** @file  psVectorBracket.c
+ *
+ *  Vector Bracketing tools
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-12-10 18:27:26 $
+ *
+ *  Copyright 2006 Institute for Astronomy, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pslib.h>
+#include "psVectorBracket.h"
+
+// return the last entry below or first entry above key value
+int psVectorBracket(const psVector *index, psF32 key, bool above)
+{
+
+    int N;
+    int Nlo = 0;
+    int Nhi = index->n;
+
+    if (above) {
+        while (Nhi - Nlo > 10) {
+            N = 0.5*(Nlo + Nhi);
+            if (index->data.F32[N] > key) {
+                Nhi = N;
+            } else {
+                Nlo = N - 1;
+            }
+        }
+        // at this point, index[Nhi] > key >= index[Nlo]
+        N = Nlo;
+        while ((index->data.F32[N] <= key) && (N < Nhi)) {
+            N++;
+        }
+        return (N);
+    }
+    while (Nhi - Nlo > 10) {
+        N = 0.5*(Nlo + Nhi);
+        if (index->data.F32[N] < key) {
+            Nlo = N;
+        } else {
+            Nhi = N + 1;
+        }
+    }
+    // at this point, index[Nhi] >= key > index[Nlo]
+    N = Nhi;
+    while ((index->data.F32[N] >= key) && (N > Nlo)) {
+        N--;
+    }
+    return (N);
+}
+
+// return the last entry below or first entry above key value (reverse sorted input)
+int psVectorBracketDescend(const psVector *index, psF32 key, bool above)
+{
+
+    int N;
+    int Nhi = 0;
+    int Nlo = index->n;
+
+    if (above) {
+        while (Nlo - Nhi > 10) {
+            N = 0.5*(Nhi + Nlo);
+            if (index->data.F32[N] < key) {
+                Nlo = N;
+            } else {
+                Nhi = N - 1;
+            }
+        }
+        // at this point, index[Nhi] >= key > index[Nlo]
+        N = Nhi;
+        while ((index->data.F32[N] >= key) && (N < Nlo)) {
+            N++;
+        }
+        return (N);
+    }
+    while (Nlo - Nhi > 10) {
+        N = 0.5*(Nhi + Nlo);
+        if (index->data.F32[N] > key) {
+            Nhi = N;
+        } else {
+            Nlo = N + 1;
+        }
+    }
+    // at this point, index[Nhi] > key >= index[Nlo]
+    N = Nlo;
+    while ((index->data.F32[N] <= key) && (N > Nhi)) {
+        N--;
+    }
+    return (N);
+}
+
+// search for the bins bounding key in index, interpolate the corresponding values
+psF32 psVectorInterpolate(const psVector *index, const psVector *value, psF32 key)
+{
+
+    int n0 = 0;
+    int n1 = 0;
+
+    // extrapolate at ends
+    if (key < index->data.F32[0]) {
+        n0 = 0;
+        n1 = 1;
+    }
+
+    // extrapolate at ends
+    if (key > index->data.F32[index->n-1]) {
+        n0 = index->n-2;
+        n1 = index->n-1;
+    }
+
+    if (n1 == 0) {
+        n0 = psVectorBracket (index, key, FALSE);
+        n1 = n0 + 1;
+    }
+
+    if (n0 == index->n-1) {
+        n1 = n0;
+        n0 = n1 - 1;
+    }
+
+    float dy = value->data.F32[n1] - value->data.F32[n0];
+    float dx = index->data.F32[n1] - index->data.F32[n0];
+    float dX = key - index->data.F32[n0];
+    float dY = dX * (dy/dx);
+    float result = value->data.F32[n0] + dY;
+    return result;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/extras/psVectorBracket.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/extras/psVectorBracket.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/extras/psVectorBracket.h	(revision 22157)
@@ -0,0 +1,22 @@
+/* @file  psVectorBracket.h
+ * @brief vector bracket functions
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-01-24 02:54:15 $
+ * Copyright 2004-2005 Institute for Astronomy, University of Hawaii
+ */
+
+# ifndef PS_VECTOR_BRACKET_H
+# define PS_VECTOR_BRACKET_H
+
+/// @addtogroup Extras Miscellaneous Funtions
+/// @{
+
+int psVectorBracket(const psVector *index, psF32 key, bool above);
+int psVectorBracketDescend(const psVector *index, psF32 key, bool above);
+psF32 psVectorInterpolate(const psVector *index, const psVector *value, psF32 key);
+
+/// @}
+# endif /* PS_VECTOR_BRACKET_H */
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/.cvsignore	(revision 22157)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.la
+*.lo
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/Makefile.am	(revision 22157)
@@ -0,0 +1,35 @@
+noinst_LTLIBRARIES = libpsmodulesimcombine.la
+
+libpsmodulesimcombine_la_CPPFLAGS = $(SRCINC) $(PSMODULES_CFLAGS)
+libpsmodulesimcombine_la_LDFLAGS  = -release $(PACKAGE_VERSION)
+
+libpsmodulesimcombine_la_SOURCES = \
+	pmReadoutCombine.c	\
+	pmStack.c		\
+	pmStackReject.c		\
+	pmSubtraction.c		\
+	pmSubtractionEquation.c	\
+	pmSubtractionIO.c	\
+	pmSubtractionKernels.c	\
+	pmSubtractionMask.c	\
+	pmSubtractionMatch.c	\
+	pmSubtractionParams.c	\
+	pmSubtractionStamps.c	\
+	pmPSFEnvelope.c
+
+pkginclude_HEADERS = \
+	pmImageCombine.h \
+	pmReadoutCombine.h	\
+	pmStack.h		\
+	pmStackReject.h		\
+	pmSubtraction.h		\
+	pmSubtractionEquation.h	\
+	pmSubtractionIO.h	\
+	pmSubtractionKernels.h	\
+	pmSubtractionMask.h	\
+	pmSubtractionMatch.h	\
+	pmSubtractionParams.h	\
+	pmSubtractionStamps.h	\
+	pmPSFEnvelope.h
+
+CLEANFILES = *~
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/demo_psftool.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/demo_psftool.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/demo_psftool.c	(revision 22157)
@@ -0,0 +1,194 @@
+// This is a very quick and dirty test program for pmPSFEnvelope
+//
+// gcc -g demo_psftool.c -o demo_psftool `psmodules-config --cflags --libs` --std=gnu99 -Wall
+//
+// ./demo_psftool test MOPS.skycell.0198767.wrp*.psf
+//
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+// Add a single filename to the arguments as an array, so that it can be used with pmFPAfileBindFromArgs, etc
+static void fileList(const char *file, // The symbolic name for the file
+                     const char *name, // The name of the file
+                     const char *comment, // Description of the file
+                     pmConfig *config // Configuration
+    )
+{
+    psArray *files = psArrayAlloc(1); // Array with file names
+    files->data[0] = psStringCopy(name);
+    psMetadataAddArray(config->arguments, PS_LIST_TAIL, file, 0, comment, files);
+    psFree(files);
+    return;
+}
+
+
+
+int main(int argc, char *argv[])
+{
+    psLibInit(NULL);
+    pmConfig *config = pmConfigRead(&argc, argv, "PSF");
+    if (!config) {
+        psErrorStackPrint(stderr, "Error reading configuration.");
+        exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    psTraceSetLevel("psModules.imcombine", 5);
+    psTraceSetLevel("psModules.objects", 0);
+    psTraceSetLevel("psLib.math", 0);
+
+    pmModelClassInit();
+
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "Name of the output", argv[1]);
+
+    psArray *files = psArrayAlloc(argc - 2);
+    for (int i = 2; i < argc; i++) {
+        psString name = NULL;           // Name of file list
+        psStringAppend(&name, "INPUT_%d", i);
+
+        fileList(name, argv[i], "Input PSF", config);
+
+        pmFPAfile *file = pmFPAfileDefineFromArgs(NULL, config, "PSPHOT.PSF.LOAD", name);
+        psFree(name);
+        if (!file) {
+            psErrorStackPrint(stderr, "Can't define PSF file from %s --> %s", name, argv[i]);
+            psFree(files);
+            psFree(config);
+            exit(PS_EXIT_SYS_ERROR);
+        }
+
+        files->data[i - 2] = psMemIncrRefCounter(file);
+    }
+
+    pmFPAfile *outFile = pmFPAfileDefineOutput(config, NULL, "PSPHOT.PSF.SAVE");
+    if (!outFile) {
+        psErrorStackPrint(stderr, "Can't define output PSF file");
+        psFree(files);
+        psFree(config);
+        exit(PS_EXIT_SYS_ERROR);
+    }
+    outFile->save = true;
+
+    // XXX This is a bit dodgy; should be more rigorous for a real system
+    {
+        pmFPAview *phuView = pmFPAviewAlloc(0);
+        phuView->chip = 0;
+        if (!pmFPAAddSourceFromView(outFile->fpa, "Envelope PSF", phuView, outFile->format)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to add PHU to output.");
+            psFree(phuView);
+            return false;
+        }
+        psFree(phuView);
+    }
+
+    pmFPAview *view = pmFPAviewAlloc(0);
+
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+        psErrorStackPrint(stderr, "Problem in I/O");
+        psFree(view);
+        psFree(files);
+        psFree(config);
+        exit(PS_EXIT_SYS_ERROR);
+    }
+
+    pmChip *chip;
+    while ((chip = pmFPAviewNextChip(view, outFile->fpa, 1)) != NULL) {
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+            psErrorStackPrint(stderr, "Problem in I/O");
+            psFree(view);
+            psFree(files);
+            psFree(config);
+            exit(PS_EXIT_SYS_ERROR);
+        }
+
+#if 0
+        pmFPAfileActivate(config->files, "PSPHOT.PSF.SAVE", false);
+        pmCell *cell;
+        while ((cell = pmFPAviewNextCell(view, outFile->fpa, 1)) != NULL) {
+            if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+                psErrorStackPrint(stderr, "Problem in I/O");
+                psFree(view);
+                psFree(files);
+                psFree(config);
+                exit(PS_EXIT_SYS_ERROR);
+            }
+        }
+#endif
+
+        psArray *inputs = psArrayAlloc(files->n);
+        int numCols = 4501, numRows = 4751;
+        for (int i = 0; i < files->n; i++) {
+            pmFPAfile *file = files->data[i];
+            pmChip *chip = pmFPAviewThisChip(view, file->fpa);
+
+            pmPSF *psf = psMetadataLookupPtr(NULL, chip->analysis, "PSPHOT.PSF");
+            if (!psf) {
+                psErrorStackPrint(stderr, "Can't find PSF in file %d", i);
+                psFree(inputs);
+                psFree(files);
+                psFree(config);
+                exit(PS_EXIT_PROG_ERROR);
+            }
+
+#if 0
+            pmHDU *hdu = pmHDUGetLowest(file->fpa, chip, NULL);
+            int imaxis1 = psMetadataLookupS32(NULL, hdu->header, "IMAXIS1");
+            int imaxis2 = psMetadataLookupS32(NULL, hdu->header, "IMAXIS2");
+            if (imaxis1 == 0 || imaxis2 == 0) {
+                psErrorStackPrint(stderr, "Size of image %d can't be determined.", i);
+                psFree(inputs);
+                psFree(files);
+                psFree(config);
+                exit(PS_EXIT_SYS_ERROR);
+            }
+            if (numCols == 0 && numRows == 0) {
+                numCols = imaxis1;
+                numRows = imaxis2;
+            } else if (imaxis1 != numCols || imaxis2 != numRows) {
+                psErrorStackPrint(stderr, "Image %d differs in size: %dx%d vs %dx%d",
+                                  i, imaxis1, imaxis2, numCols, numRows);
+                psFree(inputs);
+                psFree(files);
+                psFree(config);
+                exit(PS_EXIT_SYS_ERROR);
+            }
+#endif
+
+            inputs->data[i] = psMemIncrRefCounter(psf);
+        }
+
+        pmPSF *psf = pmPSFEnvelope(numCols, numRows, inputs, 5, 20, "PS_MODEL_RGAUSS", 3, 3);
+        psFree(inputs);
+        if (!psf) {
+            psErrorStackPrint(stderr, "Can't generate envelope PSF.");
+            psFree(config);
+            exit(PS_EXIT_SYS_ERROR);
+        }
+
+        psMetadataAddPtr(chip->analysis, PS_LIST_TAIL, "PSPHOT.PSF", PS_DATA_UNKNOWN, "Envelope PSF", psf);
+        psFree(psf);
+        chip->data_exists = true;
+
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+            psErrorStackPrint(stderr, "Problem in I/O");
+            psFree(view);
+            psFree(files);
+            psFree(config);
+            exit(PS_EXIT_SYS_ERROR);
+            return false;
+        }
+    }
+
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+        psErrorStackPrint(stderr, "Problem in I/O");
+        psFree(view);
+        psFree(files);
+        psFree(config);
+        exit(PS_EXIT_SYS_ERROR);
+    }
+
+    psFree(config);
+
+    exit(PS_EXIT_SUCCESS);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmImageCombine.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmImageCombine.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmImageCombine.c	(revision 22157)
@@ -0,0 +1,683 @@
+/** @file  pmImageCombine.c
+ *
+ *  This file will perform image combination of several images of the
+ *  same field, produce a list of questionable pixels, then tag some
+ *  of those pixels as cosmic rays.
+ *
+ *  @author Paul Price, IfA (original prototype)
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.12 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-04-04 22:42:48 $
+ *
+ *  XXX: pmRejectPixels() has a known bug with the pmImageTransform() call.
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <config.h>
+#include <stdio.h>
+#include <math.h>
+#include "pslib.h"
+
+#define PIXEL_LIST_BUFFER 100           // Size of the pixel list buffer
+
+// Data structure for use as a buffer in combining pixels
+typedef struct
+{
+    psVector *pixels;                   // Pixel values
+    psVector *masks;                    // Pixel masks
+    psVector *errors;                   // Pixel errors
+    psStats *stats;                     // Statistics to use with combination
+}
+combineBuffer;
+
+void combineBufferFree(combineBuffer *buffer)
+{
+    psFree(buffer->pixels);
+    psFree(buffer->masks);
+    psFree(buffer->errors);
+    psFree(buffer->stats);
+}
+
+combineBuffer *combineBufferAlloc(long numImages // Number of images that will be combined
+                                 )
+{
+    combineBuffer *buffer = psAlloc(sizeof(combineBuffer));
+    psMemSetDeallocator(buffer, (psFreeFunc)combineBufferFree);
+
+    buffer->pixels = psVectorAlloc(numImages, PS_TYPE_F32);
+    buffer->masks = psVectorAlloc(numImages, PS_TYPE_MASK);
+    buffer->errors = psVectorAlloc(numImages, PS_TYPE_F32);
+    buffer->stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV);
+
+    return buffer;
+}
+
+
+static bool combinePixels(psImage *combine, // Combined image, for output
+                          psArray *questionablePixels, // Array of rejection masks
+                          int x, int y, // Position in the images
+                          const psArray *images, // Array of input images
+                          const psArray *errors, // Array of input error images
+                          const psArray *masks, // Array of input masks
+                          psU32 maskVal, // Mask value
+                          psS32 numIter, // Number of rejection iterations
+                          psF32 sigmaClip, // Number of standard deviations at which to reject
+                          combineBuffer *buffer // Buffer for combination; to avoid multiple allocations
+                         )
+{
+    assert(combine);
+    assert(x >= 0 && x < combine->numCols);
+    assert(y >= 0 && y < combine->numRows);
+    assert(images);
+    int numImages = images->n;          // Number of images to combine
+    if (masks) {
+        assert(masks->n == numImages);
+    }
+    if (errors) {
+        assert(errors->n == numImages);
+    }
+    assert(numIter >= 0);
+    assert(sigmaClip > 0);
+    assert(!questionablePixels || (questionablePixels && questionablePixels->n == numImages));
+
+    if (buffer) {
+        psMemIncrRefCounter(buffer);
+    } else {
+        buffer = combineBufferAlloc(numImages);
+    }
+
+    psVector *pixelData = buffer->pixels; // Values for the pixel of interest
+    psVector *pixelErrors = buffer->errors; // Errors for the pixel of interest
+    psVector *pixelMasks = buffer->masks; // Masks for the pixel of interest
+    psStats *stats = buffer->stats;     // Statistics for combination
+
+    //
+    // Loop through each image, extract the pixel/mask/error data into psVectors.
+    //
+    if (!masks) {
+        psVectorInit(pixelMasks, 0);
+    }
+    if (!errors) {
+        pixelErrors = NULL;
+    }
+    for (int i = 0; i < numImages; i++) {
+        // Set the pixel data
+        psImage *image = images->data[i]; // Image of interest
+        pixelData->data.F32[i] = image->data.F32[y][x];
+        // Set the pixel mask data, if necessary
+        if (masks) {
+            psImage *mask = masks->data[i]; // Mask of interest
+            pixelMasks->data.U8[i] = mask->data.U8[y][x];
+        }        // Set the pixel error data, if necessary
+        if (errors) {
+            psImage *error = errors->data[i]; // Error image of interest
+            pixelErrors->data.F32[i] = error->data.F32[y][x];
+        }
+    }
+
+    //
+    // Iterate on the pixels, rejecting outliers
+    //
+    for (int iter = 0; iter < numIter; iter++) {
+        // Combine all the pixels, using the specified stat.
+        if (!psVectorStats(stats, pixelData, pixelErrors, pixelMasks, maskVal)) {
+            combine->data.F32[y][x] = NAN;
+            psFree(buffer);
+            return false;
+        }
+        float combinedPixel = stats->sampleMean; // Value of the combination
+
+        if (iter == 0) {
+            // Save the value produced with no rejection, since it may be useful later
+            // (if the rejection turns out to be unnecessary)
+            combine->data.F32[y][x] = combinedPixel;
+        }
+
+        //
+        // Reject all pixels that lie more that sigmaClip standard deviations from
+        // the combined pixel value.
+        //
+        int numRejects = 0;     // Number of rejections
+        float stdev = stats->sampleStdev;
+        for (int i = 0; i < numImages; i++) {
+            if (!(pixelMasks->data.U8[i] & maskVal) &&
+                    fabs(pixelData->data.F32[i] - combinedPixel) > sigmaClip * stdev) {
+                // Reject pixel as questionable
+                numRejects++;
+                pixelMasks->data.U8[i] = maskVal;
+                if (questionablePixels) {
+                    // Mark the pixel as questionable
+                    psPixels *qp = questionablePixels->data[i]; // Questionable pixels for this image
+                    int qpNum = qp->n; // Number of QPs in the image of interest
+                    if (qpNum >= qp->nalloc) {
+                        // Grow dynamically, if required
+                        qp = psPixelsRealloc(qp, qp->nalloc + PIXEL_LIST_BUFFER);
+                        questionablePixels->data[i] = qp;
+                    }
+                    qp->data[qpNum].x = x;
+                    qp->data[qpNum].y = y;
+                    qp->n++;
+                }
+            }
+        }
+
+        //
+        // If the number of rejected pixels is zero, then there's no point continuing the loop.
+        //
+        if (numRejects == 0) {
+            break;
+        }
+
+        //
+        // XXX: Is it possible to have all pixels rejected?  If so, we should exit the loop.
+        //
+    }
+
+    psFree(buffer);
+    return true;
+}
+
+psImage *pmCombineImages(
+    psImage *combine,                   ///< Combined image (output)
+    psArray **questionablePixels,       ///< Array of rejection masks
+    const psArray *images,              ///< Array of input images
+    const psArray *errors,              ///< Array of input error images
+    const psArray *masks,               ///< Array of input masks
+    psU32 maskVal,                      ///< Mask value
+    const psPixels *pixels,             ///< Pixels to combine
+    psS32 numIter,                      ///< Number of rejection iterations
+    psF32 sigmaClip                     ///< Number of standard deviations at which to reject
+)
+{
+    psTrace("psModules.imcombine", 3, "Calling pmCombineImages(%ld)\n", images->n);
+
+    PS_ASSERT_ARRAY_NON_NULL(images, NULL);
+    PS_ASSERT_INT_POSITIVE(images->n, NULL);
+    long numImages = images->n;          // Number of images
+    int numCols = ((psImage*)images->data[0])->numCols; // Size in x
+    int numRows = ((psImage*)images->data[0])->numRows; // Size in y
+
+    if (combine) {
+        PS_ASSERT_IMAGE_NON_NULL(combine, NULL);
+        PS_ASSERT_IMAGE_SIZE(combine, numCols, numRows, NULL);
+    }
+    if (questionablePixels && !*questionablePixels) {
+        PS_ASSERT_ARRAY_NON_NULL(*questionablePixels, NULL);
+        PS_ASSERT_ARRAY_SIZE(*questionablePixels, numImages, 0);
+    }
+    for (int i = 1; i < images->n; i++) {
+        psImage *image = images->data[i]; // Image of interest
+        PS_ASSERT_IMAGE_TYPE(image, PS_TYPE_F32, NULL);
+        PS_ASSERT_IMAGE_SIZE(image, numCols, numRows, NULL);
+    }
+    if (errors) {
+        PS_ASSERT_ARRAYS_SIZE_EQUAL(images, errors, NULL);
+        for (int i = 0; i < images->n; i++) {
+            psImage *error = errors->data[i];
+            PS_ASSERT_IMAGE_SIZE(error, numCols, numRows, NULL);
+            PS_ASSERT_IMAGE_TYPE(error, PS_TYPE_F32, NULL);
+        }
+    }
+    if (masks) {
+        PS_ASSERT_ARRAYS_SIZE_EQUAL(images, masks, NULL);
+        for (int i = 0; i < images->n; i++) {
+            psImage *mask  = masks->data[i];
+            PS_ASSERT_IMAGE_SIZE(mask, numCols, numRows, NULL);
+            PS_ASSERT_IMAGE_TYPE(mask, PS_TYPE_MASK, NULL);
+        }
+    }
+    PS_ASSERT_INT_POSITIVE(numIter, NULL);
+    PS_ASSERT_FLOAT_LARGER_THAN(sigmaClip, 0.0, NULL);
+
+    // Allocate and initialize the combined image, if necessary.
+    if (!combine) {
+        combine = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+        if (pixels) {
+            // Set everything we're not working on to NAN
+            psImageInit(combine, NAN);
+        }
+    }
+
+    //
+    // Allocate the questionablePixels psArray, if necesssary, then create a psPixels
+    // struct for each image.
+    //
+    if (questionablePixels) {
+        if (*questionablePixels == NULL) {
+            *questionablePixels = psArrayAlloc(numImages);
+        } else if ((*questionablePixels)->n != numImages) {
+            *questionablePixels = psArrayRealloc(*questionablePixels, numImages);
+        }
+        for (int i = 0; i < numImages; i++) {
+            psFree((*questionablePixels)->data[i]);
+            (*questionablePixels)->data[i] = psPixelsAlloc(PIXEL_LIST_BUFFER);
+        }
+    }
+
+    combineBuffer *buffer = combineBufferAlloc(numImages); // Buffer for combination
+
+    if (pixels) {
+        // Only those specified pixels should be combined.
+
+        for (int p = 0; p < pixels->n; p++) {
+            int x = pixels->data[p].x; // Column of interest
+            int y = pixels->data[p].y; // Row of interest
+
+            if (!combinePixels(combine, questionablePixels ? *questionablePixels : NULL, x, y,
+                               images, errors, masks, maskVal, numIter, sigmaClip, buffer)) {
+                // Bad pixel --- no big deal
+                psErrorClear();
+            }
+        }
+    } else {
+        //
+        // We get here if there is a NULL list of pixels to combine.
+        // Therefore, we combine all pixels in all images.
+        //
+
+        //
+        // Loop over all pixels in all images, set the appropriate data, mask,
+        // error vectors, call psVectorStats(), and set the result in the
+        // combine image.
+        //
+        for (int y = 0; y < numRows; y++) {
+            for (int x = 0; x < numCols; x++) {
+                if (!combinePixels(combine, questionablePixels ? *questionablePixels : NULL, x, y,
+                                   images, errors, masks, maskVal, numIter, sigmaClip, buffer)) {
+                    // Bad pixel --- no big deal
+                    psErrorClear();
+                }
+            }
+        }
+    }
+
+    psFree(buffer);
+
+    psTrace("psModules.imcombine", 3, "Exiting pmCombineImages(%ld)\n", images->n);
+    return combine;
+}
+
+
+/******************************************************************************
+XXX: Directly from Paul Price
+ *****************************************************************************/
+static psF32 CalcGradient(
+    psImage *image,
+    psImage *imageMask,
+    psS32 x,
+    psS32 y
+)
+{
+    psTrace("psModules.imcombine", 4, "Calling CalcGradient(%d, %d)\n", x, y);
+    int num = 0;
+    psVector *pixels = psVectorAlloc(8, PS_TYPE_F32); // Array of pixels
+    psVector *mask = psVectorAlloc(8, PS_TYPE_U8); // Corresponding mask
+
+    // Get limits
+    int xMin = PS_MAX(x - 1, 0);
+    int xMax = PS_MIN(x + 1, image->numCols - 1);
+    int yMin = PS_MAX(y - 1, 0);
+    int yMax = PS_MIN(y + 1, image->numRows - 1);
+    if (imageMask != NULL) {
+        for (int j = yMin; j <= yMax; j++) {
+            for (int i = xMin; i <= xMax; i++) {
+                if ((i != x) && (j != y) && (0 == imageMask->data.U8[j][i])) {
+                    pixels->data.F32[num] = image->data.F32[j][i];
+                    mask->data.U8[num] = 0;
+                    num++;
+                } else {
+                    mask->data.U8[num] = 1;
+                }
+            }
+        }
+    } else {
+        //
+        // This code is simply the previous loop without the imageMask.
+        // XXX: Consider restructuring this.
+        //
+        for (int j = yMin; j <= yMax; j++) {
+            for (int i = xMin; i <= xMax; i++) {
+                if ((i != x) && (j != y)) {
+                    pixels->data.F32[num] = image->data.F32[j][i];
+                    mask->data.U8[num] = 0;
+                    num++;
+                } else {
+                    mask->data.U8[num] = 1;
+                }
+            }
+        }
+    }
+
+    pixels->n = num;
+    mask->n = num;
+
+    // Get the median
+    psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEDIAN);
+    psVectorStats(stats, pixels, NULL, mask, 1);
+    float median = stats->sampleMedian;
+    psFree(stats);
+    psFree(pixels);
+    psFree(mask);
+
+    psTrace("psModules.imcombine", 4, "Exiting CalcGradient(%d, %d)\n", x, y);
+    return(median / image->data.F32[y][x]);
+}
+
+/******************************************************************************
+DetermineRegion(image, myOutToIn): for a psImage and a psPlaneTransform to that
+image, this routine determines the size of the input image which maps to that
+image, and returns the result in a psRegion struct.
+
+XXX: Basically, this routine is only guaranteed to work if the transform is
+linear.
+
+XXX: Shouldn't this functionality be part of psImageTransform()?
+ *****************************************************************************/
+static psRegion DetermineRegion(psImage *image,
+                                psPlaneTransform *myOutToIn)
+{
+    psTrace("psModules.imcombine", 4, "Calling DetermineRegion()\n");
+    psRegion myRegion;
+    myRegion.x0 = PS_MAX_F32;
+    myRegion.x1 = PS_MIN_F32;
+    myRegion.y0 = PS_MAX_F32;
+    myRegion.y1 = PS_MIN_F32;
+    psPlane in;
+    psPlane out;
+
+    in.x = 0.0;
+    in.y = 0.0;
+
+    psPlaneTransformApply(&out, myOutToIn, &in);
+    if (out.x < myRegion.x0) {
+        myRegion.x0 = out.x;
+    }
+    if (out.x > myRegion.x1) {
+        myRegion.x1 = out.x;
+    }
+    if (out.y < myRegion.y0) {
+        myRegion.y0 = out.y;
+    }
+    if (out.y > myRegion.y1) {
+        myRegion.y1 = out.y;
+    }
+
+    in.x = (psF32) (image->numCols);
+    in.y = 0.0;
+    psPlaneTransformApply(&out, myOutToIn, &in);
+    if (out.x < myRegion.x0) {
+        myRegion.x0 = out.x;
+    }
+    if (out.x > myRegion.x1) {
+        myRegion.x1 = out.x;
+    }
+    if (out.y < myRegion.y0) {
+        myRegion.y0 = out.y;
+    }
+    if (out.y > myRegion.y1) {
+        myRegion.y1 = out.y;
+    }
+
+    in.x = (psF32) (image->numCols);
+    ;
+    in.y = 0.0;
+    psPlaneTransformApply(&out, myOutToIn, &in);
+    if (out.x < myRegion.x0) {
+        myRegion.x0 = out.x;
+    }
+    if (out.x > myRegion.x1) {
+        myRegion.x1 = out.x;
+    }
+    if (out.y < myRegion.y0) {
+        myRegion.y0 = out.y;
+    }
+    if (out.y > myRegion.y1) {
+        myRegion.y1 = out.y;
+    }
+
+    in.x = (psF32) (image->numCols);
+    in.y = (psF32) (image->numRows);
+    psPlaneTransformApply(&out, myOutToIn, &in);
+    if (out.x < myRegion.x0) {
+        myRegion.x0 = out.x;
+    }
+    if (out.x > myRegion.x1) {
+        myRegion.x1 = out.x;
+    }
+    if (out.y < myRegion.y0) {
+        myRegion.y0 = out.y;
+    }
+    if (out.y > myRegion.y1) {
+        myRegion.y1 = out.y;
+    }
+
+    psTrace("psModules.imcombine", 4, "Exiting DetermineRegion()\n");
+    return(myRegion);
+}
+
+/******************************************************************************
+XXX: Don't we have a psLib function for this?
+ *****************************************************************************/
+static psImage *ImageConvertF32(psImage *image)
+{
+    psTrace("psModules.imcombine", 4, "Calling ImageConvertF32()\n");
+    psImage *imgF32 = psImageAlloc(image->numCols, image->numRows, PS_TYPE_F32);
+
+    for (psS32 i = 0 ; i < image->numRows ; i++) {
+        for (psS32 j = 0 ; j < image->numCols ; j++) {
+            imgF32->data.F32[i][j] = (psF32) image->data.U8[i][j];
+        }
+    }
+
+    psTrace("psModules.imcombine", 4, "Exiting ImageConvertF32()\n");
+    return(imgF32);
+}
+
+
+//
+// The following macros define how big the initial pixel list will be, and
+// how much it should be incremented when realloc'ed.
+//
+#define PS_REJECT_PIXEL_INITIAL_PIXEL_LIST_LENGTH 100
+#define PS_REJECT_PIXEL_INITIAL_PIXEL_LIST_LENGTH_INC 100
+/******************************************************************************
+pmRejectPixels(images, errors, inToOut, outToIn, rejThreshold,
+gradLimit)
+
+XXX: Optimization: we don't need to transform the entire mask image.
+XXX: The inToOut and outToIn transforms are confusing.  Verify that what
+     I think they mean syncs with PWP.
+ *****************************************************************************/
+psArray *pmRejectPixels(
+    const psArray *images,              ///< Array of input images
+    const psArray *masks,               ///< Array of input image masks
+    const psArray *errors,              ///< The pixels which were rejected in the combination
+    const psArray *inToOut,             ///< Transformation from input to output system
+    const psArray *outToIn,             ///< Transformation from output to input system
+    psF32 rejThreshold,                 ///< Rejection threshold
+    psF32 gradLimit                     ///< Gradient limit
+)
+{
+    psTrace("psModules.imcombine", 3, "Calling pmRejectPixels()\n");
+    PS_ASSERT_PTR_NON_NULL(images, NULL);
+    for (psS32 im = 0 ; im < images->n ; im++) {
+        psImage *tmpImage = (psImage *) images->data[im];
+        PS_ASSERT_IMAGE_NON_NULL(tmpImage, NULL);
+        PS_ASSERT_IMAGE_NON_EMPTY(tmpImage, NULL);
+        PS_ASSERT_IMAGE_TYPE(tmpImage, PS_TYPE_F32, NULL);
+        if (masks != NULL) {
+            PS_ASSERT_INT_EQUAL(images->n, masks->n, NULL);
+            psImage *tmpMask = (psImage *) masks->data[im];
+            PS_ASSERT_IMAGE_NON_NULL(tmpMask, NULL);
+            PS_ASSERT_IMAGE_NON_EMPTY(tmpMask, NULL);
+            PS_ASSERT_IMAGE_TYPE(tmpMask, PS_TYPE_F32, NULL);
+            PS_ASSERT_IMAGES_SIZE_EQUAL(tmpImage, tmpMask, NULL);
+        }
+        PS_ASSERT_IMAGES_SIZE_EQUAL(((psImage *) images->data[0]), tmpImage, NULL);
+    }
+    PS_ASSERT_PTR_NON_NULL(errors, NULL);
+    PS_ASSERT_PTR_NON_NULL(inToOut, NULL);
+    PS_ASSERT_PTR_NON_NULL(outToIn, NULL);
+    // Ensure that the psArray parameters have an element for each image.
+    psS32 numImages = images->n;
+    PS_ASSERT_INT_EQUAL(numImages, errors->n, NULL);
+    PS_ASSERT_INT_EQUAL(numImages, inToOut->n, NULL);
+    PS_ASSERT_INT_EQUAL(numImages, outToIn->n, NULL);
+
+    //
+    // Create the psArray of psPixelLists, one for each image, for rejected pixels.
+    //
+    psArray *rejects = psArrayAlloc(numImages);
+    for (psS32 im = 0 ; im < numImages ; im++) {
+        rejects->data[im] = (psPtr *) psPixelsAlloc(PS_REJECT_PIXEL_INITIAL_PIXEL_LIST_LENGTH);
+        ((psPixels *)(rejects->data[im]))->n = ((psPixels *)(rejects->data[im]))->nalloc;
+        psPixels *pixels = (psPixels *) rejects->data[im];
+        pixels->n = 0;
+    }
+    //
+    // rPtr is used to maintain a count of the questionable pixels for each image.
+    //
+    psVector *rPtr = psVectorAlloc(numImages, PS_TYPE_S32);
+    psVectorInit(rPtr, 0);
+
+    psS32 numCols = ((psImage *) images->data[0])->numCols;
+    psS32 numRows = ((psImage *) images->data[0])->numRows;
+    psRegion myRegion = psRegionSet(0, numCols-1, 0, numRows-1);
+    psU32 maskVal = 1;  // XXX: Is this appropriate?
+
+    psPlane *inCoords = psAlloc(sizeof(psPlane));
+    psPlane *outCoords = psAlloc(sizeof(psPlane));
+
+    for (psS32 im = 0 ; im < numImages ; im++) {
+        //
+        // Extract data from psArrays.
+        //
+        psPixels *pixelList = (psPixels *) errors->data[im];
+
+        psImage *currImage = (psImage *) images->data[im];
+        myRegion.x0 = 0;
+        myRegion.x1 = currImage->numCols;
+        myRegion.y0 = 0;
+        myRegion.y1 = currImage->numRows;
+        psPlaneTransform *myInToOut = (psPlaneTransform *) inToOut->data[im];
+        psPlaneTransform *myOutToIn = (psPlaneTransform *) outToIn->data[im];
+
+        //
+        // Create a psU8 mask image from the list of cosmic pixels.
+        //
+        psImage *maskImage = NULL;
+        maskImage = psPixelsToMask(maskImage, pixelList, myRegion, maskVal);
+        psImage *maskImageF32 = ImageConvertF32(maskImage);
+
+        //
+        // Transform that mask image into detector coordinate space
+        //
+        psRegion myRegionXForm = DetermineRegion(maskImageF32, myOutToIn);
+        psImage *transformedImage = psImageTransform(NULL, NULL, maskImageF32, NULL,
+                                    0, myOutToIn, myRegionXForm, NULL,
+                                    PS_INTERPOLATE_BILINEAR, 0);
+
+        //
+        // Loop over all cosmic pixels.  Transform their coords to detector space.
+        // If the value of the transformed mask is larger than rejThreshold, then
+        // this might be a cosmic ray pixel.  We then calculate the mean gradient
+        // in other images.
+        //
+
+        psImageInterpolateOptions *interp = psImageInterpolateOptionsAlloc(PS_INTERPOLATE_BILINEAR,
+                                                                           transformedImage, NULL, NULL,
+                                                                           0, 0.0, 0.0, 0, 0, 0.0);
+
+        for (psS32 p = 0 ; p < pixelList->n ; p++) {
+            inCoords->x = 0.5 + (psF32) (pixelList->data[p]).x;
+            inCoords->y = 0.5 + (psF32) (pixelList->data[p]).y;
+            psPlaneTransformApply(outCoords, myInToOut, inCoords);
+            double maskVal;
+            if (!psImageInterpolate(&maskVal, NULL, NULL, outCoords->x, outCoords->y, interp)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to interpolate image.");
+                psFree(interp);
+                psFree(maskImage);
+                psFree(maskImageF32);
+                psFree(transformedImage);
+                psFree(inCoords);
+                psFree(outCoords);
+                psFree(rejects);
+                return NULL;
+            }
+            if (maskVal > rejThreshold) {
+
+                // This is a possible cosmic array pixel.  We must calculate the gradient
+                // at this location in all input images.
+                psF32 meanGrads = 0.0;
+                psS32 numGrads = 0;
+                //
+                // Loop through all other images, calculate their mean gradient.
+                //
+                for (psS32 otherImg = 0 ; otherImg < numImages ; otherImg++) {
+                    if (im != otherImg) {
+                        // Map the outCoords to inCoords that for otherImg space.
+                        psImage *tmpMask = NULL;
+                        if (masks != NULL) {
+                            tmpMask = masks->data[otherImg];
+                        }
+                        psPlaneTransformApply(inCoords,
+                                              (psPlaneTransform * )outToIn->data[otherImg],
+                                              outCoords);
+                        psS32 xPix = (int)(inCoords->x + 0.5);
+                        psS32 yPix = (int)(inCoords->y + 0.5);
+                        if ((xPix >= 0) && (xPix <= ((psImage*)(images->data[otherImg]))->numCols - 1) &&
+                                (yPix >= 0) && (yPix <= ((psImage*)(images->data[otherImg]))->numRows - 1)) {
+                            meanGrads += CalcGradient(images->data[otherImg], tmpMask, xPix, yPix);
+                            numGrads++;
+                        }
+                    }
+                }
+                if (numGrads > 0) {
+                    meanGrads /= (psF32) numGrads;
+                } else {
+                    // XXX: my idea.  Verify with PWP:
+                    meanGrads = 1.0 + gradLimit;
+                }
+
+                // XXX: The SDRS and the prototype code differ significantly here:
+                // if (CalcGradient(inputs->data[i], pixelList->data.x, pixelList->data.y) < (gradLimit * meanGrads)) {
+                if (meanGrads < gradLimit) {
+                    //
+                    // Add this to the list of questionable pixels.  We must ensure that the
+                    // pixelList is large enough; if not, we realloc()
+                    //
+                    psS32 ptr = rPtr->data.S32[im];
+                    psPixels *pixelListPtr = (psPixels *) rejects->data[im];
+                    if (ptr >= pixelListPtr->nalloc) {
+                        rejects->data[im] = (psPtr *) psPixelsRealloc(((psPixels *) rejects->data[im]),
+                                            ((((psPixels *) rejects->data[im])->nalloc) + PS_REJECT_PIXEL_INITIAL_PIXEL_LIST_LENGTH_INC));
+                        // XXX: Can the realloc() fail?  Must we check for NULL?
+                    }
+
+                    ((psPixels *) rejects->data[im])->data[ptr].x = (pixelList->data[p]).x;
+                    ((psPixels *) rejects->data[im])->data[ptr].y = (pixelList->data[p]).y;
+                    (rPtr->data.S32[im])++;
+                    // XXX: this pixel ->n increment is wierd
+                    (((psPixels *) rejects->data[im])->n)++;
+                }
+            }
+        }
+
+        psFree(interp);
+        psFree(maskImage);
+        psFree(maskImageF32);
+        psFree(transformedImage);
+    }
+
+    psFree(inCoords);
+    psFree(outCoords);
+    psTrace("psModules.imcombine", 3, "Exiting pmRejectPixels()\n");
+    return(rejects);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmImageCombine.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmImageCombine.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmImageCombine.h	(revision 22157)
@@ -0,0 +1,44 @@
+/* @file  pmImageCombine.h
+ *
+ * This file will perform image combination of several images of the
+ * same field, produce a list of questionable pixels, then tag some
+ * of those pixels as cosmic rays.
+ *
+ * @author Paul Price, IfA (original prototype)
+ * @author GLG, MHPCC
+ *
+ * @version $Revision: 1.6 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-03-30 21:12:56 $
+ * Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+#ifndef PM_IMAGE_COMBINE_H
+#define PM_IMAGE_COMBINE_H
+
+/// @addtogroup imcombine Image Combinations
+/// @{
+
+psImage *pmCombineImages(
+    psImage *combine,                   ///< Combined image (output)
+    psArray **questionablePixels,       ///< Array of rejection masks
+    const psArray *images,              ///< Array of input images
+    const psArray *errors,              ///< Array of input error images
+    const psArray *masks,               ///< Array of input masks
+    psU32 maskVal,                      ///< Mask value
+    const psPixels *pixels,             ///< Pixels to combine
+    psS32 numIter,                      ///< Number of rejection iterations
+    psF32 sigmaClip                     ///< Number of standard deviations at which to reject
+);
+
+psArray *pmRejectPixels(
+    const psArray *images,              ///< Array of input images
+    const psArray *masks,               ///< Array of input image masks
+    const psArray *errors,              ///< The pixels which were rejected in the combination
+    const psArray *inToOut,             ///< Transformation from input to output system
+    const psArray *outToIn,             ///< Transformation from output to input system
+    psF32 rejThreshold,                 ///< Rejection threshold
+    psF32 gradLimit                     ///< Gradient limit
+);
+
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmPSFEnvelope.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmPSFEnvelope.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmPSFEnvelope.c	(revision 22157)
@@ -0,0 +1,313 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmReadoutFake.h"
+
+#include "pmMoments.h"
+#include "pmResiduals.h"
+#include "pmGrowthCurve.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmModelClass.h"
+#include "pmModelUtils.h"
+#include "pmPeaks.h"
+#include "pmSource.h"
+#include "pmSourceUtils.h"
+#include "pmSourceFitModel.h"
+#include "pmPSFtry.h"
+
+
+#include "pmPSFEnvelope.h"
+
+
+
+//#define TESTING                         // Enable test output
+#define PEAK_FLUX 1.0e4                 // Peak flux for each source
+#define SKY_VALUE 0.0e0                 // Sky value for fake image
+#define WEIGHT_VAL 10.0                 // Weighting for image
+#define WEIGHT_FACTOR 10.0              // Factor to multiply image by to get weighting
+#define PSF_STATS PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV // Statistics options for measuring PSF
+
+
+
+// XXX To do:
+//
+// * PSF variation when only a portion of the image is present (e.g., the edge of an FPA overlapping a
+// skycell) may mean a disastrously weird PSF in the missing regions.  To counter this, get a region of
+// validity for each PSF (perhaps from an associated mask, or have the user work it out), and taper the PSF
+// when outside this region (perhaps multiply the peak flux by a Gaussian whose arguments are the distance
+// from the valid region, and a width that the user supplies).
+
+
+// We deliberately do not include the calculation of and storing of residuals (data - model) for the PSF
+// model, because (1) there is no code in psModules to do this, and we're not going to implement it here; and
+// (2) this is intended to generate "nice" or "ideal" PSFs to feed into pmSubtraction (PSF matching code), so
+// any residuals will hopefully be dealt with by that.
+
+
+pmPSF *pmPSFEnvelope(int numCols, int numRows, // Size of original image
+                     const psArray *inputs, // Input PSF models
+                     int instances, // Number of instances per dimension
+                     int radius,        // Radius of each PSF
+                     const char *modelName,// Name of PSF model to use
+                     int xOrder, int yOrder // Order for PSF variation fit
+                     )
+{
+    PS_ASSERT_INT_POSITIVE(numCols, NULL);
+    PS_ASSERT_INT_POSITIVE(numRows, NULL);
+    PS_ASSERT_ARRAY_NON_NULL(inputs, NULL);
+    PS_ASSERT_INT_POSITIVE(instances, NULL);
+    PS_ASSERT_INT_POSITIVE(radius, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(modelName, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(xOrder, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(yOrder, NULL);
+
+    float xOrigSpacing = (float)(numCols - 2 * radius) / (float)(instances - 1); // Spacing between instances
+    float yOrigSpacing = (float)(numRows - 2 * radius) / (float)(instances - 1); // Spacing between instances
+    int fakeSpacing = 2 * radius + 1;   // Spacing between instances (x and y) in the fake image
+    int fakeSize = instances * fakeSpacing; // Size of fake image
+
+    // Generate list of fake sources (instances of the PSF)
+    int numFakes = PS_SQR(instances);   // Number of fake sources
+    psArray *fakes = psArrayAlloc(numFakes); // Fake sources
+    psVector *xOffset = psVectorAlloc(numFakes, PS_TYPE_S32); // X offset from fake position to image
+    psVector *yOffset = psVectorAlloc(numFakes, PS_TYPE_S32); // Y offset from fake position to image
+    for (int j = 0, index = 0; j < instances; j++) {
+        float yOrig = j * yOrigSpacing + radius; // Source position in original image
+        float yFake = j * fakeSpacing + radius; // Position in fake image
+        int dy = yFake - yOrig;         // Difference between fake and original position
+
+        for (int i = 0; i < instances; i++, index++) {
+            float xOrig = i * xOrigSpacing + radius; // Source position in original image
+            float xFake = i * fakeSpacing + radius; // Position in fake image
+            int dx = xFake - xOrig;     // Difference between fake and original position
+
+            pmSource *fake = pmSourceAlloc(); // Fake source
+            fake->peak = pmPeakAlloc(xFake - dx, yFake - dy, PEAK_FLUX, PM_PEAK_LONE);
+            fake->type = PM_SOURCE_TYPE_STAR;
+            fake->psfMag = -2.5 * log10(PEAK_FLUX);
+
+            psTrace("psModules.imcombine", 5, "Source %d: %.2f,%.2f\n",
+                    index, xOrig, yOrig);
+
+            fakes->data[index] = fake;
+            xOffset->data.S32[index] = dx;
+            yOffset->data.S32[index] = dy;
+        }
+    }
+
+    // Generate fake images with each PSF, and take the envelope
+    psImage *envelope = psImageAlloc(fakeSize, fakeSize, PS_TYPE_F32); // Image with envelope of PSFs
+    psImageInit(envelope, SKY_VALUE);
+    pmReadout *fakeRO = pmReadoutAlloc(NULL); // Fake readout
+    for (int i = 0; i < inputs->n; i++) {
+        pmPSF *psf = inputs->data[i];   // PSF of interest
+        pmResiduals *resid = psf->residuals;// PSF residuals
+        psf->residuals = NULL;
+        if (!pmReadoutFakeFromSources(fakeRO, fakeSize, fakeSize, fakes, xOffset, yOffset, psf,
+                                      NAN, radius, true)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to generate fake readout.");
+            psFree(envelope);
+            psFree(yOffset);
+            psFree(xOffset);
+            psFree(fakes);
+            psf->residuals = resid;
+            return NULL;
+        }
+        psf->residuals = resid;
+
+        // Need to renormalise sources so they all have the same peak.  You would think they do have the same
+        // peak already, but it seems that the residual map messes things up by adding extra flux
+        for (int j = 0; j < numFakes; j++) {
+            pmSource *source = fakes->data[j]; // Fake source
+            float x = source->peak->xf + xOffset->data.S32[j]; // x coordinate of source
+            float y = source->peak->yf + yOffset->data.S32[j]; // y coordinate of source
+
+            double flux = fakeRO->image->data.F32[(int)y][(int)x];
+            float norm = PEAK_FLUX / flux; // Normalisation for source
+            psRegion region = psRegionSet(x - radius, x + radius, y - radius, y + radius); // PSF region
+            psImage *subImage = psImageSubset(fakeRO->image, region); // Subimage of fake PSF
+            psImage *subEnv = psImageSubset(envelope, region); // Subimage of envelope
+            psBinaryOp(subImage, subImage, "*", psScalarAlloc(norm, PS_TYPE_F32));
+            psBinaryOp(subEnv, subEnv, "MAX", subImage);
+            psFree(subImage);
+            psFree(subEnv);
+        }
+
+#ifdef TESTING
+        {
+            // Write out the PSF field
+            psString name = NULL;
+            psStringAppend(&name, "psf_field_%03d.fits", i);
+            psFits *fits = psFitsOpen(name, "w");
+            psFitsWriteImage(fits, NULL, fakeRO->image, 0, NULL);
+            psFitsClose(fits);
+            psFree(name);
+        }
+#endif
+
+    }
+    psFree(fakeRO);
+
+#ifdef TESTING
+    {
+        // Write out the envelope
+        psFits *fits = psFitsOpen("psf_field_envelope.fits", "w");
+        psFitsWriteImage(fits, NULL, envelope, 0, NULL);
+        psFitsClose(fits);
+    }
+#endif
+
+    // Put the fake sources onto a full-size image
+    pmReadout *readout = pmReadoutAlloc(NULL); // Readout to contain envelope pixels
+    readout->image = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    psImageInit(readout->image, 0.0);
+    for (int i = 0; i < numFakes; i++) {
+        pmSource *source = fakes->data[i]; // Fake source
+        // Position of source on fake image
+        int xFake = source->peak->x + xOffset->data.S32[i];
+        int yFake = source->peak->y + yOffset->data.S32[i];
+        psRegion region = psRegionSet(xFake - radius, xFake + radius,
+                                      yFake - radius, yFake + radius); // PSF region
+        psImage *subImage = psImageSubset(envelope, region); // Subimage of fake PSF
+
+        // Position of source on "real" image
+        int x0 = source->peak->x - radius;
+        int y0 = source->peak->y - radius;
+
+        if (!psImageOverlaySection(readout->image, subImage, x0, y0, "=")) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to overlay PSF");
+            psFree(subImage);
+            psFree(readout);
+            psFree(xOffset);
+            psFree(yOffset);
+            psFree(fakes);
+            return NULL;
+        }
+        psFree(subImage);
+    }
+    psFree(xOffset);
+    psFree(yOffset);
+    psFree(envelope);
+
+    // XXX Setting the weight seems to be an art
+    // Can't set it too high so that pixels are rejected as insignificant
+    // Can't set it too low so that it's hard to get to the minimum
+    // Have also tried:
+    // *** readout->weight = (psImage*)psBinaryOp(NULL, readout->image, "*", readout->image);
+    // *** readout->weight = (psImage*)psBinaryOp(NULL, readout->image, "*", psScalarAlloc(WEIGHT_FACTOR, PS_TYPE_F32));
+    readout->weight = (psImage*)psBinaryOp(NULL, readout->image, "+", psScalarAlloc(PS_SQR(WEIGHT_VAL), PS_TYPE_F32));
+    readout->mask = psImageAlloc(numCols, numRows, PS_TYPE_MASK);
+    psImageInit(readout->mask, 0);
+
+#ifdef TESTING
+    {
+        // Write out the envelope
+        psFits *fits = psFitsOpen("psf_field_full.fits", "w");
+        psFitsWriteImage(fits, NULL, readout->image, 0, NULL);
+        psFitsClose(fits);
+    }
+#endif
+
+    // Reset the sources to point to the new pixels, and measure the moments in preparation for PSF fitting
+    for (int i = 0; i < numFakes; i++) {
+        pmSource *source = fakes->data[i]; // Fake source
+        float x = source->peak->xf;     // x coordinates of source
+        float y = source->peak->yf;     // y coordinates of source
+
+        psFree(source->pixels);
+        psFree(source->weight);
+        psFree(source->maskView);
+        psFree(source->maskObj);
+        source->pixels = NULL;
+        source->weight = NULL;
+        source->maskView = NULL;
+        source->maskObj = NULL;
+
+        if (!pmSourceDefinePixels(source, readout, x, y, (float)radius)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to define pixels for source.");
+            psFree(readout);
+            psFree(fakes);
+            return NULL;
+        }
+
+        if (!pmSourceMoments(source, radius)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to measure moments for source.");
+            psFree(readout);
+            psFree(fakes);
+            return NULL;
+        }
+    }
+
+    // Don't assume Poisson errors
+    pmPSFOptions *options = pmPSFOptionsAlloc(); // Options for fitting a PSF
+    options->poissonErrorsPhotLMM = true;
+    options->poissonErrorsPhotLin = false;
+    options->poissonErrorsParams = false;
+    options->stats = psStatsAlloc(PSF_STATS);
+    options->radius = radius;
+    options->psfTrendMode = PM_TREND_MAP;
+    options->psfTrendNx = xOrder;
+    options->psfTrendNy = yOrder;
+    options->psfFieldNx = numCols;
+    options->psfFieldNy = numRows;
+    options->psfFieldXo = 0;
+    options->psfFieldYo = 0;
+
+    pmSourceFitModelInit(50, 0.01, WEIGHT_VAL, false);
+
+    pmPSFtry *try = pmPSFtryModel(fakes, modelName, options, 0, 0xff);
+    psFree(options);
+    if (!try) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to fit PSF model to PSF envelope.");
+        psFree(readout);
+        psFree(fakes);
+        return NULL;
+    }
+
+    pmPSF *psf = psMemIncrRefCounter(try->psf); // Output PSF
+    psFree(try);
+
+#ifdef TESTING
+    {
+        // Need to translate peak flux --> integrated flux
+        pmModel *fakeModel = pmModelFromPSFforXY(psf, (float)numCols / 2.0, (float)numRows / 2.0,
+                                                 1.0); // Fake model, with central intensity of 1.0
+        float flux0 = fakeModel->modelFlux(fakeModel->params); // Flux for central intensity of 1.0
+        for (int i = 0; i < numFakes; i++) {
+            pmSource *source = fakes->data[i]; // Fake source
+            source->psfMag -= 2.5 * log10(flux0);
+        }
+
+        pmReadout *generated = pmReadoutAlloc(NULL); // Generated image
+        pmReadoutFakeFromSources(generated, numCols, numRows, fakes, NULL, NULL, psf, NAN, radius,
+                                 false);
+        {
+            psFits *fits = psFitsOpen("psf_field_model.fits", "w");
+            psFitsWriteImage(fits, NULL, generated->image, 0, NULL);
+            psFitsClose(fits);
+        }
+        psBinaryOp(generated->image, generated->image, "-", readout->image);
+        {
+            psFits *fits = psFitsOpen("psf_field_resid.fits", "w");
+            psFitsWriteImage(fits, NULL, generated->image, 0, NULL);
+            psFitsClose(fits);
+        }
+        psFree(generated);
+    }
+#endif
+
+    psFree(fakes);
+    psFree(readout);
+
+    return psf;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmPSFEnvelope.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmPSFEnvelope.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmPSFEnvelope.h	(revision 22157)
@@ -0,0 +1,22 @@
+#ifndef PM_PSF_ENVELOPE_H
+#define PM_PSF_ENVELOPE_H
+
+#include <pslib.h>
+#include <pmMoments.h>
+#include <pmResiduals.h>
+#include <pmGrowthCurve.h>
+#include <pmTrend2D.h>
+#include <pmPSF.h>
+
+/// Generate a PSF which is the envelope of an array of PSFs
+///
+/// Generates multiple instances of the PSFs (distributed over an image)
+pmPSF *pmPSFEnvelope(int numCols, int numRows, // Size of original image
+                     const psArray *inputs, // Input PSF models
+                     int instances,     // Number of instances per dimension
+                     int radius,        // Radius of each PSF
+                     const char *modelName, // Name of PSF model to use
+                     int xOrder, int yOrder // Order for PSF variation
+    );
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmReadoutCombine.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmReadoutCombine.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmReadoutCombine.c	(revision 22157)
@@ -0,0 +1,375 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmHDUUtils.h"
+#include "pmFPAMaskWeight.h"
+#include "pmConceptsAverage.h"
+#include "pmReadoutStack.h"
+
+#include "pmReadoutCombine.h"
+
+//#define SHOW_BUSY 1                   // Show that the function is busy
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Allocator for pmCombineParams
+pmCombineParams *pmCombineParamsAlloc(psStatsOptions combine)
+{
+    pmCombineParams *params = psAlloc(sizeof(pmCombineParams));
+
+    params->combine = combine;
+    params->maskVal = 0;
+    params->blank = 0;
+    params->nKeep = 0;
+    params->fracHigh = 0.0;
+    params->fracHigh = 0.0;
+    params->iter = 1;
+    params->rej = INFINITY;
+    params->weights = false;
+
+    return params;
+}
+
+
+// XXX: Maybe add support for S16 and S32 types.  Currently, only F32 supported.
+bool pmReadoutCombine(pmReadout *output, const psArray *inputs, const psVector *zero, const psVector *scale,
+                      const pmCombineParams *params)
+{
+    // Check inputs
+    PS_ASSERT_PTR_NON_NULL(output, false);
+    PS_ASSERT_ARRAY_NON_NULL(inputs, false);
+    PS_ASSERT_PTR_NON_NULL(params, false);
+    if (zero) {
+        PS_ASSERT_VECTOR_TYPE(zero, PS_TYPE_F32, false);
+        PS_ASSERT_VECTOR_SIZE(zero, inputs->n, false);
+    }
+    if (scale) {
+        PS_ASSERT_VECTOR_TYPE(scale, PS_TYPE_F32, false);
+        PS_ASSERT_VECTOR_SIZE(scale, inputs->n, false);
+    }
+    PS_ASSERT_FLOAT_WITHIN_RANGE(params->fracLow, 0.0, 1.0, false);
+    PS_ASSERT_FLOAT_WITHIN_RANGE(params->fracHigh, 0.0, 1.0, false);
+    if (params->combine != PS_STAT_SAMPLE_MEAN && params->combine != PS_STAT_SAMPLE_MEDIAN &&
+            params->combine != PS_STAT_ROBUST_MEDIAN && params->combine != PS_STAT_FITTED_MEAN &&
+            params->combine != PS_STAT_CLIPPED_MEAN) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Combination method is not SAMPLE_MEAN, SAMPLE_MEDIAN, "
+                "ROBUST_MEDIAN, FITTED_MEAN or CLIPPED_MEAN.\n");
+        return false;
+    }
+    for (int i = 0; i < inputs->n; i++) {
+        pmReadout *readout = inputs->data[i]; // Readout of interest
+        if (params->weights && !readout->weight) {
+            psError(PS_ERR_UNEXPECTED_NULL, true,
+                    "Rejection based on weights requested, but no weights supplied for image %d.\n", i);
+            return false;
+        }
+    }
+
+    bool first = !output->image;        // First pass through?
+
+    pmHDU *hdu = pmHDUFromReadout(output); // Output HDU
+    if (!hdu) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find HDU for readout.\n");
+        return false;
+    }
+
+    if (first) {
+        psString comment = NULL;        // Comment to add to header
+        psStringAppend(&comment, "Combining using statistic: %x", params->combine);
+        if (!hdu->header) {
+            hdu->header = psMetadataAlloc();
+        }
+        psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, comment, "");
+        psFree(comment);
+    }
+
+    psStatsOptions combineStdev = 0; // Statistics option for weights
+    switch (params->combine) {
+      case PS_STAT_SAMPLE_MEAN:
+      case PS_STAT_SAMPLE_MEDIAN:
+        combineStdev = PS_STAT_SAMPLE_STDEV;
+        break;
+      case PS_STAT_ROBUST_MEDIAN:
+        combineStdev = PS_STAT_ROBUST_STDEV;
+        break;
+      case PS_STAT_FITTED_MEAN:
+        combineStdev = PS_STAT_FITTED_STDEV;
+        break;
+      case PS_STAT_CLIPPED_MEAN:
+        combineStdev = PS_STAT_CLIPPED_STDEV;
+        break;
+      default:
+        psAbort("Should never get here --- checked params->combine before.\n");
+    }
+
+    psStats *stats = psStatsAlloc(params->combine | combineStdev); // The statistics to use in the combination
+    if (params->combine == PS_STAT_CLIPPED_MEAN) {
+        stats->clipSigma = params->rej;
+        stats->clipIter = params->iter;
+
+        if (first) {
+            psString comment = NULL;    // Comment to add to header
+            psStringAppend(&comment, "Combination clipping: %d iterations, rejection at %f sigma",
+                           params->iter, params->rej);
+            psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, comment, "");
+            psFree(comment);
+        }
+    }
+    if (params->weights && first) {
+        psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK,
+                         "Using input weights to combine images", "");
+    }
+
+    int minInputCols, maxInputCols, minInputRows, maxInputRows; // Smallest and largest values to combine
+    int xSize, ySize;                   // Size of the output image
+    if (!pmReadoutStackValidate(&minInputCols, &maxInputCols, &minInputRows, &maxInputRows, &xSize, &ySize,
+                                inputs)) {
+        psError(PS_ERR_UNKNOWN, false, "No valid input readouts.");
+        return false;
+    }
+
+    pmReadoutUpdateSize(output, minInputCols, minInputRows, xSize, ySize, true, params->weights,
+                        params->blank);
+    psTrace("psModules.imcombine", 7, "Output minimum: %d,%d\n", output->col0, output->row0);
+
+    psImage *counts = pmReadoutAnalysisImage(output, PM_READOUT_STACK_ANALYSIS_COUNT, xSize, ySize,
+                                             PS_TYPE_U16, 0);
+    if (!counts) {
+        return false;
+    }
+    psImage *sigma = pmReadoutAnalysisImage(output, PM_READOUT_STACK_ANALYSIS_SIGMA, xSize, ySize,
+                                            PS_TYPE_F32, NAN);
+    if (!sigma) {
+        psFree(counts);
+        return false;
+    }
+
+    stats->options |= combineStdev;
+
+    // We loop through each pixel in the output image.  We loop through each input readout.  We determine if
+    // that output pixel is contained in the image from that readout.  If so, we save it in psVector pixels.
+    // If not, we set a mask for that element in pixels.  Then, we mask off pixels not between fracLow and
+    // fracHigh.  Then we call the vector stats routine on those pixels/mask.  Then we set the output pixel
+    // value to the result of the stats call.
+
+    psVector *pixels = psVectorAlloc(inputs->n, PS_TYPE_F32); // Stack of pixels
+    psF32 *pixelsData = pixels->data.F32; // Dereference pixels
+
+    psVector *mask   = psVectorAlloc(inputs->n, PS_TYPE_U8); // Mask for stack
+    psU8 *maskData = mask->data.U8;     // Dereference mask
+
+    psVector *weights = NULL;           // Stack of weights
+    psVector *errors = NULL;            // Stack of errors (sqrt of variance/weights), for psVectorStats
+    psF32 *weightsData = NULL;          // Dereference weights
+    if (params->weights) {
+        weights = psVectorAlloc(inputs->n, PS_TYPE_F32); // Stack of weights
+        weightsData = weights->data.F32;
+    }
+    psVector *index = NULL;             // The indices to sort the pixels
+
+    float keepFrac = 1.0 - params->fracLow - params->fracHigh; // Fraction of pixels to keep
+    if (keepFrac != 1.0 && first) {
+        psString comment = NULL;        // Comment to add to header
+        psStringAppend(&comment, "Min/max rejection: %f high, %f low, keep %d",
+                       params->fracHigh, params->fracLow, params->nKeep);
+        psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, comment, "");
+        psFree(comment);
+    }
+
+    psMaskType maskVal = params->maskVal; // The mask value
+    if (maskVal && first) {
+        psString comment = NULL;        // Comment to add to header
+        psStringAppend(&comment, "Mask for combination: %x", maskVal);
+        psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, comment, "");
+        psFree(comment);
+    }
+
+    #ifndef PS_NO_TRACE
+    psTrace("psModules.imcombine", 3, "Iterating output: %d --> %d, %d --> %d\n",
+            minInputCols - output->col0, maxInputCols - output->col0,
+            minInputRows - output->row0, maxInputRows - output->row0);
+    if (psTraceGetLevel("psModules.imcombine") >= 3) {
+        for (int r = 0; r < inputs->n; r++) {
+            pmReadout *readout = inputs->data[r]; // Input readout
+            psTrace("psModules.imcombine", 3, "Iterating input %d: %d --> %d, %d --> %d\n", r,
+                    minInputCols - readout->col0, maxInputCols - readout->col0,
+                    minInputRows - readout->row0, maxInputRows - readout->row0);
+        }
+    }
+    #endif
+
+    // Dereference output products
+    psF32 **outputImage  = output->image->data.F32; // Output image
+    psU8  **outputMask   = output->mask->data.U8; // Output mask
+    psF32 **outputWeight = NULL; // Output weight map
+    if (output->weight) {
+        outputWeight = output->weight->data.F32;
+    }
+
+    psVector *invScale = NULL;          // Inverse scale; pre-calculated for efficiency
+    if (scale) {
+        invScale = (psVector*)psBinaryOp(NULL, psScalarAlloc(1.0, PS_TYPE_F32), "/", (const psPtr)scale);
+    }
+
+    for (int i = minInputRows; i < maxInputRows; i++) {
+        int yOut = i - output->row0; // y position on output readout
+        #ifdef SHOW_BUSY
+
+        if (psTraceGetLevel("psModules.imcombine") > 9) {
+            printf("Processing row %d\r", i);
+            fflush(stdout);
+        }
+        #endif
+        for (int j = minInputCols; j < maxInputCols; j++) {
+            int xOut = j - output->col0; // x position on output readout
+
+            int numValid = 0;           // Number of valid pixels in the stack
+            memset(maskData, 0, mask->n * sizeof(psU8)); // Reset the mask
+            for (int r = 0; r < inputs->n; r++) {
+                pmReadout *readout = inputs->data[r]; // Input readout
+                int yIn = i - readout->row0; // y position on input readout
+                int xIn = j - readout->col0; // x position on input readout
+                psImage *image = readout->image; // The readout image
+
+                #if 0 // This should have been taken care of already:
+                // Check bounds
+                if (xIn < 0 || xIn >= image->numCols || yIn < 0 || yIn >= image->numRows) {
+                    continue;
+                }
+                #endif
+
+                pixelsData[r] = image->data.F32[yIn][xIn];
+                if (!isfinite(pixelsData[r])) {
+                    maskData[r] = 1;
+                    continue;
+                }
+
+                // Check mask
+                psImage *roMask = readout->mask; // The mask image
+                if (roMask && roMask->data.U8[yIn][xIn] & maskVal) {
+                    maskData[r] = 1;
+                    continue;
+                }
+
+                if (params->weights) {
+                    weightsData[r] = readout->weight->data.F32[yIn][xIn];
+                }
+
+                if (zero) {
+                    pixelsData[r] -= zero->data.F32[r];
+                }
+                if (scale) {
+                    pixelsData[r] *= invScale->data.F32[r];
+                    if (params->weights) {
+                        weightsData[r] *= invScale->data.F32[r] * invScale->data.F32[r];
+                    }
+                }
+
+                numValid++;
+            }
+
+            if (numValid == 0) {
+                outputMask[yOut][xOut] = params->blank;
+                outputImage[yOut][xOut] = NAN;
+                counts->data.U16[yOut][xOut] = 0;
+                sigma->data.F32[yOut][xOut] = NAN;
+                continue;
+            }
+
+            // Apply fracLow,fracHigh if there are enough pixels
+            if (numValid * keepFrac >= params->nKeep && keepFrac != 1.0) {
+                index = psVectorSortIndex(index, pixels);
+                int numLow = numValid * params->fracLow; // Number of low pixels to clip
+                int numHigh = numValid * params->fracHigh; // Number of high pixels to clip
+                // Low pixels
+                psS32 *indexData = index->data.S32; // Dereference index
+                for (int k = 0, numMasked = 0; numMasked < numLow && k < index->n; k++) {
+                    // Don't count the ones that are already masked
+                    if (!maskData[indexData[k]]) {
+                        maskData[indexData[k]] = 1;
+                        numMasked++;
+                        numValid--;
+                    }
+                }
+                // High pixels
+                for (int k = pixels->n - 1, numMasked = 0; numMasked < numHigh && k >= 0; k--) {
+                    // Don't count the ones that are already masked
+                    if (!maskData[indexData[k]]) {
+                        maskData[indexData[k]] = 1;
+                        numMasked++;
+                        numValid--;
+                    }
+                }
+            }
+            counts->data.U16[yOut][xOut] = numValid;
+
+            // XXXXX this step probably is very expensive : convert errors to variance everywhere?
+            if (params->weights) {
+                errors = (psVector*)psUnaryOp(errors, weights, "sqrt");
+            }
+
+            // Combination
+            if (!psVectorStats(stats, pixels, errors, mask, 1)) {
+                // Can't do much about it, but it's not worth worrying about
+                psErrorClear();
+                outputImage[yOut][xOut] = NAN;
+                outputMask[yOut][xOut] = params->blank;
+                if (params->weights) {
+                    outputWeight[yOut][xOut] = NAN;
+                }
+                sigma->data.F32[yOut][xOut] = NAN;
+            } else {
+                outputImage[yOut][xOut] = psStatsGetValue(stats, params->combine);
+                outputMask[yOut][xOut] = isfinite(outputImage[yOut][xOut]) ? 0 : params->blank;
+                if (params->weights) {
+                    float stdev = psStatsGetValue(stats, combineStdev);
+                    outputWeight[yOut][xOut] = PS_SQR(stdev); // Variance
+                    // XXXX this is not the correct formal error.
+                    // also, the weighted mean is not obviously the correct thing here
+                }
+                sigma->data.F32[yOut][xOut] = psStatsGetValue(stats, combineStdev);
+            }
+        }
+    }
+    #ifdef SHOW_BUSY
+    if (psTraceGetLevel("psModules.imcombine") > 9) {
+        printf("\n");
+    }
+    #endif
+    psFree(index);
+    psFree(pixels);
+    psFree(mask);
+    psFree(weights);
+    psFree(errors);
+    psFree(stats);
+    psFree(invScale);
+    psFree(counts);
+    psFree(sigma);
+
+    // Update the "concepts"
+    psList *inputCells = psListAlloc(NULL); // List of cells
+    for (long i = 0; i < inputs->n; i++) {
+        pmReadout *readout = inputs->data[i]; // Readout of interest
+        psListAdd(inputCells, PS_LIST_TAIL, readout->parent);
+    }
+    bool success = pmConceptsAverageCells(output->parent, inputCells, NULL, NULL, true);
+    psFree(inputCells);
+
+    output->data_exists = true;
+    output->parent->data_exists = true;
+    output->parent->parent->data_exists = true;
+
+    return success;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmReadoutCombine.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmReadoutCombine.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmReadoutCombine.h	(revision 22157)
@@ -0,0 +1,47 @@
+/* @file  pmReadoutCombine.h
+ * @brief Combine multiple readouts
+ *
+ * @author George Gusciora, MHPCC
+ * @author Paul Price, IfA
+ *
+ * @version $Revision: 1.13 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-03-29 03:10:17 $
+ * Copyright 2004-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_READOUT_COMBINE_H
+#define PM_READOUT_COMBINE_H
+
+/// @addtogroup imcombine Image Combinations
+/// @{
+
+/// Combination parameters for pmReadoutCombine.
+///
+/// These values define how the combination is performed, and should not vary by detector, so that it can be
+/// re-used for multiple combinations.
+typedef struct {
+    psStatsOptions combine;             ///< Statistic to use when performing the combination
+    psMaskType maskVal;                 ///< Mask value
+    psMaskType blank;                   ///< Mask value to give blank (i.e., no data) pixels
+    int nKeep;                          ///< Mimimum number of pixels to keep
+    float fracHigh;                     ///< Fraction of high pixels to immediately throw
+    float fracLow;                      ///< Fraction of low pixels to immediately throw
+    int iter;                           ///< Number of iterations for clipping (for CLIPPED_MEAN only)
+    float rej;                          ///< Rejection threshould for clipping (for CLIPPED_MEAN only)
+    bool weights;                       ///< Use the supplied weights (instead of calculated stdev)?
+} pmCombineParams;
+
+// Allocator for pmCombineParams
+pmCombineParams *pmCombineParamsAlloc(psStatsOptions statsOptions ///< Statistic to use for combination
+                                     );
+
+/// Combine multiple readouts, applying zero and scale, with optional minmax clipping
+bool pmReadoutCombine(pmReadout *output,///< Output readout; altered and returned
+                      const psArray *inputs,  ///< Array of input readouts (F32 image and weight, U8 mask)
+                      const psVector *zero, ///< Zero corrections to subtract from input, or NULL
+                      const psVector *scale, ///< Scale corrections to divide into input, or NULL
+                      const pmCombineParams *params ///< Combination parameters
+                     );
+
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmStack.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmStack.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmStack.c	(revision 22157)
@@ -0,0 +1,705 @@
+/** @file  pmStack.c
+ *
+ *  This file will perform image combination of several images of the
+ *  same field, produce a list of questionable pixels, then tag some
+ *  of those pixels as cosmic rays.
+ *
+ *  @author Paul Price, IfA
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.32 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-06-19 01:59:59 $
+ *  Copyright 2004-2007 Institute for Astronomy, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmReadoutStack.h"
+#include "pmConceptsAverage.h"
+
+#include "pmStack.h"
+
+#define PIXEL_LIST_BUFFER 100           // Number of entries to add to pixel list at a time
+#define PIXEL_MAP_BUFFER 2              // Number of entries to add to pixel map at a time
+
+// Internal values for masks
+#define MASK_BAD 0x01                   // Mask value for pixels that have formerly been masked as bad
+#define MASK_SUSPECT 0x02               // Mask value for pixels that are suspected bad (by pmStackCombine)
+
+#define NUM_DIRECT_STDEV 5              // For less than this number of values, measure stdev directly
+
+
+// Data structure for use as a buffer when combining pixels
+// Use of this structure means we don't have to do an allocation in the combination function for each pixel
+typedef struct {
+    psVector *pixels;                   // Pixel values
+    psVector *masks;                    // Pixel masks
+    psVector *variances;                // Pixel variances
+    psVector *weights;                  // Pixel weightings
+    psVector *sources;                  // Pixel sources (which image did they come from?)
+    psVector *sort;                     // Buffer for sorting (to get a robust estimator of the standard dev)
+} combineBuffer;
+
+static void combineBufferFree(combineBuffer *buffer)
+{
+    psFree(buffer->pixels);
+    psFree(buffer->masks);
+    psFree(buffer->variances);
+    psFree(buffer->weights);
+    psFree(buffer->sources);
+    psFree(buffer->sort);
+    return;
+}
+
+static combineBuffer *combineBufferAlloc(long numImages // Number of images that will be combined
+    )
+{
+    combineBuffer *buffer = psAlloc(sizeof(combineBuffer));
+    psMemSetDeallocator(buffer, (psFreeFunc)combineBufferFree);
+
+    buffer->pixels = psVectorAlloc(numImages, PS_TYPE_F32);
+    buffer->masks = psVectorAlloc(numImages, PS_TYPE_MASK);
+    buffer->variances = psVectorAlloc(numImages, PS_TYPE_F32);
+    buffer->weights = psVectorAlloc(numImages, PS_TYPE_F32);
+    buffer->sources = psVectorAlloc(numImages, PS_TYPE_U16);
+    buffer->sort = psVectorAlloc(numImages, PS_TYPE_F32);
+    return buffer;
+}
+
+
+// Deallocator for the stack data
+static void stackDataFree(pmStackData *data)
+{
+    psFree(data->readout);
+    psFree(data->reject);
+    psFree(data->inspect);
+    return;
+}
+
+
+// Determine a mean value and variance for the combination
+// Not using psVectorStats because it assumes that the "weights" are errors, and weights by 1/error^2
+static bool combinationMeanVariance(float *mean, // Mean value, to return
+                                    float *var, // Variance value, to return
+                                    const psVector *values, // Values to combine
+                                    const psVector *variances, // Pixel variances to combine
+                                    const psVector *weights // Weights to apply
+                                    )
+{
+    assert(mean);
+    assert(values && weights);
+    assert(values->n == weights->n);
+    assert((var && variances) || !var);
+    assert(!variances || variances->n == values->n);
+    assert(values->type.type == PS_TYPE_F32);
+    assert(!values || values->type.type == PS_TYPE_F32);
+    assert(weights->type.type == PS_TYPE_F32);
+
+    // We're not using the input pixel variances to generate a weighted average for the pixel flux (because
+    // that introduces systematic biases), so the variance of the output pixel value should simply be:
+    //     simga^2 = sum(weight_i^2 * sigma_i^2) / (sum(weight_i))^2
+    // This reduces, when the weights are all identically unity, to:
+    //     variance_combination = sum(variance_i) / N^2
+    // and if the variances are all equal:
+    //     variance_combination = variance_individual / N
+    // which makes sense --- the standard deviation of the combination is reduced by a factor of sqrt(N).
+
+    float sumValueWeight = 0.0;         // Sum of the value multiplied by the weight
+    float sumVarianceWeight = 0.0;     // Sum of the pixel variances multiplied by the global weights
+    float sumWeight = 0.0;              // Sum of the image weights
+    for (int i = 0; i < values->n; i++) {
+        sumValueWeight += values->data.F32[i] * weights->data.F32[i];
+        sumWeight += weights->data.F32[i];
+        if (variances) {
+            sumVarianceWeight += variances->data.F32[i] * PS_SQR(weights->data.F32[i]);
+        }
+    }
+
+    if (sumWeight <= 0) {
+        return false;
+    }
+
+    *mean = sumValueWeight / sumWeight;
+    if (var) {
+        *var = sumVarianceWeight / PS_SQR(sumWeight);
+    }
+    return true;
+}
+
+// Return the median and standard deviation for the pixels
+// Not using psVectorStats because it has additional allocations which slow things down
+static bool combinationMedianStdev(float *median, // Median value, to return
+                                   float *stdev, // Standard deviation value, to return
+                                   const psVector *values, // Values to combine
+                                   const psVector *masks, // Mask to apply
+                                   psVector *sortBuffer // Buffer for sorting
+                                   )
+{
+    assert(values);
+    assert(!masks || values->n == masks->n);
+    assert(values->type.type == PS_TYPE_F32);
+    assert(!masks || masks->type.type == PS_TYPE_MASK);
+    assert(sortBuffer && sortBuffer->nalloc >= values->n && sortBuffer->type.type == PS_TYPE_F32);
+
+    // Need to filter out clipped values
+    int num = 0;            // Number of valid values
+    for (int i = 0; i < values->n; i++) {
+        if (!masks || !masks->data.PS_TYPE_MASK_DATA[i]) {
+            sortBuffer->data.F32[num++] = values->data.F32[i];
+        }
+    }
+    sortBuffer->n = num;
+    if (!psVectorSortInPlace(sortBuffer)) {
+        return false;
+    }
+
+    if (num == 3) {
+        // Attempt to measure standard deviation with only three values (and one of those possibly corrupted)
+        *median = sortBuffer->data.F32[1];
+        if (stdev) {
+            float diff1 = sortBuffer->data.F32[0] - *median;
+            float diff2 = sortBuffer->data.F32[2] - *median;
+            // This factor of sqrt(2) might not be exact, but it's about right
+            *stdev = M_SQRT2 * PS_MIN(fabsf(diff1), fabsf(diff2));
+        }
+    } else {
+        *median = num % 2 ? sortBuffer->data.F32[num / 2] :
+            (sortBuffer->data.F32[num / 2 - 1] + sortBuffer->data.F32[num / 2]) / 2.0;
+        if (stdev) {
+            if (num <= NUM_DIRECT_STDEV) {
+                // If there are not many values, the direct standard deviation is better
+                double sum = 0.0;
+                for (int i = 0; i < num; i++) {
+                    sum += PS_SQR(sortBuffer->data.F32[i] - *median);
+                }
+                *stdev = sqrt(sum / (double)(num - 1));
+            } else {
+                // Standard deviation from the interquartile range
+                *stdev = 0.74 * (sortBuffer->data.F32[(int)(0.75 * num)] -
+                                 sortBuffer->data.F32[(int)(0.25 * num)]);
+            }
+        }
+    }
+
+    return true;
+}
+
+
+// Mark a pixel for inspection
+static inline void combineInspect(const psArray *inputs, // Stack data
+                                  int x, int y, // Pixel
+                                  int source // Source image index
+                                  )
+{
+    pmStackData *data = inputs->data[source]; // Stack data of interest
+    if (!data) {
+        psWarning("Can't find input data for source %d", source);
+        return;
+    }
+    data->inspect = psPixelsAdd(data->inspect, PIXEL_LIST_BUFFER, x, y);
+    return;
+}
+
+// Given a stack of images, combine with optional rejection.
+// Pixels in the stack that are rejected are marked for subsequent inspection
+static void combinePixels(psImage *image, // Combined image, for output
+                          psImage *mask, // Combined mask, for output
+                          psImage *variance, // Combined variance map, for output
+                          const psArray *inputs, // Stack data
+                          const psVector *weights, // Global (single value) weights for data, or NULL
+                          const psVector *varFactors, // Variance factors for data
+                          const psVector *reject, // Indices of pixels to reject, or NULL
+                          int x, int y, // Coordinates of interest; frame of output image
+                          psMaskType maskVal, // Value to mask
+                          psMaskType bad, // Value to give bad pixels
+                          int numIter, // Number of rejection iterations
+                          float rej, // Number of standard deviations at which to reject
+                          bool useVariance, // Use variance for rejection when combining?
+                          bool safe,    // Combine safely?
+                          combineBuffer *buffer // Buffer for combination; to avoid multiple allocations
+                         )
+{
+    // Rudimentary error checking
+    assert(image);
+    assert(mask);
+    assert(inputs);
+    assert(numIter >= 0);
+    assert(buffer);
+    assert(varFactors);
+    assert((useVariance && variance) || !useVariance);
+
+    psVector *pixelData = buffer->pixels; // Values for the pixel of interest
+    psVector *pixelMasks = buffer->masks; // Masks for the pixel of interest
+    psVector *pixelVariances = variance ? buffer->variances : NULL; // Variances for the pixel of interest
+    psVector *pixelWeights = buffer->weights; // Image weights for the pixel of interest
+    psVector *pixelSources = buffer->sources; // Sources for the pixel of interest
+    psVector *sort = buffer->sort;      // Sort buffer
+
+    // Extract the pixel and mask data
+    int num = 0;                        // Number of good images
+    for (int i = 0, j = 0; i < inputs->n; i++) {
+        // Check if this pixel has been rejected.  Assumes that the rejection pixel list is sorted --- it
+        // should be because of how pixelMapGenerate works
+        if (reject && reject->data.U16[j] == i) {
+            j++;
+            continue;
+        }
+
+        pmStackData *data = inputs->data[i]; // Stack data of interest
+        if (!data) {
+            continue;
+        }
+
+        int xIn = x - data->readout->col0, yIn = y - data->readout->row0; // Coordinates on input readout
+        psImage *mask = data->readout->mask; // Mask of interest
+        if (mask->data.PS_TYPE_MASK_DATA[yIn][xIn] & maskVal) {
+            continue;
+        }
+
+        psImage *image = data->readout->image; // Image of interest
+        psImage *variance = data->readout->weight; // Variance ("weight") map of interest
+        pixelData->data.F32[num] = image->data.F32[yIn][xIn];
+        if (variance) {
+            pixelVariances->data.F32[num] = variance->data.F32[yIn][xIn];
+        }
+        pixelWeights->data.F32[num] = data->weight;
+        pixelSources->data.U16[num] = i;
+        num++;
+    }
+    pixelData->n = num;
+    if (variance) {
+        pixelVariances->n = num;
+    }
+    pixelWeights->n = num;
+    pixelSources->n = num;
+
+
+    // The sensible thing to do varies according to how many good pixels there are.
+    // Default option is that the pixel is bad
+    float imageValue = NAN, varianceValue = NAN; // Value for combined image and variance map
+    psMaskType maskValue = bad;         // Value for combined mask
+    switch (num) {
+      case 0:
+        // Nothing to combine: it's bad
+        break;
+      case 1: {
+          // Accept the single pixel unless we have to be safe
+          if (!safe) {
+              imageValue = pixelData->data.F32[0];
+              if (variance) {
+                  varianceValue = pixelVariances->data.F32[0];
+              }
+              maskValue = 0;
+          }
+          break;
+      }
+      case 2: {
+          // Accept the mean of the pixels only if we're going to reject based on the variance, or we're not
+          // playing safe
+          if (useVariance || !safe) {
+              float mean, var;   // Mean and variance from combination
+              if (combinationMeanVariance(&mean, &var, pixelData, pixelVariances, pixelWeights)) {
+                  imageValue = mean;
+                  varianceValue = var;
+                  maskValue = 0;
+              }
+          }
+          if (useVariance && safe && numIter > 0) {
+              // Use variance to check that the two are consistent
+              float diff = pixelData->data.F32[0] - pixelData->data.F32[1];
+              float sigma = sqrtf(pixelVariances->data.F32[0] + pixelVariances->data.F32[1]);
+              if (fabs(diff) > rej * sigma) {
+                  // Not consistent: mark both for inspection
+                  combineInspect(inputs, x, y, pixelSources->data.U16[0]);
+                  combineInspect(inputs, x, y, pixelSources->data.U16[1]);
+              }
+          }
+          break;
+      }
+      default: {
+          // Record the value derived with no clipping, because pixels rejected using the harsh clipping
+          // applied in the first pass might later be accepted.
+          float mean, var;           // Mean and variance of the combination
+          if (!combinationMeanVariance(&mean, &var, pixelData, pixelVariances, pixelWeights)) {
+              break;
+          }
+          imageValue = mean;
+          varianceValue = var;
+          maskValue = 0;
+
+          // Prepare for clipping iteration
+          if (numIter > 0) {
+              pixelMasks->n = num;
+              psVectorInit(pixelMasks, 0);
+              if (useVariance) {
+                  // Convert to rejection limits --- saves doing it later.
+                  // Using squared rejection limit because it's cheaper than sqrts
+                  rej *= rej;
+                  for (int i = 0; i < num; i++) {
+                      pixelVariances->data.F32[i] = rej * pixelVariances->data.F32[i] *
+                          varFactors->data.F32[i];
+                  }
+              }
+          }
+
+          // The clipping that follows is solely to identify suspect pixels.
+          // These suspect pixels will be inspected in more detail by other functions.
+          int numClipped = INT_MAX;       // Number of pixels clipped per iteration
+          int totalClipped = 0;           // Total number of pixels clipped
+          for (int i = 0; i < numIter && numClipped > 0 && num - totalClipped > 2; i++) {
+              numClipped = 0;
+              float median, stdev;    // Median and stdev of the combination, for rejection
+
+              if (!combinationMedianStdev(&median, useVariance ? NULL : &stdev, pixelData, pixelMasks, sort)) {
+                  psWarning("Bad median/stdev at %d,%d", x, y);
+                  break;
+              }
+
+              float limit = NAN;        // Rejection limit, when rejecting based on data properties
+              if (!useVariance) {
+                  limit = rej * stdev;
+              }
+
+// Mask a pixel for inspection
+#define MASK_PIXEL_FOR_INSPECTION() \
+    pixelMasks->data.PS_TYPE_MASK_DATA[j] = 0xff; \
+    combineInspect(inputs, x, y, pixelSources->data.U16[j]); \
+    numClipped++; \
+    totalClipped++;
+
+              for (int j = 0; j < num; j++) {
+                  if (pixelMasks->data.PS_TYPE_MASK_DATA[j]) {
+                      continue;
+                  }
+                  float diff = fabsf(pixelData->data.F32[j] - median); // Difference from expected
+                  if (useVariance) {
+                      // Comparing squares --- cheaper than lots of sqrts
+                      if (PS_SQR(diff) > pixelVariances->data.F32[i]) {
+                          MASK_PIXEL_FOR_INSPECTION();
+                      }
+                  } else if (diff > limit) {
+                      MASK_PIXEL_FOR_INSPECTION();
+                  }
+              }
+          }
+          break;
+      }
+    }
+
+    image->data.F32[y][x] = imageValue;
+    mask->data.PS_TYPE_MASK_DATA[y][x] = maskValue;
+    if (variance) {
+        variance->data.F32[y][x] = varianceValue;
+    }
+
+    return;
+}
+
+
+// Ensure the input array of pmStackData is valid, and get some details out of it
+static bool validateInputData(bool *haveVariances, // Do we have variance maps in the sky images?
+                              bool *haveRejects, // Do we have lists of rejected pixels?
+                              int *num,    // Number of inputs
+                              int *numCols, int *numRows, // Size of (sky) images
+                              psArray *input // Input array of pmStackData to validate
+    )
+{
+    PS_ASSERT_ARRAY_NON_NULL(input, false);
+    *num = input->n;
+
+    pmStackData *data = NULL;           // First image off the rank, used as a template
+    for (int i = 0; !data && i < input->n; i++) {
+        data = input->data[i];
+    }
+    PS_ASSERT_PTR_NON_NULL(data, false);
+    assert(psMemGetDeallocator(data) == (psFreeFunc)stackDataFree); // Ensure it's the right type
+    *haveVariances = false;
+    PS_ASSERT_IMAGE_NON_NULL(data->readout->image, false);
+    PS_ASSERT_IMAGE_TYPE(data->readout->image, PS_TYPE_F32, false);
+    PS_ASSERT_IMAGE_NON_NULL(data->readout->mask, false);
+    PS_ASSERT_IMAGE_TYPE(data->readout->mask, PS_TYPE_MASK, false);
+    PS_ASSERT_IMAGES_SIZE_EQUAL(data->readout->image, data->readout->mask, false);
+    *numCols = data->readout->image->numCols;
+    *numRows = data->readout->image->numRows;
+    if (data->readout->weight) {
+        *haveVariances = true;
+        PS_ASSERT_IMAGE_NON_NULL(data->readout->weight, false);
+        PS_ASSERT_IMAGES_SIZE_EQUAL(data->readout->image, data->readout->weight, false);
+        PS_ASSERT_IMAGE_TYPE(data->readout->weight, PS_TYPE_F32, false);
+    }
+    *haveRejects = (data->reject != NULL);
+
+    // Make sure the rest correspond with the first
+    for (int i = 1; i < *num; i++) {
+        pmStackData *data = input->data[i]; // Stack data for this input
+        if (!data) {
+            continue;
+        }
+        assert(psMemGetDeallocator(data) == (psFreeFunc)stackDataFree); // Ensure it's the right type
+        if (!data->readout) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "The readout is specified in some but not all inputs.");
+            return false;
+        }
+        if ((*haveRejects && !data->reject) || (data->reject && !*haveRejects)) {
+            psError(PS_ERR_UNEXPECTED_NULL, true,
+                    "The rejected pixels are specified in some but not all inputs.");
+            return false;
+        }
+        PS_ASSERT_IMAGE_NON_NULL(data->readout->image, false);
+        PS_ASSERT_IMAGE_NON_NULL(data->readout->mask, false);
+        PS_ASSERT_IMAGE_TYPE(data->readout->image, PS_TYPE_F32, false);
+        PS_ASSERT_IMAGE_TYPE(data->readout->mask, PS_TYPE_MASK, false);
+        PS_ASSERT_IMAGE_SIZE(data->readout->image, *numCols, *numRows, false);
+        PS_ASSERT_IMAGES_SIZE_EQUAL(data->readout->image, data->readout->mask, false);
+        if (*haveVariances) {
+            PS_ASSERT_IMAGE_NON_NULL(data->readout->weight, false);
+            PS_ASSERT_IMAGES_SIZE_EQUAL(data->readout->image, data->readout->weight, false);
+            PS_ASSERT_IMAGE_TYPE(data->readout->weight, PS_TYPE_F32, false);
+        }
+    }
+
+    return true;
+}
+
+
+// Generate a "pixel map".
+//
+// A "pixel map" is an image-like structure containing a vector that contains the indices of images.  The idea
+// is to provide a reverse lookup for an array of pixel lists, so that the image for which a pixel is flagged
+// can be identified easily.
+static psArray *pixelMapGenerate(const psArray *input, // Data to stack
+                                 int numCols, int numRows // Size of (sky) images
+    )
+{
+    psArray *map = psArrayAlloc(numRows); // The pixel map
+    for (int y = 0; y < numRows; y++) {
+        map->data[y] = psArrayAlloc(numCols);
+    }
+
+    for (int i = 0; i < input->n; i++) {
+        pmStackData *data = input->data[i];
+        if (!data) {
+            continue;
+        }
+        assert(data->reject);
+        psPixels *pixels = data->reject; // The rejected pixels
+        for (int j = 0; j < pixels->n; j++) {
+            int x = pixels->data[j].x, y = pixels->data[j].y; // Coordinates of interest
+            if (x < 0 || x >= numCols || y < 0 || y >= numRows) {
+                //                psWarning("Bad pixel coordinate: %d,%d --- ignored.", x, y);
+                continue;
+            }
+            psArray *columns = map->data[y]; // The columns for that row
+            psVector *images = columns->data[x]; // The images for that column
+            if (!images) {
+                images = columns->data[x] = psVectorAllocEmpty(PIXEL_MAP_BUFFER, PS_TYPE_U16);
+            }
+            int size = images->n;       // Element number at which to add
+            columns->data[x] = psVectorExtend(images, PIXEL_MAP_BUFFER, 1);
+            images->data.U16[size] = i;
+        }
+    }
+
+    return map;
+}
+
+// Query a "pixel map", by returning the list of image indices for a particular pixel.
+static psVector *pixelMapQuery(const psArray *map, // Pixel map
+                               int x, int y // Coordinates of interest
+    )
+{
+    assert(y >= 0 && y < map->n);
+    psArray *colMap = map->data[y];     // Columns for that row
+    assert(x >= 0 && x < colMap->n);
+    return colMap->data[x];
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/// Constructor
+pmStackData *pmStackDataAlloc(pmReadout *readout, float weight)
+{
+    pmStackData *data = psAlloc(sizeof(pmStackData)); // Stack data, to return
+    psMemSetDeallocator(data, (psFreeFunc)stackDataFree);
+
+    data->readout = psMemIncrRefCounter(readout);
+    data->reject = NULL;
+    data->inspect = NULL;
+    data->weight = weight;
+
+    return data;
+}
+
+/// Stack input images
+bool pmStackCombine(pmReadout *combined, psArray *input, psMaskType maskVal, psMaskType bad,
+                    int kernelSize, int numIter, float rej, bool entire, bool useVariance, bool safe)
+{
+    PS_ASSERT_PTR_NON_NULL(combined, false);
+    bool haveVariances;                 // Do we have the variance maps?
+    bool haveRejects;                   // Do we have lists of rejected pixels?
+    int num;                            // Number of inputs
+    int numCols, numRows;               // Size of (sky) images
+    if (!validateInputData(&haveVariances, &haveRejects, &num, &numCols, &numRows, input)) {
+        return false;
+    }
+    PS_ASSERT_INT_NONNEGATIVE(kernelSize, false);
+    PS_ASSERT_INT_POSITIVE(bad, false);
+    PS_ASSERT_INT_NONNEGATIVE(numIter, false);
+    if (isnan(rej)) {
+        PS_ASSERT_INT_EQUAL(numIter, 0, false);
+    } else {
+        PS_ASSERT_FLOAT_LARGER_THAN(rej, 0.0, false);
+    }
+    if (haveRejects) {
+        // This is a subsequent combination, so expect that the image and mask already exist
+        PS_ASSERT_IMAGE_NON_NULL(combined->image, false);
+        PS_ASSERT_IMAGE_TYPE(combined->image, PS_TYPE_F32, false);
+        PS_ASSERT_IMAGE_NON_NULL(combined->mask, false);
+        PS_ASSERT_IMAGE_TYPE(combined->mask, PS_TYPE_MASK, false);
+        PS_ASSERT_IMAGES_SIZE_EQUAL(combined->image, combined->mask, false);
+    }
+    if (useVariance && !haveVariances) {
+        psWarning("Unable to use variance in rejection if no variance maps supplied --- option turned off");
+        useVariance = false;
+    }
+
+    psVector *varFactors = psVectorAlloc(num, PS_TYPE_F32); // Variance factors for each image
+    psVector *weights = psVectorAlloc(num, PS_TYPE_F32); // Relative weighting for each image
+    psArray *stack = psArrayAlloc(num); // Stack of readouts
+    for (int i = 0; i < num; i++) {
+        pmStackData *data = input->data[i]; // Stack data for this input
+        if (!data) {
+            weights->data.F32[i] = 0.0;
+            continue;
+        }
+        weights->data.F32[i] = data->weight;
+        stack->data[i] = psMemIncrRefCounter(data->readout);
+        // Variance factor
+        float vf = psMetadataLookupF32(NULL, data->readout->parent->concepts, "CELL.VARFACTOR"); // Var factor
+        if (!isfinite(vf)) {
+            psWarning("Non-finite CELL.VARFACTOR for image %d --- setting to unity.", i);
+            vf = 1.0;
+        }
+        varFactors->data.F32[i] = vf;
+    }
+
+    int minInputCols, maxInputCols, minInputRows, maxInputRows; // Smallest and largest values to combine
+    int xSize, ySize;                   // Size of the output image
+    if (!pmReadoutStackValidate(&minInputCols, &maxInputCols, &minInputRows, &maxInputRows, &xSize, &ySize,
+                                stack)) {
+        psError(PS_ERR_UNKNOWN, false, "Input stack is not valid.");
+        psFree(stack);
+        return false;
+    }
+    psFree(stack);
+    pmReadoutUpdateSize(combined, minInputCols, minInputRows, xSize, ySize, true, true, bad);
+    psTrace("psModules.imcombine", 1, "Have for combination [%d:%d,%d:%d] (%dx%d)\n",
+            minInputCols, maxInputCols, minInputRows, maxInputRows, xSize, ySize);
+
+    // Reduce combination area by the size of the kernel
+    minInputCols += kernelSize;
+    maxInputCols -= kernelSize;
+    minInputRows += kernelSize;
+    maxInputRows -= kernelSize;
+    psTrace("psModules.imcombine", 1, "Combining on [%d:%d,%d:%d]\n",
+            minInputCols, maxInputCols, minInputRows, maxInputRows);
+
+
+    // Buffer for combination
+    combineBuffer *buffer = combineBufferAlloc(num);
+
+    if (haveRejects) {
+        psImage *combinedImage = combined->image; // Combined image
+        psImage *combinedMask = combined->mask; // Combined mask
+        psImage *combinedVariance = combined->weight; // Combined variance map
+
+        psArray *pixelMap = pixelMapGenerate(input, maxInputCols, maxInputRows); // Map of pixels to source
+        psPixels *pixels = NULL;            // Total list of pixels, with no duplicates
+        for (int i = 0; i < num; i++) {
+            pmStackData *data = input->data[i]; // Stacking data; contains the list of pixels
+            if (!data) {
+                continue;
+            }
+            pixels = psPixelsConcatenate(pixels, data->reject);
+        }
+
+        if (entire) {
+            // Combine entire image
+            for (int y = minInputRows; y < maxInputRows; y++) {
+                for (int x = minInputCols; x < maxInputCols; x++) {
+                    psVector *reject = pixelMapQuery(pixelMap, x, y); // Inspect these images closely
+                    combinePixels(combinedImage, combinedMask, combinedVariance, input, weights, varFactors,
+                                  reject, x, y, maskVal, bad, numIter, rej, useVariance, safe, buffer);
+                }
+            }
+        } else {
+            // Only combine previously rejected pixels
+            for (int i = 0; i < pixels->n; i++) {
+                // Pixel coordinates are in the frame of the original image
+                int x = pixels->data[i].x, y = pixels->data[i].y; // Coordinates of interest
+                if (x < minInputCols || x >= maxInputCols || y < minInputRows || y >= maxInputRows) {
+                    continue;
+                }
+                psVector *reject = pixelMapQuery(pixelMap, x, y); // Inspect these images closely
+                combinePixels(combinedImage, combinedMask, combinedVariance, input, weights, varFactors,
+                              reject, x, y, maskVal, bad, numIter, rej, useVariance, safe, buffer);
+            }
+        }
+        psFree(pixels);
+        psFree(pixelMap);
+    } else {
+        // Pull the products out, allocate if necessary
+        psImage *combinedImage = combined->image; // Combined image
+        if (!combinedImage) {
+            combined->image = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+            combinedImage = combined->image;
+        }
+        psImage *combinedMask = combined->mask; // Combined mask
+        if (!combinedMask) {
+            combined->mask = psImageAlloc(numCols, numRows, PS_TYPE_MASK);
+            combinedMask = combined->mask;
+        }
+
+        psImage *combinedVariance = combined->weight; // Combined variance map
+        if (haveVariances && !combinedVariance) {
+            combined->weight = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+            combinedVariance = combined->weight;
+        }
+
+        for (int y = minInputRows; y < maxInputRows; y++) {
+            for (int x = minInputCols; x < maxInputCols; x++) {
+                combinePixels(combinedImage, combinedMask, combinedVariance, input, weights, varFactors,
+                              NULL, x, y, maskVal, bad, numIter, rej, useVariance, safe, buffer);
+            }
+        }
+
+#ifndef PS_NO_TRACE
+        if (psTraceGetLevel("psModules.imcombine") >= 5) {
+            for (int i = 0; i < num; i++) {
+                pmStackData *data = input->data[i]; // Stack data for this input
+                if (!data || !data->inspect) {
+                    continue;
+                }
+                psTrace("psModules.imcombine", 5, "Image %d: %ld pixels to inspect.\n", i, data->inspect->n);
+            }
+        }
+#endif
+    }
+
+    psFree(weights);
+    psFree(buffer);
+
+    return true;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmStack.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmStack.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmStack.h	(revision 22157)
@@ -0,0 +1,54 @@
+/* @file  pmStack.h
+ *
+ * This file will perform image combination of several images of the
+ * same field, produce a list of questionable pixels, then tag some
+ * of those pixels as defects.
+ *
+ * @author Paul Price, IfA
+ * @author GLG, MHPCC
+ *
+ * @version $Revision: 1.7 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-03-17 21:38:43 $
+ *
+ * Copyright 2004-2007 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_STACK_H
+#define PM_STACK_H
+
+#include <pslib.h>
+#include <pmHDU.h>
+#include <pmFPA.h>
+
+/// @addtogroup imcombine Image Combinations
+/// @{
+
+
+/// Container for input image
+typedef struct {
+    pmReadout *readout;                 ///< Warped readout (sky cell)
+    psPixels *reject;                   ///< Pixels to reject
+    psPixels *inspect;                  ///< Pixels to inspect
+    float weight;                       ///< Relative weighting for image
+} pmStackData;
+
+/// Constructor
+pmStackData *pmStackDataAlloc(pmReadout *readout, ///< Warped readout (sky cell)
+                              float weight ///< Weight to apply
+    );
+
+/// Stack input images
+bool pmStackCombine(pmReadout *combined,///< Combined readout (output)
+                    psArray *input,     ///< Input array of pmStackData
+                    psMaskType maskVal, ///< Mask value of bad pixels
+                    psMaskType bad,     ///< Mask value to give rejected pixels
+                    int kernelSize,     ///< Half-size of the convolution kernel
+                    int numIter,        ///< Number of iterations
+                    float rej,          ///< Rejection limit (standard deviations)
+                    bool entire,        ///< Combine entire image even if rejection lists provided?
+                    bool useVariance,   ///< Use variance values for rejection?
+                    bool safe           ///< Play safe with small numbers of input pixels (mask if N <= 2)?
+    );
+
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmStackReject.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmStackReject.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmStackReject.c	(revision 22157)
@@ -0,0 +1,195 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmSubtraction.h"
+#include "pmSubtractionKernels.h"
+
+#define PIXEL_LIST_BUFFER 100           // Number of pixels to add to list at a time
+
+//#define TESTING                         // Testing output
+
+
+psPixels *pmStackReject(const psPixels *in, const psRegion *valid, float threshold,
+                        const psArray *subRegions, const psArray *kernels)
+{
+    PS_ASSERT_PIXELS_NON_NULL(in, NULL);
+    PS_ASSERT_FLOAT_LARGER_THAN_OR_EQUAL(threshold, 0.0, NULL);
+    PS_ASSERT_FLOAT_LESS_THAN_OR_EQUAL(threshold, 1.0, NULL);
+    PS_ASSERT_ARRAY_NON_NULL(subRegions, NULL);
+    PS_ASSERT_ARRAY_NON_NULL(kernels, NULL);
+    PS_ASSERT_ARRAYS_SIZE_EQUAL(subRegions, kernels, NULL);
+
+    // Trivial case
+    if (in->n == 0) {
+        return psPixelsAllocEmpty(0);
+    }
+
+    // Get the original image size
+    int numRegions = subRegions->n;        // Number of regions
+    int numCols = 0, numRows = 0;       // Size of original image
+    int minCols = INT_MAX, minRows = INT_MAX; // Minimum coordinate for image
+    int size = 0;                       // Size of kernel
+    for (int i = 0; i < numRegions; i++) {
+        psRegion *subRegion = subRegions->data[i]; // Region of interest
+        if (subRegion->x0 < minCols) {
+            minCols = subRegion->x0;
+        }
+        if (subRegion->y0 < minRows) {
+            minRows = subRegion->y0;
+        }
+        if (subRegion->x1 > numCols) {
+            numCols = subRegion->x1;
+        }
+        if (subRegion->y1 > numRows) {
+            numRows = subRegion->y1;
+        }
+
+        pmSubtractionKernels *kernel = kernels->data[i]; // Kernel of interest
+        if (size == 0) {
+            size = kernel->size;
+        } else if (kernel->size != size) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Kernel sizes are not identical: %d vs %d",
+                    size, kernel->size);
+            return NULL;
+        }
+    }
+
+    // Adjust the size for the size of the subimage
+    if (valid) {
+        minCols = PS_MAX(valid->x0, minCols);
+        minRows = PS_MAX(valid->y0, minRows);
+        numCols = PS_MIN(valid->x1, numCols);
+        numRows = PS_MIN(valid->y1, numRows);
+    }
+    psTrace("psModules.imcombine", 1, "Rejecting [%d:%d,%d:%d]\n", minCols, numCols, minRows, numRows);
+
+    psImage *mask = psPixelsToMask(NULL, in, psRegionSet(minCols, numCols - 1, minRows, numRows - 1),
+                                   0x01); // Mask
+    psImage *image = psImageCopy(NULL, mask, PS_TYPE_F32); // Floating-point version, so we can convolve
+    psFree(mask);
+
+    // Convolve the image with the kernel --- we're basically applying a matched filter and then thresholding
+    pmReadout *convRO = pmReadoutAlloc(NULL); // Readout with convolved image
+    pmReadout *inRO = pmReadoutAlloc(NULL); // Readout with input image
+    inRO->image = image;
+    inRO->col0 = minCols;
+    inRO->row0 = minRows;
+    for (int i = 0; i < numRegions; i++) {
+        psRegion *region = subRegions->data[i]; // Region of interest
+        if (valid && (region->x0 > valid->x1 || region->x1 < valid->x0 ||
+                      region->y0 > valid->y1 || region->y1 < valid->y0)) {
+            // Region is outside of our sub-image
+            continue;
+        }
+        pmSubtractionKernels *kernel = kernels->data[i]; // Kernel of interest
+        if (!pmSubtractionConvolve(convRO, NULL, inRO, NULL, NULL, 0, region, kernel, false, true)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to convolve mask image in region %d.", i);
+            psFree(convRO);
+            psFree(inRO);
+            return NULL;
+        }
+
+        // Need to adjust the thresholding level for the normalisation of the kernel --- the application of
+        // the kernel may scale the unit level that we've inserted.
+
+        // Image of the kernel at the centre of the region
+        float xNorm = (region->x0 + 0.5 * (region->x1 - region->x0) - kernel->numCols/2.0) /
+            (float)kernel->numCols;
+        float yNorm = (region->y0 + 0.5 * (region->y1 - region->y0) - kernel->numRows/2.0) /
+            (float)kernel->numRows;
+        psImage *image = pmSubtractionKernelImage(kernel, xNorm, yNorm, false);
+        if (!image) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to generate kernel image.");
+            psFree(convRO);
+            psFree(inRO);
+            return NULL;
+        }
+        float sum = 0.0;
+        for (int y = 0; y < image->numRows; y++) {
+            for (int x = 0; x < image->numCols; x++) {
+                sum += image->data.F32[y][x];
+            }
+        }
+        psFree(image);
+
+        // Range for normalisation
+        int yMin = PS_MAX(minRows, region->y0) - inRO->row0;
+        int yMax = PS_MIN(numRows - 1, region->y1) - inRO->row0;
+        int xMin = PS_MAX(minCols, region->x0) - inRO->col0;
+        int xMax = PS_MIN(numCols - 1, region->x1) - inRO->col0;
+        psTrace("psModules.imcombine", 2, "Normalising convolved mask image by %f over %d:%d,%d:%d\n",
+                sum, xMin, xMax, yMin, yMax);
+        for (int y = yMin; y <= yMax; y++) {
+            for (int x = xMin; x <= xMax; x++) {
+                convRO->image->data.F32[y][x] /= sum;
+            }
+        }
+    }
+    psFree(inRO);
+    psImage *convolved = psMemIncrRefCounter(convRO->image);
+    psFree(convRO);
+
+#ifdef TESTING
+    {
+        static int seqNum = 0;          // Sequence number
+        psString name = NULL;           // Name of image
+        psStringAppend(&name, "inspect_conv_%02d.fits", seqNum);
+        seqNum++;
+        psFits *fits = psFitsOpen(name, "w"); // FITS file pointer
+        psFree(name);
+        psFitsWriteImage(fits, NULL, convolved, 0, NULL);
+        psFitsClose(fits);
+    }
+#endif
+
+    // Threshold the convolved image
+    psPixels *bad = psPixelsAllocEmpty(PIXEL_LIST_BUFFER); // List of pixels that should be masked
+    for (int y = size; y < convolved->numRows - size; y++) {
+        for (int x = size; x < convolved->numCols - size; x++) {
+            if (convolved->data.F32[y][x] > threshold) {
+                // Pixel coordinates in "bad" correspond to the full image
+                bad = psPixelsAdd(bad, PIXEL_LIST_BUFFER, x + minCols, y + minRows);
+            }
+        }
+    }
+    psFree(convolved);
+
+    // Now, grow the mask to include everything that touches a bad pixel in the convolution
+    int x0 = minCols, y0 = minRows;     // Offset for mask image
+    mask = psPixelsToMask(NULL, bad, psRegionSet(x0, numCols - 1, y0, numRows - 1), 0xff);
+    for (int i = 0; i < bad->n; i++) {
+        int xPix = bad->data[i].x - x0, yPix = bad->data[i].y - y0; // Coordinates in frame of mask image
+        // Convolution limits
+        int xMin = PS_MAX(xPix - size, 0);
+        int xMax = PS_MIN(xPix + size, mask->numCols - 1);
+        int yMin = PS_MAX(yPix - size, 0);
+        int yMax = PS_MIN(yPix + size, mask->numRows - 1);
+        for (int y = yMin; y <= yMax; y++) {
+            for (int x = xMin; x <= xMax; x++) {
+                assert(x < mask->numCols && y < mask->numRows);
+                mask->data.PS_TYPE_MASK_DATA[y][x] = 0xff;
+            }
+        }
+    }
+    bad = psPixelsFromMask(bad, mask, 0xff);
+    psFree(mask);
+
+    // Convert coordinates to frame of original image
+    for (int i = 0; i < bad->n; i++) {
+        int x = bad->data[i].x + x0;
+        int y = bad->data[i].y + y0;
+        if (x < 0 || x >= numCols || y < 0 || y >= numRows) {
+            psWarning("Bad pixel coordinate %d: %d,%d --- ignored.",
+                      i, x, y);
+            continue;
+        }
+        bad->data[i].x = x;
+        bad->data[i].y = y;
+    }
+
+    return bad;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmStackReject.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmStackReject.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmStackReject.h	(revision 22157)
@@ -0,0 +1,19 @@
+#ifndef PM_STACK_REJECT_H
+#define PM_STACK_REJECT_H
+
+#include <pslib.h>
+#include <pmSubtractionKernels.h>
+
+/// Given a list of pixels from the convolved image, find the corresponding (smaller subset of) pixels in the
+/// original image, and then convolve those to get the list of all pixels which should be rejected
+///
+/// We apply a matched filter to the corresponding mask image, and threshold to find the original pixels
+psPixels *pmStackReject(const psPixels *in, ///< List of pixels in the convolved image
+                        const psRegion *valid, ///< Valid region to consider
+                        float threshold, ///< Threshold on convolved image, 0..1
+                        const psArray *regions, ///< Array of image regions for image
+                        const psArray *kernels ///< Array of kernel parameters for each region
+    );
+
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtraction.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtraction.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtraction.c	(revision 22157)
@@ -0,0 +1,915 @@
+/** @file pmSubtraction.c
+ *
+ *  @author Paul Price, IfA
+ *  @author GLG, MHPCC
+ *
+ *  Copyright 2004-2007 Institute for Astronomy, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmHDU.h"                      // Required for pmFPA.h
+#include "pmFPA.h"
+#include "pmSubtractionStamps.h"
+#include "pmSubtractionEquation.h"
+
+#include "pmSubtraction.h"
+
+//#define TESTING
+
+#define PIXEL_LIST_BUFFER 100           // Number of entries to add to pixel list at a time
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Private (file-static) functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Generate the kernel to apply to the variance from the normal kernel
+static psKernel *varianceKernel(psKernel *out, // Output kernel
+                                psKernel *normalKernel // Normal kernel
+                                )
+{
+    // Kernel range
+    int xMin = normalKernel->xMin, xMax = normalKernel->xMax;
+    int yMin = normalKernel->yMin, yMax = normalKernel->yMax;
+
+    if (!out) {
+        out = psKernelAlloc(xMin, xMax, yMin, yMax);
+    }
+
+    // Take the square of the normal kernel
+    double sumNormal = 0.0, sumVariance = 0.0; // Sum of the normal and variance kernels
+    for (int v = yMin; v <= yMax; v++) {
+        for (int u = xMin; u <= xMax; u++) {
+            float value = normalKernel->kernel[v][u]; // Value of interest
+            float value2 = PS_SQR(value); // Value squared
+            sumNormal += value;
+            sumVariance += value2;
+            out->kernel[v][u] = value2;
+        }
+    }
+
+    // Normalise so that the sum of the variance kernel is the square of the sum of the normal kernel
+    // This is required to keep the relative scaling between the image and the weight map
+    psBinaryOp(out->image, out->image, "*", psScalarAlloc(PS_SQR(sumNormal) / sumVariance, PS_TYPE_F32));
+
+    return out;
+}
+
+// Generate an image of the solved kernel
+static psKernel *solvedKernel(psKernel *kernel, // Kernel, to return
+                              const pmSubtractionKernels *kernels, // Kernel basis functions
+                              const psImage *polyValues, // Spatial polynomial values
+                              bool wantDual // Want the dual (second) kernel?
+                              )
+{
+    assert(kernels);
+    assert(polyValues);
+
+    int numKernels = kernels->num;      // Number of kernel basis functions
+    int size = kernels->size;           // Kernel half-size
+    if (!kernel) {
+        kernel = psKernelAlloc(-size, size, -size, size);
+    }
+    psImageInit(kernel->image, 0.0);
+
+    for (int i = 0; i < numKernels; i++) {
+        double value = p_pmSubtractionSolutionCoeff(kernels, polyValues, i, wantDual); // Polynomial value
+
+        switch (kernels->type) {
+          case PM_SUBTRACTION_KERNEL_POIS: {
+              int u = kernels->u->data.S32[i]; // Offset in x
+              int v = kernels->v->data.S32[i]; // Offset in y
+              kernel->kernel[v][u] += value;
+              kernel->kernel[0][0] -= value;
+              break;
+          }
+          /* SPAM and FRIES use the same method */
+          case PM_SUBTRACTION_KERNEL_SPAM:
+          case PM_SUBTRACTION_KERNEL_FRIES: {
+              int uStart = kernels->u->data.S32[i];
+              int uStop = kernels->uStop->data.S32[i];
+              int vStart = kernels->v->data.S32[i];
+              int vStop = kernels->vStop->data.S32[i];
+
+              // Normalising sum of kernel component to unity
+              float norm = 1.0 / (float)((uStop - uStart + 1) * (vStop - vStart + 1));
+
+              for (int v = vStart; v <= vStop; v++) {
+                  for (int u = uStart; u <= uStop; u++) {
+                      kernel->kernel[v][u] += norm * value;
+                      kernel->kernel[0][0] -= value;
+                  }
+              }
+              break;
+          }
+          case PM_SUBTRACTION_KERNEL_GUNK: {
+              if (i < kernels->inner) {
+                  // Using pre-calculated function
+                  psKernel *preCalc = kernels->preCalc->data[i]; // Precalculated values
+                  // Iterating over the kernel
+                  for (int v = -size; v <= size; v++) {
+                      for (int u = -size; u <= size; u++) {
+                          kernel->kernel[v][u] += preCalc->kernel[v][u] * value;
+                          // Photometric scaling is built into the preCalc kernel --- no subtraction!
+                      }
+                  }
+              } else {
+                  // Using delta function
+                  int u = kernels->u->data.S32[i]; // Offset in x
+                  int v = kernels->v->data.S32[i]; // Offset in y
+                  kernel->kernel[v][u] += value;
+                  kernel->kernel[0][0] -= value;
+              }
+              break;
+          }
+          case PM_SUBTRACTION_KERNEL_ISIS: {
+              psKernel *preCalc = kernels->preCalc->data[i]; // Precalculated values
+              // Iterating over the kernel
+              for (int v = -size; v <= size; v++) {
+                  for (int u = -size; u <= size; u++) {
+                      kernel->kernel[v][u] += preCalc->kernel[v][u] * value;
+                      // Photometric scaling is built into the preCalc kernel --- no subtraction!
+                  }
+              }
+              break;
+          }
+          case PM_SUBTRACTION_KERNEL_RINGS: {
+              psArray *preCalc = kernels->preCalc->data[i]; // Precalculated data
+              psVector *uCoords = preCalc->data[0]; // u coordinates
+              psVector *vCoords = preCalc->data[1]; // v coordinates
+              psVector *poly = preCalc->data[2]; // Polynomial values
+              int num = uCoords->n;     // Number of pixels
+
+              for (int j = 0; j < num; j++) {
+                  int u = uCoords->data.S32[j], v = vCoords->data.S32[j]; // Kernel coordinates
+                  kernel->kernel[v][u] += poly->data.F32[j] * value;
+              }
+              // Photometric scaling is built into the kernel --- no subtraction!
+              break;
+          }
+          default:
+            psAbort("Should never get here.");
+        }
+    }
+
+    // Put in the normalisation component
+    kernel->kernel[0][0] += (wantDual ? 1.0 : p_pmSubtractionSolutionNorm(kernels));
+
+    return kernel;
+}
+
+// Subtract the (0,0) element to preserve photometric scaling
+static void convolveSub(psKernel *convolved, // Convolved image
+                        const psKernel *image, // Image being convolved
+                        int footprint  // Size of region of interest
+                        )
+{
+    // Can't use psBinaryOp because the images are of different size
+    for (int y = -footprint; y <= footprint; y++) {
+        for (int x = -footprint; x <= footprint; x++) {
+            convolved->kernel[y][x] -= image->kernel[y][x];
+        }
+    }
+    return;
+}
+
+// Generate the convolution given some offset
+static psKernel *convolveOffset(const psKernel *image, // Image to convolve (a kernel for convenience)
+                                int u, int v, // Offset to apply
+                                int footprint // Size of region of interest
+                                )
+{
+    psKernel *convolved = psKernelAlloc(-footprint, footprint, -footprint, footprint); // Convolved image
+    int numBytes = (2 * footprint + 1) * PSELEMTYPE_SIZEOF(PS_TYPE_F32); // Number of bytes to copy
+    for (int y = -footprint; y <= footprint; y++) {
+        // Convolution with a delta function is just the value specified by the offset
+        memcpy(&convolved->kernel[y][-footprint], &image->kernel[y - v][-footprint - u], numBytes);
+    }
+    return convolved;
+}
+
+
+// Convolve an image using FFT
+static void convolveFFT(psImage *target,// Place the result in here
+                        const psImage *image, // Image to convolve
+                        const psImage *mask, // Mask image
+                        psMaskType maskVal, // Value to mask
+                        const psKernel *kernel, // Kernel by which to convolve
+                        psRegion region,// Region of interest
+                        float background, // Background to add
+                        int size        // Size of (square) kernel
+                        )
+{
+    psRegion border = psRegionSet(region.x0 - size, region.x1 + size,
+                                  region.y0 - size, region.y1 + size); // Add a border
+
+    // Casting away const so psImageSubset can add the child
+    psImage *subImage = psImageSubset((psImage*)image, border); // Subimage to convolve
+    psImage *subMask = mask ? psImageSubset((psImage*)mask, border) : NULL; // Subimage mask
+    psImage *convolved = psImageConvolveFFT(NULL, subImage, subMask, maskVal, kernel); // Convolution
+    psFree(subImage);
+    psFree(subMask);
+
+    // Now, we have to chop off the borders, and stick it in where it belongs
+    psImage *subConv = psImageSubset(convolved, psRegionSet(size, -size, size, -size)); // Cut off the edges
+    psImage *subTarget = psImageSubset(target, region); // Target for this subregion
+    if (background != 0.0) {
+        psBinaryOp(subTarget, subConv, "+", psScalarAlloc(background, PS_TYPE_F32));
+    } else {
+        int numBytes = subTarget->numCols * PSELEMTYPE_SIZEOF(PS_TYPE_F32); // Number of bytes to copy
+        for (int y = 0; y < subTarget->numRows; y++) {
+            memcpy(subTarget->data.F32[y], subConv->data.F32[y], numBytes);
+        }
+    }
+    psFree(subConv);
+    psFree(convolved);
+    psFree(subTarget);
+    return;
+}
+
+// Convolve an image directly
+static void convolveDirect(psImage *target, // Put the result here
+                           const psImage *image, // Image to convolve
+                           const psKernel *kernel, // Kernel by which to convolve
+                           psRegion region,// Region of interest
+                           float background, // Background to add
+                           int size        // Size of (square) kernel
+                           )
+{
+    for (int y = region.y0; y < region.y1; y++) {
+        for (int x = region.x0; x < region.x1; x++) {
+            target->data.F32[y][x] = background;
+            for (int v = -size; v <= size; v++) {
+                for (int u = -size; u <= size; u++) {
+                    target->data.F32[y][x] += kernel->kernel[v][u] *
+                        image->data.F32[y - v][x - u];
+                }
+            }
+        }
+    }
+    return;
+}
+
+// Convolve a region of an image
+static inline void convolveRegion(psImage *convImage, // Convolved image (output)
+                                  psImage *convWeight, // Convolved weight map (output), or NULL
+                                  psKernel **kernelImage, // Convolution kernel for the image
+                                  psKernel **kernelWeight, // Convolution kernel for the weight map, or NULL
+                                  const psImage *image, // Image to convolve
+                                  const psImage *weight, // Weight map to convolve, or NULL
+                                  const psImage *subMask, // Subtraction mask
+                                  psMaskType maskVal, // Mask value to apply in convolution
+                                  const pmSubtractionKernels *kernels, // Kernels
+                                  psImage *polyValues, // Polynomial values
+                                  float background, // Background value to apply
+                                  psRegion region, // Region to convolve
+                                  bool useFFT,  // Use FFT to convolve?
+                                  bool wantDual // Want the dual convolution?
+    )
+{
+    *kernelImage = solvedKernel(*kernelImage, kernels, polyValues, wantDual);
+    if (weight) {
+        *kernelWeight = varianceKernel(*kernelWeight, *kernelImage);
+    }
+
+    if (useFFT) {
+        // Use Fast Fourier Transform to do the convolution
+        // This provides a big speed-up for large kernels
+        convolveFFT(convImage, image, subMask, maskVal, *kernelImage, region, background, kernels->size);
+        if (weight) {
+            convolveFFT(convWeight, weight, subMask, maskVal, *kernelWeight, region, 0.0, kernels->size);
+        }
+    } else {
+        convolveDirect(convImage, image, *kernelImage, region, background, kernels->size);
+        if (weight) {
+            convolveDirect(convWeight, weight, *kernelWeight, region, 0.0, kernels->size);
+        }
+    }
+
+    return;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Semi-public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+psKernel *p_pmSubtractionConvolveStampPrecalc(const psKernel *image, const psKernel *kernel)
+{
+    PS_ASSERT_KERNEL_NON_NULL(image, NULL);
+    PS_ASSERT_KERNEL_NON_NULL(kernel, NULL);
+
+    psImage *conv = psImageConvolveFFT(NULL, image->image, NULL, 0, kernel); // Convolved image
+    int x0 = - image->xMin, y0 = - image->yMin; // Position of centre of convolved image
+    psKernel *convolved = psKernelAllocFromImage(conv, x0, y0); // Kernel version
+    psFree(conv);
+    return convolved;
+}
+
+psImage *p_pmSubtractionPolynomial(psImage *output, int spatialOrder, float x, float y)
+{
+    assert(spatialOrder >= 0);
+    assert(x >= -1 && x <= 1);
+    assert(y >= -1 && y <= 1);
+
+    output = psImageRecycle(output, spatialOrder + 1, spatialOrder + 1, PS_TYPE_F64);
+    output->data.F64[0][0] = 1.0;
+
+    double value = 1.0;
+    for (int i = 1; i <= spatialOrder; i++) {
+        value *= x;
+        output->data.F64[0][i] = value;
+    }
+
+    value = 1.0;
+    for (int j = 1; j <= spatialOrder; j++) {
+        value *= y;
+        output->data.F64[j][0] = value;
+    }
+
+    for (int j = 1; j <= spatialOrder; j++) {
+        for (int i = 1; i <= spatialOrder - j; i++) {
+            output->data.F64[j][i] = output->data.F64[j][0] * output->data.F64[0][i];
+        }
+    }
+
+    return output;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+// Convolve a stamp by a single kernel basis function
+static psKernel *convolveStampSingle(const pmSubtractionKernels *kernels, // Kernel basis functions
+                                     int index, // Kernel basis function index
+                                     const psKernel *image, // Image to convolve (a kernel for convenience)
+                                     int footprint // Size of region of interest
+    )
+{
+#if 0
+    if (index == kernels->num) {
+        // Normalisation component
+        return convolveOffset(image, 0, 0, footprint);
+    }
+#endif
+
+    switch (kernels->type) {
+      case PM_SUBTRACTION_KERNEL_POIS: {
+          int u = kernels->u->data.S32[index]; // Offset in x
+          int v = kernels->v->data.S32[index]; // Offset in y
+          psKernel *convolved = convolveOffset(image, u, v, footprint); // Convolved image
+          convolveSub(convolved, image, footprint);
+          return convolved;
+      }
+        // Method for SPAM and FRIES is the same
+      case PM_SUBTRACTION_KERNEL_SPAM:
+      case PM_SUBTRACTION_KERNEL_FRIES: {
+          psKernel *convolved = psKernelAlloc(-footprint, footprint,
+                                              -footprint, footprint); // Convolved image
+          int uStart = kernels->u->data.S32[index];
+          int uStop = kernels->uStop->data.S32[index];
+          int vStart = kernels->v->data.S32[index];
+          int vStop = kernels->vStop->data.S32[index];
+          float norm = 1.0 / (uStop - uStart + 1) * (vStop - vStart + 1); // Normalisation
+          for (int y = -footprint; y <= footprint; y++) {
+              for (int x = -footprint; x <= footprint; x++) {
+                  double sum = 0.0;
+                  for (int v = vStart; v <= vStop; v++) {
+                      for (int u = uStart; u <= uStop; u++) {
+                          sum += image->kernel[y - v][x - u];
+                      }
+                  }
+                  convolved->kernel[y][x] = norm * sum;
+              }
+          }
+          convolveSub(convolved, image, footprint);
+          return convolved;
+      }
+      case PM_SUBTRACTION_KERNEL_GUNK: {
+          if (index < kernels->inner) {
+              // Photometric scaling is already built in to the precalculated kernel
+              return p_pmSubtractionConvolveStampPrecalc(image, kernels->preCalc->data[index]);
+          }
+          // Using delta function
+          int u = kernels->u->data.S32[index]; // Offset in x
+          int v = kernels->v->data.S32[index]; // Offset in y
+          psKernel *convolved = convolveOffset(image, u, v, footprint); // Convolved image
+          convolveSub(convolved, image, footprint);
+          return convolved;
+      }
+      case PM_SUBTRACTION_KERNEL_ISIS: {
+          // Photometric scaling is already built in to the precalculated kernel
+          return p_pmSubtractionConvolveStampPrecalc(image, kernels->preCalc->data[index]);
+      }
+      case PM_SUBTRACTION_KERNEL_RINGS: {
+          psKernel *convolved = psKernelAlloc(-footprint, footprint,
+                                              -footprint, footprint); // Convolved image
+          psArray *preCalc = kernels->preCalc->data[index]; // Precalculated data
+          psVector *uCoords = preCalc->data[0]; // u coordinates
+          psVector *vCoords = preCalc->data[1]; // v coordinates
+          psVector *poly = preCalc->data[2]; // Polynomial values
+          int num = uCoords->n;         // Number of pixels
+          for (int y = -footprint; y <= footprint; y++) {
+              for (int x = -footprint; x <= footprint; x++) {
+                  double sum = 0.0;             // Accumulated sum from convolution
+                  for (int j = 0; j < num; j++) {
+                      int u = uCoords->data.S32[j], v = vCoords->data.S32[j]; // Kernel coordinates
+                      sum += image->kernel[y - v][x - u] * poly->data.F32[j];
+                  }
+                  convolved->kernel[y][x] = sum;
+                  // Photometric scaling is built into the kernel --- no subtraction!
+              }
+          }
+          return convolved;
+      }
+      default:
+        psAbort("Should never get here.");
+    }
+    return NULL;
+}
+
+// Convolve the stamp by each of the kernel basis functions
+static psArray *convolveStamp(psArray *convolutions, // The convolutions
+                              const psKernel *image, // Image to convolve
+                              const pmSubtractionKernels *kernels, // Kernel basis functions
+                              int footprint // Stamp half-size
+    )
+{
+    assert(image);
+    assert(kernels);
+    assert(footprint >= 0);
+
+    if (convolutions) {
+        // Already done
+        return convolutions;
+    }
+
+    int numKernels = kernels->num;      // Number of kernels
+    convolutions = psArrayAlloc(numKernels);
+
+    for (int i = 0; i < numKernels; i++) {
+        convolutions->data[i] = convolveStampSingle(kernels, i, image, footprint);
+    }
+
+    return convolutions;
+}
+
+
+bool pmSubtractionConvolveStamp(pmSubtractionStamp *stamp, const pmSubtractionKernels *kernels, int footprint)
+{
+    PS_ASSERT_PTR_NON_NULL(stamp, false);
+    PM_ASSERT_SUBTRACTION_KERNELS_NON_NULL(kernels, false);
+    PS_ASSERT_INT_NONNEGATIVE(footprint, false);
+
+    if (stamp->status != PM_SUBTRACTION_STAMP_CALCULATE) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Stamp not marked for calculation.");
+        return false;
+    }
+
+    switch (kernels->mode) {
+      case PM_SUBTRACTION_MODE_1:
+        stamp->convolutions1 = convolveStamp(stamp->convolutions1, stamp->image1, kernels, footprint);
+        break;
+      case PM_SUBTRACTION_MODE_2:
+        stamp->convolutions2 = convolveStamp(stamp->convolutions2, stamp->image2, kernels, footprint);
+        break;
+      case PM_SUBTRACTION_MODE_UNSURE:
+      case PM_SUBTRACTION_MODE_DUAL:
+        stamp->convolutions1 = convolveStamp(stamp->convolutions1, stamp->image1, kernels, footprint);
+        stamp->convolutions2 = convolveStamp(stamp->convolutions2, stamp->image2, kernels, footprint);
+        break;
+      default:
+        psAbort("Unsupported subtraction mode: %x", kernels->mode);
+    }
+
+    return true;
+}
+
+
+
+
+int pmSubtractionRejectStamps(float *rmsPtr, int *numPtr, pmSubtractionStampList *stamps,
+                              const psVector *deviations, psImage *subMask, float sigmaRej,
+                              int footprint)
+{
+    PM_ASSERT_SUBTRACTION_STAMP_LIST_NON_NULL(stamps, -1);
+    PS_ASSERT_VECTOR_NON_NULL(deviations, -1);
+    PS_ASSERT_VECTOR_TYPE(deviations, PS_TYPE_F32, -1);
+    PS_ASSERT_IMAGE_NON_EMPTY(subMask, -1);
+    PS_ASSERT_IMAGE_TYPE(subMask, PS_TYPE_MASK, -1);
+
+    // I used to measure the rms deviation about zero, and use that as the sigma against which to clip, but
+    // the distribution is actually something like a chi^2 or Student's t, both of which become Gaussian-like
+    // with large N.  Therefore, let's just treat this as a Gaussian distribution.
+
+    double mean = 0.0;                  // Mean deviation
+    int numStamps = 0;                  // Number of used stamps
+    for (int i = 0; i < stamps->num; i++) {
+        pmSubtractionStamp *stamp = stamps->stamps->data[i]; // Stamp of interest
+        if (stamp->status != PM_SUBTRACTION_STAMP_USED) {
+            continue;
+        }
+        mean += deviations->data.F32[i];
+        numStamps++;
+    }
+    mean /= numStamps;
+
+    double rms = 0.0;                   // Standard deviation
+    for (int i = 0; i < stamps->num; i++) {
+        pmSubtractionStamp *stamp = stamps->stamps->data[i]; // Stamp of interest
+        if (stamp->status != PM_SUBTRACTION_STAMP_USED) {
+            continue;
+        }
+        rms += PS_SQR(deviations->data.F32[i] - mean);
+    }
+    rms = sqrt(rms / (numStamps - 1));
+
+    if (rmsPtr) {
+        *rmsPtr = rms;
+    }
+    if (numPtr) {
+        *numPtr = numStamps;
+    }
+
+    if (numStamps == 0) {
+        psError(PS_ERR_UNKNOWN, true, "No good stamps found.");
+        return -1;
+    }
+
+    if (!isfinite(sigmaRej) || sigmaRej <= 0.0) {
+        // User just wanted to calculate and record the RMS for posterity
+        psLogMsg("psModules.imcombine", PS_LOG_INFO, "RMS deviation from %d stamps: %lf",
+                 numStamps, rms);
+        return 0;
+    }
+
+    float limit = sigmaRej * rms; // Limit on maximum deviation
+    psTrace("psModules.imcombine", 1, "Deviation limit: %f\n", limit);
+
+    int numRejected = 0;                // Number of stamps rejected
+    int numGood = 0;                    // Number of good stamps
+    double newMean = 0.0;               // New mean
+    for (int i = 0; i < stamps->num; i++) {
+        pmSubtractionStamp *stamp = stamps->stamps->data[i]; // Stamp of interest
+        if (stamp->status == PM_SUBTRACTION_STAMP_USED) {
+            // Should we reject stars with low deviation?  Well, if this is really a Gaussian-like
+            // distribution and they're low, then we have the right to ask why.  Isn't it suspicious that
+            // they're anomalously low, compared to the rest of the population which (we hope) is indicative
+            // of normality?  Besides, the standard deviation is going to be blown up by stars that didn't
+            // subtract well, in which case very few (if any) stars will be legitimately rejected for being
+            // low.
+            if (deviations->data.F32[i] - mean > limit) {
+                // Mask out the stamp in the image so you it's not found again
+                psTrace("psModules.imcombine", 3, "Rejecting stamp %d (%d,%d)\n", i,
+                        (int)(stamp->x + 0.5), (int)(stamp->y + 0.5));
+                numRejected++;
+                for (int y = stamp->y - footprint; y <= stamp->y + footprint; y++) {
+                    for (int x = stamp->x - footprint; x <= stamp->x + footprint; x++) {
+                        subMask->data.PS_TYPE_MASK_DATA[y][x] |= PM_SUBTRACTION_MASK_REJ;
+                    }
+                }
+
+                // Set stamp for replacement
+                stamp->x = 0;
+                stamp->y = 0;
+                stamp->xNorm = NAN;
+                stamp->yNorm = NAN;
+                stamp->status = PM_SUBTRACTION_STAMP_REJECTED;
+                // Recalculate convolutions
+                psFree(stamp->convolutions1);
+                psFree(stamp->convolutions2);
+                stamp->convolutions1 = stamp->convolutions2 = NULL;
+                psFree(stamp->image1);
+                psFree(stamp->image2);
+                psFree(stamp->weight);
+                stamp->image1 = stamp->image2 = stamp->weight = NULL;
+                psFree(stamp->matrix1);
+                psFree(stamp->matrix2);
+                psFree(stamp->matrixX);
+                stamp->matrix1 = stamp->matrix2 = stamp->matrixX = NULL;
+                psFree(stamp->vector1);
+                psFree(stamp->vector2);
+                stamp->vector1 = stamp->vector2 = NULL;
+            } else {
+                numGood++;
+                newMean += deviations->data.F32[i];
+            }
+        }
+    }
+    newMean /= numGood;
+
+    if (numRejected > 0) {
+        psLogMsg("psModules.imcombine", PS_LOG_INFO,
+                 "%d good stamps; %d rejected.\nMean deviation: %lf --> %lf\n",
+                 numGood, numRejected, mean, newMean);
+    } else {
+        psLogMsg("psModules.imcombine", PS_LOG_INFO,
+                 "%d good stamps; 0 rejected.\nMean deviation: %lf\n",
+                 numGood, mean);
+    }
+
+    return numRejected;
+}
+
+psImage *pmSubtractionKernelImage(const pmSubtractionKernels *kernels, float x, float y, bool wantDual)
+{
+    PM_ASSERT_SUBTRACTION_KERNELS_NON_NULL(kernels, NULL);
+    PM_ASSERT_SUBTRACTION_KERNELS_SOLUTION(kernels, NULL);
+    PS_ASSERT_FLOAT_WITHIN_RANGE(x, -1.0, 1.0, NULL);
+    PS_ASSERT_FLOAT_WITHIN_RANGE(y, -1.0, 1.0, NULL);
+
+    // Precalulate polynomial values
+    psImage *polyValues = p_pmSubtractionPolynomial(NULL, kernels->spatialOrder, x, y);
+
+    // The appropriate kernel
+    psKernel *kernel = solvedKernel(NULL, kernels, polyValues, wantDual);
+
+    psFree(polyValues);
+
+    psImage *image = psMemIncrRefCounter(kernel->image); // Image of the kernel
+    psFree(kernel);
+
+    return image;
+}
+
+
+float pmSubtractionVarianceFactor(const pmSubtractionKernels *kernels, float x, float y, bool wantDual)
+{
+    PM_ASSERT_SUBTRACTION_KERNELS_NON_NULL(kernels, NAN);
+    PM_ASSERT_SUBTRACTION_KERNELS_SOLUTION(kernels, NAN);
+    PS_ASSERT_FLOAT_WITHIN_RANGE(x, -1.0, 1.0, NAN);
+    PS_ASSERT_FLOAT_WITHIN_RANGE(y, -1.0, 1.0, NAN);
+
+    // Precalulate polynomial values
+    psImage *polyValues = p_pmSubtractionPolynomial(NULL, kernels->spatialOrder, x, y);
+
+    psKernel *kernel = solvedKernel(NULL, kernels, polyValues, wantDual); // The appropriate kernel
+    psFree(polyValues);
+
+    double sumKernel2 = 0.0;            // Sum of the kernel squared
+    double sumKernel = 0.0;             // Sum of the kernel
+    for (int y = kernel->yMin; y <= kernel->yMax; y++) {
+        for (int x = kernel->xMin; x <= kernel->xMax; x++) {
+            sumKernel += kernel->kernel[y][x];
+            sumKernel2 += PS_SQR(kernel->kernel[y][x]);
+        }
+    }
+
+    psFree(kernel);
+
+    return sumKernel2 / PS_SQR(sumKernel);
+}
+
+#if 0
+psArray *pmSubtractionKernelSolutions(const pmSubtractionKernels *kernels, float x, float y, bool wantDual)
+{
+    PM_ASSERT_SUBTRACTION_KERNELS_NON_NULL(kernels, NULL);
+    PM_ASSERT_SUBTRACTION_KERNELS_SOLUTION(kernels, NULL);
+    PS_ASSERT_FLOAT_WITHIN_RANGE(x, -1.0, 1.0, NULL);
+    PS_ASSERT_FLOAT_WITHIN_RANGE(y, -1.0, 1.0, NULL);
+
+    psArray *images = psArrayAlloc(solution->n - 1); // Images of each kernel to return
+    psVector *fakeSolution = psVectorAlloc(solution->n, PS_TYPE_F64); // Fake solution vector
+    psVectorInit(fakeSolution, 0.0);
+
+    for (int i = 0; i < solution->n - 1; i++) {
+        fakeSolution->data.F64[i] = solution->data.F64[i];
+        images->data[i] = pmSubtractionKernelImage(kernels, x, y, wantDual);
+        fakeSolution->data.F64[i] = 0.0;
+    }
+
+    psFree(fakeSolution);
+
+    return images;
+}
+#endif
+
+
+
+bool pmSubtractionConvolve(pmReadout *out1, pmReadout *out2, const pmReadout *ro1, const pmReadout *ro2,
+                           const psImage *subMask, psMaskType blank, const psRegion *region,
+                           const pmSubtractionKernels *kernels, bool doBG, bool useFFT)
+{
+    int numCols = 0, numRows = 0;       // Image dimensions
+    int x0 = 0, y0 = 0;                 // Image offset
+    if (kernels->mode == PM_SUBTRACTION_MODE_1 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
+        PM_ASSERT_READOUT_NON_NULL(out1, false);
+        PM_ASSERT_READOUT_NON_NULL(ro1, false);
+        PM_ASSERT_READOUT_IMAGE(ro1, false);
+        numCols = ro1->image->numCols;
+        numRows = ro1->image->numRows;
+        x0 = ro1->col0;
+        y0 = ro1->row0;
+    }
+    if (kernels->mode == PM_SUBTRACTION_MODE_2 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
+        PM_ASSERT_READOUT_NON_NULL(out2, false);
+        PM_ASSERT_READOUT_NON_NULL(ro2, false);
+        PM_ASSERT_READOUT_IMAGE(ro2, false);
+        if (numCols == 0 && numRows == 0) {
+            numCols = ro2->image->numCols;
+            numRows = ro2->image->numRows;
+            x0 = ro2->col0;
+            y0 = ro2->row0;
+        }
+    }
+    if (kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
+        PS_ASSERT_IMAGES_SIZE_EQUAL(ro1->image, ro2->image, false);
+    }
+    PM_ASSERT_SUBTRACTION_KERNELS_NON_NULL(kernels, false);
+    PM_ASSERT_SUBTRACTION_KERNELS_SOLUTION(kernels, false);
+    if (subMask) {
+        PS_ASSERT_IMAGE_NON_NULL(subMask, false);
+        PS_ASSERT_IMAGE_TYPE(subMask, PS_TYPE_MASK, false);
+        PS_ASSERT_IMAGE_SIZE(subMask, numCols, numRows, false);
+    }
+    if (region && psRegionIsNaN(*region)) {
+        psString string = psRegionToString(*region);
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Input region (%s) contains NAN values", string);
+        psFree(string);
+        return false;
+    }
+
+    // Inputs
+    psImage *image1 = NULL, *weight1 = NULL; // Image and weight map from input 1
+    if (ro1) {
+        image1 = ro1->image;
+        weight1 = ro1->weight;
+    }
+    psImage *image2 = NULL, *weight2 = NULL; // Image and weight map from input 2
+    if (ro2) {
+        image2 = ro2->image;
+        weight2 = ro2->weight;
+    }
+
+    // Outputs
+    psImage *convImage1 = NULL, *convWeight1 = NULL; // Convolved image and weight from input 1
+    if (kernels->mode == PM_SUBTRACTION_MODE_1 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
+        convImage1 = out1->image;
+        if (!convImage1) {
+            convImage1 = out1->image = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+        }
+        if (weight1) {
+            if (!out1->weight) {
+                out1->weight = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+            }
+            convWeight1 = out1->weight;
+            psImageInit(convWeight1, 0.0);
+        }
+    }
+    psImage *convImage2 = NULL, *convWeight2 = NULL; // Convolved image and weight from input 2
+    if (kernels->mode == PM_SUBTRACTION_MODE_2 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
+        convImage2 = out2->image;
+        if (!convImage2) {
+            convImage2 = out2->image = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+        }
+        if (weight2) {
+            if (!out2->weight) {
+                out2->weight = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+            }
+            convWeight2 = out2->weight;
+            psImageInit(convWeight2, 0.0);
+        }
+    }
+    psImage *convMask = NULL;           // Convolved mask image (common to inputs 1 and 2)
+    if (subMask) {
+        if (kernels->mode == PM_SUBTRACTION_MODE_1 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
+            if (!out1->mask) {
+                out1->mask = psImageAlloc(numCols, numRows, PS_TYPE_MASK);
+            }
+            convMask = out1->mask;
+        }
+        if (kernels->mode == PM_SUBTRACTION_MODE_2 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
+            if (convMask) {
+                if (out2->mask) {
+                    psFree(out2->mask);
+                }
+                out2->mask = psMemIncrRefCounter(convMask);
+            } else {
+                if (!out2->mask) {
+                    out2->mask = psImageAlloc(numCols, numRows, PS_TYPE_MASK);
+                }
+                convMask = out2->mask;
+            }
+        }
+        psImageInit(convMask, 0);
+    }
+
+
+    int size = kernels->size;           // Half-size of kernel
+    int fullSize = 2 * size + 1;        // Full size of kernel
+
+    // Get region for convolution: [xMin:xMax,yMin:yMax]
+    int xMin = size, xMax = numCols - size;
+    int yMin = size, yMax = numRows - size;
+    if (region) {
+        xMin = PS_MAX(region->x0, xMin);
+        xMax = PS_MIN(region->x1, xMax);
+        yMin = PS_MAX(region->y0, yMin);
+        yMax = PS_MIN(region->y1, yMax);
+    }
+
+    // Size to use when calculating normalised coordinates (different from actual size when convolving
+    // subimage)
+    int xNormSize = (kernels->numCols > 0 ? kernels->numCols : numCols);
+    int yNormSize = (kernels->numRows > 0 ? kernels->numRows : numRows);
+
+    psMaskType maskSource;              // Mask these pixels when convolving
+    psMaskType maskTarget;              // Mark these pixels as bad when propagating the subtractionMask
+    switch (kernels->mode) {
+      case PM_SUBTRACTION_MODE_1:
+        maskSource = PM_SUBTRACTION_MASK_BAD_1;
+        maskTarget = PM_SUBTRACTION_MASK_BAD_2 | PM_SUBTRACTION_MASK_CONVOLVE_1;
+        break;
+      case PM_SUBTRACTION_MODE_2:
+        maskSource = PM_SUBTRACTION_MASK_BAD_2;
+        maskTarget = PM_SUBTRACTION_MASK_BAD_1 | PM_SUBTRACTION_MASK_CONVOLVE_2;
+        break;
+      case PM_SUBTRACTION_MODE_DUAL:
+        maskSource = PM_SUBTRACTION_MASK_BAD_1 | PM_SUBTRACTION_MASK_BAD_2;
+        maskTarget = PM_SUBTRACTION_MASK_CONVOLVE_1 | PM_SUBTRACTION_MASK_CONVOLVE_2;
+        break;
+      default:
+        psAbort("Unsupported subtraction mode: %x", kernels->mode);
+    }
+
+    psImage *polyValues = NULL;         // Pre-calculated polynomial values
+    psKernel *kernelImage = NULL;       // Kernel for the images
+    psKernel *kernelWeight = NULL;      // Kernel for the weight maps
+
+    for (int j = yMin; j < yMax; j += fullSize) {
+        int ySubMax = PS_MIN(j + fullSize, yMax); // Range for subregion of interest
+        float yNorm = 2.0 * (float)(j + y0 + size + 1 - yNormSize/2.0) /
+            (float)yNormSize; // Normalised coordinate
+        for (int i = xMin; i < xMax; i += fullSize) {
+            int xSubMax = PS_MIN(i + fullSize, xMax); // Range for subregion of interest
+            float xNorm = 2.0 * (float)(i + x0 + size + 1 - xNormSize/2.0) /
+                (float)xNormSize; // Normalised coordinate
+
+            // Only generate polynomial values every kernel footprint, since we have already assumed
+            // (with the stamps) that it does not vary rapidly on this scale.
+            polyValues = p_pmSubtractionPolynomial(polyValues, kernels->spatialOrder, xNorm, yNorm);
+            float background = doBG ? p_pmSubtractionSolutionBackground(kernels, polyValues) :
+                0.0; // Background term
+            psRegion subRegion = psRegionSet(i, xSubMax, j, ySubMax); // Sub-region to convolve
+
+            if (kernels->mode == PM_SUBTRACTION_MODE_1 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
+                convolveRegion(convImage1, convWeight1, &kernelImage, &kernelWeight, image1, weight1,
+                               subMask, maskSource, kernels, polyValues, background, subRegion, useFFT,
+                               false);
+            }
+            if (kernels->mode == PM_SUBTRACTION_MODE_2 || kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
+                convolveRegion(convImage2, convWeight2, &kernelImage, &kernelWeight, image2, weight2,
+                               subMask, maskSource, kernels, polyValues, background, subRegion, useFFT,
+                               kernels->mode == PM_SUBTRACTION_MODE_DUAL);
+            }
+
+            // Propagate the mask
+            if (subMask) {
+                for (int y = j; y < ySubMax; y++) {
+                    for (int x = i; x < xSubMax; x++) {
+                        if (subMask->data.PS_TYPE_MASK_DATA[y][x] & maskTarget) {
+                            convMask->data.PS_TYPE_MASK_DATA[y][x] |= blank;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    psFree(kernelImage);
+    psFree(kernelWeight);
+    psFree(polyValues);
+
+    // Copy anything that wasn't convolved
+    switch (kernels->mode) {
+      case PM_SUBTRACTION_MODE_1:
+        if (out2) {
+            out2->image = psMemIncrRefCounter(ro2->image);
+            out2->weight = psMemIncrRefCounter(ro2->weight);
+            out2->mask = psMemIncrRefCounter(ro2->mask);
+        }
+        break;
+      case PM_SUBTRACTION_MODE_2:
+        if (out1) {
+            out1->image = psMemIncrRefCounter(ro1->image);
+            out1->weight = psMemIncrRefCounter(ro1->weight);
+            out1->mask = psMemIncrRefCounter(ro1->mask);
+        }
+        break;
+      case PM_SUBTRACTION_MODE_DUAL:
+        break;
+      default:
+        psAbort("Should never get here.");
+    }
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtraction.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtraction.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtraction.h	(revision 22157)
@@ -0,0 +1,123 @@
+/* @file pmSubtraction.h
+ *
+ * PSF-matched image subtraction, based on the Alard & Lupton (1998) and Alard (2000) methods.
+ *
+ * @author Paul Price, IfA
+ * @author GLG, MHPCC
+ *
+ * @version $Revision: 1.26 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-06-19 00:54:09 $
+ * Copyright 2004-207 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_SUBTRACTION_H
+#define PM_SUBTRACTION_H
+
+#include <pslib.h>
+
+#include <pmHDU.h>
+#include <pmFPA.h>
+#include <pmSubtractionKernels.h>
+#include <pmSubtractionStamps.h>
+
+/// @addtogroup imcombine Image Combinations
+/// @{
+
+/// Mask values for the subtraction mask
+typedef enum {
+    PM_SUBTRACTION_MASK_CLEAR       = 0x00, // No masking
+    PM_SUBTRACTION_MASK_BAD_1       = 0x01, // Image 1 is bad
+    PM_SUBTRACTION_MASK_BAD_2       = 0x02, // Image 2 is bad
+    PM_SUBTRACTION_MASK_CONVOLVE_1  = 0x04, // If image 1 is convolved, would be bad
+    PM_SUBTRACTION_MASK_CONVOLVE_2  = 0x08, // If image 2 is convolved, would be bad
+    PM_SUBTRACTION_MASK_FOOTPRINT_1 = 0x10, // Bad pixel within the stamp footprint of image 1
+    PM_SUBTRACTION_MASK_FOOTPRINT_2 = 0x20, // Bad pixel within the stamp footprint of image 2
+    PM_SUBTRACTION_MASK_BORDER      = 0x40, // Image border
+    PM_SUBTRACTION_MASK_REJ         = 0x80, // Previously tried as a stamp, and rejected
+} pmSubtractionMasks;
+
+
+/// Number of terms in a polynomial
+#define PM_SUBTRACTION_POLYTERMS(ORDER) (((ORDER) + 1) * ((ORDER) + 2) / 2)
+
+/// Set the indices for the normalisation and background terms
+#define PM_SUBTRACTION_INDICES(NORM,BG,KERNELS) { \
+    int numSpatial = PM_SUBTRACTION_POLYTERMS((KERNELS)->spatialOrder); /* Number of spatial terms */ \
+    NORM = (KERNELS)->num * numSpatial; \
+    BG = NORM + 1; \
+}
+
+/// Return the index for the start of the normalisation terms
+#define PM_SUBTRACTION_INDEX_NORM(KERNELS) \
+    ((KERNELS)->num * PM_SUBTRACTION_POLYTERMS((KERNELS)->spatialOrder))
+
+/// Return the index for the start of the background terms
+#define PM_SUBTRACTION_INDEX_BG(KERNELS) \
+    (((KERNELS)->num * PM_SUBTRACTION_POLYTERMS((KERNELS)->spatialOrder)) + 1)
+
+
+/// Convolve the reference stamp with the kernel components
+bool pmSubtractionConvolveStamp(pmSubtractionStamp *stamp, ///< Stamp to convolve
+                                const pmSubtractionKernels *kernels, ///< Kernel parameters
+                                int footprint ///< Half-size of region over which to calculate equation
+    );
+
+/// Reject stamps
+int pmSubtractionRejectStamps(float *rms, ///< RMS deviation, to return
+                              int *num, ///< Number of good stamps, to return
+                              pmSubtractionStampList *stamps, ///< Stamps
+                              const psVector *deviations, ///< Deviations for each stamp
+                              psImage *subMask, ///< Subtraction mask
+                              float sigmaRej, ///< Number of RMS deviations above zero at which to reject
+                              int footprint ///< Half-size of stamp
+    );
+
+/// Generate an image of the convolution kernel
+psImage *pmSubtractionKernelImage(const pmSubtractionKernels *kernels, ///< Kernel parameters
+                                  float x, float y,///< Normalised position [-1,1] for which to generate image
+                                  bool wantDual ///< Calculate for the dual kernel?
+                                  );
+
+/// Return the variance factor for a kernel
+///
+/// The variance factor allows conversion from the large-scale variance (which is what is calculated by
+/// pmSubtractionConvolve) and the small-scale (pixel-to-pixel) variance.
+float pmSubtractionVarianceFactor(const pmSubtractionKernels *kernels, ///< Kernel parameters
+                                  float x, float y, ///< Normalised position [-1,1]
+                                  bool wantDual ///< Calculate for the dual kernel?
+    );
+
+/// Generate images of the convolution kernel elements
+psArray *pmSubtractionKernelSolutions(const psVector *solution, ///< Solution vector
+                                      const pmSubtractionKernels *kernels, ///< Kernel parameters
+                                      float x, float y ///< Normalised position [-1,1] for images
+    );
+
+/// Convolve image in preparation for subtraction
+bool pmSubtractionConvolve(pmReadout *out1, ///< Output image 1
+                           pmReadout *out2, ///< Output image 2 (DUAL mode only)
+                           const pmReadout *ro1, // Input image 1
+                           const pmReadout *ro2, // Input image 2
+                           const psImage *subMask, ///< Subtraction mask (or NULL)
+                           psMaskType blank, ///< Mask value for blank regions
+                           const psRegion *region, ///< Region to convolve (or NULL)
+                           const pmSubtractionKernels *kernels, ///< Kernel parameters
+                           bool doBG,   ///< Apply background term?
+                           bool useFFT  ///< Use Fast Fourier Transform for the convolution?
+    );
+
+/// Generate the convolution of an image, given a precalculated kernel
+///
+/// The 'image' is a kernel for convenience --- intended to be a stamp
+psKernel *p_pmSubtractionConvolveStampPrecalc(const psKernel *image, ///< Image to convolve
+                                              const psKernel *kernel ///< Kernel by which to convolve
+    );
+
+/// Given (normalised) coordinates (x,y), generate a matrix where the elements (i,j) are x^i * y^j
+psImage *p_pmSubtractionPolynomial(psImage *output, ///< Output matrix, or NULL
+                                   int spatialOrder, ///< Maximum spatial polynomial order
+                                   float x, float y ///< Normalised position of interest, [-1,1]
+    );
+
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionEquation.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionEquation.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionEquation.c	(revision 22157)
@@ -0,0 +1,1083 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmSubtraction.h"
+#include "pmSubtractionKernels.h"
+#include "pmSubtractionStamps.h"
+
+#include "pmSubtractionEquation.h"
+
+
+//#define TESTING
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Private (file-static) functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Calculate the sum over a stamp product
+static inline double calculateSumProduct(const psKernel *image1, // First image in multiplication
+                                         const psKernel *image2, // Second image in multiplication
+                                         const psKernel *weight, // Weight image
+                                         int footprint // (Half-)Size of stamp
+    )
+{
+    double sum = 0.0;                   // Sum of the image products
+    for (int y = - footprint; y <= footprint; y++) {
+        for (int x = - footprint; x <= footprint; x++) {
+            sum += image1->kernel[y][x] * image2->kernel[y][x] / 1.0; // weight->kernel[y][x];
+        }
+    }
+    return sum;
+}
+
+// Calculate a single element of the least-squares matrix, with the polynomial expansions in one direction
+static inline bool calculateMatrixElement1(psImage *matrix, // Matrix to calculate
+                                           int i, int j, // Coordinates of element
+                                           const psKernel *image1, // First image in multiplication
+                                           const psKernel *image2, // Second image in multiplication
+                                           const psKernel *weight, // Weight image
+                                           const psImage *polyValues, // Spatial polynomial values
+                                           int numKernels, // Number of kernel basis functions
+                                           int footprint, // (Half-)Size of stamp
+                                           int spatialOrder, // Maximum order of spatial variation
+                                           bool symmetric // Is the matrix symmetric?
+    )
+{
+    double sum = calculateSumProduct(image1, image2, weight, footprint); // Sum of the image products
+    if (!isfinite(sum)) {
+        return false;
+    }
+
+    // Generate the pseudo-convolutions from the spatial polynomial terms
+    for (int iyOrder = 0, iIndex = i; iyOrder <= spatialOrder; iyOrder++) {
+        for (int ixOrder = 0; ixOrder <= spatialOrder - iyOrder; ixOrder++, iIndex += numKernels) {
+            double convPoly = sum * polyValues->data.F64[iyOrder][ixOrder];
+
+            assert(iIndex < matrix->numRows && j < matrix->numCols);
+
+            matrix->data.F64[iIndex][j] = convPoly;
+            if (symmetric) {
+
+                assert(iIndex < matrix->numCols && j < matrix->numRows);
+
+                matrix->data.F64[j][iIndex] = convPoly;
+            }
+        }
+    }
+    return true;
+}
+
+// Calculate a single element of the least-squares matrix, with the polynomial expansions in both directions
+static inline bool calculateMatrixElement2(psImage *matrix, // Matrix to calculate
+                                           int i, int j, // Coordinates of element
+                                           const psKernel *image1, // First image in multiplication
+                                           const psKernel *image2, // Second image in multiplication
+                                           const psKernel *weight, // Weight image
+                                           const psImage *polyValues, // Spatial polynomial values
+                                           int numKernels, // Number of kernel basis functions
+                                           int footprint, // (Half-)Size of stamp
+                                           int spatialOrder, // Maximum order of spatial variation
+                                           bool symmetric // Is the matrix symmetric?
+    )
+{
+    double sum = calculateSumProduct(image1, image2, weight, footprint); // Sum of the image products
+    if (!isfinite(sum)) {
+        return false;
+    }
+
+    // Generate the pseudo-convolutions from the spatial polynomial terms
+    for (int iyOrder = 0, iIndex = i; iyOrder <= spatialOrder; iyOrder++) {
+        for (int ixOrder = 0; ixOrder <= spatialOrder - iyOrder; ixOrder++, iIndex += numKernels) {
+            double iPoly = polyValues->data.F64[iyOrder][ixOrder]; // Value of polynomial
+            for (int jyOrder = 0, jIndex = j; jyOrder <= spatialOrder; jyOrder++) {
+                for (int jxOrder = 0; jxOrder <= spatialOrder - jyOrder; jxOrder++, jIndex += numKernels) {
+                    double convPoly = sum * iPoly * polyValues->data.F64[jyOrder][jxOrder];
+
+                    assert(iIndex < matrix->numRows && jIndex < matrix->numCols);
+
+                    matrix->data.F64[iIndex][jIndex] = convPoly;
+                    if (symmetric) {
+
+                        assert(iIndex < matrix->numCols && jIndex < matrix->numRows);
+
+                        matrix->data.F64[jIndex][iIndex] = convPoly;
+                    }
+                }
+            }
+        }
+    }
+    return true;
+}
+
+// Calculate the square part of the matrix derived from multiplying convolutions
+static bool calculateMatrixSquare(psImage *matrix, // Matrix to calculate
+                                  const psArray *convolutions1, // Convolutions for element 1
+                                  const psArray *convolutions2, // Convolutions for element 2
+                                  const psKernel *weight, // Weight image
+                                  const psImage *polyValues, // Polynomial values
+                                  int numKernels, // Number of kernel basis functions
+                                  int spatialOrder, // Order of spatial variation
+                                  int footprint // Half-size of stamp
+                                  )
+{
+    bool symmetric = (convolutions1 == convolutions2 ? true : false); // Is matrix symmetric?
+
+    for (int i = 0; i < numKernels; i++) {
+        psKernel *iConv = convolutions1->data[i]; // Convolution for i-th element
+
+        for (int j = (symmetric ? i : 0); j < numKernels; j++) {
+            psKernel *jConv = convolutions2->data[j]; // Convolution for j-th element
+
+            if (!calculateMatrixElement2(matrix, i, j, iConv, jConv, weight, polyValues, numKernels,
+                                         footprint, spatialOrder, symmetric)) {
+                psTrace("psModules.imcombine", 2, "Bad sumCC at %d, %d", i, j);
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
+// Calculate least-squares matrix and vector
+static bool calculateMatrix(psImage *matrix, // Matrix to calculate
+                            const pmSubtractionKernels *kernels, // Kernel components
+                            const psArray *convolutions, // Convolutions of source with kernels
+                            const psKernel *input, // Input stamp, or NULL
+                            const psKernel *weight, // Weight stamp
+                            const psImage *polyValues, // Spatial polynomial values
+                            int footprint, // (Half-)Size of stamp
+                            bool normAndBG // Calculate normalisation and background terms?
+    )
+{
+    int numKernels = kernels->num;      // Number of kernel components
+    int spatialOrder = kernels->spatialOrder; // Maximum order of spatial variation
+    int numSpatial = PM_SUBTRACTION_POLYTERMS(spatialOrder); // Number of spatial variation terms
+    int bgOrder = kernels->bgOrder;     // Maximum order of background fit
+    int numBackground = normAndBG ? PM_SUBTRACTION_POLYTERMS(bgOrder) : 0; // Number of background terms
+    int numTerms = numKernels * numSpatial + (normAndBG ? 1 + numBackground : 0); // Total number of terms
+    assert(matrix);
+    assert(matrix->numCols == matrix->numRows);
+    assert(matrix->numCols == numTerms);
+    assert(convolutions && convolutions->n == numKernels);
+    assert(polyValues);
+    assert(!normAndBG || input);        // If we want the normalisation and BG, then we need the input image
+
+    // Square part of the matrix (convolution-convolution products)
+    if (!calculateMatrixSquare(matrix, convolutions, convolutions, weight, polyValues, numKernels,
+                               spatialOrder, footprint)) {
+        return false;
+    }
+
+    // XXX To support higher-order background model than simply constant, the below code needs to be updated.
+    if (normAndBG) {
+        int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation
+        int bgIndex = PM_SUBTRACTION_INDEX_BG(kernels); // Index in matrix for background
+
+        for (int i = 0; i < numKernels; i++) {
+            psKernel *conv = convolutions->data[i]; // Convolution for i-th element
+
+            // Normalisation-convolution terms
+            if (!calculateMatrixElement1(matrix, i, normIndex, conv, input, weight, polyValues, numKernels,
+                                         footprint, spatialOrder, true)) {
+                psTrace("psModules.imcombine", 2, "Bad sumIC at %d", i);
+                return false;
+            }
+
+            // Background-convolution terms
+            double sumC = 0.0;          // Sum of the convolution
+            for (int y = - footprint; y <= footprint; y++) {
+                for (int x = - footprint; x <= footprint; x++) {
+                    sumC += conv->kernel[y][x] / 1.0; // weight->kernel[y][x];
+                }
+            }
+            if (!isfinite(sumC)) {
+                psTrace("psModules.imcombine", 2, "Bad sumC at %d", i);
+                return false;
+            }
+
+            for (int yOrder = 0, index = i; yOrder <= spatialOrder; yOrder++) {
+                for (int xOrder = 0; xOrder <= spatialOrder - yOrder; xOrder++, index += numKernels) {
+                    double value = sumC * polyValues->data.F64[yOrder][xOrder];
+                    matrix->data.F64[index][bgIndex] = value;
+                    matrix->data.F64[bgIndex][index] = value;
+                }
+            }
+        }
+
+        // Background only, normalisation only, and background-normalisation terms
+        double sum1 = 0.0;              // Sum of the weighting
+        double sumI = 0.0;              // Sum of the input
+        double sumII = 0.0;             // Sum of the input squared
+        for (int y = - footprint; y <= footprint; y++) {
+            for (int x = - footprint; x <= footprint; x++) {
+                double invNoise2 = 1.0 / 1.0; // weight->kernel[y][x];
+                double value = input->kernel[y][x] * invNoise2;
+                sumI += value;
+                sumII += value * input->kernel[y][x];
+                sum1 += invNoise2;
+            }
+        }
+        if (!isfinite(sumI)) {
+            psTrace("psModules.imcombine", 2, "Bad sumI detected");
+            return false;
+        }
+        if (!isfinite(sumII)) {
+            psTrace("psModules.imcombine", 2, "Bad sumII detected");
+            return false;
+        }
+        if (!isfinite(sum1)) {
+            psTrace("psModules.imcombine", 2, "Bad sum1 detected");
+            return false;
+        }
+        matrix->data.F64[normIndex][normIndex] = sumII;
+        matrix->data.F64[bgIndex][bgIndex] = sum1;
+        matrix->data.F64[normIndex][bgIndex] = sumI;
+        matrix->data.F64[bgIndex][normIndex] = sumI;
+    }
+
+    return true;
+}
+
+
+// Calculate least-squares matrix and vector
+static bool calculateVector(psVector *vector, // Vector to calculate, or NULL
+                            const pmSubtractionKernels *kernels, // Kernel components
+                            const psArray *convolutions, // Convolutions of source with kernels
+                            const psKernel *input, // Input stamp, or NULL if !normAndBG
+                            const psKernel *target, // Target stamp
+                            const psKernel *weight, // Weight stamp
+                            const psImage *polyValues, // Spatial polynomial values
+                            int footprint, // (Half-)Size of stamp
+                            bool normAndBG // Calculate normalisation and background terms?
+    )
+{
+    int numKernels = kernels->num;      // Number of kernel components
+    int spatialOrder = kernels->spatialOrder; // Maximum order of spatial variation
+    int numSpatial = PM_SUBTRACTION_POLYTERMS(spatialOrder); // Number of spatial variation terms
+    int bgOrder = kernels->bgOrder;     // Maximum order of background fit
+    int numBackground = normAndBG ? PM_SUBTRACTION_POLYTERMS(bgOrder) : 0; // Number of background terms
+    int numTerms = numKernels * numSpatial + (normAndBG ? 1 + numBackground : 0); // Total number of terms
+    assert(vector && vector->n == numTerms);
+    assert(convolutions && convolutions->n == numKernels);
+    assert(target);
+    assert(polyValues);
+    assert(!normAndBG || input);       // If we want the normalisation and BG, then we need the input image
+
+    // Convolution terms
+    for (int i = 0; i < numKernels; i++) {
+        psKernel *conv = convolutions->data[i]; // Convolution for i-th element
+        double sumTC = 0.0;          // Sum of the target and convolution
+        for (int y = - footprint; y <= footprint; y++) {
+            for (int x = - footprint; x <= footprint; x++) {
+                sumTC += target->kernel[y][x] * conv->kernel[y][x] / 1.0; // weight->kernel[y][x];
+            }
+        }
+        if (!isfinite(sumTC)) {
+            psTrace("psModules.imcombine", 2, "Bad sumTC at %d", i);
+            return false;
+        }
+        for (int yOrder = 0, index = i; yOrder <= spatialOrder; yOrder++) {
+            for (int xOrder = 0; xOrder <= spatialOrder - yOrder; xOrder++, index += numKernels) {
+                vector->data.F64[index] = sumTC * polyValues->data.F64[yOrder][xOrder];
+            }
+        }
+    }
+
+    if (normAndBG) {
+        // Background terms
+        double sumT = 0.0;              // Sum of the target
+        double sumIT = 0.0;             // Sum of the input-target product
+        for (int y = - footprint; y <= footprint; y++) {
+            for (int x = - footprint; x <= footprint; x++) {
+                float value = target->kernel[y][x] / 1.0; // weight->kernel[y][x];
+                sumIT += value * input->kernel[y][x];
+                sumT += value;
+            }
+        }
+        if (!isfinite(sumT)) {
+            psTrace("psModules.imcombine", 2, "Bad sumI detected");
+            return false;
+        }
+        if (!isfinite(sumIT)) {
+            psTrace("psModules.imcombine", 2, "Bad sumIT detected");
+            return false;
+        }
+
+        int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation term
+        vector->data.F64[normIndex] = sumIT;
+        int bgIndex = PM_SUBTRACTION_INDEX_BG(kernels); // Index for background term
+        vector->data.F64[bgIndex] = sumT;
+    }
+
+    return true;
+}
+
+
+
+// Calculate the cross-matrix, composed of convolutions of each image
+// Note that the cross-matrix is NOT square
+static bool calculateMatrixCross(psImage *matrix, // Matrix to calculate
+                                 const pmSubtractionKernels *kernels, // Kernel components
+                                 const psArray *convolutions1, // Convolutions of image 1
+                                 const psArray *convolutions2, // Convolutions of image 2
+                                 const psKernel *image1, // Image 1 stamp
+                                 const psKernel *weight, // Weight stamp
+                                 const psImage *polyValues, // Spatial polynomial values
+                                 int footprint // (Half-)Size of stamp
+                                 )
+{
+    assert(matrix);
+    int numKernels = kernels->num;      // Number of kernel components
+    int spatialOrder = kernels->spatialOrder; // Maximum order of spatial variation
+    int numSpatial = PM_SUBTRACTION_POLYTERMS(spatialOrder); // Number of spatial polynomial terms
+    int numBackground = PM_SUBTRACTION_POLYTERMS(kernels->bgOrder); // Number of background terms
+    int numCols = numKernels * numSpatial + 1 + numBackground; // Number of columns
+    int numRows = numKernels * numSpatial; // Number of rows
+    assert(matrix->numCols == numCols && matrix->numRows == numRows);
+    assert(convolutions1 && convolutions1->n == numKernels);
+    assert(convolutions2 && convolutions2->n == numKernels);
+
+    int normIndex, bgIndex;             // Indices in matrix for normalisation and background terms
+    PM_SUBTRACTION_INDICES(normIndex, bgIndex, kernels);
+
+    if (!calculateMatrixSquare(matrix, convolutions1, convolutions2, weight, polyValues, numKernels,
+                               spatialOrder, footprint)) {
+        return false;
+    }
+
+    for (int i = 0; i < numKernels; i++) {
+        // Normalisation
+        psKernel *conv = convolutions2->data[i]; // Convolution
+        if (!calculateMatrixElement1(matrix, i, normIndex, conv, image1, weight, polyValues, numKernels,
+                                     footprint, spatialOrder, false)) {
+            psTrace("psModules.imcombine", 2, "Bad sumIC at %d", i);
+            return false;
+        }
+
+        // Background
+        double sumC = 0.0;              // Sum of the weighting
+        for (int y = - footprint; y <= footprint; y++) {
+            for (int x = - footprint; x <= footprint; x++) {
+                sumC += conv->kernel[y][x] / 1.0; // weight->kernel[y][x];
+            }
+        }
+        if (!isfinite(sumC)) {
+            psTrace("psModules.imcombine", 2, "Bad sumC detected at %d", i);
+            return false;
+        }
+        for (int yOrder = 0, index = i; yOrder <= spatialOrder; yOrder++) {
+            for (int xOrder = 0; xOrder <= spatialOrder - yOrder; xOrder++, index += numKernels) {
+                matrix->data.F64[index][bgIndex] = sumC * polyValues->data.F64[yOrder][xOrder];
+            }
+        }
+    }
+
+    return true;
+}
+
+
+// Add in penalty term to least-squares vector
+static bool calculatePenalty(psVector *vector, // Vector to which to add in penalty term
+                             const pmSubtractionKernels *kernels // Kernel parameters
+    )
+{
+    if (kernels->penalty == 0.0) {
+        return true;
+    }
+
+    psVector *penalties = kernels->penalties; // Penalties for each kernel component
+    int spatialOrder = kernels->spatialOrder; // Order of spatial variations
+    int numKernels = kernels->num; // Number of kernel components
+    for (int i = 0; i < numKernels; i++) {
+        for (int yOrder = 0, index = i; yOrder <= spatialOrder; yOrder++) {
+            for (int xOrder = 0; xOrder <= spatialOrder - yOrder; xOrder++, index += numKernels) {
+                vector->data.F64[index] -= penalties->data.F32[i];
+            }
+        }
+    }
+
+    return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Semi-public functions
+// XXX We might like to define these functions as "extern inline" but gcc currently doesn't handle this in c99
+// mode.  See http://gcc.gnu.org/ml/gcc/2006-11/msg00006.html
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Calculate the value of a polynomial, specified by coefficients and polynomial values
+double p_pmSubtractionCalculatePolynomial(const psVector *coeff, // Coefficients
+                                                 const psImage *polyValues, // Polynomial values
+                                                 int order, // Order of polynomials
+                                                 int index, // Index at which to begin
+                                                 int step // Step between subsequent indices
+                                                 )
+{
+    double sum = 0.0;                   // Value of the polynomial sum
+    for (int yOrder = 0; yOrder <= order; yOrder++) {
+        for (int xOrder = 0; xOrder <= order - yOrder; xOrder++, index += step) {
+
+            assert(index < coeff->n);
+
+            sum += coeff->data.F64[index] * polyValues->data.F64[yOrder][xOrder];
+        }
+    }
+    return sum;
+}
+
+double p_pmSubtractionSolutionCoeff(const pmSubtractionKernels *kernels, const psImage *polyValues,
+                                           int index, bool wantDual)
+{
+#if 0
+    // This is probably in a tight loop, so don't check inputs
+    PM_ASSERT_SUBTRACTION_KERNELS_NON_NULL(kernels, NAN);
+    PM_ASSERT_SUBTRACTION_KERNELS_SOLUTION(kernels, NAN);
+    PS_ASSERT_IMAGE_NON_NULL(polyValues, NAN);
+    PS_ASSERT_INT_POSITIVE(index, NAN);
+#endif
+
+    psVector *solution = wantDual ? kernels->solution2 : kernels->solution1; // Solution vector
+    return p_pmSubtractionCalculatePolynomial(solution, polyValues, kernels->spatialOrder, index,
+                                              kernels->num);
+}
+
+double p_pmSubtractionSolutionNorm(const pmSubtractionKernels *kernels)
+{
+#if 0
+    // This is probably in a tight loop, so don't check inputs
+    PM_ASSERT_SUBTRACTION_KERNELS_NON_NULL(kernels, NAN);
+    PM_ASSERT_SUBTRACTION_KERNELS_SOLUTION(kernels, NAN);
+    PS_ASSERT_IMAGE_NON_NULL(polyValues, NAN);
+#endif
+
+    int normIndex = PM_SUBTRACTION_INDEX_NORM(kernels); // Index for normalisation
+    return kernels->solution1->data.F64[normIndex];
+}
+
+double p_pmSubtractionSolutionBackground(const pmSubtractionKernels *kernels,
+                                                const psImage *polyValues)
+{
+#if 0
+    // This is probably in a tight loop, so don't check inputs
+    PM_ASSERT_SUBTRACTION_KERNELS_NON_NULL(kernels, NAN);
+    PM_ASSERT_SUBTRACTION_KERNELS_SOLUTION(kernels, NAN);
+    PS_ASSERT_IMAGE_NON_NULL(polyValues, NAN);
+#endif
+
+    int bgIndex = PM_SUBTRACTION_INDEX_BG(kernels); // Index for background
+    return p_pmSubtractionCalculatePolynomial(kernels->solution1, polyValues, kernels->bgOrder, bgIndex, 1);
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmSubtractionCalculateEquation(pmSubtractionStampList *stamps, const pmSubtractionKernels *kernels)
+{
+    PM_ASSERT_SUBTRACTION_STAMP_LIST_NON_NULL(stamps, false);
+    PM_ASSERT_SUBTRACTION_KERNELS_NON_NULL(kernels, false);
+
+    int footprint = stamps->footprint;  // Half-size of stamps
+    int spatialOrder = kernels->spatialOrder; // Maximum order of spatial variation
+    int numKernels = kernels->num;      // Number of kernel basis functions
+    int numSpatial = PM_SUBTRACTION_POLYTERMS(spatialOrder); // Number of spatial variations
+    int numBackground = PM_SUBTRACTION_POLYTERMS(kernels->bgOrder); // Number of background terms
+
+    // Total number of parameters to solve for: coefficient of each kernel basis function, multipled by the
+    // number of coefficients for the spatial polynomial, normalisation and a constant background offset.
+    int numParams = numKernels * numSpatial + 1 + numBackground;
+
+    psImage *polyValues = NULL;         // Polynomial terms
+
+    // We iterate over each stamp, allocate the matrix and vectors if
+    // necessary, and then calculate those matrix/vectors.
+    for (int i = 0; i < stamps->num; i++) {
+        pmSubtractionStamp *stamp = stamps->stamps->data[i]; // Stamp of interest
+        if (stamp->status != PM_SUBTRACTION_STAMP_CALCULATE) {
+            continue;
+        }
+
+        // Generate convolutions
+        if (!pmSubtractionConvolveStamp(stamp, kernels, footprint)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to convolve stamp %d.", i);
+            psFree(polyValues);
+            return NULL;
+        }
+
+#ifdef TESTING
+        for (int j = 0; j < numKernels; j++) {
+            if (stamp->convolutions1) {
+                psString convName = NULL;
+                psStringAppend(&convName, "conv1_%03d_%03d.fits", i, j);
+                psFits *fits = psFitsOpen(convName, "w");
+                psFree(convName);
+                psKernel *conv = stamp->convolutions1->data[j];
+                psFitsWriteImage(fits, NULL, conv->image, 0, NULL);
+                psFitsClose(fits);
+            }
+
+            if (stamp->convolutions2) {
+                psString convName = NULL;
+                psStringAppend(&convName, "conv2_%03d_%03d.fits", i, j);
+                psFits *fits = psFitsOpen(convName, "w");
+                psFree(convName);
+                psKernel *conv = stamp->convolutions2->data[j];
+                psFitsWriteImage(fits, NULL, conv->image, 0, NULL);
+                psFitsClose(fits);
+            }
+        }
+#endif
+
+        polyValues = p_pmSubtractionPolynomial(polyValues, spatialOrder, stamp->xNorm, stamp->yNorm);
+
+
+            stamp->matrix1 = psImageAlloc(numParams, numParams, PS_TYPE_F64);
+            stamp->vector1 = psVectorAlloc(numParams, PS_TYPE_F64);
+#ifdef TESTING
+            psImageInit(stamp->matrix1, NAN);
+            psVectorInit(stamp->vector1, NAN);
+#endif
+
+        bool status;                    // Status of least-squares matrix/vector calculation
+        switch (kernels->mode) {
+          case PM_SUBTRACTION_MODE_1:
+            status = calculateMatrix(stamp->matrix1, kernels, stamp->convolutions1, stamp->image1,
+                                     stamp->weight, polyValues, footprint, true);
+            status &= calculateVector(stamp->vector1, kernels, stamp->convolutions1, stamp->image1,
+                                      stamp->image2, stamp->weight, polyValues, footprint, true);
+            break;
+          case PM_SUBTRACTION_MODE_2:
+            status = calculateMatrix(stamp->matrix1, kernels, stamp->convolutions2, stamp->image2,
+                                     stamp->weight, polyValues, footprint, true);
+            status &= calculateVector(stamp->vector1, kernels, stamp->convolutions2, stamp->image2,
+                                      stamp->image1, stamp->weight, polyValues, footprint, true);
+            break;
+          case PM_SUBTRACTION_MODE_DUAL:
+            stamp->matrix2 = psImageAlloc(numKernels * numSpatial, numKernels * numSpatial, PS_TYPE_F64);
+            stamp->matrixX = psImageAlloc(numParams, numKernels * numSpatial, PS_TYPE_F64);
+            stamp->vector2 = psVectorAlloc(numKernels * numSpatial, PS_TYPE_F64);
+#ifdef TESTING
+            psImageInit(stamp->matrix2, NAN);
+            psImageInit(stamp->matrixX, NAN);
+            psVectorInit(stamp->vector2, NAN);
+#endif
+            status  = calculateMatrix(stamp->matrix1, kernels, stamp->convolutions1, stamp->image1,
+                                      stamp->weight, polyValues, footprint, true);
+            status &= calculateMatrix(stamp->matrix2, kernels, stamp->convolutions2, NULL,
+                                      stamp->weight, polyValues, footprint, false);
+            status &= calculateMatrixCross(stamp->matrixX, kernels, stamp->convolutions1,
+                                           stamp->convolutions2, stamp->image1, stamp->weight, polyValues,
+                                           footprint);
+            status &= calculateVector(stamp->vector1, kernels, stamp->convolutions1, stamp->image1,
+                                      stamp->image2, stamp->weight, polyValues, footprint, true);
+            status &= calculateVector(stamp->vector2, kernels, stamp->convolutions2, NULL,
+                                      stamp->image2, stamp->weight, polyValues, footprint, false);
+            break;
+          default:
+            psAbort("Unsupported subtraction mode: %x", kernels->mode);
+        }
+
+        if (!status) {
+            stamp->status = PM_SUBTRACTION_STAMP_REJECTED;
+            psWarning("Rejecting stamp %d (%d,%d) because of bad equation",
+                      i, (int)(stamp->x + 0.5), (int)(stamp->y + 0.5));
+        } else {
+            stamp->status = PM_SUBTRACTION_STAMP_USED;
+        }
+
+#ifdef TESTING
+        if (psTraceGetLevel("psModules.imcombine.equation") >= 10) {
+            psString matrixName = NULL;
+            psStringAppend(&matrixName, "matrix1_%d.fits", i);
+            psFits *matrixFile = psFitsOpen(matrixName, "w");
+            psFree(matrixName);
+            psFitsWriteImage(matrixFile, NULL, stamp->matrix1, 0, NULL);
+            psFitsClose(matrixFile);
+
+            matrixName = NULL;
+            psStringAppend(&matrixName, "vector1_%d.fits", i);
+            psImage *dummy = psImageAlloc(stamp->vector1->n, 1, PS_TYPE_F64);
+            memcpy(dummy->data.F64[0], stamp->vector1->data.F64,
+                   PSELEMTYPE_SIZEOF(PS_TYPE_F64) * stamp->vector1->n);
+            matrixFile = psFitsOpen(matrixName, "w");
+            psFree(matrixName);
+            psFitsWriteImage(matrixFile, NULL, dummy, 0, NULL);
+            psFree(dummy);
+            psFitsClose(matrixFile);
+
+            if (stamp->vector2) {
+                matrixName = NULL;
+                psStringAppend(&matrixName, "vector2_%d.fits", i);
+                dummy = psImageAlloc(stamp->vector2->n, 1, PS_TYPE_F64);
+                memcpy(dummy->data.F64[0], stamp->vector2->data.F64,
+                       PSELEMTYPE_SIZEOF(PS_TYPE_F64) * stamp->vector2->n);
+                matrixFile = psFitsOpen(matrixName, "w");
+                psFree(matrixName);
+                psFitsWriteImage(matrixFile, NULL, dummy, 0, NULL);
+                psFree(dummy);
+                psFitsClose(matrixFile);
+            }
+
+            if (kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
+                matrixName = NULL;
+                psStringAppend(&matrixName, "matrix2_%d.fits", i);
+                matrixFile = psFitsOpen(matrixName, "w");
+                psFree(matrixName);
+                psFitsWriteImage(matrixFile, NULL, stamp->matrix2, 0, NULL);
+                psFitsClose(matrixFile);
+
+                matrixName = NULL;
+                psStringAppend(&matrixName, "matrixX_%d.fits", i);
+                matrixFile = psFitsOpen(matrixName, "w");
+                psFree(matrixName);
+                psFitsWriteImage(matrixFile, NULL, stamp->matrixX, 0, NULL);
+                psFitsClose(matrixFile);
+            }
+        }
+#endif
+
+    }
+    psFree(polyValues);
+
+    return true;
+}
+
+bool pmSubtractionSolveEquation(pmSubtractionKernels *kernels, const pmSubtractionStampList *stamps)
+{
+    PM_ASSERT_SUBTRACTION_KERNELS_NON_NULL(kernels, false);
+    PM_ASSERT_SUBTRACTION_STAMP_LIST_NON_NULL(stamps, false);
+
+    // Check inputs
+    int numParams = -1;                // Number of parameters
+    int numParams2 = 0;                // Number of parameters for part solution (DUAL mode)
+    for (int i = 0; i < stamps->num; i++) {
+        pmSubtractionStamp *stamp = stamps->stamps->data[i]; // Stamp of interest
+        PS_ASSERT_PTR_NON_NULL(stamp, false);
+        if (stamp->status != PM_SUBTRACTION_STAMP_USED) {
+            continue;
+        }
+
+        PS_ASSERT_VECTOR_NON_NULL(stamp->vector1, false);
+        if (numParams == -1) {
+            numParams = stamp->vector1->n;
+        }
+        PS_ASSERT_VECTOR_SIZE(stamp->vector1, (long)numParams, false);
+        PS_ASSERT_VECTOR_TYPE(stamp->vector1, PS_TYPE_F64, false);
+        PS_ASSERT_IMAGE_NON_NULL(stamp->matrix1, false);
+        PS_ASSERT_IMAGE_SIZE(stamp->matrix1, numParams, numParams, false);
+        PS_ASSERT_IMAGE_TYPE(stamp->matrix1, PS_TYPE_F64, false);
+
+        if (kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
+            PS_ASSERT_IMAGE_NON_NULL(stamp->matrix2, false);
+            PS_ASSERT_IMAGE_NON_NULL(stamp->matrixX, false);
+            if (numParams2 == 0) {
+                numParams2 = stamp->matrix2->numCols;
+            }
+            PS_ASSERT_IMAGE_SIZE(stamp->matrix2, numParams2, numParams2, false);
+            PS_ASSERT_IMAGE_SIZE(stamp->matrixX, numParams, numParams2, false);
+            PS_ASSERT_IMAGE_TYPE(stamp->matrix2, PS_TYPE_F64, false);
+            PS_ASSERT_IMAGE_TYPE(stamp->matrixX, PS_TYPE_F64, false);
+            PS_ASSERT_VECTOR_NON_NULL(stamp->vector2, false);
+            PS_ASSERT_VECTOR_SIZE(stamp->vector2, (long)numParams2, false);
+            PS_ASSERT_VECTOR_TYPE(stamp->vector2, PS_TYPE_F64, false);
+        }
+    }
+    if (numParams == -1) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "No suitable stamps found.");
+        return NULL;
+    }
+
+    if (kernels->mode != PM_SUBTRACTION_MODE_DUAL) {
+        // Accumulate the least-squares matricies and vectors
+        psImage *sumMatrix = psImageAlloc(numParams, numParams, PS_TYPE_F64); // Combined matrix
+        psVector *sumVector = psVectorAlloc(numParams, PS_TYPE_F64); // Combined vector
+        psVectorInit(sumVector, 0.0);
+        psImageInit(sumMatrix, 0.0);
+        int numStamps = 0;              // Number of good stamps
+        for (int i = 0; i < stamps->num; i++) {
+            pmSubtractionStamp *stamp = stamps->stamps->data[i]; // Stamp of interest
+            if (stamp->status == PM_SUBTRACTION_STAMP_USED) {
+                (void)psBinaryOp(sumMatrix, sumMatrix, "+", stamp->matrix1);
+                (void)psBinaryOp(sumVector, sumVector, "+", stamp->vector1);
+                numStamps++;
+            }
+        }
+        calculatePenalty(sumVector, kernels);
+
+        psVector *permutation = NULL;       // Permutation vector, required for LU decomposition
+        psImage *luMatrix = psMatrixLUD(NULL, &permutation, sumMatrix);
+        psFree(sumMatrix);
+        if (!luMatrix) {
+            psError(PS_ERR_UNKNOWN, true, "LU Decomposition of least-squares matrix failed.\n");
+            psFree(sumVector);
+            psFree(luMatrix);
+            psFree(permutation);
+            return NULL;
+        }
+        kernels->solution1 = psMatrixLUSolve(kernels->solution1, luMatrix, sumVector, permutation);
+        psFree(sumVector);
+        psFree(luMatrix);
+        psFree(permutation);
+        if (!kernels->solution1) {
+            psError(PS_ERR_UNKNOWN, true, "Failed to solve the least-squares system.\n");
+            return NULL;
+        }
+    } else {
+        // Dual convolution solution
+
+        // Accumulation of stamp matrices/vectors
+        psImage *sumMatrix1 = psImageAlloc(numParams, numParams, PS_TYPE_F64);
+        psImage *sumMatrix2 = psImageAlloc(numParams2, numParams2, PS_TYPE_F64);
+        psImage *sumMatrixX = psImageAlloc(numParams, numParams2, PS_TYPE_F64);
+        psVector *sumVector1 = psVectorAlloc(numParams, PS_TYPE_F64);
+        psVector *sumVector2 = psVectorAlloc(numParams, PS_TYPE_F64);
+        psImageInit(sumMatrix1, 0.0);
+        psImageInit(sumMatrix2, 0.0);
+        psImageInit(sumMatrixX, 0.0);
+        psVectorInit(sumVector1, 0.0);
+        psVectorInit(sumVector2, 0.0);
+
+        int numStamps = 0;              // Number of good stamps
+        for (int i = 0; i < stamps->num; i++) {
+            pmSubtractionStamp *stamp = stamps->stamps->data[i]; // Stamp of interest
+            if (stamp->status == PM_SUBTRACTION_STAMP_USED) {
+                (void)psBinaryOp(sumMatrix1, sumMatrix1, "+", stamp->matrix1);
+                (void)psBinaryOp(sumMatrix2, sumMatrix2, "+", stamp->matrix2);
+                (void)psBinaryOp(sumMatrixX, sumMatrixX, "+", stamp->matrixX);
+                (void)psBinaryOp(sumVector1, sumVector1, "+", stamp->vector1);
+                (void)psBinaryOp(sumVector2, sumVector2, "+", stamp->vector2);
+                numStamps++;
+            }
+        }
+        calculatePenalty(sumVector1, kernels);
+        calculatePenalty(sumVector2, kernels);
+
+#if 0
+        // Apply weighting to maximise the difference between solution coefficients for the same functions
+        double fudge = PS_SQR(2 * stamps->footprint + 1); // Fudge factor
+        for (int i = 0; i < kernels->num; i++) {
+            sumMatrix1->data.F64[i][i] -= fudge;
+            sumMatrix2->data.F64[i][i] += fudge;
+        }
+#endif
+
+        // Pure matrix operations
+
+        // A * a = Ct * b + d
+        // C * a = B  * b + e
+        //
+        // a = (Ct * Bi * C - A)i (Ct * Bi * e - d)
+        // b = Bi * (C * a - e)
+        psVector *a = psVectorRecycle(kernels->solution1, numParams, PS_TYPE_F64);
+        psVector *b = psVectorRecycle(kernels->solution2, numParams2, PS_TYPE_F64);
+#ifdef TESTING
+        psVectorInit(a, NAN);
+        psVectorInit(b, NAN);
+#endif
+        psImage *A = sumMatrix1;
+        psImage *B = sumMatrix2;
+        psImage *C = sumMatrixX;
+        psVector *d = sumVector1;
+        psVector *e = sumVector2;
+
+        assert(a->n == numParams);
+        assert(b->n == numParams2);
+        assert(A->numRows == numParams && A->numCols == numParams);
+        assert(B->numRows == numParams2 && B->numCols == numParams2);
+        assert(C->numRows == numParams2 && C->numCols == numParams);
+        assert(d->n == numParams);
+        assert(e->n == numParams2);
+
+        psImage *Bi = psMatrixInvert(NULL, B, NULL);
+        assert(Bi->numRows == numParams2 && Bi->numCols == numParams2);
+        psImage *Ct = psMatrixTranspose(NULL, C);
+        assert(Ct->numRows == numParams && Ct->numCols == numParams2);
+
+        psImage *BiC = psMatrixMultiply(NULL, Bi, C);
+        assert(BiC->numRows == numParams2 && BiC->numCols == numParams);
+        psImage *CtBi = psMatrixMultiply(NULL, Ct, Bi);
+        assert(CtBi->numRows == numParams && CtBi->numCols == numParams2);
+
+        psImage *CtBiC = psMatrixMultiply(NULL, Ct, BiC);
+        assert(CtBiC->numRows == numParams && CtBiC->numCols == numParams);
+
+        psImage *F = (psImage*)psBinaryOp(NULL, CtBiC, "-", A);
+        assert(F->numRows == numParams && F->numCols == numParams);
+        float det = 0.0;
+        psImage *Fi = psMatrixInvert(NULL, F, &det);
+        assert(Fi->numRows == numParams && Fi->numCols == numParams);
+        psTrace("psModules.imcombine", 4, "Determinant of F: %f\n", det);
+
+        psVector *g = psVectorAlloc(numParams, PS_TYPE_F64);
+#ifdef TESTING
+        psVectorInit(g, NAN);
+#endif
+        assert(CtBi->numRows == numParams && CtBi->numCols == numParams2);
+        assert(e->n == numParams2);
+        assert(d->n == numParams);
+        for (int i = 0; i < numParams; i++) {
+            double value = 0.0;
+            for (int j = 0; j < numParams2; j++) {
+                value += CtBi->data.F64[i][j] * e->data.F64[j];
+            }
+            g->data.F64[i] = value - d->data.F64[i];
+        }
+
+        assert(Fi->numRows == numParams && Fi->numCols == numParams);
+        assert(g->n == numParams);
+        for (int i = 0; i < numParams; i++) {
+            double value = 0.0;
+            for (int j = 0; j < numParams; j++) {
+                value += Fi->data.F64[i][j] * g->data.F64[j];
+            }
+            a->data.F64[i] = value;
+        }
+
+        psVector *h = psVectorAlloc(numParams2, PS_TYPE_F64);
+#ifdef TESTING
+        psVectorInit(h, NAN);
+#endif
+        assert(C->numRows == numParams2 && C->numCols == numParams);
+        assert(a->n == numParams);
+        assert(e->n == numParams2);
+        for (int i = 0; i < numParams2; i++) {
+            double value = 0.0;
+            for (int j = 0; j < numParams; j++) {
+                value += C->data.F64[i][j] * a->data.F64[j];
+            }
+            h->data.F64[i] = value - e->data.F64[i];
+        }
+
+        assert(Bi->numRows == numParams2 && Bi->numCols == numParams2);
+        assert(h->n == numParams2);
+        for (int i = 0; i < numParams2; i++) {
+            double value = 0.0;
+            for (int j = 0; j < numParams2; j++) {
+                value += Bi->data.F64[i][j] * h->data.F64[j];
+            }
+            b->data.F64[i] = value;
+        }
+
+
+#if 0
+        for (int i = 0; i < numParams; i++) {
+            double aVal1 = 0.0, bVal1 = 0.0;
+            for (int j = 0; j < numParams2; j++) {
+                aVal1 += A->data.F64[i][j] * a->data.F64[j];
+                bVal1 += Ct->data.F64[i][j] * b->data.F64[j];
+            }
+            bVal1 += d->data.F64[i];
+            for (int j = numParams2; j < numParams; j++) {
+                aVal1 += A->data.F64[i][j] * a->data.F64[j];
+            }
+            printf("%d: %lf\n", i, aVal1 - bVal1);
+        }
+
+        for (int i = 0; i < numParams2; i++) {
+            double aVal2 = 0.0, bVal2 = 0.0;
+            for (int j = 0; j < numParams2; j++) {
+                aVal2 += C->data.F64[i][j] * a->data.F64[j];
+                bVal2 += B->data.F64[i][j] * b->data.F64[j];
+            }
+            bVal2 += e->data.F64[i];
+            for (int j = numParams2; j < numParams; j++) {
+                aVal2 += C->data.F64[i][j] * a->data.F64[j];
+            }
+            printf("%d: %lf\n", i, aVal2 - bVal2);
+        }
+#endif
+
+        {
+            psFits *fits = psFitsOpen("sumMatrix1.fits", "w");
+            psFitsWriteImage(fits, NULL, sumMatrix1, 0, NULL);
+            psFitsClose(fits);
+        }
+        {
+            psFits *fits = psFitsOpen("sumMatrix2.fits", "w");
+            psFitsWriteImage(fits, NULL, sumMatrix2, 0, NULL);
+            psFitsClose(fits);
+        }
+        {
+            psFits *fits = psFitsOpen("sumMatrixX.fits", "w");
+            psFitsWriteImage(fits, NULL, sumMatrixX, 0, NULL);
+            psFitsClose(fits);
+        }
+        {
+            psFits *fits = psFitsOpen("sumFinverse.fits", "w");
+            psFitsWriteImage(fits, NULL, Fi, 0, NULL);
+            psFitsClose(fits);
+        }
+
+
+        kernels->solution1 = a;
+        kernels->solution2 = b;
+
+        // XXXXX Free temporary matrices and vectors
+
+    }
+
+    if (psTraceGetLevel("psModules.imcombine") >= 7) {
+        for (int i = 0; i < kernels->solution1->n; i++) {
+            psTrace("psModules.imcombine", 7, "Solution 1 %d: %f\n", i, kernels->solution1->data.F64[i]);
+        }
+        if (kernels->mode == PM_SUBTRACTION_MODE_DUAL) {
+            for (int i = 0; i < kernels->solution2->n; i++) {
+                psTrace("psModules.imcombine", 7, "Solution 2 %d: %f\n", i, kernels->solution2->data.F64[i]);
+            }
+        }
+     }
+
+    return true;
+}
+
+psVector *pmSubtractionCalculateDeviations(pmSubtractionStampList *stamps,
+                                           const pmSubtractionKernels *kernels)
+{
+    PM_ASSERT_SUBTRACTION_STAMP_LIST_NON_NULL(stamps, NULL);
+    PM_ASSERT_SUBTRACTION_KERNELS_NON_NULL(kernels, NULL);
+    PM_ASSERT_SUBTRACTION_KERNELS_SOLUTION(kernels, NULL);
+
+    psVector *deviations = psVectorAlloc(stamps->num, PS_TYPE_F32); // Mean deviation for stamps
+    int footprint = stamps->footprint; // Half-size of stamps
+    long numPixels = PS_SQR(2 * footprint + 1); // Number of pixels in footprint
+    double devNorm = 1.0 / (double)numPixels; // Normalisation for deviations
+    int numKernels = kernels->num;      // Number of kernels
+
+    psImage *polyValues = NULL;         // Polynomial values
+    psKernel *residual = psKernelAlloc(-footprint, footprint, -footprint, footprint); // Residual image
+
+    for (int i = 0; i < stamps->num; i++) {
+        pmSubtractionStamp *stamp = stamps->stamps->data[i]; // The stamp of interest
+        if (stamp->status != PM_SUBTRACTION_STAMP_USED) {
+            deviations->data.F32[i] = NAN;
+            continue;
+        }
+
+        // Calculate coefficients of the kernel basis functions
+        polyValues = p_pmSubtractionPolynomial(polyValues, kernels->spatialOrder, stamp->xNorm, stamp->yNorm);
+        double norm = p_pmSubtractionSolutionNorm(kernels); // Normalisation
+        double background = p_pmSubtractionSolutionBackground(kernels, polyValues);// Difference in background
+
+        // Calculate residuals
+        psKernel *weight = stamp->weight; // Weight postage stamp
+        psImageInit(residual->image, 0.0);
+        if (kernels->mode != PM_SUBTRACTION_MODE_DUAL) {
+            psKernel *target;           // Target postage stamp
+            psKernel *source;           // Source postage stamp
+            psArray *convolutions;      // Convolution postage stamps for each kernel basis function
+            switch (kernels->mode) {
+              case PM_SUBTRACTION_MODE_1:
+                target = stamp->image2;
+                source = stamp->image1;
+                convolutions = stamp->convolutions1;
+                break;
+              case PM_SUBTRACTION_MODE_2:
+                target = stamp->image1;
+                source = stamp->image2;
+                convolutions = stamp->convolutions2;
+                break;
+              default:
+                psAbort("Unsupported subtraction mode: %x", kernels->mode);
+            }
+
+            for (int j = 0; j < numKernels; j++) {
+                psKernel *convolution = convolutions->data[j]; // Convolution
+                double coefficient = p_pmSubtractionSolutionCoeff(kernels, polyValues, j,
+                                                                  false); // Coefficient
+                for (int y = - footprint; y <= footprint; y++) {
+                    for (int x = - footprint; x <= footprint; x++) {
+                        residual->kernel[y][x] -= convolution->kernel[y][x] * coefficient;
+                    }
+                }
+            }
+            for (int y = - footprint; y <= footprint; y++) {
+                for (int x = - footprint; x <= footprint; x++) {
+                    residual->kernel[y][x] += target->kernel[y][x] - background - source->kernel[y][x] * norm;
+                }
+            }
+        } else {
+            // Dual convolution
+            psArray *convolutions1 = stamp->convolutions1; // Convolutions of the first image
+            psArray *convolutions2 = stamp->convolutions2; // Convolutions of the second image
+            psKernel *image1 = stamp->image1; // The first image
+            psKernel *image2 = stamp->image2; // The second image
+
+            for (int j = 0; j < numKernels; j++) {
+                psKernel *conv1 = convolutions1->data[j]; // Convolution of first image
+                psKernel *conv2 = convolutions2->data[j]; // Convolution of second image
+                double coeff1 = p_pmSubtractionSolutionCoeff(kernels, polyValues, j, false); // Coefficient 1
+                double coeff2 = p_pmSubtractionSolutionCoeff(kernels, polyValues, j, true); // Coefficient 2
+
+                for (int y = - footprint; y <= footprint; y++) {
+                    for (int x = - footprint; x <= footprint; x++) {
+                        residual->kernel[y][x] += conv2->kernel[y][x] * coeff2 - conv1->kernel[y][x] * coeff1;
+                    }
+                }
+            }
+            for (int y = - footprint; y <= footprint; y++) {
+                for (int x = - footprint; x <= footprint; x++) {
+                    residual->kernel[y][x] += image2->kernel[y][x] - background - image1->kernel[y][x] * norm;
+                }
+            }
+        }
+
+        double deviation = 0.0;         // Sum of differences
+        for (int y = - footprint; y <= footprint; y++) {
+            for (int x = - footprint; x <= footprint; x++) {
+                double dev = PS_SQR(residual->kernel[y][x]) / weight->kernel[y][x];
+                deviation += dev;
+#ifdef TESTING
+                residual->kernel[y][x] = dev;
+#endif
+            }
+        }
+        deviations->data.F32[i] = sqrtf(devNorm * deviation);
+        psTrace("psModules.imcombine", 5, "Deviation for stamp %d (%d,%d): %f\n",
+                i, (int)(stamp->x + 0.5), (int)(stamp->y + 0.5), deviations->data.F32[i]);
+        if (!isfinite(deviations->data.F32[i])) {
+            stamp->status = PM_SUBTRACTION_STAMP_REJECTED;
+            psTrace("psModules.imcombine", 5,
+                    "Rejecting stamp %d (%d,%d) because of non-finite deviation\n",
+                    i, (int)(stamp->x + 0.5), (int)(stamp->y + 0.5));
+            continue;
+        }
+
+#ifdef TESTING
+        {
+            psString filename = NULL;
+            psStringAppend(&filename, "resid_%03d.fits", i);
+            psFits *fits = psFitsOpen(filename, "w");
+            psFree(filename);
+            psFitsWriteImage(fits, NULL, residual->image, 0, NULL);
+            psFitsClose(fits);
+        }
+        {
+            psString filename = NULL;
+            psStringAppend(&filename, "stamp_image_%03d.fits", i);
+            psFits *fits = psFitsOpen(filename, "w");
+            psFree(filename);
+            psFitsWriteImage(fits, NULL, stamp->image1->image, 0, NULL);
+            psFitsClose(fits);
+        }
+        {
+            psString filename = NULL;
+            psStringAppend(&filename, "stamp_weight_%03d.fits", i);
+            psFits *fits = psFitsOpen(filename, "w");
+            psFree(filename);
+            psFitsWriteImage(fits, NULL, stamp->weight->image, 0, NULL);
+            psFitsClose(fits);
+        }
+#endif
+
+    }
+    psFree(residual);
+    psFree(polyValues);
+
+    return deviations;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionEquation.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionEquation.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionEquation.h	(revision 22157)
@@ -0,0 +1,46 @@
+#ifndef PM_SUBTRACTION_EQUATION_H
+#define PM_SUBTRACTION_EQUATION_H
+
+#include "pmSubtractionStamps.h"
+#include "pmSubtractionKernels.h"
+
+/// Calculate the least-squares equation to match the image quality
+bool pmSubtractionCalculateEquation(pmSubtractionStampList *stamps, ///< Stamps
+                                    const pmSubtractionKernels *kernels ///< Kernel parameters
+                                    );
+
+/// Solve the least-squares equation to match the image quality
+bool pmSubtractionSolveEquation(pmSubtractionKernels *kernels, ///< Kernel parameters
+                                const pmSubtractionStampList *stamps ///< Stamps
+    );
+
+/// Calculate deviations
+psVector *pmSubtractionCalculateDeviations(pmSubtractionStampList *stamps, ///< Stamps
+                                           const pmSubtractionKernels *kernels ///< Kernel parameters
+    );
+
+/// Calculate the value of a polynomial, specified by coefficients and polynomial values
+double p_pmSubtractionCalculatePolynomial(const psVector *coeff, ///< Coefficients
+                                          const psImage *polyValues, ///< Polynomial values
+                                          int order, ///< Order of polynomials
+                                          int index, ///< Index at which to begin
+                                          int step ///< Step between subsequent indices
+    );
+
+/// Return the specified coefficient in the solution
+double p_pmSubtractionSolutionCoeff(const pmSubtractionKernels *kernels, ///< Kernel parameters
+                                    const psImage *polyValues, ///< Polynomial values
+                                    int index, ///< Coefficient index to calculate
+                                    bool wantDual ///< Calculate the coefficient for the dual solution?
+    );
+
+/// Return the normalisation in the solution
+double p_pmSubtractionSolutionNorm(const pmSubtractionKernels *kernels ///< Kernel parameters
+    );
+
+/// Return the background (difference) in the solution
+double p_pmSubtractionSolutionBackground(const pmSubtractionKernels *kernels, ///< Kernel parameters
+                                         const psImage *polyValues ///< Polynomial values
+    );
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionIO.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionIO.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionIO.c	(revision 22157)
@@ -0,0 +1,284 @@
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+
+#include "pmSubtraction.h"
+#include "pmSubtractionKernels.h"
+#include "pmSubtractionMatch.h"
+
+#define ARRAY_BUFFER 16                 // Number to add to array at a time
+
+// Names of FITS table columns
+#define NAME_XMIN "XMIN"                // Region of applicability: minimum x value
+#define NAME_XMAX "XMAX"                // Region of applicability: maximum x value
+#define NAME_YMIN "YMIN"                // Region of applicability: minimum y value
+#define NAME_YMAX "YMAX"                // Region of applicability: maximum y value
+#define NAME_KERNEL "KERNEL"            // Kernel description
+#define NAME_TYPE "TYPE"                // Kernel type
+#define NAME_SIZE "SIZE"                // Kernel half-size
+#define NAME_INNER "INNER"              // Size of inner region (only applicable for some kernel types)
+#define NAME_SPATIAL "SPATIAL_ORDER"    // Order of spatial polynomial
+#define NAME_BG "BG_ORDER"              // Order of background polynomial
+#define NAME_MODE "MODE"                // Matching mode
+#define NAME_COLS "COLUMNS"             // Number of columns
+#define NAME_ROWS "ROWS"                // Number of rows
+#define NAME_SOL1 "SOLUTION_1"          // Solution for convolving image 1
+#define NAME_SOL2 "SOLUTION_2"          // Solution for convolving image 2
+
+
+// Generate an extension name for the subtraction kernel
+static psString subtractionKernelExtname(const pmReadout *ro // Readout containing kernel
+                                         )
+{
+    psAssert(ro, "Required parameter");
+
+    psString extname = NULL;            // Name of FITS extension
+    pmCell *cell = ro->parent;     // Parent cell
+    if (cell && cell->parent) {
+        pmChip *chip = cell->parent;        // Parent chip
+        const char *chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME"); // Name of chip
+        const char *cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME"); // Name of cell
+        psAssert(chipName && cellName, "Chip and cell names should be defined in concepts.");
+        int roNum = -1;                 // Index of readout
+        for (int i = 0; i < cell->readouts->n && roNum == -1; i++) {
+            if (cell->readouts->data[i] == ro) {
+                roNum = i;
+            }
+        }
+        if (roNum != -1) {
+            psStringAppend(&extname, "%s_%s_%d", chipName, cellName, roNum);
+        } else {
+            psWarning("Unable to find readout number");
+        }
+    }
+
+    if (!extname) {
+        extname = psStringCopy("SUBTRACTION_KERNEL");
+    }
+
+    return extname;
+}
+
+bool pmReadoutWriteSubtractionKernels(pmReadout *ro, psFits *fits)
+{
+    PM_ASSERT_READOUT_NON_NULL(ro, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+
+    // Extract the regions and solutions used in the image matching
+    psArray *regions = psArrayAllocEmpty(ARRAY_BUFFER); // Array of regions
+    {
+        psString regex = NULL;          // Regular expression
+        psStringAppend(&regex, "^%s$", PM_SUBTRACTION_ANALYSIS_REGION);
+        psMetadataIterator *iter = psMetadataIteratorAlloc(ro->analysis, PS_LIST_HEAD, regex); // Iterator
+        psFree(regex);
+        psMetadataItem *item = NULL;// Item from iteration
+        while ((item = psMetadataGetAndIncrement(iter))) {
+            assert(item->type == PS_DATA_REGION);
+            regions = psArrayAdd(regions, ARRAY_BUFFER, item->data.V);
+        }
+        psFree(iter);
+    }
+    if (regions->n == 0) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "No subtraction regions found.");
+        psFree(regions);
+        return false;
+    }
+
+    psArray *kernels = psArrayAllocEmpty(ARRAY_BUFFER); // Array of kernels
+    {
+        psString regex = NULL;          // Regular expression
+        psStringAppend(&regex, "^%s$", PM_SUBTRACTION_ANALYSIS_KERNEL);
+        psMetadataIterator *iter = psMetadataIteratorAlloc(ro->analysis, PS_LIST_HEAD, regex); // Iterator
+        psFree(regex);
+        psMetadataItem *item = NULL;// Item from iteration
+        while ((item = psMetadataGetAndIncrement(iter))) {
+            assert(item->type == PS_DATA_UNKNOWN);
+            // Set the normalisation dimensions, since these will be otherwise unavailable when reading the
+            // images by scans.
+            pmSubtractionKernels *kernel = item->data.V; // Kernel used in subtraction
+            kernel->numCols = ro->image->numCols;
+            kernel->numRows = ro->image->numRows;
+
+            kernels = psArrayAdd(kernels, ARRAY_BUFFER, kernel);
+        }
+        psFree(iter);
+    }
+
+    if (regions->n != kernels->n) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Number of regions (%ld) and kernels (%ld) don't match.\n",
+                regions->n, kernels->n);
+        psFree(regions);
+        psFree(kernels);
+        return false;
+    }
+
+    // Format for writing a table
+    int num = regions->n;              // Number of regions and kernels
+    psArray *rows = psArrayAlloc(num); // Array of FITS table rows
+    for (int i = 0; i < num; i++) {
+        psMetadata *row = psMetadataAlloc(); // Row of interest
+        rows->data[i] = psMemIncrRefCounter(row);
+
+        psRegion *region = regions->data[i]; // Region of interest
+        psMetadataAddS32(row, PS_LIST_TAIL, NAME_XMIN,  0, "Applicability minimum x", region->x0);
+        psMetadataAddS32(row, PS_LIST_TAIL, NAME_XMAX,  0, "Applicability maximum x", region->x1);
+        psMetadataAddS32(row, PS_LIST_TAIL, NAME_YMIN,  0, "Applicability minimum y", region->y0);
+        psMetadataAddS32(row, PS_LIST_TAIL, NAME_YMAX,  0, "Applicability maximum y", region->y1);
+
+        pmSubtractionKernels *kernel = kernels->data[i]; // Kernel
+        psMetadataAddStr(row, PS_LIST_TAIL, NAME_KERNEL, 0, "Kernel description", kernel->description);
+
+#if 0
+        psMetadataAddS32(row, PS_LIST_TAIL, NAME_TYPE,  0, "Kernel type (enum)", kernel->type);
+        psMetadataAddS32(row, PS_LIST_TAIL, NAME_SIZE,  0, "Kernel half-size", kernel->size);
+        psMetadataAddS32(row, PS_LIST_TAIL, NAME_INNER, 0, "Size of inner region", kernel->inner);
+        psMetadataAddS32(row, PS_LIST_TAIL, NAME_SPATIAL, 0, "Polynomial order for spatial variations",
+                         kernel->spatialOrder);
+#endif
+
+        psMetadataAddS32(row, PS_LIST_TAIL, NAME_BG,  0, "Polynomial order for background fitting",
+                         kernel->bgOrder);
+        psMetadataAddS32(row, PS_LIST_TAIL, NAME_MODE,  0, "Matching mode (enum)", kernel->mode);
+        psMetadataAddS32(row, PS_LIST_TAIL, NAME_COLS,  0, "Number of columns", kernel->numCols);
+        psMetadataAddS32(row, PS_LIST_TAIL, NAME_ROWS,  0, "Number of rows", kernel->numRows);
+        if (kernel->mode == PM_SUBTRACTION_MODE_1 || kernel->mode == PM_SUBTRACTION_MODE_DUAL) {
+            psMetadataAddVector(row, PS_LIST_TAIL, NAME_SOL1, 0, "Solution vector 1", kernel->solution1);
+        }
+        if (kernel->mode == PM_SUBTRACTION_MODE_2 || kernel->mode == PM_SUBTRACTION_MODE_DUAL) {
+            psMetadataAddVector(row, PS_LIST_TAIL, NAME_SOL2, 0, "Solution vector 2", kernel->solution2);
+        }
+    }
+    psFree(regions);
+    psFree(kernels);
+
+    psMetadata *header = psMetadataAlloc(); // Header for FITS file
+
+    // CVS tags, used to identify the version of this file (in case incompatibilities are introduced)
+    psString cvsFile = psStringCopy("$RCSfile: pmSubtractionIO.c,v $");
+    psString cvsRev  = psStringCopy("$Revision: 1.1 $");
+    psString cvsDate = psStringCopy("$Date: 2008-06-16 21:59:43 $");
+    psStringSubstitute(&cvsFile, NULL, "RCSfile: ");
+    psStringSubstitute(&cvsRev,  NULL, "Revision: ");
+    psStringSubstitute(&cvsDate, NULL, "Date: ");
+
+    psString version = NULL;            // Version information, for header
+    psStringAppend(&version, "%s %s %s", cvsFile, cvsRev, cvsDate);
+    psFree(cvsFile);
+    psFree(cvsRev);
+    psFree(cvsDate);
+    psStringSubstitute(&version, NULL, "$");
+    psMetadataAddStr(header, PS_LIST_TAIL, "PSVERSION", 0, "S/W version", version);
+    psFree(version);
+
+    psString extname = subtractionKernelExtname(ro); // Extension name
+
+    if (!psFitsWriteTable(fits, header, rows, extname)) {
+        psError(PS_ERR_IO, false, "Unable to write subtraction kernel to FITS table.");
+        psFree(header);
+        psFree(rows);
+        psFree(extname);
+        return false;
+    }
+
+    psFree(header);
+    psFree(rows);
+    psFree(extname);
+
+    return true;
+}
+
+
+bool pmReadoutReadSubtractionKernels(pmReadout *ro, psFits *fits)
+{
+    PM_ASSERT_READOUT_NON_NULL(ro, false);
+    PS_ASSERT_FITS_NON_NULL(fits, false);
+
+    psString extname = subtractionKernelExtname(ro); // Extension name
+    if (!psFitsMoveExtName(fits, extname)) {
+        psError(PS_ERR_IO, false, "Unable to move to subtraction kernel table.");
+        psFree(extname);
+        return false;
+    }
+    psFree(extname);
+
+    psArray *table = psFitsReadTable(fits); // Table of interest
+    if (!table) {
+        psError(PS_ERR_IO, false, "Unable to read FITS table");
+        return false;
+    }
+
+    // Look up a column value for a row
+#define TABLE_LOOKUP(TYPE, SUFFIX, TARGET, NAME) \
+    TYPE TARGET; \
+    { \
+        bool mdok; \
+        TARGET = psMetadataLookup##SUFFIX(&mdok, row, NAME); \
+        if (!mdok) { \
+            psError(PS_ERR_UNKNOWN, false, "Unable to find column %s in subtraction kernel table.", NAME); \
+            psFree(table); \
+            return false; \
+        } \
+    }
+
+    for (int i = 0; i < table->n; i++) {
+        psMetadata *row = table->data[i]; // Table row
+
+        TABLE_LOOKUP(int, S32, xMin, NAME_XMIN);
+        TABLE_LOOKUP(int, S32, xMax, NAME_XMAX);
+        TABLE_LOOKUP(int, S32, yMin, NAME_YMIN);
+        TABLE_LOOKUP(int, S32, yMax, NAME_YMAX);
+
+        psRegion *region = psRegionAlloc(xMin, xMax, yMin, yMax); // Region of applicability
+        psMetadataAddPtr(ro->analysis, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_REGION, PS_DATA_REGION,
+                         "Subtraction region", region);
+        psFree(region);
+
+        TABLE_LOOKUP(const char *, Str, description, NAME_KERNEL);
+        TABLE_LOOKUP(pmSubtractionMode, S32, mode,    NAME_MODE);
+
+#if 0
+        TABLE_LOOKUP(int, S32, size,    NAME_SIZE);
+        TABLE_LOOKUP(int, S32, inner,   NAME_INNER);
+        TABLE_LOOKUP(int, S32, spatial, NAME_SPATIAL);
+#endif
+
+        TABLE_LOOKUP(int, S32, bg,      NAME_BG);
+        TABLE_LOOKUP(int, S32, numCols, NAME_COLS);
+        TABLE_LOOKUP(int, S32, numRows, NAME_ROWS);
+
+        pmSubtractionKernels *kernels = pmSubtractionKernelsFromDescription(description, bg, mode);
+        kernels->numCols = numCols;
+        kernels->numRows = numRows;
+
+        bool mdok;                      // Status of MD lookup
+        if (mode == PM_SUBTRACTION_MODE_1 || mode == PM_SUBTRACTION_MODE_DUAL) {
+            kernels->solution1 = psMemIncrRefCounter(psMetadataLookupPtr(&mdok, row, NAME_SOL1));
+            if (!mdok) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to find column %s in subtraction kernel table.",
+                        NAME_SOL1);
+                psFree(kernels);
+                psFree(table);
+                return false;
+            }
+        }
+        if (mode == PM_SUBTRACTION_MODE_2 || mode == PM_SUBTRACTION_MODE_DUAL) {
+            kernels->solution2 = psMemIncrRefCounter(psMetadataLookupPtr(&mdok, row, NAME_SOL2));
+            if (!mdok) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to find column %s in subtraction kernel table.",
+                        NAME_SOL2);
+                psFree(kernels);
+                psFree(table);
+                return false;
+            }
+        }
+
+        psMetadataAddPtr(ro->analysis, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_KERNEL, PS_DATA_UNKNOWN,
+                         "Subtraction kernels", kernels);
+        psFree(kernels);
+    }
+    psFree(table);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionIO.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionIO.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionIO.h	(revision 22157)
@@ -0,0 +1,22 @@
+#ifndef PM_SUBTRACTION_IO_H
+#define PM_SUBTRACTION_IO_H
+
+#include <pslib.h>
+
+#include <pmHDU.h>
+#include <pmFPA.h>
+
+/// Write subtraction kernels within a readout to a FITS file
+bool pmReadoutWriteSubtractionKernels(
+    pmReadout *readout,                 ///< Readout for which to write subtraction kernels (in analysis MD)
+    psFits *fits                        ///< FITS file to which to write
+    );
+
+
+bool pmReadoutReadSubtractionKernels(
+    pmReadout *readout,                 ///< Readout for which to read subtraction kernels (into analysis MD)
+    psFits *fits                        ///< FITS file to which to write
+    );
+
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionKernels.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionKernels.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionKernels.c	(revision 22157)
@@ -0,0 +1,759 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <pslib.h>
+
+#include "pmSubtraction.h"
+#include "pmSubtractionKernels.h"
+
+#define RINGS_BUFFER 10                 // Buffer size for RINGS data
+
+
+// Free function for pmSubtractionKernels
+static void subtractionKernelsFree(pmSubtractionKernels *kernels)
+{
+    psFree(kernels->description);
+    psFree(kernels->u);
+    psFree(kernels->v);
+    psFree(kernels->widths);
+    psFree(kernels->uStop);
+    psFree(kernels->vStop);
+    psFree(kernels->preCalc);
+    psFree(kernels->penalties);
+    psFree(kernels->solution1);
+    psFree(kernels->solution2);
+}
+
+// Raise an integer to an integer power
+static inline long power(int value,     // Value
+                         int exp        // Exponent
+    )
+{
+    if (exp == 0) {
+        return 1.0;
+    }
+    long result = value;               // Result to return
+    for (int i = 2; i <= exp; i++) {
+        result *= value;
+    }
+    return result;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Semi-public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool p_pmSubtractionKernelsAddGrid(pmSubtractionKernels *kernels, int start, int size)
+{
+    PM_ASSERT_SUBTRACTION_KERNELS_NON_NULL(kernels, false);
+    PS_ASSERT_INT_NONNEGATIVE(start, false);
+    PS_ASSERT_INT_NONNEGATIVE(size, false);
+
+    int numNew = PS_SQR(2 * size + 1) - 1;  // Number of new kernel parameters to add
+
+    // Ensure the sizes match
+    kernels->widths = psVectorRealloc(kernels->widths, start + numNew);
+    kernels->u = psVectorRealloc(kernels->u, start + numNew);
+    kernels->v = psVectorRealloc(kernels->v, start + numNew);
+    kernels->preCalc = psArrayRealloc(kernels->preCalc, start + numNew);
+    kernels->penalties = psVectorRealloc(kernels->penalties, start + numNew);
+    kernels->inner = start;
+
+    // Generate a set of kernels for each (u,v)
+    for (int v = - size, index = start; v <= size; v++) {
+        for (int u = - size; u <= size; u++, index++) {
+            if (v == 0 && u == 0) {
+                // Skip normalisation component: added explicitly
+                index--;
+                continue;
+            }
+            kernels->widths->data.F32[index] = NAN;
+            kernels->u->data.S32[index] = u;
+            kernels->v->data.S32[index] = v;
+            kernels->preCalc->data[index] = NULL;
+            kernels->penalties->data.F32[index] = kernels->penalty * (PS_SQR(u) + PS_SQR(v));
+
+            psTrace("psModules.imcombine", 7, "Kernel %d: %d %d\n", index, u, v);
+        }
+    }
+
+    return true;
+}
+
+pmSubtractionKernels *p_pmSubtractionKernelsRawISIS(int size, int spatialOrder,
+                                                    const psVector *fwhms, const psVector *orders,
+                                                    float penalty, pmSubtractionMode mode)
+{
+    PS_ASSERT_VECTOR_NON_NULL(fwhms, NULL);
+    PS_ASSERT_VECTOR_TYPE(fwhms, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(orders, NULL);
+    PS_ASSERT_VECTOR_TYPE(orders, PS_TYPE_S32, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(fwhms, orders, NULL);
+    PS_ASSERT_INT_POSITIVE(size, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(spatialOrder, NULL);
+
+    int numGaussians = fwhms->n;       // Number of Gaussians
+
+    int num = 0;                        // Number of basis functions
+    psString params = NULL;             // List of parameters
+    for (int i = 0; i < numGaussians; i++) {
+        int gaussOrder = orders->data.S32[i]; // Polynomial order to apply to Gaussian
+        psStringAppend(&params, "(%.1f,%d)", fwhms->data.F32[i], orders->data.S32[i]);
+        num += (gaussOrder + 1) * (gaussOrder + 2) / 2;
+    }
+
+    pmSubtractionKernels *kernels = pmSubtractionKernelsAlloc(num, PM_SUBTRACTION_KERNEL_ISIS, size,
+                                                              spatialOrder, penalty, mode); // The kernels
+    psStringAppend(&kernels->description, "ISIS(%d,%s,%d,%.2e)", size, params, spatialOrder, penalty);
+
+    psLogMsg("psModules.imcombine", PS_LOG_INFO, "ISIS kernel: %s,%d --> %d elements",
+             params, spatialOrder, num);
+    psFree(params);
+
+    // Set the kernel parameters
+    for (int i = 0, index = 0; i < numGaussians; i++) {
+        float sigma = fwhms->data.F32[i] / (2.0 * sqrtf(2.0 * logf(2.0))); // Gaussian sigma
+        float norm = 1.0 / (M_2_PI * sqrtf(sigma)); // Normalisation for Gaussian
+        // Iterate over (u,v) order
+        for (int uOrder = 0; uOrder <= orders->data.S32[i]; uOrder++) {
+            for (int vOrder = 0; vOrder <= orders->data.S32[i] - uOrder; vOrder++, index++) {
+                // Set the pre-calculated kernel
+                psKernel *preCalc = psKernelAlloc(-size, size, -size, size);
+                double sum = 0.0;       // Normalisation
+                double moment = 0.0;    // Moment, for penalty
+                for (int v = -size; v <= size; v++) {
+                    for (int u = -size; u <= size; u++) {
+                        sum += preCalc->kernel[v][u] = norm * power(u, uOrder) * power(v, vOrder) *
+                            expf(-0.5 * (PS_SQR(u) + PS_SQR(v)) / PS_SQR(sigma));
+                        moment += preCalc->kernel[v][u] * (PS_SQR(u) + PS_SQR(v));
+                    }
+                }
+
+                // Normalise sum of kernel component to unity for even functions
+                if (uOrder % 2 == 0 && vOrder % 2 == 0) {
+                    for (int v = -size; v <= size; v++) {
+                        for (int u = -size; u <= size; u++) {
+                            preCalc->kernel[v][u] = preCalc->kernel[v][u] / sum;
+                        }
+                    }
+                    preCalc->kernel[0][0] -= 1.0;
+                }
+
+                kernels->widths->data.F32[index] = fwhms->data.F32[i];
+                kernels->u->data.S32[index] = uOrder;
+                kernels->v->data.S32[index] = vOrder;
+                if (kernels->preCalc->data[index]) {
+                    psFree(kernels->preCalc->data[index]);
+                }
+                kernels->preCalc->data[index] = preCalc;
+                kernels->penalties->data.F32[index] = kernels->penalty * fabsf(moment);
+
+                psTrace("psModules.imcombine", 7, "Kernel %d: %f %d %d %f\n", index,
+                        fwhms->data.F32[i], uOrder, vOrder, fabsf(moment));
+            }
+        }
+    }
+
+    return kernels;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+pmSubtractionKernels *pmSubtractionKernelsAlloc(int numBasisFunctions, pmSubtractionKernelsType type,
+                                                int size, int spatialOrder, float penalty,
+                                                pmSubtractionMode mode)
+{
+    pmSubtractionKernels *kernels = psAlloc(sizeof(pmSubtractionKernels)); // Kernels, to return
+    psMemSetDeallocator(kernels, (psFreeFunc)subtractionKernelsFree);
+
+    kernels->type = type;
+    kernels->description = NULL;
+    kernels->num = numBasisFunctions;
+    kernels->u = psVectorAlloc(numBasisFunctions, PS_TYPE_S32);
+    kernels->v = psVectorAlloc(numBasisFunctions, PS_TYPE_S32);
+    kernels->widths = psVectorAlloc(numBasisFunctions, PS_TYPE_F32);
+    kernels->preCalc = psArrayAlloc(numBasisFunctions);
+    kernels->penalty = penalty;
+    kernels->penalties = psVectorAlloc(numBasisFunctions, PS_TYPE_F32);
+    kernels->uStop = NULL;
+    kernels->vStop = NULL;
+    kernels->size = size;
+    kernels->inner = 0;
+    kernels->spatialOrder = spatialOrder;
+    kernels->bgOrder = 0;
+    kernels->mode = mode;
+    kernels->numCols = 0;
+    kernels->numRows = 0;
+    kernels->solution1 = NULL;
+    kernels->solution2 = NULL;
+
+    return kernels;
+}
+
+pmSubtractionKernels *pmSubtractionKernelsPOIS(int size, int spatialOrder, float penalty,
+                                               pmSubtractionMode mode)
+{
+    PS_ASSERT_INT_POSITIVE(size, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(spatialOrder, NULL);
+
+    int num = PS_SQR(2 * size + 1) - 1; // Number of basis functions
+
+    pmSubtractionKernels *kernels = pmSubtractionKernelsAlloc(num, PM_SUBTRACTION_KERNEL_POIS, size,
+                                                              spatialOrder, penalty, mode); // The kernels
+    psStringAppend(&kernels->description, "POIS(%d,%d,%.2e)", size, spatialOrder, penalty);
+    psLogMsg("psModules.imcombine", PS_LOG_INFO, "POIS kernel: %d,%d --> %d elements",
+             size, spatialOrder, num);
+
+    if (!p_pmSubtractionKernelsAddGrid(kernels, 0, size)) {
+        psAbort("Should never get here.");
+    }
+
+    return kernels;
+}
+
+
+pmSubtractionKernels *pmSubtractionKernelsISIS(int size, int spatialOrder,
+                                               const psVector *fwhms, const psVector *orders,
+                                               float penalty, pmSubtractionMode mode)
+{
+    pmSubtractionKernels *kernels = p_pmSubtractionKernelsRawISIS(size, spatialOrder, fwhms, orders,
+                                                                  penalty, mode); // Kernels
+    if (!kernels) {
+        return NULL;
+    }
+
+    if (psTraceGetLevel("psModules.imcombine.kernel") >= 10) {
+        for (int i = 0; i < kernels->num; i++) {
+            psKernel *kernel = kernels->preCalc->data[i]; // Kernel of interest
+            psString kernelName = NULL;
+            psStringAppend(&kernelName, "kernel%03d.fits", i);
+            psFits *kernelFile = psFitsOpen(kernelName, "w");
+            psFree(kernelName);
+            psFitsWriteImage(kernelFile, NULL, kernel->image, 0, NULL);
+            psFitsClose(kernelFile);
+            double sum = 0.0;
+            for (int y = 0; y < kernel->image->numRows; y++) {
+                for (int x = 0; x < kernel->image->numCols; x++) {
+                    sum += kernel->image->data.F32[y][x];
+                }
+            }
+            psTrace("psModules.imcombine.kernel", 10, "Kernel %d sum: %le\n", i, sum);
+        }
+    }
+
+    return kernels;
+}
+
+pmSubtractionKernels *pmSubtractionKernelsSPAM(int size, int spatialOrder, int inner, int binning,
+                                               float penalty, pmSubtractionMode mode)
+{
+    PS_ASSERT_INT_POSITIVE(size, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(spatialOrder, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(inner, NULL);
+    PS_ASSERT_INT_LARGER_THAN(size, inner, NULL);
+    PS_ASSERT_INT_POSITIVE(binning, NULL);
+
+    // The outer region should be divisible by the "binning"; otherwise allocate remainder to the inner region
+    int numOuter = (size - inner) / binning; // Number of summed pixels in the outer region
+    int numInner = inner + (size - inner) % binning; // Number of pixels in the inner region
+    assert(numOuter * binning + numInner == size);
+    int numTotal = numOuter + numInner; // Total number of summed pixels
+
+    psTrace("psModules.imcombine", 3, "Inner: %d Outer: %d\n", numInner, numOuter);
+
+    int num = PS_SQR(2 * numTotal + 1) - 1; // Number of basis functions
+
+    psTrace("psModules.imcombine", 3, "Number of basis functions: %d\n", num);
+
+    pmSubtractionKernels *kernels = pmSubtractionKernelsAlloc(num, PM_SUBTRACTION_KERNEL_SPAM, size,
+                                                              spatialOrder, penalty, mode); // The kernels
+    psStringAppend(&kernels->description, "SPAM(%d,%d,%d,%d,%.2e)", size, inner, binning, spatialOrder,
+                   penalty);
+
+    psLogMsg("psModules.imcombine", PS_LOG_INFO, "SPAM kernel: %d,%d,%d,%d --> %d elements",
+             size, inner, binning, spatialOrder, num);
+
+    kernels->uStop = psVectorAlloc(num, PS_TYPE_S32);
+    kernels->vStop = psVectorAlloc(num, PS_TYPE_S32);
+
+    psVector *locations = psVectorAlloc(2 * numTotal + 1, PS_TYPE_S32); // Locations for each kernel element
+    psVector *widths = psVectorAlloc(2 * numTotal + 1, PS_TYPE_S32); // Widths for each kernel element
+    locations->data.S32[numTotal] = 0;
+    widths->data.S32[numTotal] = 0;
+    for (int i = 1; i <= numInner; i++) {
+        locations->data.S32[numTotal + i] = i;
+        widths->data.S32[numTotal + i] = 0;
+        locations->data.S32[numTotal - i] = - i;
+        widths->data.S32[numTotal - i] = 0;
+    }
+    for (int i = numInner + 1; i <= numTotal; i++) {
+        locations->data.S32[numTotal + i] = locations->data.S32[numTotal + i - 1] +
+            widths->data.S32[numTotal + i - 1] + 1;
+        widths->data.S32[numTotal + i] = binning - 1;
+        locations->data.S32[numTotal - i] = locations->data.S32[numTotal - i + 1] - binning;
+        widths->data.S32[numTotal - i] = binning - 1;
+    }
+
+    if (psTraceGetLevel("psModules.imcombine") >= 10) {
+        for (int i = 0; i < 2 * numTotal + 1; i++) {
+            psTrace("psModules.imcombine", 10, "%d: %d -> %d\n", i, locations->data.S32[i],
+                    locations->data.S32[i] + widths->data.S32[i]);
+        }
+    }
+
+    // Set the kernel parameters
+    for (int i = - numTotal, index = 0; i <= numTotal; i++) {
+        int u = locations->data.S32[numTotal + i]; // Location of pixel
+        int uStop = u + widths->data.S32[numTotal + i]; // Width of pixel
+
+        for (int j = - numTotal; j <= numTotal; j++, index++) {
+            if (i == 0 && j == 0) {
+                // Skip normalisation component: added explicitly
+                index--;
+                continue;
+            }
+            int v = locations->data.S32[numTotal + j]; // Location of pixel
+            int vStop = v + widths->data.S32[numTotal + j]; // Width of pixel
+
+            kernels->u->data.S32[index] = u;
+            kernels->v->data.S32[index] = v;
+            kernels->uStop->data.S32[index] = uStop;
+            kernels->vStop->data.S32[index] = vStop;
+
+            psTrace("psModules.imcombine", 7, "Kernel %d: %d %d %d %d\n", index,
+                    u, uStop, v, vStop);
+        }
+    }
+
+    psFree(locations);
+    psFree(widths);
+
+    psWarning("Kernel penalty for dual-convolution is not configured for SPAM kernels.");
+    psVectorInit(kernels->penalties, 0.0);
+
+    return kernels;
+}
+
+
+pmSubtractionKernels *pmSubtractionKernelsFRIES(int size, int spatialOrder, int inner, float penalty,
+                                                pmSubtractionMode mode)
+{
+    PS_ASSERT_INT_POSITIVE(size, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(spatialOrder, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(inner, NULL);
+    PS_ASSERT_INT_LARGER_THAN(size, inner, NULL);
+
+    int fibNum = 0;                     // Number of Fibonacci values
+    int fibLast = 1, fibTotal = 2;      // Fibonacci sequence
+    while (fibTotal < size - inner) {
+        int temp = fibTotal;
+        fibTotal += fibLast;
+        fibLast = temp;
+        fibNum++;
+    }
+
+    int numInner = inner;               // Number of pixels in the inner region
+    int numOuter = fibNum;              // Number of summed pixels in the outer region
+    int numTotal = numOuter + numInner; // Total number of summed pixels
+
+    psTrace("psModules.imcombine", 3, "Inner: %d Outer: %d\n", numInner, numOuter);
+
+    int num = PS_SQR(2 * numTotal + 1) - 1; // Number of basis functions
+
+    psTrace("psModules.imcombine", 3, "Number of basis functions: %d\n", num);
+
+    pmSubtractionKernels *kernels = pmSubtractionKernelsAlloc(num, PM_SUBTRACTION_KERNEL_FRIES, size,
+                                                              spatialOrder, penalty, mode); // The kernels
+    psStringAppend(&kernels->description, "FRIES(%d,%d,%d,%.2e)", size, inner, spatialOrder, penalty);
+
+    psLogMsg("psModules.imcombine", PS_LOG_INFO, "FRIES kernel: %d,%d,%d --> %d elements",
+             size, inner, spatialOrder, num);
+
+    kernels->uStop = psVectorAlloc(num, PS_TYPE_S32);
+    kernels->vStop = psVectorAlloc(num, PS_TYPE_S32);
+
+    psVector *start = psVectorAlloc(2 * numTotal + 1, PS_TYPE_S32);
+    psVector *stop = psVectorAlloc(2 * numTotal + 1, PS_TYPE_S32);
+    start->data.S32[numTotal] = 0;
+    stop->data.S32[numTotal] = 0;
+    for (int i = 1; i <= numInner; i++) {
+        start->data.S32[numTotal + i] = i;
+        stop->data.S32[numTotal + i] = i;
+        start->data.S32[numTotal - i] = -i;
+        stop->data.S32[numTotal - i] = -i;
+    }
+    for (int i = numInner + 1, fibLast = 1, fib = 2, temp; i <= numTotal;
+         i++, fib = (temp = fib) + fibLast, fibLast = temp) {
+        start->data.S32[numTotal + i] = stop->data.S32[numTotal + i - 1] + 1;
+        stop->data.S32[numTotal + i] = PS_MIN(start->data.S32[numTotal + i] + fib - 1, size);
+        start->data.S32[numTotal - i] = - stop->data.S32[numTotal + i];
+        stop->data.S32[numTotal - i] = - start->data.S32[numTotal + i];
+    }
+
+    if (psTraceGetLevel("psModules.imcombine") >= 10) {
+        for (int i = 0; i < 2 * numTotal + 1; i++) {
+            psTrace("psModules.imcombine", 10, "%d: %d -> %d\n", i, start->data.S32[i], stop->data.S32[i]);
+        }
+    }
+
+    // Set the kernel parameters
+    for (int i = - numTotal, index = 0; i <= numTotal; i++) {
+        int u = start->data.S32[numTotal + i]; // Location of pixel
+        int uStop = stop->data.S32[numTotal + i]; // Width of pixel
+        for (int j = - numTotal; j <= numTotal; j++, index++) {
+            if (i == 0 && j == 0) {
+                // Skip normalisation component: added explicitly
+                index--;
+                continue;
+            }
+            int v = start->data.S32[numTotal + j]; // Location of pixel
+            int vStop = stop->data.S32[numTotal + j]; // Width of pixel
+
+            kernels->u->data.S32[index] = u;
+            kernels->v->data.S32[index] = v;
+            kernels->uStop->data.S32[index] = uStop;
+            kernels->vStop->data.S32[index] = vStop;
+
+            psTrace("psModules.imcombine", 7, "Kernel %d: %d %d %d %d\n", index,
+                    u, uStop, v, vStop);
+        }
+    }
+
+    psFree(start);
+    psFree(stop);
+
+    psWarning("Kernel penalty for dual-convolution is not configured for FRIES kernels.");
+    psVectorInit(kernels->penalties, 0.0);
+
+    return kernels;
+}
+
+// Grid United with Normal Kernel
+pmSubtractionKernels *pmSubtractionKernelsGUNK(int size, int spatialOrder, const psVector *fwhms,
+                                               const psVector *orders, int inner, float penalty,
+                                               pmSubtractionMode mode)
+{
+    PS_ASSERT_INT_POSITIVE(size, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(spatialOrder, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(fwhms, NULL);
+    PS_ASSERT_VECTOR_TYPE(fwhms, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(orders, NULL);
+    PS_ASSERT_VECTOR_TYPE(orders, PS_TYPE_S32, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(fwhms, orders, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(inner, NULL);
+    PS_ASSERT_INT_LESS_THAN(inner, size, NULL);
+
+    // XXX GUNK doesn't seem to work --- doesn't add the POIS components, or at least, they're not noticed
+
+    pmSubtractionKernels *kernels = p_pmSubtractionKernelsRawISIS(size, spatialOrder, fwhms, orders,
+                                                                  penalty, mode); // Kernels
+    psStringPrepend(&kernels->description, "GUNK=");
+    psStringAppend(&kernels->description, "+POIS(%d,%d)", inner, spatialOrder);
+
+    int numISIS = kernels->num;         // Number of ISIS kernels
+
+    if (!p_pmSubtractionKernelsAddGrid(kernels, numISIS, inner)) {
+        psAbort("Should never get here.");
+    }
+
+    return kernels;
+}
+
+// RINGS --- just what it says
+pmSubtractionKernels *pmSubtractionKernelsRINGS(int size, int spatialOrder, int inner, int ringsOrder,
+                                                float penalty, pmSubtractionMode mode)
+{
+    PS_ASSERT_INT_POSITIVE(size, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(spatialOrder, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(inner, NULL);
+    PS_ASSERT_INT_LESS_THAN_OR_EQUAL(inner, size, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(ringsOrder, NULL);
+
+    int fibNum = 0;                     // Number of Fibonacci values
+    {
+        int fibIndex = 1, fibIndexMinus1 = 0; // Fibonnacci parameters
+        int radius = inner;
+        while (radius + fibIndex < size) {
+            radius++;
+            int fibNew = fibIndex + fibIndexMinus1;
+            fibIndexMinus1 = fibIndex;
+            fibIndex = fibNew;
+            radius += fibIndex;
+            fibNum++;
+        }
+    }
+
+    int numInner = inner - 1;           // Number of pixels in the inner region
+    int numOuter = fibNum;              // Number of summed pixels in the outer region
+
+    int numRings = numOuter + numInner; // Number of rings (not including the central pixel)
+    int numPoly = PM_SUBTRACTION_POLYTERMS(ringsOrder); // Number of polynomial variants of each ring
+
+    int num = numRings * numPoly; // Total number of basis functions
+
+    pmSubtractionKernels *kernels = pmSubtractionKernelsAlloc(num, PM_SUBTRACTION_KERNEL_RINGS, size,
+                                                              spatialOrder, penalty, mode); // The kernels
+    psStringAppend(&kernels->description, "RINGS(%d,%d,%d,%d,%.2e)", size, inner, ringsOrder, spatialOrder,
+                   penalty);
+
+    psLogMsg("psModules.imcombine", PS_LOG_INFO, "RINGS kernel: %d,%d,%d,%d --> %d elements",
+             size, inner, ringsOrder, spatialOrder, num);
+
+    // Set the Gaussian kernel parameters
+    int fibIndex = 1, fibIndexMinus1 = 0; // Fibonnacci parameters
+    int radiusLast = 1;                 // Last radius
+    for (int i = 1, index = 0; i < numRings + 1; i++) {
+        float lower2;                   // Lower limit of radius^2
+        float upper2;                   // Upper limit of radius^2
+        if (i <= inner) {
+            // A ring every pixel width
+            float radius = i;
+            lower2 = PS_SQR(radius - 0.5);
+            upper2 = PS_SQR(radius + 0.5);
+            radiusLast = i;
+        } else {
+            // Rings Fibonacci distributed (2, 3, 5...)
+            int fibNew = fibIndex + fibIndexMinus1;
+            fibIndexMinus1 = fibIndex;
+            fibIndex = fibNew;
+
+            float radiusLower = radiusLast + 1;
+            radiusLast = radiusLower + fibIndex;
+            float radiusUpper = radiusLast;
+
+            lower2 = PS_SQR(radiusLower - 0.5);
+            upper2 = PS_SQR(radiusUpper + 0.5);
+        }
+
+        psTrace("psModules.imcombine", 8, "Radius limits: %f --> %f\n", sqrtf(lower2), sqrtf(upper2));
+
+        // Iterate over (u,v) order
+        for (int uOrder = 0; uOrder <= (i == 0 ? 0 : ringsOrder); uOrder++) {
+            for (int vOrder = 0; vOrder <= (i == 0 ? 0 : ringsOrder - uOrder); vOrder++, index++) {
+
+                psArray *data = psArrayAlloc(3); // Container for data
+                psVector *uCoords = data->data[0] = psVectorAllocEmpty(RINGS_BUFFER, PS_TYPE_S32); // u coords
+                psVector *vCoords = data->data[1] = psVectorAllocEmpty(RINGS_BUFFER, PS_TYPE_S32); // v coords
+                psVector *poly = data->data[2] = psVectorAllocEmpty(RINGS_BUFFER, PS_TYPE_F32); // Polynomial
+                double moment = 0.0;    // Moment, for penalty
+
+                if (i == 0) {
+                    // Central pixel is easy
+                    uCoords->data.S32[0] = vCoords->data.S32[0] = 0;
+                    poly->data.F32[0] = 1.0;
+                    uCoords->n = vCoords->n = poly->n = 1;
+                    radiusLast = 0;
+                    moment = 0.0;
+                } else {
+                    int j = 0;          // Index for data
+                    double norm = 0.0;  // Normalisation
+                    for (int v = -size; v <= size; v++) {
+                        int v2 = PS_SQR(v);   // Square of v
+                        float vPoly = powf(v/(float)size, vOrder); // Value of v^vOrder
+
+                        for (int u = -size; u <= size; u++) {
+                            int u2 = PS_SQR(u); // Square of u
+                            int distance2 = u2 + v2; // Distance from the centre
+                            if (distance2 > lower2 && distance2 < upper2) {
+                                float uPoly = powf(u/(float)size, uOrder); // Value of u^uOrder
+
+                                float polyVal = uPoly * vPoly; // Value of polynomial
+                                if (polyVal != 0) { // No point adding it otherwise
+                                    uCoords->data.S32[j] = u;
+                                    vCoords->data.S32[j] = v;
+                                    poly->data.F32[j] = polyVal;
+                                    norm += polyVal;
+                                    moment += polyVal * (PS_SQR(u) + PS_SQR(v));
+
+                                    psVectorExtend(uCoords, RINGS_BUFFER, 1);
+                                    psVectorExtend(vCoords, RINGS_BUFFER, 1);
+                                    psVectorExtend(poly, RINGS_BUFFER, 1);
+                                    psTrace("psModules.imcombine", 9, "u = %d, v = %d, poly = %f\n",
+                                            u, v, poly->data.F32[j]);
+                                    j++;
+                                }
+                            }
+                        }
+                    }
+                    // Normalise kernel component to unit sum
+                    if (uOrder % 2 == 0 && vOrder % 2 == 0) {
+                        psBinaryOp(poly, poly, "*", psScalarAlloc(1.0 / norm, PS_TYPE_F32));
+                        // Add subtraction of 0,0 component to preserve photometric scaling
+                        uCoords->data.S32[j] = 0;
+                        vCoords->data.S32[j] = 0;
+                        poly->data.F32[j] = -1.0;
+                        psVectorExtend(uCoords, RINGS_BUFFER, 1);
+                        psVectorExtend(vCoords, RINGS_BUFFER, 1);
+                        psVectorExtend(poly, RINGS_BUFFER, 1);
+                    } else {
+                        norm = powf(size, uOrder) * powf(size, vOrder);
+                        psBinaryOp(poly, poly, "*", psScalarAlloc(1.0 / norm, PS_TYPE_F32));
+                    }
+//                    moment /= norm;
+                }
+
+                psTrace("psModules.imcombine", 8, "%ld pixels in kernel\n", uCoords->n);
+
+                kernels->preCalc->data[index] = data;
+                kernels->u->data.S32[index] = uOrder;
+                kernels->v->data.S32[index] = vOrder;
+                kernels->penalties->data.F32[index] = kernels->penalty * fabsf(moment);
+
+                psTrace("psModules.imcombine", 7, "Kernel %d: %d %d %d\n", index,
+                        i, uOrder, vOrder);
+            }
+        }
+    }
+
+    return kernels;
+}
+
+pmSubtractionKernels *pmSubtractionKernelsGenerate(pmSubtractionKernelsType type, int size, int spatialOrder,
+                                                   const psVector *fwhms, const psVector *orders, int inner,
+                                                   int binning, int ringsOrder, float penalty,
+                                                   pmSubtractionMode mode)
+{
+    switch (type) {
+      case PM_SUBTRACTION_KERNEL_POIS:
+        return pmSubtractionKernelsPOIS(size, spatialOrder, penalty, mode);
+      case PM_SUBTRACTION_KERNEL_ISIS:
+        return pmSubtractionKernelsISIS(size, spatialOrder, fwhms, orders, penalty, mode);
+      case PM_SUBTRACTION_KERNEL_SPAM:
+        return pmSubtractionKernelsSPAM(size, spatialOrder, inner, binning, penalty, mode);
+      case PM_SUBTRACTION_KERNEL_FRIES:
+        return pmSubtractionKernelsFRIES(size, spatialOrder, inner, penalty, mode);
+      case PM_SUBTRACTION_KERNEL_GUNK:
+        return pmSubtractionKernelsGUNK(size, spatialOrder, fwhms, orders, inner, penalty, mode);
+      case PM_SUBTRACTION_KERNEL_RINGS:
+        return pmSubtractionKernelsRINGS(size, spatialOrder, inner, ringsOrder, penalty, mode);
+      default:
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unknown kernel type: %x", type);
+        return NULL;
+    }
+}
+
+
+// Intermediate string parsing functions required because of different APIs for strtol and strtof
+static inline int parseStringInt(const char *string)
+{
+    return strtol(string, NULL, 10);
+}
+static inline float parseStringFloat(const char *string)
+{
+    return strtof(string, NULL);
+}
+
+
+// Parse a string of a number, up to some delimiter, and advance past the delimiter
+#define PARSE_STRING_NUMBER(TARGET, STRING, DELIM, PARSEFUNC) { \
+    char *start = STRING;               /* Start of string */ \
+    char *end = strchr(STRING, DELIM);  /* End of string */ \
+    if (!end) { \
+        psAbort("End of string encountered"); \
+    } \
+    int stringSize = end - STRING;      /* Size of string with value, NOT including \0 */ \
+    char value[stringSize + 1];         /* String to parse */ \
+    strncpy(value, start, stringSize); \
+    value[stringSize] = '\0'; \
+    TARGET = PARSEFUNC(value); \
+    STRING += stringSize + 1;           /* Advance past delimiter */ \
+}
+
+
+pmSubtractionKernels *pmSubtractionKernelsFromDescription(const char *description, int bgOrder,
+                                                          pmSubtractionMode mode)
+{
+    PS_ASSERT_STRING_NON_EMPTY(description, NULL);
+
+    if (bgOrder != 0) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Background order %d is not yet supported.", bgOrder);
+        return false;
+    }
+
+    pmSubtractionKernelsType type = PM_SUBTRACTION_KERNEL_NONE; // Type of kernel
+    int size = 0;                       // Half-size of kernel
+    int spatialOrder = 0;               // Order of spatial variations
+    const psVector *fwhms = NULL;       // FWHM of Gaussians
+    const psVector *orders = NULL;      // Polynomial order for each FWHM
+    int inner = 0;                      // Size of inner region
+    int binning = 0;                    // Binning to use
+    int ringsOrder = 0;                 // Polynomial order for rings
+    float penalty = 0.0;                // Penalty for wideness
+
+    if (strncmp(description, "ISIS", 4) == 0) {
+        // XXX Support for GUNK
+        if (strstr(description, "+POIS")) {
+            type = PM_SUBTRACTION_KERNEL_GUNK;
+            psAbort("Deciphering GUNK kernels (%s) is not currently supported.", description);
+        } else {
+            type = PM_SUBTRACTION_KERNEL_ISIS;
+            char *ptr = (char*)description + 5;    // Eat "ISIS("
+            PARSE_STRING_NUMBER(size, ptr, ',', parseStringInt);
+
+            // Count the number of Gaussians
+            int numGauss = 0;
+            for (char *string = ptr; string; string = strchr(string, '(')) {
+                numGauss++;
+            }
+
+            fwhms = psVectorAlloc(numGauss, PS_TYPE_F32);
+            orders = psVectorAlloc(numGauss, PS_TYPE_S32);
+
+            for (int i = 0; i < numGauss; i++) {
+                ptr++;                  // Eat the '('
+                PARSE_STRING_NUMBER(fwhms->data.F32[i], ptr, ',', parseStringFloat); // Eat "1.234,"
+                PARSE_STRING_NUMBER(orders->data.S32[i], ptr, ')', parseStringInt); // Eat "3)"
+            }
+
+            ptr++;                      // Eat ','
+            PARSE_STRING_NUMBER(spatialOrder, ptr, ',', parseStringInt);
+            penalty = parseStringFloat(ptr);
+        }
+    } else if (strncmp(description, "RINGS", 5) == 0) {
+        type = PM_SUBTRACTION_KERNEL_RINGS;
+        char *ptr = (char*)description + 6;
+        PARSE_STRING_NUMBER(size, ptr, ',', parseStringInt);
+        PARSE_STRING_NUMBER(inner, ptr, ',', parseStringInt);
+        PARSE_STRING_NUMBER(ringsOrder, ptr, ',', parseStringInt);
+        PARSE_STRING_NUMBER(spatialOrder, ptr, ',', parseStringInt);
+        PARSE_STRING_NUMBER(penalty, ptr, ')', parseStringInt);
+    } else {
+        psAbort("Deciphering kernels other than ISIS and RINGS is not currently supported.");
+    }
+
+
+    return pmSubtractionKernelsGenerate(type, size, spatialOrder, fwhms, orders,
+                                        inner, binning, ringsOrder, penalty, mode);
+}
+
+
+pmSubtractionKernelsType pmSubtractionKernelsTypeFromString(const char *type)
+{
+    if (strcasecmp(type, "POIS") == 0) {
+        return PM_SUBTRACTION_KERNEL_POIS;
+    }
+    if (strcasecmp(type, "ISIS") == 0) {
+        return PM_SUBTRACTION_KERNEL_ISIS;
+    }
+    if (strcasecmp(type, "SPAM") == 0) {
+        return PM_SUBTRACTION_KERNEL_SPAM;
+    }
+    if (strcasecmp(type, "FRIES") == 0) {
+        return PM_SUBTRACTION_KERNEL_FRIES;
+    }
+    if (strcasecmp(type, "GUNK") == 0) {
+        return PM_SUBTRACTION_KERNEL_GUNK;
+    }
+    if (strcasecmp(type, "RINGS") == 0) {
+        return PM_SUBTRACTION_KERNEL_RINGS;
+    }
+
+    psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unrecognised kernel type: %s", type);
+    return PM_SUBTRACTION_KERNEL_NONE;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionKernels.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionKernels.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionKernels.h	(revision 22157)
@@ -0,0 +1,202 @@
+#ifndef PM_SUBTRACTION_KERNELS_H
+#define PM_SUBTRACTION_KERNELS_H
+
+#include <pslib.h>
+
+/// Type of subtraction kernel
+typedef enum {
+    PM_SUBTRACTION_KERNEL_NONE,         ///< Nothing --- an error
+    PM_SUBTRACTION_KERNEL_POIS,         ///< Pan-STARRS Optimal Image Subtraction --- delta functions
+    PM_SUBTRACTION_KERNEL_ISIS,         ///< Traditional kernel --- gaussians modified by polynomials
+    PM_SUBTRACTION_KERNEL_SPAM,         ///< Summed Pixels for Advanced Matching --- summed delta functions
+    PM_SUBTRACTION_KERNEL_FRIES,        ///< Fibonacci Radius Increases Excellence of Subtraction
+    PM_SUBTRACTION_KERNEL_GUNK,         ///< Grid United with Normal Kernel --- POIS and ISIS hybrid
+    PM_SUBTRACTION_KERNEL_RINGS,        ///< Rings Instead of the Normal Gaussian Subtraction
+} pmSubtractionKernelsType;
+
+/// Modes --- specifies which image to convolve
+typedef enum {
+    PM_SUBTRACTION_MODE_ERR,            // Error in the mode
+    PM_SUBTRACTION_MODE_1,              // Convolve image 1
+    PM_SUBTRACTION_MODE_2,              // Convolve image 2
+    PM_SUBTRACTION_MODE_UNSURE,         // Not sure yet which image to convolve so try to satisfy both
+    PM_SUBTRACTION_MODE_DUAL,           // Dual convolution
+} pmSubtractionMode;
+
+/// Kernels specification
+typedef struct {
+    pmSubtractionKernelsType type;      ///< Type of kernels --- allowing the use of multiple kernels
+    psString description;               ///< Description of the kernel parameters
+    long num;                           ///< Number of kernel components (not including the spatial ones)
+    psVector *u, *v;                    ///< Offset (for POIS) or polynomial order (for ISIS)
+    psVector *widths;                   ///< Gaussian FWHMs (ISIS)
+    psVector *uStop, *vStop;            ///< Width of kernel element (SPAM,FRIES only)
+    psArray *preCalc;                   ///< Array of images containing pre-calculated kernel (for ISIS)
+    float penalty;                      ///< Penalty for wideness
+    psVector *penalties;                ///< Penalty for each kernel component
+    int size;                           ///< The half-size of the kernel
+    int inner;                          ///< The size of an inner region
+    int spatialOrder;                   ///< The spatial order of the kernels
+    int bgOrder;                        ///< The order for the background fitting
+    pmSubtractionMode mode;             ///< Mode for subtraction
+    int numCols, numRows;               ///< Size of image (for normalisation), or zero to use image provided
+    psVector *solution1, *solution2;    ///< Solution for the PSF matching
+} pmSubtractionKernels;
+
+// Assertion to check pmSubtractionKernels
+#define PM_ASSERT_SUBTRACTION_KERNELS_NON_NULL(KERNELS, RETURNVALUE) { \
+    PS_ASSERT_PTR_NON_NULL(KERNELS, RETURNVALUE); \
+    PS_ASSERT_STRING_NON_EMPTY((KERNELS)->description, RETURNVALUE); \
+    PS_ASSERT_INT_POSITIVE((KERNELS)->num, RETURNVALUE); \
+    PS_ASSERT_VECTOR_NON_NULL((KERNELS)->u, RETURNVALUE); \
+    PS_ASSERT_VECTOR_NON_NULL((KERNELS)->v, RETURNVALUE); \
+    PS_ASSERT_VECTOR_TYPE((KERNELS)->u, PS_TYPE_S32, RETURNVALUE); \
+    PS_ASSERT_VECTOR_TYPE((KERNELS)->v, PS_TYPE_S32, RETURNVALUE); \
+    PS_ASSERT_VECTOR_SIZE((KERNELS)->u, (KERNELS)->num, RETURNVALUE); \
+    PS_ASSERT_VECTOR_SIZE((KERNELS)->v, (KERNELS)->num, RETURNVALUE); \
+    if ((KERNELS)->type == PM_SUBTRACTION_KERNEL_ISIS) { \
+        PS_ASSERT_VECTOR_NON_NULL((KERNELS)->widths, RETURNVALUE); \
+        PS_ASSERT_VECTOR_TYPE((KERNELS)->widths, PS_TYPE_F32, RETURNVALUE); \
+        PS_ASSERT_VECTOR_SIZE((KERNELS)->widths, (KERNELS)->num, RETURNVALUE); \
+    } \
+    if ((KERNELS)->uStop || (KERNELS)->vStop) { \
+        PS_ASSERT_VECTOR_NON_NULL((KERNELS)->uStop, RETURNVALUE); \
+        PS_ASSERT_VECTOR_NON_NULL((KERNELS)->vStop, RETURNVALUE); \
+        PS_ASSERT_VECTOR_TYPE((KERNELS)->uStop, PS_TYPE_S32, RETURNVALUE); \
+        PS_ASSERT_VECTOR_TYPE((KERNELS)->vStop, PS_TYPE_S32, RETURNVALUE); \
+        PS_ASSERT_VECTOR_SIZE((KERNELS)->uStop, (KERNELS)->num, RETURNVALUE); \
+        PS_ASSERT_VECTOR_SIZE((KERNELS)->vStop, (KERNELS)->num, RETURNVALUE); \
+    } \
+    if ((KERNELS)->preCalc) { \
+        PS_ASSERT_ARRAY_NON_NULL((KERNELS)->preCalc, RETURNVALUE); \
+        PS_ASSERT_ARRAY_SIZE((KERNELS)->preCalc, (KERNELS)->num, RETURNVALUE); \
+    } \
+    PS_ASSERT_INT_NONNEGATIVE((KERNELS)->size, RETURNVALUE); \
+    PS_ASSERT_INT_NONNEGATIVE((KERNELS)->inner, RETURNVALUE); \
+    PS_ASSERT_INT_NONNEGATIVE((KERNELS)->spatialOrder, RETURNVALUE); \
+    PS_ASSERT_INT_NONNEGATIVE((KERNELS)->bgOrder, RETURNVALUE); \
+}
+
+// Assertion to check that the solution is attached
+#define PM_ASSERT_SUBTRACTION_KERNELS_SOLUTION(KERNELS, RETURNVALUE) { \
+    PS_ASSERT_VECTOR_NON_NULL((KERNELS)->solution1, RETURNVALUE); \
+    PS_ASSERT_VECTOR_TYPE((KERNELS)->solution1, PS_TYPE_F64, RETURNVALUE); \
+    PS_ASSERT_VECTOR_SIZE((KERNELS)->solution1, \
+                          (KERNELS)->num * PM_SUBTRACTION_POLYTERMS((KERNELS)->spatialOrder) + 1 + \
+                              PM_SUBTRACTION_POLYTERMS((KERNELS)->bgOrder), \
+                          RETURNVALUE); \
+    if (kernels->mode == PM_SUBTRACTION_MODE_DUAL) { \
+        PS_ASSERT_VECTOR_NON_NULL(kernels->solution2, RETURNVALUE); \
+        PS_ASSERT_VECTOR_TYPE((KERNELS)->solution2, PS_TYPE_F64, RETURNVALUE); \
+        PS_ASSERT_VECTOR_SIZE((KERNELS)->solution2, \
+                              (KERNELS)->num * PM_SUBTRACTION_POLYTERMS((KERNELS)->spatialOrder), \
+                               RETURNVALUE); \
+    } \
+}
+
+/// Generate a delta-function grid for subtraction kernels (like the POIS kernel)
+bool p_pmSubtractionKernelsAddGrid(pmSubtractionKernels *kernels, ///< The subtraction kernels to append to
+                                   int start, ///< Index at which to start appending
+                                   int size ///< Half-size of the grid
+    );
+
+/// General allocator for pmSubtractionKernels
+///
+/// Unlike the functions for the specific kernel type, this function does not set up the basis functions, but
+/// merely allocates space for their storage.
+pmSubtractionKernels *pmSubtractionKernelsAlloc(int numBasisFunctions, ///< Number of basis functions
+                                                pmSubtractionKernelsType type, ///< Kernel type
+                                                int size, ///< Half-size of kernel
+                                                int spatialOrder, ///< Order of spatial variations
+                                                float penalty, ///< Penalty for wideness
+                                                pmSubtractionMode mode ///< Mode for subtraction
+    );
+
+/// Generate POIS kernels
+pmSubtractionKernels *pmSubtractionKernelsPOIS(int size, ///< Half-size of the kernel (in both dims)
+                                               int spatialOrder, ///< Order of spatial variations
+                                               float penalty, ///< Penalty for wideness
+                                               pmSubtractionMode mode ///< Mode for subtraction
+    );
+
+/// Generate ISIS kernels without the flux scaling built in
+pmSubtractionKernels *p_pmSubtractionKernelsRawISIS(int size, ///< Half-size of the kernel
+                                                    int spatialOrder, ///< Order of spatial variations
+                                                    const psVector *fwhms, ///< Gaussian FWHMs
+                                                    const psVector *orders, ///< Polynomial order of gaussians
+                                                    float penalty, ///< Penalty for wideness
+                                                    pmSubtractionMode mode ///< Mode for subtraction
+    );
+
+/// Generate ISIS kernels
+pmSubtractionKernels *pmSubtractionKernelsISIS(int size, ///< Half-size of the kernel
+                                               int spatialOrder, ///< Order of spatial variations
+                                               const psVector *fwhms, ///< Gaussian FWHMs
+                                               const psVector *orders, ///< Polynomial order of gaussians
+                                               float penalty, ///< Penalty for wideness
+                                               pmSubtractionMode mode ///< Mode for subtraction
+                                               );
+
+/// Generate SPAM kernels
+pmSubtractionKernels *pmSubtractionKernelsSPAM(int size, ///< Half-size of the kernel
+                                               int spatialOrder, ///< Order of spatial variations
+                                               int inner, ///< Inner radius to preserve unbinned
+                                               int binning, ///< Kernel binning factor
+                                               float penalty, ///< Penalty for wideness
+                                               pmSubtractionMode mode ///< Mode for subtraction
+    );
+
+/// Generate FRIES kernels
+pmSubtractionKernels *pmSubtractionKernelsFRIES(int size, ///< Half-size of the kernel
+                                                int spatialOrder, ///< Order of spatial variations
+                                                int inner, ///< Inner radius to preserve unbinned
+                                                float penalty, ///< Penalty for wideness
+                                                pmSubtractionMode mode ///< Mode for subtraction
+    );
+
+/// Generate GUNK kernels
+pmSubtractionKernels *pmSubtractionKernelsGUNK(int size, ///< Half-size of the kernel
+                                               int spatialOrder, ///< Order of spatial variations
+                                               const psVector *fwhms, ///< Gaussian FWHMs
+                                               const psVector *orders, ///< Polynomial order of gaussians
+                                               int inner, ///< Inner radius containing grid of delta functions
+                                               float penalty, ///< Penalty for wideness
+                                               pmSubtractionMode mode ///< Mode for subtraction
+    );
+
+/// Generate RINGS kernels
+pmSubtractionKernels *pmSubtractionKernelsRINGS(int size, ///< Half-size of the kernel
+                                                int spatialOrder, ///< Order of spatial variations
+                                                int inner, ///< Inner radius to preserve unbinned
+                                                int ringsOrder, ///< Polynomial order
+                                                float penalty, ///< Penalty for wideness
+                                                pmSubtractionMode mode ///< Mode for subtraction
+    );
+
+
+/// Generate a kernel of a specified type
+pmSubtractionKernels *pmSubtractionKernelsGenerate(pmSubtractionKernelsType type, ///< Kernel type
+                                                   int size, ///< Half-size of the kernel
+                                                   int spatialOrder, ///< Order of spatial variations
+                                                   const psVector *fwhms, ///< Gaussian FWHMs
+                                                   const psVector *orders, ///< Polynomial order of gaussians
+                                                   int inner, ///< Inner radius to preserve unbinned
+                                                   int binning, ///< Kernel binning factor
+                                                   int ringsOrder, ///< Polynomial order for RINGS
+                                                   float penalty, ///< Penalty for wideness
+                                                   pmSubtractionMode mode ///< Mode for subtraction
+    );
+
+/// Generate a kernel using the description
+pmSubtractionKernels *pmSubtractionKernelsFromDescription(
+    const char *description,            ///< Description of kernel
+    int bgOrder,                        ///< Polynomial order for background fitting
+    pmSubtractionMode mode              ///< Mode for subtraction
+    );
+
+/// Return the appropriate type from a string
+pmSubtractionKernelsType pmSubtractionKernelsTypeFromString(const char *string // String name for kernel type
+    );
+
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionMask.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionMask.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionMask.c	(revision 22157)
@@ -0,0 +1,259 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmSubtraction.h"
+#include "pmSubtractionKernels.h"
+
+#include "pmSubtractionMask.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Private (file-static) functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Mark a pixel as blank in the image, mask and weight
+static inline void markBlank(psImage *image, // Image to mark as blank
+                             psImage *mask, // Mask to mark as blank (or NULL)
+                             psImage *weight, // Weight map to mark as blank (or NULL)
+                             int x, int y, // Coordinates to mark blank
+                             psMaskType blank // Blank mask value
+    )
+{
+    image->data.F32[y][x] = NAN;
+    if (mask) {
+        mask->data.PS_TYPE_MASK_DATA[y][x] |= blank;
+    }
+    if (weight) {
+        weight->data.F32[y][x] = NAN;
+    }
+    return;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+psImage *pmSubtractionMask(const psImage *mask1, const psImage *mask2, psMaskType maskVal,
+                           int size, int footprint, float badFrac, bool useFFT)
+{
+    PS_ASSERT_IMAGE_NON_NULL(mask1, NULL);
+    PS_ASSERT_IMAGE_TYPE(mask1, PS_TYPE_MASK, NULL);
+    if (mask2) {
+        PS_ASSERT_IMAGE_NON_NULL(mask2, NULL);
+        PS_ASSERT_IMAGE_TYPE(mask2, PS_TYPE_MASK, NULL);
+        PS_ASSERT_IMAGES_SIZE_EQUAL(mask2, mask1, NULL);
+    }
+    PS_ASSERT_INT_NONNEGATIVE(size, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(footprint, NULL);
+    if (isfinite(badFrac)) {
+        PS_ASSERT_FLOAT_LARGER_THAN(badFrac, 0.0, NULL);
+        PS_ASSERT_FLOAT_LESS_THAN_OR_EQUAL(badFrac, 1.0, NULL);
+    }
+
+    int numCols = mask1->numCols, numRows = mask1->numRows; // Size of the images
+
+    // Dereference inputs for convenience
+    psMaskType **data1 = mask1->data.PS_TYPE_MASK_DATA;
+    psMaskType **data2 = NULL;
+    if (mask2) {
+        data2 = mask2->data.PS_TYPE_MASK_DATA;
+    }
+
+    // First, a pass through to determine the fraction of bad pixels
+    if (isfinite(badFrac) && badFrac != 1.0) {
+        int numBad = 0;                 // Number of bad pixels
+        for (int y = 0; y < numRows; y++) {
+            for (int x = 0; x < numCols; x++) {
+                if (data1[y][x] & maskVal) {
+                    numBad++;
+                    continue;
+                }
+                if (data2 && data2[y][x] & maskVal) {
+                    numBad++;
+                }
+            }
+        }
+        if (numBad > badFrac * numCols * numRows) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                    "Fraction of bad pixels (%d/%d=%f) exceeds limit (%f)\n",
+                    numBad, numCols * numRows, (float)numBad/(float)(numCols * numRows), badFrac);
+            return NULL;
+        }
+    }
+
+    // Worried about the masks for bad pixels and bad stamps colliding, so make our own mask
+    psImage *mask = psImageAlloc(numCols, numRows, PS_TYPE_MASK); // The global mask
+    psImageInit(mask, 0);
+    psMaskType **maskData = mask->data.PS_TYPE_MASK_DATA; // Dereference for convenience
+
+    // Block out a border around the edge of the image
+
+    // Bottom stripe
+    for (int y = 0; y < PS_MIN(size + footprint, numRows); y++) {
+        for (int x = 0; x < numCols; x++) {
+            maskData[y][x] |= PM_SUBTRACTION_MASK_BORDER;
+        }
+    }
+    // Either side
+    for (int y = PS_MIN(size + footprint, numRows); y < numRows - size - footprint; y++) {
+        for (int x = 0; x < PS_MIN(size + footprint, numCols); x++) {
+            maskData[y][x] |= PM_SUBTRACTION_MASK_BORDER;
+        }
+        for (int x = PS_MAX(numCols - size - footprint, 0); x < numCols; x++) {
+            maskData[y][x] |= PM_SUBTRACTION_MASK_BORDER;
+        }
+    }
+    // Top stripe
+    for (int y = PS_MAX(numRows - size - footprint, 0); y < numRows; y++) {
+        for (int x = 0; x < numCols; x++) {
+            maskData[y][x] |= PM_SUBTRACTION_MASK_BORDER;
+        }
+    }
+
+    // XXX Could do something smarter here --- we will get images that are predominantly masked (where the
+    // skycell isn't overlapped by a large fraction by the observation), so that convolving around every bad
+    // pixel is wasting time.  As a first cut, I've put in a check on the fraction of bad pixels, but we could
+    // imagine looking for the edge of big regions and convolving just at the edge.  As a second cut, allow
+    // use of FFT convolution.
+
+    for (int y = 0; y < numRows; y++) {
+        for (int x = 0; x < numCols; x++) {
+            if (data1[y][x] & maskVal) {
+                maskData[y][x] |= PM_SUBTRACTION_MASK_BAD_1;
+            }
+            if (data2 && data2[y][x] & maskVal) {
+                maskData[y][x] |= PM_SUBTRACTION_MASK_BAD_2;
+            }
+        }
+    }
+
+    // Block out the entire stamp footprint around bad input pixels.
+
+    // We want to block out with the CONVOLVE mask anything that would be bad if we convolved with a bad
+    // reference pixel (within 'size').  Then we want to block out with the FOOTPRINT mask everything within a
+    // footprint's distance of those (within 'footprint').
+
+    if (!psImageConvolveMask(mask, mask, PM_SUBTRACTION_MASK_BAD_1,
+                             PM_SUBTRACTION_MASK_CONVOLVE_1 | PM_SUBTRACTION_MASK_FOOTPRINT_1,
+                             -size, size, -size, size)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to convolve bad pixels from mask 1.");
+        psFree(mask);
+        return NULL;
+    }
+    if (!psImageConvolveMask(mask, mask, PM_SUBTRACTION_MASK_BAD_2,
+                             PM_SUBTRACTION_MASK_CONVOLVE_2 | PM_SUBTRACTION_MASK_FOOTPRINT_2,
+                             -size, size, -size, size)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to convolve bad pixels from mask 2.");
+        psFree(mask);
+        return NULL;
+    }
+    if (!psImageConvolveMask(mask, mask, PM_SUBTRACTION_MASK_CONVOLVE_1,
+                             PM_SUBTRACTION_MASK_FOOTPRINT_1,
+                             -footprint, footprint, -footprint, footprint)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to reconvolve bad pixels from mask 1.");
+        psFree(mask);
+        return NULL;
+    }
+    if (!psImageConvolveMask(mask, mask, PM_SUBTRACTION_MASK_CONVOLVE_2,
+                             PM_SUBTRACTION_MASK_FOOTPRINT_2,
+                             -footprint, footprint, -footprint, footprint)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to reconvolve bad pixels from mask 2.");
+        psFree(mask);
+        return NULL;
+    }
+
+    return mask;
+}
+
+
+bool pmSubtractionBorder(psImage *image, psImage *weight, psImage *mask,
+                         int size, psMaskType blank)
+{
+    PS_ASSERT_IMAGE_NON_NULL(image, false);
+    PS_ASSERT_IMAGE_TYPE(image, PS_TYPE_F32, false);
+    if (mask) {
+        PS_ASSERT_IMAGE_NON_NULL(mask, false);
+        PS_ASSERT_IMAGES_SIZE_EQUAL(mask, image, false);
+        PS_ASSERT_IMAGE_TYPE(mask, PS_TYPE_MASK, false);
+    }
+    if (weight) {
+        PS_ASSERT_IMAGE_NON_NULL(weight, false);
+        PS_ASSERT_IMAGES_SIZE_EQUAL(weight, image, false);
+        PS_ASSERT_IMAGE_TYPE(weight, PS_TYPE_F32, false);
+    }
+
+    int numCols = image->numCols, numRows = image->numRows; // Image dimensions
+
+    for (int y = size; y < numRows - size; y++) {
+        for (int x = 0; x < size; x++) {
+            markBlank(image, mask, weight, x, y, blank);
+        }
+        for (int x = numCols - size; x < numCols; x++) {
+            markBlank(image, mask, weight, x, y, blank);
+        }
+    }
+    for (int y = 0; y < size; y++) {
+        for (int x = 0; x < numCols; x++) {
+            markBlank(image, mask, weight, x, y, blank);
+        }
+    }
+    for (int y = numRows - size; y < numRows; y++) {
+        for (int x = 0; x < numCols; x++) {
+            markBlank(image, mask, weight, x, y, blank);
+        }
+    }
+
+    return true;
+}
+
+
+bool pmSubtractionMaskApply(psImage *image, psImage *weight, const psImage *mask, pmSubtractionMode mode)
+{
+    PS_ASSERT_IMAGE_NON_NULL(image, false);
+    PS_ASSERT_IMAGE_TYPE(image, PS_TYPE_F32, false);
+    if (weight) {
+        PS_ASSERT_IMAGE_NON_NULL(weight, false);
+        PS_ASSERT_IMAGE_TYPE(weight, PS_TYPE_F32, false);
+        PS_ASSERT_IMAGES_SIZE_EQUAL(weight, image, false);
+    }
+    PS_ASSERT_IMAGE_NON_NULL(mask, false);
+    PS_ASSERT_IMAGE_TYPE(mask, PS_TYPE_MASK, false);
+    PS_ASSERT_IMAGES_SIZE_EQUAL(mask, image, false);
+
+    bool maskVal = PM_SUBTRACTION_MASK_BORDER; // Value to mask
+    switch (mode) {
+      case PM_SUBTRACTION_MODE_1:
+        maskVal |= PM_SUBTRACTION_MASK_CONVOLVE_1;
+        break;
+      case PM_SUBTRACTION_MODE_2:
+        maskVal |= PM_SUBTRACTION_MASK_CONVOLVE_2;
+        break;
+      case PM_SUBTRACTION_MODE_DUAL:
+        maskVal |= PM_SUBTRACTION_MASK_CONVOLVE_2 | PM_SUBTRACTION_MASK_CONVOLVE_2;
+        break;
+      case PM_SUBTRACTION_MODE_ERR:
+      case PM_SUBTRACTION_MODE_UNSURE:
+      default:
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unsuppored subtraction mode: %x", mode);
+        return false;
+    }
+
+    int numCols = image->numCols, numRows = image->numRows; // Size of image
+    psMaskType **maskData = mask->data.PS_TYPE_MASK_DATA; // Dereference mask
+
+    for (int y = 0; y < numRows; y++) {
+        for (int x = 0; x < numCols; x++) {
+            if (maskData[y][x] & maskVal) {
+                image->data.F32[y][x] = NAN;
+                if (weight) {
+                    weight->data.F32[y][x] = NAN;
+                }
+            }
+        }
+    }
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionMask.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionMask.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionMask.h	(revision 22157)
@@ -0,0 +1,36 @@
+#ifndef PM_SUBTRACTION_MASK_H
+#define PM_SUBTRACTION_MASK_H
+
+#include <pslib.h>
+
+/// Generate a mask for use in the subtraction process
+psImage *pmSubtractionMask(const psImage *refMask, ///< Mask for the reference image (will be convolved)
+                           const psImage *inMask, ///< Mask for the input image, or NULL
+                           psMaskType maskVal, ///< Value to mask out
+                           int size, ///< Half-size of the kernel (pmSubtractionKernels.size)
+                           int footprint, ///< Half-size of the kernel footprint
+                           float badFrac, ///< Maximum fraction of bad input pixels to accept
+                           bool useFFT  ///< Use FFT to do convolution?
+    );
+
+/// Mark the non-convolved part of the image as blank
+bool pmSubtractionBorder(psImage *image,///< Image
+                         psImage *weight, ///< Weight map (or NULL)
+                         psImage *mask, ///< Mask (or NULL)
+                         int size,      ///< Kernel half-size
+                         psMaskType blank ///< Mask value for blank regions
+    );
+
+/// Apply the subtraction mask to an image and weight.
+///
+/// Unfortunately, image subtraction may result in a bi-modal image in masked areas, which can upset image
+/// statistics (very important for quantising images so that a product can be written out!).  This function
+/// sets masked areas to NAN in the image and weight.
+bool pmSubtractionMaskApply(psImage *image, ///< Image to which to apply mask
+                            psImage *weight, ///< Weight map to which to apply mask (or NULL)
+                            const psImage *mask, ///< Subtraction mask
+                            pmSubtractionMode mode ///< Subtraction mode
+    );
+
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionMatch.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionMatch.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionMatch.c	(revision 22157)
@@ -0,0 +1,751 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmSubtractionParams.h"
+#include "pmSubtractionKernels.h"
+#include "pmSubtractionStamps.h"
+#include "pmSubtractionEquation.h"
+#include "pmSubtraction.h"
+#include "pmSubtractionMask.h"
+#include "pmSubtractionMatch.h"
+
+#define KERNEL_MOSAIC 2                 // Half-number of kernel instances in the mosaic image
+#define BG_STAT PS_STAT_ROBUST_MEDIAN   // Statistic to use for background
+
+static bool useFFT = true;              // Do convolutions using FFT
+
+
+//#define TESTING
+//#define TESTING_MEMORY
+
+// Output memory usage information
+static void memCheck(const char *where)
+{
+#ifdef TESTING_MEMORY
+    psMemBlock **leaks = NULL;
+    int numLeaks = psMemCheckLeaks(0, &leaks, NULL, true);
+    size_t largestSize = 0;
+    psMemId largest = 0;
+    size_t totalSize = 0;
+    for (int i = 0; i < numLeaks; i++) {
+        psMemBlock *mb = leaks[i];
+        totalSize += mb->userMemorySize;
+        if (mb->userMemorySize > largestSize) {
+            largestSize = mb->userMemorySize;
+            largest = mb->id;
+        }
+    }
+    psFree(leaks);
+    fprintf(stderr, "%s:\n", where);
+    fprintf(stderr, "    Memory in use: %zd\n", totalSize);
+    fprintf(stderr, "    Largest block: %ld\n", largest);
+    fprintf(stderr, "    sbrk(): %zd\n", (size_t)sbrk(0));
+#endif
+    return;
+}
+
+
+static bool getStamps(pmSubtractionStampList **stamps, // Stamps to read
+                      const pmReadout *ro1, // Readout 1
+                      const pmReadout *ro2, // Readout 2
+                      const psImage *subMask, // Mask for subtraction, or NULL
+                      psImage *weight,  // Weight map
+                      const psRegion *region, // Region of interest, or NULL
+                      float threshold,  // Threshold for stamp finding
+                      float stampSpacing, // Spacing between stamps
+                      int size,         // Kernel half-size
+                      int footprint,     // Convolution footprint for stamps
+                      pmSubtractionMode mode // Mode for subtraction
+    )
+{
+    psTrace("psModules.imcombine", 3, "Finding stamps...\n");
+    *stamps = pmSubtractionStampsFind(*stamps, ro1->image, subMask, region, threshold, footprint,
+                                      stampSpacing, mode);
+    if (!*stamps) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to find stamps.");
+        return false;
+    }
+
+    memCheck("  find stamps");
+
+    psTrace("psModules.imcombine", 3, "Extracting stamps...\n");
+    if (!pmSubtractionStampsExtract(*stamps, ro1->image, ro2 ? ro2->image : NULL, weight, size)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to extract stamps.");
+        return false;
+    }
+
+    memCheck("   extract stamps");
+
+    return true;
+}
+
+
+// Record the variance factor in a readout
+static bool recordVarianceFactor(pmReadout *ro, // Readout of interest
+                                 float varFactor, // Variance factor
+                                 const psRegion *region // Region of interest
+                                 )
+{
+    if (region) {
+        varFactor *= (region->x1 - region->x1 + 1) * (region->y1 * region->y1 + 1) /
+            (ro->image->numCols * ro->image->numRows);
+    }
+
+    psMetadataItem *vfItem = psMetadataLookup(ro->analysis, PM_SUBTRACTION_ANALYSIS_VARFACTOR);
+    if (vfItem) {
+        psAssert(vfItem->type == PS_TYPE_F32, "Should be the type we said.");
+        vfItem->data.F32 += varFactor;
+    } else {
+        psMetadataAddF32(ro->analysis, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_VARFACTOR, 0,
+                         "Variance factor weighted by the area", varFactor);
+    }
+
+    return true;
+}
+
+bool pmSubtractionMatch(pmReadout *conv1, pmReadout *conv2, const pmReadout *ro1, const pmReadout *ro2,
+                        int footprint, float regionSize, float stampSpacing, float threshold,
+                        const psArray *sources, const char *stampsName,
+                        pmSubtractionKernelsType type, int size, int spatialOrder,
+                        const psVector *isisWidths, const psVector *isisOrders,
+                        int inner, int ringsOrder, int binning, float penalty,
+                        bool optimum, const psVector *optFWHMs, int optOrder, float optThreshold,
+                        int iter, float rej, psMaskType maskBad, psMaskType maskBlank,
+                        float badFrac, pmSubtractionMode mode)
+{
+    if (mode != PM_SUBTRACTION_MODE_2) {
+        PM_ASSERT_READOUT_NON_NULL(conv1, false);
+        if (conv1->image) {
+            psFree(conv1->image);
+            conv1->image = NULL;
+        }
+        if (conv1->mask) {
+            psFree(conv1->mask);
+            conv1->mask = NULL;
+        }
+        if (conv1->weight) {
+            psFree(conv1->weight);
+            conv1->weight = NULL;
+        }
+    }
+    if (mode != PM_SUBTRACTION_MODE_1) {
+        PM_ASSERT_READOUT_NON_NULL(conv2, false);
+        if (conv2->image) {
+            psFree(conv2->image);
+            conv2->image = NULL;
+        }
+        if (conv2->mask) {
+            psFree(conv2->mask);
+            conv2->mask = NULL;
+        }
+        if (conv2->weight) {
+            psFree(conv2->weight);
+            conv2->weight = NULL;
+        }
+    }
+
+    PM_ASSERT_READOUT_NON_NULL(ro1, false);
+    PM_ASSERT_READOUT_NON_NULL(ro2, false);
+    PM_ASSERT_READOUT_IMAGE(ro1, false);
+    PM_ASSERT_READOUT_IMAGE(ro2, false);
+    PS_ASSERT_IMAGES_SIZE_EQUAL(ro1->image, ro2->image, false);
+
+    PS_ASSERT_INT_NONNEGATIVE(footprint, false);
+    // regionSize can be just about anything (except maybe negative, but it can be NAN)
+    PS_ASSERT_FLOAT_LARGER_THAN(stampSpacing, 0.0, false);
+    // Don't care what threshold is
+    if (sources) {
+        PS_ASSERT_ARRAY_NON_NULL(sources, false);
+    }
+    // stampsName may be anything
+    // We'll check kernel type when we allocate the kernels
+    PS_ASSERT_INT_POSITIVE(size, false);
+    PS_ASSERT_INT_NONNEGATIVE(spatialOrder, false);
+    if (isisWidths || isisOrders) {
+        PS_ASSERT_VECTOR_NON_NULL(isisWidths, false);
+        PS_ASSERT_VECTOR_TYPE(isisWidths, PS_TYPE_F32, false);
+        PS_ASSERT_VECTOR_NON_NULL(isisOrders, false);
+        PS_ASSERT_VECTOR_TYPE(isisOrders, PS_TYPE_S32, false);
+        PS_ASSERT_VECTORS_SIZE_EQUAL(isisWidths, isisOrders, false);
+    }
+    PS_ASSERT_INT_NONNEGATIVE(inner, false);
+    PS_ASSERT_INT_NONNEGATIVE(ringsOrder, false);
+    PS_ASSERT_INT_POSITIVE(binning, false);
+    if (optimum) {
+        PS_ASSERT_VECTOR_NON_NULL(optFWHMs, false);
+        PS_ASSERT_INT_NONNEGATIVE(optOrder, false);
+        PS_ASSERT_FLOAT_LARGER_THAN(optThreshold, 0.0, false);
+        PS_ASSERT_FLOAT_LESS_THAN_OR_EQUAL(optThreshold, 1.0, false);
+    }
+    PS_ASSERT_INT_POSITIVE(iter, false);
+    PS_ASSERT_FLOAT_LARGER_THAN(rej, 0.0, false);
+    // Don't care about maskBad
+    // Don't care about maskBlank
+    if (isfinite(badFrac)) {
+        PS_ASSERT_FLOAT_LARGER_THAN(badFrac, 0.0, NULL);
+        PS_ASSERT_FLOAT_LESS_THAN_OR_EQUAL(badFrac, 1.0, NULL);
+    }
+
+    // If the stamp footprint is smaller than the kernel size, then we won't get much signal in the outer
+    // parts of the kernel, which can result in bad matching artifacts.
+    if (footprint < size) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "Stamp footprint (%d) should be larger than or equal to the kernel size (%d)",
+                footprint, size);
+        return false;
+    }
+
+    // Where does our weight map come from?
+    // Getting the weight exactly right is not necessary --- it's just used for weighting.
+    psImage *weight = NULL;             // Weight image to use
+    if (ro1->weight && ro2->weight) {
+        weight = (psImage*)psBinaryOp(NULL, ro1->weight, "+", ro2->weight);
+    } else if (ro1->weight) {
+        weight = psMemIncrRefCounter(ro1->weight);
+    } else if (ro2->weight) {
+        weight = psMemIncrRefCounter(ro2->weight);
+    } else {
+        weight = (psImage*)psBinaryOp(NULL, ro1->image, "+", ro2->image);
+    }
+
+    // Putting important variable declarations here, since they are freed after a "goto" if there is an error.
+    psImage *subMask = NULL;            // Mask for subtraction
+    psRegion *region = NULL;            // Iso-kernel region
+    psString regionString = NULL;       // String for region
+    pmSubtractionStampList *stamps = NULL; // Stamps for matching PSF
+    pmSubtractionKernels *kernels = NULL; // Kernel basis functions
+
+    int numCols = ro1->image->numCols, numRows = ro1->image->numRows; // Image dimensions
+
+    memCheck("start");
+
+    subMask = pmSubtractionMask(ro1->mask, ro2 ? ro2->mask : NULL, maskBad, size, footprint,
+                                badFrac, useFFT);
+    if (!subMask) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to generate subtraction mask.");
+        psFree(weight);
+        return false;
+    }
+
+    memCheck("mask");
+
+    // Get region of interest
+    int xRegions = 1, yRegions = 1;     // Number of iso-kernel regions
+    float xRegionSize = 0, yRegionSize = 0; // Size of iso-kernel regions
+    if (isfinite(regionSize) && regionSize != 0.0) {
+        xRegions = numCols / regionSize + 1;
+        yRegions = numRows / regionSize + 1;
+        xRegionSize = (float)numCols / (float)xRegions;
+        yRegionSize = (float)numRows / (float)yRegions;
+        region = psRegionAlloc(NAN, NAN, NAN, NAN);
+    }
+
+    // Iterate over iso-kernel regions
+    for (int j = 0; j < yRegions; j++) {
+        for (int i = 0; i < xRegions; i++) {
+            psTrace("psModules.imcombine", 1, "Subtracting region %d of %d...\n",
+                    j * xRegions + i + 1, xRegions * yRegions);
+            if (region) {
+                *region = psRegionSet((int)(i * xRegionSize), (int)((i + 1) * xRegionSize),
+                                      (int)(j * yRegionSize), (int)((j + 1) * yRegionSize));
+                psFree(regionString);
+                regionString = psRegionToString(*region);
+                psTrace("psModules.imcombine", 3, "Iso-kernel region: %s out of %d,%d\n",
+                        regionString, numCols, numRows);
+            }
+
+            if (sources) {
+                stamps = pmSubtractionStampsSetFromSources(sources, subMask, region, footprint,
+                                                           stampSpacing, mode);
+            } else if (stampsName && strlen(stampsName) > 0) {
+                stamps = pmSubtractionStampsSetFromFile(stampsName, ro1->image, subMask, region, footprint,
+                                                        stampSpacing, mode);
+            }
+
+            // We get the stamps here; we will also attempt to get stamps at the first iteration, but it
+            // doesn't matter.
+            if (!getStamps(&stamps, ro1, ro2, subMask, weight, NULL, threshold, stampSpacing,
+                           size, footprint, mode)) {
+                goto MATCH_ERROR;
+            }
+
+            if (mode == PM_SUBTRACTION_MODE_UNSURE) {
+                // Get backgrounds
+                psStats *bgStats = psStatsAlloc(BG_STAT); // Statistics for background
+                psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, 0); // Random number generator
+                psVector *buffer = NULL;// Buffer for stats
+                if (!psImageBackground(bgStats, &buffer, ro1->image, ro1->mask, maskBad, rng)) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to measure background of image 1.");
+                    psFree(bgStats);
+                    psFree(rng);
+                    psFree(buffer);
+                    goto MATCH_ERROR;
+                }
+                float bg1 = psStatsGetValue(bgStats, BG_STAT); // Background for image 1
+                if (!psImageBackground(bgStats, &buffer, ro2->image, ro2->mask, maskBad, rng)) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to measure background of image 2.");
+                    psFree(bgStats);
+                    psFree(rng);
+                    psFree(buffer);
+                    goto MATCH_ERROR;
+                }
+                float bg2 = psStatsGetValue(bgStats, BG_STAT); // Background for image 2
+                psFree(bgStats);
+                psFree(rng);
+                psFree(buffer);
+
+                pmSubtractionMode newMode = pmSubtractionOrder(stamps, bg1, bg2); // Subtraction mode to use
+                switch (newMode) {
+                  case PM_SUBTRACTION_MODE_1:
+                    psLogMsg("psModules.imcombine", PS_LOG_INFO, "Convolving image 1 to match image 2.");
+                    break;
+                  case PM_SUBTRACTION_MODE_2:
+                    psLogMsg("psModules.imcombine", PS_LOG_INFO, "Convolving image 2 to match image 1.");
+                    break;
+                  default:
+                    psError(PS_ERR_UNKNOWN, false, "Unable to determine subtraction order.");
+                    goto MATCH_ERROR;
+                }
+                mode = newMode;
+            }
+
+            // Define kernel basis functions
+            if (optimum && (type == PM_SUBTRACTION_KERNEL_ISIS || type == PM_SUBTRACTION_KERNEL_GUNK)) {
+                kernels = pmSubtractionKernelsOptimumISIS(type, size, inner, spatialOrder, optFWHMs, optOrder,
+                                                          stamps, footprint, optThreshold, penalty, mode);
+                if (!kernels) {
+                    psErrorClear();
+                    psWarning("Unable to derive optimum ISIS kernel --- switching to default.");
+                }
+            }
+            if (kernels == NULL) {
+                // Not an ISIS/GUNK kernel, or the optimum kernel search failed
+                kernels = pmSubtractionKernelsGenerate(type, size, spatialOrder, isisWidths, isisOrders,
+                                                       inner, binning, ringsOrder, penalty, mode);
+            }
+
+            // Add analysis metadata
+            {
+                psRegion *subRegion;    // Region over which subtraction was performed
+                if (region) {
+                    subRegion = psMemIncrRefCounter(region);
+                } else {
+                    subRegion = psRegionAlloc(0, numCols, 0, numRows);
+                }
+
+                if (mode == PM_SUBTRACTION_MODE_1 || mode == PM_SUBTRACTION_MODE_DUAL) {
+                    psMetadataAddPtr(conv1->analysis, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_KERNEL,
+                                     PS_DATA_UNKNOWN | PS_META_DUPLICATE_OK, "Subtraction kernels", kernels);
+                    psMetadataAddS32(conv1->analysis, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_MODE,
+                                     PS_META_DUPLICATE_OK, "Subtraction kernels", mode);
+                    psMetadataAddPtr(conv1->analysis, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_REGION,
+                                     PS_DATA_REGION | PS_META_DUPLICATE_OK,
+                                     "Region over which subtraction was performed", subRegion);
+                }
+                if (mode == PM_SUBTRACTION_MODE_2 || mode == PM_SUBTRACTION_MODE_DUAL) {
+                    psMetadataAddPtr(conv2->analysis, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_KERNEL,
+                                     PS_DATA_UNKNOWN | PS_META_DUPLICATE_OK, "Subtraction kernels", kernels);
+                    psMetadataAddS32(conv2->analysis, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_MODE,
+                                     PS_META_DUPLICATE_OK, "Subtraction kernels", mode);
+                    psMetadataAddPtr(conv2->analysis, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_REGION,
+                                     PS_DATA_REGION | PS_META_DUPLICATE_OK,
+                                     "Region over which subtraction was performed", subRegion);
+                }
+                psFree(subRegion);
+            }
+
+            memCheck("kernels");
+
+            float rmsStamps = NAN;      // RMS for stamps
+            int numStamps = 0;          // Number of good stamps
+            int numRejected = -1;       // Number of rejected stamps in each iteration
+            for (int k = 0; k < iter && numRejected != 0; k++) {
+                psLogMsg("psModules.imcombine", PS_LOG_INFO, "Iteration %d.", k);
+
+                if (!getStamps(&stamps, ro1, ro2, subMask, weight, region, threshold, stampSpacing,
+                               size, footprint, mode)) {
+                    goto MATCH_ERROR;
+                }
+
+                psTrace("psModules.imcombine", 3, "Calculating equation...\n");
+                if (!pmSubtractionCalculateEquation(stamps, kernels)) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to calculate least-squares equation.");
+                    goto MATCH_ERROR;
+                }
+
+                memCheck("  calculate equation");
+
+                psTrace("psModules.imcombine", 3, "Solving equation...\n");
+
+                if (!pmSubtractionSolveEquation(kernels, stamps)) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to calculate least-squares equation.");
+                    goto MATCH_ERROR;
+                }
+
+                memCheck("  solve equation");
+
+                psVector *deviations = pmSubtractionCalculateDeviations(stamps, kernels); // Stamp deviations
+                if (!deviations) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to calculate deviations.");
+                    goto MATCH_ERROR;
+                }
+
+                memCheck("   calculate deviations");
+
+                psTrace("psModules.imcombine", 3, "Rejecting stamps...\n");
+                numRejected = pmSubtractionRejectStamps(&rmsStamps, &numStamps, stamps, deviations,
+                                                        subMask, rej, footprint);
+                if (numRejected < 0) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to reject stamps.");
+                    psFree(deviations);
+                    goto MATCH_ERROR;
+                }
+                psFree(deviations);
+
+                memCheck("  reject stamps");
+            }
+
+            if (numRejected > 0) {
+                psTrace("psModules.imcombine", 3, "Solving equation...\n");
+                if (!pmSubtractionSolveEquation(kernels, stamps)) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to calculate least-squares equation.");
+                    goto MATCH_ERROR;
+                }
+                psVector *deviations = pmSubtractionCalculateDeviations(stamps, kernels); // Stamp deviations
+                if (!deviations) {
+                    psError(PS_ERR_UNKNOWN, false, "Unable to calculate deviations.");
+                    goto MATCH_ERROR;
+                }
+                pmSubtractionRejectStamps(&rmsStamps, &numStamps, stamps, deviations,
+                                          subMask, NAN, footprint);
+                psFree(deviations);
+            }
+            psFree(stamps);
+            stamps = NULL;
+
+            if (mode == PM_SUBTRACTION_MODE_1 || mode == PM_SUBTRACTION_MODE_DUAL) {
+                psMetadataAddS32(conv1->analysis, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_STAMPS_NUM,
+                                 PS_META_DUPLICATE_OK, "Number of good stamps", numStamps);
+                psMetadataAddF32(conv1->analysis, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_STAMPS_RMS,
+                                 PS_META_DUPLICATE_OK, "RMS deviation of stamps", rmsStamps);
+            }
+            if (mode == PM_SUBTRACTION_MODE_2 || mode == PM_SUBTRACTION_MODE_DUAL) {
+                psMetadataAddS32(conv2->analysis, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_STAMPS_NUM,
+                                 PS_META_DUPLICATE_OK, "Number of good stamps", numStamps);
+                psMetadataAddF32(conv2->analysis, PS_LIST_TAIL, PM_SUBTRACTION_ANALYSIS_STAMPS_RMS,
+                                 PS_META_DUPLICATE_OK, "RMS deviation of stamps", rmsStamps);
+            }
+
+            memCheck("solution");
+
+            {
+                psTrace("psModules.imcombine", 2, "Generating diagnostics...\n");
+                // Generate image with convolution kernels
+                int fullSize = 2 * size + 1 + 1;    // Full size of kernel
+                int imageSize = (2 * KERNEL_MOSAIC + 1) * fullSize;
+                psImage *convKernels = psImageAlloc((mode == PM_SUBTRACTION_MODE_DUAL ? 2 : 1) *
+                                                    imageSize - 1 +
+                                                    (mode == PM_SUBTRACTION_MODE_DUAL ? 4 : 0),
+                                                    imageSize - 1, PS_TYPE_F32);
+                psImageInit(convKernels, NAN);
+                for (int j = -KERNEL_MOSAIC; j <= KERNEL_MOSAIC; j++) {
+                    for (int i = -KERNEL_MOSAIC; i <= KERNEL_MOSAIC; i++) {
+                        psImage *kernel = pmSubtractionKernelImage(kernels, (float)i / (float)KERNEL_MOSAIC,
+                                                                   (float)j / (float)KERNEL_MOSAIC,
+                                                                   false); // Image of the kernel
+                        if (!kernel) {
+                            psError(PS_ERR_UNKNOWN, false, "Unable to generate kernel image.");
+                            psFree(convKernels);
+                            goto MATCH_ERROR;
+                        }
+
+                        if (psImageOverlaySection(convKernels, kernel, (i + KERNEL_MOSAIC) * fullSize,
+                                                  (j + KERNEL_MOSAIC) * fullSize, "=") == 0) {
+                            psError(PS_ERR_UNKNOWN, false, "Unable to overlay kernel image.");
+                            psFree(kernel);
+                            psFree(convKernels);
+                            goto MATCH_ERROR;
+                        }
+                        psFree(kernel);
+
+                        if (mode == PM_SUBTRACTION_MODE_DUAL) {
+                            kernel = pmSubtractionKernelImage(kernels, (float)i / (float)KERNEL_MOSAIC,
+                                                              (float)j / (float)KERNEL_MOSAIC,
+                                                              true); // Image of the kernel
+                            if (!kernel) {
+                                psError(PS_ERR_UNKNOWN, false, "Unable to generate kernel image.");
+                                psFree(convKernels);
+                                goto MATCH_ERROR;
+                            }
+
+                            if (psImageOverlaySection(convKernels, kernel,
+                                                      (2 * KERNEL_MOSAIC + 1 + i + KERNEL_MOSAIC) * fullSize +
+                                                      4,
+                                                      (j + KERNEL_MOSAIC) * fullSize, "=") == 0) {
+                                psError(PS_ERR_UNKNOWN, false, "Unable to overlay kernel image.");
+                                psFree(kernel);
+                                psFree(convKernels);
+                                goto MATCH_ERROR;
+                            }
+                            psFree(kernel);
+                        }
+
+                    }
+                }
+
+                psString comment = NULL; // Comment for metadata
+                psStringAppend(&comment, "Subtraction kernel for region %s", regionString);
+                psMetadataAddImage(conv1->analysis, PS_LIST_TAIL, "SUBTRACTION.KERNEL.IMAGE",
+                                   PS_META_DUPLICATE_OK, comment, convKernels);
+                psFree(comment);
+                psFree(convKernels);
+            }
+
+#if 0
+            {
+                // Generate images of the kernel components
+                psMetadata *header = psMetadataAlloc(); // Header
+                for (int i = 0; i < solution->n; i++) {
+                    psString name = NULL;       // Header keyword
+                    psStringAppend(&name, "SOLN%04d", i);
+                    psMetadataAddF64(header, PS_LIST_TAIL, name, 0, NULL, solution->data.F64[i]);
+                    psFree(name);
+                }
+                psArray *kernelImages = pmSubtractionKernelSolutions(solution, kernels, 0.0, 0.0);
+                psFits *kernelFile = psFitsOpen("kernels.fits", "w");
+                (void)psFitsWriteImageCube(kernelFile, header, kernelImages, NULL);
+                psFitsClose(kernelFile);
+                psFree(kernelImages);
+                psFree(header);
+            }
+#endif
+
+            memCheck("diag outputs");
+
+            psTrace("psModules.imcombine", 2, "Convolving...\n");
+            if (!pmSubtractionConvolve(conv1, conv2, ro1, ro2, subMask, maskBlank, region, kernels,
+                                       true, useFFT)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to convolve image.");
+                goto MATCH_ERROR;
+            }
+
+            // Set the variance factors
+            switch (mode) {
+              case PM_SUBTRACTION_MODE_1: {
+                  recordVarianceFactor(conv1, pmSubtractionVarianceFactor(kernels, 0.5, 0.5, false),
+                                       region);
+                  break;
+              }
+              case PM_SUBTRACTION_MODE_2:
+                recordVarianceFactor(conv1, pmSubtractionVarianceFactor(kernels, 0.5, 0.5, false),
+                                     region);
+                break;
+              case PM_SUBTRACTION_MODE_DUAL:
+                recordVarianceFactor(conv1, pmSubtractionVarianceFactor(kernels, 0.5, 0.5, false),
+                                     region);
+                recordVarianceFactor(conv2, pmSubtractionVarianceFactor(kernels, 0.5, 0.5, true),
+                                     region);
+                break;
+              default:
+                psAbort("Should never reach here.");
+            }
+
+            psFree(kernels);
+            kernels = NULL;
+
+            // There is data in the readout now
+            if (mode == PM_SUBTRACTION_MODE_1 || mode == PM_SUBTRACTION_MODE_DUAL) {
+                conv1->data_exists = true;
+                if (conv1->parent) {
+                    conv1->parent->data_exists = true;
+                    conv1->parent->parent->data_exists = true;
+                }
+            }
+            if (mode == PM_SUBTRACTION_MODE_2 || mode == PM_SUBTRACTION_MODE_DUAL) {
+                conv2->data_exists = true;
+                if (conv2->parent) {
+                    conv2->parent->data_exists = true;
+                    conv2->parent->parent->data_exists = true;
+                }
+            }
+        }
+    }
+    psFree(region);
+    region = NULL;
+    psFree(regionString);
+    regionString = NULL;
+    psFree(subMask);
+    subMask = NULL;
+    psFree(weight);
+    weight = NULL;
+
+    if (!pmSubtractionBorder(conv1->image, conv1->weight, conv1->mask, size, maskBlank)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to set border of convolved image.");
+        goto MATCH_ERROR;
+    }
+
+    memCheck("convolution");
+
+
+#ifdef TESTING
+    {
+        if (mode == PM_SUBTRACTION_MODE_1 || mode == PM_SUBTRACTION_MODE_DUAL) {
+            psFits *fits = psFitsOpen("convolved1.fits", "w");
+            psFitsWriteImage(fits, NULL, conv1->image, 0, NULL);
+            psFitsClose(fits);
+        }
+
+        if (mode == PM_SUBTRACTION_MODE_2 || mode == PM_SUBTRACTION_MODE_DUAL) {
+            psFits *fits = psFitsOpen("convolved2.fits", "w");
+            psFitsWriteImage(fits, NULL, conv2->image, 0, NULL);
+            psFitsClose(fits);
+        }
+    }
+#endif
+
+    return true;
+
+MATCH_ERROR:
+    psFree(region);
+    psFree(regionString);
+    psFree(subMask);
+    psFree(kernels);
+    psFree(stamps);
+    psFree(weight);
+    return false;
+}
+
+
+// Determine a rough width (integer value) of the star in the image
+// XXX Could improve this by using a user-provided list of floating-point widths (or an end point and
+// increment).
+static int subtractionOrderWidth(const psKernel *kernel, // Image
+                                 float bg, // Background in image
+                                 int size, // Maximum size
+                                 psArray **models, // Buffer of models
+                                 psVector **modelSums // Buffer of model sums
+    )
+{
+    assert(kernel);
+    assert(models);
+    assert(modelSums);
+
+    int xMin = kernel->xMin, xMax = kernel->xMax; // Bounds in x
+    int yMin = kernel->yMin, yMax = kernel->yMax; // Bounds in y
+
+    // Generate models
+    if (!*models) {
+        assert(!*modelSums);
+        *models = psArrayAlloc(size);
+        *modelSums = psVectorAlloc(size, PS_TYPE_F64);
+        for (int sigma = 0; sigma < size; sigma++) {
+            psKernel *model = psKernelAlloc(xMin, xMax, yMin, yMax); // Gaussian model
+            float invSigma2 = 1.0 / (float)PS_SQR(1 + sigma); // Inverse sigma squared
+            double sumGG = 0.0;         // Sum of square of Gaussian
+            for (int y = yMin; y <= yMax; y++) {
+                int y2 = PS_SQR(y);     // y squared
+                for (int x = xMin; x <= xMax; x++) {
+                    float rad2 = PS_SQR(x) + y2; // Radius squared
+                    float value = expf(-rad2 * invSigma2); // Model value
+                    model->kernel[y][x] = value;
+                    sumGG += PS_SQR(value);
+                }
+            }
+            (*models)->data[sigma] = model;
+            (*modelSums)->data.F64[sigma] = sumGG;
+        }
+    }
+
+    // Fit gaussians of varying widths to the image, record the chi^2
+    psVector *chi2 = psVectorAlloc(size, PS_TYPE_F32); // chi^2 as a function of radius
+    for (int sigma = 0; sigma < size; sigma++) {
+        double sumFG = 0.0; // Sum for calculating the normalisation of the Gaussian
+        psKernel *model = (*models)->data[sigma]; // Model of interest
+        for (int y = yMin; y <= yMax; y++) {
+            for (int x = xMin; x <= xMax; x++) {
+                sumFG += model->kernel[y][x] * (kernel->kernel[y][x] - bg);
+            }
+        }
+        float norm = sumFG / (*modelSums)->data.F64[sigma]; // Normalisation for Gaussian
+        double sumDev2 = 0.0;           // Sum of square deviations
+        for (int y = yMin; y <= yMax; y++) {
+            for (int x = xMin; x <= xMax; x++) {
+                float dev = kernel->kernel[y][x] - bg - norm * model->kernel[y][x]; // Deviation
+                sumDev2 += PS_SQR(dev);
+            }
+        }
+        chi2->data.F32[sigma] = sumDev2;
+    }
+
+    // Find the minimum chi^2
+    int bestIndex = -1;                 // Index of best chi^2
+    float bestChi2 = INFINITY;          // Best chi^2
+    for (int i = 0; i < size; i++) {
+        if (chi2->data.F32[i] < bestChi2) {
+            bestChi2 = chi2->data.F32[i];
+            bestIndex = i;
+        }
+    }
+    psFree(chi2);
+
+    return bestIndex + 1;
+}
+
+pmSubtractionMode pmSubtractionOrder(pmSubtractionStampList *stamps, float bg1, float bg2)
+{
+    PM_ASSERT_SUBTRACTION_STAMP_LIST_NON_NULL(stamps, PM_SUBTRACTION_MODE_ERR);
+
+    psVector *mask = psVectorAlloc(stamps->num, PS_TYPE_MASK); // Mask for stamps
+    psVector *ratios = psVectorAlloc(stamps->num, PS_TYPE_F32); // Ratios of widths
+    psArray *models = NULL;             // Gaussian models
+    psVector *modelSums = NULL;         // Gaussian model sums
+    for (int i = 0; i < stamps->num; i++) {
+        pmSubtractionStamp *stamp = stamps->stamps->data[i]; // Stamp of interest
+        if (stamp->status != PM_SUBTRACTION_STAMP_CALCULATE && stamp->status != PM_SUBTRACTION_STAMP_USED) {
+            mask->data.PS_TYPE_MASK_DATA[i] = 0xff;
+            continue;
+        }
+
+        // Widths of stars
+        int width1 = subtractionOrderWidth(stamp->image1, bg1, stamps->footprint, &models, &modelSums);
+        int width2 = subtractionOrderWidth(stamp->image2, bg2, stamps->footprint, &models, &modelSums);
+
+        if (width1 == 0 || width2 == 0) {
+            ratios->data.F32[i] = NAN;
+            mask->data.PS_TYPE_MASK_DATA[i] = 0xff;
+        } else {
+            ratios->data.F32[i] = (float)width1 / (float)width2;
+            mask->data.PS_TYPE_MASK_DATA[i] = 0;
+        }
+    }
+    psFree(models);
+    psFree(modelSums);
+
+    psStats *stats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN);
+    if (!psVectorStats(stats, ratios, NULL, mask, 0xff)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to calculate statistics for moments ratio.");
+        psFree(mask);
+        psFree(ratios);
+        psFree(stats);
+        return PM_SUBTRACTION_MODE_ERR;
+    }
+    psFree(ratios);
+    psFree(mask);
+
+    psLogMsg("psModules.imcombine", PS_LOG_INFO, "Median width ratio: %lf", stats->robustMedian);
+    pmSubtractionMode mode = (stats->robustMedian <= 1.0 ? PM_SUBTRACTION_MODE_1 : PM_SUBTRACTION_MODE_2);
+    psFree(stats);
+
+    return mode;
+}
+
+
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionMatch.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionMatch.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionMatch.h	(revision 22157)
@@ -0,0 +1,59 @@
+#ifndef PM_SUBTRACTION_MATCH_H
+#define PM_SUBTRACTION_MATCH_H
+
+#include <pslib.h>
+
+#include <pmHDU.h>
+#include <pmFPA.h>
+#include <pmSubtractionKernels.h>
+#include <pmSubtractionStamps.h>
+
+#define PM_SUBTRACTION_ANALYSIS_KERNEL "SUBTRACTION.KERNEL" // Name of kernel in the analysis metadata
+#define PM_SUBTRACTION_ANALYSIS_MODE "SUBTRACTION.MODE" // Name of subtraction mode in the analysis metadata
+#define PM_SUBTRACTION_ANALYSIS_REGION "SUBTRACTION.REGION" // Name of subtraction region in the analysis MD
+#define PM_SUBTRACTION_ANALYSIS_STAMPS_RMS "SUBTRACTION.RMS" // Name of stamp rms in the analysis metadata
+#define PM_SUBTRACTION_ANALYSIS_STAMPS_NUM "SUBTRACTION.NUM"// Name of the number of stamps in the analysis MD
+#define PM_SUBTRACTION_ANALYSIS_VARFACTOR "SUBTRACTION.VARFACTOR"// Name of variance factor in the analysis MD
+
+/// Match two images
+bool pmSubtractionMatch(pmReadout *conv1, ///< Output convolved data for image 1
+                        pmReadout *conv2, ///< Output convolved data for image 2
+                        const pmReadout *ro1, ///< Image 1
+                        const pmReadout *ro2, ///< Image 2
+                        // Stamp parameters
+                        int footprint,  ///< Stamp half-size
+                        float regionSize, ///< Typical size of iso-kernel regions
+                        float stampSpacing, ///< Typical spacing between stamps
+                        float threshold, ///< Threshold for stamps
+                        const psArray *sources, ///< Sources for stamps
+                        const char *stampsName, ///< Filename for stamps
+                        // Kernel parameters
+                        pmSubtractionKernelsType type, ///< Kernel type
+                        int size,       ///< Kernel half-size
+                        int order,      ///< Spatial polynomial order
+                        const psVector *widths, ///< ISIS Gaussian widths
+                        const psVector *orders, ///< ISIS Polynomial orders
+                        int inner,      ///< Inner radius for various kernel types
+                        int ringsOrder, ///< RINGS polynomial order
+                        int binning,    ///< SPAM kernel binning
+                        float penalty,  ///< Penalty for wideness
+                        bool optimum,   ///< Search for optimum ISIS kernel?
+                        const psVector *optFWHMs, ///< FWHMs for optimum search
+                        int optOrder,   ///< Maximum order for optimum search
+                        float optThreshold, ///< Threshold for optimum search (0..1)
+                        // Operational parameters
+                        int iter,       ///< Rejection iterations
+                        float rej,      ///< Rejection threshold
+                        psMaskType maskBad, ///< Value to mask
+                        psMaskType maskBlank, ///< Mask for blank region
+                        float badFrac,   ///< Maximum fraction of bad input pixels to accept
+                        pmSubtractionMode mode ///< Mode of subtraction; may be modified
+    );
+
+/// Determine which image to convolve
+pmSubtractionMode pmSubtractionOrder(pmSubtractionStampList *stamps, ///< Stamps that have been extracted
+                                     float bg1, float bg2 // Background for each image
+    );
+
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionParams.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionParams.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionParams.c	(revision 22157)
@@ -0,0 +1,510 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <pslib.h>
+
+#include "pmSubtractionStamps.h"
+#include "pmSubtraction.h"
+#include "pmSubtractionParams.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if 0
+// Convolve the reference stamp by the kernel
+static psKernel *convolveStamp(const pmSubtractionStamp *stamp, // Stamp to be convolved
+                               const psKernel *kernel, // Kernel by which to convolve
+                               int footprint // Size of area to be convolved
+    )
+{
+    psKernel *convolution = psKernelAlloc(-footprint, footprint, -footprint, footprint); // Result
+    psKernel *reference = stamp->reference; // Reference stamp, to be convolved
+
+    // Range of kernel
+    int uMin = kernel->xMin;
+    int uMax = kernel->xMax;
+    int vMin = kernel->yMin;
+    int vMax = kernel->yMax;
+
+    for (int y = -footprint; y <= footprint; y++) {
+        psF32 *conv = &convolution->kernel[y][-footprint]; // Dereference convolution
+        for (int x = -footprint; x <= footprint; x++, conv++) {
+            *conv = 0.0;
+
+            int xStart = x + uMin;      // Start index for convolution
+            for (int v = vMin; v <= vMax; v++) {
+                psF32 *ref = &reference->kernel[y + v][xStart]; // Dereference reference image
+                psF32 *krnl = &kernel->kernel[v][uMin]; // Dereference kernel in x
+                for (int u = uMin; u <= uMax; u++, ref++, krnl++) {
+                    *conv += *ref * *krnl;
+                }
+            }
+        }
+    }
+
+    return convolution;
+}
+#endif
+
+/// Select the appropriate convolution, given the kernel basis function and subtraction mode
+static inline psKernel *selectConvolution(const pmSubtractionStamp *stamp, // Stamp
+                                          int kernelIndex, // Index for kernel component
+                                          pmSubtractionMode mode // Mode of subtraction
+    )
+{
+    switch (mode) {
+      case PM_SUBTRACTION_MODE_1:
+        return stamp->convolutions1->data[kernelIndex];
+      case PM_SUBTRACTION_MODE_2:
+        return stamp->convolutions2->data[kernelIndex];
+      default:
+        psAbort("Unsupported subtraction mode: %x", mode);
+    }
+    return NULL;                        // Unreached
+}
+
+// Accumulate cross-term sums for a stamp
+static void accumulateCross(double *sumI, // Sum of I(x)/sigma(x)^2
+                            double *sumII, // Sum of I(x)^2/sigma(x)^2
+                            double *sumIC, // Sum of I(x)conv(x)/sigma(x)^2
+                            const pmSubtractionStamp *stamp, // Stamp with weight
+                            const psKernel *target, // Target stamp
+                            int kernelIndex, // Index for kernel component
+                            int footprint, // Size of region of interest
+                            pmSubtractionMode mode // Mode of subtraction
+    )
+{
+    psKernel *weight = stamp->weight;   // Weight, sigma(x)^2
+    psKernel *convolution = selectConvolution(stamp, kernelIndex, mode); // Convolution of interest
+
+    for (int y = -footprint; y <= footprint; y++) {
+        psF32 *in = &target->kernel[y][-footprint]; // Dereference input
+        psF32 *wt = &weight->kernel[y][-footprint]; // Dereference weight
+        psF32 *conv = &convolution->kernel[y][-footprint]; // Dereference convolution
+        for (int x = -footprint; x <= footprint; x++, in++, wt++, conv++) {
+            double temp = *in / *wt; // Temporary product
+            *sumI += temp;
+            *sumII += *in * temp;
+            *sumIC += *conv * temp;
+        }
+    }
+    return;
+}
+
+// Accumulate convolution sums for a stamp
+static void accumulateConvolutions(double *sumC, // Sum of conv(x)/sigma(x)^2
+                                   double *sumCC, // Sum of conv(x)^2/sigma(x)^2
+                                   const pmSubtractionStamp *stamp, // Stamp with input and weight
+                                   int kernelIndex, // Index for kernel component
+                                   int footprint, // Size of region of interest
+                                   pmSubtractionMode mode // Mode of subtraction
+    )
+{
+    psKernel *weight = stamp->weight;   // Weight, sigma(x)^2
+    psKernel *convolution = selectConvolution(stamp, kernelIndex, mode); // Convolution of interest
+
+    for (int y = -footprint; y <= footprint; y++) {
+        psF32 *wt = &weight->kernel[y][-footprint]; // Dereference weight
+        psF32 *conv = &convolution->kernel[y][-footprint]; // Dereference convolution
+        for (int x = -footprint; x <= footprint; x++, wt++, conv++) {
+            double convNoise = *conv / *wt; // Temporary product
+            *sumC += convNoise;
+            *sumCC += *conv * convNoise;
+        }
+    }
+    return;
+}
+
+static double accumulateChi2(const psKernel *target, // Target stamp
+                             pmSubtractionStamp *stamp, // Stamp with weight
+                             int kernelIndex, // Index for kernel component
+                             double coeff, // Coefficient of convolution
+                             double bg,  // Background term
+                             int footprint, // Size of region of interest
+                             pmSubtractionMode mode // Mode of subtraction
+    )
+{
+    double chi2 = 0.0;
+    psKernel *weight = stamp->weight;   // Weight, sigma(x)^2
+    psKernel *convolution = selectConvolution(stamp, kernelIndex, mode); // Convolution of interest
+
+    for (int y = -footprint; y <= footprint; y++) {
+        psF32 *in = &target->kernel[y][-footprint]; // Dereference input
+        psF32 *wt = &weight->kernel[y][-footprint]; // Dereference weight
+        psF32 *conv = &convolution->kernel[y][-footprint]; // Dereference convolution
+        for (int x = -footprint; x <= footprint; x++, in++, wt++, conv++) {
+            chi2 += PS_SQR(*in - bg - coeff * *conv) / *wt;
+        }
+    }
+
+    return chi2;
+}
+
+// Return the initial value of chi^2
+static double initialChi2(const psKernel *target, // Target stamp
+                          const pmSubtractionStamp *stamp, // Stamp with weight
+                          int footprint, // Size of convolution
+                          pmSubtractionMode mode // Mode of subtraction
+    )
+{
+    psKernel *weight = stamp->weight;   // Weight map
+    psKernel *source;                   // Source stamp
+    switch (mode) {
+      case PM_SUBTRACTION_MODE_1:
+        source = stamp->image1;
+        break;
+      case PM_SUBTRACTION_MODE_2:
+        source = stamp->image2;
+        break;
+      default:
+        psAbort("Unsupported subtraction mode: %x", mode);
+    }
+
+    double chi2 = 0.0;                  // Chi^2
+    for (int y = -footprint; y <= footprint; y++) {
+        psF32 *in = &target->kernel[y][-footprint]; // Dereference input
+        psF32 *wt = &weight->kernel[y][-footprint]; // Dereference weight
+        psF32 *ref = &source->kernel[y][-footprint]; // Derference reference
+        for (int x = -footprint; x <= footprint; x++, in++, wt++, ref++) {
+            float diff = *in - *ref;    // Temporary value
+            chi2 += PS_SQR(diff) / *wt;
+        }
+    }
+
+    return chi2;
+}
+
+// Subtract a convolution from the input
+static void subtractConvolution(psKernel *target, // Target stamp
+                                const pmSubtractionStamp *stamp, // Stamp with weight
+                                int kernelIndex, // Index for kernel component
+                                float coeff, // Coefficient of subtraction
+                                float bg, // Background term
+                                int footprint, // Size of region of interest
+                                pmSubtractionMode mode // Mode of subtraction
+    )
+{
+    psKernel *convolution = selectConvolution(stamp, kernelIndex, mode); // Convolution of interest
+    for (int y = -footprint; y <= footprint; y++) {
+        psF32 *in = &target->kernel[y][-footprint]; // Dereference input
+        psF32 *conv = &convolution->kernel[y][-footprint]; // Dereference convolution
+        for (int x = -footprint; x <= footprint; x++, in++, conv++) {
+            *in -= *conv * coeff + bg;
+        }
+    }
+
+    return;
+}
+
+
+pmSubtractionKernels *pmSubtractionKernelsOptimumISIS(pmSubtractionKernelsType type, int size, int inner,
+                                                      int spatialOrder, const psVector *fwhms, int maxOrder,
+                                                      const pmSubtractionStampList *stamps, int footprint,
+                                                      float tolerance, float penalty, pmSubtractionMode mode)
+{
+    if (type != PM_SUBTRACTION_KERNEL_ISIS && type != PM_SUBTRACTION_KERNEL_GUNK) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Invalid kernel type: %x\n", type);
+        return NULL;
+    }
+    PS_ASSERT_INT_NONNEGATIVE(size, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(inner, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(spatialOrder, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(fwhms, NULL);
+    PS_ASSERT_VECTOR_TYPE(fwhms, PS_TYPE_F32, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(maxOrder, NULL);
+    PM_ASSERT_SUBTRACTION_STAMP_LIST_NON_NULL(stamps, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(footprint, NULL);
+    PS_ASSERT_FLOAT_LARGER_THAN(tolerance, 0.0, NULL);
+
+    // Generate the kernels to test
+    int numGaussians = fwhms->n;       // Number of Gaussians
+    int numKernels = numGaussians * (maxOrder + 1) * (maxOrder + 2) / 2; // Number of kernel components
+    psString params = NULL;             // Parameter, for description
+    for (int i = 0; i < numGaussians; i++) {
+        psStringAppend(&params, "%.2f,", fwhms->data.F32[i]);
+    }
+    params[strlen(params) - 1] = '\0';
+
+    psVector *orders = psVectorAlloc(numGaussians, PS_TYPE_S32); // Polynomial orders
+    psVectorInit(orders, maxOrder);
+    pmSubtractionKernels *kernels = p_pmSubtractionKernelsRawISIS(size, spatialOrder, fwhms, orders,
+                                                                  penalty, mode); // Kernels
+    psFree(orders);
+    psFree(kernels->description);
+    kernels->description = NULL;
+    psStringAppend(&kernels->description, "OptISIS(%d,(%s),%d)", size, params, spatialOrder);
+    psFree(params);
+
+    // Need to save the stamp inputs --- we're changing the values!
+    int numStamps = stamps->num;        // Number of stamps
+    psArray *targets = psArrayAlloc(numStamps); // Deep copies of the targets
+    psVector *badStamps = psVectorAlloc(numStamps, PS_TYPE_U8); // Mark the bad stamps
+    psVectorInit(badStamps, 0);
+    for (int i = 0; i < numStamps; i++) {
+        pmSubtractionStamp *stamp = stamps->stamps->data[i]; // Stamp of interest
+        if (stamp->status != PM_SUBTRACTION_STAMP_CALCULATE && stamp->status != PM_SUBTRACTION_STAMP_USED) {
+            badStamps->data.U8[i] = 0xff;
+            continue;
+        }
+        psKernel *target;               // Target image of interest
+        switch (mode) {
+          case PM_SUBTRACTION_MODE_1:
+            target = stamp->image2;
+            break;
+          case PM_SUBTRACTION_MODE_2:
+            target = stamp->image1;
+            break;
+          default:
+            psAbort("Unsupported subtraction mode: %x", mode);
+        }
+        psImage *copy = psImageCopy(NULL, target->image, PS_TYPE_F32); // Copy of the image
+        targets->data[i] = psKernelAllocFromImage(copy, size + footprint, size + footprint);
+        psFree(copy);                   // Drop reference
+    }
+
+    // Generate the convolutions, accumulate sums, and measure initial chi^2
+    double sum1 = 0.0;                  // sum of 1/sigma(x,y)^2
+    psVector *sumC = psVectorAlloc(numKernels, PS_TYPE_F64); // sum of R(x)*k(u)/sigma(x)^2
+    psVector *sumCC = psVectorAlloc(numKernels, PS_TYPE_F64); // sum of [R(x)*k(u)]^2/sigma(x)^2
+    psVectorInit(sumC, 0.0);
+    psVectorInit(sumCC, 0.0);
+    double lastChi2 = 0.0;              // Chi^2 from last iteration
+    int numPixels = 0;                  // Number of pixels contributing to chi^2
+    for (int i = 0; i < numStamps; i++) {
+        pmSubtractionStamp *stamp = stamps->stamps->data[i]; // Stamp of interest
+        if (badStamps->data.U8[i]) {
+            continue;
+        }
+        if (!pmSubtractionConvolveStamp(stamp, kernels, footprint)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to convolve stamp %d.", i);
+            psFree(targets);
+            psFree(kernels);
+            psFree(badStamps);
+            return NULL;
+        }
+
+        // This sum is invariant to the kernel
+        psKernel *weight = stamp->weight; // Weight map for stamp
+        for (int v = -footprint; v <= footprint; v++) {
+            psF32 *wt = &weight->kernel[v][-footprint]; // Dereference weight map
+            for (int u = -footprint; u <= footprint; u++, wt++) {
+                sum1 += 1.0 / *wt;
+            }
+        }
+        if (!isfinite(sum1)) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                    "Sum of 1/sigma^2 is non-finite for stamp %d (%d,%d)\n",
+                    i, (int)stamp->x, (int)stamp->y);
+            psFree(targets);
+            psFree(kernels);
+            psFree(badStamps);
+            return NULL;
+        }
+
+        for (int j = 0; j < numKernels; j++) {
+            accumulateConvolutions(&sumC->data.F64[j], &sumCC->data.F64[j], stamp, j, footprint, mode);
+        }
+
+        lastChi2 += initialChi2(targets->data[i], stamp, footprint, mode);
+        numPixels += PS_SQR(2 * footprint + 1);
+    }
+    lastChi2 /= numPixels;
+
+    // Rank the kernel components
+    psVector *ranking = psVectorAlloc(numKernels, PS_TYPE_S32); // Ranking of the kernel components
+    psVectorInit(ranking, -1);
+    int cutIndex = -1;                  // Index at which to cut off kernels
+    for (int iter = 0; iter < numKernels; iter++) {
+        int bestIndex = -1;             // Index of best kernel component
+        double bestChi2 = INFINITY;     // Value of best chi^2
+        double bestCoeff = 0;           // Value of best coefficient
+        double bestBG = 0;              // Value of best background
+
+        for (int i = 0; i < numKernels; i++) {
+            if (ranking->data.S32[i] >= 0) {
+                continue;
+            }
+
+            double sumI = 0.0;          // sum of I(x)/sigma(x)^2
+            double sumII = 0.0;         // sum of I(x)^2/sigma(x)^2
+            double sumIC = 0.0;         // sum of I(x)C(x)/sigma(x)^2
+
+            for (int j = 0; j < numStamps; j++) {
+                if (badStamps->data.U8[j]) {
+                    continue;
+                }
+                pmSubtractionStamp *stamp = stamps->stamps->data[j]; // Stamp of interest
+                accumulateCross(&sumI, &sumII, &sumIC, stamp, targets->data[j], i, footprint, mode);
+            }
+
+            double invDet = 1.0 / (sum1 * sumCC->data.F64[i] - PS_SQR(sumC->data.F64[i])); // Determinant^-1
+            double coeff = invDet * (sum1 * sumIC - sumC->data.F64[i] * sumI); // Coefficient for kernel
+            double bg = invDet * (sumCC->data.F64[i] * sumI - sumC->data.F64[i] * sumIC); // Background
+
+            double chi2 = 0.0;          // Chi^2
+            for (int j = 0; j < numStamps; j++) {
+                if (badStamps->data.U8[j]) {
+                    continue;
+                }
+                pmSubtractionStamp *stamp = stamps->stamps->data[j]; // Stamp of interest
+                chi2 += accumulateChi2(targets->data[j], stamp, i, coeff, bg, footprint, mode);
+            }
+
+            if (chi2 < bestChi2) {
+                bestIndex = i;
+                bestCoeff = coeff;
+                bestChi2 = chi2;
+                bestBG = bg;
+            }
+
+            psTrace("psModules.imcombine", 8, "%d: %lf %lf %lf %lf %lf %lf\n", i, sum1, sumI, sumII, sumIC,
+                    sumC->data.F64[i], sumCC->data.F64[i]);
+            psTrace("psModules.imcombine", 6, "%d: %lf %lf %lf\n", i, coeff, bg, chi2);
+        }
+        bestChi2 /= numPixels;
+
+        if (bestIndex == -1) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to find best kernel component in round %d.", iter);
+            psFree(targets);
+            psFree(sumC);
+            psFree(sumCC);
+            psFree(ranking);
+            psFree(kernels);
+            psFree(badStamps);
+            return NULL;
+        }
+
+        // And the winner is....
+        ranking->data.S32[bestIndex] = iter;
+        // Remove its contribution, and don't include it in the future.
+        for (int j = 0; j < numStamps; j++) {
+            if (badStamps->data.U8[j]) {
+                continue;
+            }
+            pmSubtractionStamp *stamp = stamps->stamps->data[j]; // Stamp of interest
+            subtractConvolution(targets->data[j], stamp, bestIndex, bestCoeff, bestBG, footprint, mode);
+        }
+
+        double diff = lastChi2 - bestChi2; // Difference in chi^2 between iterations
+
+        psTrace("psModules.imcombine", 3, "The winner of round %d is %d (%f,%d,%d): %lf (%lf) --> %lf\n",
+                iter, bestIndex, kernels->widths->data.F32[bestIndex], kernels->u->data.S32[bestIndex],
+                kernels->v->data.S32[bestIndex], bestCoeff, diff, bestChi2);
+
+        if (fabsf(diff) < tolerance) {
+            cutIndex = iter;
+            break;
+        }
+
+        lastChi2 = bestChi2;
+    }
+    psFree(targets);
+    psFree(sumC);
+    psFree(sumCC);
+
+    if (cutIndex < 0) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to converge to tolerance %g\n", tolerance);
+        psFree(ranking);
+        psFree(kernels);
+        psFree(badStamps);
+        return NULL;
+    }
+
+    int newSize = cutIndex + 1;         // Size of new kernel basis set
+    psTrace("psModules.imcombine", 2, "Accepting %d kernels.\n", newSize);
+    psVector *uNew = psVectorAlloc(newSize, PS_TYPE_S32);
+    psVector *vNew = psVectorAlloc(newSize, PS_TYPE_S32);
+    psVector *widthsNew = psVectorAlloc(newSize, PS_TYPE_F32);
+    psArray *preCalcNew = psArrayAlloc(newSize);
+    psArray *convNew = psArrayAlloc(numStamps);
+    for (int i = 0; i < numStamps; i++) {
+        if (badStamps->data.U8[i]) {
+            continue;
+        }
+        convNew->data[i] = psArrayAlloc(newSize);
+    }
+
+    for (int i = 0; i < numKernels; i++) {
+        int rank = ranking->data.S32[i]; // This kernel component's ranking
+        if (rank >= 0 && rank < newSize) {
+            uNew->data.S32[rank] = kernels->u->data.S32[i];
+            vNew->data.S32[rank] = kernels->v->data.S32[i];
+            widthsNew->data.F32[rank] = kernels->widths->data.F32[i];
+            preCalcNew->data[rank] = psMemIncrRefCounter(kernels->preCalc->data[i]);
+
+            for (int j = 0; j < numStamps; j++) {
+                if (badStamps->data.U8[j]) {
+                    continue;
+                }
+                pmSubtractionStamp *stamp = stamps->stamps->data[j]; // Stamp of interest
+                psArray *convolutions = convNew->data[j]; // Convolutions for this stamp
+                convolutions->data[rank] = psMemIncrRefCounter(selectConvolution(stamp, i, mode));
+            }
+        }
+    }
+    psFree(kernels->u);
+    psFree(kernels->v);
+    psFree(kernels->widths);
+    psFree(kernels->preCalc);
+    kernels->u = uNew;
+    kernels->v = vNew;
+    kernels->widths = widthsNew;
+    kernels->preCalc = preCalcNew;
+    kernels->num = newSize;
+
+    for (int i = 0; i < numStamps; i++) {
+        if (badStamps->data.U8[i]) {
+            continue;
+        }
+        pmSubtractionStamp *stamp = stamps->stamps->data[i]; // Stamp of interest
+        psFree(stamp->convolutions1);
+        psFree(stamp->convolutions2);
+        switch (mode) {
+          case PM_SUBTRACTION_MODE_1:
+            stamp->convolutions1 = convNew->data[i];
+            stamp->convolutions2 = NULL;
+            break;
+          case PM_SUBTRACTION_MODE_2:
+            stamp->convolutions1 = NULL;
+            stamp->convolutions2 = convNew->data[i];
+            break;
+          default:
+            psAbort("Unsupported subtraction mode: %x", mode);
+        }
+    }
+
+    psFree(badStamps);
+    psFree(ranking);
+
+    // Maintain photometric scaling
+    if (type == PM_SUBTRACTION_KERNEL_ISIS) {
+        psKernel *subtract = kernels->preCalc->data[0]; // Kernel to subtract from the rest
+        for (int i = 1; i < newSize; i++) {
+            if (kernels->u->data.S32[i] % 2 == 0 && kernels->v->data.S32[i] % 2 == 0) {
+                psKernel *kernel = kernels->preCalc->data[i]; // Kernel of interest
+                psBinaryOp(kernel->image, kernel->image, "-", subtract->image);
+            }
+        }
+    } else if (type == PM_SUBTRACTION_KERNEL_GUNK) {
+        psStringPrepend(&kernels->description, "GUNK=");
+        psStringAppend(&kernels->description, "+POIS(%d,%d)", inner, spatialOrder);
+
+        for (int i = 0; i < newSize; i++) {
+            if (kernels->u->data.S32[i] % 2 == 0 && kernels->v->data.S32[i] % 2 == 0) {
+                psKernel *kernel = kernels->preCalc->data[i]; // Kernel of interest
+                kernel->kernel[0][0] -= 1.0;
+            }
+        }
+
+        if (!p_pmSubtractionKernelsAddGrid(kernels, numGaussians, inner)) {
+            psAbort("Should never get here.");
+        }
+
+        kernels->type = PM_SUBTRACTION_KERNEL_GUNK;
+    }
+
+    return kernels;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionParams.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionParams.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionParams.h	(revision 22157)
@@ -0,0 +1,22 @@
+#ifndef PM_SUBTRACTION_PARAMS_H
+#define PM_SUBTRACTION_PARAMS_H
+
+#include <pslib.h>
+#include <pmSubtractionKernels.h>
+#include <pmSubtractionStamps.h>
+
+/// Generate a set of optimum kernels for ISIS (or GUNK)
+pmSubtractionKernels *pmSubtractionKernelsOptimumISIS(pmSubtractionKernelsType type, ///< Kernel type
+                                                      int size, ///< Half-size of kernel
+                                                      int inner, ///< Inner radius for GUNK
+                                                      int spatialOrder, ///< Spatial polynomial order
+                                                      const psVector *fwhms, ///< Gaussian FWHMs to try
+                                                      int maxOrder, ///< Maximum polynomial order
+                                                      const pmSubtractionStampList *stamps, ///< Stamps
+                                                      int footprint, ///< Convolution footprint for stamps
+                                                      float tolerance, ///< Maximum difference in chi^2
+                                                      float penalty, ///< Penalty for wideness
+                                                      pmSubtractionMode mode // Mode for subtraction
+    );
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionStamps.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionStamps.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionStamps.c	(revision 22157)
@@ -0,0 +1,603 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+
+// All these includes required to get stamps out of an array of pmSources
+#include "pmMoments.h"
+#include "pmPeaks.h"
+#include "pmResiduals.h"
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmGrowthCurve.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+
+
+#include "pmSubtraction.h"
+#include "pmSubtractionStamps.h"
+
+#define STAMP_LIST_BUFFER 20            // Number of stamps to add to list at a time
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Private (file-static) functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Free function for pmSubtractionStampList
+static void subtractionStampListFree(pmSubtractionStampList *list // Stamp list to free
+                                     )
+{
+    psFree(list->stamps);
+    psFree(list->regions);
+    psFree(list->x);
+    psFree(list->y);
+    psFree(list->flux);
+}
+
+// Free function for pmSubtractionStamp
+static void subtractionStampFree(pmSubtractionStamp *stamp // Stamp to free
+                                 )
+{
+    psFree(stamp->image1);
+    psFree(stamp->image2);
+    psFree(stamp->weight);
+    psFree(stamp->convolutions1);
+    psFree(stamp->convolutions2);
+
+    psFree(stamp->matrix1);
+    psFree(stamp->matrix2);
+    psFree(stamp->matrixX);
+    psFree(stamp->vector1);
+    psFree(stamp->vector2);
+
+}
+
+// Is this region OK?
+static bool checkStampRegion(int x, int y, // Coordinates of stamp
+                             const psRegion *region // Region of interest
+                             )
+{
+    if (!region) {
+        return true;
+    }
+    return (x < region->x0 || x > region->x1 || y < region->y0 || y > region->y1) ?
+        false : true;
+}
+
+// Is this position unmasked?
+static bool checkStampMask(int x, int y, // Coordinates of stamp
+                           const psImage *mask, // Mask
+                           pmSubtractionMode mode // Mode for subtraction
+                           )
+{
+    if (!mask) {
+        return true;
+    }
+    if (x < 0 || x >= mask->numCols || y < 0 || y >= mask->numRows) {
+        return false;
+    }
+
+    psMaskType maskVal = PM_SUBTRACTION_MASK_BORDER | PM_SUBTRACTION_MASK_REJ; // Mask value
+    switch (mode) {
+      case PM_SUBTRACTION_MODE_1:
+        maskVal |= PM_SUBTRACTION_MASK_FOOTPRINT_1;
+        break;
+      case PM_SUBTRACTION_MODE_2:
+        maskVal |= PM_SUBTRACTION_MASK_FOOTPRINT_2;
+        break;
+      case PM_SUBTRACTION_MODE_UNSURE:
+      case PM_SUBTRACTION_MODE_DUAL:
+        maskVal |= PM_SUBTRACTION_MASK_FOOTPRINT_1 | PM_SUBTRACTION_MASK_FOOTPRINT_2;
+        break;
+      default:
+        psAbort("Unsupported subtraction mode: %x", mode);
+    }
+
+    return (mask->data.PS_TYPE_MASK_DATA[y][x] & maskVal) ? false : true;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+pmSubtractionStampList *pmSubtractionStampListAlloc(int numCols, int numRows, const psRegion *region,
+                                                    int footprint, float spacing)
+{
+    pmSubtractionStampList *list = psAlloc(sizeof(pmSubtractionStampList)); // Stamp list to return
+    psMemSetDeallocator(list, (psFreeFunc)subtractionStampListFree);
+
+    // Get region in which to find stamps: [xMin:xMax,yMin:yMax]
+    int xMin = 0, xMax = numCols, yMin = 0, yMax = numRows;
+    if (region) {
+        xMin = PS_MAX(region->x0, xMin);
+        xMax = PS_MIN(region->x1, xMax);
+        yMin = PS_MAX(region->y0, yMin);
+        yMax = PS_MIN(region->y1, yMax);
+    }
+    int xSize = xMax - xMin, ySize = yMax - yMin; // Size of region of interest
+    int xStamps = (float)xSize / spacing + 1, yStamps = (float)ySize / spacing + 1; // Number of stamps
+
+    list->num = xStamps * yStamps;
+    list->stamps = psArrayAlloc(list->num);
+    list->regions = psArrayAlloc(list->num);
+
+    for (int y = 0, index = 0; y < yStamps; y++) {
+        int yStart = yMin + y * ((float)ySize / (float)(yStamps)); // Subregion starts here
+        int yStop = yMin + (y + 1) * ((float)ySize / (float)(yStamps)) - 1; // Subregion stops here
+        assert(yStart >= yMin && yStop < yMax);
+
+        for (int x = 0; x < xStamps; x++, index++) {
+            int xStart = xMin + x * ((float)xSize / (float)(xStamps)); // Subregion starts here
+            int xStop = xMin + (x + 1) * ((float)xSize / (float)(xStamps)) - 1; // Subregion stops here
+            assert(xStart >= xMin && xStop < xMax);
+
+            list->stamps->data[index] = pmSubtractionStampAlloc();
+            psTrace("psModules.imcombine", 6, "Stamp region %d: [%d:%d,%d:%d]\n",
+                    index, xStart, xStop, yStart, yStop);
+            list->regions->data[index] = psRegionAlloc(xStart, xStop, yStart, yStop);
+        }
+    }
+
+    list->x = NULL;
+    list->y = NULL;
+    list->flux = NULL;
+    list->footprint = footprint;
+
+    return list;
+}
+
+pmSubtractionStamp *pmSubtractionStampAlloc(void)
+{
+    pmSubtractionStamp *stamp = psAlloc(sizeof(pmSubtractionStamp)); // Stamp to return
+    psMemSetDeallocator(stamp, (psFreeFunc)subtractionStampFree);
+
+    stamp->x = NAN;
+    stamp->y = NAN;
+    stamp->flux = NAN;
+    stamp->xNorm = NAN;
+    stamp->yNorm = NAN;
+    stamp->status = PM_SUBTRACTION_STAMP_INIT;
+
+    stamp->image1 = NULL;
+    stamp->image2 = NULL;
+    stamp->weight = NULL;
+    stamp->convolutions1 = NULL;
+    stamp->convolutions2 = NULL;
+
+    stamp->matrix1 = NULL;
+    stamp->matrix2 = NULL;
+    stamp->matrixX = NULL;
+    stamp->vector1 = NULL;
+    stamp->vector2 = NULL;
+
+    return stamp;
+}
+
+
+pmSubtractionStampList *pmSubtractionStampsFind(pmSubtractionStampList *stamps, const psImage *image,
+                                                const psImage *subMask, const psRegion *region,
+                                                float threshold, int footprint, float spacing,
+                                                pmSubtractionMode mode)
+{
+    PS_ASSERT_IMAGE_NON_NULL(image, NULL);
+    PS_ASSERT_IMAGE_TYPE(image, PS_TYPE_F32, NULL);
+    if (subMask) {
+        PS_ASSERT_IMAGE_NON_NULL(subMask, NULL);
+        PS_ASSERT_IMAGES_SIZE_EQUAL(image, subMask, NULL);
+        PS_ASSERT_IMAGE_TYPE(subMask, PS_TYPE_MASK, NULL);
+    }
+    PS_ASSERT_INT_NONNEGATIVE(footprint, NULL);
+    PS_ASSERT_FLOAT_LARGER_THAN(spacing, 0.0, NULL);
+    if (region) {
+        if (psRegionIsNaN(*region)) {
+            psString string = psRegionToString(*region);
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Input region (%s) contains NAN values", string);
+            psFree(string);
+            return false;
+        }
+        if (region->x0 < 0 || region->x1 > image->numCols ||
+            region->y0 < 0 || region->y1 > image->numRows) {
+            psString string = psRegionToString(*region);
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Input region (%s) does not fit in image (%dx%d)",
+                    string, image->numCols, image->numRows);
+            psFree(string);
+            return false;
+        }
+    }
+
+    int numRows = image->numRows, numCols = image->numCols; // Size of image
+
+    if (!stamps) {
+        stamps = pmSubtractionStampListAlloc(numCols, numRows, region, footprint, spacing);
+    }
+
+    int numStamps = stamps->num;        // Number of stamp regions
+    int numFound = 0;                   // Number of stamps found
+    int numValid = 0;                   // Number of valid regions
+    for (int i = 0; i < numStamps; i++) {
+        pmSubtractionStamp *stamp = stamps->stamps->data[i]; // Stamp of interest
+
+        // Only find a new stamp if we need to
+        if (stamp->status != PM_SUBTRACTION_STAMP_REJECTED && stamp->status != PM_SUBTRACTION_STAMP_INIT) {
+            continue;
+        }
+        numValid++;
+
+        float xStamp = 0, yStamp = 0;   // Coordinates of stamp
+        float fluxStamp = NAN;          // Flux of stamp
+        bool goodStamp = false;         // Found a good stamp?
+
+        // A couple different ways of finding stamps:
+        if (stamps->x && stamps->y) {
+            // Get the next stamp from the list
+            psVector *xList = stamps->x->data[i], *yList = stamps->y->data[i]; // Coordinate lists
+            psVector *fluxList = stamps->flux->data[i]; // List of stamp fluxes
+
+            // Take stamp off the top of the (sorted) list
+            if (xList->n > 0) {
+                int index = xList->n - 1; // Index of new stamp
+                xStamp = xList->data.F32[index];
+                yStamp = yList->data.F32[index];
+                fluxStamp = fluxList->data.F32[index];
+
+                // Chop off the top of the list
+                xList->n = index;
+                yList->n = index;
+                fluxList->n = index;
+
+                goodStamp = true;
+            }
+        } else {
+            // Use a simple method of automatically finding stamps --- take the highest pixel in the subregion
+            fluxStamp = threshold;
+            psRegion *subRegion = stamps->regions->data[i]; // Sub-region of interest
+            for (int y = subRegion->y0; y <= subRegion->y1; y++) {
+                for (int x = subRegion->x0; x <= subRegion->x1; x++) {
+                    if (checkStampMask(x, y, subMask, mode) && image->data.F32[y][x] > fluxStamp) {
+                        fluxStamp = image->data.F32[y][x];
+                        xStamp = x;
+                        yStamp = y;
+                        goodStamp = true;
+                    }
+                }
+            }
+        }
+
+        if (goodStamp) {
+            stamp->x = xStamp;
+            stamp->y = yStamp;
+            stamp->flux = fluxStamp;
+
+            // Reset the postage stamps since we're making a new stamp
+            psFree(stamp->image1);
+            psFree(stamp->image2);
+            psFree(stamp->weight);
+            psFree(stamp->convolutions1);
+            psFree(stamp->convolutions2);
+            stamp->image1 = stamp->image2 = stamp->weight = NULL;
+            stamp->convolutions1 = stamp->convolutions2 = NULL;
+
+            stamp->status = PM_SUBTRACTION_STAMP_FOUND;
+            numFound++;
+            psTrace("psModules.imcombine", 5, "Found stamp in subregion %d: %d,%d\n",
+                    i, (int)stamp->x, (int)stamp->y);
+        } else {
+            stamp->status = PM_SUBTRACTION_STAMP_NONE;
+        }
+    }
+
+    if (numValid > 0) {
+        psLogMsg("psModules.imcombine", PS_LOG_INFO, "Found %d stamps", numFound);
+    }
+
+    return stamps;
+}
+
+
+pmSubtractionStampList *pmSubtractionStampsSet(const psVector *x, const psVector *y, const psVector *flux,
+                                               const psImage *image, const psImage *subMask,
+                                               const psRegion *region, int footprint, float spacing,
+                                               pmSubtractionMode mode)
+
+{
+    PS_ASSERT_VECTOR_NON_NULL(x, NULL);
+    PS_ASSERT_VECTOR_TYPE(x, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(y, NULL);
+    PS_ASSERT_VECTOR_TYPE(y, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(y, x, NULL);
+    if (flux) {
+        PS_ASSERT_VECTOR_NON_NULL(flux, NULL);
+        PS_ASSERT_VECTOR_TYPE(flux, PS_TYPE_F32, NULL);
+        PS_ASSERT_VECTORS_SIZE_EQUAL(flux, x, NULL);
+    } else {
+        PS_ASSERT_IMAGE_NON_NULL(image, NULL);
+    }
+    if (subMask) {
+        PS_ASSERT_IMAGE_NON_NULL(subMask, NULL);
+        PS_ASSERT_IMAGE_TYPE(subMask, PS_TYPE_MASK, NULL);
+        if (image) {
+            PS_ASSERT_IMAGE_NON_NULL(image, NULL);
+            PS_ASSERT_IMAGES_SIZE_EQUAL(image, subMask, NULL);
+        }
+    }
+    PS_ASSERT_FLOAT_LARGER_THAN(spacing, 0.0, NULL);
+
+    int numStars = x->n;                // Number of stars
+    pmSubtractionStampList *stamps = pmSubtractionStampListAlloc(subMask->numCols, subMask->numRows,
+                                                                 region, footprint, spacing); // Stamp list
+    int numStamps = stamps->num;        // Number of stamps
+
+    // Initialise the lists
+    stamps->x = psArrayAlloc(numStamps);
+    stamps->y = psArrayAlloc(numStamps);
+    stamps->flux = psArrayAlloc(numStamps);
+    for (int i = 0; i < numStamps; i++) {
+        stamps->x->data[i] = psVectorAllocEmpty(STAMP_LIST_BUFFER, PS_TYPE_F32);
+        stamps->y->data[i] = psVectorAllocEmpty(STAMP_LIST_BUFFER, PS_TYPE_F32);
+        stamps->flux->data[i] = psVectorAllocEmpty(STAMP_LIST_BUFFER, PS_TYPE_F32);
+    }
+
+    // Put the stars into their appropriate subregions
+    for (int i = 0; i < numStars; i++) {
+        float xStamp = x->data.F32[i], yStamp = y->data.F32[i]; // Coordinates of stamp
+        int xPix = xStamp + 0.5, yPix = yStamp + 0.5; // Pixel coordinate of stamp
+        if (!checkStampRegion(xPix, yPix, region)) {
+            // It's not in the big region
+            psTrace("psModules.imcombine", 9, "Rejecting input stamp (%d,%d) because outside region",
+                    xPix, yPix);
+            continue;
+        }
+        if (!checkStampMask(xPix, yPix, subMask, mode)) {
+            // Not a good stamp
+            psTrace("psModules.imcombine", 9, "Rejecting input stamp (%d,%d) because bad mask",
+                    xPix, yPix);
+            continue;
+        }
+
+        bool found = false;
+        for (int j = 0; j < numStamps && !found; j++) {
+            psRegion *subRegion = stamps->regions->data[j]; // Subregion of interest
+            if (checkStampRegion(xPix, yPix, subRegion)) {
+                psVector *xList = stamps->x->data[j], *yList = stamps->y->data[j]; // Pixel lists
+                psVector *fluxList = stamps->flux->data[j]; // Flux list
+
+                int index = xList->n;   // Index of new stamp candidate
+
+                psVectorExtend(xList, STAMP_LIST_BUFFER, 1);
+                psVectorExtend(yList, STAMP_LIST_BUFFER, 1);
+                psVectorExtend(fluxList, STAMP_LIST_BUFFER, 1);
+
+                xList->data.F32[index] = xStamp;
+                yList->data.F32[index] = yStamp;
+
+                if (flux) {
+                    fluxList->data.F32[index] = flux->data.F32[i];
+                } else {
+                    fluxList->data.F32[index] = image->data.F32[yPix][xPix];
+                }
+
+                found = true;
+                psTrace("psModules.imcombine", 9, "Putting input stamp (%d,%d) into subregion %d",
+                        xPix, yPix, j);
+            }
+        }
+
+        if (!found) {
+            psTrace("psModules.imcombine", 9, "Unable to find subregion for stamp (%d,%d)",
+                    xPix, yPix);
+        }
+    }
+
+    // Sort the list by flux, with the brightest last
+    for (int i = 0; i < numStamps; i++) {
+        psVector *xList = stamps->x->data[i], *yList = stamps->y->data[i]; // Pixel lists
+        psVector *fluxList = stamps->flux->data[i]; // Flux list
+
+        psVector *indexes = psVectorSortIndex(NULL, fluxList); // Indices to sort flux
+        int num = indexes->n;           // Number of candidate stamps in this subregion
+
+        psVector *xSorted = psVectorAlloc(num, PS_TYPE_F32); // Sorted version of x list
+        psVector *ySorted = psVectorAlloc(num, PS_TYPE_F32); // Sorted version of y list
+        psVector *fluxSorted = psVectorAlloc(num, PS_TYPE_F32); // Sorted version of flux list
+        for (int j = 0; j < num; j++) {
+            int k = indexes->data.S32[j]; // Sorted index
+            xSorted->data.F32[j] = xList->data.F32[k];
+            ySorted->data.F32[j] = yList->data.F32[k];
+            fluxSorted->data.F32[j] = fluxList->data.F32[k];
+        }
+        psFree(indexes);
+
+        psFree(stamps->x->data[i]);
+        psFree(stamps->y->data[i]);
+        psFree(stamps->flux->data[i]);
+
+        stamps->x->data[i] = xSorted;
+        stamps->y->data[i] = ySorted;
+        stamps->flux->data[i] = fluxSorted;
+    }
+
+
+    return stamps;
+}
+
+
+bool pmSubtractionStampsExtract(pmSubtractionStampList *stamps, psImage *image1, psImage *image2,
+                                psImage *weight, int kernelSize)
+{
+    PM_ASSERT_SUBTRACTION_STAMP_LIST_NON_NULL(stamps, false);
+    PS_ASSERT_IMAGE_NON_NULL(image1, false);
+    PS_ASSERT_IMAGE_TYPE(image1, PS_TYPE_F32, false);
+    if (image2) {
+        PS_ASSERT_IMAGE_NON_NULL(image2, false);
+        PS_ASSERT_IMAGES_SIZE_EQUAL(image2, image1, false);
+        PS_ASSERT_IMAGE_TYPE(image2, PS_TYPE_F32, false);
+    }
+    PS_ASSERT_IMAGE_NON_NULL(weight, false);
+    PS_ASSERT_IMAGES_SIZE_EQUAL(weight, image1, false);
+    PS_ASSERT_IMAGE_TYPE(weight, PS_TYPE_F32, false);
+    PS_ASSERT_INT_NONNEGATIVE(kernelSize, false);
+
+    int numCols = image1->numCols, numRows = image1->numRows; // Size of images
+    int size = kernelSize + stamps->footprint; // Size of postage stamps
+
+    for (int i = 0; i < stamps->num; i++) {
+        pmSubtractionStamp *stamp = stamps->stamps->data[i]; // Stamp of interest
+        if (!stamp || stamp->status != PM_SUBTRACTION_STAMP_FOUND) {
+            continue;
+        }
+
+        if (isnan(stamp->xNorm)) {
+            stamp->xNorm = 2.0 * (stamp->x - (float)numCols/2.0) / (float)numCols;
+        }
+        if (isnan(stamp->yNorm)) {
+            stamp->yNorm = 2.0 * (stamp->y - (float)numRows/2.0) / (float)numRows;
+        }
+
+        int x = stamp->x + 0.5, y = stamp->y + 0.5; // Stamp coordinates
+        if (x < size || x > numCols - size || y < size || y > numRows - size) {
+            psError(PS_ERR_UNKNOWN, false, "Stamp %d (%d,%d) is within the image border.\n", i, x, y);
+            return false;
+        }
+
+        // Catch memory leaks --- these should have been freed and NULLed before
+        assert(stamp->image1 == NULL);
+        assert(stamp->image2 == NULL);
+        assert(stamp->weight == NULL);
+
+        psRegion region = psRegionSet(x - size, x + size + 1, y - size, y + size + 1); // Region of interest
+
+        psImage *sub1 = psImageSubset(image1, region); // Subimage with stamp
+        stamp->image1 = psKernelAllocFromImage(sub1, size, size);
+        psFree(sub1);                   // Drop reference
+
+        if (image2) {
+            psImage *sub2 = psImageSubset(image2, region); // Subimage with stamp
+            stamp->image2 = psKernelAllocFromImage(sub2, size, size);
+            psFree(sub2);               // Drop reference
+        }
+
+        psImage *wtSub = psImageSubset(weight, region); // Subimage with stamp
+        stamp->weight = psKernelAllocFromImage(wtSub, size, size);
+        psFree(wtSub);                  // Drop reference
+
+        stamp->status = PM_SUBTRACTION_STAMP_CALCULATE;
+    }
+
+    return true;
+}
+
+#if 0
+bool pmSubtractionStampsGenerate(pmSubtractionStampList *stamps, float fwhm, int kernelSize)
+{
+    PM_ASSERT_SUBTRACTION_STAMP_LIST_NON_NULL(stamps, false);
+    PS_ASSERT_FLOAT_LARGER_THAN(fwhm, 0.0, false);
+    PS_ASSERT_INT_NONNEGATIVE(kernelSize, false);
+
+    int size = kernelSize + stamps->footprint; // Size of postage stamps
+    int num = stamps->num;              // Number of stamps
+    float sigma = fwhm / (2.0 * sqrtf(2.0 * log(2.0))); // Gaussian sigma
+
+    for (int i = 0; i < num; i++) {
+        pmSubtractionStamp *stamp = stamps->stamps->data[i]; // Stamp of interest
+        if (!(stamp->status & PM_SUBTRACTION_STAMP_CALCULATE)) {
+            continue;
+        }
+
+        float x = stamp->x, y = stamp->y; // Coordinates of stamp
+        float flux = stamp->flux; // Flux of star
+        if (!isfinite(flux)) {
+            psWarning("Unable to generate PSF for stamp %d --- bad flux.", i);
+            stamp->status = PM_SUBTRACTION_STAMP_REJECTED;
+            continue;
+        }
+
+        float xStamp = x - (int)(x + 0.5); // x coordinate of star in stamp frame
+        float yStamp = y - (int)(y + 0.5); // y coordinate of star in stamp frame
+
+        psFree(stamp->image2);
+        stamp->image2 = psKernelAlloc(-size, size, -size, size);
+        psKernel *target = stamp->image2; // Target stamp
+
+        // Put in a Waussian, just for fun!
+        for (int v = -size; v <= size; v++) {
+            for (int u = -size; u <= size; u++) {
+                float z = (PS_SQR(u + xStamp) + PS_SQR(v + yStamp)) / (2.0 * PS_SQR(sigma));
+                target->kernel[v][u] = flux / sigma * 0.5 * M_2_SQRTPI * M_SQRT1_2 / (1.0 + z + PS_SQR(z));
+            }
+        }
+
+    }
+
+    return true;
+}
+#endif
+
+pmSubtractionStampList *pmSubtractionStampsSetFromSources(const psArray *sources, const psImage *subMask,
+                                                          const psRegion *region, int footprint,
+                                                          float spacing, pmSubtractionMode mode)
+{
+    PS_ASSERT_ARRAY_NON_NULL(sources, NULL);
+    // Let pmSubtractionStampsSet take care of the rest of the assertions
+
+    int numSources = sources->n;          // Number of stars
+
+    psVector *x = psVectorAlloc(numSources, PS_TYPE_F32); // x coordinates
+    psVector *y = psVectorAlloc(numSources, PS_TYPE_F32); // y coordinates
+    psVector *flux = psVectorAlloc(numSources, PS_TYPE_F32); // Fluxes
+
+    for (int i = 0; i < numSources; i++) {
+        pmSource *source = sources->data[i]; // Source of interest
+        if (source->modelPSF) {
+            x->data.F32[i] = source->modelPSF->params->data.F32[PM_PAR_XPOS];
+            y->data.F32[i] = source->modelPSF->params->data.F32[PM_PAR_YPOS];
+        } else {
+            x->data.F32[i] = source->peak->xf;
+            y->data.F32[i] = source->peak->yf;
+        }
+        flux->data.F32[i] = powf(10.0, -0.4 * source->psfMag);
+    }
+
+    pmSubtractionStampList *stamps = pmSubtractionStampsSet(x, y, flux, NULL, subMask, region,
+                                                            footprint, spacing, mode); // Stamps to return
+    psFree(x);
+    psFree(y);
+    psFree(flux);
+
+    if (!stamps) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to set stamps from sources.");
+    }
+
+    return stamps;
+}
+
+
+pmSubtractionStampList *pmSubtractionStampsSetFromFile(const char *filename, const psImage *image,
+                                                       const psImage *subMask, const psRegion *region,
+                                                       int footprint, float spacing, pmSubtractionMode mode)
+{
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+    // Let pmSubtractionStampsSet take care of the rest of the assertions
+
+    psArray *data = psVectorsReadFromFile(filename, "%f %f");
+    if (!data) {
+        psError(PS_ERR_IO, false, "Unable to read stamps file %s", filename);
+        return NULL;
+    }
+    psVector *x = data->data[0], *y = data->data[1]; // Stamp positions
+
+    // Correct for IRAF/FITS (unit-offset) positions to C (zero-offset) positions
+    psBinaryOp(x, x, "-", psScalarAlloc(1.0, PS_TYPE_F32));
+    psBinaryOp(y, y, "-", psScalarAlloc(1.0, PS_TYPE_F32));
+
+    pmSubtractionStampList *stamps = pmSubtractionStampsSet(x, y, NULL, image, subMask, region, footprint,
+                                                            spacing, mode);
+    psFree(data);
+
+    return stamps;
+
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionStamps.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionStamps.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/imcombine/pmSubtractionStamps.h	(revision 22157)
@@ -0,0 +1,126 @@
+#ifndef PM_SUBTRACTION_STAMPS_H
+#define PM_SUBTRACTION_STAMPS_H
+
+#include <pslib.h>
+
+#include "pmSubtractionKernels.h"
+
+/// Status of stamp
+typedef enum {
+    PM_SUBTRACTION_STAMP_INIT,          ///< Initial state
+    PM_SUBTRACTION_STAMP_FOUND,         ///< Found a suitable source for this stamp
+    PM_SUBTRACTION_STAMP_CALCULATE,     ///< Calculate matrix and vector values for this stamp
+    PM_SUBTRACTION_STAMP_USED,          ///< Use this stamp
+    PM_SUBTRACTION_STAMP_REJECTED,      ///< This stamp has been rejected
+    PM_SUBTRACTION_STAMP_NONE           ///< No stamp in this region
+} pmSubtractionStampStatus;
+
+/// A list of stamps
+typedef struct {
+    long num;                           ///< Number of stamps
+    psArray *stamps;                    ///< The stamps
+    psArray *regions;                   ///< Regions for each stamp
+    psArray *x, *y;                     ///< Coordinates for possible stamps (or NULL)
+    psArray *flux;                      ///< Fluxes for possible stamps (or NULL)
+    int footprint;                      ///< Half-size of stamps
+} pmSubtractionStampList;
+
+/// Allocate a list of stamps
+pmSubtractionStampList *pmSubtractionStampListAlloc(int numCols, // Number of columns in image
+                                                    int numRows, // Number of rows in image
+                                                    const psRegion *region, // Region for stamps, or NULL
+                                                    int footprint, // Half-size of stamps
+                                                    float spacing // Rough average spacing between stamps
+    );
+
+/// Assertion for stamp list to be valid
+#define PM_ASSERT_SUBTRACTION_STAMP_LIST_NON_NULL(LIST, RETURNVALUE) { \
+    PS_ASSERT_PTR_NON_NULL(LIST, RETURNVALUE); \
+    PS_ASSERT_ARRAY_NON_NULL((LIST)->stamps, RETURNVALUE); \
+    PS_ASSERT_ARRAY_NON_NULL((LIST)->regions, RETURNVALUE); \
+    PS_ASSERT_INT_POSITIVE((LIST)->num, RETURNVALUE); \
+    PS_ASSERT_ARRAY_SIZE((LIST)->stamps, (LIST)->num, RETURNVALUE); \
+    PS_ASSERT_ARRAY_SIZE((LIST)->regions, (LIST)->num, RETURNVALUE); \
+    PS_ASSERT_INT_NONNEGATIVE((LIST)->footprint, RETURNVALUE); \
+    if ((LIST)->x || (LIST)->y || (LIST)->flux) { \
+        PS_ASSERT_ARRAY_NON_NULL((LIST)->x, RETURNVALUE); \
+        PS_ASSERT_ARRAY_NON_NULL((LIST)->y, RETURNVALUE); \
+        PS_ASSERT_ARRAY_NON_NULL((LIST)->flux, RETURNVALUE); \
+        PS_ASSERT_ARRAY_SIZE((LIST)->x, (LIST)->num, RETURNVALUE); \
+        PS_ASSERT_ARRAY_SIZE((LIST)->y, (LIST)->num, RETURNVALUE); \
+        PS_ASSERT_ARRAY_SIZE((LIST)->flux, (LIST)->num, RETURNVALUE); \
+    } \
+}
+
+/// A stamp for image subtraction
+typedef struct {
+    float x, y;                         ///< Position
+    float flux;                         ///< Flux
+    float xNorm, yNorm;                 ///< Normalised position
+    psKernel *image1;                   ///< Reference image postage stamp
+    psKernel *image2;                   ///< Input image postage stamp
+    psKernel *weight;                   ///< Weight image postage stamp, or NULL
+    psArray *convolutions1;             ///< Convolutions of image 1 for each kernel component, or NULL
+    psArray *convolutions2;             ///< Convolutions of image 2 for each kernel component, or NULL
+    psImage *matrix1, *matrix2;         ///< Least-squares matrices for each image, or NULL
+    psImage *matrixX;                   ///< Cross-matrix (for mode DUAL), or NULL
+    psVector *vector1, *vector2;        ///< Least-squares vectors for each image, or NULL
+    pmSubtractionStampStatus status;    ///< Status of stamp
+} pmSubtractionStamp;
+
+/// Allocate a stamp
+pmSubtractionStamp *pmSubtractionStampAlloc(void);
+
+/// Find stamps on an image
+pmSubtractionStampList *pmSubtractionStampsFind(pmSubtractionStampList *stamps, ///< Output stamps, or NULL
+                                                const psImage *image, ///< Image for which to find stamps
+                                                const psImage *mask, ///< Mask, or NULL
+                                                const psRegion *region, ///< Region to search, or NULL
+                                                float threshold, ///< Threshold for stamps in the image
+                                                int footprint, ///< Half-size for stamps
+                                                float spacing, ///< Rough spacing for stamps
+                                                pmSubtractionMode mode ///< Mode for subtraction
+    );
+
+/// Set stamps based on a list of x,y
+pmSubtractionStampList *pmSubtractionStampsSet(const psVector *x, ///< x coordinates for each stamp
+                                               const psVector *y, ///< y coordinates for each stamp
+                                               const psVector *flux, ///< Flux for each stamp, or NULL
+                                               const psImage *image, ///< Image for flux of stamp
+                                               const psImage *mask, ///< Mask, or NULL
+                                               const psRegion *region, ///< Region to search, or NULL
+                                               int footprint, ///< Half-size for stamps
+                                               float spacing, ///< Rough spacing for stamps
+                                               pmSubtractionMode mode ///< Mode for subtraction
+    );
+
+/// Set stamps based on a list of sources
+pmSubtractionStampList *pmSubtractionStampsSetFromSources(
+    const psArray *sources,             ///< Sources for each stamp
+    const psImage *subMask,             ///< Mask, or NULL
+    const psRegion *region,             ///< Region to search, or NULL
+    int footprint,                      ///< Half-size for stamps
+    float spacing,                      ///< Rough spacing for stamps
+    pmSubtractionMode mode              ///< Mode for subtraction
+    );
+
+/// Set stamps based on values in a file
+pmSubtractionStampList *pmSubtractionStampsSetFromFile(
+    const char *filename,               ///< Filename of file containing x,y (or x,y,flux) on each line
+    const psImage *image,               ///< Image for flux of stamp
+    const psImage *subMask,             ///< Mask, or NULL
+    const psRegion *region,             ///< Region to search, or NULL
+    int footprint,                      ///< Half-size for stamps
+    float spacing,                      ///< Rough spacing for stamps
+    pmSubtractionMode mode              ///< Mode for subtraction
+    );
+
+/// Extract stamps from the images
+bool pmSubtractionStampsExtract(pmSubtractionStampList *stamps, ///< Stamps
+                                psImage *image1, ///< Reference image
+                                psImage *image2, ///< Input image (or NULL)
+                                psImage *weight, ///< Weight (variance) map
+                                int kernelSize ///< Kernel half-size
+    );
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/mainpage.dox
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/mainpage.dox	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/mainpage.dox	(revision 22157)
@@ -0,0 +1,127 @@
+/** @mainpage psModules Image Processing Library
+
+
+@section intro Introduction
+This library contains the Pan-STARRS Image Processing Pipeline (IPP) modules (psModules). These modules
+use the functionality of the Pan-STARRS Library (psLib) to perform more complex tasks associated with image
+processing. Modules were constructed to support each of the required processing stages and are listed according
+to the particular processing stage for which they will primarily be used. To preserve namespace, globally-visible
+structures and functions are prefixed with pm, an abbreviation for Pan-STARRS Modules.
+
+The capabilities provided by psModules are grouped into the following areas which are
+also reflected in the file system directory structure:
+ - Configuration
+ - Astrometry, Focal Plane
+ - Photometry
+ - Basic Image Detrending
+ - Object Detection and Classification
+ - Image Combination
+ - Image Subtraction
+
+The installed code for psModules consists of header files and a binary library.
+
+@section extinstall Required external Libraries
+
+Before building psLib from source, several external software libraries must
+be installed. These include:
+ - Pan-STARRS Library (psLib)
+     - Available from the Maui High Performance Computiong Center (MHPCC) at https://mhpcc.pan-starrs.org/code/releases
+     - Compatibility tested with rel5
+     - Note, psLib itself has many external library dependencies.
+ - Doxygen Documentation System
+     - Only needed if code documentation is to be made
+     - Available at http://www.doxygen.org
+     - Compatibility tested with version 1.3.6
+     - The optional companion package, GraphViz (a.k.a., the 'dot' command), is recommended to enable header-file dependency diagrams
+
+We recommend using the particular versions listed as compatibility tested, as
+that is the only versions of the external libraries tested to work well with psLib
+and psModules.  Though it is quite possible that later versions of the libraries
+listed will also work, care must be taken when upgrading these libraries to verify
+that its functionality is compatible with the tested version.
+
+@section install How to Build from Source
+
+Tested versions of psModules are put into a tar file and can be downloaded from:
+
+https://mhpcc.pan-starrs.org/code/releases
+
+If one has a login account on mhpcc.pan-starrs.org, direct CVS access is
+possible.  Example of the commands required for direct CVS retrieval are
+as follows:
+<pre>
+$ cvs -d:ext:USERNAME@mhpcc.pan-starrs.org:/data/panstarrs/cvsroot co -r RELEASEBRANCH psModule
+</pre>
+where:
+  - USERNAME is your login name on the server
+  - RELEASEBRANCH is the desired release branch, e.g. rel7.
+
+
+@section build How to Build and Test the psModules Library.
+
+The psModules library and associated tests are made via the GNU autoconf/automake system.
+
+The source should build using the configure script in the psModules directory.  The
+recommended steps are:
+<pre>
+$ cd psmodules
+$ ./configure
+$ make
+$ make check
+$ make install
+</pre>
+<i>Unless otherwise specified, the library is installed with PREFIX of the current directory.</i>
+
+If the code was retrieved from CVS, you will need to substitute 'autogen.sh' for 'configure' in
+above example.
+
+Other configuration options, such as location of external libraries, are also available.
+To get a list of options, type the following in the top psModules directory.
+<pre>
+$ configure --help
+</pre>
+A likely option needed is '--with-pslib-config', which specifies the location of
+the configuration script for psLib.  By default, configure searches for it using PATH, but that
+is not always sufficient.
+
+@section install How to Install
+
+To install the library using the prefix given in the configure step, execute in
+the top build directory:
+<pre>
+$ make install
+</pre>
+
+
+@section usage Building and Linking your code to the psModules library
+
+To assist the use of the library with your own code, a configuration tool is part
+of the psModules library package.  This tool, psmodules-config, is installed in the BIN
+directory, according to the options given to the configure script.
+
+The required CFLAG options for the compiler stage of code that uses psModules can be
+obtained via 'psmodules-config --cflags'.  This outputs the cc options that supplies
+include path(s) required to find the psModules headers.
+
+The required linking options, can be obtained via 'psmodules-config --libs'.  This
+outputs the ld options that supplies the library paths and files required to
+link to the psModules library.
+
+Note: psmodules-config usage above refers to the install locations of the library.  
+
+@section doc How to Create Code Documentation
+
+Both HTML and man page documentation may be generated from the inline
+documentation embedded in the code using the following commands:
+<pre>
+$ cd psmodules
+$ make docs
+</pre>
+<i>This places documentation in PREFIX/docs.</i>
+
+Also, a prebuilt set of code documentation for both the releases and last
+CVS snapshot can be found at:
+
+https://mhpcc.pan-starrs.org/docs/
+
+*/
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/.cvsignore	(revision 22157)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.la
+*.lo
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/Makefile.am	(revision 22157)
@@ -0,0 +1,69 @@
+noinst_LTLIBRARIES = libpsmodulesobjects.la
+
+libpsmodulesobjects_la_CPPFLAGS = $(SRCINC) $(PSMODULES_CFLAGS) -I../pslib/
+libpsmodulesobjects_la_LDFLAGS  = -release $(PACKAGE_VERSION)
+libpsmodulesobjects_la_SOURCES  = \
+     pmPeaks.c \
+     pmMoments.c \
+     pmModel.c \
+     pmModelClass.c \
+     pmModelUtils.c \
+     pmSource.c \
+     pmSourceExtendedPars.c \
+     pmSourceUtils.c \
+     pmSourceSky.c \
+     pmSourceContour.c \
+     pmSourceFitModel.c \
+     pmSourceFitSet.c \
+     pmSourcePhotometry.c \
+     pmSourceIO.c \
+     pmSourceIO_RAW.c \
+     pmSourceIO_OBJ.c \
+     pmSourceIO_SX.c \
+     pmSourceIO_CMP.c \
+     pmSourceIO_SMPDATA.c \
+     pmSourceIO_PS1_DEV_0.c \
+     pmSourceIO_PS1_DEV_1.c \
+     pmSourcePlots.c \
+     pmSourcePlotPSFModel.c \
+     pmSourcePlotMoments.c \
+     pmSourcePlotApResid.c \
+     pmResiduals.c \
+     pmPSF.c \
+     pmPSF_IO.c \
+     pmPSFtry.c \
+     pmTrend2D.c \
+     pmGrowthCurve.c
+
+EXTRA_DIST = \
+     models/pmModel_GAUSS.c \
+     models/pmModel_PGAUSS.c \
+     models/pmModel_QGAUSS.c \
+     models/pmModel_SGAUSS.c \
+     models/pmModel_RGAUSS.c \
+     models/pmModel_SERSIC.c
+
+pkginclude_HEADERS = \
+     pmPeaks.h \
+     pmMoments.h \
+     pmModel.h \
+     pmModelClass.h \
+     pmModelUtils.h \
+     pmSource.h \
+     pmSourceExtendedPars.h \
+     pmSourceUtils.h \
+     pmSourceSky.h \
+     pmSourceContour.h \
+     pmSourceFitModel.h \
+     pmSourceFitSet.h \
+     pmSourcePhotometry.h \
+     pmSourceIO.h \
+     pmSourcePlots.h \
+     pmResiduals.h \
+     pmPSF.h \
+     pmPSF_IO.h \
+     pmPSFtry.h \
+     pmTrend2D.h \
+     pmGrowthCurve.h
+
+CLEANFILES = *~
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_GAUSS.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_GAUSS.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_GAUSS.c	(revision 22157)
@@ -0,0 +1,396 @@
+/******************************************************************************
+ * this file defines the GAUSS source shape model.  Note that these model functions are loaded
+ * by pmModelGroup.c using 'include', and thus need no 'include' statements of their own.  The
+ * models use a psVector to represent the set of parameters, with the sequence used to specify
+ * the meaning of the parameter.  The meaning of the parameters may thus vary depending on the
+ * specifics of the model.  All models which are used a PSF representations share a few
+ * parameters, for which # define names are listed in pmModel.h:
+
+   pure Gaussian:
+   exp(-z)
+
+ * PM_PAR_SKY 0   - local sky : note that this is unused and may be dropped in the future
+ * PM_PAR_I0 1    - central intensity
+ * PM_PAR_XPOS 2  - X center of object
+ * PM_PAR_YPOS 3  - Y center of object
+ * PM_PAR_SXX 4   - X^2 term of elliptical contour (sqrt(2) * SigmaX)
+ * PM_PAR_SYY 5   - Y^2 term of elliptical contour (sqrt(2) * SigmaY)
+ * PM_PAR_SXY 6   - X*Y term of elliptical contour
+ *****************************************************************************/
+
+# define PM_MODEL_FUNC            pmModelFunc_GAUSS
+# define PM_MODEL_FLUX            pmModelFlux_GAUSS
+# define PM_MODEL_GUESS           pmModelGuess_GAUSS
+# define PM_MODEL_LIMITS          pmModelLimits_GAUSS
+# define PM_MODEL_RADIUS          pmModelRadius_GAUSS
+# define PM_MODEL_FROM_PSF        pmModelFromPSF_GAUSS
+# define PM_MODEL_PARAMS_FROM_PSF pmModelParamsFromPSF_GAUSS
+# define PM_MODEL_FIT_STATUS      pmModelFitStatus_GAUSS
+
+// the model is a function of the pixel coordinate (pixcoord[0,1] = x,y)
+psF32 PM_MODEL_FUNC(psVector *deriv,
+                    const psVector *params,
+                    const psVector *pixcoord)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = pixcoord->data.F32[0] - PAR[PM_PAR_XPOS];
+    psF32 Y  = pixcoord->data.F32[1] - PAR[PM_PAR_YPOS];
+    psF32 px = X / PAR[PM_PAR_SXX];
+    psF32 py = Y / PAR[PM_PAR_SYY];
+    psF32 z  = PS_SQR(px) + PS_SQR(py) + PAR[PM_PAR_SXY]*X*Y;
+    assert (z >= 0.0);
+
+    psF32 r  = exp(-z);
+    psF32 q  = PAR[PM_PAR_I0]*r;
+    psF32 f  = q + PAR[PM_PAR_SKY];
+
+    if (deriv != NULL) {
+        psF32 *dPAR = deriv->data.F32;
+        dPAR[PM_PAR_SKY]  = +1.0;
+        dPAR[PM_PAR_I0]   = +r;
+        dPAR[PM_PAR_XPOS] = q*(2*px/PAR[PM_PAR_SXX] + Y*PAR[PM_PAR_SXY]);
+        dPAR[PM_PAR_YPOS] = q*(2*py/PAR[PM_PAR_SYY] + X*PAR[PM_PAR_SXY]);
+        // the extra factor of 2 below is needed to avoid excessive swings
+        dPAR[PM_PAR_SXX]  = +4.0*q*px*px/PAR[PM_PAR_SXX];
+        dPAR[PM_PAR_SYY]  = +4.0*q*py*py/PAR[PM_PAR_SYY];
+        dPAR[PM_PAR_SXY]  = -q*X*Y;
+    }
+    return(f);
+}
+
+// define the parameter limits
+// AR_MAX is the maximum allowed axis ratio
+// AR_RATIO is ((1-R)/(1+R))^2 where R = AR_MAX^(-2)
+# define AR_MAX 20.0
+# define AR_RATIO 0.99
+
+bool PM_MODEL_LIMITS (psMinConstraintMode mode, int nParam, float *params, float *beta)
+{
+    float beta_lim = 0, params_min = 0, params_max = 0;
+    float f1 = 0, f2 = 0, q1 = 0, q2 = 0;
+
+    // we need to calculate the limits for SXY specially
+    if (nParam == PM_PAR_SXY) {
+        f1 = 1.0 / PS_SQR(params[PM_PAR_SYY]) + 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        f2 = 1.0 / PS_SQR(params[PM_PAR_SYY]) - 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        q1 = PS_SQR(f1)*AR_RATIO - PS_SQR(f2);
+        q1 = PS_MAX (0.0, q1);
+        // if q1 < 0.0, f2 ~ f1, we have a very large axis ratio near 45deg..  Saturate at that
+        // angle and let f2,f1 fight it out
+        q2  = 0.5*sqrt (q1);
+    }
+
+    switch (mode) {
+    case PS_MINIMIZE_BETA_LIMIT:
+        switch (nParam) {
+        case PM_PAR_SKY:
+            beta_lim = 1000;
+            break;
+        case PM_PAR_I0:
+            beta_lim = 3e6;
+            break;
+        case PM_PAR_XPOS:
+            beta_lim = 5;
+            break;
+        case PM_PAR_YPOS:
+            beta_lim = 5;
+            break;
+        case PM_PAR_SXX:
+            beta_lim = 2.0;
+            break;
+        case PM_PAR_SYY:
+            beta_lim = 2.0;
+            break;
+        case PM_PAR_SXY:
+            beta_lim =  0.5*q2;
+            break;
+        default:
+            psAbort("invalid parameter %d for beta test", nParam);
+        }
+        if (fabs(beta[nParam]) > fabs(beta_lim)) {
+            beta[nParam] = (beta[nParam] > 0) ? fabs(beta_lim) : -fabs(beta_lim);
+            psTrace ("psModules.objects", 5, "|beta[nParam==%d]| > |beta_lim|; %g v. %g",
+                     nParam, beta[nParam], beta_lim);
+            return false;
+        }
+        return true;
+    case PS_MINIMIZE_PARAM_MIN:
+        switch (nParam) {
+        case PM_PAR_SKY:
+            params_min = -1000;
+            break;
+        case PM_PAR_I0:
+            params_min =   0.01;
+            break;
+        case PM_PAR_XPOS:
+            params_min =  -100;
+            break;
+        case PM_PAR_YPOS:
+            params_min =  -100;
+            break;
+        case PM_PAR_SXX:
+            params_min =   0.5;
+            break;
+        case PM_PAR_SYY:
+            params_min =   0.5;
+            break;
+        case PM_PAR_SXY:
+            params_min =   -q2;
+            break;
+        default:
+            psAbort("invalid parameter %d for param min test", nParam);
+        }
+        if (params[nParam] < params_min) {
+            params[nParam] = params_min;
+            psTrace ("psModules.objects", 5, "params[nParam==%d] < params_min; %g v. %g",
+                     nParam, params[nParam], params_min);
+            return false;
+        }
+        return true;
+    case PS_MINIMIZE_PARAM_MAX:
+        switch (nParam) {
+        case PM_PAR_SKY:
+            params_max =   1e5;
+            break;
+        case PM_PAR_I0:
+            params_max =   1e8;
+            break;
+        case PM_PAR_XPOS:
+            params_max =   1e4;
+            break;
+        case PM_PAR_YPOS:
+            params_max =   1e4;
+            break;
+        case PM_PAR_SXX:
+            params_max =   100;
+            break;
+        case PM_PAR_SYY:
+            params_max =   100;
+            break;
+        case PM_PAR_SXY:
+            params_max =   +q2;
+            break;
+        default:
+            psAbort("invalid parameter %d for param max test", nParam);
+        }
+        if (params[nParam] > params_max) {
+            params[nParam] = params_max;
+            psTrace ("psModules.objects", 5, "params[nParam==%d] > params_max; %g v. %g",
+                     nParam, params[nParam], params_max);
+            return false;
+        }
+        return true;
+    default:
+        psAbort("invalid choice for limits");
+    }
+    psAbort("should not reach here");
+    return false;
+}
+
+// make an initial guess for parameters
+bool PM_MODEL_GUESS (pmModel *model, pmSource *source)
+{
+    pmMoments *moments = source->moments;
+    psF32     *PAR  = model->params->data.F32;
+
+    psEllipseMoments emoments;
+    emoments.x2 = moments->Sx;
+    emoments.y2 = moments->Sy;
+    emoments.xy = moments->Sxy;
+
+    // force the axis ratio to be < 20.0
+    psEllipseAxes axes = psEllipseMomentsToAxes (emoments, 20.0);
+    psEllipseShape shape = psEllipseAxesToShape (axes);
+
+    PAR[PM_PAR_SKY]  = moments->Sky;
+    PAR[PM_PAR_I0]   = moments->Peak - moments->Sky;
+    PAR[PM_PAR_XPOS] = moments->x;
+    PAR[PM_PAR_YPOS] = moments->y;
+    PAR[PM_PAR_SXX] = PS_MAX(0.5, M_SQRT2*shape.sx);
+    PAR[PM_PAR_SYY] = PS_MAX(0.5, M_SQRT2*shape.sy);
+    PAR[PM_PAR_SXY] = shape.sxy;
+    return(true);
+}
+
+psF64 PM_MODEL_FLUX (const psVector *params)
+{
+
+    psEllipseShape shape;
+
+    psF32 *PAR = params->data.F32;
+
+    shape.sx  = PAR[PM_PAR_SXX] / M_SQRT2;
+    shape.sy  = PAR[PM_PAR_SYY] / M_SQRT2;
+    shape.sxy = PAR[PM_PAR_SXY];
+
+    // Area is equivalent to 2 pi sigma^2
+    // axes ratio < 20
+    psEllipseAxes axes = psEllipseShapeToAxes (shape, 20.0);
+    psF64 Area = 2.0 * M_PI * axes.major * axes.minor;
+
+    psF64 Flux = params->data.F32[PM_PAR_I0] * Area;
+
+    return(Flux);
+}
+
+// return the radius which yields the requested flux
+// this function is never allowed to return <= 0
+psF64 PM_MODEL_RADIUS (const psVector *params, psF64 flux)
+{
+    psEllipseShape shape;
+
+    psF32 *PAR = params->data.F32;
+
+    if (flux <= 0)
+        return (1.0);
+    if (PAR[PM_PAR_I0] <= 0)
+        return (1.0);
+    if (flux >= PAR[PM_PAR_I0])
+        return (1.0);
+
+    shape.sx  = PAR[PM_PAR_SXX] / M_SQRT2;
+    shape.sy  = PAR[PM_PAR_SYY] / M_SQRT2;
+    shape.sxy = PAR[PM_PAR_SXY];
+
+    psEllipseAxes axes = psEllipseShapeToAxes (shape, 20.0);
+    psF64 radius = axes.major * sqrt (2.0 * log(PAR[PM_PAR_I0] / flux));
+    return (radius);
+}
+
+// construct the PSF model from the FLT model and the psf
+bool PM_MODEL_FROM_PSF (pmModel *modelPSF, pmModel *modelFLT, const pmPSF *psf)
+{
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+
+    // we require these two parameters to exist
+    assert (psf->params->n > PM_PAR_YPOS);
+    assert (psf->params->n > PM_PAR_XPOS);
+
+    // supply the model-fitted parameters, or copy from the input
+    for (int i = 0; i < psf->params->n; i++) {
+        if (psf->params->data[i] == NULL) {
+            out[i] = in[i];
+        } else {
+            pmTrend2D *trend = psf->params->data[i];
+            out[i] = pmTrend2DEval(trend, in[PM_PAR_XPOS], in[PM_PAR_YPOS]);
+        }
+    }
+
+    // the OLD 2D model for SXY actually fitted SXY / (SXX^-2 + SYY^-2); correct here
+    // out[PM_PAR_SXY] = pmPSF_SXYtoModel (out);
+
+    // the 2D PSF model fits polarization terms (E0,E1,E2)
+    // convert to shape terms (SXX,SYY,SXY)
+    // XXX user-defined value for limit?
+    if (!pmPSF_FitToModel (out, 0.1)) {
+        psError(PM_ERR_PSF, false, "Failed to fit object at (r,c) = (%.1f,%.1f)",
+                in[PM_PAR_YPOS], in[PM_PAR_XPOS]);
+        return false;
+    }
+
+    // apply the model limits here: this truncates excessive extrapolation
+    // XXX do we need to do this still?  should we put in asserts to test?
+    for (int i = 0; i < psf->params->n; i++) {
+        // apply the limits to all components or just the psf-model parameters?
+        if (psf->params->data[i] == NULL)
+            continue;
+
+        bool status = true;
+        status &= PM_MODEL_LIMITS (PS_MINIMIZE_PARAM_MIN, i, out, NULL);
+        status &= PM_MODEL_LIMITS (PS_MINIMIZE_PARAM_MAX, i, out, NULL);
+        if (!status) {
+            psTrace ("psModules.objects", 5, "Hitting parameter limits at (r,c) = (%.1f, %.1f)",
+                     in[PM_PAR_XPOS], in[PM_PAR_YPOS]);
+            modelPSF->flags |= PM_MODEL_STATUS_LIMITS;
+        }
+    }
+    return(true);
+}
+
+// construct the PSF model from the FLT model and the psf
+// XXX is this sufficiently general do be a global function, not a pmModelClass function?
+bool PM_MODEL_PARAMS_FROM_PSF (pmModel *model, const pmPSF *psf, float Xo, float Yo, float Io)
+{
+    psF32 *PAR = model->params->data.F32;
+
+    // we require these two parameters to exist
+    assert (psf->params->n > PM_PAR_YPOS);
+    assert (psf->params->n > PM_PAR_XPOS);
+
+    PAR[PM_PAR_SKY]  = 0.0;
+    PAR[PM_PAR_I0]   = Io;
+    PAR[PM_PAR_XPOS] = Xo;
+    PAR[PM_PAR_YPOS] = Yo;
+
+    // supply the model-fitted parameters, or copy from the input
+    for (int i = 0; i < psf->params->n; i++) {
+        if (i == PM_PAR_SKY) continue;
+        if (i == PM_PAR_I0) continue;
+        if (i == PM_PAR_XPOS) continue;
+        if (i == PM_PAR_YPOS) continue;
+        pmTrend2D *trend = psf->params->data[i];
+        PAR[i] = pmTrend2DEval(trend, Xo, Yo);
+    }
+
+    // the 2D PSF model fits polarization terms (E0,E1,E2)
+    // convert to shape terms (SXX,SYY,SXY)
+    // XXX user-defined value for limit?
+    if (!pmPSF_FitToModel (PAR, 0.1)) {
+        psError(PM_ERR_PSF, false, "Failed to fit object at (r,c) = (%.1f,%.1f)", Xo, Yo);
+        return false;
+    }
+
+    // apply the model limits here: this truncates excessive extrapolation
+    // XXX do we need to do this still?  should we put in asserts to test?
+    for (int i = 0; i < psf->params->n; i++) {
+        // apply the limits to all components or just the psf-model parameters?
+        if (psf->params->data[i] == NULL)
+            continue;
+
+        bool status = true;
+        status &= PM_MODEL_LIMITS (PS_MINIMIZE_PARAM_MIN, i, PAR, NULL);
+        status &= PM_MODEL_LIMITS (PS_MINIMIZE_PARAM_MAX, i, PAR, NULL);
+        if (!status) {
+            psTrace ("psModules.objects", 5, "Hitting parameter limits at (r,c) = (%.1f, %.1f)", Xo, Yo);
+            model->flags |= PM_MODEL_STATUS_LIMITS;
+        }
+    }
+    return(true);
+}
+
+// check the status of the fitted model
+// this test is invalid if the parameters are derived
+// from the PSF model
+bool PM_MODEL_FIT_STATUS (pmModel *model)
+{
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[PM_PAR_SXX] / PAR[PM_PAR_SXX]);
+    dP += PS_SQR(dPAR[PM_PAR_SYY] / PAR[PM_PAR_SYY]);
+    dP = sqrt (dP);
+
+    status = true;
+    status &= (dP < 0.5);
+    status &= (PAR[PM_PAR_I0] > 0);
+    status &= ((dPAR[PM_PAR_I0]/PAR[PM_PAR_I0]) < 0.5);
+
+    if (status)
+        return true;
+    return false;
+}
+
+# undef PM_MODEL_FUNC
+# undef PM_MODEL_FLUX
+# undef PM_MODEL_GUESS
+# undef PM_MODEL_LIMITS
+# undef PM_MODEL_RADIUS
+# undef PM_MODEL_FROM_PSF
+# undef PM_MODEL_PARAMS_FROM_PSF
+# undef PM_MODEL_FIT_STATUS
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_PGAUSS.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_PGAUSS.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_PGAUSS.c	(revision 22157)
@@ -0,0 +1,443 @@
+/******************************************************************************
+ * this file defines the PGAUSS source shape model.  Note that these model functions are loaded
+ * by pmModelGroup.c using 'include', and thus need no 'include' statements of their own.  The
+ * models use a psVector to represent the set of parameters, with the sequence used to specify
+ * the meaning of the parameter.  The meaning of the parameters may thus vary depending on the
+ * specifics of the model.  All models which are used a PSF representations share a few
+ * parameters, for which # define names are listed in pmModel.h:
+
+   Gaussian taylor expansion
+   1 / (1 + z + z^2/2 + z^3/6)
+
+ * PM_PAR_SKY 0   - local sky : note that this is unused and may be dropped in the future
+ * PM_PAR_I0 1    - central intensity
+ * PM_PAR_XPOS 2  - X center of object
+ * PM_PAR_YPOS 3  - Y center of object
+ * PM_PAR_SXX 4   - X^2 term of elliptical contour (sqrt(2) * SigmaX)
+ * PM_PAR_SYY 5   - Y^2 term of elliptical contour (sqrt(2) * SigmaY)
+ * PM_PAR_SXY 6   - X*Y term of elliptical contour
+ *****************************************************************************/
+
+# define PM_MODEL_FUNC            pmModelFunc_PGAUSS
+# define PM_MODEL_FLUX            pmModelFlux_PGAUSS
+# define PM_MODEL_GUESS           pmModelGuess_PGAUSS
+# define PM_MODEL_LIMITS          pmModelLimits_PGAUSS
+# define PM_MODEL_RADIUS          pmModelRadius_PGAUSS
+# define PM_MODEL_FROM_PSF        pmModelFromPSF_PGAUSS
+# define PM_MODEL_PARAMS_FROM_PSF pmModelParamsFromPSF_PGAUSS
+# define PM_MODEL_FIT_STATUS      pmModelFitStatus_PGAUSS
+
+// the model is a function of the pixel coordinate (pixcoord[0,1] = x,y)
+psF32 PM_MODEL_FUNC(psVector *deriv,
+                    const psVector *params,
+                    const psVector *pixcoord)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = pixcoord->data.F32[0] - PAR[PM_PAR_XPOS];
+    psF32 Y  = pixcoord->data.F32[1] - PAR[PM_PAR_YPOS];
+    psF32 px = X / PAR[PM_PAR_SXX];
+    psF32 py = Y / PAR[PM_PAR_SYY];
+    psF32 z  = PS_SQR(px) + PS_SQR(py) + PAR[PM_PAR_SXY]*X*Y;
+    assert (z >= 0.0);
+
+    psF32 t  = 1 + z + z*z/2.0;
+    psF32 r  = 1.0 / (t + z*z*z/6.0); /* exp (-Z) */
+    psF32 f  = PAR[PM_PAR_I0]*r + PAR[PM_PAR_SKY];
+
+    if (deriv != NULL) {
+        psF32 *dPAR = deriv->data.F32;
+        psF32 q = PAR[PM_PAR_I0]*r*r*t;
+        dPAR[PM_PAR_SKY] = +1.0;
+        dPAR[PM_PAR_I0] = +r;
+        dPAR[PM_PAR_XPOS] = q*(2.0*px/PAR[PM_PAR_SXX] + Y*PAR[PM_PAR_SXY]);
+        dPAR[PM_PAR_YPOS] = q*(2.0*py/PAR[PM_PAR_SYY] + X*PAR[PM_PAR_SXY]);
+        // the extra factor of 2 below is needed to avoid excessive swings
+        dPAR[PM_PAR_SXX] =  +4.0*q*px*px/PAR[PM_PAR_SXX];
+        dPAR[PM_PAR_SYY] =  +4.0*q*py*py/PAR[PM_PAR_SYY];
+        dPAR[PM_PAR_SXY] = -q*X*Y;
+    }
+    return(f);
+}
+
+// define the parameter limits
+// AR_MAX is the maximum allowed axis ratio
+// AR_RATIO is ((1-R)/(1+R))^2 where R = AR_MAX^(-2)
+# define AR_MAX 20.0
+# define AR_RATIO 0.99
+
+bool PM_MODEL_LIMITS (psMinConstraintMode mode, int nParam, float *params, float *beta)
+{
+    float beta_lim = 0, params_min = 0, params_max = 0;
+    float f1 = 0, f2 = 0, q1 = 0, q2 = 0;
+
+    // we need to calculate the limits for SXY specially
+    if (nParam == PM_PAR_SXY) {
+        f1 = 1.0 / PS_SQR(params[PM_PAR_SYY]) + 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        f2 = 1.0 / PS_SQR(params[PM_PAR_SYY]) - 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        q1 = PS_SQR(f1)*AR_RATIO - PS_SQR(f2);
+        q1 = PS_MAX (0.0, q1);
+        // if q1 < 0.0, f2 ~ f1, we have a very large axis ratio near 45deg..  Saturate at that
+        // angle and let f2,f1 fight it out
+        q2  = 0.5*sqrt (q1);
+    }
+
+    switch (mode) {
+    case PS_MINIMIZE_BETA_LIMIT:
+        switch (nParam) {
+        case PM_PAR_SKY:
+            beta_lim = 1000;
+            break;
+        case PM_PAR_I0:
+            beta_lim = 3e6;
+            break;
+        case PM_PAR_XPOS:
+            beta_lim = 5;
+            break;
+        case PM_PAR_YPOS:
+            beta_lim = 5;
+            break;
+        case PM_PAR_SXX:
+            beta_lim = 2.0;
+            break;
+        case PM_PAR_SYY:
+            beta_lim = 2.0;
+            break;
+        case PM_PAR_SXY:
+            beta_lim =  0.5*q2;
+            break;
+        default:
+            psAbort("invalid parameter %d for beta test", nParam);
+        }
+        if (fabs(beta[nParam]) > fabs(beta_lim)) {
+            beta[nParam] = (beta[nParam] > 0) ? fabs(beta_lim) : -fabs(beta_lim);
+            psTrace ("psModules.objects", 5, "|beta[nParam==%d]| > |beta_lim|; %g v. %g",
+                     nParam, beta[nParam], beta_lim);
+            return false;
+        }
+        return true;
+    case PS_MINIMIZE_PARAM_MIN:
+        switch (nParam) {
+        case PM_PAR_SKY:
+            params_min = -1000;
+            break;
+        case PM_PAR_I0:
+            params_min =  0.01;
+            break;
+        case PM_PAR_XPOS:
+            params_min =  -100;
+            break;
+        case PM_PAR_YPOS:
+            params_min =  -100;
+            break;
+        case PM_PAR_SXX:
+            params_min =   0.5;
+            break;
+        case PM_PAR_SYY:
+            params_min =   0.5;
+            break;
+        case PM_PAR_SXY:
+            params_min =   -q2;
+            break;
+        default:
+            psAbort("invalid parameter %d for param min test", nParam);
+        }
+        if (params[nParam] < params_min) {
+            params[nParam] = params_min;
+            psTrace ("psModules.objects", 5, "params[nParam==%d] < params_min; %g v. %g",
+                     nParam, params[nParam], params_min);
+            return false;
+        }
+        return true;
+    case PS_MINIMIZE_PARAM_MAX:
+        switch (nParam) {
+        case PM_PAR_SKY:
+            params_max =   1e5;
+            break;
+        case PM_PAR_I0:
+            params_max =   1e8;
+            break;
+        case PM_PAR_XPOS:
+            params_max =   1e4;
+            break;
+        case PM_PAR_YPOS:
+            params_max =   1e4;
+            break;
+        case PM_PAR_SXX:
+            params_max =   100;
+            break;
+        case PM_PAR_SYY:
+            params_max =   100;
+            break;
+        case PM_PAR_SXY:
+            params_max =   +q2;
+            break;
+        default:
+            psAbort("invalid parameter %d for param max test", nParam);
+        }
+        if (params[nParam] > params_max) {
+            params[nParam] = params_max;
+            psTrace ("psModules.objects", 5, "params[nParam==%d] > params_max; %g v. %g",
+                     nParam, params[nParam], params_max);
+            return false;
+        }
+        return true;
+    default:
+        psAbort("invalid choice for limits");
+    }
+    psAbort("should not reach here");
+    return false;
+}
+
+// make an initial guess for parameters
+bool PM_MODEL_GUESS (pmModel *model, pmSource *source)
+{
+    pmMoments *moments = source->moments;
+    psF32     *PAR     = model->params->data.F32;
+
+    psEllipseMoments emoments;
+    emoments.x2 = moments->Sx;
+    emoments.y2 = moments->Sx;
+    emoments.xy = moments->Sxy;
+
+    psEllipseAxes axes = psEllipseMomentsToAxes (emoments, 20.0);
+    psEllipseShape shape = psEllipseAxesToShape (axes);
+
+    PAR[PM_PAR_SKY]  = moments->Sky;
+    PAR[PM_PAR_I0]   = moments->Peak - moments->Sky;
+    PAR[PM_PAR_XPOS] = moments->x; // XXX use peak->xf, peak->yf?
+    PAR[PM_PAR_YPOS] = moments->y;
+    PAR[PM_PAR_SXX] = PS_MAX(0.5, M_SQRT2*shape.sx);
+    PAR[PM_PAR_SYY] = PS_MAX(0.5, M_SQRT2*shape.sy);
+    PAR[PM_PAR_SXY] = shape.sxy;
+    return(true);
+}
+
+psF64 PM_MODEL_FLUX(const psVector *params)
+{
+    float norm, z;
+    psEllipseShape shape;
+
+    psF32 *PAR = params->data.F32;
+
+    shape.sx  = PAR[PM_PAR_SXX] / M_SQRT2;
+    shape.sy  = PAR[PM_PAR_SYY] / M_SQRT2;
+    shape.sxy = PAR[PM_PAR_SXY];
+
+    // Area is equivalent to 2 pi sigma^2
+    psEllipseAxes axes = psEllipseShapeToAxes (shape, 20.0);
+    psF64 Area = 2.0 * M_PI * axes.major * axes.minor;
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+
+    # define DZ 0.25
+
+    float f0 = 1.0;
+    float f1, f2;
+    for (z = DZ; z < 50; z += DZ) {
+        f1 = 1.0 / (1 + z + z*z/2.0 + z*z*z/6.0);
+        z += DZ;
+        f2 = 1.0 / (1 + z + z*z/2.0 + z*z*z/6.0);
+        norm += f0 + 4*f1 + f2;
+        f0 = f2;
+    }
+    norm *= DZ / 3.0;
+
+    psF64 Flux = PAR[PM_PAR_I0] * Area * norm;
+
+    return(Flux);
+}
+
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 PM_MODEL_RADIUS (const psVector *params, psF64 flux)
+{
+    psF64 z, f;
+    int Nstep = 0;
+    psEllipseShape shape;
+
+    psF32 *PAR = params->data.F32;
+
+    if (flux <= 0)
+        return (1.0);
+    if (PAR[PM_PAR_I0] <= 0)
+        return (1.0);
+    if (flux >= PAR[PM_PAR_I0])
+        return (1.0);
+
+    shape.sx  = PAR[PM_PAR_SXX] / M_SQRT2;
+    shape.sy  = PAR[PM_PAR_SYY] / M_SQRT2;
+    shape.sxy = PAR[PM_PAR_SXY];
+
+    // this estimates the radius assuming f(z) is roughly exp(-z)
+    psEllipseAxes axes = psEllipseShapeToAxes (shape, 20.0);
+    psF64 sigma = axes.major;
+
+    psF64 limit = flux / PAR[PM_PAR_I0];
+
+    // use the fact that f is monotonically decreasing
+    z = 0;
+    Nstep = 0;
+
+    // choose a z value guaranteed to be beyond our limit
+    float z0 = pow((1.0 / limit), (1.0 / 3.0));
+    float z1 = (1.0 / limit);
+    z1 = PS_MAX (z0, z1);
+    z0 = 0.0;
+
+    // perform a type of bisection to find the value
+    float f0 = 1.0 / (1 + z0 + z0*z0/2.0 + z0*z0*z0/6.0);
+    float f1 = 1.0 / (1 + z1 + z1*z1/2.0 + z1*z1*z1/6.0);
+    while ((Nstep < 10) && (fabs(z1 - z0) > 0.5)) {
+        z = 0.5*(z0 + z1);
+        f = 1.0 / (1 + z + z*z/2.0 + z*z*z/6.0);
+        if (f > limit) {
+            z0 = z;
+            f0 = f;
+        } else {
+            z1 = z;
+            f1 = f;
+        }
+        Nstep ++;
+    }
+    psF64 radius = sigma * sqrt (2.0 * z);
+
+    // psF64 radius = axes.major * sqrt (2.0 * log(PAR[PM_PAR_I0] / flux));
+
+    if (isnan(radius))
+        psAbort("error in code: radius is NaN");
+    if (radius < 0)
+        psAbort("error in code: radius is negative");
+
+    return (radius);
+}
+
+bool PM_MODEL_FROM_PSF (pmModel *modelPSF, pmModel *modelFLT, const pmPSF *psf)
+{
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+
+    // we require these two parameters to exist
+    assert (psf->params->n > PM_PAR_YPOS);
+    assert (psf->params->n > PM_PAR_XPOS);
+
+    for (int i = 0; i < psf->params->n; i++) {
+        if (psf->params->data[i] == NULL) {
+            out[i] = in[i];
+        } else {
+            pmTrend2D *trend = psf->params->data[i];
+            out[i] = pmTrend2DEval(trend, in[PM_PAR_XPOS], in[PM_PAR_YPOS]);
+        }
+    }
+
+    // the OLD 2D model for SXY actually fitted SXY / (SXX^-2 + SYY^-2); correct here
+    // out[PM_PAR_SXY] = pmPSF_SXYtoModel (out);
+
+    // the 2D PSF model fits polarization terms (E0,E1,E2)
+    // convert to shape terms (SXX,SYY,SXY)
+    if (!pmPSF_FitToModel (out, 0.1)) {
+        psError(PM_ERR_PSF, false, "Failed to fit object at (r,c) = (%.1f,%.1f)",
+                in[PM_PAR_YPOS], in[PM_PAR_XPOS]);
+        return false;
+    }
+
+    // apply the model limits here: this truncates excessive extrapolation
+    // XXX do we need to do this still?  should we put in asserts to test?
+    for (int i = 0; i < psf->params->n; i++) {
+        // apply the limits to all components or just the psf-model parameters?
+        if (psf->params->data[i] == NULL)
+            continue;
+
+        bool status = true;
+        status &= PM_MODEL_LIMITS (PS_MINIMIZE_PARAM_MIN, i, out, NULL);
+        status &= PM_MODEL_LIMITS (PS_MINIMIZE_PARAM_MAX, i, out, NULL);
+        if (!status) {
+            psTrace ("psModules.objects", 5, "Hitting parameter limits at (r,c) = (%.1f, %.1f)",
+                     in[PM_PAR_XPOS], in[PM_PAR_YPOS]);
+            modelPSF->flags |= PM_MODEL_STATUS_LIMITS;
+        }
+    }
+    return(true);
+}
+
+// construct the PSF model from the FLT model and the psf
+// XXX is this sufficiently general do be a global function, not a pmModelClass function?
+bool PM_MODEL_PARAMS_FROM_PSF (pmModel *model, const pmPSF *psf, float Xo, float Yo, float Io)
+{
+    psF32 *PAR = model->params->data.F32;
+
+    // we require these two parameters to exist
+    assert (psf->params->n > PM_PAR_YPOS);
+    assert (psf->params->n > PM_PAR_XPOS);
+
+    PAR[PM_PAR_SKY]  = 0.0;
+    PAR[PM_PAR_I0]   = Io;
+    PAR[PM_PAR_XPOS] = Xo;
+    PAR[PM_PAR_YPOS] = Yo;
+
+    // supply the model-fitted parameters, or copy from the input
+    for (int i = 0; i < psf->params->n; i++) {
+        if (i == PM_PAR_SKY) continue;
+        if (i == PM_PAR_I0) continue;
+        if (i == PM_PAR_XPOS) continue;
+        if (i == PM_PAR_YPOS) continue;
+        pmTrend2D *trend = psf->params->data[i];
+        PAR[i] = pmTrend2DEval(trend, Xo, Yo);
+    }
+
+    // the 2D PSF model fits polarization terms (E0,E1,E2)
+    // convert to shape terms (SXX,SYY,SXY)
+    // XXX user-defined value for limit?
+    if (!pmPSF_FitToModel (PAR, 0.1)) {
+        psError(PM_ERR_PSF, false, "Failed to fit object at (r,c) = (%.1f,%.1f)", Xo, Yo);
+        return false;
+    }
+
+    // apply the model limits here: this truncates excessive extrapolation
+    // XXX do we need to do this still?  should we put in asserts to test?
+    for (int i = 0; i < psf->params->n; i++) {
+        // apply the limits to all components or just the psf-model parameters?
+        if (psf->params->data[i] == NULL)
+            continue;
+
+        bool status = true;
+        status &= PM_MODEL_LIMITS (PS_MINIMIZE_PARAM_MIN, i, PAR, NULL);
+        status &= PM_MODEL_LIMITS (PS_MINIMIZE_PARAM_MAX, i, PAR, NULL);
+        if (!status) {
+            psTrace ("psModules.objects", 5, "Hitting parameter limits at (r,c) = (%.1f, %.1f)", Xo, Yo);
+            model->flags |= PM_MODEL_STATUS_LIMITS;
+        }
+    }
+    return(true);
+}
+
+bool PM_MODEL_FIT_STATUS (pmModel *model)
+{
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[PM_PAR_SXX] / PAR[PM_PAR_SXX]);
+    dP += PS_SQR(dPAR[PM_PAR_SYY] / PAR[PM_PAR_SYY]);
+    dP = sqrt (dP);
+
+    status = true;
+    status &= (dP < 0.5);
+    status &= (PAR[PM_PAR_I0] > 0);
+    status &= ((dPAR[PM_PAR_I0]/PAR[PM_PAR_I0]) < 0.5);
+
+    return status;
+}
+
+# undef PM_MODEL_FUNC
+# undef PM_MODEL_FLUX
+# undef PM_MODEL_GUESS
+# undef PM_MODEL_LIMITS
+# undef PM_MODEL_RADIUS
+# undef PM_MODEL_FROM_PSF
+# undef PM_MODEL_PARAMS_FROM_PSF
+# undef PM_MODEL_FIT_STATUS
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_QGAUSS.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_QGAUSS.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_QGAUSS.c	(revision 22157)
@@ -0,0 +1,474 @@
+/******************************************************************************
+ * this file defines the QGAUSS source shape model (XXX need a better name!).  Note that these
+ * model functions are loaded by pmModelGroup.c using 'include', and thus need no 'include'
+ * statements of their own.  The models use a psVector to represent the set of parameters, with
+ * the sequence used to specify the meaning of the parameter.  The meaning of the parameters
+ * may thus vary depending on the specifics of the model.  All models which are used a PSF
+ * representations share a few parameters, for which # define names are listed in pmModel.h:
+
+   power-law with fitted linear term
+   1 / (1 + kz + z^2.25)
+
+   * PM_PAR_SKY 0   - local sky : note that this is unused and may be dropped in the future
+   * PM_PAR_I0 1    - central intensity
+   * PM_PAR_XPOS 2  - X center of object
+   * PM_PAR_YPOS 3  - Y center of object
+   * PM_PAR_SXX 4   - X^2 term of elliptical contour (sqrt(2) / SigmaX)
+   * PM_PAR_SYY 5   - Y^2 term of elliptical contour (sqrt(2) / SigmaY)
+   * PM_PAR_SXY 6   - X*Y term of elliptical contour
+   * PM_PAR_7   7   - amplitude of the linear component (k)
+   *****************************************************************************/
+
+# define PM_MODEL_FUNC            pmModelFunc_QGAUSS
+# define PM_MODEL_FLUX            pmModelFlux_QGAUSS
+# define PM_MODEL_GUESS           pmModelGuess_QGAUSS
+# define PM_MODEL_LIMITS          pmModelLimits_QGAUSS
+# define PM_MODEL_RADIUS          pmModelRadius_QGAUSS
+# define PM_MODEL_FROM_PSF        pmModelFromPSF_QGAUSS
+# define PM_MODEL_PARAMS_FROM_PSF pmModelParamsFromPSF_QGAUSS
+# define PM_MODEL_FIT_STATUS      pmModelFitStatus_QGAUSS
+
+psF32 PM_MODEL_FUNC (psVector *deriv,
+                     const psVector *params,
+                     const psVector *pixcoord)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = pixcoord->data.F32[0] - PAR[PM_PAR_XPOS];
+    psF32 Y  = pixcoord->data.F32[1] - PAR[PM_PAR_YPOS];
+    psF32 px = X / PAR[PM_PAR_SXX];
+    psF32 py = Y / PAR[PM_PAR_SYY];
+    psF32 z  = PS_SQR(px) + PS_SQR(py) + PAR[PM_PAR_SXY]*X*Y;
+
+    // XXX if the elliptical contour is defined in valid way, this step should not be required.
+    // other models (like PGAUSS) don't use fractional powers, and thus do not have NaN values
+    // for negative values of z
+    // XXX use an assert here to force the elliptical parameters to be correctly determined
+    // if (z < 0) z = 0;
+    assert (z >= 0);
+
+    psF32 zp = pow(z,1.25);
+    psF32 r  = 1.0 / (1 + PAR[PM_PAR_7]*z + z*zp);
+
+    psF32 r1 = PAR[PM_PAR_I0]*r;
+    psF32 f  = r1 + PAR[PM_PAR_SKY];
+
+    if (deriv != NULL) {
+        psF32 *dPAR = deriv->data.F32;
+
+        // note difference from a pure gaussian: q = params->data.F32[PM_PAR_I0]*r
+        psF32 t = r1*r;
+        psF32 q = t*(PAR[PM_PAR_7] + 2.25*zp);
+
+        dPAR[PM_PAR_SKY]  = +1.0;
+        dPAR[PM_PAR_I0]   = +r;
+        dPAR[PM_PAR_XPOS] = q*(2.0*px/PAR[PM_PAR_SXX] + Y*PAR[PM_PAR_SXY]);
+        dPAR[PM_PAR_YPOS] = q*(2.0*py/PAR[PM_PAR_SYY] + X*PAR[PM_PAR_SXY]);
+        // the extra factor of 2 below is needed to avoid excessive swings
+        dPAR[PM_PAR_SXX]  = +4.0*q*px*px/PAR[PM_PAR_SXX];
+        dPAR[PM_PAR_SYY]  = +4.0*q*py*py/PAR[PM_PAR_SYY];
+        dPAR[PM_PAR_SXY]  = -q*X*Y;
+        dPAR[PM_PAR_7]    = -t*z;
+    }
+    return(f);
+}
+
+// define the parameter limits
+// AR_MAX is the maximum allowed axis ratio
+// AR_RATIO is ((1-R)/(1+R))^2 where R = AR_MAX^(-2)
+# define AR_MAX 20.0
+# define AR_RATIO 0.99
+bool PM_MODEL_LIMITS (psMinConstraintMode mode, int nParam, float *params, float *beta)
+{
+    float beta_lim = 0, params_min = 0, params_max = 0;
+    float f1 = 0, f2 = 0, q1 = 0, q2 = 0;
+
+    // we need to calculate the limits for SXY specially
+    if (nParam == PM_PAR_SXY) {
+        f1 = 1.0 / PS_SQR(params[PM_PAR_SYY]) + 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        f2 = 1.0 / PS_SQR(params[PM_PAR_SYY]) - 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        q1 = PS_SQR(f1)*AR_RATIO - PS_SQR(f2);
+        q1 = (q1 < 0.0) ? 0.0 : q1;
+        // if q1 < 0.0, f2 ~ f1, we have a very large axis ratio near 45deg..  Saturate at that
+        // angle and let f2,f1 fight it out
+        q2  = 0.5*sqrt (q1);
+    }
+
+    switch (mode) {
+    case PS_MINIMIZE_BETA_LIMIT:
+        switch (nParam) {
+        case PM_PAR_SKY:
+            beta_lim = 1000;
+            break;
+        case PM_PAR_I0:
+            beta_lim = 3e6;
+            break;
+        case PM_PAR_XPOS:
+            beta_lim = 5;
+            break;
+        case PM_PAR_YPOS:
+            beta_lim = 5;
+            break;
+        case PM_PAR_SXX:
+            beta_lim = 1.0;
+            break;
+        case PM_PAR_SYY:
+            beta_lim = 1.0;
+            break;
+        case PM_PAR_SXY:
+            beta_lim =  0.5*q2;
+            break;
+        case PM_PAR_7:
+            beta_lim = 2.0;
+            break;
+        default:
+            psAbort("invalid parameter %d for beta test", nParam);
+        }
+        if (fabs(beta[nParam]) > fabs(beta_lim)) {
+            beta[nParam] = (beta[nParam] > 0) ? fabs(beta_lim) : -fabs(beta_lim);
+            psTrace ("psModules.objects", 5, "|beta[nParam==%d]| > |beta_lim|; %g v. %g",
+                     nParam, beta[nParam], beta_lim);
+            return false;
+        }
+        return true;
+    case PS_MINIMIZE_PARAM_MIN:
+        switch (nParam) {
+        case PM_PAR_SKY:
+            params_min = -1000;
+            break;
+        case PM_PAR_I0:
+            params_min =   0.01;
+            break;
+        case PM_PAR_XPOS:
+            params_min =  -100;
+            break;
+        case PM_PAR_YPOS:
+            params_min =  -100;
+            break;
+        case PM_PAR_SXX:
+            params_min =   0.5;
+            break;
+        case PM_PAR_SYY:
+            params_min =   0.5;
+            break;
+        case PM_PAR_SXY:
+            params_min =  -q2;
+            break;
+        case PM_PAR_7:
+            params_min =   0.1;
+            break;
+        default:
+            psAbort("invalid parameter %d for param min test", nParam);
+        }
+        if (params[nParam] < params_min) {
+            params[nParam] = params_min;
+            psTrace ("psModules.objects", 5, "params[nParam==%d] < params_min; %g v. %g",
+                     nParam, params[nParam], params_min);
+            return false;
+        }
+        return true;
+    case PS_MINIMIZE_PARAM_MAX:
+        switch (nParam) {
+        case PM_PAR_SKY:
+            params_max =   1e5;
+            break;
+        case PM_PAR_I0:
+            params_max =   1e8;
+            break;
+        case PM_PAR_XPOS:
+            params_max =   1e4;
+            break;
+        case PM_PAR_YPOS:
+            params_max =   1e4;
+            break;
+        case PM_PAR_SXX:
+            params_max =   100;
+            break;
+        case PM_PAR_SYY:
+            params_max =   100;
+            break;
+        case PM_PAR_SXY:
+            params_max =  +q2;
+            break;
+        case PM_PAR_7:
+            params_max =  20.0;
+            break;
+        default:
+            psAbort("invalid parameter %d for param max test", nParam);
+        }
+        if (params[nParam] > params_max) {
+            params[nParam] = params_max;
+            psTrace ("psModules.objects", 5, "params[nParam==%d] > params_max; %g v. %g",
+                     nParam, params[nParam], params_max);
+            return false;
+        }
+        return true;
+    default:
+        psAbort("invalid choice for limits");
+    }
+    psAbort("should not reach here");
+    return false;
+}
+
+
+// make an initial guess for parameters
+bool PM_MODEL_GUESS (pmModel *model, pmSource *source)
+{
+    pmMoments *moments = source->moments;
+    pmPeak    *peak    = source->peak;
+    psF32     *PAR  = model->params->data.F32;
+
+    psEllipseMoments emoments;
+    emoments.x2 = moments->Sx;
+    emoments.y2 = moments->Sy;
+    emoments.xy = moments->Sxy;
+
+    // force the axis ratio to be < 20.0
+    psEllipseAxes axes = psEllipseMomentsToAxes (emoments, 20.0);
+
+    if (!isfinite(axes.major)) return false;
+    if (!isfinite(axes.minor)) return false;
+    if (!isfinite(axes.theta)) return false;
+
+    psEllipseShape shape = psEllipseAxesToShape (axes);
+
+    if (!isfinite(shape.sx))  return false;
+    if (!isfinite(shape.sy))  return false;
+    if (!isfinite(shape.sxy)) return false;
+
+    // XXX turn this off here for now PAR[PM_PAR_SKY]  = moments->Sky;
+    PAR[PM_PAR_SKY]  = 0.0;
+    PAR[PM_PAR_I0]   = moments->Peak;
+    PAR[PM_PAR_XPOS] = peak->x;
+    PAR[PM_PAR_YPOS] = peak->y;
+    PAR[PM_PAR_SXX]  = PS_MAX(0.5, M_SQRT2*shape.sx);
+    PAR[PM_PAR_SYY]  = PS_MAX(0.5, M_SQRT2*shape.sy);
+    PAR[PM_PAR_SXY]  = shape.sxy;
+    PAR[PM_PAR_7]    = 1.0;
+
+    return(true);
+}
+
+psF64 PM_MODEL_FLUX (const psVector *params)
+{
+    float z, norm;
+    psEllipseShape shape;
+
+    psF32 *PAR = params->data.F32;
+
+    shape.sx  = PAR[PM_PAR_SXX] / M_SQRT2;
+    shape.sy  = PAR[PM_PAR_SYY] / M_SQRT2;
+    shape.sxy = PAR[PM_PAR_SXY];
+
+    // Area is equivalent to 2 pi sigma^2
+    psEllipseAxes axes = psEllipseShapeToAxes (shape, 20.0);
+    psF64 Area = 2.0 * M_PI * axes.major * axes.minor;
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+
+    # define DZ 0.25
+
+    float f0 = 1.0;
+    float f1, f2;
+    for (z = DZ; z < 50; z += DZ) {
+        f1 = 1.0 / (1 + PAR[PM_PAR_7]*z + pow(z, 2.25));
+        z += DZ;
+        f2 = 1.0 / (1 + PAR[PM_PAR_7]*z + pow(z, 2.25));
+        norm += f0 + 4*f1 + f2;
+        f0 = f2;
+    }
+    norm *= DZ / 3.0;
+
+    psF64 Flux = PAR[PM_PAR_I0] * Area * norm;
+
+    return(Flux);
+}
+
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 PM_MODEL_RADIUS (const psVector *params, psF64 flux)
+{
+    psF64 z, f;
+    int Nstep = 0;
+    psEllipseShape shape;
+
+    psF32 *PAR = params->data.F32;
+
+    if (flux <= 0)
+        return (1.0);
+    if (PAR[PM_PAR_I0] <= 0)
+        return (1.0);
+    if (flux >= PAR[PM_PAR_I0])
+        return (1.0);
+
+    shape.sx  = PAR[PM_PAR_SXX] / M_SQRT2;
+    shape.sy  = PAR[PM_PAR_SYY] / M_SQRT2;
+    shape.sxy = PAR[PM_PAR_SXY];
+
+    psEllipseAxes axes = psEllipseShapeToAxes (shape, 20.0);
+    psF64 sigma = axes.major;
+
+    psF64 limit = flux / PAR[PM_PAR_I0];
+
+    // use the fact that f is monotonically decreasing
+    z = 0;
+    Nstep = 0;
+
+    // choose a z value guaranteed to be beyond our limit
+    float z0 = pow((1.0 / limit), (1.0 / 2.25));
+    float z1 = (1.0 / limit) / PAR[PM_PAR_7];
+    z1 = PS_MAX (z0, z1);
+    z0 = 0.0;
+
+    // perform a type of bisection to find the value
+    float f0 = 1.0 / (1 + PAR[PM_PAR_7]*z0 + pow(z0, 2.25));
+    float f1 = 1.0 / (1 + PAR[PM_PAR_7]*z1 + pow(z1, 2.25));
+    while ((Nstep < 10) && (fabs(z1 - z0) > 0.5)) {
+        z = 0.5*(z0 + z1);
+        f = 1.0 / (1 + PAR[PM_PAR_7]*z + pow(z, 2.25));
+        if (f > limit) {
+            z0 = z;
+            f0 = f;
+        } else {
+            z1 = z;
+            f1 = f;
+        }
+        Nstep ++;
+    }
+    psF64 radius = sigma * sqrt (2.0 * z);
+
+    if (isnan(radius))
+        psAbort("error in code: radius is NaN");
+
+    return (radius);
+}
+
+bool PM_MODEL_FROM_PSF (pmModel *modelPSF, pmModel *modelFLT, const pmPSF *psf)
+{
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+
+    // we require these two parameters to exist
+    assert (psf->params->n > PM_PAR_YPOS);
+    assert (psf->params->n > PM_PAR_XPOS);
+
+    for (int i = 0; i < psf->params->n; i++) {
+        if (psf->params->data[i] == NULL) {
+            out[i] = in[i];
+        } else {
+            pmTrend2D *trend = psf->params->data[i];
+            out[i] = pmTrend2DEval(trend, in[PM_PAR_XPOS], in[PM_PAR_YPOS]);
+        }
+    }
+
+    // the 2D PSF model fits polarization terms (E0,E1,E2)
+    // convert to shape terms (SXX,SYY,SXY)
+    if (!pmPSF_FitToModel (out, 0.1)) {
+        psError(PM_ERR_PSF, false, "Failed to fit object at (r,c) = (%.1f,%.1f)",
+                in[PM_PAR_YPOS], in[PM_PAR_XPOS]);
+        return false;
+    }
+
+    // apply the model limits here: this truncates excessive extrapolation
+    // XXX do we need to do this still?  should we put in asserts to test?
+    for (int i = 0; i < psf->params->n; i++) {
+        // apply the limits to all components or just the psf-model parameters?
+        if (psf->params->data[i] == NULL)
+            continue;
+
+        bool status = true;
+        status &= PM_MODEL_LIMITS(PS_MINIMIZE_PARAM_MIN, i, out, NULL);
+        status &= PM_MODEL_LIMITS(PS_MINIMIZE_PARAM_MAX, i, out, NULL);
+        if (!status) {
+            psTrace ("psModules.objects", 5, "Hitting parameter limits at (r,c) = (%.1f, %.1f)",
+                     in[PM_PAR_XPOS], in[PM_PAR_YPOS]);
+            modelPSF->flags |= PM_MODEL_STATUS_LIMITS;
+        }
+    }
+
+    return true;
+}
+
+// construct the PSF model from the FLT model and the psf
+// XXX is this sufficiently general do be a global function, not a pmModelClass function?
+bool PM_MODEL_PARAMS_FROM_PSF (pmModel *model, const pmPSF *psf, float Xo, float Yo, float Io)
+{
+    psF32 *PAR = model->params->data.F32;
+
+    // we require these two parameters to exist
+    assert (psf->params->n > PM_PAR_YPOS);
+    assert (psf->params->n > PM_PAR_XPOS);
+
+    PAR[PM_PAR_SKY]  = 0.0;
+    PAR[PM_PAR_I0]   = Io;
+    PAR[PM_PAR_XPOS] = Xo;
+    PAR[PM_PAR_YPOS] = Yo;
+
+    // supply the model-fitted parameters, or copy from the input
+    for (int i = 0; i < psf->params->n; i++) {
+        if (i == PM_PAR_SKY) continue;
+        if (i == PM_PAR_I0) continue;
+        if (i == PM_PAR_XPOS) continue;
+        if (i == PM_PAR_YPOS) continue;
+        pmTrend2D *trend = psf->params->data[i];
+        PAR[i] = pmTrend2DEval(trend, Xo, Yo);
+    }
+
+    // the 2D PSF model fits polarization terms (E0,E1,E2)
+    // convert to shape terms (SXX,SYY,SXY)
+    // XXX user-defined value for limit?
+    if (!pmPSF_FitToModel (PAR, 0.1)) {
+        psError(PM_ERR_PSF, false, "Failed to fit object at (r,c) = (%.1f,%.1f)", Xo, Yo);
+        return false;
+    }
+
+    // apply the model limits here: this truncates excessive extrapolation
+    // XXX do we need to do this still?  should we put in asserts to test?
+    for (int i = 0; i < psf->params->n; i++) {
+        // apply the limits to all components or just the psf-model parameters?
+        if (psf->params->data[i] == NULL)
+            continue;
+
+        bool status = true;
+        status &= PM_MODEL_LIMITS (PS_MINIMIZE_PARAM_MIN, i, PAR, NULL);
+        status &= PM_MODEL_LIMITS (PS_MINIMIZE_PARAM_MAX, i, PAR, NULL);
+        if (!status) {
+            psTrace ("psModules.objects", 5, "Hitting parameter limits at (r,c) = (%.1f, %.1f)", Xo, Yo);
+            model->flags |= PM_MODEL_STATUS_LIMITS;
+        }
+    }
+    return(true);
+}
+
+bool PM_MODEL_FIT_STATUS (pmModel *model)
+{
+
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[PM_PAR_SXX] / PAR[PM_PAR_SXX]);
+    dP += PS_SQR(dPAR[PM_PAR_SYY] / PAR[PM_PAR_SYY]);
+    dP = sqrt (dP);
+
+    status = true;
+//    status &= (dP < 0.5);
+    status &= (PAR[PM_PAR_I0] > 0);
+    status &= ((dPAR[PM_PAR_I0]/PAR[PM_PAR_I0]) < 0.5);
+
+    return status;
+}
+
+# undef PM_MODEL_FUNC
+# undef PM_MODEL_FLUX
+# undef PM_MODEL_GUESS
+# undef PM_MODEL_LIMITS
+# undef PM_MODEL_RADIUS
+# undef PM_MODEL_FROM_PSF
+# undef PM_MODEL_PARAMS_FROM_PSF
+# undef PM_MODEL_FIT_STATUS
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_RGAUSS.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_RGAUSS.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_RGAUSS.c	(revision 22157)
@@ -0,0 +1,466 @@
+/******************************************************************************
+ * this file defines the RGAUSS source shape model (XXX need a better name!).  Note that these
+ * model functions are loaded by pmModelGroup.c using 'include', and thus need no 'include'
+ * statements of their own.  The models use a psVector to represent the set of parameters, with
+ * the sequence used to specify the meaning of the parameter.  The meaning of the parameters
+ * may thus vary depending on the specifics of the model.  All models which are used a PSF
+ * representations share a few parameters, for which # define names are listed in pmModel.h:
+
+   power-law with fitted slope
+   1 / (1 + z + z^alpha)
+
+ * PM_PAR_SKY 0   - local sky : note that this is unused and may be dropped in the future
+ * PM_PAR_I0 1    - central intensity
+ * PM_PAR_XPOS 2  - X center of object
+ * PM_PAR_YPOS 3  - Y center of object
+ * PM_PAR_SXX 4   - X^2 term of elliptical contour (sqrt(2) / SigmaX)
+ * PM_PAR_SYY 5   - Y^2 term of elliptical contour (sqrt(2) / SigmaY)
+ * PM_PAR_SXY 6   - X*Y term of elliptical contour
+ * PM_PAR_7   7   - power-law slope (alpha)
+ *****************************************************************************/
+
+# define PM_MODEL_FUNC            pmModelFunc_RGAUSS
+# define PM_MODEL_FLUX            pmModelFlux_RGAUSS
+# define PM_MODEL_GUESS           pmModelGuess_RGAUSS
+# define PM_MODEL_LIMITS          pmModelLimits_RGAUSS
+# define PM_MODEL_RADIUS          pmModelRadius_RGAUSS
+# define PM_MODEL_FROM_PSF        pmModelFromPSF_RGAUSS
+# define PM_MODEL_PARAMS_FROM_PSF pmModelParamsFromPSF_RGAUSS
+# define PM_MODEL_FIT_STATUS      pmModelFitStatus_RGAUSS
+
+psF32 PM_MODEL_FUNC (psVector *deriv,
+                     const psVector *params,
+                     const psVector *pixcoord)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = pixcoord->data.F32[0] - PAR[PM_PAR_XPOS];
+    psF32 Y  = pixcoord->data.F32[1] - PAR[PM_PAR_YPOS];
+    psF32 px = X / PAR[PM_PAR_SXX];
+    psF32 py = Y / PAR[PM_PAR_SYY];
+    psF32 z  = PS_SQR(px) + PS_SQR(py) + X*Y*PAR[PM_PAR_SXY];
+
+    assert (z >= 0);
+
+    psF32 p  = pow(z, PAR[PM_PAR_7] - 1.0);
+    psF32 r  = 1.0 / (1 + z + z*p);
+    psF32 f  = PAR[PM_PAR_I0]*r + PAR[PM_PAR_SKY];
+
+    if (deriv != NULL) {
+        psF32 *dPAR = deriv->data.F32;
+
+        // note difference from a pure gaussian: q = params->data.F32[PM_PAR_I0]*r
+        psF32 t = PAR[PM_PAR_I0]*r*r;
+        psF32 q = t*(1 + PAR[PM_PAR_7]*p);
+
+        dPAR[PM_PAR_SKY] = +1.0;
+        dPAR[PM_PAR_I0] = +r;
+        dPAR[PM_PAR_XPOS] = q*(2.0*px/PAR[PM_PAR_SXX] + Y*PAR[PM_PAR_SXY]);
+        dPAR[PM_PAR_YPOS] = q*(2.0*py/PAR[PM_PAR_SYY] + X*PAR[PM_PAR_SXY]);
+        dPAR[PM_PAR_SXX] = +2.0*q*px*px/PAR[PM_PAR_SXX];
+        dPAR[PM_PAR_SYY] = +2.0*q*py*py/PAR[PM_PAR_SYY];
+        dPAR[PM_PAR_SXY] = -q*X*Y;
+
+        // this model derivative is undefined at z = 0.0, but is actually 0.0
+        dPAR[PM_PAR_7] = (z == 0.0) ? 0.0 : -5.0*t*log(z)*p*z;
+    }
+    return(f);
+}
+
+// define the parameter limits
+// AR_MAX is the maximum allowed axis ratio
+// AR_RATIO is ((1-R)/(1+R))^2 where R = AR_MAX^(-2)
+# define AR_MAX 20.0
+# define AR_RATIO 0.99
+bool PM_MODEL_LIMITS (psMinConstraintMode mode, int nParam, float *params, float *beta)
+{
+    float beta_lim = 0, params_min = 0, params_max = 0;
+    float f1 = 0, f2 = 0, q1 = 0, q2 = 0;
+
+    // we need to calculate the limits for SXY specially
+    if (nParam == PM_PAR_SXY) {
+        f1 = 1.0 / PS_SQR(params[PM_PAR_SYY]) + 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        f2 = 1.0 / PS_SQR(params[PM_PAR_SYY]) - 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        q1 = PS_SQR(f1)*AR_RATIO - PS_SQR(f2);
+        q1 = (q1 < 0.0) ? 0.0 : q1;
+        // if q1 < 0.0, f2 ~ f1, we have a very large axis ratio near 45deg..  Saturate at that
+        // angle and let f2,f1 fight it out
+        q2  = 0.5*sqrt (q1);
+    }
+
+    switch (mode) {
+    case PS_MINIMIZE_BETA_LIMIT:
+        switch (nParam) {
+        case PM_PAR_SKY:
+            beta_lim = 1000;
+            break;
+        case PM_PAR_I0:
+            beta_lim = 3e6;
+            break;
+        case PM_PAR_XPOS:
+            beta_lim = 5;
+            break;
+        case PM_PAR_YPOS:
+            beta_lim = 5;
+            break;
+        case PM_PAR_SXX:
+            beta_lim = 0.5;
+            break;
+        case PM_PAR_SYY:
+            beta_lim = 0.5;
+            break;
+        case PM_PAR_SXY:
+            beta_lim =  0.5*q2;
+            break;
+        case PM_PAR_7:
+            beta_lim = 0.5;
+            break;
+        default:
+            psAbort("invalid parameter %d for beta test", nParam);
+        }
+        if (fabs(beta[nParam]) > fabs(beta_lim)) {
+            beta[nParam] = (beta[nParam] > 0) ? fabs(beta_lim) : -fabs(beta_lim);
+            psTrace ("psModules.objects", 5, "|beta[nParam==%d]| > |beta_lim|; %g v. %g",
+                     nParam, beta[nParam], beta_lim);
+            return false;
+        }
+        return true;
+    case PS_MINIMIZE_PARAM_MIN:
+        switch (nParam) {
+        case PM_PAR_SKY:
+            params_min = -1000;
+            break;
+        case PM_PAR_I0:
+            params_min =   0.01;
+            break;
+        case PM_PAR_XPOS:
+            params_min =  -100;
+            break;
+        case PM_PAR_YPOS:
+            params_min =  -100;
+            break;
+        case PM_PAR_SXX:
+            params_min =   0.5;
+            break;
+        case PM_PAR_SYY:
+            params_min =   0.5;
+            break;
+        case PM_PAR_SXY:
+            params_min =  -q2;
+            break;
+        case PM_PAR_7:
+            params_min =   1.25;
+            break;
+        default:
+            psAbort("invalid parameter %d for param min test", nParam);
+        }
+        if (params[nParam] < params_min) {
+            params[nParam] = params_min;
+            psTrace ("psModules.objects", 5, "params[nParam==%d] < params_min; %g v. %g",
+                     nParam, params[nParam], params_min);
+            return false;
+        }
+        return true;
+    case PS_MINIMIZE_PARAM_MAX:
+        switch (nParam) {
+        case PM_PAR_SKY:
+            params_max =   1e5;
+            break;
+        case PM_PAR_I0:
+            params_max =   1e8;
+            break;
+        case PM_PAR_XPOS:
+            params_max =   1e4;
+            break;
+        case PM_PAR_YPOS:
+            params_max =   1e4;
+            break;
+        case PM_PAR_SXX:
+            params_max =   100;
+            break;
+        case PM_PAR_SYY:
+            params_max =   100;
+            break;
+        case PM_PAR_SXY:
+            params_max =  +q2;
+            break;
+        case PM_PAR_7:
+            params_max =  4.0;
+            break;
+        default:
+            psAbort("invalid parameter %d for param max test", nParam);
+        }
+        if (params[nParam] > params_max) {
+            params[nParam] = params_max;
+            psTrace ("psModules.objects", 5, "params[nParam==%d] > params_max; %g v. %g",
+                     nParam, params[nParam], params_max);
+            return false;
+        }
+        return true;
+    default:
+        psAbort("invalid choice for limits");
+    }
+    psAbort("should not reach here");
+    return false;
+}
+
+// make an initial guess for parameters
+bool PM_MODEL_GUESS (pmModel *model, pmSource *source)
+{
+    pmMoments *moments = source->moments;
+    pmPeak    *peak    = source->peak;
+    psF32     *PAR  = model->params->data.F32;
+
+    psEllipseMoments emoments;
+    emoments.x2 = moments->Sx;
+    emoments.y2 = moments->Sy;
+    emoments.xy = moments->Sxy;
+
+    // force the axis ratio to be < 20.0
+    psEllipseAxes axes = psEllipseMomentsToAxes (emoments, 20.0);
+
+    if (!isfinite(axes.major)) return false;
+    if (!isfinite(axes.minor)) return false;
+    if (!isfinite(axes.theta)) return false;
+
+    psEllipseShape shape = psEllipseAxesToShape (axes);
+
+    if (!isfinite(shape.sx))  return false;
+    if (!isfinite(shape.sy))  return false;
+    if (!isfinite(shape.sxy)) return false;
+
+    PAR[PM_PAR_SKY]  = moments->Sky;
+    PAR[PM_PAR_I0]   = moments->Peak - moments->Sky;
+    PAR[PM_PAR_XPOS] = peak->x;
+    PAR[PM_PAR_YPOS] = peak->y;
+    PAR[PM_PAR_SXX]  = PS_MAX(0.5, M_SQRT2*shape.sx);
+    PAR[PM_PAR_SYY]  = PS_MAX(0.5, M_SQRT2*shape.sy);
+    PAR[PM_PAR_SXY]  = shape.sxy;
+    PAR[PM_PAR_7]    = 2.25;
+
+    return(true);
+}
+
+psF64 PM_MODEL_FLUX (const psVector *params)
+{
+    float norm, z;
+    psEllipseShape shape;
+
+    psF32 *PAR = params->data.F32;
+
+    shape.sx  = PAR[PM_PAR_SXX] / M_SQRT2;
+    shape.sy  = PAR[PM_PAR_SYY] / M_SQRT2;
+    shape.sxy = PAR[PM_PAR_SXY];
+
+    // Area is equivalent to 2 pi sigma^2
+    psEllipseAxes axes = psEllipseShapeToAxes (shape, 20.0);
+    psF64 Area = 2.0 * M_PI * axes.major * axes.minor;
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+
+    # define DZ 0.25
+
+    float f0 = 1.0;
+    float f1, f2;
+    for (z = DZ; z < 50; z += DZ) {
+        f1 = 1.0 / (1 + z + pow(z, PAR[PM_PAR_7]));
+        z += DZ;
+        f2 = 1.0 / (1 + z + pow(z, PAR[PM_PAR_7]));
+        norm += f0 + 4*f1 + f2;
+        f0 = f2;
+    }
+    norm *= DZ / 3.0;
+
+    psF64 Flux = PAR[PM_PAR_I0] * Area * norm;
+
+    return(Flux);
+}
+
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 PM_MODEL_RADIUS (const psVector *params, psF64 flux)
+{
+    psF64 z, f;
+    int Nstep = 0;
+    psEllipseShape shape;
+
+    psF32 *PAR = params->data.F32;
+
+    if (flux <= 0)
+        return (1.0);
+    if (PAR[PM_PAR_I0] <= 0)
+        return (1.0);
+    if (flux >= PAR[PM_PAR_I0])
+        return (1.0);
+
+    shape.sx  = PAR[PM_PAR_SXX] / M_SQRT2;
+    shape.sy  = PAR[PM_PAR_SYY] / M_SQRT2;
+    shape.sxy = PAR[PM_PAR_SXY];
+
+    psEllipseAxes axes = psEllipseShapeToAxes (shape, 20.0);
+    psF64 sigma = axes.major;
+
+    psF64 limit = flux / PAR[PM_PAR_I0];
+
+    // use the fact that f is monotonically decreasing
+    z = 0;
+    Nstep = 0;
+
+    // choose a z value guaranteed to be beyond our limit
+    float z0 = pow((1.0 / limit), (1.0 / PAR[PM_PAR_7]));
+    float z1 = (1.0 / limit);
+    z1 = PS_MAX (z0, z1);
+    z0 = 0.0;
+
+    // perform a type of bisection to find the value
+    float f0 = 1.0 / (1 + z0 + pow(z0, PAR[PM_PAR_7]));
+    float f1 = 1.0 / (1 + z1 + pow(z1, PAR[PM_PAR_7]));
+    while ((Nstep < 10) && (fabs(z1 - z0) > 0.5)) {
+        z = 0.5*(z0 + z1);
+        f = 1.0 / (1 + z + pow(z, PAR[PM_PAR_7]));
+        if (f > limit) {
+            z0 = z;
+            f0 = f;
+        } else {
+            z1 = z;
+            f1 = f;
+        }
+        Nstep ++;
+    }
+    psF64 radius = sigma * sqrt (2.0 * z);
+
+    if (isnan(radius))
+        psAbort("error in code: radius is NaN");
+
+    return (radius);
+}
+
+bool PM_MODEL_FROM_PSF (pmModel *modelPSF, pmModel *modelFLT, const pmPSF *psf)
+{
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+
+    // we require these two parameters to exist
+    assert (psf->params->n > PM_PAR_YPOS);
+    assert (psf->params->n > PM_PAR_XPOS);
+
+    for (int i = 0; i < psf->params->n; i++) {
+        if (psf->params->data[i] == NULL) {
+            out[i] = in[i];
+        } else {
+            pmTrend2D *trend = psf->params->data[i];
+            out[i] = pmTrend2DEval(trend, in[PM_PAR_XPOS], in[PM_PAR_YPOS]);
+        }
+    }
+
+    // the 2D PSF model fits polarization terms (E0,E1,E2)
+    // convert to shape terms (SXX,SYY,SXY)
+    if (!pmPSF_FitToModel (out, 0.1)) {
+        psError(PM_ERR_PSF, false, "Failed to fit object at (r,c) = (%.1f,%.1f)",
+                in[PM_PAR_YPOS], in[PM_PAR_XPOS]);
+        return false;
+    }
+
+    // apply the model limits here: this truncates excessive extrapolation
+    // XXX do we need to do this still?  should we put in asserts to test?
+    for (int i = 0; i < psf->params->n; i++) {
+        // apply the limits to all components or just the psf-model parameters?
+        if (psf->params->data[i] == NULL)
+            continue;
+
+        bool status = true;
+        status &= PM_MODEL_LIMITS(PS_MINIMIZE_PARAM_MIN, i, out, NULL);
+        status &= PM_MODEL_LIMITS(PS_MINIMIZE_PARAM_MAX, i, out, NULL);
+        if (!status) {
+            psTrace ("psModules.objects", 5, "Hitting parameter limits at (r,c) = (%.1f, %.1f)",
+                     in[PM_PAR_XPOS], in[PM_PAR_YPOS]);
+            modelPSF->flags |= PM_MODEL_STATUS_LIMITS;
+        }
+    }
+
+    return true;
+}
+
+// construct the PSF model from the FLT model and the psf
+// XXX is this sufficiently general do be a global function, not a pmModelClass function?
+bool PM_MODEL_PARAMS_FROM_PSF (pmModel *model, const pmPSF *psf, float Xo, float Yo, float Io)
+{
+    psF32 *PAR = model->params->data.F32;
+
+    // we require these two parameters to exist
+    assert (psf->params->n > PM_PAR_YPOS);
+    assert (psf->params->n > PM_PAR_XPOS);
+
+    PAR[PM_PAR_SKY]  = 0.0;
+    PAR[PM_PAR_I0]   = Io;
+    PAR[PM_PAR_XPOS] = Xo;
+    PAR[PM_PAR_YPOS] = Yo;
+
+    // supply the model-fitted parameters, or copy from the input
+    for (int i = 0; i < psf->params->n; i++) {
+        if (i == PM_PAR_SKY) continue;
+        if (i == PM_PAR_I0) continue;
+        if (i == PM_PAR_XPOS) continue;
+        if (i == PM_PAR_YPOS) continue;
+        pmTrend2D *trend = psf->params->data[i];
+        PAR[i] = pmTrend2DEval(trend, Xo, Yo);
+    }
+
+    // the 2D PSF model fits polarization terms (E0,E1,E2)
+    // convert to shape terms (SXX,SYY,SXY)
+    // XXX user-defined value for limit?
+    if (!pmPSF_FitToModel (PAR, 0.1)) {
+        psError(PM_ERR_PSF, false, "Failed to fit object at (r,c) = (%.1f,%.1f)", Xo, Yo);
+        return false;
+    }
+
+    // apply the model limits here: this truncates excessive extrapolation
+    // XXX do we need to do this still?  should we put in asserts to test?
+    for (int i = 0; i < psf->params->n; i++) {
+        // apply the limits to all components or just the psf-model parameters?
+        if (psf->params->data[i] == NULL)
+            continue;
+
+        bool status = true;
+        status &= PM_MODEL_LIMITS (PS_MINIMIZE_PARAM_MIN, i, PAR, NULL);
+        status &= PM_MODEL_LIMITS (PS_MINIMIZE_PARAM_MAX, i, PAR, NULL);
+        if (!status) {
+            psTrace ("psModules.objects", 5, "Hitting parameter limits at (r,c) = (%.1f, %.1f)", Xo, Yo);
+            model->flags |= PM_MODEL_STATUS_LIMITS;
+        }
+    }
+    return(true);
+}
+
+bool PM_MODEL_FIT_STATUS (pmModel *model)
+{
+
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[PM_PAR_SXX] / PAR[PM_PAR_SXX]);
+    dP += PS_SQR(dPAR[PM_PAR_SYY] / PAR[PM_PAR_SYY]);
+    dP = sqrt (dP);
+
+    status = true;
+    status &= (dP < 0.5);
+    status &= (PAR[PM_PAR_I0] > 0);
+    status &= ((dPAR[PM_PAR_I0]/PAR[PM_PAR_I0]) < 0.5);
+
+    return status;
+}
+
+# undef PM_MODEL_FUNC
+# undef PM_MODEL_FLUX
+# undef PM_MODEL_GUESS
+# undef PM_MODEL_LIMITS
+# undef PM_MODEL_RADIUS
+# undef PM_MODEL_FROM_PSF
+# undef PM_MODEL_PARAMS_FROM_PSF
+# undef PM_MODEL_FIT_STATUS
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_SERSIC.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_SERSIC.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_SERSIC.c	(revision 22157)
@@ -0,0 +1,462 @@
+/******************************************************************************
+ * this file defines the SERSIC source shape model.  Note that these model functions are loaded
+ * by pmModelGroup.c using 'include', and thus need no 'include' statements of their own.  The
+ * models use a psVector to represent the set of parameters, with the sequence used to specify
+ * the meaning of the parameter.  The meaning of the parameters may thus vary depending on the
+ * specifics of the model.  All models which are used a PSF representations share a few
+ * parameters, for which # define names are listed in pmModel.h:
+
+   f = exp(-z^n)
+
+   * PM_PAR_SKY 0   - local sky : note that this is unused and may be dropped in the future
+   * PM_PAR_I0 1    - central intensity
+   * PM_PAR_XPOS 2  - X center of object
+   * PM_PAR_YPOS 3  - Y center of object
+   * PM_PAR_SXX 4   - X^2 term of elliptical contour (sqrt(2) / SigmaX)
+   * PM_PAR_SYY 5   - Y^2 term of elliptical contour (sqrt(2) / SigmaY)
+   * PM_PAR_SXY 6   - X*Y term of elliptical contour
+   * PM_PAR_7   7   - normalized sersic parameter
+
+   note that a standard sersic model uses exp(-K*(z^(1/n) - 1).  the additional elements (K,
+   the -1 offset) are absorbed in this model by the normalization, the exponent, and the
+   radial scale.  We fit the elements in this form, then re-normalize them on output.
+   *****************************************************************************/
+
+# define PM_MODEL_FUNC            pmModelFunc_SERSIC
+# define PM_MODEL_FLUX            pmModelFlux_SERSIC
+# define PM_MODEL_GUESS           pmModelGuess_SERSIC
+# define PM_MODEL_LIMITS          pmModelLimits_SERSIC
+# define PM_MODEL_RADIUS          pmModelRadius_SERSIC
+# define PM_MODEL_FROM_PSF        pmModelFromPSF_SERSIC
+# define PM_MODEL_PARAMS_FROM_PSF pmModelParamsFromPSF_SERSIC
+# define PM_MODEL_FIT_STATUS      pmModelFitStatus_SERSIC
+
+psF32 PM_MODEL_FUNC (psVector *deriv,
+                     const psVector *params,
+                     const psVector *pixcoord)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = pixcoord->data.F32[0] - PAR[PM_PAR_XPOS];
+    psF32 Y  = pixcoord->data.F32[1] - PAR[PM_PAR_YPOS];
+    psF32 px = X / PAR[PM_PAR_SXX];
+    psF32 py = Y / PAR[PM_PAR_SYY];
+    psF32 z  = PS_SQR(px) + PS_SQR(py) + PAR[PM_PAR_SXY]*X*Y;
+
+    // XXX if the elliptical contour is defined in valid way, this step should not be required.
+    // other models (like PGAUSS) don't use fractional powers, and thus do not have NaN values
+    // for negative values of z
+    // XXX use an assert here to force the elliptical parameters to be correctly determined
+    // if (z < 0) z = 0;
+    assert (z >= 0);
+
+    psF32 f2 = pow(z,PAR[PM_PAR_7]);
+    psF32 f1 = exp(-f2);
+    psF32 z0 = PAR[PM_PAR_I0]*f1;
+    psF32 f0 = PAR[PM_PAR_SKY] + z0;
+
+    assert (isfinite(f2));
+    assert (isfinite(f1));
+    assert (isfinite(z0));
+    assert (isfinite(f0));
+
+    if (deriv != NULL) {
+        psF32 *dPAR = deriv->data.F32;
+
+        // gradient is infinite for z = 0; saturate at z = 0.01
+        psF32 z1 = (z < 0.01) ? z0*PAR[PM_PAR_7]*pow(0.01,PAR[PM_PAR_7] - 1.0) : z0*PAR[PM_PAR_7]*pow(z,PAR[PM_PAR_7] - 1.0);
+
+        dPAR[PM_PAR_SKY]  = +1.0;
+        dPAR[PM_PAR_I0]   = +f1;
+        dPAR[PM_PAR_7]    = (z == 0.0) ? 0.0 : -z0*f2*log(z);
+
+        assert (isfinite(z1));
+        assert (isfinite(dPAR[PM_PAR_7]));
+
+        dPAR[PM_PAR_XPOS] = +1.0*z1*(2.0*px/PAR[PM_PAR_SXX] + Y*PAR[PM_PAR_SXY]);
+        dPAR[PM_PAR_YPOS] = +1.0*z1*(2.0*py/PAR[PM_PAR_SYY] + X*PAR[PM_PAR_SXY]);
+        dPAR[PM_PAR_SXX]  = +2.0*z1*px*px/PAR[PM_PAR_SXX];
+        dPAR[PM_PAR_SYY]  = +2.0*z1*py*py/PAR[PM_PAR_SYY];
+        dPAR[PM_PAR_SXY]  = -1.0*z1*X*Y;
+        dPAR[PM_PAR_SXY]  = -1.0*z1*X*Y;
+    }
+    return (f0);
+}
+
+// define the parameter limits
+// AR_MAX is the maximum allowed axis ratio
+// AR_RATIO is ((1-R)/(1+R))^2 where R = AR_MAX^(-2)
+# define AR_MAX 20.0
+# define AR_RATIO 0.99
+bool PM_MODEL_LIMITS (psMinConstraintMode mode, int nParam, float *params, float *beta)
+{
+    float beta_lim = 0, params_min = 0, params_max = 0;
+    float f1 = 0, f2 = 0, q1 = 0, q2 = 0;
+
+    // we need to calculate the limits for SXY specially
+    if (nParam == PM_PAR_SXY) {
+        f1 = 1.0 / PS_SQR(params[PM_PAR_SYY]) + 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        f2 = 1.0 / PS_SQR(params[PM_PAR_SYY]) - 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        q1 = PS_SQR(f1)*AR_RATIO - PS_SQR(f2);
+        q1 = (q1 < 0.0) ? 0.0 : q1;
+        // if q1 < 0.0, f2 ~ f1, we have a very large axis ratio near 45deg..  Saturate at that
+        // angle and let f2,f1 fight it out
+        q2  = 0.5*sqrt (q1);
+    }
+
+    switch (mode) {
+    case PS_MINIMIZE_BETA_LIMIT:
+        switch (nParam) {
+        case PM_PAR_SKY:
+            beta_lim = 1000;
+            break;
+        case PM_PAR_I0:
+            beta_lim = 3e6;
+            break;
+        case PM_PAR_XPOS:
+            beta_lim = 5;
+            break;
+        case PM_PAR_YPOS:
+            beta_lim = 5;
+            break;
+        case PM_PAR_SXX:
+            beta_lim = 1.0;
+            break;
+        case PM_PAR_SYY:
+            beta_lim = 1.0;
+            break;
+        case PM_PAR_SXY:
+            beta_lim =  0.5*q2;
+            break;
+        case PM_PAR_7:
+            beta_lim = 2.0;
+            break;
+        default:
+            psAbort("invalid parameter %d for beta test", nParam);
+        }
+        if (fabs(beta[nParam]) > fabs(beta_lim)) {
+            beta[nParam] = (beta[nParam] > 0) ? fabs(beta_lim) : -fabs(beta_lim);
+            psTrace ("psModules.objects", 5, "|beta[nParam==%d]| > |beta_lim|; %g v. %g",
+                     nParam, beta[nParam], beta_lim);
+            return false;
+        }
+        return true;
+    case PS_MINIMIZE_PARAM_MIN:
+        switch (nParam) {
+        case PM_PAR_SKY:
+            params_min = -1000;
+            break;
+        case PM_PAR_I0:
+            params_min =     0.01;
+            break;
+        case PM_PAR_XPOS:
+            params_min =  -100;
+            break;
+        case PM_PAR_YPOS:
+            params_min =  -100;
+            break;
+        case PM_PAR_SXX:
+            params_min =   0.05;
+            break;
+        case PM_PAR_SYY:
+            params_min =   0.05;
+            break;
+        case PM_PAR_SXY:
+            params_min =  -q2;
+            break;
+        case PM_PAR_7:
+            params_min =   0.05;
+            break;
+        default:
+            psAbort("invalid parameter %d for param min test", nParam);
+        }
+        if (params[nParam] < params_min) {
+            params[nParam] = params_min;
+            psTrace ("psModules.objects", 5, "params[nParam==%d] < params_min; %g v. %g",
+                     nParam, params[nParam], params_min);
+            return false;
+        }
+        return true;
+    case PS_MINIMIZE_PARAM_MAX:
+        switch (nParam) {
+        case PM_PAR_SKY:
+            params_max =   1e5;
+            break;
+        case PM_PAR_I0:
+            params_max =   1e8;
+            break;
+        case PM_PAR_XPOS:
+            params_max =   1e4;
+            break;
+        case PM_PAR_YPOS:
+            params_max =   1e4;
+            break;
+        case PM_PAR_SXX:
+            params_max =   100;
+            break;
+        case PM_PAR_SYY:
+            params_max =   100;
+            break;
+        case PM_PAR_SXY:
+            params_max =  +q2;
+            break;
+        case PM_PAR_7:
+            params_max =   4.0;
+            break;
+        default:
+            psAbort("invalid parameter %d for param max test", nParam);
+        }
+        if (params[nParam] > params_max) {
+            params[nParam] = params_max;
+            psTrace ("psModules.objects", 5, "params[nParam==%d] > params_max; %g v. %g",
+                     nParam, params[nParam], params_max);
+            return false;
+        }
+        return true;
+    default:
+        psAbort("invalid choice for limits");
+    }
+    psAbort("should not reach here");
+    return false;
+}
+
+
+// make an initial guess for parameters
+bool PM_MODEL_GUESS (pmModel *model, pmSource *source)
+{
+    pmMoments *moments = source->moments;
+    pmPeak    *peak    = source->peak;
+    psF32     *PAR  = model->params->data.F32;
+
+    psEllipseMoments emoments;
+    emoments.x2 = moments->Sx;
+    emoments.y2 = moments->Sy;
+    emoments.xy = moments->Sxy;
+
+    // force the axis ratio to be < 20.0
+    psEllipseAxes axes = psEllipseMomentsToAxes (emoments, 20.0);
+
+    if (!isfinite(axes.major)) return false;
+    if (!isfinite(axes.minor)) return false;
+    if (!isfinite(axes.theta)) return false;
+
+    psEllipseShape shape = psEllipseAxesToShape (axes);
+
+    if (!isfinite(shape.sx))  return false;
+    if (!isfinite(shape.sy))  return false;
+    if (!isfinite(shape.sxy)) return false;
+
+    // XXX PAR[PM_PAR_SKY]  = moments->Sky;
+    PAR[PM_PAR_SKY]  = 0.0;
+    PAR[PM_PAR_I0]   = moments->Peak;
+    PAR[PM_PAR_XPOS] = peak->x;
+    PAR[PM_PAR_YPOS] = peak->y;
+    PAR[PM_PAR_SXX]  = PS_MAX(0.5, M_SQRT2*shape.sx);
+    PAR[PM_PAR_SYY]  = PS_MAX(0.5, M_SQRT2*shape.sy);
+    PAR[PM_PAR_SXY]  = shape.sxy;
+    PAR[PM_PAR_7]    = 0.5;
+
+    return(true);
+}
+
+psF64 PM_MODEL_FLUX (const psVector *params)
+{
+    float z, norm;
+    psEllipseShape shape;
+
+    psF32 *PAR = params->data.F32;
+
+    shape.sx  = PAR[PM_PAR_SXX] / M_SQRT2;
+    shape.sy  = PAR[PM_PAR_SYY] / M_SQRT2;
+    shape.sxy = PAR[PM_PAR_SXY];
+
+    // Area is equivalent to 2 pi sigma^2
+    psEllipseAxes axes = psEllipseShapeToAxes (shape, 20.0);
+    psF64 Area = 2.0 * M_PI * axes.major * axes.minor;
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+
+    # define DZ 0.25
+
+    float f0 = 1.0;
+    float f1, f2;
+    for (z = DZ; z < 50; z += DZ) {
+        f1 = 1.0 / (1 + PAR[PM_PAR_7]*z + pow(z, 2.25));
+        z += DZ;
+        f2 = 1.0 / (1 + PAR[PM_PAR_7]*z + pow(z, 2.25));
+        norm += f0 + 4*f1 + f2;
+        f0 = f2;
+    }
+    norm *= DZ / 3.0;
+
+    psF64 Flux = PAR[PM_PAR_I0] * Area * norm;
+
+    return(Flux);
+}
+
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 PM_MODEL_RADIUS (const psVector *params, psF64 flux)
+{
+    psEllipseShape shape;
+
+    psF32 *PAR = params->data.F32;
+
+    if (flux <= 0)
+        return (1.0);
+    if (PAR[PM_PAR_I0] <= 0)
+        return (1.0);
+    if (flux >= PAR[PM_PAR_I0])
+        return (1.0);
+
+    shape.sx  = PAR[PM_PAR_SXX] / M_SQRT2;
+    shape.sy  = PAR[PM_PAR_SYY] / M_SQRT2;
+    shape.sxy = PAR[PM_PAR_SXY];
+
+    psEllipseAxes axes = psEllipseShapeToAxes (shape, 20.0);
+    psF64 sigma = axes.major;
+
+    psF64 limit = flux / PAR[PM_PAR_I0];
+
+    psF64 z = pow (-log(limit), (1.0 / PAR[PM_PAR_7]));
+
+    psF64 radius = sigma * sqrt (2.0 * z);
+
+    if (isnan(radius))
+        psAbort("error in code: radius is NaN");
+
+    return (radius);
+}
+
+bool PM_MODEL_FROM_PSF (pmModel *modelPSF, pmModel *modelFLT, const pmPSF *psf)
+{
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+
+    // we require these two parameters to exist
+    assert (psf->params->n > PM_PAR_YPOS);
+    assert (psf->params->n > PM_PAR_XPOS);
+
+    for (int i = 0; i < psf->params->n; i++) {
+        if (psf->params->data[i] == NULL) {
+            out[i] = in[i];
+        } else {
+            pmTrend2D *trend = psf->params->data[i];
+            out[i] = pmTrend2DEval(trend, in[PM_PAR_XPOS], in[PM_PAR_YPOS]);
+        }
+    }
+
+    // the 2D PSF model fits polarization terms (E0,E1,E2)
+    // convert to shape terms (SXX,SYY,SXY)
+    if (!pmPSF_FitToModel (out, 0.1)) {
+        psError(PM_ERR_PSF, false, "Failed to fit object at (r,c) = (%.1f,%.1f)",
+                in[PM_PAR_YPOS], in[PM_PAR_XPOS]);
+        return false;
+    }
+
+    // apply the model limits here: this truncates excessive extrapolation
+    // XXX do we need to do this still?  should we put in asserts to test?
+    for (int i = 0; i < psf->params->n; i++) {
+        // apply the limits to all components or just the psf-model parameters?
+        if (psf->params->data[i] == NULL)
+            continue;
+
+        bool status = true;
+        status &= PM_MODEL_LIMITS(PS_MINIMIZE_PARAM_MIN, i, out, NULL);
+        status &= PM_MODEL_LIMITS(PS_MINIMIZE_PARAM_MAX, i, out, NULL);
+        if (!status) {
+            psTrace ("psModules.objects", 5, "Hitting parameter limits at (r,c) = (%.1f, %.1f)",
+                     in[PM_PAR_XPOS], in[PM_PAR_YPOS]);
+            modelPSF->flags |= PM_MODEL_STATUS_LIMITS;
+        }
+    }
+
+    return true;
+}
+
+// construct the PSF model from the FLT model and the psf
+// XXX is this sufficiently general do be a global function, not a pmModelClass function?
+bool PM_MODEL_PARAMS_FROM_PSF (pmModel *model, const pmPSF *psf, float Xo, float Yo, float Io)
+{
+    psF32 *PAR = model->params->data.F32;
+
+    // we require these two parameters to exist
+    assert (psf->params->n > PM_PAR_YPOS);
+    assert (psf->params->n > PM_PAR_XPOS);
+
+    PAR[PM_PAR_SKY]  = 0.0;
+    PAR[PM_PAR_I0]   = Io;
+    PAR[PM_PAR_XPOS] = Xo;
+    PAR[PM_PAR_YPOS] = Yo;
+
+    // supply the model-fitted parameters, or copy from the input
+    for (int i = 0; i < psf->params->n; i++) {
+        if (i == PM_PAR_SKY) continue;
+        if (i == PM_PAR_I0) continue;
+        if (i == PM_PAR_XPOS) continue;
+        if (i == PM_PAR_YPOS) continue;
+        pmTrend2D *trend = psf->params->data[i];
+        PAR[i] = pmTrend2DEval(trend, Xo, Yo);
+    }
+
+    // the 2D PSF model fits polarization terms (E0,E1,E2)
+    // convert to shape terms (SXX,SYY,SXY)
+    // XXX user-defined value for limit?
+    if (!pmPSF_FitToModel (PAR, 0.1)) {
+        psError(PM_ERR_PSF, false, "Failed to fit object at (r,c) = (%.1f,%.1f)", Xo, Yo);
+        return false;
+    }
+
+    // apply the model limits here: this truncates excessive extrapolation
+    // XXX do we need to do this still?  should we put in asserts to test?
+    for (int i = 0; i < psf->params->n; i++) {
+        // apply the limits to all components or just the psf-model parameters?
+        if (psf->params->data[i] == NULL)
+            continue;
+
+        bool status = true;
+        status &= PM_MODEL_LIMITS (PS_MINIMIZE_PARAM_MIN, i, PAR, NULL);
+        status &= PM_MODEL_LIMITS (PS_MINIMIZE_PARAM_MAX, i, PAR, NULL);
+        if (!status) {
+            psTrace ("psModules.objects", 5, "Hitting parameter limits at (r,c) = (%.1f, %.1f)", Xo, Yo);
+            model->flags |= PM_MODEL_STATUS_LIMITS;
+        }
+    }
+    return(true);
+}
+
+bool PM_MODEL_FIT_STATUS (pmModel *model)
+{
+
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[PM_PAR_SXX] / PAR[PM_PAR_SXX]);
+    dP += PS_SQR(dPAR[PM_PAR_SYY] / PAR[PM_PAR_SYY]);
+    dP = sqrt (dP);
+
+    status = true;
+//    status &= (dP < 0.5);
+    status &= (PAR[PM_PAR_I0] > 0);
+    status &= ((dPAR[PM_PAR_I0]/PAR[PM_PAR_I0]) < 0.5);
+
+    fprintf (stderr, "SERSIC status pars: dP: %f, I0: %f, S/N: %f\n",
+	     dP, PAR[PM_PAR_I0], (dPAR[PM_PAR_I0]/PAR[PM_PAR_I0]));
+
+    return status;
+}
+
+# undef PM_MODEL_FUNC
+# undef PM_MODEL_FLUX
+# undef PM_MODEL_GUESS
+# undef PM_MODEL_LIMITS
+# undef PM_MODEL_RADIUS
+# undef PM_MODEL_FROM_PSF
+# undef PM_MODEL_PARAMS_FROM_PSF
+# undef PM_MODEL_FIT_STATUS
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_SGAUSS.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_SGAUSS.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_SGAUSS.c	(revision 22157)
@@ -0,0 +1,386 @@
+/******************************************************************************
+ * this file defines the SGAUSS source shape model (XXX need a better name!).  Note that these
+ * model functions are loaded by pmModelGroup.c using 'include', and thus need no 'include'
+ * statements of their own.  The models use a psVector to represent the set of parameters, with
+ * the sequence used to specify the meaning of the parameter.  The meaning of the parameters
+ * may thus vary depending on the specifics of the model.  All models which are used a PSF
+ * representations share a few parameters, for which # define names are listed in pmModel.h:
+
+   power-law with fitted slope and outer tidal radius
+   1 / (1 + z^N + kz^4)
+
+   * PM_PAR_SKY 0   - local sky : note that this is unused and may be dropped in the future
+   * PM_PAR_I0 1    - central intensity
+   * PM_PAR_XPOS 2  - X center of object
+   * PM_PAR_YPOS 3  - Y center of object
+   * PM_PAR_SXX 4   - X^2 term of elliptical contour (sqrt(2) / SigmaX)
+   * PM_PAR_SYY 5   - Y^2 term of elliptical contour (sqrt(2) / SigmaY)
+   * PM_PAR_SXY 6   - X*Y term of elliptical contour
+   * PM_PAR_7   7   - slope of power-law component (N)
+   * PM_PAR_8   8   - amplitude of the tidal cutoff (k)
+   *****************************************************************************/
+
+/***
+    XXXX the model in this file needs to be tested more carefully.
+    the code for guessing the power-law slope based on the radial profile
+    is either too slow or does not work well.
+    fix up the code to follow conventions in the other model function files.
+***/
+
+XXX broken code
+
+# define PM_MODEL_FUNC       pmModelFunc_SGAUSS
+# define PM_MODEL_FLUX       pmModelFlux_SGAUSS
+# define PM_MODEL_GUESS      pmModelGuess_SGAUSS
+# define PM_MODEL_LIMITS     pmModelLimits_SGAUSS
+# define PM_MODEL_RADIUS     pmModelRadius_SGAUSS
+# define PM_MODEL_FROM_PSF   pmModelFromPSF_SGAUSS
+# define PM_MODEL_FIT_STATUS pmModelFitStatus_SGAUSS
+
+psF64 psImageEllipseContour (psEllipseAxes axes, double xc, double yc, psImage *image);
+
+psF32 PM_MODEL_FUNC (psVector *deriv,
+                     const psVector *params,
+                     const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[2];
+    psF32 Y  = x->data.F32[1] - PAR[3];
+    psF32 px = PAR[4]*X;
+    psF32 py = PAR[5]*Y;
+    psF32 z  = PS_MAX((0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[6]*X*Y), 1e-8);
+    // note that if z -> 0, dPAR[7] -> -inf
+    // also z^(PAR[7]-1) -> Inf
+
+    psF32 pr = z*PAR[8];
+    psF32 pr3 = pr*pr*pr;
+    psF32 p  = pow(z, PAR[7] - 1.0);
+    psF32 r  = 1.0 / (1 + z*p + pr*pr3);
+    psF32 f  = PAR[1]*r + PAR[0];
+
+    if (deriv != NULL) {
+        // note difference from a pure gaussian: q = params->data.F32[1]*r
+        psF32 t = PAR[1]*r*r;
+        psF32 q = t*(PAR[7]*p + 4*PAR[8]*pr3);
+
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
+        deriv->data.F32[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -2*t*log(z)*z*p;
+        deriv->data.F32[8] = -2*t*4*z*pr3;
+    }
+    return(f);
+}
+
+bool PM_MODEL_LIMITS  (psVector **beta_lim, psVector **params_min, psVector **params_max)
+{
+
+    *beta_lim   = psVectorAlloc (9, PS_TYPE_F32);
+    *params_min = psVectorAlloc (9, PS_TYPE_F32);
+    *params_max = psVectorAlloc (9, PS_TYPE_F32);
+
+    beta_lim[0][0].data.F32[0] = 1000;
+    beta_lim[0][0].data.F32[1] = 10000;
+    beta_lim[0][0].data.F32[2] = 5;
+    beta_lim[0][0].data.F32[3] = 5;
+    beta_lim[0][0].data.F32[4] = 0.5;
+    beta_lim[0][0].data.F32[5] = 0.5;
+    beta_lim[0][0].data.F32[6] = 0.5;
+    beta_lim[0][0].data.F32[7] = 0.5;
+    beta_lim[0][0].data.F32[8] = 0.05;
+
+    params_min[0][0].data.F32[0] = -1000;
+    params_min[0][0].data.F32[1] = 0;
+    params_min[0][0].data.F32[2] = -100;
+    params_min[0][0].data.F32[3] = -100;
+    params_min[0][0].data.F32[4] = 0.01;
+    params_min[0][0].data.F32[5] = 0.01;
+    params_min[0][0].data.F32[6] = -5.0;
+    params_min[0][0].data.F32[7] = 0.5;
+    params_min[0][0].data.F32[8] = 0.001;
+
+    params_max[0][0].data.F32[0] = 1e5;
+    params_max[0][0].data.F32[1] = 1e6;
+    params_max[0][0].data.F32[2] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[3] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[4] = 2.0;
+    params_max[0][0].data.F32[5] = 2.0;
+    params_max[0][0].data.F32[6] = +3.0;
+    params_max[0][0].data.F32[7] = 5.0;
+    params_max[0][0].data.F32[8] = 0.5;
+
+    return (TRUE);
+}
+
+bool PM_MODEL_GUESS  (pmModel *model, pmSource *source)
+{
+
+    pmMoments *sMoments = source->moments;
+    pmPeak    *peak     = source->peak;
+    psF32     *params   = model->params->data.F32;
+
+    psEllipseAxes axes;
+    psEllipseShape shape;
+    psEllipseMoments moments;
+
+    moments.x2 = PS_SQR(sMoments->Sx);
+    moments.y2 = PS_SQR(sMoments->Sy);
+    moments.xy = sMoments->Sxy;
+
+    // solve the math to go from Moments To Shape
+    axes = psEllipseMomentsToAxes(moments);
+    shape = psEllipseAxesToShape(axes);
+
+    params[0] = sMoments->Sky;
+    params[1] = sMoments->Peak - sMoments->Sky;
+    params[2] = peak->x;
+    params[3] = peak->y;
+    params[4] = 1.0 / shape.sx;
+    params[5] = 1.0 / shape.sy;
+    params[6] = shape.sxy;
+
+    # if (0)
+
+        f1 = psImageEllipseContour (axes, peak->x, peak->y, source->pixels);
+    axes.major *= 2.0;
+    axes.minor *= 2.0;
+    f2 = psImageEllipseContour (axes, peak->x, peak->y, source->pixels);
+
+    if (f1 > f2) {
+        params[7] = PS_MIN (3.0, PS_MAX (0.5, log(2.0*(f1/f2) - 1.0) / log(2.0)));
+    } else {
+        params[7] = 0.5;
+    }
+    # endif
+
+    params[7] = 1.8;
+    params[8] = 0.1;
+
+
+    return(true);
+}
+
+psF64 PM_MODEL_FLUX (const psVector *params)
+{
+    float f, norm, z;
+
+    psF32 *PAR = params->data.F32;
+
+    psF64 A1   = PS_SQR(PAR[4]);
+    psF64 A2   = PS_SQR(PAR[5]);
+    psF64 A3   = PS_SQR(PAR[6]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+    for (z = 0.005; z < 50; z += 0.01) {
+        psF32 pr = PAR[8]*z;
+        f = 1.0 / (1 + pow(z, PAR[7]) + PS_SQR(PS_SQR(pr)));
+        norm += f;
+    }
+    norm *= 0.01;
+
+    psF64 Flux = PAR[1] * Area * norm;
+
+    return(Flux);
+}
+
+// XXX need to define the radius along the major axis
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 PM_MODEL_RADIUS   (const psVector *params, psF64 flux)
+{
+    psF64 r, z = 0.0, pr, f;
+    psF32 *PAR = params->data.F32;
+
+    psEllipseAxes axes;
+    psEllipseShape shape;
+
+    if (flux <= 0)
+        return (1.0);
+    if (PAR[1] <= 0)
+        return (1.0);
+    if (flux >= PAR[1])
+        return (1.0);
+
+    // convert Sx,Sy,Sxy to major/minor axes
+    shape.sx = 1.0 / PAR[4];
+    shape.sy = 1.0 / PAR[5];
+    shape.sxy = PAR[6];
+
+    axes = psEllipseShapeToAxes (shape);
+    psF64 dr = 1.0 / axes.major;
+    psF64 limit = flux / PAR[1];
+
+    // XXX : we can do this faster with an intelligent starting choice
+    for (r = 0.0; r < 20.0; r += dr) {
+        z = PS_SQR(r);
+        pr = PAR[8]*z;
+        f = 1.0 / (1 + pow(z, PAR[7]) + PS_SQR(PS_SQR(pr)));
+        if (f < limit)
+            break;
+    }
+    psF64 radius = 2.0 * axes.major * sqrt (z);
+    if (isnan(radius)) {
+        fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool PM_MODEL_FROM_PSF  (pmModel *modelPSF, pmModel *modelFLT, const pmPSF *psf)
+{
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 9; i++) {
+        psPolynomial2D *poly = psf->params->data[i-4];
+        // XXX: Verify this (from EAM change)
+        //out[i] = Polynomial2DEval_EAM(poly, out[2], out[3]);
+        out[i] = psPolynomial2DEval(poly, out[2], out[3]);
+    }
+    return(true);
+}
+
+bool PM_MODEL_FIT_STATUS  (pmModel *model)
+{
+
+    psF32 dP;
+    bool  status;
+    psEllipseAxes axes;
+    psEllipseShape shape;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    shape.sx = 1.0 / PAR[4];
+    shape.sy = 1.0 / PAR[5];
+    shape.sxy = PAR[6];
+
+    axes = psEllipseShapeToAxes (shape);
+
+    dP = 0;
+    dP += PS_SQR(dPAR[4] / PAR[4]);
+    dP += PS_SQR(dPAR[5] / PAR[5]);
+    dP += PS_SQR(dPAR[7] / PAR[7]);
+    dP = sqrt (dP);
+
+    status = true;
+    status &= (dP < 0.5);
+    status &= (PAR[1] > 0);
+    status &= ((dPAR[1]/PAR[1]) < 0.5);
+    status &= (fabs(PAR[8]) < 0.5);
+    status &= (dPAR[8] < 0.1);
+    status &= (axes.major > 1.41);
+    status &= (axes.minor > 1.41);
+    status &= ((axes.major / axes.minor) < 5.0);
+    status &= (PAR[7] > 0.5);
+
+    if (status)
+        return true;
+    return false;
+}
+
+// measure the flux for the elliptical contour
+psF64 psImageEllipseContour (psEllipseAxes axes, double xc, double yc, psImage *image)
+{
+
+    double t, dt, ct, st, xo, yo, value;
+    int N, Nt, x, y;
+
+    // choose dt to uniformly divide contour, with ~1 pix spacing at most
+    dt = asin (1 / axes.minor);
+    Nt = (int)(2*M_PI / dt) + 1;
+    dt = 2*M_PI / Nt;
+
+    ct = cos(axes.theta);
+    st = sin(axes.theta);
+    xo = xc - image->col0;
+    yo = yc - image->row0;
+
+    psVector *contour = psVectorAlloc (Nt, PS_TYPE_F32);
+    contour->n = contour->nalloc;
+    for (t = 0, N = 0; (t < 2*M_PI) && (N < Nt); t += dt) {
+        x = ct*axes.major*cos(t) + st*axes.minor*sin(t) + xo;
+        y = ct*axes.minor*sin(t) + st*axes.major*cos(t) + yo;
+        value = p_psImageGetElementF64(image, x, y);
+        if (isfinite(value)) {
+            contour->data.F32[N] = value;
+            N++;
+        }
+    }
+    contour->n = N;
+    // accept every pixel: double counting is not so problematic here...
+
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN);
+    psVectorStats (stats, contour, NULL, NULL, 0);
+    value = stats->sampleMedian;
+
+    psFree (stats);
+    psFree (contour);
+
+    return (value);
+}
+
+// XXX EAM : test version using flux contours to guess slope
+bool PM_MODEL_GUESS_HARD (pmModel *model, pmSource *source)
+{
+
+    pmMoments *sMoments = source->moments;
+    pmPeak    *peak     = source->peak;
+    psF32     *params   = model->params->data.F32;
+    float f1, f2;
+
+    psEllipseAxes axes;
+    psEllipseShape shape;
+    psEllipseMoments moments;
+
+    moments.x2 = PS_SQR(sMoments->Sx);
+    moments.y2 = PS_SQR(sMoments->Sy);
+    moments.xy = sMoments->Sxy;
+
+    // solve the math to go from Moments To Shape
+    axes = psEllipseMomentsToAxes(moments);
+    shape = psEllipseAxesToShape(axes);
+
+    params[0] = sMoments->Sky;
+    params[1] = sMoments->Peak - sMoments->Sky;
+    params[2] = peak->x;
+    params[3] = peak->y;
+    params[4] = 1.0 / shape.sx;
+    params[5] = 1.0 / shape.sy;
+    params[6] = shape.sxy;
+
+    f1 = psImageEllipseContour (axes, peak->x, peak->y, source->pixels);
+    axes.major *= 2.0;
+    axes.minor *= 2.0;
+    f2 = psImageEllipseContour (axes, peak->x, peak->y, source->pixels);
+
+    if (f1 > f2) {
+        params[7] = PS_MIN (3.0, PS_MAX (0.5, log(2.0*(f1/f2) - 1.0) / log(2.0)));
+    } else {
+        params[7] = 0.5;
+    }
+    params[8] = 0.1;
+
+    return(true);
+}
+
+# undef PM_MODEL_FUNC
+# undef PM_MODEL_FLUX
+# undef PM_MODEL_GUESS
+# undef PM_MODEL_LIMITS
+# undef PM_MODEL_RADIUS
+# undef PM_MODEL_FROM_PSF
+# undef PM_MODEL_FIT_STATUS
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_TGAUSS.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_TGAUSS.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_TGAUSS.c	(revision 22157)
@@ -0,0 +1,202 @@
+/******************************************************************************
+ * this file defines the TGAUSS source shape model (XXX need a better name!).  Note that these
+ * model functions are loaded by pmModelGroup.c using 'include', and thus need no 'include'
+ * statements of their own.  The models use a psVector to represent the set of parameters, with
+ * the sequence used to specify the meaning of the parameter.  The meaning of the parameters
+ * may thus vary depending on the specifics of the model.  All models which are used a PSF
+ * representations share a few parameters, for which # define names are listed in pmModel.h:
+
+   power-law with fixed slope and fitted amplitude
+   1 / (1 + z + kz^2.2)
+
+   * PM_PAR_SKY 0   - local sky : note that this is unused and may be dropped in the future
+   * PM_PAR_I0 1    - central intensity
+   * PM_PAR_XPOS 2  - X center of object
+   * PM_PAR_YPOS 3  - Y center of object
+   * PM_PAR_SXX 4   - X^2 term of elliptical contour (sqrt(2) / SigmaX)
+   * PM_PAR_SYY 5   - Y^2 term of elliptical contour (sqrt(2) / SigmaY)
+   * PM_PAR_SXY 6   - X*Y term of elliptical contour
+   * PM_PAR_7   7   - amplitude of high-order component (k)
+   *****************************************************************************/
+
+/***
+    XXXX the model in this file needs to be tested more carefully.
+    fix up the code to follow conventions in the other model function files.
+***/
+
+XXX broken code
+
+# define PM_MODEL_FUNC       pmModelFunc_TGAUSS
+# define PM_MODEL_FLUX       pmModelFlux_TGAUSS
+# define PM_MODEL_GUESS      pmModelGuess_TGAUSS
+# define PM_MODEL_LIMITS     pmModelLimits_TGAUSS
+# define PM_MODEL_RADIUS     pmModelRadius_TGAUSS
+# define PM_MODEL_FROM_PSF   pmModelFromPSF_TGAUSS
+# define PM_MODEL_FIT_STATUS pmModelFitStatus_TGAUSS
+
+psF64 PS_MODEL_FUNC (psVector *deriv,
+                     const psVector *params,
+                     const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[2];
+    psF32 Y  = x->data.F32[1] - PAR[3];
+    psF32 px = PAR[4]*X;
+    psF32 py = PAR[5]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[6]*X*Y;
+
+    psF32 p  = pow(z, 1.2);
+    psF32 r  = 1.0 / (1 + z + PAR[7]*z*p);
+    psF32 f  = PAR[1]*r + PAR[0];
+
+    if (deriv != NULL) {
+        // note difference from a pure gaussian: q = params->data.F32[1]*r
+        psF32 t = PAR[1]*r*r;
+        psF32 q = t*(1 + PAR[7]*2.2*p);
+
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
+        deriv->data.F32[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -t*z*p;
+    }
+    return(f);
+}
+
+bool PM_MODEL_LIMITS  (psVector **beta_lim, psVector **params_min, psVector **params_max)
+{
+
+    *beta_lim   = psVectorAlloc (8, PS_TYPE_F32);
+    *params_min = psVectorAlloc (8, PS_TYPE_F32);
+    *params_max = psVectorAlloc (8, PS_TYPE_F32);
+
+    beta_lim[0][0].data.F32[PM_PAR_SKY] = 1000;
+    beta_lim[0][0].data.F32[PM_PAR_I0] = 3e6;
+    beta_lim[0][0].data.F32[PM_PAR_XPOS] = 5;
+    beta_lim[0][0].data.F32[PM_PAR_YPOS] = 5;
+    beta_lim[0][0].data.F32[PM_PAR_SXX] = 0.5;
+    beta_lim[0][0].data.F32[PM_PAR_SYY] = 0.5;
+    beta_lim[0][0].data.F32[PM_PAR_SXY] = 0.5;
+    beta_lim[0][0].data.F32[PM_PAR_7] = 0.5;
+
+    params_min[0][0].data.F32[PM_PAR_SKY] = -1000;
+    params_min[0][0].data.F32[PM_PAR_I0] = 0;
+    params_min[0][0].data.F32[PM_PAR_XPOS] = -100;
+    params_min[0][0].data.F32[PM_PAR_YPOS] = -100;
+    params_min[0][0].data.F32[PM_PAR_SXX] = 0.5;
+    params_min[0][0].data.F32[PM_PAR_SYY] = 0.5;
+    params_min[0][0].data.F32[PM_PAR_SXY] = -5.0;
+    params_min[0][0].data.F32[PM_PAR_7] = 0.1;
+
+    params_max[0][0].data.F32[PM_PAR_SKY] = 1e5;
+    params_max[0][0].data.F32[PM_PAR_I0] = 1e8;
+    params_max[0][0].data.F32[PM_PAR_XPOS] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[PM_PAR_YPOS] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[PM_PAR_SXX] = 100.0;
+    params_max[0][0].data.F32[PM_PAR_SYY] = 100.0;
+    params_max[0][0].data.F32[PM_PAR_SXY] = +5.0;
+    params_max[0][0].data.F32[PM_PAR_7] = 10.0;
+
+    return (TRUE);
+}
+
+bool PS_MODEL_GUESS  (psModel *model, psSource *source)
+{
+
+    psVector *params = model->params;
+
+    params->data.F32[0] = source->moments->Sky;
+    params->data.F32[1] = source->peak->counts - source->moments->Sky;
+    params->data.F32[2] = source->moments->x;
+    params->data.F32[3] = source->moments->y;
+    params->data.F32[4] = 1.0/source->moments->Sx;
+    params->data.F32[5] = 1.0/source->moments->Sy;
+    // params->data.F32[6] = source->moments->Sxy;
+    params->data.F32[6] = 0.0;
+    params->data.F32[7] = 5.0;
+    return(true);
+}
+
+psF64 PS_MODEL_FLUX (const psVector *params)
+{
+    psF64 A1   = 1 / PS_SQR(params->data.F32[4]);
+    psF64 A2   = 1 / PS_SQR(params->data.F32[5]);
+    psF64 A3   = params->data.F32[6];
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - PS_SQR(A3));
+    // Area is equivalent to 2 pi sigma^2
+
+    psF64 Flux = params->data.F32[1] * Area;
+
+    return(Flux);
+}
+
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 PS_MODEL_RADIUS   (const psVector *params, psF64 flux)
+{
+    if (flux <= 0)
+        return (1.0);
+    if (params->data.F32[1] <= 0)
+        return (1.0);
+    if (flux >= params->data.F32[1])
+        return (1.0);
+
+    psF64 sigma  = sqrt(2.0) * hypot (1.0 / params->data.F32[4], 1.0 / params->data.F32[5]);
+    psF64 radius = sigma * sqrt (2.0 * log(params->data.F32[1] / flux));
+    if (isnan(radius)) {
+        fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool PS_MODEL_FROM_PSF  (psModel *modelPSF, psModel *modelFLT, const pmPSF *psf)
+{
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 8; i++) {
+        psPolynomial2D *poly = psf->params->data[i-4];
+        out[i] = Polynomial2DEval (poly, out[2], out[3]);
+    }
+    return(true);
+}
+
+bool PM_MODEL_FIT_STATUS (pmModel *model)
+{
+
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[PM_PAR_SXX] / PAR[PM_PAR_SXX]);
+    dP += PS_SQR(dPAR[PM_PAR_SYY] / PAR[PM_PAR_SYY]);
+    dP = sqrt (dP);
+
+    status = true;
+    status &= (dP < 0.5);
+    status &= (PAR[PM_PAR_I0] > 0);
+    status &= ((dPAR[PM_PAR_I0]/PAR[PM_PAR_I0]) < 0.5);
+
+    return status;
+}
+
+# undef PM_MODEL_FUNC
+# undef PM_MODEL_FLUX
+# undef PM_MODEL_GUESS
+# undef PM_MODEL_LIMITS
+# undef PM_MODEL_RADIUS
+# undef PM_MODEL_FROM_PSF
+# undef PM_MODEL_FIT_STATUS
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_WAUSS.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_WAUSS.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_WAUSS.c	(revision 22157)
@@ -0,0 +1,198 @@
+/******************************************************************************
+ * this file defines the WAUSS source shape model (XXX need a better name!).  Note that these
+ * model functions are loaded by pmModelGroup.c using 'include', and thus need no 'include'
+ * statements of their own.  The models use a psVector to represent the set of parameters, with
+ * the sequence used to specify the meaning of the parameter.  The meaning of the parameters
+ * may thus vary depending on the specifics of the model.  All models which are used a PSF
+ * representations share a few parameters, for which # define names are listed in pmModel.h:
+
+   power-law with fitted linear term
+   1 / (1 + Az + Bz^2 + z^3/6)
+
+   * PM_PAR_SKY 0   - local sky : note that this is unused and may be dropped in the future
+   * PM_PAR_I0 1    - central intensity
+   * PM_PAR_XPOS 2  - X center of object
+   * PM_PAR_YPOS 3  - Y center of object
+   * PM_PAR_SXX 4   - X^2 term of elliptical contour (SigmaX / sqrt(2))
+   * PM_PAR_SYY 5   - Y^2 term of elliptical contour (SigmaY / sqrt(2))
+   * PM_PAR_SXY 6   - X*Y term of elliptical contour
+   * PM_PAR_7   7   - amplitude of the linear component (A)
+   * PM_PAR_8   8   - amplitude of the quadratic component (B)
+   *****************************************************************************/
+
+/***
+    XXXX the model in this file needs to be tested more carefully.
+    fix up the code to follow conventions in the other model function files.
+***/
+
+XXX broken code
+
+# define PM_MODEL_FUNC       pmModelFunc_WAUSS
+# define PM_MODEL_FLUX       pmModelFlux_WAUSS
+# define PM_MODEL_GUESS      pmModelGuess_WAUSS
+# define PM_MODEL_LIMITS     pmModelLimits_WAUSS
+# define PM_MODEL_RADIUS     pmModelRadius_WAUSS
+# define PM_MODEL_FROM_PSF   pmModelFromPSF_WAUSS
+# define PM_MODEL_FIT_STATUS pmModelFitStatus_WAUSS
+
+psF64 PS_MODEL_FUNC (psVector *deriv,
+                     const psVector *params,
+                     const psVector *x)
+{
+    psF32 X = x->data.F32[0] - params->data.F32[2];
+    psF32 Y = x->data.F32[1] - params->data.F32[2];
+    psF32 px = params->data.F32[4]*X;
+    psF32 py = params->data.F32[5]*Y;
+    psF32 z = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + params->data.F32[6]*X*Y;
+    psF32 t = 0.5*z*z*(1.0 + params->data.F32[8]*z/3.0);
+    psF32 r = 1.0 / (1.0 + z + params->data.F32[7]*t); /* exp (-Z) */
+    psF32 f = params->data.F32[1]*r + params->data.F32[0];
+
+    if (deriv != NULL) {
+        // note difference from gaussian: q = params->data.F32[1]*r
+        psF32 q = params->data.F32[1]*r*r*(1.0 + params->data.F32[7]*z*(1.0 + params->data.F32[8]*z/2.0));
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*params->data.F32[4] + params->data.F32[6]*Y);
+        deriv->data.F32[3] = q*(2.0*py*params->data.F32[5] + params->data.F32[6]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -100.0*params->data.F32[1]*r*r*t;
+        deriv->data.F32[8] = -100.0*params->data.F32[1]*r*r*params->data.F32[7]*(z*z*z)/6.0;
+        // The values of 100 dampen the swing of params->data.F32[7,8] */
+    }
+    return(f);
+}
+
+bool PM_MODEL_LIMITS  (psVector **beta_lim, psVector **params_min, psVector **params_max)
+{
+
+    *beta_lim   = psVectorAlloc (8, PS_TYPE_F32);
+    *params_min = psVectorAlloc (8, PS_TYPE_F32);
+    *params_max = psVectorAlloc (8, PS_TYPE_F32);
+
+    beta_lim[0][0].data.F32[PM_PAR_SKY] = 1000;
+    beta_lim[0][0].data.F32[PM_PAR_I0] = 3e6;
+    beta_lim[0][0].data.F32[PM_PAR_XPOS] = 5;
+    beta_lim[0][0].data.F32[PM_PAR_YPOS] = 5;
+    beta_lim[0][0].data.F32[PM_PAR_SXX] = 0.5;
+    beta_lim[0][0].data.F32[PM_PAR_SYY] = 0.5;
+    beta_lim[0][0].data.F32[PM_PAR_SXY] = 0.5;
+    beta_lim[0][0].data.F32[PM_PAR_7] = 0.5;
+
+    params_min[0][0].data.F32[PM_PAR_SKY] = -1000;
+    params_min[0][0].data.F32[PM_PAR_I0] = 0;
+    params_min[0][0].data.F32[PM_PAR_XPOS] = -100;
+    params_min[0][0].data.F32[PM_PAR_YPOS] = -100;
+    params_min[0][0].data.F32[PM_PAR_SXX] = 0.5;
+    params_min[0][0].data.F32[PM_PAR_SYY] = 0.5;
+    params_min[0][0].data.F32[PM_PAR_SXY] = -5.0;
+    params_min[0][0].data.F32[PM_PAR_7] = 0.1;
+
+    params_max[0][0].data.F32[PM_PAR_SKY] = 1e5;
+    params_max[0][0].data.F32[PM_PAR_I0] = 1e8;
+    params_max[0][0].data.F32[PM_PAR_XPOS] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[PM_PAR_YPOS] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[PM_PAR_SXX] = 100.0;
+    params_max[0][0].data.F32[PM_PAR_SYY] = 100.0;
+    params_max[0][0].data.F32[PM_PAR_SXY] = +5.0;
+    params_max[0][0].data.F32[PM_PAR_7] = 10.0;
+
+    return (TRUE);
+}
+
+bool PS_MODEL_GUESS  (psModel *model, psSource *source)
+{
+
+    psVector *params = model->params;
+
+    params->data.F32[0] = source->moments->Sky;
+    params->data.F32[1] = source->peak->counts - source->moments->Sky;
+    params->data.F32[2] = source->moments->x;
+    params->data.F32[3] = source->moments->y;
+    params->data.F32[4] = sqrt(2.0) / source->moments->Sx;
+    params->data.F32[5] = sqrt(2.0) / source->moments->Sy;
+    params->data.F32[6] = source->moments->Sxy;
+    // XXX: What are these?
+    // params->data.F32[7] = B2;
+    // params->data.F32[8] = B3;
+    return(true);
+}
+
+// this is probably wrong since it uses the gauss integral 2 pi sigma^2
+psF64 PS_MODEL_FLUX (const psVector *params)
+{
+    psF64 A1   = 1 / PS_SQR(params->data.F32[4]);
+    psF64 A2   = 1 / PS_SQR(params->data.F32[5]);
+    psF64 A3   = params->data.F32[6];
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - PS_SQR(A3));
+    // Area is equivalent to 2 pi sigma^2
+
+    psF64 Flux = params->data.F32[1] * Area;
+
+    return(Flux);
+}
+
+// return the radius which yields the requested flux
+psF64 PS_MODEL_RADIUS   (const psVector *params, psF64 flux)
+{
+    if (flux <= 0)
+        return (1.0);
+    if (params->data.F32[1] <= 0)
+        return (1.0);
+    if (flux >= params->data.F32[1] <= 0)
+        return (1.0);
+
+    psF64 sigma  = sqrt(2.0) * hypot (1.0 / params->data.F32[4], 1.0 / params->data.F32[5]);
+    psF64 radius = sigma * sqrt (2.0 * log(params->data.F32[1] / flux));
+    return (radius);
+}
+
+bool PS_MODEL_FROM_PSF  (psModel *modelPSF, psModel *modelFLT, const pmPSF *psf)
+{
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 9; i++) {
+        psPolynomial2D *poly = psf->params->data[i-4];
+        out[i] = Polynomial2DEval (poly, out[2], out[3]);
+    }
+    return(true);
+}
+
+bool PM_MODEL_FIT_STATUS (pmModel *model)
+{
+
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[PM_PAR_SXX] / PAR[PM_PAR_SXX]);
+    dP += PS_SQR(dPAR[PM_PAR_SYY] / PAR[PM_PAR_SYY]);
+    dP = sqrt (dP);
+
+    status = true;
+    status &= (dP < 0.5);
+    status &= (PAR[PM_PAR_I0] > 0);
+    status &= ((dPAR[PM_PAR_I0]/PAR[PM_PAR_I0]) < 0.5);
+
+    return status;
+}
+
+# undef PM_MODEL_FUNC
+# undef PM_MODEL_FLUX
+# undef PM_MODEL_GUESS
+# undef PM_MODEL_LIMITS
+# undef PM_MODEL_RADIUS
+# undef PM_MODEL_FROM_PSF
+# undef PM_MODEL_FIT_STATUS
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_ZGAUSS.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_ZGAUSS.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/models/pmModel_ZGAUSS.c	(revision 22157)
@@ -0,0 +1,251 @@
+/******************************************************************************
+ * this file defines the ZGAUSS source shape model (XXX need a better name!).  Note that these
+ * model functions are loaded by pmModelGroup.c using 'include', and thus need no 'include'
+ * statements of their own.  The models use a psVector to represent the set of parameters, with
+ * the sequence used to specify the meaning of the parameter.  The meaning of the parameters
+ * may thus vary depending on the specifics of the model.  All models which are used a PSF
+ * representations share a few parameters, for which # define names are listed in pmModel.h:
+
+   power-law with fitted slope and tidal cutoff
+   1 / (1 + z^N + (Az)^4)
+
+   * PM_PAR_SKY 0   - local sky : note that this is unused and may be dropped in the future
+   * PM_PAR_I0 1    - central intensity
+   * PM_PAR_XPOS 2  - X center of object
+   * PM_PAR_YPOS 3  - Y center of object
+   * PM_PAR_SXX 4   - X^2 term of elliptical contour (sqrt(2) / SigmaX)
+   * PM_PAR_SYY 5   - Y^2 term of elliptical contour (sqrt(2) / SigmaY)
+   * PM_PAR_SXY 6   - X*Y term of elliptical contour
+   * PM_PAR_7   7   - slope of power-law component (N)
+   *****************************************************************************/
+
+/***
+    XXXX the model in this file needs to be tested more carefully.
+    fix up the code to follow conventions in the other model function files.
+***/
+
+XXX broken code
+
+# define PM_MODEL_FUNC       pmModelFunc_ZGAUSS
+# define PM_MODEL_FLUX       pmModelFlux_ZGAUSS
+# define PM_MODEL_GUESS      pmModelGuess_ZGAUSS
+# define PM_MODEL_LIMITS     pmModelLimits_ZGAUSS
+# define PM_MODEL_RADIUS     pmModelRadius_ZGAUSS
+# define PM_MODEL_FROM_PSF   pmModelFromPSF_ZGAUSS
+# define PM_MODEL_FIT_STATUS pmModelFitStatus_ZGAUSS
+
+# define PAR8 0.1
+
+psF64 PS_MODEL_FUNC (psVector *deriv,
+                     const psVector *params,
+                     const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[2];
+    psF32 Y  = x->data.F32[1] - PAR[3];
+    psF32 px = PAR[4]*X;
+    psF32 py = PAR[5]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[6]*X*Y;
+
+    psF32 pr = PAR8*z;
+    psF32 p  = pow(z, PAR[7] - 1.0);
+    psF32 r  = 1.0 / (1 + z*p + SQ(SQ(pr)));
+    psF32 f  = PAR[1]*r + PAR[0];
+
+    if (deriv != NULL) {
+        // note difference from a pure gaussian: q = params->data.F32[1]*r
+        psF32 t = PAR[1]*r*r;
+        psF32 q = t*(PAR[7]*p + 4*PAR8*pr*pr*pr);
+
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
+        deriv->data.F32[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
+        deriv->data.F32[4] = -q*px*X;
+        deriv->data.F32[5] = -q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -t*log(z)*z*p;
+    }
+    return(f);
+}
+
+bool PM_MODEL_LIMITS  (psVector **beta_lim, psVector **params_min, psVector **params_max)
+{
+
+    *beta_lim   = psVectorAlloc (8, PS_TYPE_F32);
+    *params_min = psVectorAlloc (8, PS_TYPE_F32);
+    *params_max = psVectorAlloc (8, PS_TYPE_F32);
+
+    beta_lim[0][0].data.F32[PM_PAR_SKY] = 1000;
+    beta_lim[0][0].data.F32[PM_PAR_I0] = 3e6;
+    beta_lim[0][0].data.F32[PM_PAR_XPOS] = 5;
+    beta_lim[0][0].data.F32[PM_PAR_YPOS] = 5;
+    beta_lim[0][0].data.F32[PM_PAR_SXX] = 0.5;
+    beta_lim[0][0].data.F32[PM_PAR_SYY] = 0.5;
+    beta_lim[0][0].data.F32[PM_PAR_SXY] = 0.5;
+    beta_lim[0][0].data.F32[PM_PAR_7] = 0.5;
+
+    params_min[0][0].data.F32[PM_PAR_SKY] = -1000;
+    params_min[0][0].data.F32[PM_PAR_I0] = 0;
+    params_min[0][0].data.F32[PM_PAR_XPOS] = -100;
+    params_min[0][0].data.F32[PM_PAR_YPOS] = -100;
+    params_min[0][0].data.F32[PM_PAR_SXX] = 0.5;
+    params_min[0][0].data.F32[PM_PAR_SYY] = 0.5;
+    params_min[0][0].data.F32[PM_PAR_SXY] = -5.0;
+    params_min[0][0].data.F32[PM_PAR_7] = 0.1;
+
+    params_max[0][0].data.F32[PM_PAR_SKY] = 1e5;
+    params_max[0][0].data.F32[PM_PAR_I0] = 1e8;
+    params_max[0][0].data.F32[PM_PAR_XPOS] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[PM_PAR_YPOS] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[PM_PAR_SXX] = 100.0;
+    params_max[0][0].data.F32[PM_PAR_SYY] = 100.0;
+    params_max[0][0].data.F32[PM_PAR_SXY] = +5.0;
+    params_max[0][0].data.F32[PM_PAR_7] = 10.0;
+
+    return (TRUE);
+}
+
+bool PS_MODEL_GUESS  (psModel *model, psSource *source)
+{
+
+    psVector *params = model->params;
+
+    psEllipseAxes axes;
+    psEllipseShape shape;
+    psEllipseMoments moments;
+
+    moments.x2 = PS_SQR(source->moments->Sx);
+    moments.y2 = PS_SQR(source->moments->Sy);
+    moments.xy = source->moments->Sxy;
+
+    axes = psEllipseMomentsToAxes(moments);
+    shape = psEllipseAxesToShape(axes);
+
+    params->data.F32[0] = source->moments->Sky;
+    params->data.F32[1] = source->peak->counts - source->moments->Sky;
+    params->data.F32[2] = source->moments->x;
+    params->data.F32[3] = source->moments->y;
+    params->data.F32[4] = 1.0 / shape.sx;
+    params->data.F32[5] = 1.0 / shape.sy;
+    params->data.F32[6] = shape.sxy;
+    params->data.F32[7] = 1.9;
+    return(true);
+}
+
+psF64 PS_MODEL_FLUX (const psVector *params)
+{
+    float f, norm, z;
+
+    psF32 *PAR = params->data.F32;
+
+    psF64 A1   = PS_SQR(PAR[4]);
+    psF64 A2   = PS_SQR(PAR[5]);
+    psF64 A3   = PS_SQR(PAR[6]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+    psF32 pr = PAR8*z;
+    for (z = 0.005; z < 50; z += 0.01) {
+        f = 1.0 / (1 + pow(z, PAR[7]) + SQ(SQ(pr)));
+        norm += f;
+    }
+    norm *= 0.01;
+
+    psF64 Flux = PAR[1] * Area * norm;
+
+    return(Flux);
+}
+
+// XXX need to define the radius along the major axis
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 PS_MODEL_RADIUS   (const psVector *params, psF64 flux)
+{
+    psF64 r, z, pr, f;
+    psF32 *PAR = params->data.F32;
+
+    psEllipseAxes axes;
+    psEllipseShape shape;
+
+    if (flux <= 0)
+        return (1.0);
+    if (PAR[1] <= 0)
+        return (1.0);
+    if (flux >= PAR[1])
+        return (1.0);
+
+    // convert Sx,Sy,Sxy to major/minor axes
+    shape.sx = 1.0 / PAR[4];
+    shape.sy = 1.0 / PAR[5];
+    shape.sxy = PAR[6];
+
+    axes = psEllipseShapeToAxes (shape);
+    psF64 dr = 1.0 / axes.major;
+    psF64 limit = flux / PAR[1];
+
+    // XXX : we can do this faster with an intelligent starting choice
+    for (r = 0.0; r < 20.0; r += dr) {
+        z = SQ(r);
+        pr = PAR8*z;
+        f = 1.0 / (1 + pow(z, PAR[7]) + SQ(SQ(pr)));
+        if (f < limit)
+            break;
+    }
+    psF64 radius = 2.0 * axes.major * sqrt (z);
+    if (isnan(radius)) {
+        fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool PS_MODEL_FROM_PSF  (psModel *modelPSF, psModel *modelFLT, const pmPSF *psf)
+{
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 8; i++) {
+        psPolynomial2D *poly = psf->params->data[i-4];
+        out[i] = Polynomial2DEval (poly, out[2], out[3]);
+    }
+    return(true);
+}
+
+bool PM_MODEL_FIT_STATUS (pmModel *model)
+{
+
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[PM_PAR_SXX] / PAR[PM_PAR_SXX]);
+    dP += PS_SQR(dPAR[PM_PAR_SYY] / PAR[PM_PAR_SYY]);
+    dP = sqrt (dP);
+
+    status = true;
+    status &= (dP < 0.5);
+    status &= (PAR[PM_PAR_I0] > 0);
+    status &= ((dPAR[PM_PAR_I0]/PAR[PM_PAR_I0]) < 0.5);
+
+    return status;
+}
+
+# undef PM_MODEL_FUNC
+# undef PM_MODEL_FLUX
+# undef PM_MODEL_GUESS
+# undef PM_MODEL_LIMITS
+# undef PM_MODEL_RADIUS
+# undef PM_MODEL_FROM_PSF
+# undef PM_MODEL_FIT_STATUS
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmGrowthCurve.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmGrowthCurve.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmGrowthCurve.c	(revision 22157)
@@ -0,0 +1,72 @@
+/** @file  pmGrowthCurve.c
+ *
+ *  Measure the curve-of-growth for sources
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.13 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-10-03 00:42:40 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmResiduals.h"
+#include "pmGrowthCurve.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "psVectorBracket.h"
+
+static void pmGrowthCurveFree (pmGrowthCurve *growth)
+{
+    if (growth == NULL)
+        return;
+
+    psFree (growth->radius);
+    psFree (growth->apMag);
+    return;
+}
+
+bool psMemCheckGrowthCurve(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc)pmGrowthCurveFree );
+}
+
+pmGrowthCurve *pmGrowthCurveAlloc (psF32 minRadius, psF32 maxRadius, psF32 refRadius)
+{
+
+    pmGrowthCurve *growth = psAlloc (sizeof(pmGrowthCurve));
+    psMemSetDeallocator(growth, (psFreeFunc) pmGrowthCurveFree);
+
+    // fractional pixel radii are not well defined; use integer steps
+    growth->radius = psVectorCreate (NULL, minRadius, maxRadius, 1.0, PS_TYPE_F32);
+    growth->apMag  = psVectorAlloc (growth->radius->n, PS_TYPE_F32);
+
+    // XXX may want to extend this to allow for a different refRadius;
+    growth->refRadius = refRadius;
+    growth->maxRadius = maxRadius;
+    growth->apLoss = 0.0;
+    growth->fitMag = 0.0;
+    return growth;
+}
+
+psF32 pmGrowthCurveCorrect(pmGrowthCurve *growth, psF32 radius)
+{
+    PS_ASSERT_PTR_NON_NULL(growth, NAN);
+    float apRad = psVectorInterpolate (growth->radius, growth->apMag, radius);
+    float apCor = growth->apRef - apRad;
+    return apCor;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmGrowthCurve.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmGrowthCurve.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmGrowthCurve.h	(revision 22157)
@@ -0,0 +1,35 @@
+/* @file  pmGrowthCurve.h
+ * @brief functions to manipulate the curve-of-growth data
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.8 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-11-10 01:09:20 $
+ * Copyright 2006 Institute for Astronomy, University of Hawaii
+ */
+
+# ifndef PM_GROWTH_CURVE_H
+# define PM_GROWTH_CURVE_H
+
+/// @addtogroup Objects Object Detection / Analysis Functions
+/// @{
+
+typedef struct
+{
+    psVector *radius;
+    psVector *apMag;
+    psF32 refRadius;
+    psF32 maxRadius;
+    psF32 fitMag;
+    psF32 apRef;   // apMag[refRadius]
+    psF32 apLoss;  // fitMag - apRef
+}
+pmGrowthCurve;
+
+bool psMemCheckGrowthCurve(psPtr ptr);
+
+pmGrowthCurve *pmGrowthCurveAlloc (psF32 minRadius, psF32 maxRadius, psF32 refRadius);
+psF32 pmGrowthCurveCorrect (pmGrowthCurve *growth, psF32 radius);
+
+/// @}
+# endif /* PM_GROWTH_CURVE_H */
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmModel.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmModel.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmModel.c	(revision 22157)
@@ -0,0 +1,412 @@
+/** @file  pmModel.c
+ *
+ *  Functions to define and manipulate object models
+ *
+ *  @author GLG, MHPCC
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.22 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-01-02 20:36:36 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmResiduals.h"
+#include "pmGrowthCurve.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmModelClass.h"
+
+static void modelFree(pmModel *tmp)
+{
+    psTrace("psModules.objects", 4, "---- %s() begin ----\n", __func__);
+    psFree(tmp->params);
+    psFree(tmp->dparams);
+    psTrace("psModules.objects", 4, "---- %s() end ----\n", __func__);
+}
+
+/******************************************************************************
+pmModelAlloc(): Allocate the pmModel structure, along with its parameters,
+and initialize the type member.  Initialize the params to 0.0.
+*****************************************************************************/
+pmModel *pmModelAlloc(pmModelType type)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+
+    pmModelClass *class = pmModelClassSelect (type);
+    if (class == NULL) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return(NULL);
+    }
+
+    pmModel *tmp = (pmModel *) psAlloc(sizeof(pmModel));
+    psMemSetDeallocator(tmp, (psFreeFunc) modelFree);
+
+    tmp->type = type;
+    tmp->chisq = 0.0;
+    tmp->chisqNorm = 0.0;
+    tmp->nDOF  = 0;
+    tmp->nIter = 0;
+    tmp->radiusFit = 0;
+    tmp->flags = PM_MODEL_STATUS_NONE;
+    tmp->residuals = NULL;              // XXX should the model own this memory?
+
+    psS32 Nparams = pmModelClassParameterCount(type);
+    assert (Nparams);
+
+    tmp->params  = psVectorAlloc(Nparams, PS_TYPE_F32);
+    tmp->dparams = psVectorAlloc(Nparams, PS_TYPE_F32);
+    assert (tmp->params);
+    assert (tmp->dparams);
+
+    for (psS32 i = 0; i < tmp->params->n; i++) {
+        tmp->params->data.F32[i] = 0.0;
+        tmp->dparams->data.F32[i] = 0.0;
+    }
+
+    tmp->modelFunc          = class->modelFunc;
+    tmp->modelFlux          = class->modelFlux;
+    tmp->modelRadius        = class->modelRadius;
+    tmp->modelLimits        = class->modelLimits;
+    tmp->modelGuess         = class->modelGuess;
+    tmp->modelFromPSF       = class->modelFromPSF;
+    tmp->modelParamsFromPSF = class->modelParamsFromPSF;
+    tmp->modelFitStatus     = class->modelFitStatus;
+
+    psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+    return(tmp);
+}
+
+bool psMemCheckModel(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) modelFree);
+}
+
+// copy model to a new structure
+pmModel *pmModelCopy (pmModel *model)
+{
+    PS_ASSERT_PTR_NON_NULL(model, NULL);
+
+    pmModel *new = pmModelAlloc (model->type);
+
+    new->chisq     = model->chisq;
+    new->nDOF      = model->nDOF;
+    new->nIter     = model->nIter;
+    new->flags     = model->flags;
+    new->radiusFit = model->radiusFit;
+
+    for (int i = 0; i < new->params->n; i++) {
+        new->params->data.F32[i]  = model->params->data.F32[i];
+        new->dparams->data.F32[i] = model->dparams->data.F32[i];
+    }
+
+    // note that model->residuals is just a reference
+    new->residuals = model->residuals;
+
+    return (new);
+}
+
+/******************************************************************************
+    pmModelEval(source, level, row): evaluates the model function at the specified coords.
+
+    NOTE: The coords are in subImage source->pixel coords, not image coords.
+
+    XXX: Use static vectors for x (NO: needs to be thread safe)
+*****************************************************************************/
+psF32 pmModelEval(pmModel *model, psImage *image, psS32 col, psS32 row)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(image, NAN);
+    PS_ASSERT_PTR_NON_NULL(model, NAN);
+    PS_ASSERT_PTR_NON_NULL(model->params, NAN);
+
+    // Allocate the x coordinate structure and convert row/col to image space.
+    //
+    psVector *x = psVectorAlloc(2, PS_TYPE_F32);
+    x->data.F32[0] = (psF32) (col + image->col0);
+    x->data.F32[1] = (psF32) (row + image->row0);
+    psF32 tmpF;
+
+    tmpF = model->modelFunc (NULL, model->params, x);
+    psFree(x);
+    psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+    return(tmpF);
+}
+
+psF32 pmModelEvalWithOffset(pmModel *model, psImage *image, psS32 col, psS32 row, int dx, int dy)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(image, false);
+    PS_ASSERT_PTR_NON_NULL(model, false);
+    PS_ASSERT_PTR_NON_NULL(model->params, false);
+
+    // Allocate the x coordinate structure and convert row/col to image space.
+    //
+    psVector *x = psVectorAlloc(2, PS_TYPE_F32);
+    x->data.F32[0] = (psF32) (col + image->col0 + dx);
+    x->data.F32[1] = (psF32) (row + image->row0 + dy);
+    psF32 tmpF;
+
+    tmpF = model->modelFunc (NULL, model->params, x);
+    psFree(x);
+    psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+    return(tmpF);
+}
+
+static bool AddOrSubModel(psImage *image,
+                          psImage *mask,
+                          pmModel *model,
+                          pmModelOpMode mode,
+                          bool add,
+                          psMaskType maskVal,
+                          int dx,
+                          int dy
+    )
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+
+    PS_ASSERT_PTR_NON_NULL(model, false);
+    PS_ASSERT_IMAGE_NON_NULL(image, false);
+    PS_ASSERT_IMAGE_TYPE(image, PS_TYPE_F32, false);
+
+    psVector *x = psVectorAlloc(2, PS_TYPE_F32);
+    psVector *params = model->params;
+
+    psS32 imageCol;
+    psS32 imageRow;
+    psF32 pixelValue;
+
+    // save original values; restore before returning
+    // use the true source position for the residual model
+    // the PSF model has presumably already been set for this coordinate
+    float xPos    = params->data.F32[PM_PAR_XPOS];
+    float yPos    = params->data.F32[PM_PAR_YPOS];
+    float IoSave  = params->data.F32[PM_PAR_I0];
+    float skySave = params->data.F32[PM_PAR_SKY];
+
+    if (mode & PM_MODEL_OP_NORM) {
+        params->data.F32[PM_PAR_I0] = 1.0;
+    }
+    if (!(mode & PM_MODEL_OP_SKY)) {
+        params->data.F32[PM_PAR_SKY] = 0.0;
+    }
+    if (mode & PM_MODEL_OP_CENTER) {
+        params->data.F32[PM_PAR_XPOS] = image->col0 + 0.5*image->numCols;
+        params->data.F32[PM_PAR_YPOS] = image->row0 + 0.5*image->numRows;
+    }
+
+    // use these values for this realization
+    float xCenter  = params->data.F32[PM_PAR_XPOS];
+    float yCenter  = params->data.F32[PM_PAR_YPOS];
+    float Io       = params->data.F32[PM_PAR_I0];
+
+    int xBin = 1;
+    int yBin = 1;
+    float xResidCenter = 0.0;
+    float yResidCenter = 0.0;
+
+    psImage *myRo = NULL;
+    psImage *myRx = NULL;
+    psImage *myRy = NULL;
+    psImageInterpolateOptions *Ro = NULL;
+    psImageInterpolateOptions *Rx = NULL;
+    psImageInterpolateOptions *Ry = NULL;
+    if (model->residuals && (mode & (PM_MODEL_OP_RES0 | PM_MODEL_OP_RES1))) {
+        // if the residual image and object image don't match,
+        // supply an appropriately overlapped residual image
+        psImage *inRo = model->residuals->Ro;
+        psImage *inRx = model->residuals->Rx;
+        psImage *inRy = model->residuals->Ry;
+        xBin = model->residuals->xBin;
+        yBin = model->residuals->yBin;
+        xResidCenter = model->residuals->xCenter;
+        yResidCenter = model->residuals->yCenter;
+        if ((image->numCols != inRo->numCols) ||
+            (image->numRows != inRo->numRows)) {
+            myRo = psImageAlloc (image->numCols, image->numRows, PS_TYPE_F32);
+            myRx = psImageAlloc (image->numCols, image->numRows, PS_TYPE_F32);
+            myRy = psImageAlloc (image->numCols, image->numRows, PS_TYPE_F32);
+            // Difference between input and desired centres
+            int xDiff = (int)(inRo->numCols / 2) - (xPos - image->col0);
+            int yDiff = (int)(inRo->numRows / 2) - (yPos - image->row0);
+            xResidCenter -= xDiff;
+            yResidCenter -= yDiff;
+            for (int iy = 0; iy < myRo->numRows; iy++) {
+                int jy = iy + yDiff;
+                if ((jy < 0) || (jy >= inRo->numRows)) {
+                    for (int ix = 0; ix < myRo->numCols; ix++) {
+                        myRo->data.F32[iy][ix] = 0.0;
+                        myRx->data.F32[iy][ix] = 0.0;
+                        myRy->data.F32[iy][ix] = 0.0;
+                    }
+                    continue;
+                }
+                for (int ix = 0; ix < myRo->numCols; ix++) {
+                    int jx = ix + xDiff;
+                    if ((jx < 0) || (jx >= inRo->numCols)) {
+                        myRo->data.F32[iy][ix] = 0.0;
+                        myRx->data.F32[iy][ix] = 0.0;
+                        myRy->data.F32[iy][ix] = 0.0;
+                    } else {
+                        myRo->data.F32[iy][ix] = inRo->data.F32[jy][jx];
+                        myRx->data.F32[iy][ix] = inRx->data.F32[jy][jx];
+                        myRy->data.F32[iy][ix] = inRy->data.F32[jy][jx];
+                    }
+                }
+            }
+        } else {
+            myRo = psMemIncrRefCounter (inRo);
+            myRx = psMemIncrRefCounter (inRx);
+            myRy = psMemIncrRefCounter (inRy);
+        }
+
+        Ro = psImageInterpolateOptionsAlloc(PS_INTERPOLATE_BILINEAR, myRo, NULL, mask, 0, 0.0, 0.0, 1, 0, 0.0);
+        Rx = psImageInterpolateOptionsAlloc(PS_INTERPOLATE_BILINEAR, myRx, NULL, NULL, 0, 0.0, 0.0, 1, 0, 0.0);
+        Ry = psImageInterpolateOptionsAlloc(PS_INTERPOLATE_BILINEAR, myRy, NULL, NULL, 0, 0.0, 0.0, 1, 0, 0.0);
+
+    }
+
+    for (psS32 iy = 0; iy < image->numRows; iy++) {
+        for (psS32 ix = 0; ix < image->numCols; ix++) {
+            if ((mask != NULL) && (mask->data.U8[iy][ix] & maskVal))
+                continue;
+
+            // Convert i/j to image coord space:
+            // XXX should we use using 0.5 pixel offset?
+            imageCol = ix + image->col0 + dx;
+            imageRow = iy + image->row0 + dy;
+
+            x->data.F32[0] = (float) imageCol;
+            x->data.F32[1] = (float) imageRow;
+
+            pixelValue = 0.0;
+
+            // add in the desired components for this coordinate
+            if (mode & PM_MODEL_OP_FUNC) {
+                pixelValue += model->modelFunc (NULL, params, x);
+            }
+
+            // get the contribution from the residual model
+            if (Ro) {
+                // fractional image position
+                float ox = xBin*(imageCol + 0.5 - xCenter) + xResidCenter;
+                float oy = yBin*(imageRow + 0.5 - yCenter) + yResidCenter;
+
+                psU8 mflux = 0;
+                if (mode & PM_MODEL_OP_RES0) {
+                    double Fo = 0.0;
+                    psImageInterpolate (&Fo, NULL, &mflux, ox, oy, Ro);
+                    if (!mflux && isfinite(Fo)) {
+                        pixelValue += Io*Fo;
+                    }
+                }
+                // skip Rx,Ry if Ro is masked
+                if (!mflux && (mode & PM_MODEL_OP_RES1)) {
+                    double Fx = 0.0;
+                    double Fy = 0.0;
+                    psImageInterpolate (&Fx, NULL, &mflux, ox, oy, Rx);
+                    psImageInterpolate (&Fy, NULL, &mflux, ox, oy, Ry);
+                    if (!mflux && isfinite(Fx) && isfinite(Fy)) {
+                        pixelValue += Io*(xPos*Fx + yPos*Fy);
+                    }
+                }
+            }
+
+            // add or subtract the value
+            if (add) {
+                image->data.F32[iy][ix] += pixelValue;
+            } else {
+                image->data.F32[iy][ix] -= pixelValue;
+            }
+        }
+    }
+
+    // restore original values
+    params->data.F32[PM_PAR_I0]   = IoSave;
+    params->data.F32[PM_PAR_SKY]  = skySave;
+    params->data.F32[PM_PAR_XPOS] = xPos;
+    params->data.F32[PM_PAR_YPOS] = yPos;
+
+    psFree(x);
+    psFree(Ro);
+    psFree(Rx);
+    psFree(Ry);
+    psFree(myRo);
+    psFree(myRx);
+    psFree(myRy);
+    psTrace("psModules.objects", 3, "---- %s(true) end ----\n", __func__);
+    return(true);
+}
+
+/******************************************************************************
+ *****************************************************************************/
+bool pmModelAdd(psImage *image,
+                psImage *mask,
+                pmModel *model,
+                pmModelOpMode mode,
+                psMaskType maskVal)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    psBool rc = AddOrSubModel(image, mask, model, mode, true, maskVal, 0.0, 0.0);
+    psTrace("psModules.objects", 3, "---- %s(%d) end ----\n", __func__, rc);
+    return(rc);
+}
+
+/******************************************************************************
+ *****************************************************************************/
+bool pmModelSub(psImage *image,
+                psImage *mask,
+                pmModel *model,
+                pmModelOpMode mode,
+                psMaskType maskVal)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    psBool rc = AddOrSubModel(image, mask, model, mode, false, maskVal, 0.0, 0.0);
+    psTrace("psModules.objects", 3, "---- %s(%d) end ----\n", __func__, rc);
+    return(rc);
+}
+
+/******************************************************************************
+ *****************************************************************************/
+bool pmModelAddWithOffset(psImage *image,
+                          psImage *mask,
+                          pmModel *model,
+                          pmModelOpMode mode,
+                          psMaskType maskVal,
+                          int dx,
+                          int dy)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    psBool rc = AddOrSubModel(image, mask, model, mode, true, maskVal, dx, dy);
+    psTrace("psModules.objects", 3, "---- %s(%d) end ----\n", __func__, rc);
+    return(rc);
+}
+
+/******************************************************************************
+ *****************************************************************************/
+bool pmModelSubWithOffset(psImage *image,
+                          psImage *mask,
+                          pmModel *model,
+                          pmModelOpMode mode,
+                          psMaskType maskVal,
+                          int dx,
+                          int dy)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    psBool rc = AddOrSubModel(image, mask, model, mode, false, maskVal, dx, dy);
+    psTrace("psModules.objects", 3, "---- %s(%d) end ----\n", __func__, rc);
+    return(rc);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmModel.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmModel.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmModel.h	(revision 22157)
@@ -0,0 +1,201 @@
+/* @file  pmModel.h
+ * @brief Functions to define and manipulate object models
+ *
+ * @author GLG, MHPCC
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.16 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-04-08 18:33:16 $
+ *
+ * Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+# ifndef PM_MODEL_H
+# define PM_MODEL_H
+
+/// @addtogroup Objects Object Detection / Analysis Functions
+/// @{
+
+/* pointers for the functions types below are supplied to each pmModel, and can be used by
+   the programmer without needing to know the model class */
+
+typedef enum {
+    PM_MODEL_STATUS_NONE         = 0x00, ///< model fit not yet attempted, no other info
+    PM_MODEL_STATUS_FITTED       = 0x01, ///< model fit completed
+    PM_MODEL_STATUS_NONCONVERGE  = 0x02, ///< model fit did not converge
+    PM_MODEL_STATUS_OFFIMAGE     = 0x04, ///< model fit drove out of range
+    PM_MODEL_STATUS_BADARGS      = 0x08, ///< model fit called with invalid args
+    PM_MODEL_STATUS_LIMITS       = 0x10  ///< model parameters hit limits
+} pmModelStatus;
+
+typedef enum {
+    PM_MODEL_OP_NONE    = 0x00,
+    PM_MODEL_OP_FUNC    = 0x01,
+    PM_MODEL_OP_RES0    = 0x02,
+    PM_MODEL_OP_RES1    = 0x04,
+    PM_MODEL_OP_FULL    = 0x07,
+    PM_MODEL_OP_SKY     = 0x08,
+    PM_MODEL_OP_CENTER  = 0x10,
+    PM_MODEL_OP_NORM    = 0x20,
+    PM_MODEL_OP_NOISE   = 0x40,
+} pmModelOpMode;
+
+typedef struct pmModel pmModel;
+typedef struct pmSource pmSource;
+
+//  This function is the model chi-square minimization function for this model.
+typedef psMinimizeLMChi2Func pmModelFunc;
+
+//  This function sets the parameter limits for this model.
+typedef psMinimizeLMLimitFunc pmModelLimits;
+
+// This function returns the integrated flux for the given model parameters.
+typedef psF64 (*pmModelFlux)(const psVector *params);
+
+// This function returns the radius at which the given model and parameters
+// achieves the given flux.
+typedef psF64 (*pmModelRadius)(const psVector *params, double flux);
+
+//  This function provides the model guess parameters based on the details of
+//  the given source.
+typedef bool (*pmModelGuessFunc)(pmModel *model, pmSource *source);
+
+//  This function constructs the PSF model for the given source based on the
+//  supplied psf and the EXT model for the object.
+typedef bool (*pmModelFromPSFFunc)(pmModel *modelPSF, pmModel *modelEXT, const pmPSF *psf);
+
+//  This function sets the model parameters based on the PSF for a given coordinate and central
+//  intensity
+typedef bool (*pmModelParamsFromPSF)(pmModel *model, const pmPSF *psf, float Xo, float Yo, float Io);
+
+//  This function returns the success / failure status of the given model fit
+typedef bool (*pmModelFitStatusFunc)(pmModel *model);
+
+/** pmModel data structure
+ *
+ * Every source may have two types of models: a PSF model and a EXT (extended-source)
+ * model. The PSF model represents the best fit of the image PSF to the specific
+ * object. In this case, the PSF-dependent parameters are specified for the
+ * object by the PSF, not by the fit. The EXT model represents the best fit of
+ * the given model to the object, with all shape parameters floating in the fit.
+ *
+ */
+struct pmModel {
+    pmModelType type;                   ///< Model to be used.
+    psVector *params;                   ///< Paramater values.
+    psVector *dparams;                  ///< Parameter errors.
+    float chisq;                        ///< Fit chi-squared.
+    float chisqNorm;                    ///< re-normalized fit chi-squared.
+    float mag;				///< integrated model magnitude 
+    float magErr;			///< integrated model magnitude error
+    int nDOF;                           ///< number of degrees of freedom
+    int nIter;                          ///< number of iterations to reach min
+    pmModelStatus flags;                ///< model status flags
+    float radiusFit;                    ///< fit radius actually used
+    pmResiduals *residuals;             ///< normalized PSF residuals
+
+    // functions for this model which depend on the model class
+    pmModelFunc          modelFunc;
+    pmModelFlux          modelFlux;
+    pmModelRadius        modelRadius;
+    pmModelLimits        modelLimits;
+    pmModelGuessFunc     modelGuess;
+    pmModelFromPSFFunc   modelFromPSF;
+    pmModelParamsFromPSF modelParamsFromPSF;
+    pmModelFitStatusFunc modelFitStatus;
+};
+
+/** Symbolic names for the elements of [d]params
+ * Note: these are #defines not enums as a given element of [d]params
+ * may/will correspond to different parameters in different contexts
+ */
+#define PM_PAR_SKY 0   ///< Sky
+#define PM_PAR_I0 1   ///< Central intensity
+#define PM_PAR_XPOS 2   ///< X centre of object
+#define PM_PAR_YPOS 3   ///< Y centre of object
+#define PM_PAR_SXX 4   ///< shape X^2 moment
+#define PM_PAR_SYY 5   ///< shape Y^2 moment
+#define PM_PAR_SXY 6   ///< shape XY moment
+#define PM_PAR_7 7   ///< ??? Unknown parameter
+#define PM_PAR_8 8   ///< ??? Unknown parameter
+
+/** pmModelAlloc()
+ *
+ */
+pmModel *pmModelAlloc(pmModelType type);
+bool psMemCheckModel(psPtr ptr);
+
+// copy model to a new structure
+pmModel *pmModelCopy (pmModel *model);
+
+psF32 pmModelEval(pmModel *model, psImage *image, psS32 col, psS32 row);
+psF32 pmModelEvalWithOffset(pmModel *model, psImage *image, psS32 col, psS32 row, int dx, int dy);
+
+/** pmModelAdd()
+ *
+ * Add the given source model flux to/from the provided image. The boolean
+ * option center selects if the source is re-centered to the image center or if
+ * it is placed at its centroid location. The boolean option sky selects if the
+ * background sky is applied (TRUE) or not. The pixel range in the target image
+ * is at most the pixel range specified by the source.pixels image. The success
+ * status is returned.
+ *
+ */
+bool pmModelAdd(
+    psImage *image,                     ///< The output image (float)
+    psImage *mask,                      ///< The image pixel mask (valid == 0)
+    pmModel *model,                     ///< The input pmModel
+    pmModelOpMode mode,                 ///< mode to control how the model is added into the image
+    psMaskType maskVal                  ///< Value to mask
+);
+
+/** pmModelSub()
+ *
+ * Subtract the given source model flux to/from the provided image. The
+ * boolean option center selects if the source is re-centered to the image center
+ * or if it is placed at its centroid location. The boolean option sky selects if
+ * the background sky is applied (TRUE) or not. The pixel range in the target
+ * image is at most the pixel range specified by the source.pixels image. The
+ * success status is returned.
+ *
+ */
+bool pmModelSub(
+    psImage *image,                     ///< The output image (float)
+    psImage *mask,                      ///< The image pixel mask (valid == 0)
+    pmModel *model,                     ///< The input pmModel
+    pmModelOpMode mode,                 ///< mode to control how the model is added into the image
+    psMaskType maskVal                  ///< Value to mask
+);
+
+bool pmModelAddWithOffset(psImage *image,
+                          psImage *mask,
+                          pmModel *model,
+                          pmModelOpMode mode,
+                          psMaskType maskVal,
+                          int dx,
+                          int dy);
+
+bool pmModelSubWithOffset(psImage *image,
+                          psImage *mask,
+                          pmModel *model,
+                          pmModelOpMode mode,
+                          psMaskType maskVal,
+                          int dx,
+                          int dy);
+
+/** pmModelFitStatus()
+ *
+ * This function wraps the call to the model-specific function returned by
+ * pmModelFitStatusFunc_GetFunction.  The model-specific function examines the
+ * model parameters, parameter errors, Chisq, S/N, and other parameters available
+ * from model to decide if the particular fit was successful or not.
+ *
+ * XXX: Must code this.
+ *
+ */
+bool pmModelFitStatus(
+    pmModel *model                      ///< Model to be used
+);
+
+/// @}
+# endif /* PM_MODEL_H */
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmModelClass.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmModelClass.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmModelClass.c	(revision 22157)
@@ -0,0 +1,165 @@
+/** @file  pmModelClass.c
+ *
+ *  Functions to define and manipulate object model attributes
+ *
+ *  @author GLG, MHPCC
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-11-27 03:14:57 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmResiduals.h"
+#include "pmGrowthCurve.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmModelClass.h"
+#include "pmErrorCodes.h"
+
+// XXX shouldn't these be defined for us in pslib.h ???
+double hypot(double x, double y);
+double sqrt (double x);
+
+# include "models/pmModel_GAUSS.c"
+# include "models/pmModel_PGAUSS.c"
+# include "models/pmModel_QGAUSS.c"
+# include "models/pmModel_RGAUSS.c"
+# include "models/pmModel_SERSIC.c"
+
+static pmModelClass defaultModels[] = {
+    {"PS_MODEL_GAUSS",        7, pmModelFunc_GAUSS,   pmModelFlux_GAUSS,   pmModelRadius_GAUSS,   pmModelLimits_GAUSS,   pmModelGuess_GAUSS,  pmModelFromPSF_GAUSS,  pmModelParamsFromPSF_GAUSS,  pmModelFitStatus_GAUSS},
+    {"PS_MODEL_PGAUSS",       7, pmModelFunc_PGAUSS,  pmModelFlux_PGAUSS,  pmModelRadius_PGAUSS,  pmModelLimits_PGAUSS,  pmModelGuess_PGAUSS, pmModelFromPSF_PGAUSS, pmModelParamsFromPSF_PGAUSS, pmModelFitStatus_PGAUSS},
+    {"PS_MODEL_QGAUSS",       8, pmModelFunc_QGAUSS,  pmModelFlux_QGAUSS,  pmModelRadius_QGAUSS,  pmModelLimits_QGAUSS,  pmModelGuess_QGAUSS, pmModelFromPSF_QGAUSS, pmModelParamsFromPSF_QGAUSS, pmModelFitStatus_QGAUSS},
+    {"PS_MODEL_RGAUSS",       8, pmModelFunc_RGAUSS,  pmModelFlux_RGAUSS,  pmModelRadius_RGAUSS,  pmModelLimits_RGAUSS,  pmModelGuess_RGAUSS, pmModelFromPSF_RGAUSS, pmModelParamsFromPSF_RGAUSS, pmModelFitStatus_RGAUSS},
+    {"PS_MODEL_SERSIC",       8, pmModelFunc_SERSIC,  pmModelFlux_SERSIC,  pmModelRadius_SERSIC,  pmModelLimits_SERSIC,  pmModelGuess_SERSIC, pmModelFromPSF_SERSIC, pmModelParamsFromPSF_SERSIC, pmModelFitStatus_SERSIC}
+};
+
+static pmModelClass *models = NULL;
+static int Nmodels = 0;
+
+static void ModelClassFree (pmModelClass *modelClass)
+{
+    if (modelClass == NULL)
+        return;
+    return;
+}
+
+pmModelClass *pmModelClassAlloc (int nModels)
+{
+    pmModelClass *modelClass = (pmModelClass *) psAlloc (nModels * sizeof(pmModelClass));
+    psMemSetDeallocator(modelClass, (psFreeFunc) ModelClassFree);
+    return (modelClass);
+}
+
+bool psMemCheckModelClass(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) ModelClassFree);
+}
+
+void pmModelClassAdd (pmModelClass *model)
+{
+    if (models == NULL) {
+        pmModelClassInit();
+    }
+
+    Nmodels ++;
+    models = (pmModelClass *) psRealloc (models, Nmodels*sizeof(pmModelClass));
+    models[Nmodels-1] = model[0];
+    return;
+}
+
+bool pmModelClassInit (void)
+{
+    // if we do not need to init, return false;
+    if (models != NULL) {
+        return false;
+    }
+
+    int Nnew = sizeof (defaultModels) / sizeof (pmModelClass);
+
+    models = pmModelClassAlloc (Nnew);
+    for (int i = 0; i < Nnew; i++) {
+        models[i] = defaultModels[i];
+    }
+    Nmodels = Nnew;
+    return true;
+}
+
+pmModelClass *pmModelClassSelect (pmModelType type)
+{
+    if (models == NULL) {
+        pmModelClassInit();
+    }
+
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (NULL);
+    }
+    return (&models[type]);
+}
+
+void pmModelClassCleanup (void)
+{
+    psFree (models);
+    models = NULL;
+    Nmodels = 0;
+    return;
+}
+
+psS32 pmModelClassParameterCount (pmModelType type)
+{
+    if (models == NULL) {
+        pmModelClassInit();
+    }
+
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (0);
+    }
+    return (models[type].nParams);
+}
+
+psS32 pmModelClassGetType (const char *name)
+{
+    if (models == NULL) {
+        pmModelClassInit();
+    }
+
+    for (int i = 0; i < Nmodels; i++) {
+        if (!strcmp(models[i].name, name)) {
+            return (i);
+        }
+    }
+    return (-1);
+}
+
+char *pmModelClassGetName (pmModelType type)
+{
+    if (models == NULL) {
+        pmModelClassInit();
+    }
+
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (NULL);
+    }
+    return (models[type].name);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmModelClass.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmModelClass.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmModelClass.h	(revision 22157)
@@ -0,0 +1,76 @@
+/* @file  pmModelClass.h
+ *
+ * The object model function types are desined to allow for the flexible addition of new
+ * object models. Every object model, with parameters represented by pmModel, has an
+ * associated set of functions which provide necessary support operations.  A These
+ * functions allow the programmer to select the approriate function or property for a
+ * specific object model class.
+ *
+ * Every model instance belongs to a class of models, defined by the value of the
+ * pmModelType type entry. Various functions need access to information about each of the
+ * models. Some of this information varies from model to model, and may depend on the
+ * current parameter values or other data quantities. In order to keep the code from
+ * requiring the information about each model to be coded into the low-level fitting
+ * routines, we define a collection of functions which allow us to abstract this type of
+ * model-dependent information. These generic functions take the model type and return the
+ * corresponding function pointer for the specified model. Each model is defined by
+ * creating this collection of specific functions, and placing them in a single file for
+ * each model. We define the following structure to carry the collection of information
+ * about the models.
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-11-27 03:14:57 $
+ * Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+# ifndef PM_MODEL_CLASS_H
+# define PM_MODEL_CLASS_H
+
+/// @addtogroup Objects Object Detection / Analysis Functions
+/// @{
+
+typedef struct
+{
+    char *name;
+    int nParams;
+    pmModelFunc          modelFunc;
+    pmModelFlux          modelFlux;
+    pmModelRadius        modelRadius;
+    pmModelLimits        modelLimits;
+    pmModelGuessFunc     modelGuess;
+    pmModelFromPSFFunc   modelFromPSF;
+    pmModelParamsFromPSF modelParamsFromPSF;
+    pmModelFitStatusFunc modelFitStatus;
+} pmModelClass;
+
+// allocate a pmModelClass to hold nModels entries
+pmModelClass *pmModelClassAlloc (int nModels);
+
+//
+bool psMemCheckModelClass(psPtr ptr);
+
+// initialize the internal (static) model class with the default models
+bool pmModelClassInit (void);
+
+// free the internal (static) model class
+void pmModelClassCleanup (void);
+
+// add a new model class to the collection of model classes
+void pmModelClassAdd (pmModelClass *modelClass);
+
+// get the specified model class
+pmModelClass *pmModelClassSelect (pmModelType type);
+
+// This function returns the number of parameters used by the listed function.
+int pmModelClassParameterCount (pmModelType type);
+
+// This function returns the user-space model names for the specified model type.
+char *pmModelClassGetName (pmModelType type);
+
+// This function returns the internal model type code for the user-space model names.
+pmModelType pmModelClassGetType (const char *name);
+
+/// @}
+# endif /* PM_MODEL_CLASS_H */
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmModelGroup.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmModelGroup.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmModelGroup.c	(revision 22157)
@@ -0,0 +1,202 @@
+/** @file  pmModelGroup.c
+ *
+ *  Functions to define and manipulate object model attributes
+ *
+ *  @author GLG, MHPCC
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.19 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-11-10 01:09:20 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmGrowthCurve.h"
+#include "pmResiduals.h"
+#include "pmModel.h"
+#include "pmModelGroup.h"
+#include "pmErrorCodes.h"
+
+// XXX shouldn't these be defined for us in pslib.h ???
+double hypot(double x, double y);
+double sqrt (double x);
+
+# include "models/pmModel_GAUSS.c"
+# include "models/pmModel_PGAUSS.c"
+# include "models/pmModel_QGAUSS.c"
+# include "models/pmModel_RGAUSS.c"
+# include "models/pmModel_SERSIC.c"
+
+static pmModelGroup defaultModels[] = {
+                                          {"PS_MODEL_GAUSS",        7, pmModelFunc_GAUSS,   pmModelFlux_GAUSS,   pmModelRadius_GAUSS,   pmModelLimits_GAUSS,   pmModelGuess_GAUSS,  pmModelFromPSF_GAUSS,  pmModelFitStatus_GAUSS},
+                                          {"PS_MODEL_PGAUSS",       7, pmModelFunc_PGAUSS,  pmModelFlux_PGAUSS,  pmModelRadius_PGAUSS,  pmModelLimits_PGAUSS,  pmModelGuess_PGAUSS, pmModelFromPSF_PGAUSS, pmModelFitStatus_PGAUSS},
+                                          {"PS_MODEL_QGAUSS",       8, pmModelFunc_QGAUSS,  pmModelFlux_QGAUSS,  pmModelRadius_QGAUSS,  pmModelLimits_QGAUSS,  pmModelGuess_QGAUSS, pmModelFromPSF_QGAUSS, pmModelFitStatus_QGAUSS},
+                                          {"PS_MODEL_RGAUSS",       8, pmModelFunc_RGAUSS,  pmModelFlux_RGAUSS,  pmModelRadius_RGAUSS,  pmModelLimits_RGAUSS,  pmModelGuess_RGAUSS, pmModelFromPSF_RGAUSS, pmModelFitStatus_RGAUSS},
+                                          {"PS_MODEL_SERSIC",       8, pmModelFunc_SERSIC,  pmModelFlux_SERSIC,  pmModelRadius_SERSIC,  pmModelLimits_SERSIC,  pmModelGuess_SERSIC, pmModelFromPSF_SERSIC, pmModelFitStatus_SERSIC}
+                                      };
+
+static pmModelGroup *models = NULL;
+static int Nmodels = 0;
+
+static void ModelGroupFree (pmModelGroup *modelGroup)
+{
+
+    if (modelGroup == NULL)
+        return;
+    return;
+}
+
+pmModelGroup *pmModelGroupAlloc (int nModels)
+{
+
+    pmModelGroup *modelGroup = (pmModelGroup *) psAlloc (nModels * sizeof(pmModelGroup));
+    psMemSetDeallocator(modelGroup, (psFreeFunc) ModelGroupFree);
+    return (modelGroup);
+}
+
+bool psMemCheckModelGroup(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) ModelGroupFree);
+}
+
+void pmModelGroupAdd (pmModelGroup *model)
+{
+
+    if (models == NULL) {
+        pmModelGroupInit ();
+    }
+
+    Nmodels ++;
+    models = (pmModelGroup *) psRealloc (models, Nmodels*sizeof(pmModelGroup));
+    models[Nmodels-1] = model[0];
+    return;
+}
+
+bool pmModelGroupInit (void)
+{
+
+    // if we do not need to init, return false;
+    if (models != NULL)
+        return false;
+
+    int Nnew = sizeof (defaultModels) / sizeof (pmModelGroup);
+
+    models = pmModelGroupAlloc (Nnew);
+    for (int i = 0; i < Nnew; i++) {
+        models[i] = defaultModels[i];
+    }
+    Nmodels = Nnew;
+    return true;
+}
+
+void pmModelGroupCleanup (void)
+{
+    psFree (models);
+    models = NULL;
+    return;
+}
+
+pmModelFunc pmModelFunc_GetFunction (pmModelType type)
+{
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (NULL);
+    }
+    return (models[type].modelFunc);
+}
+
+pmModelFlux pmModelFlux_GetFunction (pmModelType type)
+{
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (NULL);
+    }
+    return (models[type].modelFlux);
+}
+
+pmModelRadius pmModelRadius_GetFunction (pmModelType type)
+{
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (NULL);
+    }
+    return (models[type].modelRadius);
+}
+
+pmModelLimits pmModelLimits_GetFunction (pmModelType type)
+{
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (NULL);
+    }
+    return (models[type].modelLimits);
+}
+
+pmModelGuessFunc pmModelGuessFunc_GetFunction (pmModelType type)
+{
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (NULL);
+    }
+    return (models[type].modelGuessFunc);
+}
+
+pmModelFitStatusFunc pmModelFitStatusFunc_GetFunction (pmModelType type)
+{
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (NULL);
+    }
+    return (models[type].modelFitStatusFunc);
+}
+
+pmModelFromPSFFunc pmModelFromPSFFunc_GetFunction (pmModelType type)
+{
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (NULL);
+    }
+    return (models[type].modelFromPSFFunc);
+}
+
+psS32 pmModelParameterCount (pmModelType type)
+{
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (0);
+    }
+    return (models[type].nParams);
+}
+
+psS32 pmModelSetType (char *name)
+{
+    for (int i = 0; i < Nmodels; i++) {
+        if (!strcmp(models[i].name, name)) {
+            return (i);
+        }
+    }
+    return (-1);
+}
+
+char *pmModelGetType (pmModelType type)
+{
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (NULL);
+    }
+    return (models[type].name);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmModelGroup.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmModelGroup.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmModelGroup.h	(revision 22157)
@@ -0,0 +1,201 @@
+/* @file  pmModelGroup.h
+ *
+ * The object model function types are desined to allow for the flexible addition of new object
+ * models. Every object model, with parameters represented by pmModel, has an associated set of
+ * functions which provide necessary support operations. A set of abstract functions allow the
+ * programmer to select the approriate function or property for a specific named object model.
+ *
+ * Every model instance belongs to a class of models, defined by the value of
+ * the pmModelType type entry. Various functions need access to information about
+ * each of the models. Some of this information varies from model to model, and
+ * may depend on the current parameter values or other data quantities. In order
+ * to keep the code from requiring the information about each model to be coded
+ * into the low-level fitting routines, we define a collection of functions which
+ * allow us to abstract this type of model-dependent information. These generic
+ * functions take the model type and return the corresponding function pointer
+ * for the specified model. Each model is defined by creating this collection of
+ * specific functions, and placing them in a single file for each model. We
+ * define the following structure to carry the collection of information about
+ * the models.
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.9 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-11-10 01:09:20 $
+ * Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+# ifndef PM_MODEL_GROUP_H
+# define PM_MODEL_GROUP_H
+
+/// @addtogroup Objects Object Detection / Analysis Functions
+/// @{
+
+//  This function is the model chi-square minimization function for this model.
+typedef psMinimizeLMChi2Func pmModelFunc;
+
+//  This function sets the parameter limits for this model.
+typedef psMinimizeLMLimitFunc pmModelLimits;
+
+// This function returns the integrated flux for the given model parameters.
+typedef psF64 (*pmModelFlux)(const psVector *params);
+
+// This function returns the radius at which the given model and parameters
+// achieves the given flux.
+typedef psF64 (*pmModelRadius)(const psVector *params, double flux);
+
+//  This function provides the model guess parameters based on the details of
+//  the given source.
+typedef bool (*pmModelGuessFunc)(pmModel *model, pmSource *source);
+
+//  This function constructs the PSF model for the given source based on the
+//  supplied psf and the EXT model for the object.
+typedef bool (*pmModelFromPSFFunc)(pmModel *modelPSF, pmModel *modelEXT, pmPSF *psf);
+
+//  This function sets the model parameters based on the PSF for a given coordinate and central
+//  intensity
+typedef bool (*pmModelParamsFromPSF)(pmModel *model, pmPSF *psf, float Xo, float Yo, float Io);
+
+//  This function returns the success / failure status of the given model fit
+typedef bool (*pmModelFitStatusFunc)(pmModel *model);
+
+typedef struct
+{
+    char *name;
+    int nParams;
+    pmModelFunc          modelFunc;
+    pmModelFlux          modelFlux;
+    pmModelRadius        modelRadius;
+    pmModelLimits        modelLimits;
+    pmModelGuessFunc     modelGuessFunc;
+    pmModelFromPSFFunc   modelFromPSFFunc;
+    pmModelParamsFromPSF modelParamsFromPSF;
+    pmModelFitStatusFunc modelFitStatusFunc;
+}
+pmModelGroup;
+
+// allocate a pmModelGroup to hold nModels entries
+pmModelGroup *pmModelGroupAlloc (int nModels);
+
+bool psMemCheckModelGroup(psPtr ptr);
+
+// initialize the internal (static) model group with the default models
+bool pmModelGroupInit (void);
+
+// free the internal (static) model group
+void pmModelGroupCleanup (void);
+
+// add a new model to the internal (static) model group
+void pmModelGroupAdd (pmModelGroup *model);
+
+/* This function returns the number of parameters used by the listed function.
+ */
+int pmModelParameterCount(
+    pmModelType type                    ///< Add comment.
+);
+
+
+/* This function returns the user-space model names for the specified model type.
+ */
+char *pmModelGetType(
+    pmModelType type                    ///< Add comment.
+);
+
+
+/**
+ * 
+ * This function returns the internal model type code for the user-space model names.
+ * 
+ */
+pmModelType pmModelSetType(
+    char *name                          ///< Add comment.
+);
+
+/**
+ * 
+ *  Each of the function types above has a corresponding function which returns
+ *  the function given the model type:
+ * 
+ */
+
+/**
+ * 
+ * pmModelFunc is the function used to determine the value of the model at a
+ * specific coordinate, and is the one used by psMinimizeLMChi2.
+ * 
+ */
+pmModelFunc          pmModelFunc_GetFunction (pmModelType type);
+
+
+/**
+ * 
+ * pmModelFlux returns the total integrated flux for the given input parameters.
+ * 
+ */
+pmModelFlux          pmModelFlux_GetFunction (pmModelType type);
+
+
+/**
+ * 
+ * pmModelRadius returns the scaling radius at which the flux of the model
+ * matches the specified flux. This presumes that the model is a function of an
+ * elliptical contour.
+ * 
+ */
+pmModelRadius        pmModelRadius_GetFunction (pmModelType type);
+
+
+/**
+ * 
+ * pmModelLimits sets the parameter limit vectors for the function.
+ * 
+ */
+pmModelLimits        pmModelLimits_GetFunction (pmModelType type);
+
+
+/**
+ * 
+ * pmModelGuessFunc generates an initial guess for the model based on the
+ * provided source statistics (moments and pixel values as needed).
+ * 
+ */
+pmModelGuessFunc     pmModelGuessFunc_GetFunction (pmModelType type);
+
+
+/**
+ * 
+ * pmModelFromPSFFunc takes as input a representation of the psf and a value
+ * for the model and fills in the PSF parameters of the model. The input
+ * primarily relies upon the centroid coordinates of the input model, though the
+ * normalization may potentially be used.
+ * 
+ */
+pmModelFromPSFFunc   pmModelFromPSFFunc_GetFunction (pmModelType type);
+
+
+/**
+ * 
+ * pmModelFitStatusFunc returns a true or false values based on the success or
+ * failure of a model fit.  The success is determined by quantities such as the
+ * chisq or the signal-to-noise.
+ * 
+ */
+pmModelFitStatusFunc pmModelFitStatusFunc_GetFunction (pmModelType type);
+
+
+/** pmSourceModelGuess()
+ *
+ * Convert available data to an initial guess for the given model. This
+ * function allocates a pmModel entry for the pmSource structure based on the
+ * provided model selection. The method of defining the model parameter guesses
+ * are specified for each model below. The guess values are placed in the model
+ * parameters. The function returns TRUE on success or FALSE on failure.
+ *
+ */
+pmModel *pmSourceModelGuess(
+    pmSource *source,   ///< The input pmSource
+    pmModelType model   ///< The type of model to be created.
+);
+
+/// @}
+# endif /* PM_MODEL_GROUP_H */
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmModelUtils.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmModelUtils.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmModelUtils.c	(revision 22157)
@@ -0,0 +1,95 @@
+/** @file  pmModelUtils.c
+ *
+ *  Functions to manipulate object models
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-12-15 01:22:11 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmResiduals.h"
+#include "pmGrowthCurve.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmErrorCodes.h"
+#include "pmModelUtils.h"
+
+/*****************************************************************************
+pmModelFromPSF (*modelEXT, *psf):  use the model position parameters to
+construct a realization of the PSF model at the object coordinates
+ *****************************************************************************/
+pmModel *pmModelFromPSF (pmModel *modelEXT, const pmPSF *psf)
+{
+    PS_ASSERT_PTR_NON_NULL(psf, NULL);
+    PS_ASSERT_PTR_NON_NULL(modelEXT, NULL);
+
+    // allocate a new pmModel to hold the PSF version
+    pmModel *modelPSF = pmModelAlloc (psf->type);
+
+    // set model parameters for this source based on PSF information
+    if (!modelEXT->modelFromPSF (modelPSF, modelEXT, psf)) {
+        psError(PM_ERR_PSF, false, "Failed to set model params from PSF");
+        psFree(modelPSF);
+        return NULL;
+    }
+    // XXX note that model->residuals is just a reference
+    modelPSF->residuals = psf->residuals;
+
+    return (modelPSF);
+}
+
+// instantiate a model for the PSF at this location with peak flux
+// NOTE: psf and (Xo,Yo) are defined wrt chip coordinates
+pmModel *pmModelFromPSFforXY (const pmPSF *psf, float Xo, float Yo, float Io)
+{
+    PS_ASSERT_PTR_NON_NULL(psf, NULL);
+
+    // allocate a new pmModel to hold the PSF version
+    pmModel *modelPSF = pmModelAlloc (psf->type);
+
+    // set model parameters for this source based on PSF information
+    if (!modelPSF->modelParamsFromPSF (modelPSF, psf, Xo, Yo, Io)) {
+        psError(PM_ERR_PSF, false, "Failed to set model params from PSF");
+        psFree(modelPSF);
+        return NULL;
+    }
+
+    // XXX note that model->residuals is just a reference
+    modelPSF->residuals = psf->residuals;
+
+    return (modelPSF);
+}
+
+// set this model to have the requested flux
+bool pmModelSetFlux (pmModel *model, float flux) {
+    PS_ASSERT_PTR_NON_NULL(model, NULL);
+    PS_ASSERT_PTR_NON_NULL(model->params, NULL);
+
+    // set Io to be 1.0
+    model->params->data.F32[PM_PAR_I0] = 1.0;
+
+    // determine the normalized flux
+    float normFlux = model->modelFlux (model->params);
+    assert (isfinite(normFlux));
+    assert (normFlux > 0);
+
+    // set the desired normalization
+    model->params->data.F32[PM_PAR_I0] = flux / normFlux;
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmModelUtils.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmModelUtils.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmModelUtils.h	(revision 22157)
@@ -0,0 +1,47 @@
+/* @file  pmModelUtils.h
+ *
+ * Utility functions for working with pmSources
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-12-15 01:22:11 $
+ * Copyright 2007 IfA, University of Hawaii
+ */
+
+# ifndef PM_MODEL_UTILS_H
+# define PM_MODEL_UTILS_H
+
+/// @addtogroup Objects Object Detection / Analysis Functions
+/// @{
+
+/**
+ *
+ * This function constructs a pmModel instance based on the pmPSF description
+ * of the PSF. The input is a pmModel with at least the values of the centroid
+ * coordinates (possibly normalization if this is needed) defined. The values of
+ * the PSF-dependent parameters are specified for the specific realization based
+ * on the coordinates of the object.
+ *
+ */
+pmModel *pmModelFromPSF(
+    pmModel *model,                     ///< Add comment
+    const pmPSF *psf                    ///< Add comment
+);
+
+pmModel *pmModelFromPSFforXY (
+    const pmPSF *psf,
+    float Xo,
+    float Yo,
+    float Io
+    );
+
+bool pmModelSetFlux (
+    pmModel *model,
+    float flux
+    );
+
+
+
+/// @}
+# endif /* PM_MODEL_UTILS_H */
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmMoments.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmMoments.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmMoments.c	(revision 22157)
@@ -0,0 +1,44 @@
+/** @file  pmMoments.c
+ *
+ *  Functions defining the pmMoments structure
+ *
+ *  @author GLG, MHPCC
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-09-15 09:49:01 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include "pmMoments.h"
+
+/******************************************************************************
+pmMomentsAlloc(): Allocate the pmMoments structure and initialize the members
+to zero.
+*****************************************************************************/
+pmMoments *pmMomentsAlloc()
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    pmMoments *tmp = (pmMoments *) psAlloc(sizeof(pmMoments));
+    tmp->x = 0.0;
+    tmp->y = 0.0;
+    tmp->Sx = 0.0;
+    tmp->Sy = 0.0;
+    tmp->Sxy = 0.0;
+    tmp->Sum = 0.0;
+    tmp->Peak = 0.0;
+    tmp->Sky = 0.0;
+    tmp->nPixels = 0;
+
+    psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+    return(tmp);
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmMoments.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmMoments.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmMoments.h	(revision 22157)
@@ -0,0 +1,46 @@
+/* @file  pmMoments.h
+ * @brief Definitions of the moments structure
+ *
+ * @author GLG, MHPCC
+ *
+ * @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-01-24 02:54:15 $
+ * Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+# ifndef PM_MOMENTS_H
+# define PM_MOMENTS_H
+
+/// @addtogroup Objects Object Detection / Analysis Functions
+/// @{
+
+/** pmMoments data structure
+ *
+ * One of the simplest measurements which can be made quickly for an object
+ * are the object moments. We specify a structure to carry the moment information
+ * for a specific source:
+ *
+ */
+typedef struct
+{
+    float x;     ///< X-coord of centroid.
+    float y;     ///< Y-coord of centroid.
+    float Sx;    ///< x-second moment.
+    float Sy;    ///< y-second moment.
+    float Sxy;   ///< xy cross moment.
+    float Sum;   ///< Pixel sum above sky (background).
+    float Peak;  ///< Peak counts above sky.
+    float Sky;   ///< Sky level (background).
+    float dSky;  ///< local Sky variance
+    float SN;    ///< approx signal-to-noise
+    int nPixels; ///< Number of pixels used.
+}
+pmMoments;
+
+/** pmMomentsAlloc()
+ *
+ */
+pmMoments *pmMomentsAlloc();
+
+/// @}
+# endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmObjects.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmObjects.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmObjects.c	(revision 22157)
@@ -0,0 +1,25 @@
+/** @file  pmObjects.c
+ *
+ *  This file will ...
+ *
+ *  @author GLG, MHPCC
+ *  @author EAM, IfA: significant modifications.
+ *
+ *  @version $Revision: 1.13 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-09-15 09:49:01 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmObjects.h"
+#include "pmModelGroup.h"
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmObjects.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmObjects.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmObjects.h	(revision 22157)
@@ -0,0 +1,68 @@
+/* @file  pmObjects.h
+ *
+ * The process of finding, measuring, and classifying astronomical sources on
+ * images is one of the critical tasks of the IPP or any astronomical software
+ * system. This file will define structures and functions related to the task
+ * of source detection and measurement. The elements defined in this section
+ * are generally low-level components which can be connected together to
+ * construct a complete object measurement suite.
+ *
+ * @author GLG, MHPCC
+ *
+ * @version $Revision: 1.8 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-01-24 02:54:15 $
+ * Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+#ifndef PM_OBJECTS_H
+#define PM_OBJECTS_H
+
+/// @addtogroup Objects Object Detection / Analysis Functions
+/// @{
+
+#include <stdio.h>
+#include <math.h>
+#include <pslib.h>
+
+/**
+ *
+ * This function converts the source classification into the closest available
+ * approximation to the Dophot classification scheme:
+ * XXX EAM : fix this to use current source classification scheme
+ *
+ * PM_SOURCE_DEFECT: 8
+ * PM_SOURCE_SATURATED: 8
+ * PM_SOURCE_SATSTAR: 10
+ * PM_SOURCE_PSFSTAR: 1
+ * PM_SOURCE_GOODSTAR: 1
+ * PM_SOURCE_POOR_FIT_PSF: 7
+ * PM_SOURCE_FAIL_FIT_PSF: 4
+ * PM_SOURCE_FAINTSTAR: 4
+ * PM_SOURCE_GALAXY: 2
+ * PM_SOURCE_FAINT_GALAXY: 2
+ * PM_SOURCE_DROP_GALAXY: 2
+ * PM_SOURCE_FAIL_FIT_GAL: 2
+ * PM_SOURCE_POOR_FIT_GAL: 2
+ * PM_SOURCE_OTHER: ?
+ *
+ */
+int pmSourceDophotType(
+    pmSource *source                    ///< Add comment.
+);
+
+
+/** pmSourceSextractType()
+ *
+ * This function converts the source classification into the closest available
+ * approximation to the Sextractor classification scheme. the correspondence is
+ * not yet defined (TBD) .
+ *
+ * XXX: Must code this.
+ *
+ */
+int pmSourceSextractType(
+    pmSource *source                    ///< Add comment.
+);
+
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPSF.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPSF.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPSF.c	(revision 22157)
@@ -0,0 +1,505 @@
+/** @file  pmPSF.c
+ *
+ * This file contains typedefs for the Point-Spread Function and prototypes
+ * for functions that calculate the PSF.
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.35.18.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-07-11 18:35:04 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/*****************************************************************************/
+/* INCLUDE FILES                                                             */
+/*****************************************************************************/
+
+#include <strings.h>  // for strcasecmp
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmResiduals.h"
+#include "pmGrowthCurve.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmModelClass.h"
+#include "pmModelUtils.h"
+#include "pmSourcePhotometry.h"
+#include "pmFPAMaskWeight.h"
+#include "psVectorBracket.h"
+#include "pmErrorCodes.h"
+
+/*****************************************************************************/
+/* FUNCTION IMPLEMENTATION - PUBLIC                                          */
+/*****************************************************************************/
+
+static void pmPSFOptionsFree (pmPSFOptions *options) {
+
+    if (!options) return;
+
+    psFree (options->stats);
+    return;
+}
+
+pmPSFOptions *pmPSFOptionsAlloc () {
+
+    pmPSFOptions *options = (pmPSFOptions *) psAlloc(sizeof(pmPSFOptions));
+    psMemSetDeallocator(options, (psFreeFunc) pmPSFOptionsFree);
+
+    options->type          = 0;
+
+    options->stats         = NULL;
+
+    options->psfTrendMode  = PM_TREND_NONE;
+    options->psfTrendNx    = 0;
+    options->psfTrendNy    = 0;
+    options->psfFieldNx    = 0;
+    options->psfFieldNy    = 0;
+    options->psfFieldXo    = 0;
+    options->psfFieldYo    = 0;
+
+    options->poissonErrorsPhotLMM = true;
+    options->poissonErrorsPhotLin = false;
+    options->poissonErrorsParams  = true;
+
+    return options;
+}
+
+bool psMemCheckPSFOptions(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) pmPSFOptionsFree);
+}
+
+/*****************************************************************************
+pmPSFFree(psf): function to free a pmPSF structure
+ *****************************************************************************/
+static void pmPSFFree (pmPSF *psf)
+{
+    if (psf == NULL) {
+        return;
+    }
+
+    psFree (psf->ChiTrend);
+    psFree (psf->psfTrendStats);
+    psFree (psf->ApTrend);
+    psFree (psf->FluxScale);
+    psFree (psf->growth);
+    psFree (psf->params);
+    psFree (psf->residuals);
+    return;
+}
+
+/*****************************************************************************
+ pmPSFAlloc (type): allocate a pmPSF.
+
+ NOTE: PSF model parameters which are not modeled on an image are set to NULL
+ in psf->params.
+
+ These are normally:
+
+ X-center
+ Y-center
+ Sky background value
+ Object Normalization
+ *****************************************************************************/
+pmPSF *pmPSFAlloc (const pmPSFOptions *options)
+{
+    PS_ASSERT_PTR_NON_NULL(options, NULL);
+    int Nparams;
+
+    pmPSF *psf = (pmPSF *) psAlloc(sizeof(pmPSF));
+    psMemSetDeallocator(psf, (psFreeFunc) pmPSFFree);
+
+    psf->type     = options->type;
+    psf->chisq    = 0.0;
+    psf->ApResid  = 0.0;
+    psf->dApResid = 0.0;
+    psf->skyBias  = 0.0;
+    psf->skySat   = 0.0;
+    psf->nPSFstars  = 0;
+    psf->nApResid   = 0;
+
+    psf->poissonErrorsPhotLMM = options->poissonErrorsPhotLMM;
+    psf->poissonErrorsPhotLin = options->poissonErrorsPhotLin;
+    psf->poissonErrorsParams = options->poissonErrorsParams;
+
+    // the ApTrend components are (x, y).  It may be represented with a polynomial or with a
+    // psImageMap.  We set it initially to NULL. the user must allocate it before using.
+    // psf->ApTrend = pmTrend2DAlloc (PM_TREND_MAP, Nx, Ny, 1, 1, stats);
+    psf->ApTrend = NULL;
+
+    // the flux scale is the relationship between the integrated flux and the peak flux for a
+    // psf model.  this is a 2D function of position, and is modeled with pmTrend2D after the
+    // psf model is determined for an image.  until it is determined, the flux calculation
+    // integrates the sources
+    psf->FluxScale = NULL;
+
+    if (psf->poissonErrorsPhotLMM) {
+        psf->ChiTrend = psPolynomial1DAlloc (PS_POLYNOMIAL_ORD, 1);
+    } else {
+        psf->ChiTrend = psPolynomial1DAlloc (PS_POLYNOMIAL_ORD, 2);
+    }
+
+    // don't define a growth curve : user needs to choose radius bins
+    psf->growth = NULL;
+
+    // by default, we do not construct the residual image
+    psf->residuals = NULL;
+
+    Nparams = pmModelClassParameterCount (options->type);
+    if (!Nparams) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return(NULL);
+    }
+    psf->params = psArrayAlloc(Nparams);
+
+    // save the trend stats on the psf for use in pmPSFFromPSFtry
+    psf->psfTrendStats = psMemIncrRefCounter (options->stats);
+
+    // the psf parameters may have 2D variations represented as either a polynomial (ordinary
+    // or chebychev) or as an image map.  The size of the image map is determined by pmPSFtry
+    // by minimizing the scatter in the fitted data and the rms error in the complete image
+    // map.  In this case, the user-supplied options of psfTrendNx and psfTrendNy are the
+    // maximum value used for these axes.  For the polynomial terms, the order is not currently
+    // set, dynamically.  The value of psfTrendNx and psfTrendNy are used.
+
+    psImageBinning *binning = psImageBinningAlloc();
+    binning->nXruff = options->psfTrendNx;
+    binning->nYruff = options->psfTrendNy;
+    binning->nXfine = options->psfFieldNx;
+    binning->nYfine = options->psfFieldNy;
+
+    // for polynomial representations, nXruff, nYruff are the order number, and may be 0.
+    // in this case, we cannot set the psImageBinning scale because 0 would be invalid.  these
+    // elements are only used by the image map representation
+    if (options->psfTrendMode == PM_TREND_MAP) {
+        psImageBinningSetScale (binning, PS_IMAGE_BINNING_CENTER);
+        psImageBinningSetSkipByOffset (binning, options->psfFieldXo, options->psfFieldYo);
+    }
+
+    // trendNx & trendNy are used in pmPSFtry as the max for these values
+    psf->psfTrendMode = options->psfTrendMode;
+    psf->trendNx      = options->psfTrendNx;
+    psf->trendNy      = options->psfTrendNy;
+    psf->fieldNx      = options->psfFieldNx;
+    psf->fieldNy      = options->psfFieldNy;
+    psf->fieldXo      = options->psfFieldXo;
+    psf->fieldYo      = options->psfFieldYo;
+
+    // define the parameter trends
+    if (options->psfTrendMode != PM_TREND_NONE) {
+        for (int i = 0; i < psf->params->n; i++) {
+            if (i == PM_PAR_SKY) continue;
+            if (i == PM_PAR_I0) continue;
+            if (i == PM_PAR_XPOS) continue;
+            if (i == PM_PAR_YPOS) continue;
+
+            psf->params->data[i] = pmTrend2DNoImageAlloc (options->psfTrendMode, binning, options->stats);
+        }
+    }
+    psFree (binning);
+    return psf;
+}
+
+bool psMemCheckPSF(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) pmPSFFree);
+}
+
+// the PSF models the \sigma_{xy} variation of the elliptical contour as a function of position in the image with a
+// polynomial.  an individual object has a contour of the form (x^2/2sx^2) + (y^2/2sy^2) + sxy*x*y
+// these are the values of the model->params.  the psf->params term for sxy is actually fitted
+// to sxy/(sxx^-2 + syy^-2)^2
+
+// XXX this is only an approximate solution.  A better solution would be to fit the second moment, Mxy. the
+// problem here is that converting from Mxy,SXX,SYY -> SXY is a third order problem:
+// Mxy = SXY * (SXX^-4 + SYY^-4 - 2 SXY ^2)
+
+// input: model->param, output: psf->param[PM_PAR_SXY]
+double pmPSF_SXYfromModel (psF32 *modelPar)
+{
+    PS_ASSERT_PTR_NON_NULL(modelPar, NAN);
+
+    double SXX = modelPar[PM_PAR_SXX];
+    double SYY = modelPar[PM_PAR_SYY];
+    double SXY = modelPar[PM_PAR_SXY];
+
+    double par = SXY / PS_SQR(1.0 / PS_SQR(SXX) + 1.0 / PS_SQR(SYY));
+    return (par);
+}
+
+// input: fitted psf->param, output: model->param[PM_PAR_SXY]
+double pmPSF_SXYtoModel (psF32 *fittedPar)
+{
+    PS_ASSERT_PTR_NON_NULL(fittedPar, NAN);
+
+    double SXX = fittedPar[PM_PAR_SXX];
+    double SYY = fittedPar[PM_PAR_SYY];
+    double fit = fittedPar[PM_PAR_SXY];
+
+    double SXY = fit * PS_SQR(1.0 / PS_SQR(SXX) + 1.0 / PS_SQR(SYY));
+
+    assert (!isnan(SXY));
+
+    return SXY;
+}
+
+// New Concept: the PSF modelling function fits the polarization terms e0, e1, e2:
+
+// convert the parameters used in the fitted source model
+// to the parameters used in the 2D PSF model
+bool pmPSF_FitToModel (psF32 *fittedPar, float minMinorAxis)
+{
+    PS_ASSERT_PTR_NON_NULL(fittedPar, false);
+
+    psEllipsePol pol;
+
+    pol.e0 = fittedPar[PM_PAR_E0];
+    pol.e1 = fittedPar[PM_PAR_E1];
+    pol.e2 = fittedPar[PM_PAR_E2];
+
+    psEllipseAxes axes;
+    if (psEllipsePolToAxes (pol, minMinorAxis, &axes) != PS_ERR_NONE) {
+        psError(PM_ERR_PSF, false, "Failed to convert e[012] (%g,%g,%g) to axes",
+                pol.e0, pol.e1, pol.e2);
+        return false;
+    }
+    psEllipseShape shape = psEllipseAxesToShape (axes);
+
+    fittedPar[PM_PAR_SXX] = shape.sx * M_SQRT2;
+    fittedPar[PM_PAR_SYY] = shape.sy * M_SQRT2;
+    fittedPar[PM_PAR_SXY] = shape.sxy;
+
+    return true;
+}
+
+// convert the PSF parameters used in the 2D PSF model fit into the
+// parameters used in the source model
+psEllipsePol pmPSF_ModelToFit (psF32 *modelPar)
+{
+    // must assert non-NULL input parameter
+    psEllipsePol pol;
+    pol.e0 = NAN;
+    pol.e1 = NAN;
+    pol.e2 = NAN;
+    PS_ASSERT_PTR_NON_NULL(modelPar, pol);
+
+    psEllipseShape shape;
+
+    shape.sx  = modelPar[PM_PAR_SXX] / M_SQRT2;
+    shape.sy  = modelPar[PM_PAR_SYY] / M_SQRT2;
+    shape.sxy = modelPar[PM_PAR_SXY];
+
+    pol = psEllipseShapeToPol (shape);
+
+    return pol;
+}
+
+// convert the parameters used in the fitted source model to the psEllipseAxes representation
+// (major,minor,theta)
+psEllipseAxes pmPSF_ModelToAxes (psF32 *modelPar, double maxAR)
+{
+    psEllipseShape shape;
+    psEllipseAxes axes;
+    axes.major = NAN;
+    axes.minor = NAN;
+    axes.theta = NAN;
+//   XXX: must assert non-NULL input parameter
+    PS_ASSERT_PTR_NON_NULL(modelPar, axes);
+
+    shape.sx  = modelPar[PM_PAR_SXX] / M_SQRT2;
+    shape.sy  = modelPar[PM_PAR_SYY] / M_SQRT2;
+    shape.sxy = modelPar[PM_PAR_SXY];
+
+    if ((shape.sx == 0) || (shape.sy == 0)) {
+        axes.major = 0.0;
+        axes.minor = 0.0;
+        axes.theta = 0.0;
+    } else {
+        // XXX this is not really consistent with the model fit range above
+        axes = psEllipseShapeToAxes (shape, maxAR);
+    }
+
+    return axes;
+}
+
+// convert the psEllipseAxes representation (major,minor,theta) to the parameters used in the
+// fitted source model
+bool pmPSF_AxesToModel (psF32 *modelPar, psEllipseAxes axes)
+{
+    PS_ASSERT_PTR_NON_NULL(modelPar, false);
+
+    if ((axes.major <= 0) || (axes.minor <= 0)) {
+        modelPar[PM_PAR_SXX] = 0.0;
+        modelPar[PM_PAR_SYY] = 0.0;
+        modelPar[PM_PAR_SXY] = 0.0;
+        return true;
+    }
+
+    psEllipseShape shape = psEllipseAxesToShape (axes);
+
+    modelPar[PM_PAR_SXX] = shape.sx * M_SQRT2;
+    modelPar[PM_PAR_SYY] = shape.sy * M_SQRT2;
+    modelPar[PM_PAR_SXY] = shape.sxy;
+
+    return true;
+}
+
+// generate a psf model of the requested type, with fixed shape
+pmPSF *pmPSFBuildSimple (char *typeName, float sxx, float syy, float sxy, ...)
+{
+
+    va_list ap;
+    va_start(ap, sxy);
+
+    pmPSFOptions *options = pmPSFOptionsAlloc ();
+    options->type = pmModelClassGetType (typeName);
+    options->psfTrendMode = PM_TREND_POLY_ORD;
+    options->psfTrendNx = 0;
+    options->psfTrendNy = 0;
+
+    pmPSF *psf = pmPSFAlloc (options);
+
+    psVector *par = psVectorAlloc (psf->params->n, PS_TYPE_F32);
+    par->data.F32[PM_PAR_SXX] = sxx;
+    par->data.F32[PM_PAR_SYY] = syy;
+    par->data.F32[PM_PAR_SXY] = sxy;
+
+    psEllipsePol pol = pmPSF_ModelToFit(par->data.F32);
+
+    pmTrend2D *trend = NULL;
+
+    // set the psf shape parameters
+    trend = psf->params->data[PM_PAR_E0];
+    trend->poly->coeff[0][0] = pol.e0;
+
+    trend = psf->params->data[PM_PAR_E1];
+    trend->poly->coeff[0][0] = pol.e1;
+
+    trend = psf->params->data[PM_PAR_E2];
+    trend->poly->coeff[0][0] = pol.e2;
+
+    for (int i = PM_PAR_SXY + 1; i < psf->params->n; i++) {
+        trend = psf->params->data[i];
+        trend->poly->coeff[0][0] = (psF32)va_arg(ap, psF64);
+    }
+    va_end(ap);
+
+    psFree (par);
+    psFree (options);
+    return psf;
+}
+
+// we generate the growth curve for the center of the image with the specified psf model
+bool pmGrowthCurveGenerate (pmReadout *readout, pmPSF *psf, bool ignore, psMaskType maskVal, psMaskType markVal)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(readout->image, false);
+
+    // bool status;
+    float xc, yc, dx, dy;
+    float fitMag, apMag;
+    float radius;
+
+    // create template model
+    pmModel *modelRef = pmModelAlloc(psf->type);
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // use the center of the center pixel of the image
+    xc = 0.5*readout->image->numCols + readout->image->col0 + 0.5;
+    yc = 0.5*readout->image->numRows + readout->image->row0 + 0.5;
+    dx = psf->growth->maxRadius + 1;
+    dy = psf->growth->maxRadius + 1;
+
+    // assign the x and y coords to the image center
+    // create an object with center intensity of 1000
+    modelRef->params->data.F32[PM_PAR_SKY] = 0;
+    modelRef->params->data.F32[PM_PAR_I0] = 1000;
+    modelRef->params->data.F32[PM_PAR_XPOS] = xc;
+    modelRef->params->data.F32[PM_PAR_YPOS] = yc;
+
+    // create modelPSF from this model
+    pmModel *model = pmModelFromPSF (modelRef, psf);
+
+    // measure the fitMag for this model
+    pmSourcePhotometryModel (&fitMag, model);
+    psf->growth->fitMag = fitMag;
+
+    // generate working image for this source
+    psRegion region = {xc - dx, xc + dx, yc - dy, yc + dy};
+
+    // force region to stop at dimensions of image
+    region = psRegionForImage (readout->image, region);
+
+    // the view, image, and mask retain col0,row0
+    psImage *view = psImageSubset (readout->image, region);
+    psImage *image = psImageCopy (NULL, view, PS_TYPE_F32);
+    psImage *mask = psImageCopy (NULL, view, PS_TYPE_U8);
+
+    psImageInit (image, 0.0);
+    psImageInit (mask, 0);
+
+    // place the reference object in the image center
+    // no need to mask the source here
+    // XXX should we measure this for the analytical model only or the full model?
+    pmModelAdd (image, NULL, model, PM_MODEL_OP_FULL, maskVal);
+
+    // loop over a range of source fluxes
+    // no need to interpolate since we have forced the object center
+    // to 0.5, 0.5 above
+    bool completeGrowthCurve = true;            // do we have a complete curve of growth?
+    for (int i = 0; i < psf->growth->radius->n; i++) {
+
+        radius = psf->growth->radius->data.F32[i];
+
+        // mask the given aperture and measure the apMag
+        psImageKeepCircle (mask, xc, yc, radius, "OR", markVal);
+        if (!pmSourcePhotometryAper (&apMag, model, image, mask, maskVal)) {
+            psError(PM_ERR_PHOTOM, false, "Measuring apMag for radius == %g", radius);
+            psErrorStackPrint (stderr, "failure to get growth curve");
+            psErrorClear();
+            completeGrowthCurve = false;
+            break;
+        }
+        psImageKeepCircle (mask, xc, yc, radius, "AND", PS_NOT_U8(markVal));
+
+        // the 'ignore' mode is for testing
+        if (ignore) {
+            psf->growth->apMag->data.F32[i] = fitMag;
+        } else {
+            psf->growth->apMag->data.F32[i] = apMag;
+        }
+    }
+
+    if (completeGrowthCurve) {
+        psf->growth->apRef = psVectorInterpolate (psf->growth->radius, psf->growth->apMag, psf->growth->refRadius);
+        psf->growth->apLoss = psf->growth->fitMag - psf->growth->apRef;
+    } else {
+        psf->growth->apRef = NAN;
+        psf->growth->apLoss = 0;
+    }
+
+    psLogMsg ("psphot.growth", 4, "GrowthCurve : apLoss : %f\n", psf->growth->apLoss);
+
+    psFree (view);
+    psFree (image);
+    psFree (mask);
+    psFree (model);
+    psFree (modelRef);
+
+    return completeGrowthCurve ? true : false;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPSF.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPSF.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPSF.h	(revision 22157)
@@ -0,0 +1,108 @@
+/* @file  pmPSF.h
+ *
+ * This file contains typedefs for the Point-Spread Function and prototypes
+ * for functions that calculate the PSF.
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.20 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-11-27 03:14:57 $
+ * Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+# ifndef PM_PSF_H
+# define PM_PSF_H
+
+/// @addtogroup Objects Object Detection / Analysis Functions
+/// @{
+
+// type of model carried by the pmModel structure
+typedef int pmModelType;
+
+/** pmPSF data structure
+ *
+ * It is useful to generate a model to define the point-spread-function which
+ * describes the flux distribution for unresolved sources in an image. In
+ * general, the PSF varies with position in the image. We allow any of the source
+ * models defined for the pmModel to represent the PSF. For a given source model,
+ * the 2D spatial variation of all of the source parameters, except the first
+ * four PSF-independent parameters, are represented as polynomial, stored in a
+ * psArray. The other elements of the structure define the quality of the PSF
+ * determination.
+ *
+ */
+typedef struct
+{
+    pmModelType type;                   ///< PSF Model in use
+    psArray *params;                    ///< Model parameters (psPolynomial2D)
+    psStats *psfTrendStats;             ///< psf parameter trend clipping stats
+    pmTrend2DMode psfTrendMode;
+    psPolynomial1D *ChiTrend;           ///< Chisq vs flux fit (correction for systematic errors)
+    pmTrend2D *ApTrend;                 ///< ApResid vs (x,y)
+    pmTrend2D *FluxScale;               ///< Flux for PSF at (x,y) for normalization = 1.0
+    float ApResid;                      ///< apMag - psfMag (for PSF stars)
+    float dApResid;                     ///< scatter of ApResid
+    float skyBias;                      ///< implied residual sky offset from ApResid fit
+    float skySat;                       ///< roll-over of ApResid fit
+    float chisq;                        ///< PSF goodness statistic (unused??)
+    int nPSFstars;                      ///< number of stars used to measure PSF
+    int nApResid;                       ///< number of stars used to measure ApResid
+    int trendNx;
+    int trendNy;
+    int fieldNx;
+    int fieldNy;
+    int fieldXo;
+    int fieldYo;
+    bool poissonErrorsPhotLMM;          ///< use poission errors for non-linear model fitting
+    bool poissonErrorsPhotLin;          ///< use poission errors for linear model fitting
+    bool poissonErrorsParams;           ///< use poission errors for model parameter fitting
+    pmGrowthCurve *growth;              ///< apMag vs Radius
+    pmResiduals *residuals;             ///< normalized residual image (no spatial variation)
+}
+pmPSF;
+
+typedef struct {
+    pmModelType   type;
+    psStats      *stats;                // psfTrend clipping stats
+    pmTrend2DMode psfTrendMode;
+    int           psfTrendNx;
+    int           psfTrendNy;
+    int           psfFieldNx;
+    int           psfFieldNy;
+    int           psfFieldXo;
+    int           psfFieldYo;
+    bool          poissonErrorsPhotLMM; ///< use poission errors for non-linear model fitting
+    bool          poissonErrorsPhotLin; ///< use poission errors for linear model fitting
+    bool          poissonErrorsParams; ///< use poission errors for model parameter fitting
+    float         radius;
+} pmPSFOptions;
+
+# define PM_PAR_E0 PM_PAR_SXX
+# define PM_PAR_E1 PM_PAR_SYY
+# define PM_PAR_E2 PM_PAR_SXY
+
+/**
+ *
+ * Allocator for the pmPSF structure.
+ *
+ */
+
+pmPSF *pmPSFAlloc (const pmPSFOptions *options);
+bool psMemCheckPSF(psPtr ptr);
+pmPSFOptions *pmPSFOptionsAlloc();
+bool psMemCheckPSFOptions(psPtr ptr);
+
+double pmPSF_SXYfromModel (psF32 *modelPar);
+double pmPSF_SXYtoModel (psF32 *fittedPar);
+
+bool pmGrowthCurveGenerate (pmReadout *readout, pmPSF *psf, bool ignore, psMaskType maskVal, psMaskType mark);
+pmPSF *pmPSFBuildSimple (char *typeName, float sxx, float syy, float sxy, ...);
+
+bool pmPSF_AxesToModel (psF32 *modelPar, psEllipseAxes axes);
+bool pmPSF_FitToModel (psF32 *fittedPar, float minMinorAxis);
+
+psEllipsePol pmPSF_ModelToFit (psF32 *modelPar);
+psEllipseAxes pmPSF_ModelToAxes (psF32 *modelPar, double maxAR);
+
+/// @}
+# endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPSF_IO.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPSF_IO.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPSF_IO.c	(revision 22157)
@@ -0,0 +1,818 @@
+/** @file  pmPSF_IO.c
+ *
+ * This file contains functions to read and write PSF models using the psMetadata Config file
+ * format.
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.32 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-06-09 00:43:04 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/*****************************************************************************/
+/* INCLUDE FILES                                                             */
+/*****************************************************************************/
+
+#include <string.h>
+#include <strings.h>            /* for strn?casecmp */
+
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmGrowthCurve.h"
+#include "pmResiduals.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmPSF_IO.h"
+#include "pmSource.h"
+#include "pmModelClass.h"
+#include "pmSourceIO.h"
+
+bool pmPSFmodelCheckDataStatusForView (const pmFPAview *view, const pmFPAfile *file)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(file->fpa, false);
+    PS_ASSERT_PTR_NON_NULL(file->fpa->chips, false);
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        bool exists = pmPSFmodelCheckDataStatusForFPA (fpa);
+        return exists;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        psError(PS_ERR_IO, true, "Requested chip == %d >= fpa->chips->n == %ld", view->chip, fpa->chips->n);
+        return false;
+    }
+
+    pmChip *chip = fpa->chips->data[view->chip];
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(chip->cells, false);
+
+    if (view->cell == -1) {
+        bool exists = pmPSFmodelCheckDataStatusForChip (chip);
+        return exists;
+    }
+
+    if (view->cell >= chip->cells->n) {
+        psError(PS_ERR_IO, true, "Requested cell == %d >= chip->cells->n == %ld", view->cell, chip->cells->n);
+        return false;
+    }
+
+    psError(PS_ERR_IO, false, "PSF only valid at the chip level");
+    return false;
+}
+
+bool pmPSFmodelCheckDataStatusForFPA (const pmFPA *fpa) {
+
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(fpa->chips, false);
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        if (!chip) continue;
+        if (pmPSFmodelCheckDataStatusForChip (chip)) return true;
+    }
+    return false;
+}
+
+bool pmPSFmodelCheckDataStatusForChip (const pmChip *chip) {
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+
+    bool status;
+
+    // select the psf of interest
+    pmPSF *psf = psMetadataLookupPtr(&status, chip->analysis, "PSPHOT.PSF");
+    return psf ? true : false;
+}
+
+bool pmPSFmodelWriteForView (const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(file->fpa, false);
+    PS_ASSERT_PTR_NON_NULL(file->fpa->chips, false);
+
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        if (!pmPSFmodelWriteFPA(fpa, view, file, config)) {
+            psError(PS_ERR_IO, false, "Failed to write PSF for fpa");
+            return false;
+        }
+        return true;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        if (!pmPSFmodelWriteChip (chip, view, file, config)) {
+            psError(PS_ERR_IO, false, "Failed to write PSF for chip");
+            return false;
+        }
+        return true;
+    }
+
+    psError(PS_ERR_IO, false, "PSF must be written at the chip level");
+    return false;
+}
+
+// read in all chip-level PSFmodel files for this FPA
+bool pmPSFmodelWriteFPA (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(fpa->chips, false);
+    pmFPAview *thisView = pmFPAviewAlloc (view->nRows);
+    *thisView = *view;
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        thisView->chip = i;
+        if (!pmPSFmodelWriteChip (chip, thisView, file, config)) {
+            psError(PS_ERR_IO, false, "Failed to write PSF for %dth chip", i);
+            psFree(thisView);
+            return false;
+        }
+    }
+    psFree(thisView);
+    return true;
+}
+
+// read in all cell-level PSFmodel files for this chip
+bool pmPSFmodelWriteChip (pmChip *chip, const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+
+    if (!pmPSFmodelWrite (chip->analysis, view, file, config)) {
+        psError(PS_ERR_IO, false, "Failed to write PSF for chip");
+        return false;
+    }
+    return true;
+}
+
+// for a pmPSF supplied on the analysis metadata, we write out
+// if needed:
+//   - a PHU blank header
+// - image header        : FITS Image NAXIS = 0
+// if (trendMode == MAP)
+//   - psf resid (+header) : FITS Image
+// else
+//   - psf table (+header) : FITS Table
+bool pmPSFmodelWrite (psMetadata *analysis, const pmFPAview *view,
+                      pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(file->fpa, false);
+    bool status;
+    char *headName, *tableName, *residName;
+
+    if (!analysis) return false;
+
+    // select the current recipe
+    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, "PSPHOT");
+    if (!recipe) {
+        psError(PS_ERR_UNKNOWN, false, "missing recipe %s", "PSPHOT");
+        return false;
+    }
+
+    // write a PHU? (only if input image is MEF)
+    // write a header? (only if this is the first readout for cell)
+    //   note that the file->header is set to track the last hdu->header written
+    // write the data? (always?)
+
+    // get the current header
+    pmHDU *hdu = pmFPAviewThisHDU (view, file->fpa);
+    if (!hdu) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to find HDU");
+        return false;
+    }
+
+    // if file does not yet have a PHU, attempt to write one to disk
+    // we only need a PHU if chips->n > 1 and file->fileLevel == FPA
+    // otherwise, the chip header fills the PHU location
+    // XXX this code could be placed in a 'pmPSF_WritePHU' function and called
+    // from pmFPAfileIO.c.
+
+    // define the EXTNAME values used for image header, table data, and residual image segments
+    {
+        // lookup the EXTNAME values used for table data and image header segments
+        char *rule = NULL;
+
+        // Menu of EXTNAME rules
+        psMetadata *menu = psMetadataLookupMetadata(&status, file->camera, "EXTNAME.RULES");
+        if (!menu) {
+            psError(PS_ERR_UNKNOWN, true, "missing EXTNAME.RULES in camera.config");
+            return false;
+        }
+
+        // EXTNAME for image header
+        rule = psMetadataLookupStr(&status, menu, "PSF.HEAD");
+        if (!rule) {
+            psError(PS_ERR_UNKNOWN, false, "missing entry for PSF.HEAD in EXTNAME.RULES in camera.config");
+            return false;
+        }
+        headName = pmFPAfileNameFromRule (rule, file, view);
+
+        // EXTNAME for table data
+        rule = psMetadataLookupStr(&status, menu, "PSF.TABLE");
+        if (!rule) {
+            psError(PS_ERR_UNKNOWN, false, "missing entry for PSF.TABLE in EXTNAME.RULES in camera.config");
+            psFree (headName);
+            return false;
+        }
+        tableName = pmFPAfileNameFromRule (rule, file, view);
+
+        // EXTNAME for resid data
+        rule = psMetadataLookupStr(&status, menu, "PSF.RESID");
+        if (!rule) {
+            psError(PS_ERR_UNKNOWN, false, "missing entry for PSF.RESID in EXTNAME.RULES in camera.config");
+            psFree (headName);
+            psFree (tableName);
+            return false;
+        }
+        residName = pmFPAfileNameFromRule (rule, file, view);
+    }
+
+    // write out the IMAGE header segment
+    // if this header block is new, write it to disk
+    if (hdu->header != file->header) {
+        // add EXTNAME, EXTHEAD, EXTTYPE to header
+        psMetadataAddStr (hdu->header, PS_LIST_TAIL, "EXTTABLE", PS_META_REPLACE, "name of table extension", tableName);
+        psMetadataAddStr (hdu->header, PS_LIST_TAIL, "EXTRESID", PS_META_REPLACE, "name of resid extension", residName);
+        psMetadataAddStr (hdu->header, PS_LIST_TAIL, "EXTTYPE", PS_META_REPLACE, "extension type", "IMAGE");
+        if (!file->wrote_phu) {
+            // this hdu->header acts as the PHU: set EXTEND to be true
+            psMetadataAddBool (hdu->header, PS_LIST_TAIL, "EXTEND", PS_META_REPLACE, "this file has extensions", true);
+            file->wrote_phu = true;
+        }
+
+        psFitsWriteBlank (file->fits, hdu->header, headName);
+        psTrace ("pmFPAfile", 5, "wrote ext head %s (type: %d)\n", file->filename, file->type);
+        file->header = hdu->header;
+        psFree (headName);
+    }
+
+    // select the psf of interest
+    pmPSF *psf = psMetadataLookupPtr (&status, analysis, "PSPHOT.PSF");
+    if (!psf) {
+        psError(PS_ERR_UNKNOWN, true, "missing PSF for this analysis metadata");
+        psFree (tableName);
+        psFree (residName);
+        return false;
+    }
+
+    // write the PSF model parameters in a FITS table
+    {
+        // we need to write a header for the table,
+        psMetadata *header = psMetadataAlloc();
+
+        char *modelName = pmModelClassGetName (psf->type);
+        psMetadataAddStr (header, PS_LIST_TAIL, "PSF_NAME", 0, "PSF model name", modelName);
+
+        psMetadataAddBool (header, PS_LIST_TAIL, "ERR_LMM",  0, "Use Poisson errors in fits?", psf->poissonErrorsPhotLMM);
+        psMetadataAddBool (header, PS_LIST_TAIL, "ERR_LIN",  0, "Use Poisson errors in fits?", psf->poissonErrorsPhotLin);
+        psMetadataAddBool (header, PS_LIST_TAIL, "ERR_PAR",  0, "Use Poisson errors in fits?", psf->poissonErrorsParams);
+
+        int nPar = pmModelClassParameterCount (psf->type)    ;
+        psMetadataAdd (header, PS_LIST_TAIL, "PSF_NPAR", PS_DATA_S32, "PSF model parameter count", nPar);
+
+        psMetadataAddS32 (header, PS_LIST_TAIL, "IMAXIS1", 0, "Image X Size", psf->fieldNx);
+        psMetadataAddS32 (header, PS_LIST_TAIL, "IMAXIS2", 0, "Image Y Size", psf->fieldNy);
+        psMetadataAddS32 (header, PS_LIST_TAIL, "IMREF1",  0, "Image X Ref",  psf->fieldXo);
+        psMetadataAddS32 (header, PS_LIST_TAIL, "IMREF2",  0, "Image Y Ref",  psf->fieldYo);
+
+        // extract PSF Clump info
+        pmPSFClump psfClump;
+
+        psfClump.X  = psMetadataLookupF32 (&status, recipe, "PSF.CLUMP.X");   assert (status);
+        psfClump.Y  = psMetadataLookupF32 (&status, recipe, "PSF.CLUMP.Y");   assert (status);
+        psfClump.dX = psMetadataLookupF32 (&status, recipe, "PSF.CLUMP.DX");  assert (status);
+        psfClump.dY = psMetadataLookupF32 (&status, recipe, "PSF.CLUMP.DY");  assert (status);
+
+        psMetadataAddF32 (header, PS_LIST_TAIL, "PSF_CLX", 0, "psf clump center", psfClump.X);
+        psMetadataAddF32 (header, PS_LIST_TAIL, "PSF_CLY", 0, "psf clump center", psfClump.Y);
+        psMetadataAddF32 (header, PS_LIST_TAIL, "PSF_CLDX", 0, "psf clump size", psfClump.dX);
+        psMetadataAddF32 (header, PS_LIST_TAIL, "PSF_CLDY", 0, "psf clump size", psfClump.dY);
+
+        // save the dimensions of each parameter
+        for (int i = 0; i < nPar; i++) {
+            char name[9];
+            int nX, nY;
+
+            pmTrend2D *trend = psf->params->data[i];
+            if (trend == NULL) continue;
+
+            if (trend->mode == PM_TREND_MAP) {
+              nX = trend->map->map->numCols;
+              nY = trend->map->map->numRows;
+            } else {
+              nX = trend->poly->nX;
+              nY = trend->poly->nY;
+            }
+            snprintf (name, 9, "PAR%02d_NX", i);
+            psMetadataAddS32 (header, PS_LIST_TAIL, name, 0, "", nX);
+            snprintf (name, 9, "PAR%02d_NY", i);
+            psMetadataAddS32 (header, PS_LIST_TAIL, name, 0, "", nY);
+            snprintf (name, 9, "PAR%02d_MD", i);
+            char *modeName = pmTrend2DModeToString (trend->mode);
+            psMetadataAddStr (header, PS_LIST_TAIL, name, 0, "", modeName);
+            psFree (modeName);
+        }
+
+        // other required information describing the PSF
+        psMetadataAddF32 (header, PS_LIST_TAIL, "AP_RESID", 0, "aperture residual", psf->ApResid);
+        psMetadataAddF32 (header, PS_LIST_TAIL, "AP_ERROR", 0, "aperture residual scatter", psf->dApResid);
+        psMetadataAddF32 (header, PS_LIST_TAIL, "CHISQ",    0, "chi-square for fit", psf->chisq);
+        psMetadataAddS32 (header, PS_LIST_TAIL, "NSTARS",   0, "number of stars used to measure PSF", psf->nPSFstars);
+
+        // XXX can we drop this now?
+        psMetadataAddF32 (header, PS_LIST_TAIL, "SKY_BIAS", PS_DATA_F32, "sky bias level", psf->skyBias);
+
+        // build a FITS table of the PSF parameters
+        psArray *psfTable = psArrayAllocEmpty (100);
+        for (int i = 0; i < nPar; i++) {
+            pmTrend2D *trend = psf->params->data[i];
+            if (trend == NULL) continue; // skip unset parameters (eg, XPOS)
+
+            if (trend->mode == PM_TREND_MAP) {
+                // write the image components into a table: this is needed because they may each be a different size
+                psImageMap *map = trend->map;
+                for (int ix = 0; ix < map->map->numCols; ix++) {
+                    for (int iy = 0; iy < map->map->numRows; iy++) {
+                        psMetadata *row = psMetadataAlloc ();
+                        psMetadataAddS32 (row, PS_LIST_TAIL, "MODEL_TERM", 0, "", i);
+                        psMetadataAddS32 (row, PS_LIST_TAIL, "X_POWER",    0, "", ix);
+                        psMetadataAddS32 (row, PS_LIST_TAIL, "Y_POWER",    0, "", iy);
+                        psMetadataAddF32 (row, PS_LIST_TAIL, "VALUE",      0, "", map->map->data.F32[iy][ix]);
+                        psMetadataAddF32 (row, PS_LIST_TAIL, "ERROR",      0, "", map->error->data.F32[iy][ix]);
+                        psMetadataAddU8  (row, PS_LIST_TAIL, "MASK",       0, "", 0); // no cells are masked
+
+                        psArrayAdd (psfTable, 100, row);
+                        psFree (row);
+                    }
+                }
+            } else {
+                // write the polynomial components into a table
+                psPolynomial2D *poly = trend->poly;
+                for (int ix = 0; ix <= poly->nX; ix++) {
+                    for (int iy = 0; iy <= poly->nY; iy++) {
+                        psMetadata *row = psMetadataAlloc ();
+                        psMetadataAddS32 (row, PS_LIST_TAIL, "MODEL_TERM", 0, "", i);
+                        psMetadataAddS32 (row, PS_LIST_TAIL, "X_POWER",    0, "", ix);
+                        psMetadataAddS32 (row, PS_LIST_TAIL, "Y_POWER",    0, "", iy);
+                        psMetadataAddF32 (row, PS_LIST_TAIL, "VALUE",      0, "", poly->coeff[ix][iy]);
+                        psMetadataAddF32 (row, PS_LIST_TAIL, "ERROR",      0, "", poly->coeffErr[ix][iy]);
+                        psMetadataAddU8  (row, PS_LIST_TAIL, "MASK",       0, "", poly->coeffMask[ix][iy]);
+
+                        psArrayAdd (psfTable, 100, row);
+                        psFree (row);
+                    }
+                }
+            }
+        }
+
+        // write an empty FITS segment if we have no PSF information
+        if (psfTable->n == 0) {
+            // XXX this is probably an error (if we have a PSF, how do we have no data?)
+            psFitsWriteBlank (file->fits, header, tableName);
+        } else {
+            psTrace ("pmFPAfile", 5, "writing psf data %s\n", tableName);
+            if (!psFitsWriteTable (file->fits, header, psfTable, tableName)) {
+                psError(PS_ERR_IO, false, "writing psf table data %s\n", tableName);
+                psFree (tableName);
+                psFree (residName);
+                psFree (psfTable);
+                psFree (header);
+                return false;
+            }
+        }
+        psFree (tableName);
+        psFree (psfTable);
+        psFree (header);
+    }
+
+    // write the residual images (3D)
+    {
+        psMetadata *header = psMetadataAlloc ();
+        if (psf->residuals == NULL) {
+            // set some header keywords to make it clear there are no residuals?
+            psFitsWriteBlank (file->fits, header, residName);
+            psFree (residName);
+            psFree (header);
+            return true;
+        }
+
+        psMetadataAddS32 (header, PS_LIST_TAIL, "XBIN",    0, "", psf->residuals->xBin);
+        psMetadataAddS32 (header, PS_LIST_TAIL, "YBIN",    0, "", psf->residuals->yBin);
+        psMetadataAddS32 (header, PS_LIST_TAIL, "XCENTER", 0, "", psf->residuals->xCenter);
+        psMetadataAddS32 (header, PS_LIST_TAIL, "YCENTER", 0, "", psf->residuals->yCenter);
+
+        // write the residuals as three planes of the image
+        // this call creates an extension with NAXIS3 = 3
+        if (psf->residuals->Rx) {
+            // this call creates an extension with NAXIS3 = 3
+            psArray *images = psArrayAllocEmpty (3);
+            psArrayAdd (images, 1, psf->residuals->Ro);
+            psArrayAdd (images, 1, psf->residuals->Rx);
+            psArrayAdd (images, 1, psf->residuals->Ry);
+
+            psFitsWriteImageCube (file->fits, header, images, residName);
+            psFree (images);
+        } else {
+            // this call creates an extension with NAXIS3 = 1
+            psFitsWriteImage(file->fits, header, psf->residuals->Ro, 0, residName);
+        }
+        psFree (residName);
+        psFree (header);
+    }
+
+    return true;
+
+    // XXX save the growth curve
+    // XXX save ApTrend (as image?)
+    // XXX write the ApTrend with the same API as will be used for the PSF parameters above
+
+# if (0)
+    // build a FITS table of the fit to the Aperture Residuals
+    psArray *apresTable = psArrayAllocEmpty (100);
+    psPolynomial4D *poly = psf->ApTrend;
+    for (int ix = 0; ix < poly->nX; ix++) {
+        for (int iy = 0; iy < poly->nY; iy++) {
+
+            row = psMetadataAlloc ();
+            psMetadataAddS32 (row, PS_LIST_TAIL, "X_POWER",    0, "", ix);
+            psMetadataAddS32 (row, PS_LIST_TAIL, "Y_POWER",    0, "", iy);
+            psMetadataAddF32 (row, PS_LIST_TAIL, "VALUE",      0, "", poly->coeff[ix][iy]);
+            psMetadataAddF32 (row, PS_LIST_TAIL, "ERROR",      0, "", poly->coeffErr[ix][iy]);
+            psMetadataAddU8  (row, PS_LIST_TAIL, "MASK",       0, "", poly->mask[ix][iy]);
+
+            psArrayAdd (psfTable, 100, row);
+            psFree (row);
+        }
+    }
+# endif
+}
+
+// if this file needs to have a PHU written out, write one
+bool pmPSFmodelWritePHU (const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(file->fpa, false);
+    // not needed if already written
+    if (file->wrote_phu) return true;
+
+    // not needed if not FPA
+    // XXX this prevents us from defining a SPLIT/MEF CMF file...
+    if (file->fileLevel != PM_FPA_LEVEL_FPA) return true;
+
+    // not needed if only one chip
+    if (file->fpa->chips->n == 1) return true;
+
+    // find the FPA phu
+    pmHDU *phu = pmFPAviewThisPHU (view, file->fpa);
+
+    // if there is no PHU, this is a single header+image (extension-less) file. This could be
+    // the case for an input SPLIT set of files being written out as a MEF.  if there is a PHU,
+    // write it out as a 'blank'
+    psMetadata *outhead = psMetadataAlloc();
+    if (phu) {
+        psMetadataCopy (outhead, phu->header);
+    } else {
+        pmConfigConformHeader (outhead, file->format);
+
+# if (0)
+	// XXX this is no longer needed : FPA.OBS is now correctly copied in pmConfigConformHeader
+        psMetadata *fileData = psMetadataLookupMetadata(NULL, file->format, "FILE"); // File information
+        const char *fpaNameHdr = psMetadataLookupStr(NULL, fileData, "FPA.NAME");
+        if (fpaNameHdr && strlen(fpaNameHdr) > 0) {
+            const char *fpaName = psMetadataLookupStr(NULL, file->fpa->concepts, "FPA.NAME");
+            psMetadataAddStr(outhead, PS_LIST_TAIL, fpaNameHdr, PS_META_REPLACE, "FPA name", fpaName);
+        }
+# endif
+    }
+
+    psMetadataAddBool (outhead, PS_LIST_TAIL, "EXTEND", PS_META_REPLACE, "this file has extensions", true);
+    psFitsWriteBlank (file->fits, outhead, "");
+    file->wrote_phu = true;
+
+    psTrace ("pmFPAfile", 5, "wrote phu %s (type: %d)\n", file->filename, file->type);
+    psFree (outhead);
+
+    return true;
+}
+
+bool pmPSFmodelReadForView (const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(file->fpa, false);
+
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        return pmPSFmodelReadFPA(fpa, view, file, config);
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        psAbort("Programming error: view does not apply to FPA.");
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        return pmPSFmodelReadChip(chip, view, file, config);
+    }
+
+    psError(PS_ERR_IO, false, "PSF must be read at the chip level");
+    return false;
+}
+
+// read in all chip-level PSFmodel files for this FPA
+bool pmPSFmodelReadFPA (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(file->fpa, false);
+
+    bool success = true;                // Was everything successful?
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        success &= pmPSFmodelReadChip(chip, view, file, config);
+    }
+    return success;
+}
+
+// read in all cell-level PSFmodel files for this chip
+bool pmPSFmodelReadChip (pmChip *chip, const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(file->fpa, false);
+
+    if (!pmPSFmodelRead (chip->analysis, view, file, config)) {
+        psError(PS_ERR_IO, false, "Failed to write PSF for chip");
+        return false;
+    }
+    return true;
+}
+
+// for each Readout (ie, analysed image), we write out: header + table with PSF model parameters,
+// and header + image for the PSF residual images
+bool pmPSFmodelRead (psMetadata *analysis, const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(file->fpa, false);
+
+    bool status;
+    char *rule = NULL;
+    psMetadata *header = NULL;
+
+    psTrace ("psModules.objects", 5, "read psf model for %s\n", file->filename);
+
+    // select the current recipe
+    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, "PSPHOT");
+    if (!recipe) {
+        psError(PS_ERR_UNKNOWN, false, "missing recipe %s", "PSPHOT");
+        return false;
+    }
+
+    // Menu of EXTNAME rules
+    psMetadata *menu = psMetadataLookupMetadata(&status, file->camera, "EXTNAME.RULES");
+    if (!menu) {
+        psError(PS_ERR_UNKNOWN, true, "missing EXTNAME.RULES in camera.config");
+        return false;
+    }
+    // EXTNAME for table data
+    rule = psMetadataLookupStr(&status, menu, "PSF.TABLE");
+    if (!rule) {
+        psError(PS_ERR_UNKNOWN, true, "missing entry for PSF.TABLE in EXTNAME.RULES in camera.config");
+        return false;
+    }
+    char *tableName = pmFPAfileNameFromRule (rule, file, view);
+    // EXTNAME for residual images
+    rule = psMetadataLookupStr(&status, menu, "PSF.RESID");
+    if (!rule) {
+        psError(PS_ERR_UNKNOWN, true, "missing entry for PSF.RESID in EXTNAME.RULES in camera.config");
+        return false;
+    }
+    char *imageName = pmFPAfileNameFromRule (rule, file, view);
+
+    // move fits pointer to table and read header
+    // advance to the table data extension
+    // since we have read the IMAGE header, the TABLE header should exist
+    if (!psFitsMoveExtName (file->fits, tableName)) {
+        psAbort("cannot find data extension %s in %s", tableName, file->filename);
+    }
+
+    // load the PSF model table header
+    header = psFitsReadHeader (NULL, file->fits);
+    if (!header) psAbort("cannot read table header");
+
+    pmPSFOptions *options = pmPSFOptionsAlloc();
+
+    // load the PSF model parameters from the FITS table
+    char *modelName = psMetadataLookupStr (&status, header, "PSF_NAME");
+    options->type = pmModelClassGetType (modelName);
+    if (options->type == -1) {
+        psError(PS_ERR_UNKNOWN, true, "invalid model name %s in psf file %s", modelName, file->filename);
+        return false;
+    }
+
+    // psf clump data
+    pmPSFClump psfClump;
+
+    psfClump.X  = psMetadataLookupF32 (&status, header, "PSF_CLX" );  assert(status);
+    psfClump.Y  = psMetadataLookupF32 (&status, header, "PSF_CLY" );  assert(status);
+    psfClump.dX = psMetadataLookupF32 (&status, header, "PSF_CLDX");  assert(status);
+    psfClump.dY = psMetadataLookupF32 (&status, header, "PSF_CLDY");  assert(status);
+
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "PSF.CLUMP.X" , PS_META_REPLACE, "psf clump center", psfClump.X);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "PSF.CLUMP.Y" , PS_META_REPLACE, "psf clump center", psfClump.Y);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "PSF.CLUMP.DX", PS_META_REPLACE, "psf clump size",   psfClump.dX);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "PSF.CLUMP.DY", PS_META_REPLACE, "psf clump size",   psfClump.dY);
+
+    options->poissonErrorsPhotLMM = psMetadataLookupBool (&status, header, "ERR_LMM");
+    options->poissonErrorsPhotLin = psMetadataLookupBool (&status, header, "ERR_LIN");
+    options->poissonErrorsParams  = psMetadataLookupBool (&status, header, "ERR_PAR");
+
+    options->psfFieldNx = psMetadataLookupS32 (&status, header, "IMAXIS1");
+    options->psfFieldNy = psMetadataLookupS32 (&status, header, "IMAXIS2");
+    options->psfFieldXo = psMetadataLookupS32 (&status, header, "IMREF1");
+    options->psfFieldYo = psMetadataLookupS32 (&status, header, "IMREF2");
+
+    psImageBinning *binning = psImageBinningAlloc();
+    binning->nXfine = options->psfFieldNx;
+    binning->nYfine = options->psfFieldNy;
+
+    // we determine the PSF parameter polynomials from the MD-defined polynomials
+    pmPSF *psf = pmPSFAlloc (options);
+
+    // check the number of expected parameters
+    int nPar = psMetadataLookupS32 (&status, header, "PSF_NPAR");
+    if (nPar != pmModelClassParameterCount (psf->type))
+        psAbort("mismatch model par count");
+
+    // load the trend mode and dimensions of each parameter
+    for (int i = 0; i < nPar; i++) {
+        char name[9];
+        snprintf (name, 9, "PAR%02d_NX", i);
+        binning->nXruff = psMetadataLookupS32 (&status, header, name);
+        if (!status) continue;          // not all parameters are defined
+
+        snprintf (name, 9, "PAR%02d_NY", i);
+        binning->nYruff = psMetadataLookupS32 (&status, header, name);
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, true, "inconsistent PSF header: NX defined for PAR %d, but not NY", i);
+            return false;
+        }
+
+        snprintf (name, 9, "PAR%02d_MD", i);
+        char *modeName = psMetadataLookupStr (&status, header, name);
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, true, "inconsistent PSF header: NX & NY defined for PAR %d, but not MD", i);
+            return false;
+        }
+        pmTrend2DMode psfTrendMode = pmTrend2DModeFromString (modeName);
+        if (psfTrendMode == PM_TREND_NONE) {
+            psfTrendMode = PM_TREND_POLY_ORD;
+        }
+
+        // XXX Attempting to guard against failing assertions on nXruff and nYruff in psImageBinningSetScale.
+        // This replicates code in psphotCheckStarDistribution, where these values are generated.  Not sure
+        // it's correct, though.
+        if (psfTrendMode != PM_TREND_MAP) {
+            binning->nXruff++;
+            binning->nYruff++;
+        }
+
+        psImageBinningSetScale (binning, PS_IMAGE_BINNING_CENTER);
+        psImageBinningSetSkipByOffset (binning, options->psfFieldXo, options->psfFieldYo);
+        psf->params->data[i] = pmTrend2DNoImageAlloc (psfTrendMode, binning, NULL);
+    }
+    psFree (binning);
+    psFree(options);
+
+    // other required information describing the PSF
+    psf->ApResid   = psMetadataLookupF32 (&status, header, "AP_RESID");
+    psf->dApResid  = psMetadataLookupF32 (&status, header, "AP_ERROR");
+    psf->chisq     = psMetadataLookupF32 (&status, header, "CHISQ");
+    psf->nPSFstars = psMetadataLookupS32 (&status, header, "NSTARS");
+
+    // XXX can we drop this now?
+    psf->skyBias   = psMetadataLookupF32 (&status, header, "SKY_BIAS");
+
+    // read the raw table data
+    psArray *table = psFitsReadTable (file->fits);
+
+    // fill in the matching psf->params entries
+    for (int i = 0; i < table->n; i++) {
+        psMetadata *row = table->data[i];
+        int iPar = psMetadataLookupS32 (&status, row, "MODEL_TERM");
+        int xPow = psMetadataLookupS32 (&status, row, "X_POWER");
+        int yPow = psMetadataLookupS32 (&status, row, "Y_POWER");
+
+        pmTrend2D *trend = psf->params->data[iPar];
+        if (trend == NULL) {
+            psError(PS_ERR_UNKNOWN, true, "parameter %d not available", iPar);
+            return false;
+        }
+
+        if (trend->mode == PM_TREND_MAP) {
+            psImageMap *map = trend->map;
+            assert (map);
+            assert (map->map);
+            assert (map->error);
+            assert (xPow >= 0);
+            assert (yPow >= 0);
+            assert (xPow < map->map->numCols);
+            assert (yPow < map->map->numRows);
+            map->map->data.F32[yPow][xPow]    = psMetadataLookupF32 (&status, row, "VALUE");
+            map->error->data.F32[yPow][xPow]  = psMetadataLookupF32 (&status, row, "ERROR");
+        } else {
+            psPolynomial2D *poly = trend->poly;
+            assert (poly);
+            assert (xPow >= 0);
+            assert (yPow >= 0);
+            assert (xPow <= poly->nX);
+            assert (yPow <= poly->nY);
+            poly->coeff[xPow][yPow]     = psMetadataLookupF32 (&status, row, "VALUE");
+            poly->coeffErr[xPow][yPow]  = psMetadataLookupF32 (&status, row, "ERROR");
+            poly->coeffMask[xPow][yPow] = psMetadataLookupU8  (&status, row, "MASK");
+        }
+    }
+    psFree (header);
+    psFree (table);
+
+    // move fits pointer to residual image and read header
+    // advance to the table data extension
+    // since we have read the IMAGE header, the TABLE header should exist
+    if (!psFitsMoveExtName (file->fits, imageName)) {
+        psAbort("cannot find data extension %s in %s", imageName, file->filename);
+    }
+
+    header = psFitsReadHeader (NULL, file->fits);
+    int Naxis = psMetadataLookupS32 (&status, header, "NAXIS");
+    if (Naxis != 0) {
+
+        int Nx = psMetadataLookupS32 (&status, header, "NAXIS1");
+        int Ny = psMetadataLookupS32 (&status, header, "NAXIS2");
+        int Nz = psMetadataLookupS32 (&status, header, "NAXIS3");
+
+        int xBin  = psMetadataLookupS32 (&status, header, "XBIN");
+        int yBin  = psMetadataLookupS32 (&status, header, "YBIN");
+
+        int xSize = Nx / xBin;
+        int ySize = Ny / yBin;
+
+        psf->residuals = pmResidualsAlloc (xSize, ySize, xBin, yBin);
+
+        psf->residuals->xCenter = psMetadataLookupS32 (&status, header, "XCENTER");
+        psf->residuals->yCenter = psMetadataLookupS32 (&status, header, "YCENTER");
+
+        psRegion fullImage = {0, 0, 0, 0};
+        psFitsReadImageBuffer(psf->residuals->Ro, file->fits, fullImage, 0); // Desired pixels
+        if (Nz > 1) {
+            assert (Nz == 3);
+            psFitsReadImageBuffer(psf->residuals->Rx, file->fits, fullImage, 1); // Desired pixels
+            psFitsReadImageBuffer(psf->residuals->Ry, file->fits, fullImage, 2); // Desired pixels
+        }
+    }
+
+    psMetadataAdd (analysis, PS_LIST_TAIL, "PSPHOT.PSF",     PS_DATA_UNKNOWN,  "psphot psf", psf);
+    psFree (psf);
+
+    psFree (tableName);
+    psFree (imageName);
+    psFree (header);
+
+    return true;
+}
+
+// XXX pmPSF to/from Metadata functions were defined for 1.22 and earlier, but were dropped
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPSF_IO.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPSF_IO.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPSF_IO.h	(revision 22157)
@@ -0,0 +1,36 @@
+/* @file  pmPSF.h
+ *
+ * This file contains typedefs for the Point-Spread Function and prototypes
+ * for functions that calculate the PSF.
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.10 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-09-24 21:27:58 $
+ * Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+# ifndef PM_PSF_IO_H
+# define PM_PSF_IO_H
+
+/// @addtogroup Objects Object Detection / Analysis Functions
+/// @{
+
+bool pmPSFmodelWriteForView (const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmPSFmodelWriteFPA (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmPSFmodelWriteChip (pmChip *chip, const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmPSFmodelWrite (psMetadata *analysis, const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+
+bool pmPSFmodelWritePHU (const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+
+bool pmPSFmodelReadForView (const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmPSFmodelReadFPA (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmPSFmodelReadChip (pmChip *chip, const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmPSFmodelRead (psMetadata *analysis, const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+
+bool pmPSFmodelCheckDataStatusForView (const pmFPAview *view, const pmFPAfile *file);
+bool pmPSFmodelCheckDataStatusForFPA (const pmFPA *fpa);
+bool pmPSFmodelCheckDataStatusForChip (const pmChip *chip);
+
+/// @}
+# endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPSFtoMetadata.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPSFtoMetadata.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPSFtoMetadata.c	(revision 22157)
@@ -0,0 +1,91 @@
+
+// create a psMetadata representation (human-readable) of a psf model
+psMetadata *pmPSFtoMetadata (psMetadata *metadata, pmPSF *psf)
+{
+
+    if (metadata == NULL) {
+        metadata = psMetadataAlloc ();
+    }
+
+    char *modelName = pmModelClassGetName (psf->type);
+    psMetadataAdd (metadata, PS_LIST_TAIL, "PSF_MODEL_NAME", PS_DATA_STRING, "PSF model name", modelName);
+
+    int nPar = pmModelClassParameterCount (psf->type)    ;
+    psMetadataAdd (metadata, PS_LIST_TAIL, "PSF_MODEL_NPAR", PS_DATA_S32, "PSF model parameter count", nPar);
+
+    for (int i = 0; i < nPar; i++) {
+        psPolynomial2D *poly = psf->params->data[i];
+        if (poly == NULL)
+            continue;
+        psPolynomial2DtoMetadata (metadata, poly, "PSF_PAR%02d", i);
+    }
+
+    // XXX fix this
+    psWarning ("APTREND is currently missing");
+    // psPolynomial4DtoMetadata (metadata, psf->ApTrend, "APTREND");
+
+    psMetadataAdd (metadata, PS_LIST_TAIL, "PSF_AP_RESID", PS_DATA_F32, "aperture residual", psf->ApResid);
+    psMetadataAdd (metadata, PS_LIST_TAIL, "PSF_dAP_RESID", PS_DATA_F32, "aperture residual scatter", psf->dApResid);
+    psMetadataAdd (metadata, PS_LIST_TAIL, "PSF_SKY_BIAS", PS_DATA_F32, "sky bias level", psf->skyBias);
+
+    psMetadataAdd (metadata, PS_LIST_TAIL, "PSF_CHISQ", PS_DATA_F32, "chi-square for fit", psf->chisq);
+    psMetadataAdd (metadata, PS_LIST_TAIL, "PSF_NSTARS", PS_DATA_S32, "number of stars used to measure PSF", psf->nPSFstars);
+    psMetadataAdd (metadata, PS_LIST_TAIL, "PSF_POISSON_ERRORS", PS_DATA_BOOL, "Poisson errors for fits", psf->poissonErrors);
+
+    return metadata;
+}
+
+// parse a psMetadata representation (human-readable) of a psf model
+pmPSF *pmPSFfromMetadata (psMetadata *metadata)
+{
+
+    bool status;
+    char keyword[80];
+
+    char *modelName = psMetadataLookupPtr (&status, metadata, "PSF_MODEL_NAME");
+    pmModelType type = pmModelClassGetType (modelName);
+
+    bool poissonErrors = psMetadataLookupPtr (&status, metadata, "PSF_POISSON_ERRORS");
+    if (!status)
+        poissonErrors = true;
+
+    // we determine the PSF parameter polynomials from the MD-defined polynomials
+    pmPSF *psf = pmPSFAlloc (type, poissonErrors, NULL);
+
+    int nPar = psMetadataLookupS32 (&status, metadata, "PSF_MODEL_NPAR");
+    if (nPar != pmModelClassParameterCount (psf->type))
+        psAbort("mismatch model par count");
+
+    // un-fitted terms, not in the Metadata, are left NULL
+    // XXX add a double-check of the expected number?
+    for (int i = 0; i < nPar; i++) {
+        sprintf (keyword, "PSF_PAR%02d", i);
+        psMetadata *folder = psMetadataLookupPtr (&status, metadata, keyword);
+        if (!status)
+            continue;
+        psPolynomial2D *poly = psPolynomial2DfromMetadata (folder);
+        psFree (psf->params->data[i]);
+        psf->params->data[i] = poly;
+    }
+
+    // load the APTREND data
+    // XXX fix this to work with pmTrend2D
+    psWarning ("APTREND is not being read");
+    # if (0)
+    sprintf (keyword, "APTREND");
+    psMetadata *folder = psMetadataLookupPtr (&status, metadata, keyword);
+    psPolynomial4D *poly = psPolynomial4DfromMetadata (folder);
+    psFree (psf->ApTrend);
+    psf->ApTrend = poly;
+    # endif
+
+    psf->ApResid = psMetadataLookupF32 (&status, metadata, "PSF_AP_RESID");
+    psf->dApResid = psMetadataLookupF32 (&status, metadata, "PSF_dAP_RESID");
+    psf->skyBias = psMetadataLookupF32 (&status, metadata, "PSF_SKY_BIAS");
+
+    psf->chisq = psMetadataLookupF32 (&status, metadata, "PSF_CHISQ");
+    psf->nPSFstars = psMetadataLookupS32 (&status, metadata, "PSF_NSTARS");
+
+    psFree (metadata);
+    return (psf);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPSFtry.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPSFtry.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPSFtry.c	(revision 22157)
@@ -0,0 +1,847 @@
+/** @file  pmPSFtry.c
+ *
+ *  XXX: need description of file purpose
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.56.8.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-07-11 18:35:04 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAMaskWeight.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmResiduals.h"
+#include "pmGrowthCurve.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmSourceUtils.h"
+#include "pmPSFtry.h"
+#include "pmModelClass.h"
+#include "pmModelUtils.h"
+#include "pmSourceFitModel.h"
+#include "pmSourcePhotometry.h"
+
+// ********  pmPSFtry functions  **************************************************
+// * pmPSFtry holds a single pmPSF model test, with the input sources, the freely
+// * fitted version of the model, the pmPSF fit to the fitted model parameters,
+// * and the PSF fits to the source. It also includes the statistics from the
+// * fits, both the individual sources, and the collection
+
+// free a pmPSFtry structure
+static void pmPSFtryFree (pmPSFtry *test)
+{
+    if (test == NULL) return;
+
+    psFree (test->psf);
+    psFree (test->sources);
+    psFree (test->metric);
+    psFree (test->metricErr);
+    psFree (test->fitMag);
+    psFree (test->mask);
+    return;
+}
+
+// allocate a pmPSFtry based on the desired sources and the model (identified by name)
+pmPSFtry *pmPSFtryAlloc (const psArray *sources, const pmPSFOptions *options)
+{
+    pmPSFtry *test = (pmPSFtry *) psAlloc(sizeof(pmPSFtry));
+    psMemSetDeallocator(test, (psFreeFunc) pmPSFtryFree);
+
+    test->psf       = pmPSFAlloc (options);
+    test->metric    = psVectorAlloc (sources->n, PS_TYPE_F32);
+    test->metricErr = psVectorAlloc (sources->n, PS_TYPE_F32);
+    test->fitMag    = psVectorAlloc (sources->n, PS_TYPE_F32);
+    test->mask      = psVectorAlloc (sources->n, PS_TYPE_U8);
+
+    psVectorInit (test->mask,        0);
+    psVectorInit (test->metric,    0.0);
+    psVectorInit (test->metricErr, 0.0);
+    psVectorInit (test->fitMag,    0.0);
+
+    test->sources   = psArrayAlloc (sources->n);
+
+    for (int i = 0; i < sources->n; i++) {
+        test->sources->data[i] = pmSourceCopy (sources->data[i]);
+    }
+
+    return (test);
+}
+
+bool psMemCheckPSFtry(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) pmPSFtryFree);
+}
+
+
+// build a pmPSFtry for the given model:
+// - fit each source with the free-floating model
+// - construct the pmPSF from the collection of models
+// - fit each source with the PSF-parameter models
+// - measure the pmPSF quality metric (dApResid)
+
+// sources used in for pmPSFtry may be masked by the analysis
+// mask values indicate the reason the source was rejected:
+
+// generate a pmPSFtry with a copy of the test PSF sources
+pmPSFtry *pmPSFtryModel (const psArray *sources, const char *modelName, pmPSFOptions *options, psMaskType maskVal, psMaskType markVal)
+{
+    bool status;
+    int Next = 0;
+    int Npsf = 0;
+
+    // validate the requested model name
+    options->type = pmModelClassGetType (modelName);
+    if (options->type == -1) {
+        psError (PS_ERR_UNKNOWN, true, "invalid model name %s", modelName);
+        return NULL;
+    }
+
+    pmPSFtry *psfTry = pmPSFtryAlloc (sources, options);
+    if (psfTry == NULL) {
+        psError (PS_ERR_UNKNOWN, false, "failed to allocate psf model");
+        return NULL;
+    }
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // stage 1:  fit an EXT model to all candidates PSF sources
+    psTimerStart ("fit");
+    for (int i = 0; i < psfTry->sources->n; i++) {
+
+        pmSource *source = psfTry->sources->data[i];
+        source->modelEXT = pmSourceModelGuess (source, psfTry->psf->type);
+        if (source->modelEXT == NULL) {
+            psError(PS_ERR_UNKNOWN, false, "failed to build model");
+            psFree(psfTry);
+            return NULL;
+        }
+
+        // set object mask to define valid pixels
+        psImageKeepCircle (source->maskObj, source->peak->x, source->peak->y, options->radius, "OR", markVal);
+
+        // fit model as EXT, not PSF
+        status = pmSourceFitModel (source, source->modelEXT, PM_SOURCE_FIT_EXT, maskVal);
+
+	// clear object mask to define valid pixels
+        psImageKeepCircle (source->maskObj, source->peak->x, source->peak->y, options->radius, "AND", PS_NOT_U8(markVal));
+
+        // exclude the poor fits
+        if (!status) {
+            psfTry->mask->data.U8[i] = PSFTRY_MASK_EXT_FAIL;
+            continue;
+        }
+        Next ++;
+    }
+    psLogMsg ("psphot.psftry", 4, "fit ext:   %f sec for %d of %ld sources\n", psTimerMark ("fit"), Next, sources->n);
+    psTrace ("psphot.psftry", 3, "keeping %d of %ld PSF candidates (EXT)\n", Next, sources->n);
+
+    // stage 2: construct a psf (pmPSF) from this collection of model fits
+    if (!pmPSFFromPSFtry (psfTry)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to construct a psf model from collection of sources");
+        psFree(psfTry);
+        return NULL;
+    }
+
+    // stage 3: refit with fixed shape parameters
+    psTimerStart ("fit");
+    for (int i = 0; i < psfTry->sources->n; i++) {
+        // masked for: bad model fit, outlier in parameters
+        if (psfTry->mask->data.U8[i] & PSFTRY_MASK_ALL)
+            continue;
+
+        pmSource *source = psfTry->sources->data[i];
+
+        // set shape for this model based on PSF
+        source->modelPSF = pmModelFromPSF (source->modelEXT, psfTry->psf);
+        if (source->modelPSF == NULL) {
+            psfTry->mask->data.U8[i] = PSFTRY_MASK_BAD_MODEL;
+            abort();
+            continue;
+        }
+        source->modelPSF->radiusFit = options->radius;
+
+        // set object mask to define valid pixels
+        psImageKeepCircle (source->maskObj, source->peak->x, source->peak->y, options->radius, "OR", markVal);
+
+        // fit the PSF model to the source
+        status = pmSourceFitModel (source, source->modelPSF, PM_SOURCE_FIT_PSF, maskVal);
+
+        // skip poor fits
+        if (!status) {
+	    psImageKeepCircle (source->maskObj, source->peak->x, source->peak->y, options->radius, "AND", PS_NOT_U8(markVal));
+            psfTry->mask->data.U8[i] = PSFTRY_MASK_PSF_FAIL;
+            psTrace ("psModules.objects", 4, "dropping %d (%d,%d) : failed PSF fit\n", i, source->peak->x, source->peak->y);
+            continue;
+        }
+
+        status = pmSourceMagnitudes (source, psfTry->psf, PM_SOURCE_PHOT_INTERP, maskVal);
+	if (!status || isnan(source->apMag)) {
+	    psImageKeepCircle (source->maskObj, source->peak->x, source->peak->y, options->radius, "AND", PS_NOT_U8(markVal));
+            psfTry->mask->data.U8[i] = PSFTRY_MASK_BAD_PHOT;
+            psTrace ("psModules.objects", 4, "dropping %d (%d,%d) : poor photometry\n", i, source->peak->x, source->peak->y);
+            continue;
+        }
+
+	// clear object mask to define valid pixels
+        psImageKeepCircle (source->maskObj, source->peak->x, source->peak->y, options->radius, "AND", PS_NOT_U8(markVal));
+
+        psfTry->fitMag->data.F32[i] = source->psfMag;
+        psfTry->metric->data.F32[i] = source->apMag - source->psfMag;
+        psfTry->metricErr->data.F32[i] = source->errMag;
+
+        Npsf ++;
+    }
+    psfTry->psf->nPSFstars = Npsf;
+
+    psLogMsg ("psphot.psftry", 4, "fit psf:   %f sec for %d of %ld sources\n", psTimerMark ("fit"), Npsf, sources->n);
+    psTrace ("psphot.psftry", 3, "keeping %d of %ld PSF candidates (PSF)\n", Npsf, sources->n);
+
+    // measure the chi-square trend as a function of flux (PAR[PM_PAR_I0])
+    // this should be linear for Poisson errors and quadratic for constant sky errors
+    psVector *flux  = psVectorAlloc (psfTry->sources->n, PS_TYPE_F32);
+    psVector *chisq = psVectorAlloc (psfTry->sources->n, PS_TYPE_F32);
+    psVector *mask  = psVectorAlloc (psfTry->sources->n, PS_TYPE_MASK);
+
+    // generate the x and y vectors, and mask missing models
+    for (int i = 0; i < psfTry->sources->n; i++) {
+        pmSource *source = psfTry->sources->data[i];
+        if (source->modelPSF == NULL) {
+            flux->data.F32[i] = 0.0;
+            chisq->data.F32[i] = 0.0;
+            mask->data.U8[i] = 0xff;
+        } else {
+            flux->data.F32[i] = source->modelPSF->params->data.F32[PM_PAR_I0];
+            chisq->data.F32[i] = source->modelPSF->chisq / source->modelPSF->nDOF;
+            mask->data.U8[i] = 0;
+        }
+    }
+
+    // use 3hi/3lo sigma clipping on the chisq fit
+    psStats *stats = options->stats;
+
+    // linear clipped fit of chisq trend vs flux
+    bool result = psVectorClipFitPolynomial1D(psfTry->psf->ChiTrend, options->stats, mask,
+                                              0xff, chisq, NULL, flux);
+    psStatsOptions meanStat = psStatsMeanOption(options->stats->options); // Statistic for mean
+    psStatsOptions stdevStat = psStatsStdevOption(options->stats->options); // Statistic for stdev
+
+    psLogMsg ("pmPSFtry", 4, "chisq vs flux fit: %f +/- %f\n",
+              psStatsGetValue(stats, meanStat), psStatsGetValue(stats, stdevStat));
+
+    psFree(flux);
+    psFree(mask);
+    psFree(chisq);
+
+    if (!result) {
+        psError(PS_ERR_UNKNOWN, false, "Failed to fit psf->ChiTrend");
+        psFree(psfTry);
+        return NULL;
+    }
+
+    for (int i = 0; i < psfTry->psf->ChiTrend->nX + 1; i++) {
+        psLogMsg ("pmPSFtry", 4, "chisq vs flux fit term %d: %f +/- %f\n", i,
+                  psfTry->psf->ChiTrend->coeff[i]*pow(10000, i),
+                  psfTry->psf->ChiTrend->coeffErr[i]*pow(10000,i));
+    }
+
+    // XXX this function wants aperture radius for pmSourcePhotometry
+    if (!pmPSFtryMetric (psfTry, options)) {
+        psError(PS_ERR_UNKNOWN, false, "Attempt to fit PSF with model %s failed.", modelName);
+	psFree (psfTry);
+        return NULL;
+    }
+
+    psLogMsg ("psphot.pspsf", 3, "try model %s, ap-fit: %f +/- %f : sky bias: %f\n",
+              modelName, psfTry->psf->ApResid, psfTry->psf->dApResid, psfTry->psf->skyBias);
+
+    return (psfTry);
+}
+
+bool pmPSFtryMetric (pmPSFtry *psfTry, pmPSFOptions *options)
+{
+    PS_ASSERT_PTR_NON_NULL(psfTry, false);
+    PS_ASSERT_PTR_NON_NULL(options, false);
+    PS_ASSERT_PTR_NON_NULL(psfTry->sources, false);
+
+    float RADIUS = options->radius;
+
+    // the measured (aperture - fit) magnitudes (dA == psfTry->metric)
+    //   depend on both the true ap-fit (dAo) and the bias in the sky measurement:
+    //     dA = dAo + dsky/flux
+    //   where flux is the flux of the star
+    // we fit this trend to find the infinite flux aperture correction (dAo),
+    //   the nominal sky bias (dsky), and the error on dAo
+    // the values of dA are contaminated by stars with close neighbors in the aperture
+    //   we use an outlier rejection to avoid this bias
+
+    // r2rflux = radius^2 * ten(0.4*fitMag);
+    psVector *r2rflux = psVectorAlloc (psfTry->sources->n, PS_TYPE_F32);
+
+    for (int i = 0; i < psfTry->sources->n; i++) {
+        if (psfTry->mask->data.U8[i] & PSFTRY_MASK_ALL)
+            continue;
+        r2rflux->data.F32[i] = PS_SQR(RADIUS) * pow(10.0, 0.4*psfTry->fitMag->data.F32[i]);
+    }
+
+    // XXX test dump of aperture residual data
+    if (psTraceGetLevel("psModules.objects") >= 5) {
+        FILE *f = fopen ("apresid.dat", "w");
+        for (int i = 0; i < psfTry->sources->n; i++) {
+            int keep = (psfTry->mask->data.U8[i] & PSFTRY_MASK_ALL);
+
+            pmSource *source = psfTry->sources->data[i];
+            float x = source->peak->x;
+            float y = source->peak->y;
+
+            fprintf (f, "%d  %d, %f %f %f  %f %f %f \n",
+                     i, keep, x, y,
+                     psfTry->fitMag->data.F32[i],
+                     r2rflux->data.F32[i],
+                     psfTry->metric->data.F32[i],
+                     psfTry->metricErr->data.F32[i]);
+        }
+        fclose (f);
+    }
+
+    // This analysis of the apResid statistics is only approximate.  The fitted magnitudes
+    // measured at this point (in the PSF fit) use Poisson errors, and are thus biased as a
+    // function of magnitude.  We re-measure the apResid statistics later in psphot using the
+    // linear, constant-error fitting.  Do not reject outliers with excessive vigor here.
+
+    // fit ApTrend only to r2rflux, ignore x,y,flux variations for now
+    // linear clipped fit of ApResid to r2rflux
+    psPolynomial1D *poly = psPolynomial1DAlloc (PS_POLYNOMIAL_ORD, 1);
+    poly->coeffMask[1] = PS_POLY_MASK_SET; // fit only a constant offset (no SKYBIAS)
+
+    // XXX replace this with a psVectorStats call?  since we are not fitting the trend
+    bool result = psVectorClipFitPolynomial1D(poly, options->stats, psfTry->mask, PSFTRY_MASK_ALL,
+                                              psfTry->metric, psfTry->metricErr, r2rflux);
+    if (!result) {
+        psError(PS_ERR_UNKNOWN, false, "Failed to fit clipped poly");
+
+        psFree(poly);
+        psFree(r2rflux);
+
+        return false;
+    }
+    psStatsOptions stdevStat = psStatsStdevOption(options->stats->options); // Statistic for stdev
+    psLogMsg ("pmPSFtryMetric", 4, "apresid: %f +/- %f; from statistics of %ld psf stars\n", poly->coeff[0],
+              psStatsGetValue(options->stats, stdevStat), psfTry->sources->n);
+
+
+
+    // XXX test dump of fitted model (dump when tracing?)
+    if (psTraceGetLevel("psModules.objects") >= 4) {
+        FILE *f = fopen ("resid.dat", "w");
+        psVector *apfit = psPolynomial1DEvalVector (poly, r2rflux);
+        for (int i = 0; i < psfTry->sources->n; i++) {
+            int keep = (psfTry->mask->data.U8[i] & PSFTRY_MASK_ALL);
+
+            pmSource *source = psfTry->sources->data[i];
+            float x = source->peak->x;
+            float y = source->peak->y;
+
+            fprintf (f, "%d  %d, %f %f %f  %f %f %f  %f\n",
+                     i, keep, x, y,
+                     psfTry->fitMag->data.F32[i],
+                     r2rflux->data.F32[i],
+                     psfTry->metric->data.F32[i],
+                     psfTry->metricErr->data.F32[i],
+                     apfit->data.F32[i]);
+        }
+        fclose (f);
+        psFree (apfit);
+    }
+
+    // XXX drop the skyBias value, or include above??
+    psfTry->psf->skyBias  = poly->coeff[1];
+    psfTry->psf->ApResid  = poly->coeff[0];
+    psfTry->psf->dApResid = psStatsGetValue(options->stats, stdevStat);
+
+    psFree (r2rflux);
+    psFree (poly);
+
+    return true;
+}
+
+/*
+  (aprMag' - fitMag) = rflux*skyBias + ApTrend(x,y)
+  (aprMag - rflux*skyBias) - fitMag = ApTrend(x,y)
+  (aprMag - rflux*skyBias) = fitMag + ApTrend(x,y)
+*/
+
+/*****************************************************************************
+pmPSFFromPSFtry (psfTry): build a PSF model from a collection of
+source->modelEXT entries.  The PSF ignores the first 4 (independent) model
+parameters and constructs a polynomial fit to the remaining as a function of
+image coordinate.
+    input: psfTry with fitted source->modelEXT collection, pre-allocated psf
+Note: some of the array entries may be NULL (failed fits); ignore them.
+ *****************************************************************************/
+bool pmPSFFromPSFtry (pmPSFtry *psfTry)
+{
+    PS_ASSERT_PTR_NON_NULL(psfTry, false);
+    PS_ASSERT_PTR_NON_NULL(psfTry->sources, false);
+
+    pmPSF *psf = psfTry->psf;
+
+    // construct the fit vectors from the collection of objects
+    psVector *x  = psVectorAlloc (psfTry->sources->n, PS_TYPE_F32);
+    psVector *y  = psVectorAlloc (psfTry->sources->n, PS_TYPE_F32);
+    psVector *z  = psVectorAlloc (psfTry->sources->n, PS_TYPE_F32);
+
+    // construct the x,y terms
+    for (int i = 0; i < psfTry->sources->n; i++) {
+        pmSource *source = psfTry->sources->data[i];
+        if (source->modelEXT == NULL)
+            continue;
+
+        x->data.F32[i] = source->modelEXT->params->data.F32[PM_PAR_XPOS];
+        y->data.F32[i] = source->modelEXT->params->data.F32[PM_PAR_YPOS];
+    }
+
+    if (!pmPSFFitShapeParams (psf, psfTry->sources, x, y, psfTry->mask)) {
+        psFree(x);
+        psFree(y);
+        psFree(z);
+        return false;
+    }
+
+    // skip the unfitted parameters (X, Y, Io, Sky) and the shape parameters (SXX, SYY, SXY)
+    // apply the values of Nx, Ny determined above for E0,E1,E2 to the remaining parameters
+    for (int i = 0; i < psf->params->n; i++) {
+        switch (i) {
+          case PM_PAR_SKY:
+          case PM_PAR_I0:
+          case PM_PAR_XPOS:
+          case PM_PAR_YPOS:
+          case PM_PAR_SXX:
+          case PM_PAR_SYY:
+          case PM_PAR_SXY:
+            continue;
+          default:
+            break;
+        }
+
+        // select the per-object fitted data for this parameter
+        for (int j = 0; j < psfTry->sources->n; j++) {
+            pmSource *source = psfTry->sources->data[j];
+            if (source->modelEXT == NULL) continue;
+            z->data.F32[j] = source->modelEXT->params->data.F32[i];
+        }
+
+        psImageBinning *binning = psImageBinningAlloc();
+        binning->nXruff = psf->trendNx;
+        binning->nYruff = psf->trendNy;
+        binning->nXfine = psf->fieldNx;
+        binning->nYfine = psf->fieldNy;
+
+        if (psf->psfTrendMode == PM_TREND_MAP) {
+            psImageBinningSetScale (binning, PS_IMAGE_BINNING_CENTER);
+            psImageBinningSetSkipByOffset (binning, psf->fieldXo, psf->fieldYo);
+        }
+
+        // free existing trend, re-alloc
+        psFree (psf->params->data[i]);
+        psf->params->data[i] = pmTrend2DNoImageAlloc (psf->psfTrendMode, binning, psf->psfTrendStats);
+        psFree (binning);
+
+        // fit the collection of measured parameters to the PSF 2D model
+        // the mask is carried from previous steps and updated with this operation
+        // the weight is either the flux error or NULL, depending on 'psf->poissonErrorParams'
+        if (!pmTrend2DFit (psf->params->data[i], psfTry->mask, 0xff, x, y, z, NULL)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to build psf model for parameter %d", i);
+            psFree(x);
+            psFree(y);
+            psFree(z);
+            return false;
+        }
+    }
+
+    // test dump of star parameters vs position (compare with fitted values)
+    if (psTraceGetLevel("psModules.objects") >= 4) {
+        FILE *f = fopen ("params.dat", "w");
+
+        for (int j = 0; j < psfTry->sources->n; j++) {
+            pmSource *source = psfTry->sources->data[j];
+            if (source == NULL) continue;
+            if (source->modelEXT == NULL) continue;
+
+            pmModel *modelPSF = pmModelFromPSF (source->modelEXT, psf);
+
+            fprintf (f, "%f %f : ", source->modelEXT->params->data.F32[PM_PAR_XPOS], source->modelEXT->params->data.F32[PM_PAR_YPOS]);
+
+            for (int i = 0; i < psf->params->n; i++) {
+                if (psf->params->data[i] == NULL) continue;
+                fprintf (f, "%f %f : ", source->modelEXT->params->data.F32[i], modelPSF->params->data.F32[i]);
+            }
+            fprintf (f, "%f %d\n", source->modelEXT->chisq, source->modelEXT->nIter);
+
+            psFree(modelPSF);
+        }
+        fclose (f);
+    }
+
+    psFree (x);
+    psFree (y);
+    psFree (z);
+    return true;
+}
+
+
+bool pmPSFFitShapeParams (pmPSF *psf, psArray *sources, psVector *x, psVector *y, psVector *srcMask) {
+
+    // we are doing a robust fit.  after each pass, we drop points which are more deviant than
+    // three sigma.  mask is currently updated for each pass.
+
+    // The shape parameters (SXX, SXY, SYY) are strongly coupled.  We have to handle them very
+    // carefully.  First, we convert them to the Ellipse Polarization terms (E0, E1, E2) for
+    // each source and fit this set of parameters.  These values are less tightly coupled, but
+    // are still inter-related.  The fitted values do a good job of constraining the major axis
+    // and the position angle, but the minor axis is weakly measured.  When we apply the PSF
+    // model to construct a source model, we convert the fitted values of E0,E1,E2 to the shape
+    // parameters, with the constraint that the minor axis must be greater than a minimum
+    // threshold.
+
+    // convert the measured source shape paramters to polarization terms
+    psVector *e0   = psVectorAlloc (sources->n, PS_TYPE_F32);
+    psVector *e1   = psVectorAlloc (sources->n, PS_TYPE_F32);
+    psVector *e2   = psVectorAlloc (sources->n, PS_TYPE_F32);
+    psVector *mag  = psVectorAlloc (sources->n, PS_TYPE_F32);
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        if (source->modelEXT == NULL) continue;
+        // XXX I am relying on the fact that none of the masked sources
+        // have modelEXT set here.  perhaps use the value of psfTry->mask instead?
+
+        psEllipsePol pol = pmPSF_ModelToFit (source->modelEXT->params->data.F32);
+
+        e0->data.F32[i] = pol.e0;
+        e1->data.F32[i] = pol.e1;
+        e2->data.F32[i] = pol.e2;
+
+        float flux = source->modelEXT->params->data.F32[PM_PAR_I0];
+        mag->data.F32[i] = (flux > 0.0) ? -2.5*log(flux) : -100.0;
+    }
+
+    if (psf->psfTrendMode == PM_TREND_MAP) {
+        float errorFloor = 0.0;
+        float errorTotal = 0.0;
+        float errorTotalMin = FLT_MAX;
+        int entryMin = -1;
+
+        psVector *dz = NULL;
+        psVector *mask = psVectorAlloc (sources->n, PS_TYPE_U8);
+
+        // check the fit residuals and increase Nx,Ny until the error is minimized
+        // pmPSFParamTrend increases the number along the longer of x or y
+        for (int i = 1; i < PS_MAX (psf->trendNx, psf->trendNy); i++) {
+
+            psVectorInit (mask, 0);
+            if (!pmPSFFitShapeParamsMap (psf, i, &errorFloor, &errorTotal, mask, x, y, mag, e0, e1, e2, dz)) {
+                break;
+            }
+
+            // we do not have a good model for the error distributions on e0, e1, e2.
+            // use the bright end scatter from the constant fit to set the scale for the
+            // versions with 2D variation.  potentially scale by poisson errors...
+            if (i == 1) {
+                // if (psf->poissonErrorsParams) do something else..
+                dz = psVectorAlloc (sources->n, PS_TYPE_F32);
+                for (int i = 0; i < sources->n; i++) {
+                    dz->data.F32[i] = errorFloor;
+                }
+            }
+
+            // store the resulting errorTotal values and the scales, redo the best
+            if (errorTotal < errorTotalMin) {
+                errorTotalMin = errorTotal;
+                entryMin = i;
+            }
+        }
+        if (entryMin == -1) {
+            psError (PS_ERR_UNKNOWN, true, "failed to find image map for shape params");
+            return false;
+        }
+
+        // XXX supply the resulting mask values back to srcMask
+        psVectorInit (mask, 0);
+        if (!pmPSFFitShapeParamsMap (psf, entryMin, &errorFloor, &errorTotal, mask, x, y, mag, e0, e1, e2, dz)) {
+            psAbort ("failed pmPSFFitShapeParamsMap on second pass?");
+        }
+
+        pmTrend2D *trend = psf->params->data[PM_PAR_E0];
+        psf->trendNx = trend->map->map->numCols;
+        psf->trendNy = trend->map->map->numRows;
+
+        psFree (mask);
+        psFree (dz);
+    } else {
+
+        // XXX iterate Nx, Ny based on scatter?
+        // XXX we force the x & y order to be the same
+        // modify the order to correspond to the actual number of matched stars:
+        int order = PS_MAX (psf->trendNx, psf->trendNy);
+        if ((sources->n < 15) && (order >= 3)) order = 2;
+        if ((sources->n < 11) && (order >= 2)) order = 1;
+        if ((sources->n <  8) && (order >= 1)) order = 0;
+        if ((sources->n <  3)) {
+            psError (PS_ERR_UNKNOWN, true, "failed to fit polynomial to shape params");
+            return false;
+        }
+        psf->trendNx = order;
+        psf->trendNy = order;
+
+        // we run 'clipIter' cycles clipping in each of x and y, with only one iteration each.
+        // This way, the parameters masked by one of the fits will be applied to the others
+        for (int i = 0; i < psf->psfTrendStats->clipIter; i++) {
+
+            // XXX we are using the same stats structure on each pass: do we need to re-init it?
+            bool status = true;
+            status &= pmTrend2DFit (psf->params->data[PM_PAR_E0], srcMask, 0xff, x, y, e0, NULL);
+            psTrace ("psModules.pmPSFtry", 4, "clipped E0 : keeping %ld of %ld\n", psf->psfTrendStats->clippedNvalues, e0->n);
+            status &= pmTrend2DFit (psf->params->data[PM_PAR_E1], srcMask, 0xff, x, y, e1, NULL);
+            psTrace ("psModules.pmPSFtry", 4, "clipped E1 : keeping %ld of %ld\n", psf->psfTrendStats->clippedNvalues, e1->n);
+            status &= pmTrend2DFit (psf->params->data[PM_PAR_E2], srcMask, 0xff, x, y, e2, NULL);
+            psTrace ("psModules.pmPSFtry", 4, "clipped E2 : keeping %ld of %ld\n", psf->psfTrendStats->clippedNvalues, e2->n);
+
+            if (!status) {
+                psError (PS_ERR_UNKNOWN, true, "failed to fit polynomial to shape params");
+                return false;
+            }
+        }
+    }
+
+    // test dump of the psf parameters
+    if (psTraceGetLevel("psModules.objects") >= 4) {
+        FILE *f = fopen ("pol.dat", "w");
+        fprintf (f, "# x y  :  e0obs e1obs e2obs  : e0fit e1fit e2fit : mask\n");
+        for (int i = 0; i < e0->n; i++) {
+            fprintf (f, "%f %f  :  %f %f %f  : %f %f %f  : %d\n",
+                     x->data.F32[i], y->data.F32[i],
+                     e0->data.F32[i], e1->data.F32[i], e2->data.F32[i],
+                     pmTrend2DEval (psf->params->data[PM_PAR_E0], x->data.F32[i], y->data.F32[i]),
+                     pmTrend2DEval (psf->params->data[PM_PAR_E1], x->data.F32[i], y->data.F32[i]),
+                     pmTrend2DEval (psf->params->data[PM_PAR_E2], x->data.F32[i], y->data.F32[i]),
+                     srcMask->data.U8[i]);
+        }
+        fclose (f);
+    }
+
+    psFree (e0);
+    psFree (e1);
+    psFree (e2);
+    psFree (mag);
+    return true;
+}
+
+// fit the shape variations as a psImageMap for the given scale factor
+bool pmPSFFitShapeParamsMap (pmPSF *psf, int scale, float *errorFloor, float *errorTotal, psVector *mask, psVector *x, psVector *y, psVector *mag, psVector *e0obs, psVector *e1obs, psVector *e2obs, psVector *dz) {
+
+    int Nx, Ny;
+
+    // set the map scale to match the aspect ratio
+    if (psf->fieldNx > psf->fieldNy) {
+        Nx = scale;
+        Ny = PS_MAX (1, Nx * (psf->fieldNy / psf->fieldNx));
+    } else {
+        Ny = scale;
+        Nx = PS_MAX (1, Ny * (psf->fieldNx / psf->fieldNy));
+    }
+
+    // do we have enough sources for this fine of a grid?
+    if (x->n < 3*Nx*Ny) {
+        return false;
+    }
+
+    // the mask marks the values not used to calculate the ApTrend
+    psVectorInit (mask, 0);
+
+    // XXX check this against the exising type
+    pmTrend2DMode psfTrendMode = PM_TREND_MAP;
+
+    psImageBinning *binning = psImageBinningAlloc();
+    binning->nXruff = Nx;
+    binning->nYruff = Ny;
+    binning->nXfine = psf->fieldNx;
+    binning->nYfine = psf->fieldNy;
+    psImageBinningSetScale (binning, PS_IMAGE_BINNING_CENTER);
+    psImageBinningSetSkipByOffset (binning, psf->fieldXo, psf->fieldYo);
+
+    psFree (psf->params->data[PM_PAR_E0]);
+    psFree (psf->params->data[PM_PAR_E1]);
+    psFree (psf->params->data[PM_PAR_E2]);
+
+    int nIter = psf->psfTrendStats->clipIter;
+    psf->psfTrendStats->clipIter = 1;
+    psf->params->data[PM_PAR_E0] = pmTrend2DNoImageAlloc (psfTrendMode, binning, psf->psfTrendStats);
+    psf->params->data[PM_PAR_E1] = pmTrend2DNoImageAlloc (psfTrendMode, binning, psf->psfTrendStats);
+    psf->params->data[PM_PAR_E2] = pmTrend2DNoImageAlloc (psfTrendMode, binning, psf->psfTrendStats);
+    psFree (binning);
+
+    // we run 'clipIter' cycles clipping in each of x and y, with only one iteration each.
+    // This way, the parameters masked by one of the fits will be applied to the others
+    for (int i = 0; i < nIter; i++) {
+        // XXX we are using the same stats structure on each pass: do we need to re-init it?
+        pmTrend2DFit (psf->params->data[PM_PAR_E0], mask, 0xff, x, y, e0obs, dz);
+        psTrace ("psModules.objects", 5, "clipped E0 : keeping %ld of %ld\n", psf->psfTrendStats->clippedNvalues, e0obs->n);
+        pmTrend2DFit (psf->params->data[PM_PAR_E1], mask, 0xff, x, y, e1obs, dz);
+        psTrace ("psModules.objects", 5, "clipped E1 : keeping %ld of %ld\n", psf->psfTrendStats->clippedNvalues, e1obs->n);
+        pmTrend2DFit (psf->params->data[PM_PAR_E2], mask, 0xff, x, y, e2obs, dz);
+        psTrace ("psModules.objects", 5, "clipped E2 : keeping %ld of %ld\n", psf->psfTrendStats->clippedNvalues, e2obs->n);
+    }
+    psf->psfTrendStats->clipIter = nIter; // restore default setting
+
+    // construct the fitted values and the residuals
+    psVector *e0fit = pmTrend2DEvalVector (psf->params->data[PM_PAR_E0], x, y);
+    psVector *e1fit = pmTrend2DEvalVector (psf->params->data[PM_PAR_E1], x, y);
+    psVector *e2fit = pmTrend2DEvalVector (psf->params->data[PM_PAR_E2], x, y);
+
+    psVector *e0res = (psVector *) psBinaryOp (NULL, (void *) e0obs, "-", (void *) e0fit);
+    psVector *e1res = (psVector *) psBinaryOp (NULL, (void *) e1obs, "-", (void *) e1fit);
+    psVector *e2res = (psVector *) psBinaryOp (NULL, (void *) e2obs, "-", (void *) e2fit);
+
+    // measure errors on the map pixels
+    pmTrend2D *trend;
+
+    trend = psf->params->data[PM_PAR_E0];
+    float mapErrorSum = 0.0;
+    float mapError = 0.0;
+    for (int iy = 0; iy < trend->map->error->numRows; iy++) {
+        for (int ix = 0; ix < trend->map->error->numCols; ix++) {
+            mapError += PS_SQR (trend->map->error->data.F32[iy][ix]);
+        }
+    }
+    psTrace ("psModules.objects", 4, "E0 error: %f\n", sqrt(mapError));
+    mapErrorSum += mapError;
+
+    trend = psf->params->data[PM_PAR_E1];
+    mapError = 0.0;
+    for (int iy = 0; iy < trend->map->error->numRows; iy++) {
+        for (int ix = 0; ix < trend->map->error->numCols; ix++) {
+            mapError += PS_SQR (trend->map->error->data.F32[iy][ix]);
+        }
+    }
+    psTrace ("psModules.objects", 4, "E1 error: %f\n", sqrt(mapError));
+    mapErrorSum += mapError;
+
+    trend = psf->params->data[PM_PAR_E2];
+    mapError = 0.0;
+    for (int iy = 0; iy < trend->map->error->numRows; iy++) {
+        for (int ix = 0; ix < trend->map->error->numCols; ix++) {
+            mapError += PS_SQR (trend->map->error->data.F32[iy][ix]);
+        }
+    }
+    psTrace ("psModules.objects", 4, "E2 error: %f\n", sqrt(mapError));
+    mapErrorSum += mapError;
+
+    psTrace ("psModules.objects", PS_LOG_INFO, "total map error: %f\n", sqrt(mapErrorSum));
+
+    // measure systematic errorFloor & systematic / photon scale factor
+    // XXX this is a bit arbitrary, but it forces ~3 stars from the bright bin per spatial bin
+    int nGroup = PS_MAX (3*Nx*Ny, 10);
+    pmPSFShapeParamsErrors (errorFloor, mag, e0res, e1res, e2res, mask, nGroup,
+                            psStatsStdevOption(psf->psfTrendStats->options));
+
+    *errorTotal = sqrt(PS_SQR(*errorFloor) + mapErrorSum);
+
+    psLogMsg ("psphot.psftry", PS_LOG_INFO, "result of %d x %d grid (%d stars per bin)\n", Nx, Ny, nGroup);
+    psLogMsg ("psphot.psftry", PS_LOG_INFO, "systematic scatter floor: %f, error map total: %f\n", *errorFloor, *errorTotal);
+
+    psFree (e0fit);
+    psFree (e1fit);
+    psFree (e2fit);
+
+    psFree (e0res);
+    psFree (e1res);
+    psFree (e2res);
+
+    return true;
+}
+
+// calculate the minimum scatter of the parameters
+bool pmPSFShapeParamsErrors(float *errorFloor, psVector *mag, psVector *e0res, psVector *e1res,
+                            psVector *e2res, psVector *mask, int nGroup, psStatsOptions stdevOpt)
+{
+
+    psStats *statsS = psStatsAlloc(stdevOpt);
+
+    // measure the trend in bins with 10 values each; if < 10 total, use them all
+    int nBin = PS_MAX (mag->n / nGroup, 1);
+
+    // output vectors for ApResid trend
+    psVector *dSo = psVectorAlloc (nBin, PS_TYPE_F32);
+
+    // use mag to group parameters in sequence
+    psVector *index = psVectorSortIndex (NULL, mag);
+
+    // subset vectors for mag and dap values within the given range
+    psVector *dE0subset = psVectorAllocEmpty (nGroup, PS_TYPE_F32);
+    psVector *dE1subset = psVectorAllocEmpty (nGroup, PS_TYPE_F32);
+    psVector *dE2subset = psVectorAllocEmpty (nGroup, PS_TYPE_F32);
+    psVector *mkSubset  = psVectorAllocEmpty (nGroup, PS_TYPE_U8);
+
+    int n = 0;
+    for (int i = 0; i < dSo->n; i++) {
+        int j;
+        for (j = 0; (j < nGroup) && (n < mag->n); j++, n++) {
+            int N = index->data.U32[n];
+            dE0subset->data.F32[j] = e0res->data.F32[N];
+            dE1subset->data.F32[j] = e1res->data.F32[N];
+            dE2subset->data.F32[j] = e2res->data.F32[N];
+
+            mkSubset->data.U8[j]   = mask->data.U8[N];
+        }
+        dE0subset->n = j;
+        dE1subset->n = j;
+        dE2subset->n = j;
+        mkSubset->n = j;
+
+        // calculate the root-mean-square of E0, E1, E2
+        float dEsquare = 0.0;
+        psStatsInit (statsS);
+        psVectorStats (statsS, dE0subset, NULL, mkSubset, 0xff);
+        dEsquare += PS_SQR(psStatsGetValue(statsS, stdevOpt));
+
+        psStatsInit (statsS);
+        psVectorStats (statsS, dE1subset, NULL, mkSubset, 0xff);
+        dEsquare += PS_SQR(psStatsGetValue(statsS, stdevOpt));
+
+        psStatsInit (statsS);
+        psVectorStats (statsS, dE2subset, NULL, mkSubset, 0xff);
+        dEsquare += PS_SQR(psStatsGetValue(statsS, stdevOpt));
+
+        dSo->data.F32[i] = sqrt(dEsquare);
+    }
+    psFree (dE0subset);
+    psFree (dE1subset);
+    psFree (dE2subset);
+    psFree (mkSubset);
+
+    psStats *minStats = psStatsAlloc(PS_STAT_MIN);
+    psVectorStats(minStats, dSo, NULL, NULL, 0);
+    *errorFloor = minStats->min;
+    psFree(minStats);
+
+    psFree(index);
+    psFree (dSo);
+
+    psFree (statsS);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPSFtry.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPSFtry.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPSFtry.h	(revision 22157)
@@ -0,0 +1,134 @@
+/* @file  pmPSFtry.h
+ *
+ * This file contains code that allows the user to try to fit several
+ * PSF models to an image.
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.19 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-12-15 01:23:18 $
+ * Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+# ifndef PM_PSF_TRY_H
+# define PM_PSF_TRY_H
+
+/// @addtogroup Objects Object Detection / Analysis Functions
+/// @{
+
+/**
+ *
+ * This structure contains a pointer to the collection of sources which will
+ * be used to test the PSF model form. It lists the pmModelType type of model
+ * being tests, and contains an element to store the resulting psf
+ * representation. In addition, this structure carries the complete collection of
+ * EXT (floating parameter) and PSF (fixed parameter) model fits to each of the
+ * sources modelEXT and modelPSF. It also contains a mask which is set by the
+ * model fitting and psf fitting steps. For each model, the value of the quality
+ * metric is stored in the vector metric and the fitted instrumental magnitude is
+ * stored in fitMag. The quality metric for the PSF model is the aperture
+ * magnitude minus the fitted magnitude for each source. This collection of
+ * aperture residuals is examined in the analysis process, and a linear trend of
+ * the residual with the inverse object flux (ie, 100:4his structure contains a
+ * pointer to the collection of sources which will be used to test the PSF model
+ * form. It lists the pmModelType type of modmag) is fitted. The result of this
+ * fit is a measured sky bias (systematic error in the sky measured by the fits),
+ * an effective infinite-magnitude aperture correction (ApResid), and the scatter
+ * of the aperture correction for the ensemble of PSF stars (dApResid). The
+ * ultimate metric to intercompare multiple types of PSF models is the value of
+ * the aperture correction scatter.
+ *
+ * XXX: There are many more members in the SDRS then in the prototype code.
+ * I stuck with the prototype code.
+ *
+ *
+ */
+typedef struct
+{
+    pmPSF      *psf;                    ///< Add comment.
+    psArray    *sources;                ///< pointers to the original sources
+    psVector   *mask;                   ///< Add comment.
+    psVector   *metric;                 ///< Add comment.
+    psVector   *metricErr;              ///< Add comment.
+    psVector   *fitMag;                 ///< Add comment.
+}
+pmPSFtry;
+
+
+/** pmPSFtryMaskValues
+ *
+ * The following datatype defines the masks used by the pmPSFtry analysis to
+ * identify sources which should or should not be included in the analysis.
+ *
+ */
+typedef enum {
+    PSFTRY_MASK_CLEAR    = 0x00,        ///< Add comment.
+    PSFTRY_MASK_OUTLIER  = 0x01,        ///< 1: outlier in psf polynomial fit (provided by psPolynomials)
+    PSFTRY_MASK_EXT_FAIL = 0x02,        ///< 2: ext model failed to converge
+    PSFTRY_MASK_PSF_FAIL = 0x04,        ///< 3: psf model failed to converge
+    PSFTRY_MASK_BAD_PHOT = 0x08,        ///< 4: invalid source photometry
+    PSFTRY_MASK_BAD_MODEL= 0x10,        ///< 5: could not build PSF from EXT (!??)
+    PSFTRY_MASK_ALL      = 0x1f,        ///< Add comment.
+} pmPSFtryMaskValues;
+
+
+/** pmPSFtryAlloc()
+ *
+ * Allocate a pmPSFtry data structure.
+ *
+ */
+
+pmPSFtry *pmPSFtryAlloc (const psArray *sources, const pmPSFOptions *options);
+bool psMemCheckPSFtry(psPtr ptr);
+
+/** pmPSFtryModel()
+ *
+ * This function takes the input collection of sources and performs a complete
+ * analysis to determine a PSF model of the given type (specified by model name).
+ * The result is a pmPSFtry with the results of the analysis.
+ *
+ */
+pmPSFtry *pmPSFtryModel (const psArray *sources, const char *modelName, pmPSFOptions *options, psMaskType maskVal, psMaskType mark);
+
+/** pmPSFtryMetric()
+ *
+ * This function is used to measure the PSF model metric for the set of
+ * results contained in the pmPSFtry structure.
+ *
+ */
+bool pmPSFtryMetric(
+    pmPSFtry *psfTry,                  ///< Add comment.
+    pmPSFOptions *options              ///< PSF fitting options
+);
+
+/** pmPSFtryMetric_Alt()
+ *
+ * This function is used to measure the PSF model metric for the set of
+ * results contained in the pmPSFtry structure (alternative implementation).
+ *
+ */
+bool pmPSFtryMetric_Alt(
+    pmPSFtry *try,                      ///< Add comment.
+    float RADIUS                        ///< Add comment.
+);
+
+/**
+ *
+ * This function takes a collection of pmModel fitted models from across a
+ * single image and builds a pmPSF representation of the PSF. The input array of
+ * model fits may consist of entries to be ignored (noted by a non-zero mask
+ * entry). The analysis of the models fits a 2D polynomial for each parameter to
+ * the collection of model parameters as a function of position (and
+ * normalization?). In this process, some of the input models may be marked as
+ * outliers and excluded from the fit. These elements will be marked with a
+ * specific mask value (1 == PSFTRY_MASK_OUTLIER).
+ *
+ */
+bool pmPSFFromPSFtry (pmPSFtry *psfTry);
+
+bool pmPSFFitShapeParams (pmPSF *psf, psArray *sources, psVector *x, psVector *y, psVector *srcMask);
+bool pmPSFFitShapeParamsMap (pmPSF *psf, int scale, float *errorFloor, float *errorTotal, psVector *mask, psVector *x, psVector *y, psVector *mag, psVector *e0obs, psVector *e1obs, psVector *e2obs, psVector *dz);
+bool pmPSFShapeParamsErrors (float *errorFloor, psVector *mag, psVector *e0res, psVector *e1res, psVector *e2res, psVector *mask, int nGroup, psStatsOptions stdevOpt);
+
+/// @}
+# endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPeaks.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPeaks.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPeaks.c	(revision 22157)
@@ -0,0 +1,568 @@
+/** @file  pmPeaks.c
+ *
+ *  This file defines functions to detect and manipulate peaks in images
+ *
+ *  @author GLG, MHPCC
+ *  @author EAM, IfA: significant modifications.
+ *
+ *  @version $Revision: 1.20 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-04-08 18:35:38 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmPeaks.h"
+
+/******************************************************************************
+AddPeak(): A private function which allocates a psArray, if the peaks
+argument is NULL, otherwise it adds the peak to that array.
+XXX EAM : row,col now refer to image coords, NOT parent (since this is private) 
+XXX EAM : now also calculates fractional peak positions from 3x3 bicube region
+*****************************************************************************/
+static psArray *AddPeak(psArray *peaks,
+                        const psImage *image,
+                        psS32 row,
+                        psS32 col,
+                        pmPeakType type)
+{
+    psTrace("psModules.objects", 5, "---- begin ----\n");
+
+    if (peaks == NULL) {
+        peaks = psArrayAllocEmpty(100);
+    }
+
+    // the peak position is in parent coordinates
+    pmPeak *peak = pmPeakAlloc(col + image->col0, row + image->row0, image->data.F32[row][col], type);
+
+    // measure fractional peak position using the 3x3 bicube fit
+
+    // ix,iy must land on image with 1 pixel border
+    int ix = PS_MAX (PS_MIN (col, image->numCols - 2), 1);
+    int iy = PS_MAX (PS_MIN (row, image->numRows - 2), 1);
+
+    // calculate peak position relative to ix,iy
+    // XXX these functions need to take a mask, weight, and calculate the errors
+    psPolynomial2D *bicube = psImageBicubeFit (image, ix + image->col0, iy + image->row0);
+    psPlane min = psImageBicubeMin (bicube);
+    psFree (bicube);
+
+    // if min point is too deviant, use the peak value
+    if ((fabs(min.x) < 1.5) && (fabs(min.y) < 1.5)) {
+        peak->xf = min.x + ix + image->col0;
+        peak->yf = min.y + iy + image->row0;
+	peak->dx = 0.0;
+	peak->dy = 0.0;
+    } else {
+        peak->xf = ix;
+        peak->yf = iy;
+	peak->dx = 1.0;
+	peak->dy = 1.0;
+    }
+
+    psArrayAdd(peaks, 100, peak);
+    psFree (peak);
+
+    psTrace("psModules.objects", 5, "---- end ----\n");
+    return(peaks);
+}
+
+/******************************************************************************
+getRowVectorFromImage(): a private function which simply returns a
+psVector containing the specified row of data from the psImage.
+ 
+XXX: Is there a better way to do this?
+XXX EAM: does this really need to alloc a new vector???
+*****************************************************************************/
+static psVector *getRowVectorFromImage(psImage *image,
+                                       psU32 row)
+{
+    psTrace("psModules.objects", 4, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_IMAGE_NON_NULL(image, NULL);
+    PS_ASSERT_IMAGE_TYPE(image, PS_TYPE_F32, NULL);
+
+    psVector *tmpVector = psVectorAlloc(image->numCols, PS_TYPE_F32);
+    for (psU32 col = 0; col < image->numCols ; col++) {
+        tmpVector->data.F32[col] = image->data.F32[row][col];
+    }
+    psTrace("psModules.objects", 4, "---- %s() end ----\n", __func__);
+    return(tmpVector);
+}
+
+/******************************************************************************
+isItInThisRegion(): a private function which simply returns a
+boolean denoting if specified coordinate is in the region.
+XXX: Macro this.
+*****************************************************************************/
+static bool isItInThisRegion(const psRegion valid,
+                             psS32 x,
+                             psS32 y)
+{
+    psTrace("psModules.objects", 4, "---- %s() begin ----\n", __func__);
+    if ((x >= valid.x0) &&
+            (x <= valid.x1) &&
+            (y >= valid.y0) &&
+            (y <= valid.y1)) {
+        psTrace("psModules.objects", 4, "---- %s(true) end ----\n", __func__);
+        return(true);
+    }
+    psTrace("psModules.objects", 4, "---- %s(false) end ----\n", __func__);
+    return(false);
+}
+
+/******************************************************************************
+pmPeakAlloc(): Allocate the pmPeak data structure and set appropriate members.
+*****************************************************************************/
+static void peakFree(pmPeak *tmp)
+{} //
+
+pmPeak *pmPeakAlloc(psS32 x,
+                    psS32 y,
+                    psF32 value,
+                    pmPeakType type)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    static int id = 1;
+    pmPeak *tmp = (pmPeak *) psAlloc(sizeof(pmPeak));
+    *(int *)&tmp->id = id++;
+    tmp->x = x;
+    tmp->y = y;
+    tmp->value = value;
+    tmp->flux = 0;
+    tmp->SN = 0;
+    tmp->xf = x;
+    tmp->yf = y;
+    tmp->assigned = false;
+    tmp->type = type;
+
+    psMemSetDeallocator(tmp, (psFreeFunc) peakFree);
+
+    psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+    return(tmp);
+}
+
+bool psMemCheckPeak(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) peakFree);
+}
+
+
+// psSort comparison function for peaks
+// XXX: Add error-checking for NULL args
+int pmPeaksCompareAscend (const void **a, const void **b)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    pmPeak *A = *(pmPeak **)a;
+    pmPeak *B = *(pmPeak **)b;
+
+    psF32 diff;
+
+    diff = A->value - B->value;
+    if (diff < FLT_EPSILON) {
+        psTrace("psModules.objects", 3, "---- %s(-1) end ----\n", __func__);
+        return (-1);
+    } else if (diff > FLT_EPSILON) {
+        psTrace("psModules.objects", 3, "---- %s(+1) end ----\n", __func__);
+        return (+1);
+    }
+    psTrace("psModules.objects", 3, "---- %s(0) end ----\n", __func__);
+    return (0);
+}
+
+// psSort comparison function for peaks
+// XXX: Add error-checking for NULL args
+int pmPeaksCompareDescend (const void **a, const void **b)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    pmPeak *A = *(pmPeak **)a;
+    pmPeak *B = *(pmPeak **)b;
+
+    psF32 diff;
+
+    diff = A->value - B->value;
+    if (diff < FLT_EPSILON) {
+        psTrace("psModules.objects", 3, "---- %s(+1) end ----\n", __func__);
+        return (+1);
+    } else if (diff > FLT_EPSILON) {
+        psTrace("psModules.objects", 3, "---- %s(-1) end ----\n", __func__);
+        return (-1);
+    }
+    psTrace("psModules.objects", 3, "---- %s(0) end ----\n", __func__);
+    return (0);
+}
+
+// sort by SN (descending)
+int pmPeakSortBySN (const void **a, const void **b)
+{
+    pmPeak *A = *(pmPeak **)a;
+    pmPeak *B = *(pmPeak **)b;
+
+    psF32 fA = A->SN;
+    psF32 fB = B->SN;
+    if (isnan (fA)) fA = 0;
+    if (isnan (fB)) fB = 0;
+
+    psF32 diff = fA - fB;
+    if (diff > FLT_EPSILON) return (-1);
+    if (diff < FLT_EPSILON) return (+1);
+    return (0);
+}
+
+// sort by Y (ascending)
+int pmPeakSortByY (const void **a, const void **b)
+{
+    pmPeak *A = *(pmPeak **)a;
+    pmPeak *B = *(pmPeak **)b;
+
+    psF32 fA = A->y;
+    psF32 fB = B->y;
+
+    psF32 diff = fA - fB;
+    if (diff > FLT_EPSILON) return (+1);
+    if (diff < FLT_EPSILON) return (-1);
+    return (0);
+}
+
+/******************************************************************************
+pmPeaksInVector(vector, threshold): Find all local peaks in the given vector
+above the given threshold.  Returns a vector of type PS_TYPE_U32 containing
+the location (x value) of all peaks.
+ 
+XXX: What types should be supported?  Only F32 is implemented.
+ 
+XXX: We currently step through the input vector twice; once to determine the
+size of the output vector, then to set the values of the output vector.
+Depending upon actual use, this may need to be optimized.
+*****************************************************************************/
+psVector *pmPeaksInVector(const psVector *vector,
+			 psF32 threshold)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_VECTOR_NON_NULL(vector, NULL);
+    PS_ASSERT_VECTOR_NON_EMPTY(vector, NULL);
+    PS_ASSERT_VECTOR_TYPE(vector, PS_TYPE_F32, NULL);
+    int count = 0;
+    int n = vector->n;
+
+    //
+    // Special case: the input vector has a single element.
+    //
+    if (n == 1) {
+        psVector *tmpVector = NULL;
+        if (vector->data.F32[0] > threshold) {
+            tmpVector = psVectorAlloc(1, PS_TYPE_U32);
+            tmpVector->data.U32[0] = 0;
+        } else {
+            tmpVector = psVectorAlloc(0, PS_TYPE_U32);
+        }
+        psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+        return(tmpVector);
+    }
+
+    //
+    // Determine if first pixel is a peak
+    //
+    if ((vector->data.F32[0] > vector->data.F32[1]) &&
+            (vector->data.F32[0] > threshold)) {
+        count++;
+    }
+
+    //
+    // Determine if interior pixels are peaks
+    //
+    for (psU32 i = 1; i < n-1 ; i++) {
+        if ((vector->data.F32[i] > vector->data.F32[i-1]) &&
+                (vector->data.F32[i] >= vector->data.F32[i+1]) &&
+                (vector->data.F32[i] > threshold)) {
+            count++;
+        }
+    }
+
+    //
+    // Determine if last pixel is a peak
+    //
+    if ((vector->data.F32[n-1] > vector->data.F32[n-2]) &&
+            (vector->data.F32[n-1] > threshold)) {
+        count++;
+    }
+
+    //
+    // We know how many peaks exist, so we now allocate a psVector to store
+    // those peaks.
+    //
+    psVector *tmpVector = psVectorAlloc(count, PS_TYPE_U32);
+    count = 0;
+
+    //
+    // Determine if first pixel is a peak
+    //
+    if ((vector->data.F32[0] > vector->data.F32[1]) &&
+            (vector->data.F32[0] > threshold)) {
+        tmpVector->data.U32[count++] = 0;
+    }
+
+    //
+    // Determine if interior pixels are peaks
+    //
+    for (psU32 i = 1; i < (n-1) ; i++) {
+        if ((vector->data.F32[i] > vector->data.F32[i-1]) &&
+                (vector->data.F32[i] >= vector->data.F32[i+1]) &&
+                (vector->data.F32[i] > threshold)) {
+            tmpVector->data.U32[count++] = i;
+        }
+    }
+
+    //
+    // Determine if last pixel is a peak
+    //
+    if ((vector->data.F32[n-1] > vector->data.F32[n-2]) &&
+            (vector->data.F32[n-1] > threshold)) {
+        tmpVector->data.U32[count++] = n-1;
+    }
+
+    psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+    return(tmpVector);
+}
+
+
+/******************************************************************************
+pmPeaksInImage(image, threshold): Find all local peaks in the given psImage
+above the given threshold.  Returns a psArray containing location (x/y value)
+of all peaks.
+ 
+XXX: I'm not convinced the peak type definition in the SDRS is mutually
+exclusive.  Some peaks can have multiple types.  Edges for sure.  Also, a
+digonal line with the same value at each point will have a peak for every
+point on that line.
+ 
+XXX: This does not work if image has either a single row, or a single column.
+ 
+The peak is returned in the image parent coordinates
+
+*****************************************************************************/
+psArray *pmPeaksInImage(const psImage *image, psF32 threshold)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_IMAGE_NON_NULL(image, NULL);
+    PS_ASSERT_IMAGE_TYPE(image, PS_TYPE_F32, NULL);
+    if ((image->numRows == 1) || (image->numCols == 1)) {
+        psError(PS_ERR_UNKNOWN, true, "Currently, input image must have at least 2 rows and 2 columns.");
+        psTrace("psModules.objects", 3, "---- %s(NULL) end ----\n", __func__);
+        return(NULL);
+    }
+    psVector *tmpRow = NULL;
+    psU32 col = 0;
+    psU32 row = 0;
+    psArray *list = NULL;
+
+    // Find peaks in row 0 only.
+    row = 0;
+    tmpRow = getRowVectorFromImage((psImage *) image, row);
+    psVector *row1 = pmPeaksInVector(tmpRow, threshold);
+    // pmPeaksInVector returns coords in the vector, not corrected for col0
+
+    for (psU32 i = 0 ; i < row1->n ; i++ ) {
+        col = row1->data.U32[i];
+        // is pixel (0,0) is a peak?
+        if (col == 0) {
+            if ( (image->data.F32[row][col] >  image->data.F32[row][col+1]) &&
+                    (image->data.F32[row][col] >  image->data.F32[row+1][col]) &&
+                    (image->data.F32[row][col] >= image->data.F32[row+1][col+1])) {
+
+                if (image->data.F32[row][col] > threshold) {
+                    list = AddPeak(list, image, row, col, PM_PEAK_EDGE);
+                }
+            }
+        } else if (col < (image->numCols - 1)) {
+            if ( (image->data.F32[row][col] >= image->data.F32[row][col-1]) &&
+                    (image->data.F32[row][col] >  image->data.F32[row][col+1]) &&
+                    (image->data.F32[row][col] >= image->data.F32[row+1][col-1]) &&
+                    (image->data.F32[row][col] >  image->data.F32[row+1][col]) &&
+                    (image->data.F32[row][col] >= image->data.F32[row+1][col+1])) {
+                if (image->data.F32[row][col] > threshold) {
+                    list = AddPeak(list, image, row, col, PM_PEAK_EDGE);
+                }
+            }
+
+        } else if (col == (image->numCols - 1)) {
+            if ( (image->data.F32[row][col] >= image->data.F32[row][col-1]) &&
+                    (image->data.F32[row][col] > image->data.F32[row+1][col]) &&
+                    (image->data.F32[row][col] >= image->data.F32[row+1][col-1])) {
+                if (image->data.F32[row][col] > threshold) {
+                    list = AddPeak(list, image, row, col, PM_PEAK_EDGE);
+                }
+            }
+
+        } else {
+            psError(PS_ERR_UNKNOWN, true, "peak specified valid column range.");
+        }
+    }
+    psFree (tmpRow);
+    psFree (row1);
+
+    //
+    // Exit if this image has a single row.
+    //
+    if (image->numRows == 1) {
+        psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+        return(list);
+    }
+
+    //
+    // Find peaks in interior rows only.
+    //
+    for (row = 1 ; row < (image->numRows - 1) ; row++) {
+        tmpRow = getRowVectorFromImage((psImage *) image, row);
+        row1 = pmPeaksInVector(tmpRow, threshold);
+
+        // Step through all local peaks in this row.
+        for (psU32 i = 0 ; i < row1->n ; i++ ) {
+            pmPeakType myType = PM_PEAK_UNDEF;
+            col = row1->data.U32[i];
+
+            if (col == 0) {
+                // If col==0, then we can not read col-1 pixels
+                if ((image->data.F32[row][col] >  image->data.F32[row-1][col]) &&
+                        (image->data.F32[row][col] >= image->data.F32[row-1][col+1]) &&
+                        (image->data.F32[row][col] >= image->data.F32[row][col+1]) &&
+                        (image->data.F32[row][col] >= image->data.F32[row+1][col]) &&
+                        (image->data.F32[row][col] >= image->data.F32[row+1][col+1])) {
+                    myType = PM_PEAK_EDGE;
+                    list = AddPeak(list, image, row, col, myType);
+                }
+            } else if (col < (image->numCols - 1)) {
+                // This is an interior pixel
+                if ((image->data.F32[row][col] >= image->data.F32[row-1][col-1]) &&
+                        (image->data.F32[row][col] >  image->data.F32[row-1][col]) &&
+                        (image->data.F32[row][col] >= image->data.F32[row-1][col+1]) &&
+                        (image->data.F32[row][col] > image->data.F32[row][col-1]) &&
+                        (image->data.F32[row][col] >= image->data.F32[row][col+1]) &&
+                        (image->data.F32[row][col] >= image->data.F32[row+1][col-1]) &&
+                        (image->data.F32[row][col] >= image->data.F32[row+1][col]) &&
+                        (image->data.F32[row][col] >= image->data.F32[row+1][col+1])) {
+                    if (image->data.F32[row][col] > threshold) {
+                        if ((image->data.F32[row][col] > image->data.F32[row-1][col-1]) &&
+                                (image->data.F32[row][col] > image->data.F32[row-1][col]) &&
+                                (image->data.F32[row][col] > image->data.F32[row-1][col+1]) &&
+                                (image->data.F32[row][col] > image->data.F32[row][col-1]) &&
+                                (image->data.F32[row][col] > image->data.F32[row][col+1]) &&
+                                (image->data.F32[row][col] > image->data.F32[row+1][col-1]) &&
+                                (image->data.F32[row][col] > image->data.F32[row+1][col]) &&
+                                (image->data.F32[row][col] > image->data.F32[row+1][col+1])) {
+                            myType = PM_PEAK_LONE;
+                        }
+
+                        if ((image->data.F32[row][col] == image->data.F32[row-1][col-1]) ||
+                                (image->data.F32[row][col] == image->data.F32[row-1][col]) ||
+                                (image->data.F32[row][col] == image->data.F32[row-1][col+1]) ||
+                                (image->data.F32[row][col] == image->data.F32[row][col-1]) ||
+                                (image->data.F32[row][col] == image->data.F32[row][col+1]) ||
+                                (image->data.F32[row][col] == image->data.F32[row+1][col-1]) ||
+                                (image->data.F32[row][col] == image->data.F32[row+1][col]) ||
+                                (image->data.F32[row][col] == image->data.F32[row+1][col+1])) {
+                            myType = PM_PEAK_FLAT;
+                        }
+
+                        list = AddPeak(list, image, row, col, myType);
+
+                    }
+                }
+            } else if (col == (image->numCols - 1)) {
+                // If col==numCols - 1, then we can not read col+1 pixels
+                if ((image->data.F32[row][col] >= image->data.F32[row-1][col-1]) &&
+                        (image->data.F32[row][col] >  image->data.F32[row-1][col]) &&
+                        (image->data.F32[row][col] > image->data.F32[row][col-1]) &&
+                        (image->data.F32[row][col] >= image->data.F32[row][col+1]) &&
+                        (image->data.F32[row][col] >= image->data.F32[row+1][col-1]) &&
+                        (image->data.F32[row][col] >= image->data.F32[row+1][col])) {
+                    myType = PM_PEAK_EDGE;
+                    list = AddPeak(list, image, row, col, myType);
+                }
+            } else {
+                psError(PS_ERR_UNKNOWN, true, "peak specified outside valid column range.");
+            }
+
+        }
+        psFree (tmpRow);
+        psFree (row1);
+    }
+
+    //
+    // Find peaks in the last row only.
+    //
+    row = image->numRows - 1;
+    tmpRow = getRowVectorFromImage((psImage *) image, row);
+    row1 = pmPeaksInVector(tmpRow, threshold);
+    for (psU32 i = 0 ; i < row1->n ; i++ ) {
+        col = row1->data.U32[i];
+        if (col == 0) {
+            if ( (image->data.F32[row][col] >  image->data.F32[row-1][col]) &&
+                    (image->data.F32[row][col] >= image->data.F32[row-1][col+1]) &&
+                    (image->data.F32[row][col] >  image->data.F32[row][col+1])) {
+                if (image->data.F32[row][col] > threshold) {
+                    list = AddPeak(list, image, row, col, PM_PEAK_EDGE);
+                }
+            }
+        } else if (col < (image->numCols - 1)) {
+            if ( (image->data.F32[row][col] >= image->data.F32[row-1][col-1]) &&
+                    (image->data.F32[row][col] >  image->data.F32[row-1][col]) &&
+                    (image->data.F32[row][col] >= image->data.F32[row-1][col+1]) &&
+                    (image->data.F32[row][col] >  image->data.F32[row][col-1]) &&
+                    (image->data.F32[row][col] >= image->data.F32[row][col+1])) {
+                if (image->data.F32[row][col] > threshold) {
+                    list = AddPeak(list, image, row, col, PM_PEAK_EDGE);
+                }
+            }
+
+        } else if (col == (image->numCols - 1)) {
+            if ( (image->data.F32[row][col] >= image->data.F32[row-1][col-1]) &&
+                    (image->data.F32[row][col] >  image->data.F32[row-1][col]) &&
+                    (image->data.F32[row][col] >  image->data.F32[row][col-1])) {
+                if (image->data.F32[row][col] > threshold) {
+                    list = AddPeak(list, image, row, col, PM_PEAK_EDGE);
+                }
+            }
+        } else {
+            psError(PS_ERR_UNKNOWN, true, "peak specified outside valid column range.");
+        }
+    }
+    psFree (tmpRow);
+    psFree (row1);
+    psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+    return(list);
+}
+
+// return a new array of peaks which are in the valid region and below threshold
+// XXX this function is unused and probably could be dropped
+psArray *pmPeaksSubset(
+    psArray *peaks,
+    psF32 maxValue,
+    const psRegion valid)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(peaks, NULL);
+
+    psArray *output = psArrayAllocEmpty (200);
+
+    psTrace ("psModules.objects", 3, "list size is %ld\n", peaks->n);
+
+    for (int i = 0; i < peaks->n; i++) {
+        pmPeak *tmpPeak = (pmPeak *) peaks->data[i];
+        if (tmpPeak->value > maxValue)
+            continue;
+        if (isItInThisRegion(valid, tmpPeak->x, tmpPeak->y))
+            continue;
+        psArrayAdd (output, 200, tmpPeak);
+    }
+    psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+    return(output);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPeaks.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPeaks.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmPeaks.h	(revision 22157)
@@ -0,0 +1,144 @@
+/* @file  pmPeaks.h
+ *
+ * The process of finding, measuring, and classifying astronomical sources on
+ * images is one of the critical tasks of the IPP or any astronomical software
+ * system. This file will define structures and functions related to the task
+ * of source detection and measurement. The elements defined in this section
+ * are generally low-level components which can be connected together to
+ * construct a complete object measurement suite.
+ *
+ * @author GLG, MHPCC
+ *
+ * @version $Revision: 1.11 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-04-08 18:35:38 $
+ * Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+# ifndef PM_PEAKS_H
+# define PM_PEAKS_H
+
+/// @addtogroup Objects Object Detection / Analysis Functions
+/// @{
+
+/** pmPeakType
+ *
+ *  A peak pixel may have several features which may be determined when the
+ *  peak is found or measured. These are specified by the pmPeakType enum.
+ *  PM_PEAK_LONE represents a single pixel which is higher than its 8 immediate
+ *  neighbors.  The PM_PEAK_EDGE represents a peak pixel which touching the image
+ *  edge. The PM_PEAK_FLAT represents a peak pixel which has more than a specific
+ *  number of neighbors at the same value, within some tolarence:
+ *
+ */
+typedef enum {
+    PM_PEAK_LONE,                       ///< Isolated peak.
+    PM_PEAK_EDGE,                       ///< Peak on edge.
+    PM_PEAK_FLAT,                       ///< Peak has equal-value neighbors.
+    PM_PEAK_UNDEF                       ///< Undefined.
+} pmPeakType;
+
+
+/** pmPeak data structure
+ *
+ *  A source has the capacity for several types of measurements. The
+ *  simplest measurement of a source is the location and flux of the peak pixel
+ *  associated with the source:
+ *
+ */
+typedef struct
+{
+    const int id;   ///< Unique ID for object
+    int x;                              ///< X-coordinate of peak pixel.
+    int y;                              ///< Y-coordinate of peak pixel.
+    float xf;                           ///< bicube fit to peak coord (x)
+    float yf;                           ///< bicube fit to peak coord (y)
+    float dx;                           ///< bicube fit error on peak coord (x)
+    float dy;                           ///< bicube fit error on peak coord (y)
+    float value;                        ///< level in detection image
+    float flux;                         ///< level in unsmoothed sci image
+    float SN;                           ///< S/N implied by detection level
+    bool assigned;                      ///< is peak assigned to a source?
+    pmPeakType type;			///< Description of peak.
+}
+pmPeak;
+
+
+/** pmPeakAlloc()
+ *
+ *  @return pmPeak*    newly allocated pmPeak with all internal pointers set to NULL
+ */
+pmPeak *pmPeakAlloc(
+    int x,    ///< Row-coordinate in image space
+    int y,    ///< Col-coordinate in image space
+    float counts,   ///< The value of the peak pixel
+    pmPeakType type   ///< The type of peak pixel
+);
+
+bool psMemCheckPeak(psPtr ptr);
+
+/** pmPeaksInVector()
+ *
+ * Find all local peaks in the given vector above the given threshold. A peak
+ * is defined as any element with a value greater than its two neighbors and with
+ * a value above the threshold. Two types of special cases must be addressed.
+ * Equal value elements: If an element has the same value as the following
+ * element, it is not considered a peak. If an element has the same value as the
+ * preceding element (but not the following), then it is considered a peak. Note
+ * that this rule (arbitrarily) identifies flat regions by their trailing edge.
+ * Edge cases: At start of the vector, the element must be higher than its
+ * neighbor. At the end of the vector, the element must be higher or equal to its
+ * neighbor. These two rules again places the peak associated with a flat region
+ * which touches the image edge at the image edge. The result of this function is
+ * a vector containing the coordinates (element number) of the detected peaks
+ * (type psU32).
+ *
+ */
+psVector *pmPeaksInVector(
+    const psVector *vector,  ///< The input vector (float)
+    float threshold   ///< Threshold above which to find a peak
+);
+
+
+/** pmPeaksInImage()
+ *
+ * Find all local peaks in the given image above the given threshold. This
+ * function should find all row peaks using pmFindVectorPeaks, then test each row
+ * peak and exclude peaks which are not local peaks. A peak is a local peak if it
+ * has a higher value than all 8 neighbors. If the peak has the same value as its
+ * +y neighbor or +x neighbor, it is NOT a local peak. If any other neighbors
+ * have an equal value, the peak is considered a valid peak. Note two points:
+ * first, the +x neighbor condition is already enforced by pmFindVectorPeaks.
+ * Second, these rules have the effect of making flat-topped regions have single
+ * peaks at the (+x,+y) corner. When selecting the peaks, their type must also be
+ * set. The result of this function is an array of pmPeak entries.
+ *
+ */
+psArray *pmPeaksInImage(
+    const psImage *image,  ///< The input image where peaks will be found (float)
+    float threshold   ///< Threshold above which to find a peak
+);
+
+
+/** pmPeaksSubset()
+ *
+ * Create a new peaks array, removing certain types of peaks from the input
+ * array of peaks based on the given criteria. Peaks should be eliminated if they
+ * have a peak value above the given maximum value limit or if the fall outside
+ * the valid region.  The result of the function is a new array with a reduced
+ * number of peaks.
+ *
+ */
+psArray *pmPeaksSubset(
+    psArray *peaks,                     ///< Add comment.
+    float maxvalue,                     ///< Add comment.
+    const psRegion valid                ///< Add comment.
+);
+
+int pmPeaksCompareAscend (const void **a, const void **b);
+int pmPeaksCompareDescend (const void **a, const void **b);
+
+int pmPeakSortBySN (const void **a, const void **b);
+int pmPeakSortByY (const void **a, const void **b);
+
+/// @}
+# endif /* PM_PEAKS_H */
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmResiduals.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmResiduals.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmResiduals.c	(revision 22157)
@@ -0,0 +1,59 @@
+/** @file pmResiduals.c
+ *
+ * Functions to manipulate the residual tables (data - model).
+ *
+ * @author EAM, IfA
+ * @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-11-10 01:09:20 $
+ * Copyright 2004 IfA, University of Hawaii
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pslib.h>
+#include "pmResiduals.h"
+
+static void pmResidualsFree (pmResiduals *resid) {
+
+    if (resid == NULL) return;
+
+    psFree (resid->Ro);
+    psFree (resid->Rx);
+    psFree (resid->Ry);
+    psFree (resid->weight);
+    psFree (resid->mask);
+    return;
+}
+
+pmResiduals *pmResidualsAlloc (int xSize, int ySize, int xBin, int yBin) {
+
+    pmResiduals *resid = (pmResiduals *) psAlloc(sizeof(pmResiduals));
+    psMemSetDeallocator(resid, (psFreeFunc) pmResidualsFree);
+
+    int nX = xSize * xBin;
+    int nY = ySize * yBin;
+
+    nX = (nX % 2) ? nX : nX + 1;
+    nY = (nY % 2) ? nY : nY + 1;
+
+    resid->Ro  = psImageAlloc (nX, nY, PS_TYPE_F32);
+    resid->Rx  = psImageAlloc (nX, nY, PS_TYPE_F32);
+    resid->Ry  = psImageAlloc (nX, nY, PS_TYPE_F32);
+    resid->weight = psImageAlloc (nX, nY, PS_TYPE_F32);
+    resid->mask   = psImageAlloc (nX, nY, PS_TYPE_U8);
+
+    resid->xBin = xBin;
+    resid->yBin = yBin;
+    resid->xCenter = 0.5*(nX - 1); 
+    resid->yCenter = 0.5*(nY - 1);
+    return resid;
+}
+
+bool psMemCheckResiduals(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) pmResidualsFree);
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmResiduals.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmResiduals.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmResiduals.h	(revision 22157)
@@ -0,0 +1,34 @@
+/** @file pmResiduals.h
+ *
+ * Functions to manipulate the residual tables (data - model).
+ *
+ * @author EAM, IfA
+ * @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-11-10 01:09:20 $
+ * Copyright 2004 IfA, University of Hawaii
+ */
+
+# ifndef PM_RESIDUALS_H
+# define PM_RESIDUALS_H
+/// @addtogroup Objects Object Detection / Analysis Functions
+/// @{
+
+/** residual tables for sources 
+ */
+typedef struct {
+    psImage *Ro;
+    psImage *Rx;
+    psImage *Ry;
+    psImage *weight;
+    psImage *mask;
+    int xBin;
+    int yBin;
+    int xCenter;
+    int yCenter;
+} pmResiduals;
+
+pmResiduals *pmResidualsAlloc (int xSize, int ySize, int xBin, int yBin);
+bool psMemCheckResiduals(psPtr ptr);
+
+/// @}
+# endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSource.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSource.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSource.c	(revision 22157)
@@ -0,0 +1,1047 @@
+/** @file  pmSource.c
+ *
+ *  Functions to define and manipulate sources on images
+ *
+ *  @author GLG, MHPCC
+ *  @author EAM, IfA: significant modifications.
+ *
+ *  @version $Revision: 1.54.8.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-07-11 18:35:04 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <strings.h>
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAMaskWeight.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmResiduals.h"
+#include "pmGrowthCurve.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+
+static void sourceFree(pmSource *tmp)
+{
+    if (!tmp)
+        return;
+
+    psTrace("psModules.objects", 5, "---- begin ----\n");
+    psFree(tmp->peak);
+    psFree(tmp->pixels);
+    psFree(tmp->weight);
+    psFree(tmp->maskObj);
+    psFree(tmp->maskView);
+    psFree(tmp->modelFlux);
+    psFree(tmp->psfFlux);
+    psFree(tmp->moments);
+    psFree(tmp->modelPSF);
+    psFree(tmp->modelEXT);
+    psFree(tmp->modelFits);
+    psFree(tmp->extpars);
+    psFree(tmp->blends);
+    psTrace("psModules.objects", 5, "---- end ----\n");
+}
+
+// free only the pixel data associated with this source
+void pmSourceFreePixels(pmSource *source)
+{
+
+    if (!source)
+        return;
+
+    psFree (source->pixels);
+    psFree (source->weight);
+    psFree (source->maskObj);
+    psFree (source->maskView);
+    psFree (source->modelFlux);
+    psFree (source->psfFlux);
+
+    source->pixels = NULL;
+    source->weight = NULL;
+    source->maskObj = NULL;
+    source->maskView = NULL;
+    source->modelFlux = NULL;
+    source->psfFlux = NULL;
+    return;
+}
+
+bool psMemCheckSource(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) sourceFree);
+}
+
+/******************************************************************************
+pmSourceAlloc(): Allocate the pmSource structure and initialize its members
+to NULL.
+*****************************************************************************/
+
+pmSource *pmSourceAlloc()
+{
+    psTrace("psModules.objects", 5, "---- begin ----\n");
+    static int id = 1;
+    pmSource *source = (pmSource *) psAlloc(sizeof(pmSource));
+    *(int *)&source->id = id++;
+    source->seq = -1;
+    source->peak = NULL;
+    source->pixels = NULL;
+    source->weight = NULL;
+    source->maskObj = NULL;
+    source->maskView = NULL;
+    source->modelFlux = NULL;
+    source->psfFlux = NULL;
+    source->moments = NULL;
+    source->blends = NULL;
+    source->modelPSF = NULL;
+    source->modelEXT = NULL;
+    source->modelFits = NULL;
+    source->type = PM_SOURCE_TYPE_UNKNOWN;
+    source->mode = PM_SOURCE_MODE_DEFAULT;
+    source->extpars = NULL;
+    source->region = psRegionSet(NAN, NAN, NAN, NAN);
+    psMemSetDeallocator(source, (psFreeFunc) sourceFree);
+
+    // default values are NAN
+    source->psfMag = NAN;
+    source->extMag = NAN;
+    source->errMag = NAN;
+    source->apMag  = NAN;
+    source->sky    = NAN;
+    source->skyErr = NAN;
+    source->pixWeight = NAN;
+
+    source->psfChisq = NAN;
+    source->crNsigma = NAN;
+    source->extNsigma = NAN;
+
+    psTrace("psModules.objects", 5, "---- end ----\n");
+    return(source);
+}
+
+/******************************************************************************
+pmSourceCopy(): copy the pmSource structure and contents
+XXX : are we OK with incrementing the ID?
+*****************************************************************************/
+pmSource *pmSourceCopy(pmSource *in)
+{
+    if (in == NULL) {
+        return(NULL);
+    }
+    // this copy is used to allow multiple fit attempts on the
+    // same object.  the pixels, weight, and mask arrays all point to
+    // the same original subarrays.  the peak and moments point at
+    // the original values.
+    pmSource *source = pmSourceAlloc ();
+
+    // this is actually the same peak as the original, is this the correct way to handle this?
+    if (in->peak != NULL) {
+        source->peak = pmPeakAlloc (in->peak->x, in->peak->y, in->peak->value, in->peak->type);
+        source->peak->xf = in->peak->xf;
+        source->peak->yf = in->peak->yf;
+        source->peak->flux = in->peak->flux;
+        source->peak->SN = in->peak->SN;
+    }
+
+    // copy the values in the moments structure
+    if (in->moments != NULL) {
+        source->moments  =  pmMomentsAlloc();
+        *source->moments = *in->moments;
+    }
+
+    // These images are all views to the parent.
+    // We want a new view, but pointing at the same pixels.
+    source->pixels   = psImageCopyView(NULL, in->pixels);
+    source->weight   = psImageCopyView(NULL, in->weight);
+    source->maskView = in->maskView ? psImageCopyView(NULL, in->maskView) : NULL;
+
+    // the maskObj is a unique mask array; create a new mask image
+    source->maskObj = in->maskObj ? psImageCopy (NULL, in->maskObj, PS_TYPE_MASK) : NULL;
+
+    source->type = in->type;
+    source->mode = in->mode;
+
+    return(source);
+}
+
+// x,y are defined in the parent image coords of readout->image
+bool pmSourceDefinePixels(pmSource *mySource,
+                          const pmReadout *readout,
+                          psF32 x,
+                          psF32 y,
+                          psF32 Radius)
+{
+    PS_ASSERT_PTR_NON_NULL(mySource, false);
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(readout->image, false);
+    PS_ASSERT_INT_POSITIVE(Radius, false);
+
+    psRegion srcRegion;
+
+    // Grab a subimage of the original image of size (2 * outerRadius).
+    srcRegion = psRegionForSquare (x, y, Radius);
+    srcRegion = psRegionForImage (readout->image, srcRegion);
+
+    // these images are subset images of the equivalent parents
+    mySource->pixels = psImageSubset(readout->image, srcRegion);
+    if (readout->weight) {
+        mySource->weight = psImageSubset(readout->weight, srcRegion);
+    }
+    if (readout->mask) {
+        mySource->maskView = psImageSubset(readout->mask,  srcRegion);
+        // the object mask is a copy, and used to define the source pixels
+        mySource->maskObj = psImageCopy(NULL, mySource->maskView, PS_TYPE_MASK);
+    }
+    mySource->region   = srcRegion;
+
+    return true;
+}
+
+bool pmSourceRedefinePixels(pmSource *mySource,
+                            const pmReadout *readout,
+                            psF32 x,
+                            psF32 y,
+                            psF32 Radius)
+{
+    PS_ASSERT_PTR_NON_NULL(mySource, false);
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(readout->image, false);
+    PS_ASSERT_INT_POSITIVE(Radius, false);
+
+    bool extend;
+    psRegion newRegion;
+
+    // check to see if new region is completely contained within old region
+    newRegion = psRegionForSquare (x, y, Radius);
+    newRegion = psRegionForImage (readout->image, newRegion);
+
+    extend = false;
+    extend |= (int)(newRegion.x0) < (int)(mySource->region.x0);
+    extend |= (int)(newRegion.x1) > (int)(mySource->region.x1);
+    extend |= (int)(newRegion.y0) < (int)(mySource->region.y0);
+    extend |= (int)(newRegion.y1) > (int)(mySource->region.y1);
+
+    extend |= (mySource->pixels == NULL);
+    extend |= (mySource->weight == NULL);
+    extend |= (mySource->maskObj == NULL);
+    extend |= (mySource->maskView == NULL);
+
+    // extend = true;
+    if (extend) {
+        // re-create the subimage
+        psFree (mySource->pixels);
+        psFree (mySource->weight);
+        psFree (mySource->maskView);
+
+        mySource->pixels   = psImageSubset(readout->image,  newRegion);
+        mySource->weight   = psImageSubset(readout->weight, newRegion);
+        mySource->maskView = psImageSubset(readout->mask,   newRegion);
+        mySource->region   = newRegion;
+
+        // re-copy the main mask pixels.  NOTE: the user will need to reset the object mask
+        // pixels (eg, with psImageKeepCircle)
+        mySource->maskObj = psImageCopy (mySource->maskObj, mySource->maskView, PS_TYPE_MASK);
+
+        // drop the old modelFlux pixels and force the user to re-create
+        psFree (mySource->modelFlux);
+        mySource->modelFlux = NULL;
+
+        // drop the old psfFlux pixels and force the user to re-create
+        psFree (mySource->psfFlux);
+        mySource->psfFlux = NULL;
+    }
+    return extend;
+}
+
+/******************************************************************************
+    pmSourcePSFClump(source, recipe): Find the likely PSF clump in the
+    sigma-x, sigma-y plane. return 0,0 clump in case of error.
+*****************************************************************************/
+
+// XXX EAM include a S/N cutoff in selecting the sources?
+// XXX this function should probably accept the values, not a recipe. wrap with a
+// psphot-specific function which applies the recipe values
+pmPSFClump pmSourcePSFClump(psArray *sources, psMetadata *recipe)
+{
+    psTrace("psModules.objects", 5, "---- begin ----\n");
+
+    # define NPIX 10
+    # define SCALE 0.1
+
+    psArray *peaks  = NULL;
+    pmPSFClump errorClump = {-1.0, -1.0, 0.0, 0.0};
+    pmPSFClump emptyClump = {+0.0, +0.0, 0.0, 0.0};
+    pmPSFClump psfClump;
+
+    PS_ASSERT_PTR_NON_NULL(sources, errorClump);
+    PS_ASSERT_PTR_NON_NULL(recipe, errorClump);
+
+    bool status = true;
+    float PSF_CLUMP_SN_LIM = psMetadataLookupF32 (&status, recipe, "PSF_CLUMP_SN_LIM");
+    if (!status) {
+        PSF_CLUMP_SN_LIM = 0;
+    }
+
+    // find the sigmaX, sigmaY clump
+    {
+        psStats *stats  = NULL;
+        psImage *splane = NULL;
+        int binX, binY;
+        bool status;
+
+        psF32 SX_MAX = psMetadataLookupF32 (&status, recipe, "MOMENTS_SX_MAX");
+        if (!status)
+            SX_MAX = 10.0;
+        psF32 SY_MAX = psMetadataLookupF32 (&status, recipe, "MOMENTS_SY_MAX");
+        if (!status)
+            SY_MAX = 10.0;
+        psF32 AR_MAX = psMetadataLookupF32 (&status, recipe, "MOMENTS_AR_MAX");
+        if (!status)
+            AR_MAX =  3.0;
+        psF32 AR_MIN = 1.0 / AR_MAX;
+
+        // construct a sigma-plane image
+        splane = psImageAlloc (SX_MAX/SCALE, SY_MAX/SCALE, PS_TYPE_F32);
+        psImageInit(splane, 0);  // psImageAlloc doesn't zero the data
+
+        // place the sources in the sigma-plane image (ignore 0,0 values?)
+        int nValid = 0;
+        for (psS32 i = 0 ; i < sources->n ; i++)
+        {
+            pmSource *tmpSrc = (pmSource *) sources->data[i];
+            if (tmpSrc == NULL)
+                continue;
+            if (tmpSrc->moments == NULL)
+                continue;
+            if (tmpSrc->moments->SN < PSF_CLUMP_SN_LIM)
+                continue;
+
+            // Sx,Sy are limited at 0.  a peak at 0,0 is artificial
+            if (fabs(tmpSrc->moments->Sx) < 0.05)
+                continue;
+            if (fabs(tmpSrc->moments->Sy) < 0.05)
+                continue;
+            if ((tmpSrc->moments->Sx / tmpSrc->moments->Sy) > AR_MAX)
+                continue;
+            if ((tmpSrc->moments->Sx / tmpSrc->moments->Sy) < AR_MIN)
+                continue;
+            if (tmpSrc->mode & PM_SOURCE_MODE_BLEND)
+                continue;
+
+            // for the moment, force splane dimensions to be 10x10 image pix
+            binX = tmpSrc->moments->Sx/SCALE;
+            if (binX < 0)
+                continue;
+            if (binX >= splane->numCols)
+                continue;
+
+            binY = tmpSrc->moments->Sy/SCALE;
+            if (binY < 0)
+                continue;
+            if (binY >= splane->numRows)
+                continue;
+
+            splane->data.F32[binY][binX] += 1.0;
+            nValid ++;
+        }
+
+        // find the peak in this image
+        stats = psStatsAlloc (PS_STAT_MAX);
+        if (!psImageStats (stats, splane, NULL, 0)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to get image statistics.\n");
+            psFree(stats);
+            psFree(splane);
+            return emptyClump;
+        }
+        peaks = pmPeaksInImage (splane, stats[0].max / 2);
+        psTrace ("psModules.objects", 2, "clump threshold is %f\n", stats[0].max/2);
+
+        const bool keep_psf_clump = psMetadataLookupBool(NULL, recipe, "KEEP_PSF_CLUMP");
+        if (keep_psf_clump)
+        {
+            psMetadataAdd(recipe, PS_LIST_TAIL,
+                          "PSF_CLUMP", PS_DATA_IMAGE, "Image of PSF coefficients", splane);
+        }
+        psFree (splane);
+        psFree (stats);
+
+        // if we failed to find a valid peak, return the empty clump (failure signal)
+        if (peaks == NULL)
+        {
+            psLogMsg ("psphot", 3, "failed to find a peak in the PSF clump image\n");
+            if (nValid == 0) {
+                psLogMsg ("psphot", 3, "no valid sources kept for PSF search\n");
+            } else {
+                psLogMsg ("psphot", 3, "no significant peak\n");
+            }
+            return (emptyClump);
+        }
+    }
+
+    // measure statistics on Sx, Sy if Sx, Sy within range of clump
+    {
+        pmPeak *clump;
+        psF32 minSx, maxSx;
+        psF32 minSy, maxSy;
+        psVector *tmpSx = NULL;
+        psVector *tmpSy = NULL;
+        psStats *stats  = NULL;
+
+        // select the single highest peak
+        psArraySort (peaks, pmPeaksCompareDescend);
+        clump = peaks->data[0];
+        psTrace ("psModules.objects", 2, "clump is at %d, %d (%f)\n", clump->x, clump->y, clump->value);
+
+        // define section window for clump
+        minSx = clump->x * SCALE - 0.2;
+        maxSx = clump->x * SCALE + 0.2;
+        minSy = clump->y * SCALE - 0.2;
+        maxSy = clump->y * SCALE + 0.2;
+
+        tmpSx = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+        tmpSy = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+
+        // create vectors with Sx, Sy values in window
+        // clip sources based on S/N
+        for (psS32 i = 0 ; i < sources->n ; i++)
+        {
+            pmSource *tmpSrc = (pmSource *) sources->data[i];
+
+            if (tmpSrc == NULL)
+                continue;
+            if (tmpSrc->moments == NULL)
+                continue;
+            if (tmpSrc->moments->SN < PSF_CLUMP_SN_LIM)
+                continue;
+
+            if (tmpSrc->moments->Sx < minSx)
+                continue;
+            if (tmpSrc->moments->Sx > maxSx)
+                continue;
+            if (tmpSrc->moments->Sy < minSy)
+                continue;
+            if (tmpSrc->moments->Sy > maxSy)
+                continue;
+            tmpSx->data.F32[tmpSx->n] = tmpSrc->moments->Sx;
+            tmpSy->data.F32[tmpSy->n] = tmpSrc->moments->Sy;
+            tmpSx->n++;
+            tmpSy->n++;
+            if (tmpSx->n == tmpSx->nalloc) {
+                psVectorRealloc (tmpSx, tmpSx->nalloc + 100);
+                psVectorRealloc (tmpSy, tmpSy->nalloc + 100);
+            }
+        }
+
+        // measures stats of Sx, Sy
+        stats = psStatsAlloc (PS_STAT_CLIPPED_MEAN | PS_STAT_CLIPPED_STDEV);
+
+        psVectorStats (stats, tmpSx, NULL, NULL, 0);
+        psfClump.X  = stats->clippedMean;
+        psfClump.dX = stats->clippedStdev;
+
+        psVectorStats (stats, tmpSy, NULL, NULL, 0);
+        psfClump.Y  = stats->clippedMean;
+        psfClump.dY = stats->clippedStdev;
+
+        psTrace ("psModules.objects", 2, "clump  X,  Y: %f, %f\n", psfClump.X, psfClump.Y);
+        psTrace ("psModules.objects", 2, "clump DX, DY: %f, %f\n", psfClump.dX, psfClump.dY);
+
+        psFree (stats);
+        psFree (peaks);
+        psFree (tmpSx);
+        psFree (tmpSy);
+    }
+
+    psTrace("psModules.objects", 5, "---- end ----\n");
+    return (psfClump);
+}
+
+/******************************************************************************
+    pmSourceRoughClass(source, recipe): make a guess at the source
+    classification.
+    XXX: How can this function ever return FALSE?
+*****************************************************************************/
+
+bool pmSourceRoughClass(psArray *sources, psMetadata *recipe, pmPSFClump clump, psMaskType maskSat)
+{
+    psTrace("psModules.objects", 5, "---- begin ----");
+
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+    PS_ASSERT_PTR_NON_NULL(recipe, false);
+
+    int Nsat     = 0;
+    int Next     = 0;
+    int Nstar    = 0;
+    int Npsf     = 0;
+    int Ncr      = 0;
+    int Nsatstar = 0;
+    psRegion inner;
+
+    // report stats on S/N values for star-like objects
+    psVector *starsn_peaks = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *starsn_moments = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+
+    // get basic parameters, or set defaults
+    bool status;
+    float PSF_SN_LIM = psMetadataLookupF32 (&status, recipe, "PSF_SN_LIM");
+    if (!status)
+        PSF_SN_LIM = 20.0;
+    float PSF_CLUMP_NSIGMA = psMetadataLookupF32 (&status, recipe, "PSF_CLUMP_NSIGMA");
+    if (!status)
+        PSF_CLUMP_NSIGMA = 1.5;
+    float INNER_RADIUS = psMetadataLookupF32 (&status, recipe, "SKY_INNER_RADIUS");
+
+    // XXX allow clump size to be scaled relative to sigmas?
+    // make rough IDs based on clumpX,Y,DX,DY
+    for (psS32 i = 0 ; i < sources->n ; i++) {
+
+        pmSource *source = (pmSource *) sources->data[i];
+
+        source->peak->type = 0;
+
+        // we are basically classifying by moments; use the default if not found
+        if (!source->moments) {
+            source->type = PM_SOURCE_TYPE_STAR;
+            Nstar++;
+            continue;
+        }
+
+        psF32 sigX = source->moments->Sx;
+        psF32 sigY = source->moments->Sy;
+
+        // XXX EAM : can we use the value of SATURATE if mask is NULL?
+        inner = psRegionForSquare (source->peak->x, source->peak->y, 2);
+        inner = psRegionForImage (source->maskView, inner);
+        int Nsatpix = psImageCountPixelMask (source->maskView, inner, maskSat);
+
+        // saturated star (size consistent with PSF or larger)
+        // Nsigma should be user-configured parameter
+        bool big = (sigX > (clump.X - clump.dX)) && (sigY > (clump.Y - clump.dY));
+        big = true;
+        if ((Nsatpix > 1) && big) {
+            source->type = PM_SOURCE_TYPE_STAR;
+            source->mode |= PM_SOURCE_MODE_SATSTAR;
+            // recalculate moments here with larger box?
+            pmSourceMoments (source, INNER_RADIUS);
+            Nsatstar ++;
+            continue;
+        }
+
+        // saturated object (not a star, eg bleed trails, hot pixels)
+        if (Nsatpix > 1) {
+            source->type = PM_SOURCE_TYPE_SATURATED;
+            source->mode |= PM_SOURCE_MODE_SATURATED;
+            Nsat ++;
+            continue;
+        }
+
+        // likely defect (too small to be stellar) (push out to 3 sigma)
+        // low S/N objects which are small are probably stellar
+        // only set candidate defects if
+        // XXX these limits are quite arbitrary
+        if ((sigX < 0.05) || (sigY < 0.05)) {
+            source->type = PM_SOURCE_TYPE_DEFECT;
+            source->mode |= PM_SOURCE_MODE_DEFECT;
+            Ncr ++;
+            continue;
+        }
+
+        // likely unsaturated extended source (too large to be stellar)
+        if ((sigX > (clump.X + 3*clump.dX)) || (sigY > (clump.Y + 3*clump.dY))) {
+            source->type = PM_SOURCE_TYPE_EXTENDED;
+            Next ++;
+            continue;
+        }
+
+        // the rest are probable stellar objects
+        starsn_moments->data.F32[starsn_moments->n] = source->moments->SN;
+        starsn_moments->n ++;
+        starsn_peaks->data.F32[starsn_peaks->n] = source->peak->SN;
+        starsn_peaks->n ++;
+        Nstar ++;
+
+        // PSF star (within 1.5 sigma of clump center, S/N > limit)
+        psF32 radius = hypot ((sigX-clump.X)/clump.dX, (sigY-clump.Y)/clump.dY);
+        if ((source->moments->SN > PSF_SN_LIM) && (radius < PSF_CLUMP_NSIGMA)) {
+            source->type = PM_SOURCE_TYPE_STAR;
+            source->mode |= PM_SOURCE_MODE_PSFSTAR;
+            Npsf ++;
+            continue;
+        }
+
+        // random type of star
+        source->type = PM_SOURCE_TYPE_STAR;
+    }
+
+    psStats *stats = NULL;
+    stats = psStatsAlloc (PS_STAT_MIN | PS_STAT_MAX);
+    if (!psVectorStats (stats, starsn_moments, NULL, NULL, 0)) {
+        // Don't care about this error
+        psErrorClear();
+    }
+    psLogMsg ("pmObjects", 3, "SN range (moments): %f - %f\n", stats[0].min, stats[0].max);
+    psFree (starsn_moments);
+    psFree (stats);
+
+    stats = psStatsAlloc (PS_STAT_MIN | PS_STAT_MAX);
+    if (!psVectorStats (stats, starsn_peaks, NULL, NULL, 0)) {
+        // Don't care about this error
+        psErrorClear();
+    }
+    psLogMsg ("pmObjects", 3, "SN range (peaks)  : %f - %f\n", stats[0].min, stats[0].max);
+    psFree (starsn_peaks);
+    psFree (stats);
+
+    psTrace ("psModules.objects", 2, "Nstar:    %3d\n", Nstar);
+    psTrace ("psModules.objects", 2, "Npsf:     %3d\n", Npsf);
+    psTrace ("psModules.objects", 2, "Next:     %3d\n", Next);
+    psTrace ("psModules.objects", 2, "Nsatstar: %3d\n", Nsatstar);
+    psTrace ("psModules.objects", 2, "Nsat:     %3d\n", Nsat);
+    psTrace ("psModules.objects", 2, "Ncr:      %3d\n", Ncr);
+
+    psTrace("psModules.objects", 5, "---- end ----\n");
+    return true;
+}
+
+/******************************************************************************
+pmSourceMoments(source, radius): this function takes a subImage defined in the
+pmSource data structure, along with the peak location, and determines the
+various moments associated with that peak.
+
+Requires the following to have been created:
+    pmSource
+    pmSource->peak
+    pmSource->pixels
+    pmSource->weight
+    pmSource->mask
+
+XXX: The peak calculations are done in image coords, not subImage coords.
+
+XXX EAM : this version clips input pixels on S/N
+XXX EAM : this version returns false for several reasons
+*****************************************************************************/
+# define VALID_RADIUS(X,Y,RAD2) (((RAD2) >= (PS_SQR(X) + PS_SQR(Y))) ? 1 : 0)
+
+bool pmSourceMoments(pmSource *source,
+                     psF32 radius)
+{
+    psTrace("psModules.objects", 5, "---- begin ----\n");
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_PTR_NON_NULL(source->peak, false);
+    PS_ASSERT_PTR_NON_NULL(source->pixels, false);
+    PS_ASSERT_FLOAT_LARGER_THAN(radius, 0.0, false);
+
+    //
+    // XXX: Verify the setting for sky if source->moments == NULL.
+    //
+    psF32 sky = 0.0;
+    if (source->moments == NULL) {
+        source->moments = pmMomentsAlloc();
+    } else {
+        sky = source->moments->Sky;
+    }
+
+    //
+    // Sum = SUM (z - sky)
+    // X1  = SUM (x - xc)*(z - sky)
+    // X2  = SUM (x - xc)^2 * (z - sky)
+    // XY  = SUM (x - xc)*(y - yc)*(z - sky)
+    //
+    psF32 peakPixel = -PS_MAX_F32;
+    psS32 numPixels = 0;
+    psF32 Sum = 0.0;
+    psF32 Var = 0.0;
+    psF32 X1 = 0.0;
+    psF32 Y1 = 0.0;
+    psF32 X2 = 0.0;
+    psF32 Y2 = 0.0;
+    psF32 XY = 0.0;
+    psF32 x  = 0;
+    psF32 y  = 0;
+    psF32 R2 = PS_SQR(radius);
+
+    psF32 xPeak = source->peak->x;
+    psF32 yPeak = source->peak->y;
+    psF32 xOff = source->pixels->col0 - source->peak->x;
+    psF32 yOff = source->pixels->row0 - source->peak->y;
+
+    // XXX why do I get different results for these two methods of finding Sx?
+    // XXX Sx, Sy would be better measured if we clip pixels close to sky
+    // XXX Sx, Sy can still be imaginary, so we probably need to keep Sx^2?
+    // We loop through all pixels in this subimage (source->pixels), and for each
+    // pixel that is not masked, AND within the radius of the peak pixel, we
+    // proceed with the moments calculation.  need to do two loops for a
+    // numerically stable result.  first loop: get the sums.
+    // XXX EAM : mask == 0 is valid
+
+    for (psS32 row = 0; row < source->pixels->numRows ; row++) {
+
+        psF32 *vPix = source->pixels->data.F32[row];
+        psF32 *vWgt = source->weight->data.F32[row];
+        psU8  *vMsk = (source->maskObj == NULL) ? NULL : source->maskObj->data.U8[row];
+
+        for (psS32 col = 0; col < source->pixels->numCols ; col++, vPix++, vWgt++) {
+            if (vMsk) {
+                if (*vMsk) {
+                    vMsk++;
+                    psTrace("psModules.objects", 10, "Ignoring pixel %d,%d due to mask: %d\n",
+                            col, row, (int)*vMsk);
+                    continue;
+                }
+                vMsk++;
+            }
+	    if (isnan(*vPix)) continue;
+
+            psF32 xDiff = col + xOff;
+            psF32 yDiff = row + yOff;
+
+            // radius is just a function of (xDiff, yDiff)
+            if (!VALID_RADIUS(xDiff, yDiff, R2)) {
+#if 1
+                psTrace("psModules.objects", 10, "Ignoring pixel %d,%d due to position: %f %f\n",
+                        col, row, xDiff, yDiff);
+#endif
+                continue;
+            }
+
+            psF32 pDiff = *vPix - sky;
+            psF32 wDiff = *vWgt;
+
+            // XXX EAM : check for valid S/N in pixel
+            // XXX EAM : should this limit be user-defined?
+            if (PS_SQR(pDiff) < wDiff) {
+#if 1
+                psTrace("psModules.objects", 10, "Ignoring pixel %d,%d due to insignificance: %f, %f\n",
+                        col, row, pDiff, wDiff);
+#endif
+                continue;
+            }
+
+            Var += wDiff;
+            Sum += pDiff;
+
+            psF32 xWght = xDiff * pDiff;
+            psF32 yWght = yDiff * pDiff;
+
+            X1  += xWght;
+            Y1  += yWght;
+
+            XY  += xDiff * yWght;
+            X2  += xDiff * xWght;
+            Y2  += yDiff * yWght;
+
+            peakPixel = PS_MAX (*vPix, peakPixel);
+            numPixels++;
+        }
+    }
+
+    // if we have less than (1/4) of the possible pixels, force a retry
+    // XXX EAM - the limit is a bit arbitrary.  make it user defined?
+    if ((numPixels < 0.75*R2) || (Sum <= 0)) {
+        psTrace ("psModules.objects", 3, "insufficient valid pixels (%d vs %d; %f) for source\n",
+                 numPixels, (int)(0.75*R2), Sum);
+        psTrace("psModules.objects", 5, "---- end (false) ----\n");
+        return (false);
+    }
+
+    psTrace ("psModules.objects", 4, "sky: %f  Sum: %f  X1: %f  Y1: %f  X2: %f  Y2: %f  XY: %f  Npix: %d\n",
+             sky, Sum, X1, Y1, X2, Y2, XY, numPixels);
+
+    //
+    // first moment X  = X1/Sum + xc
+    // second moment X = sqrt (X2/Sum - (X1/Sum)^2)
+    // Sxy             = XY / Sum
+    //
+    x = X1/Sum;
+    y = Y1/Sum;
+    if ((fabs(x) > radius) || (fabs(y) > radius)) {
+        psTrace ("psModules.objects", 3, "large centroid swing; invalid peak %d, %d\n",
+                 source->peak->x, source->peak->y);
+        psTrace("psModules.objects", 5, "---- end(false)  ----\n");
+        return (false);
+    }
+
+    source->moments->x = x + xPeak;
+    source->moments->y = y + yPeak;
+
+    // XXX EAM : Sxy needs to have x*y subtracted
+    source->moments->Sxy = XY/Sum - x*y;
+    source->moments->Sum = Sum;
+    source->moments->SN  = Sum / sqrt(Var);
+    source->moments->Peak = peakPixel;
+    source->moments->nPixels = numPixels;
+
+    // XXX EAM : these values can be negative, so we need to limit the range
+    // XXX EAM : make the use of this consistent: should this be the second moment or sqrt?
+    // source->moments->Sx = sqrt(PS_MAX(X2/Sum - PS_SQR(x), 0));
+    // source->moments->Sy = sqrt(PS_MAX(Y2/Sum - PS_SQR(y), 0));
+    source->moments->Sx = PS_MAX(X2/Sum - PS_SQR(x), 0);
+    source->moments->Sy = PS_MAX(Y2/Sum - PS_SQR(y), 0);
+
+    psTrace ("psModules.objects", 4,
+             "sky: %f  Sum: %f  x: %f  y: %f  Sx: %f  Sy: %f  Sxy: %f\n",
+             sky, Sum, source->moments->x, source->moments->y,
+             source->moments->Sx, source->moments->Sy, source->moments->Sxy);
+
+    psTrace("psModules.objects", 5, "---- end ----\n");
+    return(true);
+}
+
+// construct a realization of the source model
+bool pmSourceCacheModel (pmSource *source, psMaskType maskVal) {
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    // select appropriate model
+    pmModel *model = pmSourceGetModel (NULL, source);
+    if (model == NULL) return false;  // model must be defined
+
+    // if we already have a cached image, re-use that memory
+    source->modelFlux = psImageCopy (source->modelFlux, source->pixels, PS_TYPE_F32);
+    psImageInit (source->modelFlux, 0.0);
+
+    // in some places (psphotEnsemble), we need a normalized version
+    // in others, we just want the model.  which is more commonly used?
+    // modelFlux always has unity normalization (I0 = 1.0)
+    pmModelAdd (source->modelFlux, source->maskObj, model, PM_MODEL_OP_FULL | PM_MODEL_OP_NORM, maskVal);
+    return true;
+}
+
+// construct a realization of the source model
+// XXX this function should optionally save an existing psf image from modelFlux
+bool pmSourceCachePSF (pmSource *source, psMaskType maskVal) {
+    PS_ASSERT_PTR_NON_NULL(source, false);
+
+    // select appropriate model
+    if (source->modelPSF == NULL) return false;  // model must be defined
+
+    // if we already have a cached image, re-use that memory
+    source->psfFlux = psImageCopy (source->psfFlux, source->pixels, PS_TYPE_F32);
+    psImageInit (source->psfFlux, 0.0);
+
+    // in some places (psphotEnsemble), we need a normalized version
+    // in others, we just want the model.  which is more commonly used?
+    // psfFlux always has unity normalization (I0 = 1.0)
+    pmModelAdd (source->psfFlux, source->maskObj, source->modelPSF, PM_MODEL_OP_FULL | PM_MODEL_OP_NORM, maskVal);
+    return true;
+}
+
+// should we call pmSourceCacheModel if it does not exist?
+bool pmSourceOp (pmSource *source, pmModelOpMode mode, bool add, psMaskType maskVal, int dx, int dy)
+{
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_PTR_NON_NULL(source->peak, false);
+    bool status;
+
+    if (add) {
+        psTrace ("psphot", 3, "replacing object at %f,%f\n", source->peak->xf, source->peak->yf);
+    } else {
+        psTrace ("psphot", 3, "removing object at %f,%f\n", source->peak->xf, source->peak->yf);
+    }
+
+    pmModel *model = pmSourceGetModel (NULL, source);
+    if (model == NULL) return false;  // model must be defined
+
+    if (source->modelFlux) {
+        // add in the pixels from the modelFlux image
+        int dX = source->modelFlux->col0 - source->pixels->col0;
+        int dY = source->modelFlux->row0 - source->pixels->row0;
+        assert (dX >= 0);
+        assert (dY >= 0);
+        assert (dX + source->modelFlux->numCols <= source->pixels->numCols);
+        assert (dY + source->modelFlux->numRows <= source->pixels->numRows);
+
+        // modelFlux has unity normalization
+        float Io = model->params->data.F32[PM_PAR_I0];
+        if (mode & PM_MODEL_OP_NORM) {
+            Io = 1.0;
+        }
+
+        psU8 **mask = NULL;
+        if (source->maskObj) {
+            mask = source->maskObj->data.U8;
+        }
+
+        psF32 **target = source->pixels->data.F32;
+        if (mode & PM_MODEL_OP_NOISE) {
+	    // XXX need to scale by the gain...
+            target = source->weight->data.F32;
+        }
+
+        // XXX need to respect the source and model masks?
+        for (int iy = 0; iy < source->modelFlux->numRows; iy++) {
+            int oy = iy + dY;
+            for (int ix = 0; ix < source->modelFlux->numCols; ix++) {
+                int ox = ix + dX;
+                if (mask && (mask[iy][ix] & maskVal)) continue;
+                float value = Io*source->modelFlux->data.F32[iy][ix];
+                if (add) {
+                    target[oy][ox] += value;
+                } else {
+                    target[oy][ox] -= value;
+                }
+            }
+        }
+        return true;
+    }
+
+    psImage *target = source->pixels;
+    if (mode & PM_MODEL_OP_NOISE) {
+        target = source->weight;
+    }
+
+    if (add) {
+        status = pmModelAddWithOffset (target, source->maskObj, model, PM_MODEL_OP_FULL, maskVal, dx, dy);
+    } else {
+        status = pmModelSubWithOffset (target, source->maskObj, model, PM_MODEL_OP_FULL, maskVal, dx, dy);
+    }
+
+    return true;
+}
+
+bool pmSourceAdd (pmSource *source, pmModelOpMode mode, psMaskType maskVal) {
+    return pmSourceOp (source, mode, true, maskVal, 0, 0);
+}
+
+bool pmSourceSub (pmSource *source, pmModelOpMode mode, psMaskType maskVal) {
+    return pmSourceOp (source, mode, false, maskVal, 0, 0);
+}
+
+bool pmSourceAddWithOffset (pmSource *source, pmModelOpMode mode, psMaskType maskVal, int dx, int dy) {
+    return pmSourceOp (source, mode, true, maskVal, dx, dy);
+}
+
+bool pmSourceSubWithOffset (pmSource *source, pmModelOpMode mode, psMaskType maskVal, int dx, int dy) {
+    return pmSourceOp (source, mode, false, maskVal, dx, dy);
+}
+
+// given a source, which model is currently appropriate?
+// choose PSF or EXT based on source->type, but fall back on PSF
+// if the EXT model is NULL
+pmModel *pmSourceGetModel (bool *isPSF, const pmSource *source)
+{
+    PS_ASSERT_PTR_NON_NULL(source, NULL);
+
+    pmModel *model;
+
+    if (isPSF) {
+        *isPSF = false;
+    }
+
+    switch (source->type) {
+      case PM_SOURCE_TYPE_STAR:
+        model = source->modelPSF;
+        if (model == NULL)
+            return NULL;
+        if (isPSF) {
+            *isPSF = true;
+        }
+        return model;
+
+	// the 'best' extended model is saved in source->modelEXT (may be a pointer to one of
+	// the elements of source->modelFits)
+      case PM_SOURCE_TYPE_EXTENDED:
+	model = source->modelEXT;
+        if (!model && source->modelPSF) {
+	    // XXX raise an error or warning here?
+            if (isPSF) {
+                *isPSF = true;
+            }
+            return source->modelPSF;
+        }
+        return (model);
+        break;
+
+      default:
+        return NULL;
+    }
+    return NULL;
+}
+
+// sort by SN (descending)
+int pmSourceSortBySN (const void **a, const void **b)
+{
+    pmSource *A = *(pmSource **)a;
+    pmSource *B = *(pmSource **)b;
+
+    psF32 fA = (A->peak == NULL) ? 0 : A->peak->SN;
+    psF32 fB = (B->peak == NULL) ? 0 : B->peak->SN;
+    if (isnan (fA)) fA = 0;
+    if (isnan (fB)) fB = 0;
+
+    psF32 diff = fA - fB;
+    if (diff > FLT_EPSILON) return (-1);
+    if (diff < FLT_EPSILON) return (+1);
+    return (0);
+}
+
+// sort by Y (ascending)
+int pmSourceSortByY (const void **a, const void **b)
+{
+    pmSource *A = *(pmSource **)a;
+    pmSource *B = *(pmSource **)b;
+
+    psF32 fA = (A->peak == NULL) ? 0 : A->peak->y;
+    psF32 fB = (B->peak == NULL) ? 0 : B->peak->y;
+
+    psF32 diff = fA - fB;
+    if (diff > FLT_EPSILON) return (+1);
+    if (diff < FLT_EPSILON) return (-1);
+    return (0);
+}
+
+pmSourceMode pmSourceModeFromString (const char *name) {
+  if (!strcasecmp (name, "DEFAULT"   )) return PM_SOURCE_MODE_DEFAULT;
+  if (!strcasecmp (name, "PSFMODEL"  )) return PM_SOURCE_MODE_PSFMODEL;
+  if (!strcasecmp (name, "EXTMODEL"  )) return PM_SOURCE_MODE_EXTMODEL;
+  if (!strcasecmp (name, "FITTED"    )) return PM_SOURCE_MODE_FITTED;
+  if (!strcasecmp (name, "FAIL"      )) return PM_SOURCE_MODE_FAIL;
+  if (!strcasecmp (name, "POOR"      )) return PM_SOURCE_MODE_POOR;
+  if (!strcasecmp (name, "PAIR"      )) return PM_SOURCE_MODE_PAIR;
+  if (!strcasecmp (name, "PSFSTAR"   )) return PM_SOURCE_MODE_PSFSTAR;
+  if (!strcasecmp (name, "SATSTAR"   )) return PM_SOURCE_MODE_SATSTAR;
+  if (!strcasecmp (name, "BLEND"     )) return PM_SOURCE_MODE_BLEND;
+  if (!strcasecmp (name, "EXTERNAL"  )) return PM_SOURCE_MODE_EXTERNAL;
+  if (!strcasecmp (name, "BADPSF"    )) return PM_SOURCE_MODE_BADPSF;
+  if (!strcasecmp (name, "DEFECT"    )) return PM_SOURCE_MODE_DEFECT;
+  if (!strcasecmp (name, "SATURATED" )) return PM_SOURCE_MODE_SATURATED;
+  if (!strcasecmp (name, "CRLIMIT"   )) return PM_SOURCE_MODE_CR_LIMIT;
+  if (!strcasecmp (name, "EXTLIMIT"  )) return PM_SOURCE_MODE_EXT_LIMIT;
+  if (!strcasecmp (name, "SUBTRACTED")) return PM_SOURCE_MODE_SUBTRACTED;
+  return PM_SOURCE_MODE_DEFAULT;
+}
+
+char *pmSourceModeToString (const pmSourceMode mode) {
+  switch (mode) {
+    case PM_SOURCE_MODE_DEFAULT    : return psStringCopy ("DEFAULT"   );
+    case PM_SOURCE_MODE_PSFMODEL   : return psStringCopy ("PSFMODEL"  );
+    case PM_SOURCE_MODE_EXTMODEL   : return psStringCopy ("EXTMODEL"  );
+    case PM_SOURCE_MODE_FITTED     : return psStringCopy ("FITTED"    );
+    case PM_SOURCE_MODE_FAIL       : return psStringCopy ("FAIL"      );
+    case PM_SOURCE_MODE_POOR       : return psStringCopy ("POOR"      );
+    case PM_SOURCE_MODE_PAIR       : return psStringCopy ("PAIR"      );
+    case PM_SOURCE_MODE_PSFSTAR    : return psStringCopy ("PSFSTAR"   );
+    case PM_SOURCE_MODE_SATSTAR    : return psStringCopy ("SATSTAR"   );
+    case PM_SOURCE_MODE_BLEND      : return psStringCopy ("BLEND"     );
+    case PM_SOURCE_MODE_EXTERNAL   : return psStringCopy ("EXTERNAL"  );
+    case PM_SOURCE_MODE_BADPSF     : return psStringCopy ("BADPSF"    );
+    case PM_SOURCE_MODE_DEFECT     : return psStringCopy ("DEFECT"    );
+    case PM_SOURCE_MODE_SATURATED  : return psStringCopy ("SATURATED" );
+    case PM_SOURCE_MODE_CR_LIMIT   : return psStringCopy ("CRLIMIT"   );
+    case PM_SOURCE_MODE_EXT_LIMIT  : return psStringCopy ("EXTLIMIT"  );
+    case PM_SOURCE_MODE_SUBTRACTED : return psStringCopy ("SUBTRACTED");
+    default:
+      return NULL;
+  }
+  return NULL;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSource.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSource.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSource.h	(revision 22157)
@@ -0,0 +1,243 @@
+/* @file  pmSource.h
+ *
+ * @author EAM, IfA; GLG, MHPCC
+ *
+ * @version $Revision: 1.24 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-04-08 18:35:38 $
+ * Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+# ifndef PM_SOURCE_H
+# define PM_SOURCE_H
+
+# include "pmSourceExtendedPars.h"
+
+/// @addtogroup Objects Object Detection / Analysis Functions
+/// @{
+
+/** pmSourceType enumeration
+ *
+ * A given source may be identified as most-likely to be one of several source
+ * types. The pmSource entry pmSourceType defines the current best-guess for this
+ * source.
+ *
+ */
+typedef enum {
+    PM_SOURCE_TYPE_UNKNOWN,		///< not yet classified
+    PM_SOURCE_TYPE_DEFECT,		///< a cosmic-ray
+    PM_SOURCE_TYPE_SATURATED,		///< random saturated pixels (eg, bleed trails)
+    PM_SOURCE_TYPE_STAR,		///< a good-quality star (subtracted model is PSF)
+    PM_SOURCE_TYPE_EXTENDED,		///< an extended object (eg, galaxy) (subtracted model is EXT)
+} pmSourceType;
+
+typedef enum {
+    PM_SOURCE_MODE_DEFAULT    = 0x0000, ///<
+    PM_SOURCE_MODE_PSFMODEL   = 0x0001, ///< Source fitted with a psf model (linear or non-linear)
+    PM_SOURCE_MODE_EXTMODEL   = 0x0002, ///< Source fitted with an extended-source model
+    PM_SOURCE_MODE_FITTED     = 0x0004, ///< Source fitted with non-linear model (PSF or EXT; good or bad)
+    PM_SOURCE_MODE_FAIL       = 0x0008, ///< Fit (non-linear) failed (non-converge, off-edge, run to zero)
+    PM_SOURCE_MODE_POOR       = 0x0010, ///< Fit succeeds, but low-SN, high-Chisq, or large (for PSF -- drop?)
+    PM_SOURCE_MODE_PAIR       = 0x0020, ///< Source fitted with a double psf
+    PM_SOURCE_MODE_PSFSTAR    = 0x0040, ///< Source used to define PSF model
+    PM_SOURCE_MODE_SATSTAR    = 0x0080, ///< Source model peak is above saturation
+    PM_SOURCE_MODE_BLEND      = 0x0100, ///< Source is a blend with other sourcers
+    PM_SOURCE_MODE_EXTERNAL   = 0x0200, ///< Source based on supplied input position
+    PM_SOURCE_MODE_BADPSF     = 0x0400, ///< Failed to get good estimate of object's PSF
+    PM_SOURCE_MODE_DEFECT     = 0x0800, ///< Source is thought to be a defect
+    PM_SOURCE_MODE_SATURATED  = 0x1000, ///< Source is thought to be saturated pixels (bleed trail)
+    PM_SOURCE_MODE_CR_LIMIT   = 0x2000, ///< Source has crNsigma above limit
+    PM_SOURCE_MODE_EXT_LIMIT  = 0x4000, ///< Source has extNsigma above limit
+    PM_SOURCE_MODE_SUBTRACTED = 0x8000, ///< XXX this flag is actually only used internally (move)
+} pmSourceMode;
+
+/** pmSource data structure
+ *
+ *  This source has the capacity for several types of measurements. The
+ *  simplest measurement of a source is the location and flux of the peak pixel
+ *  associated with the source:
+ *
+ *  XXX do I have to re-organize this (again!) to allow an arbitrary set of extended model fits??
+ *  XXX put the Mag and Err inside the pmModel?
+ *  XXX keep the modelEXT or add to the psArray 
+ *  
+ *
+ */
+struct pmSource {
+    const int id;                       ///< Unique ID for object
+    int seq;				///< ID for output (generated on write)
+    pmPeak  *peak;                      ///< Description of peak pixel.
+    psImage *pixels;                    ///< Rectangular region including object pixels.
+    psImage *weight;                    ///< Image variance.
+    psImage *maskObj;                   ///< unique mask for this object which marks included pixels associated with objects.
+    psImage *maskView;                  ///< view into global image mask for this object region
+    psImage *modelFlux;                 ///< cached copy of the best model for this source
+    psImage *psfFlux;                   ///< cached copy of the psf model for this source
+    pmMoments *moments;                 ///< Basic moments measured for the object.
+    pmModel *modelPSF;                  ///< PSF Model fit (parameters and type)
+    pmModel *modelEXT;                  ///< EXT Model fit used for subtraction (parameters and type)
+    psArray *modelFits;			///< collection of extended source models (best == modelEXT)
+    pmSourceType type;                  ///< Best identification of object.
+    pmSourceMode mode;                  ///< analysis flags set for object.
+    psArray *blends;			///< collection of sources thought to be confused with object
+    float psfMag;                       ///< calculated from flux in modelPSF
+    float extMag;                       ///< calculated from flux in modelEXT
+    float errMag;                       ///< error in psfMag OR extMag (depending on type)
+    float apMag;                        ///< apMag corresponding to psfMag or extMag (depending on type)
+    float pixWeight;                    ///< model-weighted coverage of valid pixels
+    float psfChisq;			///< probability of PSF
+    float crNsigma;                     ///< Nsigma deviation from PSF to CR
+    float extNsigma;                    ///< Nsigma deviation from PSF to EXT
+    float sky, skyErr;                  ///< The sky and its error at the center of the object
+    psRegion region;                    ///< area on image covered by selected pixels
+    pmSourceExtendedPars *extpars;      ///< extended source parameters
+};
+
+/** pmPSFClump data structure
+ *
+ * A collection of object moment measurements can be used to determine
+ * approximate object classes. The key to this analysis is the location and
+ * statistics (in the second-moment plane,
+ *
+ */
+typedef struct
+{
+    float X;
+    float dX;
+    float Y;
+    float dY;
+}
+pmPSFClump;
+
+
+/** pmSourceAlloc()
+ *
+ */
+pmSource  *pmSourceAlloc();
+
+/** pmSourceCopy()
+ *
+ */
+
+bool psMemCheckSource(psPtr ptr);
+
+pmSource  *pmSourceCopy(pmSource *source);
+
+// free just the pixels for a source, keeping derived data
+void pmSourceFreePixels(pmSource *source);
+
+/** pmSourceDefinePixels()
+ *
+ * Define psImage subarrays for the source located at coordinates x,y on the
+ * image set defined by readout. The pixels defined by this operation consist of
+ * a square window (of full width 2Radius+1) centered on the pixel which contains
+ * the given coordinate, in the frame of the readout. The window is defined to
+ * have limits which are valid within the boundary of the readout image, thus if
+ * the radius would fall outside the image pixels, the subimage is truncated to
+ * only consist of valid pixels. If readout->mask or readout->weight are not
+ * NULL, matching subimages are defined for those images as well. This function
+ * fails if no valid pixels can be defined (x or y less than Radius, for
+ * example). This function should be used to define a region of interest around a
+ * source, including both source and sky pixels.
+ *
+ */
+bool pmSourceDefinePixels(
+    pmSource *mySource,                 ///< source to be re-defined
+    const pmReadout *readout,  ///< base the source on this readout
+    psF32 x,                            ///< center coords of source
+    psF32 y,                            ///< center coords of source
+    psF32 Radius                        ///< size of box on source
+);
+
+bool pmSourceRedefinePixels (
+    pmSource *mySource,   ///< source to be re-defined
+    const pmReadout *readout,   ///< base the source on this readout
+    psF32 x,     ///< center coords of source
+    psF32 y,      ///< center coords of source
+    psF32 Radius   ///< size of box on source
+);
+
+/** pmSourcePSFClump()
+ *
+ * We use the source moments to make an initial, approximate source
+ * classification, and as part of the information needed to build a PSF model for
+ * the image. As long as the PSF shape does not vary excessively across the
+ * image, the sources which are represented by a PSF (the start) will have very
+ * similar second moments. The function pmSourcePSFClump searches a collection of
+ * sources with measured moments for a group with moments which are all very
+ * similar. The function returns a pmPSFClump structure, representing the
+ * centroid and size of the clump in the sigma_x, sigma_y second-moment plane.
+ *
+ * The goal is to identify and characterize the stellar clump within the
+ * sigma_x, sigma_y second-moment plane.  To do this, an image is constructed to
+ * represent this plane.  The units of sigma_x and sigma_y are in image pixels. A
+ * pixel in this analysis image represents 0.1 pixels in the input image. The
+ * dimensions of the image need only be 10 pixels. The peak pixel in this image
+ * (above a threshold of half of the image maximum) is found. The coordinates of
+ * this peak pixel represent the 2D mode of the sigma_x, sigma_y distribution.
+ * The sources with sigma_x, sigma_y within 0.2 pixels of this value are then
+ *  * used to calculate the median and standard deviation of the sigma_x, sigma_y
+ * values. These resulting values are returned via the pmPSFClump structure.
+ *
+ * The return value indicates the success (TRUE) of the operation.
+ *
+ * XXX: Limit the S/N of the candidate sources (part of Metadata)? (TBD).
+ * XXX: Save the clump parameters on the Metadata (TBD)
+ *
+ */
+pmPSFClump pmSourcePSFClump(
+    psArray *source,                    ///< The input pmSource
+    psMetadata *metadata                ///< Contains classification parameters
+);
+
+/** pmSourceRoughClass()
+ *
+ * Based on the specified data values, make a guess at the source
+ * classification. The sources are provides as a psArray of pmSource entries.
+ * Definable parameters needed to make the classification are provided to the
+ * routine with the psMetadata structure. The rules (in SDRS) refer to values which
+ * can be extracted from the metadata using the given keywords. Except as noted,
+ * the data type for these parameters are psF32.
+ *
+ */
+bool pmSourceRoughClass(
+    psArray *source,                    ///< The input pmSource
+    psMetadata *metadata,               ///< Contains classification parameters
+    pmPSFClump clump,                   ///< Statistics about the PSF clump
+    psMaskType maskSat                  ///< Mask value for saturated pixels
+);
+
+
+/** pmSourceMoments()
+ *
+ * Measure source moments for the given source, using the value of
+ * source.moments.sky provided as the local background value and the peak
+ * coordinates as the initial source location. The resulting moment values are
+ * applied to the source.moments entry, and the source is returned. The moments
+ * are measured within the given circular radius of the source.peak coordinates.
+ * The return value indicates the success (TRUE) of the operation.
+ *
+ */
+bool pmSourceMoments(
+    pmSource *source,   ///< The input pmSource for which moments will be computed
+    float radius   ///< Use a circle of pixels around the peak
+);
+
+pmModel *pmSourceGetModel (bool *isPSF, const pmSource *source);
+
+bool pmSourceAdd (pmSource *source, pmModelOpMode mode, psMaskType maskVal);
+bool pmSourceSub (pmSource *source, pmModelOpMode mode, psMaskType maskVal);
+bool pmSourceAddWithOffset (pmSource *source, pmModelOpMode mode, psMaskType maskVal, int dx, int dy);
+bool pmSourceSubWithOffset (pmSource *source, pmModelOpMode mode, psMaskType maskVal, int dx, int dy);
+
+bool pmSourceOp (pmSource *source, pmModelOpMode mode, bool add, psMaskType maskVal, int dx, int dy);
+bool pmSourceCacheModel (pmSource *source, psMaskType maskVal);
+bool pmSourceCachePSF (pmSource *source, psMaskType maskVal);
+
+int             pmSourceSortBySN (const void **a, const void **b);
+int             pmSourceSortByY (const void **a, const void **b);
+
+pmSourceMode pmSourceModeFromString (const char *name);
+char *pmSourceModeToString (const pmSourceMode mode);
+
+/// @}
+# endif /* PM_SOURCE_H */
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceContour.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceContour.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceContour.c	(revision 22157)
@@ -0,0 +1,459 @@
+/** @file  pmSourceContour.c
+ *
+ *  Functions to measure the local sky and sky variance for sources on images
+ *
+ *  @author GLG, MHPCC
+ *  @author EAM, IfA: significant modifications.
+ *
+ *  @version $Revision: 1.12 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-01-02 20:39:04 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include "pslib.h"
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmResiduals.h"
+#include "pmGrowthCurve.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmSourceContour.h"
+
+/******************************************************************************
+findValue(source, level, row, col, dir): a private function which determines
+the column coordinate of the model function which has the value "level".  If
+dir equals 0, then you loop leftwards from the peak pixel, otherwise,
+rightwards.
+ 
+XXX: reverse order of row,col args?
+ 
+XXX: Input row/col are in image coords.
+ 
+XXX: The result is returned in image coords.
+*****************************************************************************/
+# define LEFT false
+# define RIGHT true
+
+// return the first coordinate at or below the threshold in the requested direction
+static int findContourNeg(
+    psImage *image,
+    float threshold,
+    int x,
+    int y,
+    bool right)
+{
+
+    psTrace("psModules.objects", 4, "---- %s() begin ----\n", __func__);
+
+    // We define variables incr and lastColumn so that we can use the same loop
+    // whether we are stepping leftwards, or rightwards.
+
+    int incr;
+    int subCol;
+    int lastColumn;
+    if (right) {
+        incr = 1;
+        lastColumn = image->numCols - 1;
+    } else {
+        incr = -1;
+        lastColumn = 0;
+    }
+
+    subCol = x;
+
+    while (subCol != lastColumn) {
+        float value = image->data.F32[y][subCol];
+        if (value <= threshold) {
+            psTrace("psModules.objects", 4, "---- %s() end ----\n", __func__);
+            return (subCol);
+        }
+        subCol += incr;
+    }
+    psTrace("psModules.objects", 4, "---- %s() end ----\n", __func__);
+    return (lastColumn);
+}
+
+// return the last coordinate at or below the threshold in the requested direction
+static int findContourPos(
+    psImage *image,
+    float threshold,
+    int x,
+    int y,
+    bool right,
+    int xEnd)
+{
+
+    psTrace("psModules.objects", 4, "---- %s() begin ----\n", __func__);
+
+    // We define variables incr and lastColumn so that we can use the same loop
+    // whether we are stepping leftwards, or rightwards.
+
+    int incr;
+    int subCol;
+    int lastColumn;
+    if (right) {
+        incr = 1;
+        lastColumn = PS_MIN (image->numCols - 1, xEnd);
+    } else {
+        incr = -1;
+        lastColumn = PS_MAX (0, xEnd);
+    }
+
+    subCol = x;
+    while (subCol != lastColumn) {
+        float value = image->data.F32[y][subCol];
+        if (value >= threshold) {
+            psTrace("psModules.objects", 4, "---- %s() end ----\n", __func__);
+            if (subCol == x) {
+                return (subCol);
+            }
+            return (subCol);
+        }
+        subCol += incr;
+    }
+    psTrace("psModules.objects", 4, "---- %s() end ----\n", __func__);
+    return (lastColumn);
+}
+
+/******************************************************************************
+findValue(source, level, row, col, dir): a private function which determines
+the column coordinate of the model function which has the value "level".  If
+dir equals 0, then you loop leftwards from the peak pixel, otherwise,
+rightwards.
+ 
+XXX: reverse order of row,col args?
+ 
+XXX: Input row/col are in image coords.
+ 
+XXX: The result is returned in image coords.
+*****************************************************************************/
+static psF32 findValue(pmSource *source,
+                       psF32 level,
+                       psU32 row,
+                       psU32 col,
+                       psU32 dir)
+{
+    psTrace("psModules.objects", 4, "---- %s() begin ----\n", __func__);
+    //
+    // Convert coords to subImage space.
+    //
+    psU32 subRow = row - source->pixels->row0;
+    psU32 subCol = col - source->pixels->col0;
+
+    // Ensure that the starting column is allowable.
+    if (!((0 <= subCol) && (subCol < source->pixels->numCols))) {
+        psError(PS_ERR_UNKNOWN, true, "Starting column outside subImage range");
+        psTrace("psModules.objects", 4, "---- %s(NAN) end ----\n", __func__);
+        return(NAN);
+    }
+    if (!((0 <= subRow) && (subRow < source->pixels->numRows))) {
+        psTrace("psModules.objects", 4, "---- %s(NAN) end ----\n", __func__);
+        psError(PS_ERR_UNKNOWN, true, "Starting row outside subImage range");
+        return(NAN);
+    }
+
+    // XXX EAM : i changed this to match pmModelEval above, but see
+    // XXX EAM   the note below in pmSourceContour
+    psF32 oldValue = pmModelEval(source->modelEXT, source->pixels, subCol, subRow);
+    if (oldValue == level) {
+        psTrace("psModules.objects", 4, "---- %s() end ----\n", __func__);
+        return(((psF32) (subCol + source->pixels->col0)));
+    }
+
+    //
+    // We define variables incr and lastColumn so that we can use the same loop
+    // whether we are stepping leftwards, or rightwards.
+    //
+    psS32 incr;
+    psS32 lastColumn;
+    if (dir == 0) {
+        incr = -1;
+        lastColumn = -1;
+    } else {
+        incr = 1;
+        lastColumn = source->pixels->numCols;
+    }
+    subCol+=incr;
+
+    while (subCol != lastColumn) {
+        psF32 newValue = pmModelEval(source->modelEXT, source->pixels, subCol, subRow);
+        if (oldValue == level) {
+            psTrace("psModules.objects", 4, "---- %s() end ----\n", __func__);
+            return((psF32) (subCol + source->pixels->col0));
+        }
+
+        if ((newValue <= level) && (level <= oldValue)) {
+            // This is simple linear interpolation.
+            psTrace("psModules.objects", 4, "---- %s() end ----\n", __func__);
+            return( ((psF32) (subCol + source->pixels->col0)) + ((psF32) incr) * ((level - newValue) / (oldValue - newValue)) );
+        }
+
+        if ((oldValue <= level) && (level <= newValue)) {
+            // This is simple linear interpolation.
+            psTrace("psModules.objects", 4, "---- %s() end ----\n", __func__);
+            return( ((psF32) (subCol + source->pixels->col0)) + ((psF32) incr) * ((level - oldValue) / (newValue - oldValue)) );
+        }
+
+        subCol+=incr;
+    }
+
+    psTrace("psModules.objects", 4, "---- %s(NAN) end ----\n", __func__);
+    return(NAN);
+}
+
+/******************************************************************************
+new implementation of source contour function
+*****************************************************************************/
+psArray *pmSourceContour (psImage *image, int xc, int yc, float threshold)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(image, NULL);
+
+    int xR, yR, x0, x1, x0s, x1s;
+    int x = xc - image->col0;
+    int y = yc - image->row0;
+
+    // Ensure that the starting column is allowable.
+    if (x < 0) {
+        return NULL;
+    }
+    if (y < 0) {
+        return NULL;
+    }
+    if (x >= image->numCols) {
+        return NULL;
+    }
+    if (y >= image->numRows) {
+        return NULL;
+    }
+
+    // the requested point must be within the contour
+    if (image->data.F32[y][x] < threshold) {
+        return NULL;
+    }
+
+    // Allocate data for x/y pairs.
+    psVector *xVec = psVectorAllocEmpty(100, PS_TYPE_F32);
+    psVector *yVec = psVectorAllocEmpty(100, PS_TYPE_F32);
+
+    // First row: find the left and right end-points
+    int Npt = 0;
+
+    x0 = findContourNeg (image, threshold, x, y, LEFT);
+    x1 = findContourNeg (image, threshold, x, y, RIGHT);
+    xVec->data.F32[Npt + 0] = image->col0 + x0;
+    xVec->data.F32[Npt + 1] = image->col0 + x1;
+    yVec->data.F32[Npt + 0] = image->row0 + y;
+    yVec->data.F32[Npt + 1] = image->row0 + y;
+    Npt += 2;
+
+    x0s = x0;
+    x1s = x1;
+
+    // look for contour outline above row
+    xR = x0s;
+    yR = y + 1;
+    while (yR < image->numRows) {
+        if (image->data.F32[yR][xR] < threshold) {
+            x0 = findContourPos (image, threshold, xR, yR, RIGHT, x1);
+            if (x0 == x1) {
+                // fprintf (stderr, "top: %d (%d - %d)\n", yR, xR, x1);
+                goto pt1;
+            }
+            x1 = findContourNeg (image, threshold, x0, yR, RIGHT);
+            x0--;
+        } else {
+            x0 = findContourNeg (image, threshold, xR, yR, LEFT);
+            x1 = findContourNeg (image, threshold, xR, yR, RIGHT);
+        }
+        // fprintf (stderr, "pos: %d (%d - %d)\n", yR, x0, x1);
+
+        xVec->data.F32[Npt + 0] = image->col0 + x0;
+        xVec->data.F32[Npt + 1] = image->col0 + x1;
+        yVec->data.F32[Npt + 0] = image->row0 + yR;
+        yVec->data.F32[Npt + 1] = image->row0 + yR;
+        Npt += 2;
+
+        if (Npt >= xVec->nalloc - 1) {
+            psVectorRealloc (xVec, xVec->nalloc + 100);
+            psVectorRealloc (yVec, yVec->nalloc + 100);
+        }
+        yR ++;
+        xR = x0;
+    }
+
+pt1:
+    // look for contour outline below row
+    xR = x0s;
+    x1 = x1s;
+    yR = y - 1;
+    while (yR >= 0) {
+        if (image->data.F32[yR][xR] < threshold) {
+            x0 = findContourPos (image, threshold, xR, yR, RIGHT, x1);
+            if (x0 == x1) {
+                // fprintf (stderr, "top: %d (%d - %d)\n", yR, xR, x1);
+                goto pt2;
+            }
+            x1 = findContourNeg (image, threshold, x0, yR, RIGHT);
+            x0--;
+        } else {
+            x0 = findContourNeg (image, threshold, xR, yR, LEFT);
+            x1 = findContourNeg (image, threshold, xR, yR, RIGHT);
+        }
+        // fprintf (stderr, "neg: %d (%d - %d)\n", yR, x0, x1);
+
+        xVec->data.F32[Npt + 0] = image->col0 + x0;
+        xVec->data.F32[Npt + 1] = image->col0 + x1;
+        yVec->data.F32[Npt + 0] = image->row0 + yR;
+        yVec->data.F32[Npt + 1] = image->row0 + yR;
+        Npt += 2;
+
+        if (Npt >= xVec->nalloc - 1) {
+            psVectorRealloc (xVec, xVec->nalloc + 100);
+            psVectorRealloc (yVec, yVec->nalloc + 100);
+        }
+        yR --;
+    }
+pt2:
+    xVec->n = Npt;
+    yVec->n = Npt;
+
+    // fprintf (stderr, "done\n");
+    psArray *tmpArray = psArrayAlloc(2);
+
+    tmpArray->data[0] = (psPtr *) xVec;
+    tmpArray->data[1] = (psPtr *) yVec;
+    psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+    return(tmpArray);
+}
+
+/******************************************************************************
+    pmSourceContour(src, img, level, mode): For an input subImage, and model, this
+    routine returns a psArray of coordinates that evaluate to the specified level.
+ 
+    XXX: Probably should remove the "image" argument.
+    XXX: What type should the output coordinate vectors consist of?  col,row?
+    XXX: Why a pmArray output?
+    XXX: doex x,y correspond with col,row or row/col?
+    XXX: What is mode?
+    XXX: The top, bottom of the contour is not correctly determined.
+    XXX EAM : this function is using the model for the contour, but it should
+              be using only the image counts
+*****************************************************************************/
+psArray *pmSourceContour_Crude(pmSource *source,
+                               const psImage *image,
+                               psF32 level)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_PTR_NON_NULL(image, false);
+    PS_ASSERT_PTR_NON_NULL(source->moments, false);
+    PS_ASSERT_PTR_NON_NULL(source->peak, false);
+    PS_ASSERT_PTR_NON_NULL(source->pixels, false);
+    PS_ASSERT_PTR_NON_NULL(source->modelEXT, false);
+    // XXX EAM : what is the purpose of modelPSF/modelEXT?
+
+    //
+    // Allocate data for x/y pairs.
+    //
+    psVector *xVec = psVectorAlloc(2 * source->pixels->numRows, PS_TYPE_F32);
+    psVector *yVec = psVectorAlloc(2 * source->pixels->numRows, PS_TYPE_F32);
+
+    //
+    // Start at the row with peak pixel, then decrement.
+    //
+    psS32 col = source->peak->x;
+    for (psS32 row = source->peak->y; row>= 0 ; row--) {
+        // XXX: yVec contain no real information.  Do we really need it?
+        yVec->data.F32[row] = (psF32) (source->pixels->row0 + row);
+        yVec->data.F32[row+yVec->n] = (psF32) (source->pixels->row0 + row);
+
+        // Starting at peak pixel, search leftwards for the column intercept.
+        psF32 leftIntercept = findValue(source, level, row, col, 0);
+        if (isnan(leftIntercept)) {
+            psError(PS_ERR_UNKNOWN, true, "Could not find contour edge (NAN)");
+            psFree(xVec);
+            psFree(yVec);
+            psTrace("psModules.objects", 3, "---- %s(NULL) end ----\n", __func__);
+            return(NULL);
+            //psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not find contour edge (NAN)\n");
+        }
+        xVec->data.F32[row] = ((psF32) source->pixels->col0) + leftIntercept;
+
+        // Starting at peak pixel, search rightwards for the column intercept.
+
+        psF32 rightIntercept = findValue(source, level, row, col, 1);
+        if (isnan(rightIntercept)) {
+            psError(PS_ERR_UNKNOWN, true, "Could not find contour edge (NAN)");
+            psFree(xVec);
+            psFree(yVec);
+            psTrace("psModules.objects", 3, "---- %s(NULL) end ----\n", __func__);
+            return(NULL);
+            //psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not find contour edge (NAN)\n");
+        }
+        psTrace("psModules.objects", 4, "The intercepts are (%.2f, %.2f)\n", leftIntercept, rightIntercept);
+        xVec->data.F32[row+xVec->n] = ((psF32) source->pixels->col0) + rightIntercept;
+
+        // Set starting column for next row
+        col = (psS32) ((leftIntercept + rightIntercept) / 2.0);
+    }
+    //
+    // Start at the row (+1) with peak pixel, then increment.
+    //
+    col = source->peak->x;
+    for (psS32 row = 1 + source->peak->y; row < source->pixels->numRows ; row++) {
+        // XXX: yVec contain no real information.  Do we really need it?
+        yVec->data.F32[row] = (psF32) (source->pixels->row0 + row);
+        yVec->data.F32[row+yVec->n] = (psF32) (source->pixels->row0 + row);
+
+        // Starting at peak pixel, search leftwards for the column intercept.
+        psF32 leftIntercept = findValue(source, level, row, col, 0);
+        if (isnan(leftIntercept)) {
+            psError(PS_ERR_UNKNOWN, true, "Could not find contour edge (NAN)");
+            psFree(xVec);
+            psFree(yVec);
+            psTrace("psModules.objects", 3, "---- %s(NULL) end ----\n", __func__);
+            return(NULL);
+            //psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not find contour edge (NAN)\n");
+        }
+        xVec->data.F32[row] = ((psF32) source->pixels->col0) + leftIntercept;
+
+        // Starting at peak pixel, search rightwards for the column intercept.
+        psF32 rightIntercept = findValue(source, level, row, col, 1);
+        if (isnan(rightIntercept)) {
+            psError(PS_ERR_UNKNOWN, true, "Could not find contour edge (NAN)");
+            psFree(xVec);
+            psFree(yVec);
+            psTrace("psModules.objects", 3, "---- %s(NULL) end ----\n", __func__);
+            return(NULL);
+            //psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not find contour edge (NAN)\n");
+        }
+        xVec->data.F32[row+xVec->n] = ((psF32) source->pixels->col0) + rightIntercept;
+
+        // Set starting column for next row
+        col = (psS32) ((leftIntercept + rightIntercept) / 2.0);
+    }
+
+    //
+    // Allocate an array for result, store coord vectors there.
+    //
+    psArray *tmpArray = psArrayAlloc(2);
+
+    tmpArray->data[0] = (psPtr *) yVec;
+    tmpArray->data[1] = (psPtr *) xVec;
+    psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+    return(tmpArray);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceContour.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceContour.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceContour.h	(revision 22157)
@@ -0,0 +1,39 @@
+/* @file  pmSourceContour.h
+ *
+ * @author EAM, IfA; GLG, MHPCC
+ *
+ * @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-01-24 02:54:15 $
+ * Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+# ifndef PM_SOURCE_CONTOUR_H
+# define PM_SOURCE_CONTOUR_H
+
+/// @addtogroup Objects Object Detection / Analysis Functions
+/// @{
+
+psArray *pmSourceContour (psImage *image, int xc, int yc, float threshold);
+
+
+/** pmSourceContour()
+ *
+ * Find points in a contour for the given source at the given level. If type
+ * is PM_CONTOUR_CRUDE, the contour is found by starting at the source peak,
+ * running along each pixel row until the level is crossed, then interpolating to
+ * the level coordinate for that row. This is done for each row, with the
+ * starting point determined by the midpoint of the previous row, until the
+ * starting point has a value below the contour level. The returned contour
+ * consists of two vectors giving the x and y coordinates of the contour levels.
+ * This function may be used as part of the model guess inputs.  Other contour
+ * types may be specified in the future for more refined contours (TBD)
+ *
+ */
+psArray *pmSourceContour_Crude(
+    pmSource *source,   ///< The input pmSource
+    const psImage *image,  ///< The input image (float) (this arg should be removed)
+    float level   ///< The level of the contour
+);
+
+/// @}
+# endif /* PM_SOURCE_PHOTOMETRY_H */
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceExtendedPars.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceExtendedPars.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceExtendedPars.c	(revision 22157)
@@ -0,0 +1,198 @@
+/** @file  pmSourceExtendedPars.c
+ *
+ *  Functions to define and manipulate sources on images
+ *
+ *  @author GLG, MHPCC
+ *  @author EAM, IfA: significant modifications.
+ *
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-12-22 00:31:41 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAMaskWeight.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmResiduals.h"
+#include "pmGrowthCurve.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+
+static void pmSourceExtendedParsFree (pmSourceExtendedPars *pars) {
+    if (!pars) return;
+
+    psFree(pars->profile);
+    psFree(pars->annuli);
+    psFree(pars->isophot);
+    psFree(pars->petrosian);
+    psFree(pars->kron);
+    return;
+}
+
+pmSourceExtendedPars *pmSourceExtendedParsAlloc () {
+    pmSourceExtendedPars *pars = (pmSourceExtendedPars *) psAlloc(sizeof(pmSourceExtendedPars));
+    psMemSetDeallocator(pars, (psFreeFunc) pmSourceExtendedParsFree);
+
+    pars->profile = NULL;
+    pars->annuli = NULL;
+    pars->isophot = NULL;
+    pars->petrosian = NULL;
+    pars->kron = NULL;
+
+    return pars;
+}
+
+bool psMemCheckSourceExtendedPars(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) pmSourceExtendedParsFree);
+}
+
+
+static void pmSourceRadialProfileFree (pmSourceRadialProfile *profile) {
+    if (!profile) return;
+
+    psFree(profile->radius);
+    psFree(profile->flux);
+    psFree(profile->weight);
+    return;
+}
+
+pmSourceRadialProfile *pmSourceRadialProfileAlloc () {
+
+    pmSourceRadialProfile *profile = (pmSourceRadialProfile *) psAlloc(sizeof(pmSourceRadialProfile));
+    psMemSetDeallocator(profile, (psFreeFunc) pmSourceRadialProfileFree);
+
+    profile->radius = NULL;
+    profile->flux = NULL;
+    profile->weight = NULL;
+
+    return profile;
+}
+
+bool psMemCheckSourceRadialProfile(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) pmSourceRadialProfileFree);
+}
+
+
+static void pmSourceIsophotalValuesFree (pmSourceIsophotalValues *isophot) {
+    if (!isophot) return;
+    return;
+}
+
+pmSourceIsophotalValues *pmSourceIsophotalValuesAlloc () {
+
+    pmSourceIsophotalValues *isophot = (pmSourceIsophotalValues *) psAlloc(sizeof(pmSourceIsophotalValues));
+    psMemSetDeallocator(isophot, (psFreeFunc) pmSourceIsophotalValuesFree);
+
+    isophot->mag = 0.0;
+    isophot->magErr = 0.0;
+    isophot->rad = 0.0;
+    isophot->radErr = 0.0;
+
+    return isophot;
+}
+
+
+bool psMemCheckSourceIsophotalValues(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) pmSourceIsophotalValuesFree);
+}
+
+
+static void pmSourcePetrosianValuesFree (pmSourcePetrosianValues *petrosian) {
+    if (!petrosian) return;
+    return;
+}
+
+pmSourcePetrosianValues *pmSourcePetrosianValuesAlloc () {
+
+    pmSourcePetrosianValues *petrosian = (pmSourcePetrosianValues *) psAlloc(sizeof(pmSourcePetrosianValues));
+    psMemSetDeallocator(petrosian, (psFreeFunc) pmSourcePetrosianValuesFree);
+
+    petrosian->mag = 0.0;
+    petrosian->magErr = 0.0;
+    petrosian->rad = 0.0;
+    petrosian->radErr = 0.0;
+
+    return petrosian;
+}
+
+
+bool psMemCheckSourcePetrosianValues(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) pmSourcePetrosianValuesFree);
+}
+
+static void pmSourceKronValuesFree (pmSourceKronValues *kron) {
+    if (!kron) return;
+    return;
+}
+
+pmSourceKronValues *pmSourceKronValuesAlloc () {
+
+    pmSourceKronValues *kron = (pmSourceKronValues *) psAlloc(sizeof(pmSourceKronValues));
+    psMemSetDeallocator(kron, (psFreeFunc) pmSourceKronValuesFree);
+
+    kron->mag = 0.0;
+    kron->magErr = 0.0;
+    kron->rad = 0.0;
+    kron->radErr = 0.0;
+
+    return kron;
+}
+
+
+bool psMemCheckSourceKronValues(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) pmSourceKronValuesFree);
+}
+
+
+static void pmSourceAnnuliFree (pmSourceAnnuli *annuli) {
+    if (!annuli) return;
+
+    psFree (annuli->flux);
+    psFree (annuli->fluxErr);
+    psFree (annuli->fluxVar);
+
+    return;
+}
+
+pmSourceAnnuli *pmSourceAnnuliAlloc () {
+
+    pmSourceAnnuli *annuli = (pmSourceAnnuli *) psAlloc(sizeof(pmSourceAnnuli));
+    psMemSetDeallocator(annuli, (psFreeFunc) pmSourceAnnuliFree);
+
+    annuli->flux = NULL;
+    annuli->fluxErr = NULL;
+    annuli->fluxVar = NULL;
+
+    return annuli;
+}
+
+
+bool psMemCheckSourceAnnuli(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) pmSourceAnnuliFree);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceExtendedPars.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceExtendedPars.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceExtendedPars.h	(revision 22157)
@@ -0,0 +1,71 @@
+/* @file  pmSourceExtendedPars.h
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-01-02 20:39:04 $
+ * Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+# ifndef PM_SOURCE_EXTENDED_PARS_H
+# define PM_SOURCE_EXTENDED_PARS_H
+
+/// @addtogroup Objects Object Detection / Analysis Functions
+/// @{
+
+typedef struct {
+  psVector *radius;
+  psVector *flux;
+  psVector *weight;
+} pmSourceRadialProfile;
+
+typedef struct {
+  psVector *flux;
+  psVector *fluxErr;
+  psVector *fluxVar;
+} pmSourceAnnuli;
+
+typedef struct {
+  float mag;
+  float magErr;
+  float rad;
+  float radErr;
+} pmSourceIsophotalValues;
+
+typedef struct {
+  float mag;
+  float magErr;
+  float rad;
+  float radErr;
+} pmSourcePetrosianValues;
+
+typedef struct {
+  float mag;
+  float magErr;
+  float rad;
+  float radErr;
+} pmSourceKronValues;
+
+typedef struct {
+  pmSourceRadialProfile   *profile;
+  pmSourceAnnuli          *annuli;
+  pmSourceIsophotalValues *isophot;
+  pmSourcePetrosianValues *petrosian;
+  pmSourceKronValues      *kron;
+} pmSourceExtendedPars;
+
+pmSourceExtendedPars *pmSourceExtendedParsAlloc ();
+bool psMemCheckSourceExtendedPars(psPtr ptr);
+pmSourceRadialProfile *pmSourceRadialProfileAlloc ();
+bool psMemCheckSourceRadialProfile(psPtr ptr);
+pmSourceIsophotalValues *pmSourceIsophotalValuesAlloc ();
+bool psMemCheckSourceIsophotalValues(psPtr ptr);
+pmSourcePetrosianValues *pmSourcePetrosianValuesAlloc ();
+bool psMemCheckSourcePetrosianValues(psPtr ptr);
+pmSourceKronValues *pmSourceKronValuesAlloc ();
+bool psMemCheckSourceKronValues(psPtr ptr);
+pmSourceAnnuli *pmSourceAnnuliAlloc ();
+bool psMemCheckSourceAnnuli(psPtr ptr);
+
+/// @}
+# endif /* PM_SOURCE_H */
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceFitModel.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceFitModel.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceFitModel.c	(revision 22157)
@@ -0,0 +1,233 @@
+/** @file  pmSourceFitModel.c
+ *
+ *  fit single source models to image pixels
+ *
+ *  @author EAM, IfA
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.27 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-09-28 00:38:56 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmGrowthCurve.h"
+#include "pmResiduals.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmModelClass.h"
+#include "pmSourceFitModel.h"
+
+// save as static values so they may be set externally
+static psF32 PM_SOURCE_FIT_MODEL_NUM_ITERATIONS = 15;
+static psF32 PM_SOURCE_FIT_MODEL_TOLERANCE = 0.1;
+static psF32 PM_SOURCE_FIT_MODEL_WEIGHT = 1.0;
+static bool  PM_SOURCE_FIT_MODEL_PIX_WEIGHTS = true;
+
+bool pmSourceFitModelInit (float nIter, float tol, float weight, bool poissonErrors)
+{
+
+    PM_SOURCE_FIT_MODEL_NUM_ITERATIONS = nIter;
+    PM_SOURCE_FIT_MODEL_TOLERANCE = tol;
+    PM_SOURCE_FIT_MODEL_WEIGHT = weight;
+    PM_SOURCE_FIT_MODEL_PIX_WEIGHTS = poissonErrors;
+
+    return true;
+}
+
+bool pmSourceFitModel (pmSource *source,
+                       pmModel *model,
+                       pmSourceFitMode mode,
+                       psMaskType maskVal)
+{
+    psTrace("psModules.objects", 5, "---- %s begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_PTR_NON_NULL(source->pixels, false);
+    PS_ASSERT_PTR_NON_NULL(source->maskObj, false);
+    PS_ASSERT_PTR_NON_NULL(source->weight, false);
+
+    psBool fitStatus = true;
+    psBool onPic     = true;
+    psBool rc        = true;
+
+    // maximum number of valid pixels
+    psS32 nPix = source->pixels->numRows * source->pixels->numCols;
+
+    // arrays to hold the data to be fitted
+    psArray *x = psArrayAllocEmpty(nPix);
+    psVector *y = psVectorAllocEmpty(nPix, PS_TYPE_F32);
+    psVector *yErr = psVectorAllocEmpty(nPix, PS_TYPE_F32);
+
+    // fill in the coordinate and value entries
+    nPix = 0;
+    for (psS32 i = 0; i < source->pixels->numRows; i++) {
+        for (psS32 j = 0; j < source->pixels->numCols; j++) {
+            // skip masked points
+            if (source->maskObj->data.U8[i][j] & maskVal) {
+                continue;
+            }
+            // skip zero-weight points
+            if (source->weight->data.F32[i][j] == 0) {
+                continue;
+            }
+            // skip nan values in image
+            if (!isfinite(source->pixels->data.F32[i][j])) {
+                continue;
+            }
+
+            psVector *coord = psVectorAlloc(2, PS_TYPE_F32);
+
+            // Convert i/j to image space:
+            coord->data.F32[0] = (psF32) (j + source->pixels->col0);
+            coord->data.F32[1] = (psF32) (i + source->pixels->row0);
+            x->data[nPix] = (psPtr *) coord;
+            y->data.F32[nPix] = source->pixels->data.F32[i][j];
+
+            // psMinimizeLMChi2 takes wt = 1/dY^2.  suggestion from RHL is to use the local sky
+            // as weight to avoid the bias from systematic errors here we would just use the
+            // source sky variance
+            if (PM_SOURCE_FIT_MODEL_PIX_WEIGHTS) {
+                yErr->data.F32[nPix] = 1.0 / source->weight->data.F32[i][j];
+            } else {
+                yErr->data.F32[nPix] = 1.0 / PM_SOURCE_FIT_MODEL_WEIGHT;
+            }
+            nPix++;
+        }
+    }
+    x->n = nPix;
+    y->n = nPix;
+    yErr->n = nPix;
+
+    psVector *params = model->params;
+    psVector *dparams = model->dparams;
+
+    // create the minimization constraints
+    psMinConstraint *constraint = psMinConstraintAlloc();
+    constraint->paramMask = psVectorAlloc (params->n, PS_TYPE_U8);
+    constraint->checkLimits = model->modelLimits;
+
+    // set parameter mask based on fitting mode
+    int nParams = 0;
+    switch (mode) {
+    case PM_SOURCE_FIT_NORM:
+        // NORM-only model fits only source normalization (Io)
+        nParams = 1;
+        psVectorInit (constraint->paramMask, 1);
+        constraint->paramMask->data.U8[PM_PAR_I0] = 0;
+        break;
+    case PM_SOURCE_FIT_PSF:
+        // PSF model only fits x,y,Io
+        nParams = 3;
+        psVectorInit (constraint->paramMask, 1);
+        constraint->paramMask->data.U8[PM_PAR_I0] = 0;
+        constraint->paramMask->data.U8[PM_PAR_XPOS] = 0;
+        constraint->paramMask->data.U8[PM_PAR_YPOS] = 0;
+        break;
+    case PM_SOURCE_FIT_EXT:
+        // EXT model fits all params (except sky)
+        nParams = params->n - 1;
+        psVectorInit (constraint->paramMask, 0);
+        constraint->paramMask->data.U8[PM_PAR_SKY] = 1;
+        break;
+    default:
+        psAbort("invalid fitting mode");
+    }
+    // force the floating parameters to fall within the contraint ranges
+    for (int i = 0; i < params->n; i++) {
+        model->modelLimits (PS_MINIMIZE_PARAM_MIN, i, params->data.F32, NULL);
+        model->modelLimits (PS_MINIMIZE_PARAM_MAX, i, params->data.F32, NULL);
+    }
+
+    if (nPix <  nParams + 1) {
+        psTrace ("psModules.objects", 4, "insufficient valid pixels\n");
+        model->flags |= PM_MODEL_STATUS_BADARGS;
+        psFree (x);
+        psFree (y);
+        psFree (yErr);
+        psFree (constraint);
+        return(false);
+    }
+
+    psMinimization *myMin = psMinimizationAlloc (PM_SOURCE_FIT_MODEL_NUM_ITERATIONS, PM_SOURCE_FIT_MODEL_TOLERANCE);
+
+    psImage *covar = psImageAlloc (params->n, params->n, PS_TYPE_F32);
+
+    fitStatus = psMinimizeLMChi2(myMin, covar, params, constraint, x, y, yErr, model->modelFunc);
+    for (int i = 0; i < dparams->n; i++) {
+        if (psTraceGetLevel("psModules.objects") >= 4) {
+            fprintf (stderr, "%f ", params->data.F32[i]);
+        }
+        if ((constraint->paramMask != NULL) && constraint->paramMask->data.U8[i])
+            continue;
+        dparams->data.F32[i] = sqrt(covar->data.F32[i][i]);
+    }
+    psTrace ("psModules.objects", 4, "niter: %d, chisq: %f", myMin->iter, myMin->value);
+
+    // save the resulting chisq, nDOF, nIter
+    model->chisq = myMin->value;
+    model->nIter = myMin->iter;
+    model->nDOF  = y->n - nParams;
+    model->flags |= PM_MODEL_STATUS_FITTED;
+    if (!fitStatus) model->flags |= PM_MODEL_STATUS_NONCONVERGE;
+
+    // get the Gauss-Newton distance for fixed model parameters
+    // hold the fitted parameters fixed; mask sky which is not fitted at all
+    if (constraint->paramMask != NULL) {
+        psVector *delta = psVectorAlloc (params->n, PS_TYPE_F32);
+        psVector *altmask = psVectorAlloc (params->n, PS_TYPE_U8);
+        altmask->data.U8[0] = 1;
+        for (int i = 1; i < dparams->n; i++) {
+            altmask->data.U8[i] = (constraint->paramMask->data.U8[i]) ? 0 : 1;
+        }
+        psMinimizeGaussNewtonDelta(delta, params, altmask, x, y, yErr, model->modelFunc);
+
+        for (int i = 0; i < dparams->n; i++) {
+            if (!constraint->paramMask->data.U8[i])
+                continue;
+            // note that delta is the value *subtracted* from the parameter
+            // to get the new guess.  for dparams to represent the direction
+            // of motion, we need to take -delta
+            dparams->data.F32[i] = -delta->data.F32[i];
+        }
+        psFree (delta);
+        psFree (altmask);
+    }
+
+    // models can go insane: reject these
+    onPic &= (params->data.F32[PM_PAR_XPOS] >= source->pixels->col0);
+    onPic &= (params->data.F32[PM_PAR_XPOS] <  source->pixels->col0 + source->pixels->numCols);
+    onPic &= (params->data.F32[PM_PAR_YPOS] >= source->pixels->row0);
+    onPic &= (params->data.F32[PM_PAR_YPOS] <  source->pixels->row0 + source->pixels->numRows);
+    if (!onPic) {
+        model->flags |= PM_MODEL_STATUS_OFFIMAGE;
+    }
+
+    source->mode |= PM_SOURCE_MODE_FITTED;
+
+    psFree(x);
+    psFree(y);
+    psFree(yErr);
+    psFree(myMin);
+    psFree(covar);
+    psFree(constraint);
+
+    rc = (onPic && fitStatus);
+    psTrace("psModules.objects", 5, "---- %s(%d) end ----\n", __func__, rc);
+    return(rc);
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceFitModel.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceFitModel.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceFitModel.h	(revision 22157)
@@ -0,0 +1,78 @@
+/* @file  pmSourceFitModel.h
+ *
+ * @author EAM, IfA; GLG, MHPCC
+ *
+ * @version $Revision: 1.6 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-06-20 02:22:26 $
+ * Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+# ifndef PM_SOURCE_FIT_MODEL_H
+# define PM_SOURCE_FIT_MODEL_H
+
+/// @addtogroup Objects Object Detection / Analysis Functions
+/// @{
+
+typedef enum {
+    PM_SOURCE_FIT_NORM,
+    PM_SOURCE_FIT_PSF,
+    PM_SOURCE_FIT_EXT,
+    PM_SOURCE_FIT_PSF_AND_SKY,
+    PM_SOURCE_FIT_EXT_AND_SKY
+} pmSourceFitMode;
+
+bool pmSourceFitModelInit(
+    float nIter,   ///< max number of allowed iterations
+    float tol,      ///< convergence criterion
+    float weight,      ///< use this weight for constant-weight fits
+    bool poissonErrors   // use poisson errors for fits?
+);
+
+/** pmSourceFitModel()
+ *
+ * Fit the requested model to the specified source. The starting guess for the model is given
+ * by the input source.model parameter values. The pixels of interest are specified by the
+ * source.pixels and source.mask entries. This function calls psMinimizeLMChi2() on the image
+ * data. The function returns TRUE on success or FALSE on failure.
+ *
+ */
+bool pmSourceFitModel(
+    pmSource *source,   ///< The input pmSource
+    pmModel *model,   ///< model to be fitted
+    pmSourceFitMode mode,  ///< define parameters to be fitted
+    psMaskType maskVal                  ///< Value to mask
+);
+
+
+// initialize data for a group of object models
+bool pmSourceFitSetInit (pmModelType type);
+
+// clear data for a group of object models
+void pmSourceFitSetClear (void);
+
+// function used to set limits for a group of models
+bool pmSourceFitSet_CheckLimits (psMinConstraintMode mode, int nParam, float *params, float *betas);
+
+// function used to fit a group of object models
+psF32 pmSourceFitSet_Function(psVector *deriv,
+                              const psVector *params,
+                              const psVector *x);
+
+/** pmSourceFitSet()
+ *
+ * Fit the requested model to the specified source. The starting guess for the model is given
+ * by the input source.model parameter values. The pixels of interest are specified by the
+ * source.pixels and source.mask entries. This function calls psMinimizeLMChi2() on the image
+ * data. The function returns TRUE on success or FALSE on failure.
+ *
+ */
+bool pmSourceFitSet(
+    pmSource *source,   ///< The input pmSource
+    psArray *modelSet,   ///< model to be fitted
+    pmSourceFitMode mode,  ///< define parameters to be fitted
+    psMaskType maskVal                  ///< Vale to mask
+
+);
+
+/// @}
+# endif /* PM_SOURCE_FIT_MODEL_H */
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceFitSet.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceFitSet.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceFitSet.c	(revision 22157)
@@ -0,0 +1,495 @@
+/** @file  pmSourceFitModel.c
+ *
+ *  fit single source models to image pixels
+ *
+ *  @author EAM, IfA
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.11 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-01-02 20:39:04 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmGrowthCurve.h"
+#include "pmResiduals.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmModelClass.h"
+#include "pmSourceFitModel.h"
+#include "pmSourceFitSet.h"
+
+// save as static values so they may be set externally
+static psF32 PM_SOURCE_FIT_MODEL_NUM_ITERATIONS = 15;
+static psF32 PM_SOURCE_FIT_MODEL_TOLERANCE = 0.1;
+static psF32 PM_SOURCE_FIT_MODEL_WEIGHT = 1.0;
+static bool  PM_SOURCE_FIT_MODEL_PIX_WEIGHTS = true;
+
+/********************* Source Model Set Functions ***************************/
+
+static pmSourceFitSetData *thisSet = NULL;
+
+static void pmSourceFitSetDataFree (pmSourceFitSetData *set) {
+    if (!set) return;
+
+    psFree (set->modelSet);
+    psFree (set->paramSet);
+    psFree (set->derivSet);
+    return;
+}
+
+pmSourceFitSetData *pmSourceFitSetDataAlloc (psArray *modelSet) {
+    PS_ASSERT_PTR_NON_NULL(modelSet, NULL);
+
+    pmSourceFitSetData *set = (pmSourceFitSetData *) psAlloc(sizeof(pmSourceFitSetData));
+    psMemSetDeallocator(set, (psFreeFunc) pmSourceFitSetDataFree);
+
+    set->modelSet = psMemIncrRefCounter (modelSet);
+    set->paramSet = psArrayAlloc (modelSet->n);
+    set->derivSet = psArrayAlloc (modelSet->n);
+    set->nParamSet = 0;
+
+    for (int i = 0; i < modelSet->n; i++) {
+        pmModel *model = modelSet->data[i];
+
+        int nParams = pmModelClassParameterCount (model->type);
+
+        set->paramSet->data[i] = psVectorCopy (NULL, model->params, PS_DATA_F32);
+        set->derivSet->data[i] = psVectorAlloc (nParams, PS_DATA_F32);
+        psVectorInit (set->derivSet->data[i], 0.0);
+
+        set->nParamSet += nParams;
+    }
+
+    return set;
+}
+
+bool psMemCheckSourceFitSetData(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) pmSourceFitSetDataFree);
+}
+
+
+// this function is called with the full set of parameters and the beta values in a single vector
+bool pmSourceFitSetCheckLimits (psMinConstraintMode mode, int nParam, float *params,
+                                float *betas)
+{
+    PS_ASSERT_PTR_NON_NULL(thisSet, false);
+
+    // nParam is the parameter in the full sequence.  determine which single model this comes from
+    int nModel = -1;
+    int nParamOne = -1;
+    int nParamBase = 0;
+    for (int i = 0; i < thisSet->modelSet->n; i++) {
+        psVector *param = thisSet->paramSet->data[i];
+        if ((nParamBase <= nParam) && (nParam < nParamBase + param->n)) {
+            nModel = i;
+            nParamOne = nParam - nParamBase;
+            break;
+        }
+        nParamBase += param->n;
+    }
+    assert (nModel > -1);
+
+    pmModel *model = thisSet->modelSet->data[nModel];
+
+    // pass the single model function a pointer to the start of that model's sequence
+    float *paramOne = params + nParamBase;
+    float *betaOne = betas + nParamBase;
+    bool status = model->modelLimits (mode, nParamOne, paramOne, betaOne);
+    return status;
+}
+
+// merge parameters from FitSet models into single param and deriv vectors
+bool pmSourceFitSetJoin (psVector *deriv, psVector *param, pmSourceFitSetData *set)
+{
+    PS_ASSERT_PTR_NON_NULL(set, false);
+    PS_ASSERT_PTR_NON_NULL(set->paramSet, false);
+    PS_ASSERT_PTR_NON_NULL(set->derivSet, false);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(set->paramSet, set->derivSet, false);
+    int n = 0;
+    int sum = 0;
+    for (int i = 0; i < set->paramSet->n; i++) {
+        sum+= set->paramSet->n;
+    }
+
+    // Must assert that the deriv and param psVectors are large enough, or else
+    // a seg fault occurs.
+    // XXX: Put the correct error call in here:
+    if (0) {
+        if (deriv->n < sum || param->n < sum) {
+            PS_ASSERT_PTR_NON_NULL(0, false);
+        }
+    }
+
+    for (int i = 0; i < set->paramSet->n; i++) {
+
+        psVector *paramOne = set->paramSet->data[i];
+        psVector *derivOne = set->derivSet->data[i];
+
+	// one or the other (param or deriv) must be set
+	assert ((deriv != NULL) || (param != NULL));
+
+	// if we are setting derive, derivOne and paramOne must be same length
+        assert ((deriv == NULL) || (paramOne->n == derivOne->n));
+	
+        for (int j = 0; j < paramOne->n; j++, n++) {
+	    if (param) {
+		param->data.F32[n] = paramOne->data.F32[j];
+	    }
+            if (deriv) {
+                deriv->data.F32[n] = derivOne->data.F32[j];
+            }
+        }
+    }
+    return true;
+}
+
+// distribute parameters from single param and deriv vectors into FitSet models
+bool pmSourceFitSetSplit (pmSourceFitSetData *set, const psVector *deriv, const psVector *param) 
+{
+    PS_ASSERT_VECTOR_NON_NULL(param, false);
+    PS_ASSERT_PTR_NON_NULL(set, false);
+    PS_ASSERT_PTR_NON_NULL(set->paramSet, false);
+    PS_ASSERT_PTR_NON_NULL(set->derivSet, false);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(set->paramSet, set->derivSet, false);
+
+    int n = 0;
+    for (int i = 0; i < set->paramSet->n; i++) {
+
+        psVector *paramOne = set->paramSet->data[i];
+        psVector *derivOne = set->derivSet->data[i];
+        assert ((deriv == NULL) || (paramOne->n == derivOne->n));
+
+        for (int j = 0; j < paramOne->n; j++, n++) {
+            paramOne->data.F32[j] = param->data.F32[n];
+            if (deriv) {
+                derivOne->data.F32[j] = deriv->data.F32[n];
+            }
+        }
+    }
+    return true;
+}
+
+bool pmSourceFitSetValues (pmSourceFitSetData *set, const psVector *dparam, 
+                           const psVector *param, pmSource *source,
+                           psMinimization *myMin, int nPix, bool fitStatus)
+{
+    PS_ASSERT_PTR_NON_NULL(set, false);
+    PS_ASSERT_PTR_NON_NULL(set->paramSet, false);
+    PS_ASSERT_VECTOR_NON_NULL(dparam, false);
+    PS_ASSERT_VECTOR_NON_NULL(param, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_PTR_NON_NULL(source->pixels, false);
+    PS_ASSERT_PTR_NON_NULL(myMin, false);
+
+    bool onPic = true;
+
+    int n = 0;
+    for (int i = 0; i < set->paramSet->n; i++) {
+
+        pmModel *model = set->modelSet->data[i];
+
+        for (int j = 0; j < model->params->n; j++, n++) {
+            if (psTraceGetLevel("psModules.objects") >= 4) {
+                fprintf (stderr, "%f ", param->data.F32[n]);
+            }
+            model->params->data.F32[j] = param->data.F32[n];
+            model->dparams->data.F32[j] = dparam->data.F32[n];
+        }
+        psTrace ("psModules.objects", 4, " src %d", i);
+
+        // save the resulting chisq, nDOF, nIter
+        // these are not unique for any one source
+        model->chisq = myMin->value;
+        model->nIter = myMin->iter;
+        model->nDOF  = nPix - model->params->n;
+
+        // set the model success or failure status
+        model->flags |= PM_MODEL_STATUS_FITTED;
+        if (!fitStatus) model->flags |= PM_MODEL_STATUS_NONCONVERGE;
+
+        // models can go insane: reject these
+        onPic &= (model->params->data.F32[PM_PAR_XPOS] >= source->pixels->col0);
+        onPic &= (model->params->data.F32[PM_PAR_XPOS] <  source->pixels->col0 + source->pixels->numCols);
+        onPic &= (model->params->data.F32[PM_PAR_XPOS] >= source->pixels->row0);
+        onPic &= (model->params->data.F32[PM_PAR_XPOS] <  source->pixels->row0 + source->pixels->numRows);
+        if (!onPic) model->flags |= PM_MODEL_STATUS_OFFIMAGE;
+    }
+    return true;
+}
+
+// generic psMinLMM-style function for fitting: split the parameters across the models, call
+// each model function one-at-a-time, the join the derivatives for on-going evaluation
+psF32 pmSourceFitSetFunction(psVector *deriv, const psVector *param, const psVector *x)
+{
+    PS_ASSERT_PTR_NON_NULL(thisSet, NAN);
+    float chisqSum = 0.0;
+    float chisqOne = 0.0;
+    pmSourceFitSetSplit (thisSet, deriv, param);
+
+    for (int i = 0; i < thisSet->modelSet->n; i++) {
+
+        pmModel *model = thisSet->modelSet->data[i];
+
+        psVector *paramOne = thisSet->paramSet->data[i];
+        psVector *derivOne = thisSet->derivSet->data[i];
+
+        chisqOne = model->modelFunc (derivOne, paramOne, x);
+        chisqSum += chisqOne;
+    }
+    pmSourceFitSetJoin (deriv, NULL, thisSet);
+
+    return (chisqSum);
+}
+
+// XXX allow the mode to be a function of the object (eg, S/N)
+bool pmSourceFitSetMasks (psMinConstraint *constraint, pmSourceFitSetData *set,
+                          pmSourceFitMode mode)
+{
+    PS_ASSERT_PTR_NON_NULL(set, false);
+    PS_ASSERT_PTR_NON_NULL(constraint, false);
+
+    // unmask everyone
+    psVectorInit (constraint->paramMask, 0);
+
+    int n = 0;
+    for (int i = 0; i < set->paramSet->n; i++) {
+        psVector *paramOne = set->paramSet->data[i];
+
+        switch (mode) {
+          case PM_SOURCE_FIT_NORM:
+            // mask all but Xo,Yo,Io
+            for (int j = 0; j < paramOne->n; j++) {
+                if (j == PM_PAR_I0) continue;
+                constraint->paramMask->data.U8[n + j] = 1;
+            }
+            break;
+          case PM_SOURCE_FIT_PSF:
+            // mask all but Xo,Yo,Io
+            for (int j = 0; j < paramOne->n; j++) {
+                if (j == PM_PAR_XPOS) continue;
+                if (j == PM_PAR_YPOS) continue;
+                if (j == PM_PAR_I0) continue;
+                constraint->paramMask->data.U8[n + j] = 1;
+            }
+            break;
+          case PM_SOURCE_FIT_EXT:
+            // EXT model fits all params (except sky)
+            constraint->paramMask->data.U8[n + PM_PAR_SKY] = 1;
+            break;
+          default:
+            psAbort("invalid fitting mode");
+        }
+        n += paramOne->n;
+    }
+    return true;
+}
+
+bool pmSourcePrintModelSet (FILE *file, psArray *modelSet) {
+
+    for (int i = 0; i < modelSet->n; i++) {
+	pmModel *model = modelSet->data[i];
+	int nParams = pmModelClassParameterCount (model->type);
+	for (int j = 0; j < nParams; j++) {
+	    fprintf (file, "%d %d  : %f %f\n", i, j, model->params->data.F32[j], model->dparams->data.F32[j]);
+	}
+    }
+    return true;
+}
+
+bool pmSourceFitSetPrint (FILE *file, pmSourceFitSetData *set) {
+
+    for (int i = 0; i < set->paramSet->n; i++) {
+        psVector *paramOne = set->paramSet->data[i];
+        psVector *derivOne = set->derivSet->data[i];
+        for (int j = 0; j < paramOne->n; j++) {
+	    fprintf (file, "%d %d  : %f %f\n", i, j, paramOne->data.F32[j], derivOne->data.F32[j]);
+        }
+    }
+    return true;
+}
+
+bool pmSourceFitSet (pmSource *source,
+                     psArray *modelSet,
+                     pmSourceFitMode mode,
+                     psMaskType maskVal)
+{
+    psTrace("psModules.objects", 3, "---- %s begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_PTR_NON_NULL(source->pixels, false);
+    PS_ASSERT_PTR_NON_NULL(source->maskObj, false);
+    PS_ASSERT_PTR_NON_NULL(source->weight, false);
+
+    bool fitStatus = true;
+    bool onPic     = true;
+
+    // maximum number of valid pixels
+    int nPix = source->pixels->numRows * source->pixels->numCols;
+
+    // construct the coordinate and value entries
+    psArray *x = psArrayAllocEmpty(nPix);
+    psVector *y = psVectorAllocEmpty(nPix, PS_TYPE_F32);
+    psVector *yErr = psVectorAllocEmpty(nPix, PS_TYPE_F32);
+
+    // fill in the coordinate and value entries
+    nPix = 0;
+    for (psS32 i = 0; i < source->pixels->numRows; i++) {
+        for (psS32 j = 0; j < source->pixels->numCols; j++) {
+            // skip masked points
+            if (source->maskObj->data.U8[i][j] & maskVal) {
+                continue;
+            }
+            // skip zero-weight points
+            if (source->weight->data.F32[i][j] == 0) {
+                continue;
+            }
+            // skip nan values in image
+            if (!isfinite(source->pixels->data.F32[i][j])) {
+                continue;
+            }
+
+            psVector *coord = psVectorAlloc(2, PS_TYPE_F32);
+
+            // Convert i/j to image space:
+            coord->data.F32[0] = (psF32) (j + source->pixels->col0);
+            coord->data.F32[1] = (psF32) (i + source->pixels->row0);
+            x->data[nPix] = (psPtr *) coord;
+            y->data.F32[nPix] = source->pixels->data.F32[i][j];
+
+            // psMinimizeLMChi2 takes wt = 1/dY^2.  suggestion from RHL is to use the local sky
+            // as weight to avoid the bias from systematic errors here we would just use the
+            // source sky variance
+            if (PM_SOURCE_FIT_MODEL_PIX_WEIGHTS) {
+                yErr->data.F32[nPix] = 1.0 / source->weight->data.F32[i][j];
+            } else {
+                yErr->data.F32[nPix] = 1.0 / PM_SOURCE_FIT_MODEL_WEIGHT;
+            }
+            nPix++;
+        }
+    }
+    x->n = nPix;
+    y->n = nPix;
+    yErr->n = nPix;
+
+    // create the FitSet and set the initial parameter guesses
+    thisSet = pmSourceFitSetDataAlloc (modelSet);
+
+    // define param and deriv vectors for complete set of parameters
+    psVector *params = psVectorAlloc (thisSet->nParamSet, PS_TYPE_F32);
+
+    // set the param and deriv vectors based on the curent values
+    pmSourceFitSetJoin (NULL, params, thisSet);
+
+    // create the minimization constraints
+    psMinConstraint *constraint = psMinConstraintAlloc();
+    constraint->paramMask = psVectorAlloc (thisSet->nParamSet, PS_TYPE_U8);
+    constraint->checkLimits = pmSourceFitSetCheckLimits;
+
+    pmSourceFitSetMasks (constraint, thisSet, mode);
+
+    // force the floating parameters to fall within the contraint ranges
+    for (int i = 0; i < params->n; i++) {
+        pmSourceFitSetCheckLimits (PS_MINIMIZE_PARAM_MIN, i, params->data.F32, NULL);
+        pmSourceFitSetCheckLimits (PS_MINIMIZE_PARAM_MAX, i, params->data.F32, NULL);
+    }
+
+    if (psTraceGetLevel("psModules.objects") >= 5) {
+        for (int i = 0; i < params->n; i++) {
+            fprintf (stderr, "%d %f %d\n", i, params->data.F32[i], constraint->paramMask->data.U8[i]);
+        }
+    }
+
+    if (nPix <  thisSet->nParamSet + 1) {
+        psTrace (__func__, 4, "insufficient valid pixels\n");
+        psTrace("psModules.objects", 3, "---- %s() end : fail pixels ----\n", __func__);
+        for (int i = 0; i < modelSet->n; i++) {
+            pmModel *model = modelSet->data[i];
+            model->flags |= PM_MODEL_STATUS_BADARGS;
+        }
+        psFree (x);
+        psFree (y);
+        psFree (yErr);
+        psFree (params);
+        psFree(constraint);
+        psFree (thisSet);
+        thisSet = NULL;
+        return(false);
+    }
+
+    psMinimization *myMin = psMinimizationAlloc (PM_SOURCE_FIT_MODEL_NUM_ITERATIONS, PM_SOURCE_FIT_MODEL_TOLERANCE);
+
+    psImage *covar = psImageAlloc (params->n, params->n, PS_TYPE_F32);
+
+    fitStatus = psMinimizeLMChi2(myMin, covar, params, constraint, x, y, yErr, pmSourceFitSetFunction);
+    if (!fitStatus) {
+        psTrace("psModules.objects", 4, "Failed to fit model (%ld components)\n", modelSet->n);
+    }
+
+    // parameter errors from the covariance matrix
+    psVector *dparams = psVectorAlloc (thisSet->nParamSet, PS_TYPE_F32);
+    for (int i = 0; i < dparams->n; i++) {
+        if ((constraint->paramMask != NULL) && constraint->paramMask->data.U8[i])
+            continue;
+        dparams->data.F32[i] = sqrt(covar->data.F32[i][i]);
+    }
+
+    // get the Gauss-Newton distance for fixed model parameters
+    if (constraint->paramMask != NULL) {
+        psVector *delta = psVectorAlloc (params->n, PS_TYPE_F32);
+        psVector *altmask = psVectorAlloc (params->n, PS_TYPE_U8);
+        altmask->data.U8[0] = 1;
+        for (int i = 1; i < dparams->n; i++) {
+            altmask->data.U8[i] = (constraint->paramMask->data.U8[i]) ? 0 : 1;
+        }
+        psMinimizeGaussNewtonDelta(delta, params, altmask, x, y, yErr, pmSourceFitSetFunction);
+
+        for (int i = 0; i < dparams->n; i++) {
+            if (!constraint->paramMask->data.U8[i])
+                continue;
+            // note that delta is the value *subtracted* from the parameter
+            // to get the new guess.  for dparams to represent the direction
+            // of motion, we need to take -delta
+            dparams->data.F32[i] = -delta->data.F32[i];
+        }
+        psFree (delta);
+        psFree (altmask);
+    }
+
+    pmSourceFitSetValues (thisSet, dparams, params, source, myMin, y->n, fitStatus);
+    psTrace ("psModules.objects", 5, "onPic: %d, fitStatus: %d, nIter: %d, chisq: %f, nPix: %ld\n", onPic, fitStatus, myMin->iter, myMin->value, y->n);
+
+    source->mode |= PM_SOURCE_MODE_FITTED;
+
+    psFree(x);
+    psFree(y);
+    psFree(yErr);
+    psFree(myMin);
+    psFree(covar);
+    psFree(constraint);
+    psFree(params);
+    psFree(dparams);
+    psFree(thisSet);
+
+    thisSet = NULL;
+
+    bool rc = (onPic && fitStatus);
+    psTrace("psModules.objects", 5, "---- %s end (%d) ----\n", __func__, rc);
+    return(rc);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceFitSet.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceFitSet.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceFitSet.h	(revision 22157)
@@ -0,0 +1,57 @@
+/* @file  pmSourceFitSet.h
+ *
+ * @author EAM, IfA; GLG, MHPCC
+ *
+ * @version $Revision: 1.6 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-11-10 01:09:20 $
+ * Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+# ifndef PM_SOURCE_FIT_SET_H
+# define PM_SOURCE_FIT_SET_H
+
+/// @addtogroup Objects Object Detection / Analysis Functions
+/// @{
+
+typedef struct {
+    psArray *modelSet;
+    psArray *paramSet;
+    psArray *derivSet;
+    int nParamSet;
+} pmSourceFitSetData;
+
+// initialize data for a group of object models
+pmSourceFitSetData *pmSourceFitSetDataAlloc (psArray *modelSet);
+bool psMemCheckSourceFitSetData(psPtr ptr);
+
+// function used to set limits for a group of models
+bool pmSourceFitSetCheckLimits (psMinConstraintMode mode, int nParam, float *params, float *betas);
+
+bool pmSourceFitSetJoin (psVector *deriv, psVector *param, pmSourceFitSetData *set);
+bool pmSourceFitSetSplit (pmSourceFitSetData *set, const psVector *deriv, const psVector *param);
+bool pmSourceFitSetValues (pmSourceFitSetData *set, const psVector *dparam, const psVector *param, pmSource *source, psMinimization *myMin, int nPix, bool fitStatus);
+
+psF32 pmSourceFitSetFunction(psVector *deriv, const psVector *param, const psVector *x);
+bool pmSourceFitSetMasks (psMinConstraint *constraint, pmSourceFitSetData *set, pmSourceFitMode mode);
+
+/** pmSourceFitSet()
+ *
+ * Fit the requested model to the specified source. The starting guess for the model is given
+ * by the input source.model parameter values. The pixels of interest are specified by the
+ * source.pixels and source.mask entries. This function calls psMinimizeLMChi2() on the image
+ * data. The function returns TRUE on success or FALSE on failure.
+ *
+ */
+bool pmSourceFitSet(
+    pmSource *source,   ///< The input pmSource
+    psArray *modelSet,   ///< model to be fitted
+    pmSourceFitMode mode,  ///< define parameters to be fitted
+    psMaskType maskVal                  ///< Vale to mask
+
+);
+
+bool pmSourcePrintModelSet (FILE *file, psArray *modelSet);
+bool pmSourceFitSetPrint (FILE *file, pmSourceFitSetData *set);
+
+/// @}
+# endif /* PM_SOURCE_FIT_MODEL_H */
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO.c	(revision 22157)
@@ -0,0 +1,973 @@
+/** @file  pmSourceIO.c
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.61 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-06-09 00:43:15 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <strings.h>            /* for strn?casecmp */
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+#include "pmConcepts.h"
+
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmGrowthCurve.h"
+#include "pmResiduals.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmModelClass.h"
+#include "pmSourceIO.h"
+
+#define BLANK_HEADERS "BLANK.HEADERS"   // Name of metadata in camera configuration containing header names
+                                        // for putting values into a blank PHU
+
+// translations between psphot object types and dophot object types
+int pmSourceGetDophotType (pmSource *source)
+{
+    PS_ASSERT_PTR_NON_NULL(source, -1);
+
+    switch (source->type) {
+
+      case PM_SOURCE_TYPE_DEFECT:
+      case PM_SOURCE_TYPE_SATURATED:
+        return (8);
+
+      case PM_SOURCE_TYPE_STAR:
+        if (source->mode & PM_SOURCE_MODE_SATSTAR)
+            return (10);
+        if (source->mode & PM_SOURCE_MODE_POOR)
+            return (7);
+        if (source->mode & PM_SOURCE_MODE_FAIL)
+            return (4);
+        return (1);
+
+      case PM_SOURCE_TYPE_EXTENDED:
+        return (2);
+
+      default:
+        return (0);
+    }
+    return (0);
+}
+
+// translations between psphot object types and dophot object types
+bool pmSourceSetDophotType (pmSource *source, int type)
+{
+    PS_ASSERT_PTR_NON_NULL(source, false);
+
+    if (type == 4) {
+        source->mode |= PM_SOURCE_MODE_FAIL;
+    }
+    if (type == 7) {
+        source->mode |= PM_SOURCE_MODE_POOR;
+    }
+    if (type == 10) {
+        source->mode |= PM_SOURCE_MODE_SATSTAR;
+    }
+
+    switch (type) {
+      case 1:
+      case 4:
+      case 7:
+      case 10:
+        source->type = PM_SOURCE_TYPE_STAR;
+        return true;
+      case 2:
+        source->type = PM_SOURCE_TYPE_EXTENDED;
+        return true;
+      case 8:
+        source->type = PM_SOURCE_TYPE_DEFECT;
+        return true;
+      default:
+        return false;
+    }
+    return false;
+}
+
+// Given a FITS file pointer, write the table of object data
+bool pmFPAviewWriteObjects (const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(file->fpa, false);
+
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        if (!pmFPAWriteObjects (fpa, view, file, config)) {
+            psError(PS_ERR_IO, false, "Failed to write objects from fpa");
+            return false;
+        }
+        return true;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        psError(PS_ERR_UNKNOWN, false, "Writing chip == %d (>= chips->n == %ld)", view->chip, fpa->chips->n);
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        if (!pmChipWriteObjects (chip, view, file, config)) {
+            psError(PS_ERR_IO, false, "Failed to write objects from chip");
+            return false;
+        }
+        return true;
+    }
+
+    if (view->cell >= chip->cells->n) {
+        psError(PS_ERR_UNKNOWN, false, "Writing cell == %d (>= cells->n == %ld)",
+                view->cell, chip->cells->n);
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        if (!pmCellWriteObjects (cell, view, file, config)) {
+            psError(PS_ERR_IO, false, "Failed to write objects from cell");
+            return false;
+        }
+
+        return true;
+    }
+
+    if (view->readout >= cell->readouts->n) {
+        psError(PS_ERR_UNKNOWN, false, "Writing readout == %d (>= readouts->n == %ld)",
+                view->readout, cell->readouts->n);
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[view->readout];
+
+    if (!pmReadoutWriteObjects (readout, view, file, config)) {
+        psError(PS_ERR_IO, false, "Failed to write objects from readout %d", view->readout);
+        return false;
+    }
+
+    return true;
+}
+
+// read in all chip-level Objects files for this FPA
+bool pmFPAWriteObjects (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(fpa->chips, false);
+
+    pmFPAview *thisView = pmFPAviewAlloc (view->nRows);
+    *thisView = *view;
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        thisView->chip = i;
+        if (!pmChipWriteObjects (chip, thisView, file, config)) {
+            psError(PS_ERR_IO, false, "Failed to write %dth chip", i);
+            psFree (thisView);
+            return false;
+        }
+    }
+    psFree (thisView);
+    return true;
+}
+
+// read in all cell-level Objects files for this chip
+bool pmChipWriteObjects (pmChip *chip, const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(chip->cells, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    pmFPAview *thisView = pmFPAviewAlloc (view->nRows);
+    *thisView = *view;
+
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCell *cell = chip->cells->data[i];
+        thisView->cell = i;
+        if (!pmCellWriteObjects (cell, thisView, file, config)) {
+            psError(PS_ERR_IO, false, "Failed to write %dth cell", i);
+            psFree (thisView);
+            return false;
+        }
+    }
+    psFree (thisView);
+    return true;
+}
+
+// read in all readout-level Objects files for this cell
+bool pmCellWriteObjects (pmCell *cell, const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(cell->readouts, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    pmFPAview *thisView = pmFPAviewAlloc (view->nRows);
+    *thisView = *view;
+
+    for (int i = 0; i < cell->readouts->n; i++) {
+        pmReadout *readout = cell->readouts->data[i];
+        thisView->readout = i;
+        if (!pmReadoutWriteObjects (readout, thisView, file, config)) {
+            psError(PS_ERR_IO, false, "Failed to write %dth readout", i);
+            psFree (thisView);
+            return false;
+        }
+    }
+    psFree (thisView);
+    return true;
+}
+
+// write out all readout-level Objects files for this cell
+bool pmReadoutWriteObjects (pmReadout *readout, const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(file->fpa, false);
+
+    bool status;
+    pmHDU *hdu;
+    psMetadata *updates;
+    psMetadata *outhead;
+
+    char *exttype  = NULL;
+    char *dataname = NULL;
+    char *xsrcname = NULL;
+    char *xfitname = NULL;
+    char *headname = NULL;
+
+    // if sources is NULL, write out an empty table
+    // input / output sources are stored on the readout->analysis as "PSPHOT.SOURCES" -- a better name might be something like PM_SOURCE_DATA
+    psArray *sources = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.SOURCES");
+    if (!sources) {
+        sources = psArrayAlloc(0);
+        psMetadataAddArray(readout->analysis, PS_LIST_TAIL, "PSPHOT.SOURCES", PS_META_REPLACE, "Blank array of sources", sources);
+        psFree(sources); // Held onto by the metadata, so we can continue to use
+    }
+
+    switch (file->type) {
+      case PM_FPA_FILE_RAW:
+        pmSourcesWriteRAW (sources, file->filename);
+        break;
+
+      case PM_FPA_FILE_OBJ:
+        pmSourcesWriteOBJ (sources, file->filename);
+        break;
+
+      case PM_FPA_FILE_SX:
+        pmSourcesWriteSX (sources, file->filename);
+        break;
+
+      case PM_FPA_FILE_CMP:
+        // a SPLIT format : only one header and object table per file
+        hdu = pmFPAviewThisHDU (view, file->fpa);
+        if (!hdu) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find HDU to write sources.");
+            return false;
+        }
+
+        // copy the header to an output header, add the output header data
+        outhead = psMetadataCopy (NULL, hdu->header);
+
+        // copy over the entries saved by PSPHOT
+        updates = psMetadataLookupPtr (NULL, readout->analysis, "PSPHOT.HEADER");
+        if (updates) {
+            psMetadataCopy (outhead, updates);
+        }
+
+        // copy over the entries saved by PSASTRO
+        updates = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.HEADER");
+        if (updates) {
+            psMetadataCopy (outhead, updates);
+        }
+
+        bool status = pmSourcesWriteCMP (sources, file->filename, outhead);
+        psFree (outhead);
+
+        if (!status) {
+            psError(PS_ERR_IO, false, "Failed to write CMP file\n");
+            return false;
+        }
+        break;
+
+      case PM_FPA_FILE_CMF:
+        // write a header? (only if this is the first readout for cell)
+        //   note that the file->header is set to track the last hdu->header written
+        // write the data? (always?)
+
+        // get the current header
+        hdu = pmFPAviewThisHDU (view, file->fpa);
+        if (!hdu) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find HDU to write sources.");
+            return false;
+        }
+
+        // determine the output table format
+        psMetadata *recipe = psMetadataLookupMetadata(&status, config->recipes, "PSPHOT");
+        if (!status) {
+          psError(PS_ERR_UNKNOWN, true, "missing recipe PSPHOT in config data");
+          return false;
+        }
+
+        // if this is not TRUE, the output files only contain the psf measurements.
+        bool XSRC_OUTPUT = psMetadataLookupBool(&status, recipe, "EXTENDED_SOURCE_ANALYSIS");
+        bool XFIT_OUTPUT = psMetadataLookupBool(&status, recipe, "EXTENDED_SOURCE_FITS");
+
+        // define the EXTNAME values for the different data segments:
+        {
+            // lookup the EXTNAME values used for table data and image header segments
+            char *rule = NULL;
+
+            // Menu of EXTNAME rules
+            psMetadata *menu = psMetadataLookupMetadata(&status, file->camera, "EXTNAME.RULES");
+            if (!menu) {
+                psError(PS_ERR_UNKNOWN, true, "missing EXTNAME.RULES in camera.config");
+                return false;
+            }
+
+            // EXTNAME for image header
+            rule = psMetadataLookupStr(&status, menu, "CMF.HEAD");
+            if (!rule) {
+                psError(PS_ERR_UNKNOWN, true, "missing entry for CMF.HEAD in EXTNAME.RULES in camera.config");
+                return false;
+            }
+            headname = pmFPAfileNameFromRule (rule, file, view);
+
+            // EXTNAME for table data
+            rule = psMetadataLookupStr(&status, menu, "CMF.DATA");
+            if (!rule) {
+                psError(PS_ERR_UNKNOWN, true, "missing entry for CMF.DATA in EXTNAME.RULES in camera.config");
+                return false;
+            }
+            dataname = pmFPAfileNameFromRule (rule, file, view);
+
+            if (XSRC_OUTPUT) {
+              // EXTNAME for extended source data table
+              rule = psMetadataLookupStr(&status, menu, "CMF.XSRC");
+              if (!rule) {
+                psError(PS_ERR_UNKNOWN, true, "missing entry for CMF.XSRC in EXTNAME.RULES in camera.config");
+                return false;
+              }
+              xsrcname = pmFPAfileNameFromRule (rule, file, view);
+            }
+            if (XFIT_OUTPUT) {
+              // EXTNAME for extended source data table
+              rule = psMetadataLookupStr(&status, menu, "CMF.XFIT");
+              if (!rule) {
+                psError(PS_ERR_UNKNOWN, true, "missing entry for CMF.XFIT in EXTNAME.RULES in camera.config");
+                return false;
+              }
+              xfitname = pmFPAfileNameFromRule (rule, file, view);
+            }
+        }
+
+        // write out the IMAGE header segment (only for the first readout of the cell)
+        {
+            // this header block is new, write it to disk
+            if (hdu->header != file->header) {
+                // add EXTNAME, EXTHEAD, EXTTYPE to header
+                psMetadataAddStr (hdu->header, PS_LIST_TAIL, "EXTDATA", PS_META_REPLACE, "name of table extension", dataname);
+                psMetadataAddStr (hdu->header, PS_LIST_TAIL, "EXTTYPE", PS_META_REPLACE, "extension type", "IMAGE");
+                if (!file->wrote_phu) {
+                    // this hdu->header acts as the PHU: set EXTEND to be true
+                    psMetadataAddBool (hdu->header, PS_LIST_TAIL, "EXTEND", PS_META_REPLACE, "this file has extensions", true);
+                    file->wrote_phu = true;
+                }
+
+                // save psphot and psastro metadata in the image and table headers
+                updates = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.HEADER");
+                if (updates) {
+                    psMetadataCopy (hdu->header, updates);
+                }
+                updates = psMetadataLookupPtr (&status, readout->analysis, "PSASTRO.HEADER");
+                if (updates) {
+                    psMetadataCopy (hdu->header, updates);
+                }
+
+                pmConfigConformHeader(hdu->header, file->format);
+
+                psFitsWriteBlank (file->fits, hdu->header, headname);
+                psTrace ("pmFPAfile", 5, "wrote ext head %s (type: %d)\n", file->filename, file->type);
+                file->header = hdu->header;
+            }
+        }
+
+        // write out the TABLE data segment
+        {
+            // create a header to hold the output data
+            outhead = psMetadataAlloc ();
+
+            exttype = psMemIncrRefCounter (psMetadataLookupStr(&status, recipe, "OUTPUT.FORMAT"));
+            if (!exttype) {
+                exttype = psStringCopy ("SMPDATA");
+            }
+
+            // write the links to the image header
+            psMetadataAddStr (outhead, PS_LIST_TAIL, "EXTHEAD", PS_META_REPLACE, "name of image extension w/", headname);
+            psMetadataAddStr (outhead, PS_LIST_TAIL, "EXTTYPE", PS_META_REPLACE, "extension type", exttype);
+            psFree (exttype);
+
+            // if we request XSRC output, add the XSRC name to this header
+            if (xsrcname) {
+              psMetadataAddStr (outhead, PS_LIST_TAIL, "XSRCNAME", PS_META_REPLACE, "name of XSRC table extension", xsrcname);
+            }
+            if (xfitname) {
+              psMetadataAddStr (outhead, PS_LIST_TAIL, "XFITNAME", PS_META_REPLACE, "name of XFIT table extension", xfitname);
+            }
+
+            // XXX these are case-sensitive since the EXTYPE is case-sensitive
+            status = false;
+            if (!strcmp (exttype, "SMPDATA")) {
+                status = pmSourcesWrite_SMPDATA (file->fits, sources, file->header, outhead, dataname);
+            }
+            if (!strcmp (exttype, "PS1_DEV_0")) {
+                status = pmSourcesWrite_PS1_DEV_0 (file->fits, sources, file->header, outhead, dataname);
+            }
+            if (!strcmp (exttype, "PS1_DEV_1")) {
+                status = pmSourcesWrite_PS1_DEV_1 (file->fits, sources, file->header, outhead, dataname, xsrcname);
+            }
+            if (xsrcname) {
+              if (!strcmp (exttype, "PS1_DEV_1")) {
+                status = pmSourcesWrite_PS1_DEV_1_XSRC (file->fits, sources, xsrcname, recipe);
+              }
+            }
+            if (xfitname) {
+              if (!strcmp (exttype, "PS1_DEV_1")) {
+                status = pmSourcesWrite_PS1_DEV_1_XFIT (file->fits, sources, xfitname);
+              }
+            }
+            if (!status) {
+                psError(PS_ERR_IO, false, "writing CMF data to %s with format %s\n", file->filename, exttype);
+                psFree (headname);
+                psFree (dataname);
+                psFree (xsrcname);
+                psFree (xfitname);
+                psFree (outhead);
+                return false;
+            }
+        }
+
+        psTrace ("pmFPAfile", 5, "wrote ext data %s (type: %d)\n", file->filename, file->type);
+
+        psFree (headname);
+        psFree (dataname);
+        psFree (xsrcname);
+        psFree (xfitname);
+        psFree (outhead);
+        break;
+
+      default:
+        fprintf (stderr, "warning: type mismatch\n");
+        break;
+    }
+    return true;
+}
+// a MEF CMF file has: PHU, CELL-HEAD, TABLE, CELL-HEAD, TABLE, TABLE, TABLE...
+
+// if this file needs to have a PHU written out, write one
+bool pmSource_CMF_WritePHU (const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    bool status;
+
+    // not needed if already written
+    if (file->wrote_phu) return true;
+
+    // not needed if not FPA
+    // XXX this prevents us from defining a SPLIT/MEF CMF file...
+    if (file->fileLevel != PM_FPA_LEVEL_FPA) return true;
+
+    // not needed if only one chip
+    if (file->fpa->chips->n == 1) return true;
+
+    // find the FPA phu
+    pmHDU *phu = pmFPAviewThisPHU (view, file->fpa);
+
+    // if there is no PHU, this is a single header+image (extension-less) file. This could be
+    // the case for an input SPLIT set of files being written out as a MEF.  if there is a PHU,
+    // write it out as a 'blank'
+    psMetadata *outhead = psMetadataAlloc();
+    if (phu) {
+        psMetadataCopy (outhead, phu->header);
+    }
+
+    pmConfigConformHeader (outhead, file->format);
+
+    // We need to get some FPA-level concepts in there
+    // This is a hack, not very pretty.  But then, so is writing the FPA in this manner without
+    // using the pmFPAMosaic functions....
+    // XXX why are these not correctly inserted by pmConfigConformHeader??
+
+    psMetadata *headers = psMetadataLookupMetadata(NULL, file->fpa->camera, BLANK_HEADERS); // Header names
+    if (!headers) {
+        psError(PS_ERR_UNEXPECTED_NULL, false,
+                "Unable to find %s metadata within camera configuration", BLANK_HEADERS);
+        psFree(outhead);
+        return false;
+    }
+
+    {
+        const char *mjdName = psMetadataLookupStr(NULL, headers, "FPA.TIME"); // Header name
+        if (!mjdName || strlen(mjdName) == 0) {
+            psError(PS_ERR_UNEXPECTED_NULL, false,
+                    "Unable to find FPA.TIME in %s within camera configuration.", BLANK_HEADERS);
+            psFree(outhead);
+            return false;
+        }
+        psTime *time = psMetadataLookupTime(NULL, file->fpa->concepts, "FPA.TIME"); // Time of observation
+        double mjd = psTimeToMJD(time); // The MJD of observation
+        psMetadataAddF64(outhead, PS_LIST_TAIL, mjdName, PS_META_REPLACE,
+                         "Time of observation", mjd);
+    }
+
+    {
+        const char *expName = psMetadataLookupStr(NULL, headers, "FPA.EXPOSURE"); // Header name
+        if (!expName || strlen(expName) == 0) {
+            psError(PS_ERR_UNEXPECTED_NULL, false,
+                    "Unable to find FPA.EXPOSURE in %s within camera configuration.",
+                    BLANK_HEADERS);
+            psFree(outhead);
+            return false;
+        }
+        float exptime = psMetadataLookupF32(NULL, file->fpa->concepts,
+                                            "FPA.EXPOSURE"); // Exposure time
+        psMetadataAddF32(outhead, PS_LIST_TAIL, expName, PS_META_REPLACE,
+                         "Exposure time (sec)", exptime);
+    }
+
+    {
+        const char *amName = psMetadataLookupStr(NULL, headers, "FPA.AIRMASS"); // Header name
+        if (!amName || strlen(amName) == 0) {
+            psError(PS_ERR_UNEXPECTED_NULL, false,
+                    "Unable to find FPA.AIRMASS in %s within camera configuration.",
+                    BLANK_HEADERS);
+            psFree(outhead);
+            return false;
+        }
+        float airmass = psMetadataLookupF32(NULL, file->fpa->concepts, "FPA.AIRMASS"); // Airmass
+        psMetadataAddF32(outhead, PS_LIST_TAIL, amName, PS_META_REPLACE,
+                         "Observation airmass", airmass);
+    }
+
+    psMetadata *fileData = psMetadataLookupMetadata(NULL, file->format, "FILE"); // File information
+    const char *fpaNameHdr = psMetadataLookupStr(NULL, fileData, "FPA.NAME");
+    if (fpaNameHdr && strlen(fpaNameHdr) > 0) {
+        const char *fpaName = psMetadataLookupStr(NULL, file->fpa->concepts, "FPA.NAME");
+        psMetadataAddStr(outhead, PS_LIST_TAIL, fpaNameHdr, PS_META_REPLACE, "FPA name", fpaName);
+    }
+
+    // if we have mosaic-level astrometry information, add it here:
+    psMetadata *updates = psMetadataLookupPtr (&status, file->fpa->analysis, "PSASTRO.HEADER");
+    if (updates) {
+        psMetadataCopy (outhead, updates);
+    }
+
+    psMetadataAddBool (outhead, PS_LIST_TAIL, "EXTEND", PS_META_REPLACE, "this file has extensions", true);
+    psFitsWriteBlank (file->fits, outhead, "");
+    file->wrote_phu = true;
+
+    psTrace ("pmFPAfile", 5, "wrote phu %s (type: %d)\n", file->filename, file->type);
+    psFree (outhead);
+
+    return true;
+}
+
+// Given a FITS file pointer, read the table of object data
+// XXX add in error handling
+bool pmFPAviewReadObjects (const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(file->fpa, false);
+
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        pmFPAReadObjects (fpa, view, file, config);
+        return true;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        pmChipReadObjects (chip, view, file, config);
+        return true;
+    }
+
+    if (view->cell >= chip->cells->n) {
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        pmCellReadObjects (cell, view, file, config);
+        return true;
+    }
+
+    if (view->readout >= cell->readouts->n) {
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[view->readout];
+
+    pmReadoutReadObjects (readout, view, file, config);
+    return true;
+}
+
+// read in all chip-level Objects files for this FPA
+bool pmFPAReadObjects (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(file->fpa, false);
+    PS_ASSERT_PTR_NON_NULL(file->fpa->chips, false);
+
+    pmFPAview *thisView = pmFPAviewAlloc (view->nRows);
+    *thisView = *view;
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        thisView->chip = i;
+        pmChipReadObjects (chip, thisView, file, config);
+    }
+    psFree (thisView);
+
+    if (!pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_HEADER, true, NULL)) {
+        psError(PS_ERR_IO, false, "Failed to read concepts for fpa.\n");
+        return false;
+    }
+
+    return true;
+}
+
+// read in all cell-level Objects files for this chip
+bool pmChipReadObjects (pmChip *chip, const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(chip->cells, false);
+
+    pmFPAview *thisView = pmFPAviewAlloc (view->nRows);
+    *thisView = *view;
+
+    chip->data_exists = false;
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCell *cell = chip->cells->data[i];
+        thisView->cell = i;
+        pmCellReadObjects (cell, thisView, file, config);
+        if (!cell->data_exists) continue;
+        chip->data_exists = true;
+    }
+    psFree (thisView);
+
+    if (!pmConceptsReadChip(chip, PM_CONCEPT_SOURCE_HEADER, true, true, NULL)) {
+        psError(PS_ERR_IO, false, "Failed to read concepts for chip.\n");
+        return false;
+    }
+
+    return true;
+}
+
+// read in all readout-level Objects files for this cell
+bool pmCellReadObjects (pmCell *cell, const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(cell->readouts, false);
+
+    pmFPAview *thisView = pmFPAviewAlloc (view->nRows);
+    *thisView = *view;
+
+    // multiple readout mode is not yet defined for CMP or CMF files
+    // if they have not been allocated, allocate a single readout
+    if (!cell->readouts || !cell->readouts->n) {
+        pmReadout *readout = pmReadoutAlloc (cell);
+        psFree (readout);
+    }
+
+    cell->data_exists = false;
+    for (int i = 0; i < cell->readouts->n; i++) {
+        pmReadout *readout = cell->readouts->data[i];
+        thisView->readout = i;
+        pmReadoutReadObjects (readout, thisView, file, config);
+        if (!readout->data_exists) {
+            continue;
+        }
+
+        // load in the concept information for this cell
+        if (!pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_HEADER, true, NULL)) {
+            //psError(PS_ERR_UNKNOWN, false, "Failed to read concepts for cell");
+            //return false;
+            psWarning("Difficulty reading concepts for cell; attempting to proceed.");
+        }
+        cell->data_exists = true;
+    }
+    psFree (thisView);
+
+    if (!pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_HEADER, true, NULL)) {
+        //psError(PS_ERR_UNKNOWN, false, "Failed to read concepts for cell");
+        //return false;
+        psWarning("Difficulty reading concepts for cell; attempting to proceed.");
+    }
+
+    return true;
+}
+
+// read in all readout-level Objects files for this cell
+bool pmReadoutReadObjects (pmReadout *readout, const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    bool status;
+    psArray *sources = NULL;
+    pmHDU *hdu;
+
+    switch (file->type) {
+      case PM_FPA_FILE_OBJ:
+        psError(PS_ERR_UNKNOWN, true, "OBJ is not supported as an input object format");
+        return false;
+
+      case PM_FPA_FILE_SX:
+        psError(PS_ERR_UNKNOWN, true, "SX is not supported as an input object format");
+        return false;
+
+      case PM_FPA_FILE_CMP:
+        // a SPLIT format : only one header and object table per file
+
+        // read in header, if not yet loaded
+        hdu = pmFPAviewThisHDU (view, file->fpa);
+#if 0
+        if (file->filename)
+            psFree (file->filename);
+        file->filename = pmFPAfileNameFromRule (file->filerule, file, view);
+#endif
+
+        // indirect filenames
+        if (!strcasecmp (file->filename, "@FILES")) {
+            psFree (file->filename);
+            char *filesrc = pmFPAfileNameFromRule (file->filesrc, file, view);
+            file->filename = psMetadataLookupStr (&status, file->names, filesrc);
+            psFree (filesrc);
+            if (file->filename == NULL) return false;
+            // psMetadataLookupStr just returns a view, file->filename must be protected
+            psMemIncrRefCounter (file->filename);
+        }
+
+        // read the PHU from this file
+        file->fits = psFitsOpen (file->filename, "r");
+        if (hdu->header != NULL) {
+            psFree (hdu->header);
+        }
+        hdu->header = psFitsReadHeader (NULL, file->fits);
+        psFitsClose (file->fits);
+        file->fits = NULL;
+
+        sources = pmSourcesReadCMP (file->filename, hdu->header);
+        break;
+
+      case PM_FPA_FILE_CMF:
+        // read in header, if not yet loaded
+        hdu = pmFPAviewThisHDU (view, file->fpa);
+
+        // lookup the EXTNAME values used for table data and image header segments
+        char *rule = NULL;
+        // Menu of EXTNAME rules
+        psMetadata *menu = psMetadataLookupMetadata(&status, file->camera, "EXTNAME.RULES");
+        if (!menu) {
+            psError(PS_ERR_UNKNOWN, true, "missing EXTNAME.RULES in camera.config");
+            return false;
+        }
+        // EXTNAME for image header
+        rule = psMetadataLookupStr(&status, menu, "CMF.HEAD");
+        if (!rule) {
+            psError(PS_ERR_UNKNOWN, true, "missing entry for CMF.HEAD in EXTNAME.RULES in camera.config");
+            return false;
+        }
+        char *headname = pmFPAfileNameFromRule (rule, file, view);
+        // EXTNAME for table data
+        rule = psMetadataLookupStr(&status, menu, "CMF.DATA");
+        if (!rule) {
+            psError(PS_ERR_UNKNOWN, true, "missing entry for CMF.DATA in EXTNAME.RULES in camera.config");
+            return false;
+        }
+        char *dataname = pmFPAfileNameFromRule (rule, file, view);
+
+        // advance to the IMAGE HEADER extension
+        if (hdu->header == NULL) {
+            // if the IMAGE header does not exist, we have no data for this view
+            if (!psFitsMoveExtName (file->fits, headname)) {
+                readout->data_exists = false;
+                psFree (headname);
+                psFree (dataname);
+                return true;
+            }
+            hdu->header = psFitsReadHeader (NULL, file->fits);
+        }
+
+        // we need to find the corresponding table EXTNAME.
+        // first check the header
+        char *extdata = psMetadataLookupStr (NULL, hdu->header, "EXTDATA");
+        if (extdata) {
+            // if EXTDATA is defined in the header, use that value for 'dataname'
+            psFree (dataname);
+            dataname = psMemIncrRefCounter (extdata);
+        }
+
+        // advance to the table data extension
+        // since we have read the IMAGE header, the TABLE header should exist
+        if (!psFitsMoveExtName (file->fits, dataname)) {
+            psAbort("cannot find data extension %s in %s", dataname, file->filename);
+        }
+
+        psMetadata *tableHeader = psFitsReadHeader(NULL, file->fits); // The FITS header
+        if (!tableHeader) psAbort("cannot read table header");
+
+        char *exttype = psMetadataLookupStr (NULL, tableHeader, "EXTTYPE");
+        if (!exttype) psAbort("cannot read table type");
+
+        // XXX these are case-sensitive since the EXTYPE is case-sensitive
+        if (!strcmp (exttype, "SMPDATA")) {
+            sources = pmSourcesRead_SMPDATA (file->fits, hdu->header);
+        }
+        if (!strcmp (exttype, "PS1_DEV_0")) {
+            sources = pmSourcesRead_PS1_DEV_0 (file->fits, hdu->header);
+        }
+        if (!strcmp (exttype, "PS1_DEV_1")) {
+            sources = pmSourcesRead_PS1_DEV_1 (file->fits, hdu->header);
+        }
+
+        psTrace("psModules.objects", 6, "read CMF table from %s : %s : %s", file->filename, headname, dataname);
+        psFree (headname);
+        psFree (dataname);
+        psFree (tableHeader);
+        break;
+
+      default:
+        fprintf (stderr, "warning: type mismatch\n");
+        break;
+    }
+    readout->data_exists = true;
+    status = psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.SOURCES", PS_DATA_ARRAY, "input sources", sources);
+    psFree (sources);
+    return true;
+}
+
+bool pmFPAviewCheckDataStatusForSources (const pmFPAview *view, const pmFPAfile *file)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(file->fpa, false);
+
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        bool exists = pmFPACheckDataStatusForSources (fpa);
+        return exists;
+    }
+    if (view->chip >= fpa->chips->n) {
+        psError(PS_ERR_IO, true, "Requested chip == %d >= fpa->chips->n == %ld", view->chip, fpa->chips->n);
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        bool exists = pmChipCheckDataStatusForSources (chip);
+        return exists;
+    }
+    if (view->cell >= chip->cells->n) {
+        psError(PS_ERR_IO, true, "Requested cell == %d >= chip->cells->n == %ld", view->cell, chip->cells->n);
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        bool exists = pmCellCheckDataStatusForSources (cell);
+        return exists;
+    }
+
+    if (view->readout >= cell->readouts->n) {
+        psError(PS_ERR_IO, true, "Requested readout == %d >= cell->readouds->n == %ld", view->readout, cell->readouts->n);
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[view->readout];
+
+    bool exists = pmReadoutCheckDataStatusForSources (readout);
+    return exists;
+}
+
+bool pmFPACheckDataStatusForSources (const pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(fpa->chips, false);
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        if (!chip) continue;
+        if (pmChipCheckDataStatusForSources (chip)) return true;
+    }
+    return false;
+}
+
+bool pmChipCheckDataStatusForSources (const pmChip *chip)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(chip->cells, false);
+
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCell *cell = chip->cells->data[i];
+        if (!cell) continue;
+        if (pmCellCheckDataStatusForSources (cell)) return true;
+    }
+    return false;
+}
+
+bool pmCellCheckDataStatusForSources (const pmCell *cell)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(cell->readouts, false);
+
+    for (int i = 0; i < cell->readouts->n; i++) {
+        pmReadout *readout = cell->readouts->data[i];
+        if (!readout) continue;
+        if (pmReadoutCheckDataStatusForSources (readout)) return true;
+    }
+    return false;
+}
+
+bool pmReadoutCheckDataStatusForSources (const pmReadout *readout)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+
+    bool status;
+
+    // select the psf of interest
+    pmPSF *psf = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.SOURCES");
+    if (!psf) return false;
+    return true;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO.h	(revision 22157)
@@ -0,0 +1,65 @@
+/* @file  pmSourceIO.h
+ * @brief functions to read and write object files
+ *
+ * @author EAM, IfA; GLG, MHPCC
+ *
+ * @version $Revision: 1.17 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-05-29 13:25:38 $
+ * Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+# ifndef PM_SOURCE_IO_H
+# define PM_SOURCE_IO_H
+
+/// @addtogroup Objects Object Detection / Analysis Functions
+/// @{
+
+int pmSourceGetDophotType (pmSource *source);
+bool pmSourceSetDophotType (pmSource *source, int type);
+
+bool pmSourcesWriteRAW (psArray *sources, char *filename);
+bool pmSourcesWriteOBJ (psArray *sources, char *filename);
+bool pmSourcesWriteSX (psArray *sources, char *filename);
+bool pmSourcesWriteCMP (psArray *sources, char *filename, psMetadata *header);
+
+bool pmSourcesWrite_SMPDATA (psFits *fits, psArray *sources, psMetadata *imageHeader, psMetadata *tableHeader, char *extname);
+bool pmSourcesWrite_PS1_DEV_0 (psFits *fits, psArray *sources, psMetadata *imageHeader, psMetadata *tableHeader, char *extname);
+bool pmSourcesWrite_PS1_DEV_1 (psFits *fits, psArray *sources, psMetadata *imageHeader, psMetadata *tableHeader, char *extname, char *xsrcname);
+bool pmSourcesWrite_PS1_DEV_1_XSRC (psFits *fits, psArray *sources, char *extname, psMetadata *recipe);
+bool pmSourcesWrite_PS1_DEV_1_XFIT (psFits *fits, psArray *sources, char *extname);
+
+bool pmSource_CMF_WritePHU (const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+
+psArray *pmSourcesReadCMP (char *filename, psMetadata *header);
+
+psArray *pmSourcesRead_SMPDATA (psFits *fits, psMetadata *header);
+psArray *pmSourcesRead_PS1_DEV_0 (psFits *fits, psMetadata *header);
+psArray *pmSourcesRead_PS1_DEV_1 (psFits *fits, psMetadata *header);
+
+bool pmSourcesWritePSFs (psArray *sources, char *filename);
+bool pmSourcesWriteEXTs (psArray *sources, char *filename, bool require);
+bool pmSourcesWriteNULLs (psArray *sources, char *filename);
+bool pmMomentsWriteText (psArray *sources, char *filename);
+bool pmPeaksWriteText (psArray *peaks, char *filename);
+
+bool pmFPAviewReadObjects (const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmFPAReadObjects (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmChipReadObjects (pmChip *chip, const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmCellReadObjects (pmCell *cell, const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmReadoutReadObjects (pmReadout *readout, const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+
+bool pmFPAviewWriteObjects (const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmFPAWriteObjects (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmChipWriteObjects (pmChip *chip, const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmCellWriteObjects (pmCell *cell, const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmReadoutWriteObjects (pmReadout *readout, const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+
+bool pmFPAviewCheckDataStatusForSources (const pmFPAview *view, const pmFPAfile *file);
+bool pmFPACheckDataStatusForSources (const pmFPA *fpa);
+bool pmChipCheckDataStatusForSources (const pmChip *chip);
+bool pmCellCheckDataStatusForSources (const pmCell *cell);
+bool pmReadoutCheckDataStatusForSources (const pmReadout *readout);
+
+/// @}
+# endif /* PM_SOURCE_IO_H */
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO_CMP.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO_CMP.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO_CMP.c	(revision 22157)
@@ -0,0 +1,297 @@
+/** @file  pmSourceIO.c
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.32 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-11-10 01:09:20 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmGrowthCurve.h"
+#include "pmResiduals.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmModelClass.h"
+#include "pmSourceIO.h"
+
+// XXX make sure in and out have consistent zero-point adjustments
+// XXX make sure the angle in correctly translated to/from degrees
+// XXX we lose all information from the 'type' field
+
+// XXX update this file is we convert to PAR[4] : SigmaX*sqrt(2) (not 1/SigmaX)
+
+// elixir-style pseudo FITS table (header + ascii list)
+bool pmSourcesWriteCMP (psArray *sources, char *filename, psMetadata *header)
+{
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+    PS_ASSERT_PTR_NON_NULL(filename, false);
+    PS_ASSERT_PTR_NON_NULL(header, false);
+
+    int i, type;
+    // psMetadataItem *mdi;
+    psF32 *PAR, *dPAR;
+    float lsky = 0;
+    bool status;
+    psEllipseAxes axes;
+
+    // find config information for output header
+    float ZERO_POINT = psMetadataLookupF32 (&status, header, "ZERO_PT");
+    if (!status) {
+        ZERO_POINT = 25.0;
+    }
+
+    // MEF elements have XTENSION, not SIMPLE: remove this (replace with SIMPLE)
+    psMetadataLookupStr (&status, header, "XTENSION");
+    if (status) {
+        psMetadataRemoveKey (header, "XTENSION");
+    }
+
+    // create file, write-out header
+    psMetadataAddS32 (header, PS_LIST_HEAD, "NAXIS", PS_META_REPLACE, "head data only", 0);
+    psMetadataAddBool (header, PS_LIST_HEAD, "SIMPLE", PS_META_REPLACE, "CMP file, not simple", false);
+
+    psFits *fits = psFitsOpen (filename, "w");
+    if (fits == NULL) {
+        psError(PS_ERR_IO, false, "can't open output file for write %s\n", filename);
+        return false;
+    }
+    // XXX what is the EXTNAME??
+    if (!psFitsWriteBlank(fits, header, "")) {
+        psError(PS_ERR_IO, false, "Writing header to %s\n", filename);
+        (void)psFitsClose(fits);
+        return false;
+    }
+    if (!psFitsClose(fits)) {
+        const psErrorCode code = psErrorCodeLast();
+
+        if (code == PS_ERR_BAD_FITS) {
+            psErrorClear();
+        } else {
+            psError(PS_ERR_IO, false, "Closing %s\n", filename);
+            return false;
+        }
+    }
+
+    // re-open, add data to end of file
+    FILE *f = fopen (filename, "a+");
+    if (f == NULL) {
+        psLogMsg ("WriteSourceOBJ", 3, "can't reopen output file for append %s\n", filename);
+        psError(PS_ERR_IO, false, "can't open output file for output %s\n", filename);
+        return false;
+    }
+
+    fseeko(f, 0, SEEK_END);
+
+    psLine *line = psLineAlloc (67);  // 66 is imclean-defined line length
+
+    // write sources with models first
+    for (i = 0; i < sources->n; i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+
+	// no difference between PSF and non-PSF model
+        pmModel *model = pmSourceGetModel (NULL, source);
+        if (model == NULL)
+            continue;
+
+        PAR = model->params->data.F32;
+        dPAR = model->dparams->data.F32;
+
+        type = pmSourceGetDophotType (source);
+        lsky = (source->sky < 1.0) ? 0.0 : log10(source->sky);
+
+        axes = pmPSF_ModelToAxes (PAR, 20.0);
+
+        float errMag = isfinite(source->errMag) ? source->errMag : 999;
+
+        psLineInit (line);
+        psLineAdd (line, "%6.1f ",  PAR[PM_PAR_XPOS]);
+        psLineAdd (line, "%6.1f ",  PAR[PM_PAR_YPOS]);
+        psLineAdd (line, "%6.3f ",  PS_MIN (99.0, source->psfMag + ZERO_POINT));
+        psLineAdd (line, "%03d ",   PS_MIN (999, (int)errMag));
+        psLineAdd (line, "%2d ",    type);
+        psLineAdd (line, "%3.1f ",  lsky);
+        psLineAdd (line, "%6.3f ",  PS_MIN (99.0, source->extMag + ZERO_POINT));
+        psLineAdd (line, "%6.3f ",  PS_MIN (99.0, source->apMag  + ZERO_POINT));
+        psLineAdd (line, "%6.2f ",  axes.major);
+        psLineAdd (line, "%6.2f ",  axes.minor);
+        psLineAdd (line, "%5.1f\n", axes.theta);
+        fwrite (line->line, 1, line->Nline, f);
+    }
+    fclose (f);
+    psFree (line);
+    return true;
+}
+
+# define BYTES_STAR 66
+# define BLOCK 1000
+
+// elixir-style pseudo FITS table (header + ascii list)
+psArray *pmSourcesReadCMP (char *filename, psMetadata *header)
+{
+    PS_ASSERT_PTR_NON_NULL(filename, false);
+    PS_ASSERT_PTR_NON_NULL(header, false);
+
+    bool status;
+    int Ninstar;
+    psF32 *PAR, *dPAR;
+    psEllipseAxes axes;
+
+    // define PSF model type
+    int modelType = pmModelClassGetType ("PS_MODEL_GAUSS");
+
+    char *PSF_NAME = psMetadataLookupStr (&status, header, "PSF_NAME");
+    if (PSF_NAME != NULL) {
+        modelType = pmModelClassGetType (PSF_NAME);
+    }
+
+    // find config information for output header
+    float ZERO_POINT = psMetadataLookupF32 (&status, header, "ZERO_PT");
+    if (!status)
+        ZERO_POINT = 25.0;
+
+    // how many lines in the header?
+    long nLines = header->list->n;
+    off_t nBytes = nLines * 80;
+    if (nBytes % 2880) {
+        off_t nBlock = 1 + (off_t)(nBytes / 2880);
+        nBytes = nBlock * 2880;
+    }
+
+    // re-open, seek to end of header
+    FILE *f = fopen (filename, "r");
+    if (f == NULL) {
+        psLogMsg ("pmSourcesReadCMP", 3, "can't open output file for input %s\n", filename);
+        return NULL;
+    }
+
+    fseeko(f, nBytes, SEEK_SET);
+
+    // prepare array to store data
+    int nStars = psMetadataLookupS32 (&status, header, "NSTARS");
+    psArray *sources = psArrayAlloc (nStars);
+    sources->n = 0;
+
+    // we have fixed bytes / line : use that info
+    // XXX use the min of nStars and BLOCK?
+    char *buffer = psAlloc (BYTES_STAR*PS_MIN(nStars, BLOCK));
+
+    int Nextra = 0;
+    while (true) {
+        /* load next data block */
+        // XXX fix the use of two vars with different case -JH
+        off_t Nbytes = BYTES_STAR * BLOCK - Nextra;
+        off_t nbytes = fread (&buffer[Nextra], 1, Nbytes, f);
+        if (nbytes == 0) {
+            goto done_load;
+        }
+        nbytes += Nextra;
+
+        /* check line-by-line integrity */
+        char *c  = buffer;
+        char *c2 = NULL;
+        while (c < buffer + nbytes) {
+            for (c2 = c; *c2 == '\n'; c2++)
+                ;
+            if (c2 > c) { /* extra return chars */
+                memmove (c, c2, (int)(buffer + nbytes - c2));
+                int Nskip = c2 - c;
+                nbytes -= Nskip;
+                memset(buffer + nbytes, '\0', Nskip);
+                psLogMsg (__func__, 4, "deleted %d extra return chars\n", Nskip);
+            }
+            c2 = strchr (c, '\n');
+            if (c2 == (char *) NULL) {
+                goto done_check;
+            }
+            c2++;
+            if ((c2 - c) != BYTES_STAR) { /* bad line, delete it */
+                memmove (c, c2, (int)(buffer + nbytes - c2));
+                int Nskip = c2 - c;
+                nbytes -= Nskip;
+		memset(buffer + nbytes, '\0', Nskip);
+                psLogMsg (__func__, 4, "deleted line, %d extra chars\n", Nskip);
+            } else {
+                c = c2;
+            }
+        }
+done_check:
+
+        /* extract data for stars */
+        Ninstar = nbytes / BYTES_STAR;
+        Nextra = nbytes % BYTES_STAR;
+        for (int j = 0; j < Ninstar; j++) {
+            psString line = psStringNCopy (&buffer[j*BYTES_STAR], BYTES_STAR);
+
+            psArray *array = psStringSplitArray (line, " ", false);
+
+            // XXX this is a bit cheap: I don't even attempt to interpret the
+            // sextractor / dophot analysis to distinguish stars and galaxies
+            // your milage may vary...
+            pmSource *source = pmSourceAlloc ();
+            source->modelPSF = pmModelAlloc (modelType);
+            source->type = PM_SOURCE_TYPE_STAR;
+
+            PAR = source->modelPSF->params->data.F32;
+            dPAR = source->modelPSF->dparams->data.F32;
+
+            PAR[PM_PAR_SKY] = pow (atof (array->data[5]), 10.0);
+            PAR[PM_PAR_XPOS] = atof (array->data[0]);
+            PAR[PM_PAR_YPOS] = atof (array->data[1]);
+            source->psfMag = atof (array->data[2]);
+            source->extMag = atof (array->data[6]);
+            source->errMag = atof (array->data[3]) / 1000.0;
+            source->apMag  = atof (array->data[7]);
+            axes.major     = atof (array->data[8]);
+            axes.minor     = atof (array->data[9]);
+            axes.theta  = atof (array->data[10]);
+
+            if (!isfinite(axes.major))
+                goto skip_source;
+            if (!isfinite(axes.minor))
+                goto skip_source;
+            if (!isfinite(axes.theta))
+                goto skip_source;
+
+	    pmPSF_AxesToModel (PAR, axes);
+
+            psArrayAdd (sources, 100, source);
+
+skip_source:
+            psFree (line);
+            psFree (array);
+            psFree (source);
+
+        }
+    }
+done_load:
+
+    // XXX if sources->n != nStars, give an error?
+    psFree (buffer);
+
+    fclose (f);
+    return (sources);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO_OBJ.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO_OBJ.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO_OBJ.c	(revision 22157)
@@ -0,0 +1,103 @@
+/** @file  pmSourceIO.c
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.16 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-11-10 01:09:20 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmGrowthCurve.h"
+#include "pmResiduals.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmModelClass.h"
+#include "pmSourceIO.h"
+
+// dophot-style output list with fixed line width
+bool pmSourcesWriteOBJ (psArray *sources, char *filename)
+{
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+    PS_ASSERT_PTR_NON_NULL(filename, false);
+
+    int type;
+    psF32 *PAR, *dPAR;
+    float dmag, apResid;
+    psEllipseAxes axes;
+
+    psTimerStart ("string");
+
+    psLine *line = psLineAlloc (104);  // 104 is dophot-defined line length
+
+    FILE *f = fopen (filename, "w");
+    if (f == NULL) {
+        psLogMsg (__func__, 3, "can't open output file for output %s\n", filename);
+        return false;
+    }
+
+    // write sources with models
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+
+	// no difference between PSF and non-PSF model
+        pmModel *model = pmSourceGetModel (NULL, source);
+        if (model == NULL)
+            continue;
+
+        PAR = model->params->data.F32;
+        dPAR = model->dparams->data.F32;
+
+        dmag = dPAR[PM_PAR_I0] / PAR[PM_PAR_I0];
+        type = pmSourceGetDophotType (source);
+        if ((source->apMag < 99.0) && (source->psfMag < 99.0)) {
+            apResid = source->apMag - source->psfMag;
+        } else {
+            apResid = 0.0;
+        }
+
+        axes = pmPSF_ModelToAxes (PAR, 20.0);
+
+        psLineInit (line);
+        psLineAdd (line, "%3d",   type);
+        psLineAdd (line, "%8.2f", PAR[PM_PAR_XPOS]);
+        psLineAdd (line, "%8.2f", PAR[PM_PAR_YPOS]);
+        psLineAdd (line, "%8.3f", source->psfMag);
+        psLineAdd (line, "%6.3f", dmag);
+        psLineAdd (line, "%9.2f", source->sky);
+        psLineAdd (line, "%9.3f", axes.major);
+        psLineAdd (line, "%9.3f", axes.minor);
+        psLineAdd (line, "%7.2f", axes.theta);
+        psLineAdd (line, "%8.3f", source->extMag);
+        psLineAdd (line, "%8.3f", source->apMag);
+        psLineAdd (line, "%8.2f\n", apResid);
+        fwrite (line->line, 1, line->Nline, f);
+    }
+    fclose (f);
+    psFree (line);
+    fprintf (stderr, "%f seconds for %d objects\n", psTimerMark ("string"), (int)sources->n);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO_PS1_DEV_0.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO_PS1_DEV_0.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO_PS1_DEV_0.c	(revision 22157)
@@ -0,0 +1,224 @@
+/** @file  pmSourceIO.c
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.15 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-01-02 20:39:04 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmGrowthCurve.h"
+#include "pmResiduals.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmModelClass.h"
+#include "pmSourceIO.h"
+
+// panstars-style FITS table output (header + table in 1st extension)
+// this format consists of a header derived from the image header
+// followed by a zero-size matrix, followed by the table data
+
+// this output format is valid for psphot analysis of an image, and does not include calibrated
+// values derived in the DVO database.
+// XXX how do I generate the source tables which I need to send to PSPS?
+// XXX: input parameter imageHeader is never used.
+bool pmSourcesWrite_PS1_DEV_0 (psFits *fits, psArray *sources, psMetadata *imageHeader,
+                               psMetadata *tableHeader, char *extname)
+{
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+    PS_ASSERT_PTR_NON_NULL(extname, false);
+
+    psArray *table;
+    psMetadata *row;
+    int i;
+    psF32 *PAR, *dPAR;
+    psEllipseAxes axes;
+    psF32 xPos, yPos;
+    psF32 xErr, yErr;
+
+    table = psArrayAllocEmpty (sources->n);
+
+    // we write out all sources, regardless of quality.  the source flags tell us the state
+    for (i = 0; i < sources->n; i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+
+        // no difference between PSF and non-PSF model
+        pmModel *model = pmSourceGetModel(NULL, source);
+
+        if (model != NULL) {
+            PAR = model->params->data.F32;
+            dPAR = model->dparams->data.F32;
+            xPos = PAR[PM_PAR_XPOS];
+            yPos = PAR[PM_PAR_YPOS];
+            xErr = dPAR[PM_PAR_XPOS];
+            yErr = dPAR[PM_PAR_YPOS];
+
+            axes = pmPSF_ModelToAxes (PAR, 20.0);
+        } else {
+            // XXX: This code seg faults if source->peak is NULL.
+            xPos = source->peak->xf;
+            yPos = source->peak->yf;
+            xErr = 0.0; // XXX a better choice, please
+            yErr = 0.0; // XXX a better choice, please
+            axes.major = 0.0;
+            axes.minor = 0.0;
+            axes.theta = 0.0;
+        }
+
+        float peakMag = (source->peak->flux > 0) ? -2.5*log10(source->peak->flux) : NAN;
+        psS16 nImageOverlap = 1;
+        psS32 ID = 0; // XXX need to figure out how to generate this
+
+        row = psMetadataAlloc ();
+        // XXX we are not writing out the mode (flags) or the type (psf, ext, etc)
+        psMetadataAdd (row, PS_LIST_TAIL, "IPP_IDET",         PS_DATA_U32, "IPP detection identifier index",             ID);
+        psMetadataAdd (row, PS_LIST_TAIL, "X_PSF",            PS_DATA_F32, "PSF x coordinate",                           xPos);
+        psMetadataAdd (row, PS_LIST_TAIL, "Y_PSF",            PS_DATA_F32, "PSF y coordinate",                           yPos);
+        psMetadataAdd (row, PS_LIST_TAIL, "X_PSF_SIG",        PS_DATA_F32, "Sigma in PSF x coordinate",                  xErr);
+        psMetadataAdd (row, PS_LIST_TAIL, "Y_PSF_SIG",        PS_DATA_F32, "Sigma in PSF y coordinate",                  yErr);
+        psMetadataAdd (row, PS_LIST_TAIL, "PSF_INST_MAG",     PS_DATA_F32, "PSF fit instrumental magnitude",             PS_MIN (99.0, source->psfMag));
+        psMetadataAdd (row, PS_LIST_TAIL, "PSF_INST_MAG_SIG", PS_DATA_F32, "Sigma of PSF instrumental magnitude",        PS_MIN (99.0, source->errMag));
+        psMetadataAdd (row, PS_LIST_TAIL, "PEAK_FLUX_AS_MAG", PS_DATA_F32, "Peak flux expressed as magnitude",           PS_MIN (99.0, peakMag));
+        psMetadataAdd (row, PS_LIST_TAIL, "SKY",              PS_DATA_F32, "Sky level",                                  source->sky);
+        psMetadataAdd (row, PS_LIST_TAIL, "SKY_SIGMA",        PS_DATA_F32, "Sigma of sky level",                         source->skyErr);
+        // XXX this is called STAR_GALAXY_SEP in the ICD; PSF_PROB is better
+        // XXX need to set this value in psphotEvalPSF
+        // XXX can I use the 2d polynomial peak fit to constrain this for the low-sn sources?
+        psMetadataAdd (row, PS_LIST_TAIL, "PSF_PROBABILITY",  PS_DATA_F32,  "Probability of PSF-ness",                   NAN);
+        // XXX these should be major and minor, not 'x' and 'y'
+        psMetadataAdd (row, PS_LIST_TAIL, "PSF_WIDTH_X",      PS_DATA_F32, "PSF width in x coordinate",                  axes.major);
+        psMetadataAdd (row, PS_LIST_TAIL, "PSF_WIDTH_Y",      PS_DATA_F32, "PSF width in y coordinate",                  axes.minor);
+        psMetadataAdd (row, PS_LIST_TAIL, "PSF_THETA",        PS_DATA_F32, "PSF orientation angle",                      axes.theta);
+        psMetadataAdd (row, PS_LIST_TAIL, "PSF_QF",           PS_DATA_F32, "PSF coverage/quality factor",                source->pixWeight);
+        // XXX not sure how to get this : need to load Nimages with weight
+        psMetadataAdd (row, PS_LIST_TAIL, "N_FRAMES",         PS_DATA_U16, "Number of frames overlapping source center", nImageOverlap);
+        psMetadataAdd (row, PS_LIST_TAIL, "DUMMY",            PS_DATA_U16, "padding", 0);
+
+        // XXX these calibrated values are not supplied by psphot analysis
+        // psMetadataAdd (row, PS_LIST_TAIL, "RA_PSF",           PS_DATA_F64, "RA from PSF fit",                         RA);
+        // psMetadataAdd (row, PS_LIST_TAIL, "DEC_PSF",          PS_DATA_F64, "DEC from PSF fit",                        DEC);
+        // psMetadataAdd (row, PS_LIST_TAIL, "RA_PSF_SIG",       PS_DATA_F32, "Sigma of PSF fit RA",                     dRA);
+        // psMetadataAdd (row, PS_LIST_TAIL, "DEC_PSF_SIG",      PS_DATA_F32, "Sigma of PSF fit DEC",                    dDEC);
+        // psMetadataAdd (row, PS_LIST_TAIL, "CAL_PSF_MAG",      PS_DATA_F32, "Calibrated magnitude",                    calMag);
+        // psMetadataAdd (row, PS_LIST_TAIL, "CAL_PSF_MAG_sIG",  PS_DATA_F32, "Sigma of calibrated magnitude",           calMagErr);
+
+        psArrayAdd (table, 100, row);
+        psFree (row);
+    }
+
+    if (table->n == 0) {
+        psFitsWriteBlank (fits, tableHeader, extname);
+        psFree (table);
+        return true;
+    }
+
+    psTrace ("pmFPAfile", 5, "writing ext data %s\n", extname);
+    if (!psFitsWriteTable (fits, tableHeader, table, extname)) {
+        psError(PS_ERR_IO, false, "writing ext data %s\n", extname);
+        psFree(table);
+        return false;
+    }
+
+    psFree (table);
+    return true;
+}
+
+// read in a readout from the fits file
+psArray *pmSourcesRead_PS1_DEV_0 (psFits *fits, psMetadata *header)
+{
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    PS_ASSERT_PTR_NON_NULL(header, false);
+
+    bool status;
+    psF32 *PAR, *dPAR;
+    psEllipseAxes axes;
+
+    // define PSF model type
+    int modelType = pmModelClassGetType ("PS_MODEL_GAUSS");
+
+    char *PSF_NAME = psMetadataLookupStr (&status, header, "PSF_NAME");
+    if (PSF_NAME != NULL) {
+        modelType = pmModelClassGetType (PSF_NAME);
+    }
+
+    psArray *table = psFitsReadTable (fits);
+    // validate a single row of the table (must match SMP)
+    // XXX: The following seg-faults if table returns NULL, so I added the ASSERT
+    PS_ASSERT_PTR_NON_NULL(table, NULL);
+    psArray *sources = psArrayAlloc (table->n);
+
+    // convert the table to the pmSource entries
+    // XXX need to chooose PSF vs EXT, based on type?
+    for (int i = 0; i < table->n; i++) {
+        pmSource *source = pmSourceAlloc ();
+        pmModel *model = pmModelAlloc (modelType);
+        source->modelPSF  = model;
+        source->type = PM_SOURCE_TYPE_STAR;
+
+        // NOTE: A SEGV here because "model" is NULL is probably caused by not initialising the models.
+        PAR = model->params->data.F32;
+        dPAR = model->dparams->data.F32;
+
+        psMetadata *row = table->data[i];
+
+        PAR[PM_PAR_XPOS]  = psMetadataLookupF32 (&status, row, "X_PSF");
+        PAR[PM_PAR_YPOS]  = psMetadataLookupF32 (&status, row, "Y_PSF");
+        dPAR[PM_PAR_XPOS] = psMetadataLookupF32 (&status, row, "X_PSF_SIG");
+        dPAR[PM_PAR_YPOS] = psMetadataLookupF32 (&status, row, "Y_PSF_SIG");
+        axes.major        = psMetadataLookupF32 (&status, row, "PSF_WIDTH_X");
+        axes.minor        = psMetadataLookupF32 (&status, row, "PSF_WIDTH_Y");
+        axes.theta        = psMetadataLookupF32 (&status, row, "PSF_THETA");
+
+        PAR[PM_PAR_SKY]   = psMetadataLookupF32 (&status, row, "SKY");
+        dPAR[PM_PAR_SKY]  = psMetadataLookupF32 (&status, row, "SKY_SIGMA");
+        source->sky = PAR[PM_PAR_SKY];
+        source->skyErr = dPAR[PM_PAR_SKY];
+
+        // XXX use these to determine PAR[PM_PAR_I0]?
+        source->psfMag    = psMetadataLookupF32 (&status, row, "PSF_INST_MAG");
+        source->errMag    = psMetadataLookupF32 (&status, row, "PSF_INST_MAG_SIG");
+
+        pmPSF_AxesToModel (PAR, axes);
+
+        float lflux = psMetadataLookupF32 (&status, row, "PEAK_FLUX_AS_MAG");
+        float flux = (isfinite(lflux)) ? pow(10.0, -0.4*lflux) : NAN;
+        source->peak = pmPeakAlloc(PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], flux, PM_PEAK_LONE);
+        source->peak->flux = flux;
+
+        source->pixWeight = psMetadataLookupF32 (&status, row, "PSF_QF");
+
+        // XXX other values saved but not loaded?
+        // psMetadataLookupS64 (&status, row, "IPP_IDET");
+        // psMetadataLookupF32 (&status, row, "PSF_PROBABILITY");
+        // psMetadataLookupF32 (&status, row, "N_FRAMES");
+
+        sources->data[i] = source;
+    }
+    psFree (table);
+    return (sources);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO_PS1_DEV_1.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO_PS1_DEV_1.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO_PS1_DEV_1.c	(revision 22157)
@@ -0,0 +1,544 @@
+/** @file  pmSourceIO.c
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.10 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-04-08 18:35:38 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmGrowthCurve.h"
+#include "pmResiduals.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmModelClass.h"
+#include "pmSourceIO.h"
+
+// panstarrs-style FITS table output (header + table in 1st extension)
+// this format consists of a header derived from the image header
+// followed by a zero-size matrix, followed by the table data
+
+// this output format is valid for psphot analysis of an image, and does not include calibrated
+// values derived in the DVO database.
+// XXX how do I generate the source tables which I need to send to PSPS?
+
+bool pmSourcesWrite_PS1_DEV_1 (psFits *fits, psArray *sources, psMetadata *imageHeader,
+                               psMetadata *tableHeader, char *extname, char *xsrcname)
+{
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+    PS_ASSERT_PTR_NON_NULL(extname, false);
+
+    psArray *table;
+    psMetadata *row;
+    int i;
+    psF32 *PAR, *dPAR;
+    psEllipseAxes axes;
+    psF32 xPos, yPos;
+    psF32 xErr, yErr;
+
+    // let's write these out in S/N order
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    table = psArrayAllocEmpty (sources->n);
+
+    // we write out all sources, regardless of quality.  the source flags tell us the state
+    for (i = 0; i < sources->n; i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+        source->seq = i;
+
+        // no difference between PSF and non-PSF model
+        pmModel *model = pmSourceGetModel (NULL, source);
+
+        if (model != NULL) {
+            PAR = model->params->data.F32;
+            dPAR = model->dparams->data.F32;
+            xPos = PAR[PM_PAR_XPOS];
+            yPos = PAR[PM_PAR_YPOS];
+            xErr = dPAR[PM_PAR_XPOS];
+            yErr = dPAR[PM_PAR_YPOS];
+
+            axes = pmPSF_ModelToAxes (PAR, 20.0);
+        } else {
+            xPos = source->peak->xf;
+            yPos = source->peak->yf;
+            xErr = source->peak->dx;
+            yErr = source->peak->dy;
+            axes.major = 0.0;
+            axes.minor = 0.0;
+            axes.theta = 0.0;
+        }
+
+        float peakMag = (source->peak->flux > 0) ? -2.5*log10(source->peak->flux) : NAN;
+        psS16 nImageOverlap = 1;
+
+        row = psMetadataAlloc ();
+        // XXX we are not writing out the mode (flags) or the type (psf, ext, etc)
+        psMetadataAdd (row, PS_LIST_TAIL, "IPP_IDET",         PS_DATA_U32, "IPP detection identifier index",             source->seq);
+        psMetadataAdd (row, PS_LIST_TAIL, "X_PSF",            PS_DATA_F32, "PSF x coordinate",                           xPos);
+        psMetadataAdd (row, PS_LIST_TAIL, "Y_PSF",            PS_DATA_F32, "PSF y coordinate",                           yPos);
+        psMetadataAdd (row, PS_LIST_TAIL, "X_PSF_SIG",        PS_DATA_F32, "Sigma in PSF x coordinate",                  xErr);
+        psMetadataAdd (row, PS_LIST_TAIL, "Y_PSF_SIG",        PS_DATA_F32, "Sigma in PSF y coordinate",                  yErr);
+        psMetadataAdd (row, PS_LIST_TAIL, "PSF_INST_MAG",     PS_DATA_F32, "PSF fit instrumental magnitude",             PS_MIN (99.0, source->psfMag));
+        psMetadataAdd (row, PS_LIST_TAIL, "PSF_INST_MAG_SIG", PS_DATA_F32, "Sigma of PSF instrumental magnitude",        PS_MIN (99.0, source->errMag));
+        psMetadataAdd (row, PS_LIST_TAIL, "PEAK_FLUX_AS_MAG", PS_DATA_F32, "Peak flux expressed as magnitude",           PS_MIN (99.0, peakMag));
+        psMetadataAdd (row, PS_LIST_TAIL, "SKY",              PS_DATA_F32, "Sky level",                                  source->sky);
+        psMetadataAdd (row, PS_LIST_TAIL, "SKY_SIGMA",        PS_DATA_F32, "Sigma of sky level",                         source->skyErr);
+
+        psMetadataAdd (row, PS_LIST_TAIL, "PSF_CHISQ",        PS_DATA_F32,  "Chisq of PSF-fit to top 9 pixels",          source->psfChisq);
+        psMetadataAdd (row, PS_LIST_TAIL, "CR_NSIGMA",        PS_DATA_F32,  "Nsigma deviations from PSF to CF",          source->crNsigma);
+        psMetadataAdd (row, PS_LIST_TAIL, "EXT_NSIGMA",       PS_DATA_F32,  "Nsigma deviations from PSF to EXT",         source->extNsigma);
+
+        // XXX these should be major and minor, not 'x' and 'y'
+        psMetadataAdd (row, PS_LIST_TAIL, "PSF_WIDTH_X",      PS_DATA_F32, "PSF width in x coordinate",                  axes.major);
+        psMetadataAdd (row, PS_LIST_TAIL, "PSF_WIDTH_Y",      PS_DATA_F32, "PSF width in y coordinate",                  axes.minor);
+        psMetadataAdd (row, PS_LIST_TAIL, "PSF_THETA",        PS_DATA_F32, "PSF orientation angle",                      axes.theta);
+        psMetadataAdd (row, PS_LIST_TAIL, "PSF_QF",           PS_DATA_F32, "PSF coverage/quality factor",                source->pixWeight);
+
+        // XXX not sure how to get this : need to load Nimages with weight?
+        psMetadataAdd (row, PS_LIST_TAIL, "N_FRAMES",         PS_DATA_U16, "Number of frames overlapping source center", nImageOverlap);
+        psMetadataAdd (row, PS_LIST_TAIL, "FLAGS",            PS_DATA_U16, "psphot analysis flags",                      source->mode);
+
+        // XXX these calibrated values are not supplied by psphot analysis
+        // psMetadataAdd (row, PS_LIST_TAIL, "RA_PSF",           PS_DATA_F64, "RA from PSF fit",                         RA);
+        // psMetadataAdd (row, PS_LIST_TAIL, "DEC_PSF",          PS_DATA_F64, "DEC from PSF fit",                        DEC);
+        // psMetadataAdd (row, PS_LIST_TAIL, "RA_PSF_SIG",       PS_DATA_F32, "Sigma of PSF fit RA",                     dRA);
+        // psMetadataAdd (row, PS_LIST_TAIL, "DEC_PSF_SIG",      PS_DATA_F32, "Sigma of PSF fit DEC",                    dDEC);
+        // psMetadataAdd (row, PS_LIST_TAIL, "CAL_PSF_MAG",      PS_DATA_F32, "Calibrated magnitude",                    calMag);
+        // psMetadataAdd (row, PS_LIST_TAIL, "CAL_PSF_MAG_sIG",  PS_DATA_F32, "Sigma of calibrated magnitude",           calMagErr);
+
+        psArrayAdd (table, 100, row);
+        psFree (row);
+    }
+
+    if (table->n == 0) {
+        psFitsWriteBlank (fits, tableHeader, extname);
+        psFree (table);
+        return true;
+    }
+
+    psTrace ("pmFPAfile", 5, "writing ext data %s\n", extname);
+    if (!psFitsWriteTable (fits, tableHeader, table, extname)) {
+        psError(PS_ERR_IO, false, "writing ext data %s\n", extname);
+        psFree(table);
+        return false;
+    }
+    psFree (table);
+
+    return true;
+}
+
+// read in a readout from the fits file
+psArray *pmSourcesRead_PS1_DEV_1 (psFits *fits, psMetadata *header)
+{
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    PS_ASSERT_PTR_NON_NULL(header, false);
+
+    bool status;
+    psF32 *PAR, *dPAR;
+    psEllipseAxes axes;
+
+    // define PSF model type
+    int modelType = pmModelClassGetType ("PS_MODEL_GAUSS");
+
+    char *PSF_NAME = psMetadataLookupStr (&status, header, "PSF_NAME");
+    if (PSF_NAME != NULL) {
+        modelType = pmModelClassGetType (PSF_NAME);
+    }
+    assert (modelType > -1);
+
+    // XXX need to look up the XSRCNAME entries
+
+    // validate a single row of the table (must match SMP)
+
+    // XXX test return values
+
+    // XXX we have a memory problem, which is illustrated here: if I allocate the sources array
+    // (line 1) before freeing the table, the data is not really freed (but not a leak?)  if I
+    // allocate the sources array *after* freeing the table, the data is actually freed
+    // psArray *fooSources = psArrayAllocEmpty (10000);
+    // psFree (table);
+    // return (fooSources);
+
+
+    // We get the size of the table, and allocate the array of sources first because the table is large and
+    // ephemeral --- when the table gets blown away, whatever is allocated after the table is read.  In fact,
+    // it's better to read the table row by row.
+    long numSources = psFitsTableSize(fits); // Number of sources in table
+    psArray *sources = psArrayAlloc(numSources); // Array of sources, to return
+
+    // convert the table to the pmSource entries
+    // XXX need to chooose PSF vs EXT, based on type?
+    for (int i = 0; i < numSources; i++) {
+        psMetadata *row = psFitsReadTableRow(fits, i); // Table row
+
+        pmSource *source = pmSourceAlloc ();
+        pmModel *model = pmModelAlloc (modelType);
+        source->modelPSF  = model;
+        source->type = PM_SOURCE_TYPE_STAR;
+
+        // NOTE: A SEGV here because "model" is NULL is probably caused by not initialising the models.
+        PAR = model->params->data.F32;
+        dPAR = model->dparams->data.F32;
+
+        PAR[PM_PAR_XPOS]  = psMetadataLookupF32 (&status, row, "X_PSF");
+        PAR[PM_PAR_YPOS]  = psMetadataLookupF32 (&status, row, "Y_PSF");
+        dPAR[PM_PAR_XPOS] = psMetadataLookupF32 (&status, row, "X_PSF_SIG");
+        dPAR[PM_PAR_YPOS] = psMetadataLookupF32 (&status, row, "Y_PSF_SIG");
+        axes.major        = psMetadataLookupF32 (&status, row, "PSF_WIDTH_X");
+        axes.minor        = psMetadataLookupF32 (&status, row, "PSF_WIDTH_Y");
+        axes.theta        = psMetadataLookupF32 (&status, row, "PSF_THETA");
+
+        PAR[PM_PAR_SKY]   = psMetadataLookupF32 (&status, row, "SKY");
+        dPAR[PM_PAR_SKY]  = psMetadataLookupF32 (&status, row, "SKY_SIGMA");
+        source->sky = PAR[PM_PAR_SKY];
+        source->skyErr = dPAR[PM_PAR_SKY];
+
+        // XXX use these to determine PAR[PM_PAR_I0]?
+        source->psfMag    = psMetadataLookupF32 (&status, row, "PSF_INST_MAG");
+        source->errMag    = psMetadataLookupF32 (&status, row, "PSF_INST_MAG_SIG");
+
+        pmPSF_AxesToModel (PAR, axes);
+
+        float lflux = psMetadataLookupF32 (&status, row, "PEAK_FLUX_AS_MAG");
+        float flux = (isfinite(lflux)) ? pow(10.0, -0.4*lflux) : NAN;
+        source->peak = pmPeakAlloc(PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], flux, PM_PEAK_LONE);
+        source->peak->flux = flux;
+
+        source->pixWeight = psMetadataLookupF32 (&status, row, "PSF_QF");
+
+	// note that some older versions used PSF_PROBABILITY: this was not well defined.
+        source->psfChisq  = psMetadataLookupF32 (&status, row, "PSF_CHISQ");
+        source->crNsigma  = psMetadataLookupF32 (&status, row, "CR_NSIGMA");
+        source->extNsigma = psMetadataLookupF32 (&status, row, "EXT_NSIGMA");
+
+        source->mode      = psMetadataLookupU16 (&status, row, "FLAGS");
+        assert (status);
+
+        // XXX other values saved but not loaded?
+        // psMetadataLookupS64 (&status, row, "IPP_IDET");
+        // psMetadataLookupF32 (&status, row, "N_FRAMES");
+
+        sources->data[i] = source;
+        psFree(row);
+    }
+
+    return sources;
+}
+
+bool pmSourcesWrite_PS1_DEV_1_XSRC (psFits *fits, psArray *sources, char *extname, psMetadata *recipe)
+{
+
+    bool status;
+    psArray *table;
+    psMetadata *row;
+    psF32 *PAR, *dPAR;
+    psF32 xPos, yPos;
+    psF32 xErr, yErr;
+
+    // create a header to hold the output data
+    psMetadata *outhead = psMetadataAlloc ();
+
+    // write the links to the image header
+    psMetadataAddStr (outhead, PS_LIST_TAIL, "EXTNAME", PS_META_REPLACE, "xsrc table extension", extname);
+
+    // let's write these out in S/N order
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    table = psArrayAllocEmpty (sources->n);
+
+    // which extended source analyses should we perform?
+    bool doPetrosian    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_PETROSIAN");
+    bool doIsophotal    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ISOPHOTAL");
+    bool doAnnuli       = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ANNULI");
+    bool doKron         = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_KRON");
+
+    psVector *radialBinsLower = psMetadataLookupPtr (&status, recipe, "RADIAL.ANNULAR.BINS.LOWER");
+    psVector *radialBinsUpper = psMetadataLookupPtr (&status, recipe, "RADIAL.ANNULAR.BINS.UPPER");
+    assert (radialBinsLower->n == radialBinsUpper->n);
+
+    // we write out all sources, regardless of quality.  the source flags tell us the state
+    for (int i = 0; i < sources->n; i++) {
+	// skip source if it is not a ext sourc
+	// XXX we have two places that extended source parameters are measured:
+	// psphotExtendedSources, which measures the aperture-like parameters and (potentially) the psf-convolved extended source models,
+	// psphotFitEXT, which does the simple extended source model fit (not psf-convolved)
+	// should we require both?
+
+	pmSource *source = sources->data[i];
+
+	// skip sources without measurements
+	if (source->extpars == NULL) continue;
+
+	// we require a PSF model fit (ignore the real crud)
+	pmModel *model = source->modelPSF;
+	if (model == NULL) continue;
+
+	// XXX I need to split the extended models from the extended aperture measurements
+	PAR = model->params->data.F32;
+	dPAR = model->dparams->data.F32;
+	xPos = PAR[PM_PAR_XPOS];
+	yPos = PAR[PM_PAR_YPOS];
+	xErr = dPAR[PM_PAR_XPOS];
+	yErr = dPAR[PM_PAR_YPOS];
+
+        row = psMetadataAlloc ();
+
+        // XXX we are not writing out the mode (flags) or the type (psf, ext, etc)
+        psMetadataAdd (row, PS_LIST_TAIL, "IPP_IDET",         PS_DATA_U32, "IPP detection identifier index",             source->seq);
+        psMetadataAdd (row, PS_LIST_TAIL, "X_EXT",            PS_DATA_F32, "EXT model x coordinate",                     xPos);
+        psMetadataAdd (row, PS_LIST_TAIL, "Y_EXT",            PS_DATA_F32, "EXT model y coordinate",                     yPos);
+        psMetadataAdd (row, PS_LIST_TAIL, "X_EXT_SIG",        PS_DATA_F32, "Sigma in EXT x coordinate",                  xErr);
+        psMetadataAdd (row, PS_LIST_TAIL, "Y_EXT_SIG",        PS_DATA_F32, "Sigma in EXT y coordinate",                  yErr);
+
+	// Petrosian measurements
+	// XXX insert header data: petrosian ref radius, flux ratio
+	if (doPetrosian) {
+	    pmSourcePetrosianValues *petrosian = source->extpars->petrosian;
+	    if (petrosian) {
+		psMetadataAdd (row, PS_LIST_TAIL, "PETRO_MAG",        PS_DATA_F32, "Petrosian Magnitude",       petrosian->mag);
+		psMetadataAdd (row, PS_LIST_TAIL, "PETRO_MAG_ERR",    PS_DATA_F32, "Petrosian Magnitude Error", petrosian->magErr);
+		psMetadataAdd (row, PS_LIST_TAIL, "PETRO_RADIUS",     PS_DATA_F32, "Petrosian Radius",          petrosian->rad);
+		psMetadataAdd (row, PS_LIST_TAIL, "PETRO_RADIUS_ERR", PS_DATA_F32, "Petrosian Radius Error",    petrosian->radErr);
+	    } else {
+		psMetadataAdd (row, PS_LIST_TAIL, "PETRO_MAG",        PS_DATA_F32, "Petrosian Magnitude",       NAN);
+		psMetadataAdd (row, PS_LIST_TAIL, "PETRO_MAG_ERR",    PS_DATA_F32, "Petrosian Magnitude Error", NAN);
+		psMetadataAdd (row, PS_LIST_TAIL, "PETRO_RADIUS",     PS_DATA_F32, "Petrosian Radius",          NAN);
+		psMetadataAdd (row, PS_LIST_TAIL, "PETRO_RADIUS_ERR", PS_DATA_F32, "Petrosian Radius Error",    NAN);
+	    }
+	} 
+
+	// Kron measurements
+	if (doKron) {
+	    pmSourceKronValues *kron = source->extpars->kron;
+	    if (kron) {
+		psMetadataAdd (row, PS_LIST_TAIL, "KRON_MAG",        PS_DATA_F32, "Kron Magnitude",       kron->mag);
+		psMetadataAdd (row, PS_LIST_TAIL, "KRON_MAG_ERR",    PS_DATA_F32, "Kron Magnitude Error", kron->magErr);
+		psMetadataAdd (row, PS_LIST_TAIL, "KRON_RADIUS",     PS_DATA_F32, "Kron Radius",          kron->rad);
+		psMetadataAdd (row, PS_LIST_TAIL, "KRON_RADIUS_ERR", PS_DATA_F32, "Kron Radius Error",    kron->radErr);
+	    } else {
+		psMetadataAdd (row, PS_LIST_TAIL, "KRON_MAG",        PS_DATA_F32, "Kron Magnitude",       NAN);
+		psMetadataAdd (row, PS_LIST_TAIL, "KRON_MAG_ERR",    PS_DATA_F32, "Kron Magnitude Error", NAN);
+		psMetadataAdd (row, PS_LIST_TAIL, "KRON_RADIUS",     PS_DATA_F32, "Kron Radius",          NAN);
+		psMetadataAdd (row, PS_LIST_TAIL, "KRON_RADIUS_ERR", PS_DATA_F32, "Kron Radius Error",    NAN);
+	    }
+	}
+
+	// Isophot measurements
+	// XXX insert header data: isophotal level
+	if (doIsophotal) {
+	    pmSourceIsophotalValues *isophot = source->extpars->isophot;
+	    if (isophot) {
+		psMetadataAdd (row, PS_LIST_TAIL, "ISOPHOT_MAG",        PS_DATA_F32, "Isophot Magnitude",       isophot->mag);
+		psMetadataAdd (row, PS_LIST_TAIL, "ISOPHOT_MAG_ERR",    PS_DATA_F32, "Isophot Magnitude Error", isophot->magErr);
+		psMetadataAdd (row, PS_LIST_TAIL, "ISOPHOT_RADIUS",     PS_DATA_F32, "Isophot Radius",          isophot->rad);
+		psMetadataAdd (row, PS_LIST_TAIL, "ISOPHOT_RADIUS_ERR", PS_DATA_F32, "Isophot Radius Error",    isophot->radErr);
+	    } else {
+		psMetadataAdd (row, PS_LIST_TAIL, "ISOPHOT_MAG",        PS_DATA_F32, "Isophot Magnitude",       NAN);
+		psMetadataAdd (row, PS_LIST_TAIL, "ISOPHOT_MAG_ERR",    PS_DATA_F32, "Isophot Magnitude Error", NAN);
+		psMetadataAdd (row, PS_LIST_TAIL, "ISOPHOT_RADIUS",     PS_DATA_F32, "Isophot Radius",          NAN);
+		psMetadataAdd (row, PS_LIST_TAIL, "ISOPHOT_RADIUS_ERR", PS_DATA_F32, "Isophot Radius Error",    NAN);
+	    }
+	}
+
+	// Flux Annuli
+	if (doAnnuli) {
+	    pmSourceAnnuli *annuli = source->extpars->annuli;
+	    if (annuli) {
+		psVector *fluxVal = annuli->flux;
+		psVector *fluxErr = annuli->fluxErr;
+		psVector *fluxVar = annuli->fluxVar;
+
+		for (int j = 0; j < fluxVal->n; j++) {
+		    char name[32];
+		    sprintf (name, "FLUX_VAL_R_%02d", j);
+		    psMetadataAdd (row, PS_LIST_TAIL, name, PS_DATA_F32, "flux value in annulus", fluxVal->data.F32[j]);
+		    sprintf (name, "FLUX_ERR_R_%02d", j);
+		    psMetadataAdd (row, PS_LIST_TAIL, name, PS_DATA_F32, "flux error in annulus", fluxErr->data.F32[j]);
+		    sprintf (name, "FLUX_VAR_R_%02d", j);
+		    psMetadataAdd (row, PS_LIST_TAIL, name, PS_DATA_F32, "flux stdev in annulus", fluxVar->data.F32[j]);
+		} 
+	    } else {
+		for (int j = 0; j < radialBinsLower->n; j++) {
+		    char name[32];
+		    sprintf (name, "FLUX_VAL_R_%02d", j);
+		    psMetadataAdd (row, PS_LIST_TAIL, name, PS_DATA_F32, "flux value in annulus", NAN);
+		    sprintf (name, "FLUX_ERR_R_%02d", j);
+		    psMetadataAdd (row, PS_LIST_TAIL, name, PS_DATA_F32, "flux error in annulus", NAN);
+		    sprintf (name, "FLUX_VAR_R_%02d", j);
+		    psMetadataAdd (row, PS_LIST_TAIL, name, PS_DATA_F32, "flux stdev in annulus", NAN);
+		} 
+	    }
+	}
+
+	psArrayAdd (table, 100, row);
+	psFree (row);
+    }
+
+    if (table->n == 0) {
+	psFitsWriteBlank (fits, outhead, extname);
+	psFree (outhead);
+	psFree (table);
+	return true;
+    }
+
+    psTrace ("pmFPAfile", 5, "writing ext data %s\n", extname);
+    if (!psFitsWriteTable (fits, outhead, table, extname)) {
+	psError(PS_ERR_IO, false, "writing ext data %s\n", extname);
+	psFree (outhead);
+	psFree(table);
+	return false;
+    }
+    psFree (outhead);
+    psFree (table);
+
+    return true;
+}
+
+bool pmSourcesWrite_PS1_DEV_1_XFIT (psFits *fits, psArray *sources, char *extname)
+{
+
+    psArray *table;
+    psMetadata *row;
+    psF32 *PAR, *dPAR;
+    psEllipseAxes axes;
+    psF32 xPos, yPos;
+    psF32 xErr, yErr;
+    char name[64];
+
+    // create a header to hold the output data
+    psMetadata *outhead = psMetadataAlloc ();
+
+    // write the links to the image header
+    psMetadataAddStr (outhead, PS_LIST_TAIL, "EXTNAME", PS_META_REPLACE, "xsrc table extension", extname);
+
+    // let's write these out in S/N order
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // we are writing one row per model; we need to write out same number of columns for each row: find the max Nparams
+    int nParamMax = 0;
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = sources->data[i];
+	if (source->modelFits == NULL) continue;
+	for (int j = 0; j < source->modelFits->n; j++) {
+	    pmModel *model = source->modelFits->data[j];
+	    assert (model);
+	    nParamMax = PS_MAX (nParamMax, model->params->n);
+	}
+    }
+
+    table = psArrayAllocEmpty (sources->n);
+
+    // we write out all sources, regardless of quality.  the source flags tell us the state
+    for (int i = 0; i < sources->n; i++) {
+
+	pmSource *source = sources->data[i];
+
+	// XXX if no model fits are saved, write out modelEXT?
+	if (source->modelFits == NULL) continue;
+
+	// We have multiple sources : need to flag the one used to subtract the light (the 'best' model)
+	for (int j = 0; j < source->modelFits->n; j++) {
+
+	    // choose the convolved EXT model, if available, otherwise the simple one
+	    pmModel *model = source->modelFits->data[j];
+	    assert (model);
+
+	    PAR = model->params->data.F32;
+	    dPAR = model->dparams->data.F32;
+	    xPos = PAR[PM_PAR_XPOS];
+	    yPos = PAR[PM_PAR_YPOS];
+	    xErr = dPAR[PM_PAR_XPOS];
+	    yErr = dPAR[PM_PAR_YPOS];
+
+	    axes = pmPSF_ModelToAxes (PAR, 20.0);
+
+	    row = psMetadataAlloc ();
+
+	    // XXX we are not writing out the mode (flags) or the type (psf, ext, etc)
+	    psMetadataAddU32 (row, PS_LIST_TAIL, "IPP_IDET",         0, "IPP detection identifier index",             source->seq);
+	    psMetadataAddF32 (row, PS_LIST_TAIL, "X_EXT",            0, "EXT model x coordinate",                     xPos);
+	    psMetadataAddF32 (row, PS_LIST_TAIL, "Y_EXT",            0, "EXT model y coordinate",                     yPos);
+	    psMetadataAddF32 (row, PS_LIST_TAIL, "X_EXT_SIG",        0, "Sigma in EXT x coordinate",                  xErr);
+	    psMetadataAddF32 (row, PS_LIST_TAIL, "Y_EXT_SIG",        0, "Sigma in EXT y coordinate",                  yErr);
+	    psMetadataAddF32 (row, PS_LIST_TAIL, "EXT_INST_MAG",     0, "EXT fit instrumental magnitude",             model->mag);
+	    psMetadataAddF32 (row, PS_LIST_TAIL, "EXT_INST_MAG_SIG", 0, "Sigma of PSF instrumental magnitude",        model->magErr);
+
+	    psMetadataAddF32 (row, PS_LIST_TAIL, "NPARAMS",          0, "number of model parameters",                 model->params->n);
+	    psMetadataAddStr (row, PS_LIST_TAIL, "MODEL_TYPE",       0, "name of model",                              pmModelClassGetName (model->type));
+
+	    // XXX these should be major and minor, not 'x' and 'y'
+	    psMetadataAddF32 (row, PS_LIST_TAIL, "EXT_WIDTH_MAJ",    0, "EXT width in x coordinate",                  axes.major);
+	    psMetadataAddF32 (row, PS_LIST_TAIL, "EXT_WIDTH_MIN",    0, "EXT width in y coordinate",                  axes.minor);
+	    psMetadataAddF32 (row, PS_LIST_TAIL, "EXT_THETA",        0, "EXT orientation angle",                      axes.theta);
+
+	    // write out the other generic parameters
+	    for (int k = 0; k < nParamMax; k++) {
+		if (k == PM_PAR_I0) continue;
+		if (k == PM_PAR_SKY) continue;
+		if (k == PM_PAR_XPOS) continue;
+		if (k == PM_PAR_YPOS) continue;
+		if (k == PM_PAR_SXX) continue;
+		if (k == PM_PAR_SXY) continue;
+		if (k == PM_PAR_SYY) continue;
+
+		snprintf (name, 64, "EXT_PAR_%02d", k);
+
+		if (k < model->params->n) {
+		    psMetadataAdd (row, PS_LIST_TAIL, name, PS_DATA_F32, "", model->params->data.F32[k]);
+		} else {
+		    psMetadataAddF32 (row, PS_LIST_TAIL, name, PS_DATA_F32, "", NAN);
+		}
+	    }
+
+	    // XXX other parameters which may be set.
+	    // XXX flag / value to define the model
+	    // XXX write out the model type, fit status flags
+
+	    psArrayAdd (table, 100, row);
+	    psFree (row);
+	}
+    }
+
+    if (table->n == 0) {
+	psFitsWriteBlank (fits, outhead, extname);
+	psFree (outhead);
+	psFree (table);
+	return true;
+    }
+
+    psTrace ("pmFPAfile", 5, "writing ext data %s\n", extname);
+    if (!psFitsWriteTable (fits, outhead, table, extname)) {
+	psError(PS_ERR_IO, false, "writing ext data %s\n", extname);
+	psFree (outhead);
+	psFree(table);
+	return false;
+    }
+    psFree (outhead);
+    psFree (table);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO_RAW.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO_RAW.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO_RAW.c	(revision 22157)
@@ -0,0 +1,293 @@
+/** @file  pmSourceIO.c
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.18 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-11-10 01:09:20 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmGrowthCurve.h"
+#include "pmResiduals.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmModelClass.h"
+#include "pmSourcePhotometry.h"
+#include "pmSourceIO.h"
+
+/***** Text Output Methods *****/
+bool pmSourcesWriteRAW (psArray *sources, char *filename)
+{
+
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+    PS_ASSERT_PTR_NON_NULL(filename, false);
+
+    char *name = (char *) psAlloc (strlen(filename) + 10);
+
+    sprintf (name, "%s.psf.dat", filename);
+    pmSourcesWritePSFs (sources, name);
+
+    sprintf (name, "%s.ext.dat", filename);
+    pmSourcesWriteEXTs (sources, name, true);
+
+    sprintf (name, "%s.nul.dat", filename);
+    pmSourcesWriteNULLs (sources, name);
+
+    sprintf (name, "%s.mnt.dat", filename);
+    pmMomentsWriteText (sources, name);
+
+    psFree (name);
+    return true;
+}
+
+// write the PSF sources to an output file
+bool pmSourcesWritePSFs (psArray *sources, char *filename)
+{
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+    PS_ASSERT_PTR_NON_NULL(filename, false);
+
+    double dPos;
+    int i, j;
+    FILE *f;
+    psF32 *PAR, *dPAR;
+    pmModel  *model;
+
+    f = fopen (filename, "w");
+    if (f == NULL) {
+        psLogMsg (__func__, 3, "can't open output file for PSF sources: %s\n", filename);
+        return false;
+    }
+
+    // write sources with models first
+    for (i = 0; i < sources->n; i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+        if (source->type != PM_SOURCE_TYPE_STAR)
+            continue;
+        model = source->modelPSF;
+        if (model == NULL)
+            continue;
+
+        PAR  = model->params->data.F32;
+        dPAR = model->dparams->data.F32;
+
+        // dPos is positional error, dMag is mag error
+        dPos = hypot (dPAR[PM_PAR_XPOS], dPAR[PM_PAR_YPOS]);
+
+        fprintf (f, "%7.1f %7.1f  %7.1f %8.4f  %7.4f %7.4f  ",
+                 PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], source->sky,
+                 source->psfMag, source->errMag, dPos);
+
+        for (j = 4; j < model->params->n; j++) {
+            fprintf (f, "%9.6f ", PAR[j]);
+        }
+        fprintf (f, " : ");
+        for (j = 4; j < model->params->n; j++) {
+            fprintf (f, "%9.6f ", dPAR[j]);
+        }
+
+        float logChi = ((model[0].chisq == 0.0) || (model[0].nDOF == 0)) ? NAN : log10(model[0].chisq/model[0].nDOF);
+        float logChiNorm = ((model[0].chisqNorm == 0.0) || (model[0].nDOF == 0)) ? NAN : log10(model[0].chisqNorm/model[0].nDOF);
+
+        fprintf (f, ": %8.4f %2d %#5x %7.3f %7.3f  %7.1f %7.2f %4.2f %4d %2d\n",
+                 source[0].apMag, source[0].type, source[0].mode,
+                 logChi, logChiNorm,
+                 source[0].peak->SN,
+                 model[0].radiusFit,
+                 source[0].pixWeight,
+                 model[0].nDOF,
+                 model[0].nIter);
+    }
+    fclose (f);
+    return true;
+}
+
+// dump the sources to an output file
+bool pmSourcesWriteEXTs (psArray *sources, char *filename, bool requireEXT)
+{
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+    PS_ASSERT_PTR_NON_NULL(filename, false);
+
+    double dPos;
+    int i, j;
+    FILE *f;
+    psF32 *PAR, *dPAR;
+    pmModel  *model;
+
+    f = fopen (filename, "w");
+    if (f == NULL) {
+        psLogMsg ("pmModelWriteEXTs", 3, "can't open output file for EXT sources: %s\n", filename);
+        return false;
+    }
+
+    // write sources with models first
+    for (i = 0; i < sources->n; i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+
+        if (requireEXT && (source->type != PM_SOURCE_TYPE_EXTENDED))
+            continue;
+
+        model = source->modelEXT;
+        if (model == NULL)
+            continue;
+
+        PAR  = model->params->data.F32;
+        dPAR = model->dparams->data.F32;
+
+        // dPos is shape error
+        // XXX these are hardwired for SGAUSS
+        dPos = hypot ((dPAR[PM_PAR_SXX] / PAR[PM_PAR_SXX]), (dPAR[PM_PAR_SYY] / PAR[PM_PAR_SYY]));
+
+        fprintf (f, "%7.1f %7.1f  %7.1f %8.4f  %7.4f %7.4f  ",
+                 PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], source->sky,
+                 source->extMag, source->errMag, dPos);
+
+        for (j = 4; j < model->params->n; j++) {
+            fprintf (f, "%9.6f ", PAR[j]);
+        }
+        fprintf (f, " : ");
+        for (j = 4; j < model->params->n; j++) {
+            fprintf (f, "%9.6f ", dPAR[j]);
+        }
+        fprintf (f, ": %7.4f  %2d %#5x %7.3f %7.3f  %7.1f %7.2f %4.2f %4d %2d\n",
+                 source->apMag,
+                 source[0].type, source[0].mode,
+                 log10(model[0].chisq/model[0].nDOF),
+                 log10(model[0].chisqNorm/model[0].nDOF),
+                 source[0].peak->SN,
+                 model[0].radiusFit,
+                 source[0].pixWeight,
+                 model[0].nDOF,
+                 model[0].nIter);
+    }
+    fclose (f);
+    return true;
+}
+
+// dump the sources to an output file
+bool pmSourcesWriteNULLs (psArray *sources, char *filename)
+{
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+    PS_ASSERT_PTR_NON_NULL(filename, false);
+
+    int i;
+    FILE *f;
+    pmMoments *moment = NULL;
+    pmSource *source = NULL;
+
+    f = fopen (filename, "w");
+    if (f == NULL) {
+        psLogMsg ("DumpObjects", 3, "can't open output file for NULL sources: %s\n", filename);
+        return false;
+    }
+
+    pmMoments *empty = pmMomentsAlloc ();
+
+    // write sources with models first
+    for (i = 0; i < sources->n; i++) {
+        source = sources->data[i];
+
+        // skip these sources (in PSF or EXT)
+        if (source->type == PM_SOURCE_TYPE_STAR)
+            continue;
+        if (source->type == PM_SOURCE_TYPE_EXTENDED)
+            continue;
+
+        if (source->moments == NULL) {
+            moment = empty;
+        } else {
+            moment = source->moments;
+        }
+
+        fprintf (f, "%5d %5d  %7.1f  %7.1f %7.1f  %6.3f %6.3f  %8.1f %7.1f %7.1f %7.1f  %4d %2d\n",
+                 source->peak->x, source->peak->y, source->peak->value,
+                 moment->x, moment->y,
+                 moment->Sx, moment->Sy,
+                 moment->Sum, moment->Peak,
+                 moment->Sky, moment->SN,
+                 moment->nPixels, source->type);
+    }
+    fclose (f);
+    psFree (empty);
+    return true;
+}
+
+// write the moments to an output file
+bool pmMomentsWriteText (psArray *sources, char *filename)
+{
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+    PS_ASSERT_PTR_NON_NULL(filename, false);
+
+    int i;
+    FILE *f;
+    pmSource *source = NULL;
+
+    f = fopen (filename, "w");
+    if (f == NULL) {
+        psLogMsg ("pmMomentsWriteText", 3, "can't open output file for moments: %s\n", filename);
+        return false;
+    }
+
+    for (i = 0; i < sources->n; i++) {
+        source = sources->data[i];
+        if (source->moments == NULL)
+            continue;
+        fprintf (f, "%5d %5d  %7.1f  %7.1f %7.1f  %6.3f %6.3f  %10.1f %7.1f %7.1f %7.1f  %4d %2d %#5x\n",
+                 source->peak->x, source->peak->y, source->peak->value,
+                 source->moments->x, source->moments->y,
+                 source->moments->Sx, source->moments->Sy,
+                 source->moments->Sum, source->moments->Peak,
+                 source->moments->Sky, source->moments->SN,
+                 source->moments->nPixels, source->type, source->mode);
+    }
+    fclose (f);
+    return true;
+}
+
+// write the peaks to an output file
+bool pmPeaksWriteText (psArray *peaks, char *filename)
+{
+
+    int i;
+    FILE *f;
+
+    f = fopen (filename, "w");
+    if (f == NULL) {
+        psLogMsg ("pmPeaksWriteText", 3, "can't open output file for peaks%s\n", filename);
+        return false;
+    }
+
+    for (i = 0; i < peaks->n; i++) {
+        pmPeak *peak = peaks->data[i];
+        if (peak == NULL)
+            continue;
+        fprintf (f, "%5d %5d  %7.1f %7.2f %7.2f %7.2f\n",
+                 peak->x, peak->y, peak->value, peak->SN, peak->xf, peak->yf);
+    }
+    fclose (f);
+    return true;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO_SMPDATA.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO_SMPDATA.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO_SMPDATA.c	(revision 22157)
@@ -0,0 +1,202 @@
+/** @file  pmSourceIO.c
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.12 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-11-10 01:09:20 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmResiduals.h"
+#include "pmGrowthCurve.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmModelClass.h"
+#include "pmSourceIO.h"
+
+// elixir-style FITS table output (header + table in 1st extension)
+// this format consists of a header derived from the image header
+// followed by a zero-size matrix, followed by the table data
+// XXX: input parameter imageHeader is never used
+bool pmSourcesWrite_SMPDATA (psFits *fits, psArray *sources, psMetadata *imageHeader,
+                             psMetadata *tableHeader, char *extname)
+{
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+    PS_ASSERT_PTR_NON_NULL(extname, false);
+
+    psArray *table;
+    psMetadata *row;
+    int i;
+    psF32 *PAR, *dPAR;
+    bool status;
+    psEllipseAxes axes;
+    psF32 xPos, yPos;
+
+    // find config information for output header
+    float ZERO_POINT = psMetadataLookupF32 (&status, imageHeader, "ZERO_PT");
+    if (!status)
+        ZERO_POINT = 25.0;
+
+    float lsky = 0;
+    int type = 0;
+
+    table = psArrayAllocEmpty (sources->n);
+
+    for (i = 0; i < sources->n; i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+
+	// no difference between PSF and non-PSF model
+        pmModel *model = pmSourceGetModel (NULL, source);
+        if (model != NULL) {
+	    PAR = model->params->data.F32;
+	    dPAR = model->dparams->data.F32;
+	    xPos = PAR[PM_PAR_XPOS];
+	    yPos = PAR[PM_PAR_YPOS];
+
+	    type = pmSourceGetDophotType (source);
+	    lsky = (source->sky < 1.0) ? 0.0 : log10(source->sky);
+
+	    axes = pmPSF_ModelToAxes (PAR, 20.0);
+
+	} else {
+	    xPos = source->peak->xf;
+	    yPos = source->peak->yf;
+	    axes.major = 0.0;
+	    axes.minor = 0.0;
+	    axes.theta = 0.0;
+	}
+
+        row = psMetadataAlloc ();
+        psMetadataAdd (row, PS_LIST_TAIL, "X_PIX",   PS_DATA_F32, "", xPos);
+        psMetadataAdd (row, PS_LIST_TAIL, "Y_PIX",   PS_DATA_F32, "", yPos);
+        psMetadataAdd (row, PS_LIST_TAIL, "MAG_RAW", PS_DATA_F32, "", PS_MIN (99.0, source->psfMag + ZERO_POINT));
+        psMetadataAdd (row, PS_LIST_TAIL, "MAG_ERR", PS_DATA_F32, "", PS_MIN (999, 1000*source->errMag));
+        psMetadataAdd (row, PS_LIST_TAIL, "MAG_GAL", PS_DATA_F32, "", PS_MIN (99.0, source->extMag + ZERO_POINT));
+        psMetadataAdd (row, PS_LIST_TAIL, "MAG_AP",  PS_DATA_F32, "", PS_MIN (99.0, source->apMag + ZERO_POINT));
+        psMetadataAdd (row, PS_LIST_TAIL, "LOG_SKY", PS_DATA_F32, "", lsky);
+        psMetadataAdd (row, PS_LIST_TAIL, "FWHM_X",  PS_DATA_F32, "", axes.major);
+        psMetadataAdd (row, PS_LIST_TAIL, "FWHM_Y",  PS_DATA_F32, "", axes.minor);
+        psMetadataAdd (row, PS_LIST_TAIL, "THETA",   PS_DATA_F32, "", axes.theta);
+        psMetadataAdd (row, PS_LIST_TAIL, "DOPHOT",  PS_DATA_U8,  "", type);
+        psMetadataAdd (row, PS_LIST_TAIL, "WEIGHT",  PS_DATA_U8,  "", PS_MIN (255, PS_MAX(0, 255*source->pixWeight)));
+        psMetadataAdd (row, PS_LIST_TAIL, "DUMMY",   PS_DATA_U16, "", 0);
+
+        psArrayAdd (table, 100, row);
+        psFree (row);
+    }
+
+    if (table->n == 0) {
+        psFitsWriteBlank (fits, tableHeader, extname);
+        psFree (table);
+        return true;
+    }
+
+    psTrace ("pmFPAfile", 5, "writing ext data %s\n", extname);
+    if (!psFitsWriteTable (fits, tableHeader, table, extname)) {
+        psError(PS_ERR_IO, false, "writing ext data %s\n", extname);
+        psFree(table);
+        return false;
+    }
+
+    psFree (table);
+    return true;
+}
+
+// read in a readout from the fits file
+psArray *pmSourcesRead_SMPDATA (psFits *fits, psMetadata *header)
+{
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    PS_ASSERT_PTR_NON_NULL(header, false);
+
+    bool status;
+    psF32 *PAR, *dPAR;
+    psEllipseAxes axes;
+    float lsky;
+
+    // define PSF model type
+    int modelType = pmModelClassGetType ("PS_MODEL_GAUSS");
+
+    char *PSF_NAME = psMetadataLookupStr (&status, header, "PSF_NAME");
+    if (PSF_NAME != NULL) {
+        modelType = pmModelClassGetType (PSF_NAME);
+    }
+
+    // find config information for output header
+    float ZERO_POINT = psMetadataLookupF32 (&status, header, "ZERO_PT");
+    if (!status)
+        ZERO_POINT = 25.0;
+
+    psArray *table = psFitsReadTable (fits);
+    // validate a single row of the table (must match SMP)
+
+    psArray *sources = psArrayAlloc (table->n);
+
+    // convert the table to the pmSource entries
+    // XXX need to chooose PSF vs EXT, based on type?
+    for (int i = 0; i < table->n; i++) {
+        pmSource *source = pmSourceAlloc ();
+        pmModel *model = pmModelAlloc (modelType);
+        source->modelPSF  = model;
+        source->type = PM_SOURCE_TYPE_STAR;
+
+        PAR = model->params->data.F32;
+        dPAR = model->dparams->data.F32;
+
+        psMetadata *row = table->data[i];
+
+        lsky             = psMetadataLookupF32 (&status, row, "LOG_SKY");
+        PAR[PM_PAR_SKY]  = pow(10.0, lsky);
+        source->sky    = PAR[PM_PAR_SKY];
+
+        PAR[PM_PAR_XPOS] = psMetadataLookupF32 (&status, row, "X_PIX");
+        PAR[PM_PAR_YPOS] = psMetadataLookupF32 (&status, row, "Y_PIX");
+        axes.major       = psMetadataLookupF32 (&status, row, "FWHM_X");
+        axes.minor       = psMetadataLookupF32 (&status, row, "FWHM_Y");
+        axes.theta       = psMetadataLookupF32 (&status, row, "THETA");
+
+	pmPSF_AxesToModel (PAR, axes);
+
+
+        source->psfMag = psMetadataLookupF32 (&status, row, "MAG_RAW") - ZERO_POINT;
+        source->extMag = psMetadataLookupF32 (&status, row, "MAG_GAL") - ZERO_POINT;
+        source->errMag = psMetadataLookupF32 (&status, row, "MAG_ERR") * 0.001;
+        source->apMag  = psMetadataLookupF32 (&status, row, "MAG_AP")  - ZERO_POINT;
+
+        source->pixWeight = psMetadataLookupU8 (&status, row, "WEIGHT")/255.0;
+        int dophot = psMetadataLookupU8 (&status, row, "DOPHOT");
+	pmSourceSetDophotType (source, dophot);
+
+	double Area = 2.0 * M_PI * axes.major * axes.minor;
+	double peakFlux = source->psfMag / Area;
+
+	source->peak = pmPeakAlloc(PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], peakFlux, PM_PEAK_LONE);
+        sources->data[i] = source;
+    }
+    psFree (table);
+    return (sources);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO_SX.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO_SX.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceIO_SX.c	(revision 22157)
@@ -0,0 +1,95 @@
+/** @file  pmSourceIO.c
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.14 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-11-10 01:09:20 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmGrowthCurve.h"
+#include "pmResiduals.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmModelClass.h"
+#include "pmSourceIO.h"
+
+// elixir-mode / sextractor-style output list with fixed line width
+bool pmSourcesWriteSX (psArray *sources, char *filename)
+{
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+    PS_ASSERT_PTR_NON_NULL(filename, false);
+
+    psF32 *PAR, *dPAR;
+    psEllipseAxes axes;
+
+    psLine *line = psLineAlloc (110);  // 110 is sextractor line length
+
+    FILE *f = fopen (filename, "w");
+    if (f == NULL) {
+        psLogMsg (__func__, 3, "can't open output file for output %s\n", filename);
+        return false;
+    }
+
+    // write sources with models
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+
+	// no difference between PSF and non-PSF model
+        pmModel *model = pmSourceGetModel (NULL, source);
+        if (model == NULL)
+            continue;
+
+        PAR = model->params->data.F32;
+        dPAR = model->dparams->data.F32;
+
+        // pmSourceSextractType (source, &type, &flags);
+
+	axes = pmPSF_ModelToAxes (PAR, 20.0);
+
+        psLineInit (line);
+        psLineAdd (line, "%5.2f",  0.0); // should be type
+        psLineAdd (line, "%11.3f", PAR[PM_PAR_XPOS]);
+        psLineAdd (line, "%11.3f", PAR[PM_PAR_YPOS]);
+        psLineAdd (line, "%9.4f",  source->psfMag);
+        psLineAdd (line, "%9.4f",  source->errMag);
+        psLineAdd (line, "%13.4f", source->sky);
+        psLineAdd (line, "%9.2f",  axes.major);
+        psLineAdd (line, "%9.2f",  axes.minor);
+        psLineAdd (line, "%6.1f",  axes.theta);
+        psLineAdd (line, "%9.4f",  source->extMag);
+        psLineAdd (line, "%9.4f",  source->apMag);
+        psLineAdd (line, "%4d\n",  0); // should be flags
+        fwrite (line->line, 1, line->Nline, f);
+    }
+    fclose (f);
+    psFree (line);
+    return true;
+}
+
+// XXX need to fix the FWHM / shape stuff,
+// XXX make sure we are using the correct mags, etc
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourcePhotometry.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourcePhotometry.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourcePhotometry.c	(revision 22157)
@@ -0,0 +1,809 @@
+/** @file  pmSourcePhotometry.c
+ *
+ *  @author EAM, IfA; GLG, MHPCC
+ *
+ *  @version $Revision: 1.42.8.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-07-11 18:35:04 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmErrorCodes.h"
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAMaskWeight.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmGrowthCurve.h"
+#include "pmResiduals.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmModelClass.h"
+#include "pmSourcePhotometry.h"
+
+# define DO_SKY 0
+
+static float AP_MIN_SN = 0.0;
+
+bool pmSourceMagnitudesInit (psMetadata *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    bool status;
+
+    float limit = psMetadataLookupF32 (&status, config, "AP_MIN_SN");
+    if (status) {
+        AP_MIN_SN = limit;
+    }
+    return true;
+}
+
+/**
+    this function is used to calculate the three defined source magnitudes:
+    - apMag  : only if S/N > AP_MIN_SN
+             : is optionally corrected for curve-of-growth if:
+        - the source is a STAR (PSF)
+        - the option is selected (mode & PM_SOURCE_PHOT_GROWTH)
+    - psfMag : all sources with non-NULL modelPSF
+             : is optionally corrected for aperture residual if:
+        - the source is a STAR (PSF)
+        - the option is selected (mode & PM_SOURCE_PHOT_APCORR)
+
+    - extMag : all sources with non-NULL modelEXT
+**/
+
+// XXX masked region should be (optionally) elliptical
+bool pmSourceMagnitudes (pmSource *source, pmPSF *psf, pmSourcePhotometryMode mode, psMaskType maskVal)
+{
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_PTR_NON_NULL(psf, false);
+
+    int status = false;
+    bool isPSF;
+    float x, y;
+    float rflux;
+    float SN;
+    pmModel *model;
+
+    source->psfMag = NAN;
+    source->extMag = NAN;
+    source->errMag = NAN;
+    source->apMag  = NAN;
+
+    // we must have a valid model
+    model = pmSourceGetModel (&isPSF, source);
+    if (model == NULL) {
+        psTrace ("psModules.objects", 3, "fail mag : no valid model");
+        return false;
+    }
+
+    if (model->dparams->data.F32[PM_PAR_I0] > 0) {
+        SN = model->params->data.F32[PM_PAR_I0] / model->dparams->data.F32[PM_PAR_I0];
+        source->errMag = 1.0 / SN;
+    } else {
+        SN = NAN;
+        source->errMag = NAN;
+    }
+    x = model->params->data.F32[PM_PAR_XPOS];
+    y = model->params->data.F32[PM_PAR_YPOS];
+
+    // measure PSF model photometry
+    if (psf->FluxScale) {
+        // the source peak pixel is guaranteed to be on the image, and only minimally different from the source center
+        double fluxScale = pmTrend2DEval (psf->FluxScale, (float)source->peak->x, (float)source->peak->y);
+        if (!isfinite(fluxScale)) {
+            // XXX objects on the edge can be slightly outside -- if we get an
+            // error, use the full fit.
+            psErrorClear();
+            status = pmSourcePhotometryModel (&source->psfMag, source->modelPSF);
+        } else {
+            if (isfinite(fluxScale) && (fluxScale > 0.0)) {
+                source->psfMag = -2.5*log10(fluxScale * source->modelPSF->params->data.F32[PM_PAR_I0]);
+            } else {
+                source->psfMag = NAN;
+            }
+        }
+    } else {
+        status = pmSourcePhotometryModel (&source->psfMag, source->modelPSF);
+    }
+
+    // if we have a collection of model fits, one of them is a pointer to modelEXT?
+    // XXX not guaranteed
+    if (source->modelFits) {
+      for (int i = 0; i < source->modelFits->n; i++) {
+	pmModel *model = source->modelFits->data[i];
+	status = pmSourcePhotometryModel (&model->mag, model);
+      }
+      if (source->modelEXT) {
+	source->extMag = source->modelEXT->mag;
+      }
+    } else {
+      if (source->modelEXT) {
+	status = pmSourcePhotometryModel (&source->extMag, source->modelEXT);
+      }
+    }
+
+    // for PSFs, correct both apMag and psfMag to same system, consistent with infinite flux star in aperture RADIUS
+    if ((mode & PM_SOURCE_PHOT_APCORR) && isPSF && psf && psf->ApTrend) {
+        // the source peak pixel is guaranteed to be on the image, and only minimally different from the source center
+        double apTrend = pmTrend2DEval (psf->ApTrend, (float)source->peak->x, (float)source->peak->y);
+        source->psfMag += apTrend;
+    }
+
+    if (!isfinite(SN) || (SN < AP_MIN_SN)) {
+        psTrace ("psModules.objects", 3, "fail mag : bad SN: %f (limit: %f)", SN, AP_MIN_SN);
+        return false;
+    }
+
+    // replace source flux
+    // XXX need to be certain which model and size of mask for prior subtraction
+    // XXX full model or just analytical?
+    // XXX use pmSourceAdd instead?
+    if (source->mode & PM_SOURCE_MODE_SUBTRACTED) {
+        pmModelAdd (source->pixels, source->maskObj, model, PM_MODEL_OP_FULL, maskVal);
+    }
+
+    // if we are measuring aperture photometry and applying the growth correction,
+    // we need to shift the flux in the selected pixels (but not the mask)
+    // psImageShift ();
+    psImage *flux = NULL, *mask = NULL; // Star flux and mask images, to photometer
+    if (mode & PM_SOURCE_PHOT_INTERP) {
+        float dx = 0.5 - x + (int)x;
+        float dy = 0.5 - y + (int)y;
+        x += dx;
+        y += dy;
+
+        if (!psImageShiftMask(&flux, &mask, source->pixels, source->maskObj, maskVal, dx, dy,
+                              NAN, 0xff, PS_INTERPOLATE_BICUBE)) {
+            // Not much we can do about it
+            psErrorClear();
+            psTrace ("psModules.objects", 3, "fail shift");
+            return false;
+        }
+
+        // XXX this is test code to verify the shift is doing the right thing (seems to be)
+        # if (0)
+            // measure centroid of unshifted gaussian (should be 16.0,16.0)
+        {
+            psImage *image = source->pixels;
+            float xo = 0.0;
+            float yo = 0.0;
+            float xo2 = 0.0;
+            float yo2 = 0.0;
+            float no = 0.0;
+            for (int j = 0; j < image->numRows; j++)
+            {
+                for (int i = 0; i < image->numCols; i++) {
+                    xo += i*image->data.F32[j][i];
+                    yo += j*image->data.F32[j][i];
+                    xo2 += i*i*image->data.F32[j][i];
+                    yo2 += j*j*image->data.F32[j][i];
+                    no += image->data.F32[j][i];
+                }
+            }
+            xo /= no;
+            yo /= no;
+            xo2 = sqrt (xo2/no - xo*xo);
+            yo2 = sqrt (yo2/no - yo*yo);
+            fprintf (stderr, "pre-shift centroid: %f,%f, sigma: %f,%f: flux: %f\n", xo, yo, xo2, yo2, no);
+        }
+
+        // measure centroid of unshifted gaussian (should be 16.0,16.0)
+        {
+            psImage *image = flux;
+            float xo = 0.0;
+            float yo = 0.0;
+            float xo2 = 0.0;
+            float yo2 = 0.0;
+            float no = 0.0;
+            for (int j = 0; j < image->numRows; j++)
+            {
+                for (int i = 0; i < image->numCols; i++) {
+                    xo += i*image->data.F32[j][i];
+                    yo += j*image->data.F32[j][i];
+                    xo2 += i*i*image->data.F32[j][i];
+                    yo2 += j*j*image->data.F32[j][i];
+                    no += image->data.F32[j][i];
+                }
+            }
+            xo /= no;
+            yo /= no;
+            xo2 = sqrt (xo2/no - xo*xo);
+            yo2 = sqrt (yo2/no - yo*yo);
+            fprintf (stderr, "pre-shift centroid: %f,%f, sigma: %f,%f: flux: %f\n", xo, yo, xo2, yo2, no);
+        }
+        # endif
+
+    } else {
+        flux = source->pixels;
+        mask = source->maskObj;
+    }
+
+    // measure the weight of included pixels
+    // XXX is this supposed to use the weight or the flux?
+    if (mode & PM_SOURCE_PHOT_WEIGHT) {
+        pmSourcePixelWeight (&source->pixWeight, model, source->pixels, source->maskObj, maskVal);
+    }
+
+    // measure object aperture photometry
+    status = pmSourcePhotometryAper  (&source->apMag, model, flux, mask, maskVal);
+    if (!status) {
+        psTrace ("psModules.objects", 3, "fail mag : bad Ap Mag");
+    }
+
+    // for PSFs, correct both apMag and psfMag to same system, consistent with infinite flux star in aperture RADIUS
+    // if the aper mag is NAN, the flux < 0.  this can happen for sources near the
+    // detection limits (esp near bright neighbors)
+    if (isfinite (source->apMag) && isPSF && psf) {
+        if (psf->growth && (mode & PM_SOURCE_PHOT_GROWTH)) {
+            source->apMag += pmGrowthCurveCorrect (psf->growth, model->radiusFit);
+        }
+        if (mode & PM_SOURCE_PHOT_APCORR) {
+	    rflux   = pow (10.0, 0.4*source->psfMag);
+	    source->apMag -= PS_SQR(model->radiusFit)*rflux * psf->skyBias + psf->skySat / rflux;
+        }
+    }
+    if (mode & PM_SOURCE_PHOT_INTERP) {
+        psFree(flux);
+        psFree(mask);
+    }
+
+    // if source was originally subtracted, re-subtract object, leave local sky
+    // XXX replace with pmSourceSub...
+    if (source->mode & PM_SOURCE_MODE_SUBTRACTED) {
+        pmModelSub (source->pixels, source->maskObj, model, PM_MODEL_OP_FULL, maskVal);
+    }
+
+    return status;
+}
+
+/*
+aprMag' - fitMag = flux*skySat + r^2*rflux*skyBias + ApTrend(x,y)
+(aprMag - flux*skySat - r^2*rflux*skyBias) - fitMAg = ApTrend(x,y)
+(aprMag - flux*skySat - r^2*rflux*skyBias) = fitMAg + ApTrend(x,y)
+
+*/
+
+// return source model magnitude
+bool pmSourcePhotometryModel (float *fitMag, pmModel *model)
+{
+    PS_ASSERT_PTR_NON_NULL(fitMag, false);
+    if (model == NULL) {
+        return false;
+    }
+
+    float fitSum = 0;
+    *fitMag = NAN;
+
+    // measure fitMag
+    fitSum = model->modelFlux (model->params);
+    if (fitSum <= 0)
+        return false;
+    if (!isfinite(fitSum))
+        return false;
+    *fitMag = -2.5*log10(fitSum);
+
+    return (true);
+}
+
+// return source aperture magnitude
+bool pmSourcePhotometryAper (float *apMag, pmModel *model, psImage *image, psImage *mask, psMaskType maskVal)
+{
+    PS_ASSERT_PTR_NON_NULL(apMag, false);
+    PS_ASSERT_PTR_NON_NULL(image, false);
+    PS_ASSERT_PTR_NON_NULL(mask, false);
+    PS_ASSERT_PTR_NON_NULL(model, false);
+
+    float apSum = 0;
+    float sky = 0;
+    *apMag = NAN;
+
+    if (DO_SKY) {
+        sky = model->params->data.F32[PM_PAR_SKY];
+    } else {
+        sky = 0;
+    }
+
+    psF32 **imData = image->data.F32;
+    psU8 **mkData = mask->data.U8;
+
+    // measure apMag
+    for (int ix = 0; ix < image->numCols; ix++) {
+        for (int iy = 0; iy < image->numRows; iy++) {
+            if (mkData[iy][ix] & maskVal)
+                continue;
+            apSum += imData[iy][ix] - sky;
+        }
+    }
+    if (apSum <= 0) {
+	*apMag = NAN;
+        return true;
+    }
+
+    *apMag = -2.5*log10(apSum);
+    return true;
+}
+
+// return source aperture magnitude
+bool pmSourcePixelWeight (float *pixWeight, pmModel *model, psImage *image, psImage *mask, psMaskType maskVal)
+{
+    PS_ASSERT_PTR_NON_NULL(pixWeight, false);
+    PS_ASSERT_PTR_NON_NULL(image, false);
+    PS_ASSERT_PTR_NON_NULL(mask, false);
+    PS_ASSERT_PTR_NON_NULL(model, false);
+
+    float modelSum = 0;
+    float validSum = 0;
+    float sky = 0;
+    float value;
+
+    int Xo, Yo, dP;
+    int dX, DX, NX;
+    int dY, DY, NY;
+
+    *pixWeight = 0.0;
+
+    // we only care about the value of the object model, not the local sky
+    if (DO_SKY) {
+        sky = model->params->data.F32[PM_PAR_SKY];
+    } else {
+        sky = 0;
+    }
+
+    // the model function returns the source flux at a position
+    psVector *coord = psVectorAlloc(2, PS_TYPE_F32);
+
+    psVector *params = model->params;
+
+    Xo = params->data.F32[PM_PAR_XPOS];
+    Yo = params->data.F32[PM_PAR_YPOS];
+
+    dX = Xo - image->col0;
+    dP = image->numCols - dX;
+    DX = PS_MAX(dX, dP);
+    NX = image->numCols;
+
+    dY = Yo - image->row0;
+    dP = image->numRows - dY;
+    DY = PS_MAX(dY, dP);
+    NY = image->numRows;
+
+    // measure modelSum and validSum
+    // XXX this does not work for sources near the edge: we need to measure for
+    // a full square region
+    for (int ix = -DX; ix < DX + 1; ix++) {
+        int mx = ix + dX;
+        for (int iy = -DY; iy < DY + 1; iy++) {
+            int my = iy + dY;
+
+            coord->data.F32[0] = (psF32) (ix + Xo);
+            coord->data.F32[1] = (psF32) (iy + Yo);
+
+            // for the full model, add all points
+            value = model->modelFunc (NULL, params, coord) - sky;
+            modelSum += value;
+
+            // include count only the unmasked pixels within the image area
+            if (mx < 0)
+                continue;
+            if (my < 0)
+                continue;
+            if (mx >= NX)
+                continue;
+            if (my >= NY)
+                continue;
+            if (mask->data.U8[my][mx] & maskVal)
+                continue;
+
+            validSum += value;
+        }
+    }
+    psFree (coord);
+
+    if (validSum <= 0)
+        return false;
+
+    *pixWeight = validSum / modelSum;
+    return (true);
+}
+
+# if (0)
+double pmSourceCrossProduct (const pmSource *Mi,
+                             const pmSource *Mj,
+                             const bool unweighted_sum) // should the cross product be weighted?
+{
+    PS_ASSERT_PTR_NON_NULL(Mi, NAN);
+    PS_ASSERT_PTR_NON_NULL(Mj, NAN);
+
+    int Xs, Xe, Ys, Ye;
+    int xi, xj, yi, yj;
+    int xIs, xJs, yIs, yJs;
+    int xIe, yIe;
+    double flux, wt;
+
+    const psImage *Pi = Mi->pixels;
+    assert (Pi != NULL);
+    const psImage *Pj = Mj->pixels;
+    assert (Pj != NULL);
+
+    const psImage *Wi = Mi->weight;
+    if (!unweighted_sum) {
+        assert (Wi != NULL);
+    }
+
+    const psImage *Ti = Mi->maskObj;
+    assert (Ti != NULL);
+    const psImage *Tj = Mj->maskObj;
+    assert (Tj != NULL);
+
+    Xs = PS_MAX (Pi->col0, Pj->col0);
+    Xe = PS_MIN (Pi->col0 + Pi->numCols, Pj->col0 + Pj->numCols);
+
+    Ys = PS_MAX (Pi->row0, Pj->row0);
+    Ye = PS_MIN (Pi->row0 + Pi->numRows, Pj->row0 + Pj->numRows);
+
+    xIs = Xs - Pi->col0;
+    xJs = Xs - Pj->col0;
+    yIs = Ys - Pi->row0;
+    yJs = Ys - Pj->row0;
+
+    xIe = Xe - Pi->col0;
+    yIe = Ye - Pi->row0;
+
+    // note that this is addressing the same image pixels,
+    // though only if both are source not model images
+    flux = 0;
+    for (yi = yIs, yj = yJs; yi < yIe; yi++, yj++) {
+        for (xi = xIs, xj = xJs; xi < xIe; xi++, xj++) {
+            if (Ti->data.U8[yi][xi])
+                continue;
+            if (Tj->data.U8[yj][xj])
+                continue;
+
+            if (unweighted_sum) {
+                flux += (Pi->data.F32[yi][xi] * Pj->data.F32[yj][xj]);
+            } else {
+                wt = Wi->data.F32[yi][xi];
+                if (wt > 0) {
+                    flux += (Pi->data.F32[yi][xi] * Pj->data.F32[yj][xj]) / wt;
+                }
+            }
+        }
+    }
+    return flux;
+}
+
+double pmSourceCrossWeight(const pmSource *Mi,
+                           const pmSource *Mj,
+                           const bool unweighted_sum) // should the cross product be weighted?
+{
+    PS_ASSERT_PTR_NON_NULL(Mi, NAN);
+    PS_ASSERT_PTR_NON_NULL(Mj, NAN);
+
+    int Xs, Xe, Ys, Ye;
+    int xi, xj, yi, yj;
+    int xIs, xJs, yIs, yJs;
+    int xIe, yIe;
+    double flux, wt;
+
+    const psImage *Pi = Mi->pixels;
+    assert (Pi != NULL);
+    const psImage *Pj = Mj->pixels;
+    assert (Pj != NULL);
+
+    const psImage *Wi = Mi->weight;
+    if (!unweighted_sum) {
+        assert (Wi != NULL);
+    }
+
+    const psImage *Ti = Mi->maskObj;
+    assert (Ti != NULL);
+    const psImage *Tj = Mj->maskObj;
+    assert (Tj != NULL);
+
+    Xs = PS_MAX (Pi->col0, Pj->col0);
+    Xe = PS_MIN (Pi->col0 + Pi->numCols, Pj->col0 + Pj->numCols);
+
+    Ys = PS_MAX (Pi->row0, Pj->row0);
+    Ye = PS_MIN (Pi->row0 + Pi->numRows, Pj->row0 + Pj->numRows);
+
+    xIs = Xs - Pi->col0;
+    xJs = Xs - Pj->col0;
+    yIs = Ys - Pi->row0;
+    yJs = Ys - Pj->row0;
+
+    xIe = Xe - Pi->col0;
+    yIe = Ye - Pi->row0;
+
+    // note that this is addressing the same image pixels,
+    // though only if both are source not model images
+    flux = 0;
+    for (yi = yIs, yj = yJs; yi < yIe; yi++, yj++) {
+        for (xi = xIs, xj = xJs; xi < xIe; xi++, xj++) {
+            if (Ti->data.U8[yi][xi])
+                continue;
+            if (Tj->data.U8[yj][xj])
+                continue;
+
+            if (unweighted_sum) {
+                flux++;
+            } else {
+                wt = Wi->data.F32[yi][xi];
+                if (wt > 0) {
+                    flux += 1.0 / wt;
+                }
+            }
+        }
+    }
+    return flux;
+}
+
+double pmSourceWeight(const pmSource *Mi,
+                      int term,
+                      const bool unweighted_sum) // should the cross product be weighted?
+{
+    PS_ASSERT_PTR_NON_NULL(Mi, NAN);
+    double flux = 0, wt = 0, factor = 0;
+
+    const psImage *Pi = Mi->pixels;
+    assert (Pi != NULL);
+    const psImage *Wi = Mi->weight;
+    if (!unweighted_sum) {
+        assert (Wi != NULL);
+    }
+    const psImage *Ti = Mi->maskObj;
+    assert (Ti != NULL);
+
+    // note that this is addressing the same image pixels,
+    // though only if both are source not model images
+    for (int yi = 0; yi < Pi->numRows; yi++) {
+        for (int xi = 0; xi < Pi->numCols; xi++) {
+            if (Ti->data.U8[yi][xi])
+                continue;
+            if (!unweighted_sum) {
+                wt = Wi->data.F32[yi][xi];
+                if (wt == 0)
+                    continue;
+            }
+
+            switch (term) {
+            case 0:
+                factor = 1;
+                break;
+            case 1:
+                factor = xi + Pi->col0;
+                break;
+            case 2:
+                factor = yi + Pi->row0;
+                break;
+            default:
+                psAbort("invalid term for pmSourceWeight");
+            }
+
+            if (unweighted_sum) {
+                flux += (factor * Pi->data.F32[yi][xi]);
+            } else {
+                flux += (factor * Pi->data.F32[yi][xi]) / wt;
+            }
+            // fprintf (stderr, "Pi: %f, flux: %f\n", Pi->data.F32[yi][xi], flux);
+        }
+    }
+    return flux;
+}
+# endif
+
+bool pmSourceChisq (pmModel *model, psImage *image, psImage *mask, psImage *weight,
+                    psMaskType maskVal)
+{
+    PS_ASSERT_PTR_NON_NULL(model, false);
+    PS_ASSERT_PTR_NON_NULL(image, false);
+    PS_ASSERT_PTR_NON_NULL(mask, false);
+    PS_ASSERT_PTR_NON_NULL(weight, false);
+
+    double dC = 0.0;
+    int Npix = 0;
+    for (int j = 0; j < image->numRows; j++) {
+        for (int i = 0; i < image->numCols; i++) {
+            if (mask->data.U8[j][i] & maskVal)
+                continue;
+            if (weight->data.F32[j][i] <= 0)
+                continue;
+            dC += PS_SQR (image->data.F32[j][i]) / weight->data.F32[j][i];
+            Npix ++;
+        }
+    }
+    model->nDOF = Npix - 1;
+    model->chisq = dC;
+
+    return (true);
+}
+
+
+double pmSourceModelWeight(const pmSource *Mi,
+                      int term,
+                      const bool unweighted_sum) // should the cross product be weighted?
+{
+    PS_ASSERT_PTR_NON_NULL(Mi, NAN);
+    double flux = 0, wt = 0, factor = 0;
+
+    const psImage *Pi = Mi->modelFlux;
+    assert (Pi != NULL);
+    const psImage *Wi = Mi->weight;
+    if (!unweighted_sum) {
+        assert (Wi != NULL);
+    }
+    const psImage *Ti = Mi->maskObj;
+    assert (Ti != NULL);
+
+    for (int yi = 0; yi < Pi->numRows; yi++) {
+        for (int xi = 0; xi < Pi->numCols; xi++) {
+            if (Ti->data.U8[yi][xi])
+                continue;
+            if (!unweighted_sum) {
+                wt = Wi->data.F32[yi][xi];
+                if (wt == 0)
+                    continue;
+            }
+
+            switch (term) {
+            case 0:
+                factor = 1;
+                break;
+            case 1:
+                factor = xi + Pi->col0;
+                break;
+            case 2:
+                factor = yi + Pi->row0;
+                break;
+            default:
+                psAbort("invalid term for pmSourceWeight");
+            }
+
+            if (unweighted_sum) {
+                flux += (factor * Pi->data.F32[yi][xi]);
+            } else {
+                flux += (factor * Pi->data.F32[yi][xi]) / wt;
+            }
+        }
+    }
+    return flux;
+}
+
+double pmSourceModelDotModel (const pmSource *Mi,
+                              const pmSource *Mj,
+                              const bool unweighted_sum) // should the cross product be weighted?
+{
+    PS_ASSERT_PTR_NON_NULL(Mi, NAN);
+    PS_ASSERT_PTR_NON_NULL(Mj, NAN);
+    int Xs, Xe, Ys, Ye;
+    int xi, xj, yi, yj;
+    int xIs, xJs, yIs, yJs;
+    int xIe, yIe;
+    double flux, wt;
+
+    const psImage *Pi = Mi->modelFlux;
+    assert (Pi != NULL);
+    const psImage *Pj = Mj->modelFlux;
+    assert (Pj != NULL);
+
+    const psImage *Wi = Mi->weight;
+    if (!unweighted_sum) {
+        assert (Wi != NULL);
+    }
+
+    const psImage *Ti = Mi->maskObj;
+    assert (Ti != NULL);
+    const psImage *Tj = Mj->maskObj;
+    assert (Tj != NULL);
+
+    Xs = PS_MAX (Pi->col0, Pj->col0);
+    Xe = PS_MIN (Pi->col0 + Pi->numCols, Pj->col0 + Pj->numCols);
+
+    Ys = PS_MAX (Pi->row0, Pj->row0);
+    Ye = PS_MIN (Pi->row0 + Pi->numRows, Pj->row0 + Pj->numRows);
+
+    xIs = Xs - Pi->col0;
+    xJs = Xs - Pj->col0;
+    yIs = Ys - Pi->row0;
+    yJs = Ys - Pj->row0;
+
+    xIe = Xe - Pi->col0;
+    yIe = Ye - Pi->row0;
+
+    // note that weight is addressing the same image pixels
+    flux = 0;
+    for (yi = yIs, yj = yJs; yi < yIe; yi++, yj++) {
+        for (xi = xIs, xj = xJs; xi < xIe; xi++, xj++) {
+            if (Ti->data.U8[yi][xi])
+                continue;
+            if (Tj->data.U8[yj][xj])
+                continue;
+
+            // XXX skip the nonsense weight pixels?
+            if (unweighted_sum) {
+                flux += (Pi->data.F32[yi][xi] * Pj->data.F32[yj][xj]);
+            } else {
+                wt = Wi->data.F32[yi][xi];
+                if (wt > 0) {
+                    flux += (Pi->data.F32[yi][xi] * Pj->data.F32[yj][xj]) / wt;
+                }
+            }
+        }
+    }
+    return flux;
+}
+
+double pmSourceDataDotModel (const pmSource *Mi,
+                             const pmSource *Mj,
+                             const bool unweighted_sum) // should the cross product be weighted?
+{
+    PS_ASSERT_PTR_NON_NULL(Mi, NAN);
+    PS_ASSERT_PTR_NON_NULL(Mj, NAN);
+    int Xs, Xe, Ys, Ye;
+    int xi, xj, yi, yj;
+    int xIs, xJs, yIs, yJs;
+    int xIe, yIe;
+    double flux, wt;
+
+    const psImage *Pi = Mi->pixels;
+    assert (Pi != NULL);
+    const psImage *Pj = Mj->modelFlux;
+    assert (Pj != NULL);
+
+    const psImage *Wi = Mi->weight;
+    if (!unweighted_sum) {
+        assert (Wi != NULL);
+    }
+
+    const psImage *Ti = Mi->maskObj;
+    assert (Ti != NULL);
+    const psImage *Tj = Mj->maskObj;
+    assert (Tj != NULL);
+
+    Xs = PS_MAX (Pi->col0, Pj->col0);
+    Xe = PS_MIN (Pi->col0 + Pi->numCols, Pj->col0 + Pj->numCols);
+
+    Ys = PS_MAX (Pi->row0, Pj->row0);
+    Ye = PS_MIN (Pi->row0 + Pi->numRows, Pj->row0 + Pj->numRows);
+
+    xIs = Xs - Pi->col0;
+    xJs = Xs - Pj->col0;
+    yIs = Ys - Pi->row0;
+    yJs = Ys - Pj->row0;
+
+    xIe = Xe - Pi->col0;
+    yIe = Ye - Pi->row0;
+
+    // note that weight is addressing the same image pixels,
+    flux = 0;
+    for (yi = yIs, yj = yJs; yi < yIe; yi++, yj++) {
+        for (xi = xIs, xj = xJs; xi < xIe; xi++, xj++) {
+            if (Ti->data.U8[yi][xi])
+                continue;
+            if (Tj->data.U8[yj][xj])
+                continue;
+
+            // XXX skip the nonsense weight pixels?
+            if (unweighted_sum) {
+                flux += (Pi->data.F32[yi][xi] * Pj->data.F32[yj][xj]);
+            } else {
+                wt = Wi->data.F32[yi][xi];
+                if (wt > 0) {
+                    flux += (Pi->data.F32[yi][xi] * Pj->data.F32[yj][xj]) / wt;
+                }
+            }
+        }
+    }
+    return flux;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourcePhotometry.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourcePhotometry.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourcePhotometry.h	(revision 22157)
@@ -0,0 +1,67 @@
+/* @file  pmSourcePhotometry.h
+ * @brief functions to measure source photometry
+ *
+ * @author EAM, IfA; GLG, MHPCC
+ *
+ * @version $Revision: 1.10.34.1 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2008-07-11 18:35:04 $
+ * Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+# ifndef PM_SOURCE_PHOTOMETRY_H
+# define PM_SOURCE_PHOTOMETRY_H
+
+/// @addtogroup Objects Object Detection / Analysis Functions
+/// @{
+
+/**
+ *
+ * The function returns both the magnitude of the fit, defined as -2.5log(flux),
+ * where the flux is integrated under the model, theoretically from a radius of 0
+ * to infinity. In practice, we integrate the model beyond 50sigma.  The aperture magnitude is
+ * defined as -2.5log(flux) , where the flux is summed for all pixels which are
+ * not excluded by the aperture mask. The model flux is calculated by calling the
+ * model-specific function provided by pmModelFlux_GetFunction.
+ *
+ * XXX: must code this.
+ *
+ */
+
+typedef enum {
+    PM_SOURCE_PHOT_NONE   = 0x0000,
+    PM_SOURCE_PHOT_GROWTH = 0x0001,
+    PM_SOURCE_PHOT_APCORR = 0x0002,
+    PM_SOURCE_PHOT_WEIGHT = 0x0004,
+    PM_SOURCE_PHOT_INTERP = 0x0008,
+} pmSourcePhotometryMode;
+
+bool pmSourcePhotometryModel(
+    float *fitMag,                      ///< integrated fit magnitude
+    pmModel *model                      ///< model used for photometry
+);
+
+bool pmSourcePhotometryAper(
+    float   *apMag,                     ///< aperture flux magnitude
+    pmModel *model,                     ///< model used for photometry
+    psImage *image,                     ///< image pixels to be used
+    psImage *mask,                      ///< mask of pixels to ignore
+    psMaskType maskVal                  ///< Value to mask
+);
+
+bool pmSourceMagnitudesInit (psMetadata *config);
+bool pmSourceMagnitudes (pmSource *source, pmPSF *psf, pmSourcePhotometryMode mode, psMaskType maskVal);
+bool pmSourcePixelWeight (float *pixWeight, pmModel *model, psImage *image, psImage *mask, psMaskType maskVal);
+bool pmSourceChisq (pmModel *model, psImage *image, psImage *mask, psImage *weight, psMaskType maskVal);
+
+
+double pmSourceDataDotModel (const pmSource *Mi, const pmSource *Mj, const bool unweighted_sum);
+double pmSourceModelDotModel (const pmSource *Mi, const pmSource *Mj, const bool unweighted_sum);
+double pmSourceModelWeight(const pmSource *Mi, int term, const bool unweighted_sum);
+
+// retire these:
+// double pmSourceCrossProduct(const pmSource *Mi, const pmSource *Mj, const bool unweighted_sum);
+// double pmSourceCrossWeight(const pmSource *Mi, const pmSource *Mj, const bool unweighted_sum);
+// double pmSourceWeight(const pmSource *Mi, int term, const bool unweighted_sum);
+
+/// @}
+# endif /* PM_SOURCE_PHOTOMETRY_H */
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourcePlotApResid.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourcePlotApResid.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourcePlotApResid.c	(revision 22157)
@@ -0,0 +1,163 @@
+/** @file  pmSourcePlot.c
+ *
+ *  Plot the Aperture Mag - Fitted Mag residuals
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-11-10 01:09:20 $
+ *  Copyright 2006 IfA, University of Hawaii
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmResiduals.h"
+#include "pmGrowthCurve.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmSourcePlots.h"
+#include "pmKapaPlots.h"
+
+// this variable is defined in psmodules.h if ohana-config is found
+# if (HAVE_KAPA)
+    # include <kapa.h>
+
+// plot the sx, sy, sxy as vector field,
+// plot the PSF measured sx, sy, sxy as vector field
+// pull the sources from the config / file?
+bool pmSourcePlotApResid (const pmFPAview *view, pmFPAfile *file, const pmConfig *config,
+                          pmSourcePlotLayout *layout)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(layout, false);
+
+    Graphdata graphdata;
+    KapaSection section;
+
+    psLogMsg ("psphot", 3, "creating ap mag - psf mag plot");
+
+    // find the currently selected readout
+    pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
+
+    psArray *sources = psMetadataLookupPtr (NULL, readout->analysis, "PSPHOT.SOURCES");
+    if (sources == NULL)
+        return false;
+
+    int kapa = pmKapaOpen (true);
+    if (kapa == -1) {
+        psError(PS_ERR_UNKNOWN, true, "failure to open kapa");
+        return false;
+    }
+
+    int DX = 1000;
+    float dx = DX / layout->nX;
+    float dy = dx * layout->aspectRatio;
+    int DY = dy * layout->nY;
+
+    // XXX make the aspect-ratio match the image
+    if (layout->i == 0) {
+        KapaResize (kapa, DX, DY);
+    }
+
+    KapaClearPlots (kapa);
+    KapaInitGraph (&graphdata);
+    section.dx = dx / DX;
+    section.dy = dy / DY;
+    section.x = layout->iX * section.dx;
+    section.y = layout->iY * section.dy;
+    section.name = NULL;
+    psStringAppend (&section.name, "a%d", layout->i);
+    KapaSetSection (kapa, &section);
+    psFree (section.name);
+
+    psVector *x = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *y = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+
+    graphdata.xmin = +32.0;
+    graphdata.xmax = -32.0;
+    graphdata.ymin = +32.0;
+    graphdata.ymax = -32.0;
+
+    // construct the plot vectors
+    int n = 0;
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+	if (!source) continue;
+        if (source->type != PM_SOURCE_TYPE_STAR) continue;
+	if (!isfinite (source->apMag)) continue;
+	if (!isfinite (source->psfMag)) continue;
+
+        x->data.F32[n] = source->psfMag;
+        y->data.F32[n] = source->apMag - source->psfMag;
+        graphdata.xmin = PS_MIN(graphdata.xmin, x->data.F32[n]);
+        graphdata.xmax = PS_MAX(graphdata.xmax, x->data.F32[n]);
+        graphdata.ymin = PS_MIN(graphdata.ymin, y->data.F32[n]);
+        graphdata.ymax = PS_MAX(graphdata.ymax, y->data.F32[n]);
+
+        n++;
+    }
+    x->n = y->n = n;
+
+    float range;
+    range = graphdata.xmax - graphdata.xmin;
+    graphdata.xmax += 0.05*range;
+    graphdata.xmin -= 0.05*range;
+    range = graphdata.ymax - graphdata.ymin;
+    graphdata.ymax += 0.05*range;
+    graphdata.ymin -= 0.05*range;
+
+    // XXX set the plot range to match the image
+    KapaSetLimits (kapa, &graphdata);
+
+    KapaSetFont (kapa, "helvetica", 14);
+    KapaBox (kapa, &graphdata);
+    KapaSendLabel (kapa, "PSF Mag", KAPA_LABEL_XM);
+    KapaSendLabel (kapa, "Ap Mag - PSF Mag", KAPA_LABEL_YM);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 2;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, n, &graphdata);
+    KapaPlotVector (kapa, n, x->data.F32, "x");
+    KapaPlotVector (kapa, n, y->data.F32, "y");
+
+    if (layout->i == layout->nTotal - 1) {
+        psLogMsg ("psphot", 3, "saving plot to %s", file->filename);
+        KapaPNG (kapa, file->filename);
+	KapaClearPlots (kapa);
+    }
+
+    psFree (x);
+    psFree (y);
+    return true;
+}
+
+# else
+
+bool pmSourcePlotApResid (const pmFPAview *view, pmFPAfile *file, const pmConfig *config, pmSourcePlotLayout *layout)
+{
+    psLogMsg ("psphot", 3, "skipping ap-mag resid plot");
+    return true;
+}
+
+# endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourcePlotMoments.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourcePlotMoments.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourcePlotMoments.c	(revision 22157)
@@ -0,0 +1,176 @@
+/** @file  pmSourcePlot.c
+ *
+ * This file contains functions to write source plots
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.10.20.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-07-11 18:35:04 $
+ *
+ *  Copyright 2006 IfA, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmGrowthCurve.h"
+#include "pmResiduals.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmSourcePlots.h"
+#include "pmKapaPlots.h"
+
+// this variable is defined in psmodules.h if ohana-config is found
+# if (HAVE_KAPA)
+# include <kapa.h>
+
+// plot the sx, sy moments plane (faint and bright sources)
+bool pmSourcePlotMoments (const pmFPAview *view, pmFPAfile *file, const pmConfig *config,
+                          pmSourcePlotLayout *layout)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(layout, false);
+
+    Graphdata graphdata;
+    KapaSection section;
+
+    psLogMsg ("psphot", 3, "creating moments plot");
+
+    // find the currently selected readout
+    pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
+
+    psArray *sources = psMetadataLookupPtr (NULL, readout->analysis, "PSPHOT.SOURCES");
+    if (sources == NULL)
+        return false;
+
+    int kapa = pmKapaOpen (false);
+    if (kapa == -1) {
+        psError(PS_ERR_UNKNOWN, true, "failure to open kapa");
+        return false;
+    }
+
+    // moments plot subplots are square
+    int DX = 1000;
+    int dx = DX / layout->nX;
+    int dy = dx;
+    int DY = dy * layout->nY;
+
+    // XXX make the aspect-ratio match the image
+    if (layout->i == 0) {
+        KapaResize (kapa, DX, DY);
+    }
+
+    KapaClearPlots (kapa);
+    KapaInitGraph (&graphdata);
+    section.dx = dx / (float) DX;
+    section.dy = dy / (float) DY;
+    section.x = layout->iX * section.dx;
+    section.y = layout->iY * section.dy;
+    section.name = NULL;
+    psStringAppend (&section.name, "a%d", layout->i);
+    KapaSetSection (kapa, &section);
+    psFree (section.name);
+
+    // examine sources to set data range
+    graphdata.xmin = -0.05;
+    graphdata.ymin = -0.05;
+    graphdata.xmax = +4.05;
+    graphdata.ymax = +4.05;
+    KapaSetLimits (kapa, &graphdata);
+
+    KapaSetFont (kapa, "helvetica", 14);
+    KapaBox (kapa, &graphdata);
+    KapaSendLabel (kapa, "&ss&h_x| (pixels)", KAPA_LABEL_XM);
+    KapaSendLabel (kapa, "&ss&h_y| (pixels)", KAPA_LABEL_YM);
+
+    psVector *xBright = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *yBright = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *xFaint  = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *yFaint  = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+
+    // construct the vectors
+    int nB = 0;
+    int nF = 0;
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        if (source->moments == NULL)
+            continue;
+
+        xFaint->data.F32[nF] = source->moments->Sx;
+        yFaint->data.F32[nF] = source->moments->Sy;
+        nF++;
+
+        // XXX make this a user-defined cutoff
+        if (source->moments->SN < 50)
+            continue;
+
+        xBright->data.F32[nB] = source->moments->Sx;
+        yBright->data.F32[nB] = source->moments->Sy;
+        nB++;
+    }
+    xFaint->n = nF;
+    yFaint->n = nF;
+
+    xBright->n = nB;
+    yBright->n = nB;
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 0;
+    graphdata.size = 0.3;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, nF, &graphdata);
+    KapaPlotVector (kapa, nF, xFaint->data.F32, "x");
+    KapaPlotVector (kapa, nF, yFaint->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 2;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, nB, &graphdata);
+    KapaPlotVector (kapa, nB, xBright->data.F32, "x");
+    KapaPlotVector (kapa, nB, yBright->data.F32, "y");
+
+    if (layout->i == layout->nTotal - 1) {
+        psLogMsg ("psphot", 3, "saving plot to %s", file->filename);
+        KapaPNG (kapa, file->filename);
+	KapaClearPlots (kapa);
+    }
+
+    psFree (xBright);
+    psFree (yBright);
+    psFree (xFaint);
+    psFree (yFaint);
+
+    return true;
+}
+
+
+# else
+
+    bool pmSourcePlotMoments (const pmFPAview *view, pmFPAfile *file, const pmConfig *config, pmSourcePlotLayout *layout)
+{
+    psLogMsg ("psphot", 3, "skipping moments plot");
+    return true;
+}
+
+# endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourcePlotPSFModel.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourcePlotPSFModel.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourcePlotPSFModel.c	(revision 22157)
@@ -0,0 +1,237 @@
+/** @file  pmSourcePlot.c
+ *
+ * This file contains functions to write source plots
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.12 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-11-10 01:09:20 $
+ *
+ *  Copyright 2006 IfA, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmGrowthCurve.h"
+#include "pmResiduals.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmSourcePlots.h"
+#include "pmKapaPlots.h"
+
+// this variable is defined in psmodules.h if ohana-config is found
+# if (HAVE_KAPA)
+# include <kapa.h>
+
+// plot the sx, sy, sxy as vector field,
+// plot the PSF measured sx, sy, sxy as vector field
+// pull the sources from the config / file?
+bool pmSourcePlotPSFModel (const pmFPAview *view, pmFPAfile *file, const pmConfig *config,
+                           pmSourcePlotLayout *layout)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(layout, false);
+
+    Graphdata graphdata;
+    KapaSection section;
+
+    psLogMsg ("psphot", 3, "creating psf model plot");
+
+    // find the currently selected readout
+    pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
+
+    psArray *sources = psMetadataLookupPtr (NULL, readout->analysis, "PSPHOT.SOURCES");
+    if (sources == NULL)
+        return false;
+
+    int kapa = pmKapaOpen (false);
+    if (kapa == -1) {
+        psError(PS_ERR_UNKNOWN, true, "failure to open kapa");
+        return false;
+    }
+
+    int DX = 1000;
+    float dx = DX / layout->nX;
+    float dy = dx * layout->aspectRatio;
+    int DY = dy * layout->nY;
+
+    // XXX make the aspect-ratio match the image
+    if (layout->i == 0) {
+        KapaResize (kapa, DX, DY);
+    }
+    
+    KapaClearPlots (kapa);
+    KapaInitGraph (&graphdata);
+    section.dx = dx / DX;
+    section.dy = dy / DY;
+    section.x = layout->iX * section.dx;
+    section.y = layout->iY * section.dy;
+    section.name = NULL;
+    psStringAppend (&section.name, "a%d", layout->i);
+    KapaSetSection (kapa, &section);
+    psFree (section.name);
+
+    psVector *xMNT = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+    psVector *yMNT = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+    psVector *xPSF = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+    psVector *yPSF = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+    psVector *xMIN = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+    psVector *yMIN = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+
+    // construct the plot vectors
+    int nMNT = 0;
+    int nPSF = 0;
+    int nMIN = 0;
+    dx = 0;
+    dy = 0;
+    float scale = 10;
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        if (source->moments == NULL)
+            continue;
+        if (source->moments->SN < 25)
+            continue;
+        if (source->type != PM_SOURCE_TYPE_STAR)
+            continue;
+
+        pmModel *model = source->modelPSF;
+        if (model == NULL)
+            continue;
+
+        psF32 *PAR = model->params->data.F32;
+
+        psEllipseMoments moments;
+        moments.x2 = source->moments->Sx;
+        moments.y2 = source->moments->Sy;
+        moments.xy = source->moments->Sxy;
+
+        // force the axis ratio to be < 20.0
+        psEllipseAxes axes_mnt = psEllipseMomentsToAxes (moments, 20.0);
+        psEllipseAxes axes_psf = pmPSF_ModelToAxes (PAR, 20.0);
+
+        // moments major axis
+        dx = scale*axes_mnt.major*cos(axes_mnt.theta);
+        dy = scale*axes_mnt.major*sin(axes_mnt.theta);
+        xMNT->data.F32[nMNT] = PAR[PM_PAR_XPOS] - dx;
+        yMNT->data.F32[nMNT] = PAR[PM_PAR_YPOS] - dy;
+        nMNT++;
+        xMNT->data.F32[nMNT] = PAR[PM_PAR_XPOS] + dx;
+        yMNT->data.F32[nMNT] = PAR[PM_PAR_YPOS] + dy;
+        nMNT++;
+
+        // psf major axis
+        dx = scale*axes_psf.major*cos(axes_psf.theta);
+        dy = scale*axes_psf.major*sin(axes_psf.theta);
+        xPSF->data.F32[nPSF] = PAR[PM_PAR_XPOS] - dx;
+        yPSF->data.F32[nPSF] = PAR[PM_PAR_YPOS] - dy;
+        nPSF++;
+        xPSF->data.F32[nPSF] = PAR[PM_PAR_XPOS] + dx;
+        yPSF->data.F32[nPSF] = PAR[PM_PAR_YPOS] + dy;
+        nPSF++;
+
+        // minor axis (to show size)
+        dy = +scale*axes_psf.minor*cos(axes_psf.theta);
+        dx = -scale*axes_psf.minor*sin(axes_psf.theta);
+        xMIN->data.F32[nMIN] = PAR[PM_PAR_XPOS] - dx;
+        yMIN->data.F32[nMIN] = PAR[PM_PAR_YPOS] - dy;
+        nMIN++;
+        xMIN->data.F32[nMIN] = PAR[PM_PAR_XPOS] + dx;
+        yMIN->data.F32[nMIN] = PAR[PM_PAR_YPOS] + dy;
+        nMIN++;
+
+        graphdata.xmin = PS_MIN(graphdata.xmin, PAR[PM_PAR_XPOS]);
+        graphdata.xmax = PS_MAX(graphdata.xmax, PAR[PM_PAR_XPOS]);
+        graphdata.ymin = PS_MIN(graphdata.ymin, PAR[PM_PAR_YPOS]);
+        graphdata.ymax = PS_MAX(graphdata.ymax, PAR[PM_PAR_YPOS]);
+    }
+    xMNT->n = yMNT->n = nMNT;
+    xPSF->n = yPSF->n = nPSF;
+    xMIN->n = yMIN->n = nMIN;
+
+    float range;
+    range = graphdata.xmax - graphdata.xmin;
+    graphdata.xmax += 0.05*range;
+    graphdata.xmin -= 0.05*range;
+    range = graphdata.ymax - graphdata.ymin;
+    graphdata.ymax += 0.05*range;
+    graphdata.ymin -= 0.05*range;
+
+    // XXX set the plot range to match the image
+    KapaSetLimits (kapa, &graphdata);
+
+    KapaSetFont (kapa, "helvetica", 14);
+    KapaBox (kapa, &graphdata);
+    KapaSendLabel (kapa, "x (pixels)", KAPA_LABEL_XM);
+    KapaSendLabel (kapa, "y (pixels)", KAPA_LABEL_YM);
+    KapaSendLabel (kapa, "vector is major axis (scaled by 20) : black are moments, blue are psf model, red is psf minor axis", KAPA_LABEL_XP);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 100;
+    graphdata.size = 0.3;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, nMNT, &graphdata);
+    KapaPlotVector (kapa, nMNT, xMNT->data.F32, "x");
+    KapaPlotVector (kapa, nMNT, yMNT->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("blue");
+    graphdata.ptype = 100;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, nPSF, &graphdata);
+    KapaPlotVector (kapa, nPSF, xPSF->data.F32, "x");
+    KapaPlotVector (kapa, nPSF, yPSF->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 100;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, nMIN, &graphdata);
+    KapaPlotVector (kapa, nMIN, xMIN->data.F32, "x");
+    KapaPlotVector (kapa, nMIN, yMIN->data.F32, "y");
+
+    if (layout->i == layout->nTotal - 1) {
+        psLogMsg ("psphot", 3, "saving plot to %s", file->filename);
+        KapaPNG (kapa, file->filename);
+	KapaClearPlots (kapa);
+    }
+
+    psFree (xMNT);
+    psFree (yMNT);
+    psFree (xPSF);
+    psFree (yPSF);
+    psFree (xMIN);
+    psFree (yMIN);
+
+    return true;
+}
+
+# else
+
+    bool pmSourcePlotPSFModel (const pmFPAview *view, pmFPAfile *file, const pmConfig *config, pmSourcePlotLayout *layout)
+{
+    psLogMsg ("psphot", 3, "skipping psf model plot");
+    return true;
+}
+
+# endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourcePlots.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourcePlots.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourcePlots.c	(revision 22157)
@@ -0,0 +1,178 @@
+/** @file  pmSourcePlot.c
+ *
+ * This file contains functions to write source plots
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-11-10 01:09:20 $
+ *
+ *  Copyright 2006 IfA, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmDetrendDB.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPALevel.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+#include "pmSourcePlots.h"
+
+// this function is called for the specific plotting program at the fileLevel
+// fileLevel must be >= chip
+bool pmFPAviewWriteSourcePlot(const pmFPAview *view, pmFPAfile *file, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    if ((view->readout != -1) || (view->cell != -1)) {
+        psError(PS_ERR_UNKNOWN, true, "source plots must have fileLevel >= chip");
+        return false;
+    }
+
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        pmFPAWriteSourcePlot (fpa, view, file, config, NULL);
+        return true;
+    }
+
+    pmChipWriteSourcePlot (fpa, view, file, config, NULL);
+    return true;
+}
+
+// read in all chip-level SourcePlot files for this FPA
+bool pmFPAWriteSourcePlot (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file, const pmConfig *config, pmSourcePlotLayout *layout)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    // if the layout is not defined, this level is the top level being plotted
+    // determine the plot layout
+    if (!layout) {
+        layout = pmSourcePlotLayoutAlloc ();
+        // XXX temporary hardwired values for essence?
+        layout->nX = 4;
+        layout->nY = 2;
+        layout->aspectRatio = 1.0;
+        // count the number of chips and their layout (xrange, yrange, axis ratio)
+        for (int i = 0; i < fpa->chips->n; i++) {
+            pmChip *chip = fpa->chips->data[i];
+            if (chip->data_exists) {
+                layout->nTotal ++;
+            }
+        }
+    }
+
+    pmFPAview *chipView = pmFPAviewAlloc(0);
+
+    while (pmFPAviewNextChip (chipView, fpa, 1) != NULL) {
+        pmChipWriteSourcePlot (fpa, chipView, file, config, layout);
+        layout->i ++;
+        layout->iX ++;
+        if (layout->iX == layout->nX) {
+            layout->iY++;
+            layout->iX = 0;
+        }
+    }
+    psFree (chipView);
+    psFree (layout);
+    return true;
+}
+
+// read in all cell-level SourcePlot files for this chip
+bool pmChipWriteSourcePlot (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file, const pmConfig *config, pmSourcePlotLayout *layout)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    // if the layout is not defined, this level is the top level being plotted
+    // determine the plot layout (always single chip)
+    if (!layout) {
+        layout = pmSourcePlotLayoutAlloc ();
+        layout->nTotal = 1;
+        layout->nX = 1;
+        layout->nY = 1;
+        layout->aspectRatio = 1.0;
+    } else {
+        psMemIncrRefCounter (layout);
+    }
+
+    pmFPAview *newView = pmFPAviewAlloc(0);
+    newView->chip = view->chip;
+
+    while (pmFPAviewNextCell (newView, fpa, 1) != NULL) {
+        while (pmFPAviewNextReadout (newView, fpa, 1) != NULL) {
+
+            if (!strcmp (file->name, "SOURCE.PLOT.PSFMODEL")) {
+                pmSourcePlotPSFModel (newView, file, config, layout);
+            }
+            if (!strcmp (file->name, "SOURCE.PLOT.MOMENTS")) {
+                pmSourcePlotMoments (newView, file, config, layout);
+            }
+            if (!strcmp (file->name, "SOURCE.PLOT.APRESID")) {
+                pmSourcePlotApResid (newView, file, config, layout);
+            }
+        }
+    }
+    psFree (newView);
+    psFree (layout);
+    return true;
+}
+
+static void pmSourcePlotLayoutFree(pmSourcePlotLayout *layout)
+{
+    return;
+}
+
+pmSourcePlotLayout *pmSourcePlotLayoutAlloc()
+{
+    pmSourcePlotLayout *layout = (pmSourcePlotLayout *)psAlloc(sizeof(pmSourcePlotLayout));
+    psMemSetDeallocator(layout, (psFreeFunc)pmSourcePlotLayoutFree );
+
+    layout->nX = layout->nY = layout->nTotal = 0;
+    layout->iX = layout->iY = layout->i  =  0;
+    layout->aspectRatio = 1.0;
+    return (layout);
+}
+
+bool psMemCheckSourcePlotLayout(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) pmSourcePlotLayoutFree);
+}
+
+
+/* we have three types of plots to produce (maybe more?)
+ * for each plot, we need to supply the following information:
+ *     - what are overall dimensions?
+ *     - what is the relative position of this element?
+ *     - what are the dimensions of this element?
+ *     - create a new page for this element?
+
+ * the answers to these questions come from slightly different locations
+ * for the different types of plots:
+
+ *  1) focal-plane layout based plots:
+ *     - dimensions depend on fileLevel
+
+ *  2) multi-row plots:
+ *     - dimensions depend on total number of rows
+
+ *  2) multi-page plots:
+ *     - dimensions depend on total number of rows
+ */
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourcePlots.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourcePlots.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourcePlots.h	(revision 22157)
@@ -0,0 +1,42 @@
+/* @file  pmSourcePlots.h
+ * @brief functions to create plots illustrating source properties
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-11-10 01:09:20 $
+ * Copyright 2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_SOURCE_PLOTS_H
+#define PM_SOURCE_PLOTS_H
+
+/// @addtogroup Objects Object Detection / Analysis Functions
+/// @{
+
+typedef struct
+{
+    int nX;
+    int nY;
+    int nTotal;
+    int iX;
+    int iY;
+    int i;
+    float aspectRatio;
+}
+pmSourcePlotLayout;
+
+// typedef bool (*pmSourcePlotFunction)(pmConfig *config, pmFPAview *view, pmSourcePlotLayout *layout);
+
+pmSourcePlotLayout *pmSourcePlotLayoutAlloc();
+bool psMemCheckSourcePlotLayout(psPtr ptr);
+
+bool pmFPAviewWriteSourcePlot(const pmFPAview *view, pmFPAfile *file, const pmConfig *config);
+bool pmFPAWriteSourcePlot (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file, const pmConfig *config, pmSourcePlotLayout *layout);
+bool pmChipWriteSourcePlot (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file, const pmConfig *config, pmSourcePlotLayout *layout);
+bool pmSourcePlotPSFModel (const pmFPAview *view, pmFPAfile *file, const pmConfig *config, pmSourcePlotLayout *layout);
+bool pmSourcePlotMoments (const pmFPAview *view, pmFPAfile *file, const pmConfig *config, pmSourcePlotLayout *layout);
+bool pmSourcePlotApResid (const pmFPAview *view, pmFPAfile *file, const pmConfig *config, pmSourcePlotLayout *layout);
+
+/// @}
+#endif // PM_SOURCE_PLOTS_H
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceSky.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceSky.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceSky.c	(revision 22157)
@@ -0,0 +1,156 @@
+/** @file  pmSourceSky.c
+ *
+ *  Functions to measure the local sky and sky variance for sources on images
+ *
+ *  @author GLG, MHPCC
+ *  @author EAM, IfA: significant modifications.
+ *
+ *  @version $Revision: 1.16.18.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-07-11 18:35:04 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAMaskWeight.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmResiduals.h"
+#include "pmGrowthCurve.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmSourceSky.h"
+
+/******************************************************************************
+pmSource *pmSourceLocalSky(source, statsOptions, Radius): this
+routine creates a new pmSource.moments element if needed and sets pmSource.pmMoments.sky
+
+The sky value is set from the pixels in the square annulus surrounding the
+peak pixel.
+
+The source.pixels and source.mask must already exist
+
+This function modifies the source mask; it should only be called before the object aperture is defined
+*****************************************************************************/
+
+bool pmSourceLocalSky(
+    pmSource *source,
+    psStatsOptions statsOptions,
+    psF32 Radius,
+    psMaskType maskVal,
+    psMaskType markVal)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_IMAGE_NON_NULL(source->pixels, false);
+    PS_ASSERT_IMAGE_NON_NULL(source->maskObj, false);
+    PS_ASSERT_PTR_NON_NULL(source->peak, false);
+    PS_ASSERT_INT_POSITIVE(Radius, false);
+
+    psStatsOptions statistic = psStatsSingleOption(statsOptions);
+    if (statistic == 0) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, false, "Multiple or no statistics specified: %x\n", statsOptions);
+        return NULL;
+    }
+
+    psImage *image = source->pixels;
+    psImage *mask  = source->maskObj;
+    pmPeak *peak  = source->peak;
+    psRegion srcRegion;
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    srcRegion = psRegionForSquare(peak->x, peak->y, Radius);
+    srcRegion = psRegionForImage(mask, srcRegion);
+
+    psImageMaskRegion(mask, srcRegion, "OR", markVal);
+    psStats *myStats = psStatsAlloc(statsOptions);
+    if (!psImageStats(myStats, image, mask, maskVal)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to get image statistics.\n");
+        psFree(myStats);
+        return false;
+    }
+    psImageMaskRegion(mask, srcRegion, "AND", PS_NOT_U8(markVal));
+    double value = psStatsGetValue(myStats, statistic);
+    psFree(myStats);
+
+    if (isnan(value)) {
+        psTrace("psModules.objects", 3, "---- %s(false) end ----\n", __func__);
+        return(false);
+    }
+    if (source->moments == NULL) {
+        source->moments = pmMomentsAlloc();
+    }
+    source->moments->Sky = value;
+    psTrace("psModules.objects", 3, "---- %s(true) end ----\n", __func__);
+    return (true);
+}
+
+// A complementary function to pmSourceLocalSky: calculate the local median variance
+bool pmSourceLocalSkyVariance(
+    pmSource *source,
+    psStatsOptions statsOptions,
+    psF32 Radius,
+    psMaskType maskVal,
+    psMaskType markVal
+)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_IMAGE_NON_NULL(source->weight, false);
+    PS_ASSERT_IMAGE_NON_NULL(source->maskObj, false);
+    PS_ASSERT_PTR_NON_NULL(source->peak, false);
+    PS_ASSERT_INT_POSITIVE(Radius, false);
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    psStatsOptions statistic = psStatsSingleOption(statsOptions);
+    if (statistic == 0) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, false, "Multiple or no statistics specified: %x\n", statsOptions);
+        return NULL;
+    }
+
+    psImage *image = source->weight;
+    psImage *mask  = source->maskObj;
+    pmPeak *peak  = source->peak;
+    psRegion srcRegion;
+
+    srcRegion = psRegionForSquare(peak->x, peak->y, Radius);
+    srcRegion = psRegionForImage(mask, srcRegion);
+
+    psImageMaskRegion(mask, srcRegion, "OR", markVal);
+    psStats *myStats = psStatsAlloc(statsOptions);
+    if (!psImageStats(myStats, image, mask, maskVal)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to get image statistics.\n");
+        psFree(myStats);
+        return false;
+    }
+    psImageMaskRegion(mask, srcRegion, "AND", PS_NOT_U8(markVal));
+    double value = psStatsGetValue(myStats, statistic);
+    psFree(myStats);
+
+    if (isnan(value)) {
+        psTrace("psModules.objects", 3, "---- %s(false) end ----\n", __func__);
+        return(false);
+    }
+    if (source->moments == NULL) {
+        source->moments = pmMomentsAlloc();
+    }
+    source->moments->dSky = value;
+    psTrace("psModules.objects", 3, "---- %s(true) end ----\n", __func__);
+    return (true);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceSky.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceSky.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceSky.h	(revision 22157)
@@ -0,0 +1,47 @@
+/* @file  pmSourceSky.h
+ * @author EAM, IfA; GLG, MHPCC
+ *
+ * @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-06-20 02:22:26 $
+ * Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+# ifndef PM_SOURCE_SKY_H
+# define PM_SOURCE_SKY_H
+
+/// @addtogroup Objects Object Detection / Analysis Functions
+/// @{
+
+/** pmSourceLocalSky()
+ *
+ * Measure the local sky in the vicinity of the given source. The Radius
+ * defines the square aperture in which the moments will be measured. This
+ * function assumes the source pixels have been defined, and that the value of
+ * Radius here is smaller than the value of Radius used to define the pixels. The
+ * annular region not contained within the radius defined here is used to measure
+ * the local background in the vicinity of the source. The local background
+ * measurement uses the specified statistic passed in via the statsOptions entry.
+ * This function allocates the pmMoments structure. The resulting sky is used to
+ * set the value of the pmMoments.sky element of the provided pmSource structure.
+ *
+ */
+bool pmSourceLocalSky(
+    pmSource *source,   ///< The input image (float)
+    psStatsOptions statsOptions, ///< The statistic used in calculating the background sky
+    float Radius,   ///< The inner radius of the square annulus to exclude
+    psMaskType maskVal,                 ///< Value to mask
+    psMaskType mark                     ///< Mask value for marking
+);
+
+
+// A complementary function to pmSourceLocalSky: calculate the local sky variance
+bool pmSourceLocalSkyVariance(
+    pmSource *source,   ///< The input image (float)
+    psStatsOptions statsOptions, ///< The statistic used in calculating the background sky
+    float Radius,   ///< The inner radius of the square annulus to exclude
+    psMaskType maskVal,                 ///< Value to mask
+    psMaskType mark                     ///< Mask value for marking
+);
+
+/// @}
+# endif /* PM_SOURCE_PHOTOMETRY_H */
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceUtils.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceUtils.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceUtils.c	(revision 22157)
@@ -0,0 +1,117 @@
+/** @file  pmSource.c
+ *
+ *  Functions to define and manipulate sources on images
+ *
+ *  @author GLG, MHPCC
+ *  @author EAM, IfA: significant modifications.
+ *
+ *  @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-11-10 01:09:20 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAMaskWeight.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmResiduals.h"
+#include "pmGrowthCurve.h"
+#include "pmTrend2D.h"
+#include "pmPSF.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmSourceUtils.h"
+
+/******************************************************************************
+    pmSourceModelGuess(source, model): This function allocates a new
+    pmModel structure based on the given modelType specified in the argument list.
+    The corresponding pmModelGuess function is returned, and used to
+    supply the values of the params array in the pmModel structure.
+ 
+    XXX: Many parameters are based on the src->moments structure, which is in
+    image, not subImage coords.  Therefore, the calls to the model evaluation
+    functions will be in image, not subImage coords.  Remember this.
+*****************************************************************************/
+pmModel *pmSourceModelGuess(pmSource *source,
+                            pmModelType modelType)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(source, NULL);
+    PS_ASSERT_PTR_NON_NULL(source->moments, NULL);
+    PS_ASSERT_PTR_NON_NULL(source->peak, NULL);
+
+    pmModel *model = pmModelAlloc(modelType);
+
+    if (!model->modelGuess(model, source)) {
+	psFree (model);
+	return NULL;
+    }
+
+    psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+    return(model);
+}
+
+pmSource *pmSourceFromModel (pmModel *model, pmReadout *readout, float radius, 
+                             pmSourceType type)
+{
+    PS_ASSERT_PTR_NON_NULL(model, NULL);
+    PS_ASSERT_PTR_NON_NULL(readout, NULL);
+
+    pmSource *source = pmSourceAlloc ();
+
+    // use the model centroid for the peak
+    switch (type) {
+      case PM_SOURCE_TYPE_STAR:
+	source->modelPSF = model;
+	break;
+      case PM_SOURCE_TYPE_EXTENDED:
+	source->modelEXT = model;
+	break;
+      default:
+	psAbort ("invalid source type");
+    }
+    source->type = type;
+
+    pmCell *cell = readout->parent;
+
+    float Io    = model->params->data.F32[PM_PAR_I0];
+    float xChip = model->params->data.F32[PM_PAR_XPOS];
+    float yChip = model->params->data.F32[PM_PAR_YPOS];
+
+    source->peak = pmPeakAlloc (xChip, yChip, Io, PM_PEAK_LONE);
+
+    int x0Cell = psMetadataLookupS32(NULL, cell->concepts, "CELL.X0");
+    int y0Cell = psMetadataLookupS32(NULL, cell->concepts, "CELL.Y0");
+    int xParityCell = psMetadataLookupS32(NULL, cell->concepts, "CELL.XPARITY");
+    int yParityCell = psMetadataLookupS32(NULL, cell->concepts, "CELL.YPARITY");
+
+    // XXX fix the binning : currently not selected from concepts
+    // int xBin = psMetadataLookupS32(NULL, cell->concepts, "CELL.XBIN"); // Binning in x and y
+    // int yBin = psMetadataLookupS32(NULL, cell->concepts, "CELL.YBIN"); // Binning in x and y
+    int xBin = 1;
+    int yBin = 1;
+
+    // Position on the cell 
+    float xCell = PM_CHIP_TO_CELL(xChip, x0Cell, xParityCell, xBin);
+    float yCell = PM_CHIP_TO_CELL(yChip, y0Cell, yParityCell, yBin);
+
+    // Position on the readout
+    // float xReadout = CELL_TO_READOUT(xCell, x0Readout);
+    // float yReadout = CELL_TO_READOUT(yCell, y0Readout);
+    
+    pmSourceDefinePixels (source, readout, xCell, yCell, radius);
+
+    return (source);
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceUtils.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceUtils.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmSourceUtils.h	(revision 22157)
@@ -0,0 +1,40 @@
+/* @file  pmSourceUtils.h
+ *
+ * Utility functions for working with pmSources
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-08-24 00:11:02 $
+ * Copyright 2007 IfA, University of Hawaii
+ */
+
+# ifndef PM_SOURCE_UTILS_H
+# define PM_SOURCE_UTILS_H
+
+/// @addtogroup Objects Object Detection / Analysis Functions
+/// @{
+
+/** pmSourceModelGuess()
+ *
+ * Convert available data to an initial guess for the given model. This
+ * function allocates a pmModel entry for the pmSource structure based on the
+ * provided model selection. The method of defining the model parameter guesses
+ * are specified for each model below. The guess values are placed in the model
+ * parameters. The function returns TRUE on success or FALSE on failure.
+ *
+ */
+pmModel *pmSourceModelGuess(
+    pmSource *source,   ///< The input pmSource
+    pmModelType model   ///< The type of model to be created.
+);
+
+pmSource *pmSourceFromModel (
+  pmModel *model, 
+  pmReadout *readout, 
+  float radius,
+  pmSourceType type
+  );
+
+/// @}
+# endif /* PM_MODEL_CLASS_H */
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmTrend2D.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmTrend2D.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmTrend2D.c	(revision 22157)
@@ -0,0 +1,300 @@
+/** @file  pmTrend2D.c
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.9 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-01-15 02:47:51 $
+ *  Copyright 2004 Institute for Astronomy, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include <strings.h>
+# include <pslib.h>
+# include "pmTrend2D.h"
+
+static void pmTrend2DFree (pmTrend2D *trend) {
+
+    if (trend == NULL) 
+        return;
+
+    psFree (trend->stats);
+    psFree (trend->poly);
+    psFree (trend->map);
+    return;
+}
+
+pmTrend2D *pmTrend2DAlloc (pmTrend2DMode mode, psImage *image, int nXtrend, int nYtrend, psStats *stats)
+{
+    PS_ASSERT_PTR_NON_NULL(stats, NULL);
+    if (mode == PM_TREND_MAP) {
+        PS_ASSERT_PTR_NON_NULL(image, NULL);
+    }
+
+    pmTrend2D *trend = (pmTrend2D *) psAlloc(sizeof(pmTrend2D));
+    psMemSetDeallocator(trend, (psFreeFunc) pmTrend2DFree);
+
+    trend->map = NULL;
+    trend->poly = NULL;
+    trend->stats = psMemIncrRefCounter (stats);
+    trend->mode = mode;
+	
+    switch (mode) {
+      case PM_TREND_POLY_ORD:
+	trend->poly = psPolynomial2DAlloc (PS_POLYNOMIAL_ORD, nXtrend, nYtrend);
+	// set masking somehow
+ 	for (int nx = 0; nx < trend->poly->nX + 1; nx++) {
+	    for (int ny = 0; ny < trend->poly->nY + 1; ny++) {
+		if (nx + ny >= PS_MAX (trend->poly->nX, trend->poly->nY) + 1) {
+		    trend->poly->coeffMask[nx][ny] = PS_POLY_MASK_SET;
+		} else {
+		    trend->poly->coeffMask[nx][ny] = PS_POLY_MASK_NONE;
+		}
+	    }
+	}
+	break;
+
+      case PM_TREND_POLY_CHEB:
+	trend->poly = psPolynomial2DAlloc (PS_POLYNOMIAL_CHEB, nXtrend, nYtrend);
+	break;
+
+      case PM_TREND_MAP: {
+	  // binning defines the map scale relationship
+	  psImageBinning *binning = psImageBinningAlloc();
+	  binning->nXruff = nXtrend;
+	  binning->nYruff = nYtrend;
+	  binning->nXfine = image->numCols;
+	  binning->nYfine = image->numRows;
+
+	  trend->map = psImageMapAlloc (image, binning, stats);
+	  psFree (binning);
+	  break;
+      }
+      // XXX: Put a more graceful error here.
+      default:
+	psAbort ("error");
+    }
+    return (trend);
+}
+
+bool psMemCheckTrend2D(psPtr ptr)
+{
+    PS_ASSERT_PTR(ptr, false);
+    return ( psMemGetDeallocator(ptr) == (psFreeFunc) pmTrend2DFree);
+}
+
+pmTrend2D *pmTrend2DNoImageAlloc (pmTrend2DMode mode, psImageBinning *binning, psStats *stats)
+{
+    if (mode == PM_TREND_MAP) {
+        PS_ASSERT_PTR_NON_NULL(binning, NULL);
+        PS_ASSERT_PTR_NON_NULL(stats, NULL);
+    }
+    pmTrend2D *trend = (pmTrend2D *) psAlloc(sizeof(pmTrend2D));
+    psMemSetDeallocator(trend, (psFreeFunc) pmTrend2DFree);
+
+    trend->map = NULL;
+    trend->poly = NULL;
+    trend->stats = psMemIncrRefCounter (stats);
+    trend->mode = mode;
+	
+    switch (mode) {
+      case PM_TREND_POLY_ORD:
+	trend->poly = psPolynomial2DAlloc (PS_POLYNOMIAL_ORD, binning->nXruff, binning->nYruff);
+	// set masking somehow
+ 	for (int nx = 0; nx < trend->poly->nX + 1; nx++) {
+	    for (int ny = 0; ny < trend->poly->nY + 1; ny++) {
+		if (nx + ny >= PS_MAX (trend->poly->nX, trend->poly->nY) + 1) {
+		    trend->poly->coeffMask[nx][ny] = PS_POLY_MASK_SET;
+		} else {
+		    trend->poly->coeffMask[nx][ny] = PS_POLY_MASK_NONE;
+		}
+	    }
+	}
+	break;
+
+      case PM_TREND_POLY_CHEB:
+	trend->poly = psPolynomial2DAlloc (PS_POLYNOMIAL_CHEB, binning->nXruff, binning->nYruff);
+	break;
+
+      case PM_TREND_MAP: {
+	  // binning defines the map scale relationship
+	  trend->map = psImageMapNoImageAlloc (binning, stats);
+	  break;
+      }
+
+      default:
+	psAbort ("error");
+    }
+    return (trend);
+}
+
+pmTrend2D *pmTrend2DFieldAlloc (pmTrend2DMode mode, int nXfield, int nYfield, int nXtrend, int nYtrend, psStats *stats)
+{
+    PS_ASSERT_PTR_NON_NULL(stats, NULL);
+    pmTrend2D *trend = (pmTrend2D *) psAlloc(sizeof(pmTrend2D));
+    psMemSetDeallocator(trend, (psFreeFunc) pmTrend2DFree);
+
+    trend->map = NULL;
+    trend->poly = NULL;
+    trend->stats = psMemIncrRefCounter (stats);
+    trend->mode = mode;
+	
+    switch (mode) {
+      case PM_TREND_POLY_ORD:
+	trend->poly = psPolynomial2DAlloc (PS_POLYNOMIAL_ORD, nXtrend, nYtrend);
+	// set masking somehow
+ 	for (int nx = 0; nx < trend->poly->nX + 1; nx++) {
+	    for (int ny = 0; ny < trend->poly->nY + 1; ny++) {
+		if (nx + ny >= PS_MAX (trend->poly->nX, trend->poly->nY) + 1) {
+		    trend->poly->coeffMask[nx][ny] = PS_POLY_MASK_SET;
+		} else {
+		    trend->poly->coeffMask[nx][ny] = PS_POLY_MASK_NONE;
+		}
+	    }
+	}
+	break;
+
+      case PM_TREND_POLY_CHEB:
+	trend->poly = psPolynomial2DAlloc (PS_POLYNOMIAL_CHEB, nXtrend, nYtrend);
+	break;
+
+      case PM_TREND_MAP: {
+	  // binning defines the map scale relationship
+	  psImageBinning *binning = psImageBinningAlloc();
+	  binning->nXfine = nXfield;
+	  binning->nYfine = nYfield;
+	  binning->nXruff = nXtrend;
+	  binning->nYruff = nYtrend;
+
+	  trend->map = psImageMapAlloc (NULL, binning, stats);
+	  psFree (binning);
+	  break;
+      }
+
+      default:
+        // XXX: Put a more graceful error here.
+	psAbort ("error");
+    }
+    return (trend);
+}
+
+bool pmTrend2DFit (pmTrend2D *trend, psVector *mask, psMaskType maskVal, psVector *x,
+                   psVector *y, psVector *f, psVector *df)
+{
+    PS_ASSERT_PTR_NON_NULL(trend, false);
+    PS_ASSERT_VECTOR_NON_NULL(x, false);
+    PS_ASSERT_VECTOR_NON_NULL(y, false);
+    PS_ASSERT_VECTOR_NON_NULL(f, false);
+
+    bool status;
+    switch (trend->mode) {
+      case PM_TREND_POLY_ORD:
+      case PM_TREND_POLY_CHEB:
+        status = psVectorClipFitPolynomial2D (trend->poly, trend->stats, mask, maskVal, f, df, x, y);
+	// we can use the API here which adjusts the polynomial order based on the number
+	// of points in the image, and potentially based on the fractional range of the
+	// data?
+	break;
+
+      case PM_TREND_MAP:
+	// XXX supply fraction from trend elements
+	// XXX need to add the API which adjusts the scale
+	status = psImageMapClipFit (trend->map, trend->stats, mask, maskVal, x, y, f, df);
+	break;
+
+      default:
+	psAbort ("error");
+    }
+    return status;
+}
+
+double pmTrend2DEval (pmTrend2D *trend, float x, float y)
+{
+    if (!trend) return 0.0;
+
+    double result;
+    switch (trend->mode) {
+      case PM_TREND_POLY_ORD:
+      case PM_TREND_POLY_CHEB:
+	result = psPolynomial2DEval (trend->poly, x, y);
+	break;
+
+      case PM_TREND_MAP:
+	result = psImageMapEval (trend->map, x, y);
+	break;
+
+      default:
+	psAbort ("error");
+    }
+    return result;
+}
+
+psVector *pmTrend2DEvalVector (pmTrend2D *trend, psVector *x, psVector *y)
+{
+    PS_ASSERT_PTR_NON_NULL(trend, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(x, false);
+    PS_ASSERT_VECTOR_NON_NULL(y, false);
+    psVector *result;
+
+    switch (trend->mode) {
+      case PM_TREND_POLY_ORD:
+      case PM_TREND_POLY_CHEB:
+	result = psPolynomial2DEvalVector (trend->poly, x, y);
+	break;
+
+      case PM_TREND_MAP:
+	result = psImageMapEvalVector (trend->map, x, y);
+	break;
+
+      default:
+	psAbort ("error");
+    }
+    return result;
+}
+
+psString pmTrend2DModeToString (pmTrend2DMode mode) {
+    
+    psString name;
+
+    switch (mode) {
+      case PM_TREND_NONE:
+	name = psStringCopy ("NONE");
+	break;
+      case PM_TREND_POLY_ORD:
+	name = psStringCopy ("POLY_ORD");
+	break;
+      case PM_TREND_POLY_CHEB:
+	name = psStringCopy ("POLY_CHEB");
+	break;
+      case PM_TREND_MAP:
+	name = psStringCopy ("MAP");
+	break;
+      default:
+        psError (PS_ERR_UNKNOWN, true, "Unknown pmTrend2D mode\n");
+	psAbort ("invalid mode %d", mode);
+    }
+    return name;
+}
+
+pmTrend2DMode pmTrend2DModeFromString (psString name) {
+
+    if (!name) return PM_TREND_NONE;
+
+    if (!strcasecmp (name, "NONE")) {
+	return PM_TREND_NONE;
+    }
+    if (!strcasecmp (name, "POLY_ORD")) {
+	return PM_TREND_POLY_ORD;
+    }
+    if (!strcasecmp (name, "POLY_CHEB")) {
+	return PM_TREND_POLY_CHEB;
+    }
+    if (!strcasecmp (name, "MAP")) {
+	return PM_TREND_MAP;
+    }
+    psError (PS_ERR_UNKNOWN, true, "Unknown pmTrend2D mode %s\n", name);
+    return PM_TREND_NONE;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmTrend2D.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmTrend2D.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/objects/pmTrend2D.h	(revision 22157)
@@ -0,0 +1,55 @@
+/* @file  pmTrend2D.h
+ *
+ * functions to represent ways of modeling a 2D trend
+ *
+ * @author EAM, IfA
+ *
+ * @version $Revision: 1.6 $ $Name: not supported by cvs2svn $
+ * @date $Date: 2007-12-15 01:23:18 $
+ * Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+# ifndef PM_TREND_2D_H
+# define PM_TREND_2D_H
+
+#include <pslib.h>
+
+/// @addtogroup Objects Object Detection / Analysis Functions
+/// @{
+
+typedef enum {
+    PM_TREND_NONE,
+    PM_TREND_POLY_ORD,
+    PM_TREND_POLY_CHEB,
+    PM_TREND_MAP,
+} pmTrend2DMode;
+
+typedef struct {
+    psPolynomial2D *poly;
+    psImageMap *map;
+    psStats *stats;                     // Statistics for clipped fitting
+    psStatsOptions singleMean, singleStdev; // Staistics for mean and stdev when there's a single pixel
+    pmTrend2DMode mode; // POLY_ORD, POLY_CHEB, MAP
+} pmTrend2D;
+
+// allocate a pmTrend2D structure tied to an image dimensions.  nXtrend,nYtrend is the order for the polynomials, max number of grid cells for
+// psImageMap
+pmTrend2D *pmTrend2DAlloc (pmTrend2DMode mode, psImage *image, int nXtrend, int nYtrend, psStats *stats);
+
+bool psMemCheckTrend2D(psPtr ptr);
+
+pmTrend2D *pmTrend2DNoImageAlloc (pmTrend2DMode mode, psImageBinning *binning, psStats *stats);
+
+// allocate a pmTrend2D tied to an abstract field with size nXfield,nYfield
+pmTrend2D *pmTrend2DFieldAlloc (pmTrend2DMode mode, int nXfield, int nYfield, int nXtrend, int nYtrend, psStats *stats);
+
+bool pmTrend2DFit (pmTrend2D *trend, psVector *mask, psMaskType maskVal, psVector *x, psVector *y, psVector *f, psVector *df);
+
+double pmTrend2DEval (pmTrend2D *trend, float x, float y);
+psVector *pmTrend2DEvalVector (pmTrend2D *trend, psVector *x, psVector *y);
+
+psString pmTrend2DModeToString (pmTrend2DMode mode);
+pmTrend2DMode pmTrend2DModeFromString (psString name);
+
+/// @}
+# endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/psmodules.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/psmodules.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/psmodules.h	(revision 22157)
@@ -0,0 +1,120 @@
+#ifndef PS_MODULES_H
+#define PS_MODULES_H
+
+#include <pslib.h>
+
+// the following headers are from psModule:extras
+#include <psPipe.h>
+#include <psIOBuffer.h>
+#include <psVectorBracket.h>
+#include <pmKapaPlots.h>
+
+// XXX the following headers define constructs needed by the elements below
+#include <pmConfig.h>
+#include <pmDetrendDB.h>
+#include <pmHDU.h>
+#include <pmFPA.h>
+#include <pmFPALevel.h>
+#include <pmFPAview.h>
+#include <pmFPAfile.h>
+
+// the following headers are from psModule:config
+#include <pmErrorCodes.h>
+#include <pmConfigRecipes.h>
+#include <pmConfigCamera.h>
+#include <pmConfigCommand.h>
+#include <pmConfigMask.h>
+#include <pmVersion.h>
+
+// the following headers are from psModule:concepts
+#include <pmConcepts.h>
+#include <pmConceptsRead.h>
+#include <pmConceptsStandard.h>
+#include <pmConceptsWrite.h>
+#include <pmConceptsPhotcode.h>
+#include <pmConceptsAverage.h>
+#include <pmConceptsUpdate.h>
+
+// the following headers are from psModule:camera
+#include <pmHDUUtils.h>
+#include <pmHDUGenerate.h>
+#include <pmFPAFlags.h>
+#include <pmFPAfileDefine.h>
+#include <pmFPAfileFitsIO.h>
+#include <pmFPAfileIO.h>
+#include <pmFPARead.h>
+#include <pmFPAConstruct.h>
+#include <pmFPACopy.h>
+#include <pmFPAHeader.h>
+#include <pmFPAMaskWeight.h>
+#include <pmFPAMosaic.h>
+#include <pmFPARead.h>
+#include <pmFPAWrite.h>
+#include <pmFPA_JPEG.h>
+#include <pmFPAExtent.h>
+#include <pmFPACalibration.h>
+#include <pmReadoutStack.h>
+#include <pmFPAUtils.h>
+#include <pmCellSquish.h>
+
+// the following headers are from psModule:detrend
+#include <pmFlatField.h>
+#include <pmFlatNormalize.h>
+#include <pmFringeStats.h>
+#include <pmMaskBadPixels.h>
+#include <pmNonLinear.h>
+#include <pmOverscan.h>
+#include <pmBias.h>
+#include <pmShutterCorrection.h>
+// #include <pmSkySubtract.h>
+#include <pmDark.h>
+
+// the following headers are from psModule:astrom
+#include <pmAstrometryWCS.h>
+#include <pmAstrometryUtils.h>
+#include <pmAstrometryRegions.h>
+#include <pmAstrometryObjects.h>
+#include <pmAstrometryModel.h>
+#include <pmAstrometryRefstars.h>
+#include <pmAstrometryDistortion.h>
+
+// the following headers are from psModule:imcombine
+#include <pmStack.h>
+#include <pmStackReject.h>
+#include <pmSubtraction.h>
+#include <pmSubtractionStamps.h>
+#include <pmSubtractionKernels.h>
+#include <pmSubtractionMatch.h>
+#include <pmSubtractionIO.h>
+#include <pmSubtractionParams.h>
+#include <pmSubtractionMask.h>
+#include <pmSubtractionEquation.h>
+#include <pmReadoutCombine.h>
+
+// the following headers are from psModule:objects
+#include <pmPeaks.h>
+#include <pmMoments.h>
+#include <pmResiduals.h>
+#include <pmGrowthCurve.h>
+#include <pmTrend2D.h>
+#include <pmPSF.h>
+#include <pmModel.h>
+#include <pmSource.h>
+#include <pmSourceUtils.h>
+#include <pmSourceIO.h>
+#include <pmSourceSky.h>
+#include <pmSourceFitModel.h>
+#include <pmSourceFitSet.h>
+#include <pmSourceContour.h>
+#include <pmSourcePlots.h>
+#include <pmPSF_IO.h>
+#include <pmPSFtry.h>
+#include <pmModelClass.h>
+#include <pmModelUtils.h>
+#include <pmSourcePhotometry.h>
+
+// The following headers are from random locations, here because they cross bounds
+#include <pmReadoutFake.h>
+#include <pmPSFEnvelope.h>
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/sky/pmSkyCell.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/sky/pmSkyCell.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/sky/pmSkyCell.h	(revision 22157)
@@ -0,0 +1,18 @@
+#ifndef PM_SKY_CELL_H
+#define PM_SKY_CELL_H
+
+/// Structure representing a sky cell
+typedef struct
+{
+    psImage *image;                     // Image; type F32
+    psImage *mask;                      // Mask image; type MASK_TYPE
+    psImage *weight;                    // Weight map; type F32
+    psPlaneTransform *toTPA;            // Transformation from pixels to tangent plane
+    psPlaneTransform *fromTPA;          // Transformation from tangent plane to pixels
+    psProjection *toSky;                // Projection to the sky
+}
+pmSkyCell;
+
+
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/sky/pmWarp.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/sky/pmWarp.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/sky/pmWarp.h	(revision 22157)
@@ -0,0 +1,24 @@
+#ifndef PM_WARP_MAP_H
+#define PM_WARP_MAP_H
+
+// a single pswarpMap converts coordinates from one image to a second image
+// the linear model is only valid over a limited range of pixels
+typedef struct
+{
+    double Xo, Xx, Xy;
+    double Yo, Yx, Yy;
+    int xo;
+    int yo;
+}
+pswarpMap;
+
+// the pswarpMapGrid carries a collection of pswarpMag structures representing the
+// local value of the pswarpMap at different locations in the image.
+typedef struct
+{
+    pswarpMap ***maps;
+    int nXpts, nYpts;                   // number of x,y samples in the grid
+    int nXpix, nYpix;                   // x,y spacing in src image pixels of grid samples
+    int xMin,  yMin;                    // coordinate of first grid sample
+}
+pswarpMapGrid;
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/sky/pmWarpMap.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/sky/pmWarpMap.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/sky/pmWarpMap.c	(revision 22157)
@@ -0,0 +1,241 @@
+#include <stdio.h>
+#include <math.h>
+#include <pslib.h>
+
+#include "pmWarpMap.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Allocators and deallocators
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static void warpMapFree(pswarpMap *map)
+{
+    // No operations required; this function exists merely to identify a pmWarpMap
+    return;
+}
+
+pswarpMap *pswarpMapAlloc(void)
+{
+    pswarpMap *map = (pswarpMap *) psAlloc (sizeof(pswarpMap));
+    psMemSetDeallocator(map, (psFreeFunc)warpMapFree);
+
+    map->Xo = map->Xx = map->Xy = NAN;
+    map->Yo = map->Yx = map->Yy = NAN;
+    map->xo = map->yo = NAN;
+
+    return map;
+}
+
+
+static void warpMapGridFree(pswarpMapGrid *grid)
+{
+    if (!grid->maps) {
+        // Nothing to free
+        return;
+    }
+
+    for (int i = 0; i < grid->nXpts; i++) {
+        for (int j = 0; j < grid->nYpts; j++) {
+            psFree(grid->maps[i][j]);
+        }
+        psFree(grid->maps[i]);
+    }
+    psFree (grid->maps);
+    return;
+}
+
+pswarpMapGrid *pswarpMapGridAlloc(int nXpts, int nYpts)
+{
+    PS_ASSERT_INT_POSITIVE(nXpts, NULL);
+    PS_ASSERT_INT_POSITIVE(nYpts, NULL);
+
+    pswarpMapGrid *grid = (pswarpMapGrid *) psAlloc(sizeof(pswarpMapGrid));
+    psMemSetDeallocator(grid, (psFreeFunc)warpMapGridFree);
+
+    grid->maps = psAlloc(nXpts*sizeof(void **));
+    for (int i = 0; i < nXpts; i++) {
+        grid->maps[i] = psAlloc(nYpts*sizeof(void *));
+        for (int j = 0; j < nYpts; j++) {
+            grid->maps[i][j] = pswarpMapAlloc();
+        }
+    }
+    grid->nXpts = nXpts;
+    grid->nYpts = nYpts;
+
+    grid->nXpix = 0;
+    grid->nYpix = 0;
+
+    return grid;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Operations
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// XXX for the moment, ignore readout->cell->chip offsets
+pswarpMapGrid *pswarpMapGridFromImage(pmSkyCell *dest, const pmReadout *src, int nXpix, int nYpix)
+{
+    PS_ASSERT_PTR_NON_NULL(dest, NULL);
+    PS_ASSERT_PTR_NON_NULL(src, NULL);
+    PS_ASSERT_IMAGE_NON_NULL(src->image, NULL);
+    PS_ASSERT_INT_NON_NEGATIVE(nXpix, NULL);
+    PS_ASSERT_INT_NON_NEGATIVE(nYpix, NULL);
+
+    // split the difference of the remainder
+    int xMin = 0.5 * (src->image->numCols % nXpix); // Minimum in x
+    int yMin = 0.5 * (src->image->numRows % nYpix); // Minimum in y
+
+    int nXpts = src->image->numCols / nXpix; // Number of points in x
+    if (src->image->numCols % nXpix == 0) {
+        nXpts++;
+    }
+    int nYpts = src->image->numRows / nYpix; // Number of points in y
+    if (src->image->numRows % nYpix == 0) {
+        nYpts++;
+    }
+
+    pmWarpMapGrid *grid = pmWarpMapGridAlloc(nXpts, nYpts);
+    grid->nXpix = nXpix;
+    grid->nYpix = nYpix;
+    grid->xMin = xMin;
+    grid->yMin = yMin;
+
+    for (int ni = 0, i = xMin; ni < nXpts; i += nXpix, ni++) {
+        for (int nj = 0, j = yMin; nj < nYpts; j += nYpix, nj++) {
+            pmWarpMapSetLocalModel(grid->maps[ni][nj], dest, src, i, j);
+        }
+    }
+
+    return grid;
+}
+
+bool pmWarpMapGridSetGrid(const pmWarpMapGrid *grid, int ix, int iy, int *gridX, int *gridY)
+{
+    *gridX = (ix - grid->xMin + 0.5*grid->nXpix) / grid->nXpix;
+    *gridY = (iy - grid->yMin + 0.5*grid->nYpix) / grid->nYpix;
+
+    return true;
+}
+
+bool pmWarpMapGridNextGrid(const pmWarpMapGrid *grid, int gridX, int gridY, int *nextX, int *nextY)
+{
+    *nextX = gridX*grid->nXpix + grid->xMin + 0.5*grid->nXpix;
+    *nextY = gridY*grid->nYpix + grid->yMin + 0.5*grid->nYpix;
+
+    return true;
+}
+
+// measure the max error accumulated in appling one grid point to its neighbors
+double pmWarpMapGridMaxError(const pmWarpMapGrid *grid)
+{
+    double maxError = 0;                // Maximum error
+
+    for (int i = 0; i < grid->nXpts - 1; i++) {
+        for (int j = 0; j < grid->nYpts - 1; j++) {
+            // measure the output coordinates for the next grid position using the current grid map
+            // compare with the coordinates measured using the next grid map
+            double xRaw, yRaw;          // Raw position
+            double xRef, yRef;          // Reference position
+
+            pmWarpMapApply(&xRaw, &yRaw, grid->maps[i][j], grid->maps[i][j]->xo + grid->nXpix,
+                           grid->maps[i][j]->yo);
+            pmWarpMapApply(&xRef, &yRef, grid->maps[i+1][j], grid->maps[i][j]->xo + grid->nXpix,
+                           grid->maps[i][j]->yo);
+
+            double posError = hypot(xRaw-xRef, yRaw-yRef);
+            maxError = PS_MAX(maxError, posError);
+        }
+    }
+
+    return maxError;
+}
+
+bool pmWarpMapApply(double *outX, double *outY, const pmWarpMap *map, double inX, double inY)
+{
+    *outX = map->Xo + map->Xx*inX + map->Xy*inY;
+    *outY = map->Yo + map->Yx*inX + map->Yy*inY;
+
+    return true;
+}
+
+bool pmWarpMapSetLocalModel(pmWarpMap *map, const pmSkyCell *dest, const pmReadout *src, int ix, int iy)
+{
+    PS_ASSERT_PTR_NON_NULL(map, false);
+    PS_ASSERT_PTR_NON_NULL(dest, false);
+    PS_ASSERT_PTR_NON_NULL(dest->toSky, false);
+    PS_ASSERT_PTR_NON_NULL(dest->fromTPA, false);
+    PS_ASSERT_PTR_NON_NULL(src, false);
+    pmCell *cell = src->parent;         // Parent cell of source
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    pmChip *chip = cell->parent;        // Parent chip of source
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(chip->toFPA, false);
+    pmFPA *fpa = chip->parent;          // Parent FPA of source
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(fpa->toTPA, false);
+    PS_ASSERT_PTR_NON_NULL(fpa->toSky, false);
+    PS_ASSERT_INT_NON_NEGATIVE(ix, false);
+    PS_ASSERT_INT_NON_NEGATIVE(iy, false);
+
+    // These will get allocated in the course of the calculations; may as well allocate them here
+    // XXX save these as static for speed?
+    // Alternatively, pass in a buffer containing these pre-allocated.
+    psPlane *offset = psPlaneAlloc();   // Position offset from the main position of interest (ix,iy)
+    psPlane *FP = psPlaneAlloc();       // Position in the focal plane
+    psPlane *TP = psPlaneAlloc();       // Position in the tangent plane
+    psSphere *sky = psSphereAlloc();    // Position on the sky
+
+    // XXX need to include readout->cell->chip offsets
+    // --> Look up CELL.X0, CELL.Y0 in concepts
+    // --> Use readout->col0, readout->row0
+
+    // V(0,0) position
+    offset->x = ix;
+    offset->y = iy;
+    psPlaneTransformApply(FP, chip->toFPA, offset);
+    psPlaneTransformApply (TP, fpa->toTPA, FP);
+    psDeproject(sky, TP, fpa->toSky);
+    psProject(TP, sky, dest->toSky);
+    psPlane *V00 = psPlaneTransformApply(NULL, dest->fromTPA, TP); // Coordinates at the position of interest
+
+    // V(1,0) position
+    offset->x = ix + 1;
+    offset->y = iy;
+    psPlaneTransformApply(FP, chipSrc->toFPA, offset);
+    psPlaneTransformApply(TP, fpaSrc->toTPA, FP);
+    psDeproject(sky, TP, fpaSrc->toSky);
+    psProject(TP, sky, dest->toSky);
+    psPlane *V10 = psPlaneTransformApply(NULL, dest->fromTPA, TP); // Coordinates at position + (1,0)
+
+    // V(0,1) position
+    offset->x = ix;
+    offset->y = iy + 1;
+    psPlaneTransformApply(FP, chipSrc->toFPA, offset);
+    psPlaneTransformApply(TP, fpaSrc->toTPA, FP);
+    psDeproject(sky, TP, fpaSrc->toSky);
+    psProject(TP, sky, fpaDest->toSky);
+    psPlane *V01 = psPlaneTransformApply(NULL, dest->fromTPA, TP); // Coordinates at position + (0,1)
+
+    psFree(offset);
+    psFree(FP);
+    psFree(TP);
+    psFree(sky);
+
+    // Generate local linear transformation
+    map->Xx = V10->x - V00->x;
+    map->Xy = V01->x - V00->x;
+    map->Xo = V00->x - map->Xx * ix - map->Xy * iy;
+
+    map->Yx = V10->y - V00->y;
+    map->Yy = V01->y - V00->y;
+    map->Yo = V00->y - map->Yx * ix - map->Yy * iy;
+
+    map->xo = ix;
+    map->yo = iy;
+
+    psFree(V00);
+    psFree(V10);
+    psFree(V01);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/src/sky/pmWarpMap.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/src/sky/pmWarpMap.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/src/sky/pmWarpMap.h	(revision 22157)
@@ -0,0 +1,88 @@
+/*  @file pmWarpMap.h
+ *  @brief Warp Map Manipulation functions
+ * 
+ *  @author Paul Price, IfA
+ *  @author Eugene Magnier, IfA
+ * 
+ *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-01-24 01:05:41 $
+ *  Copyright 2005-2006 Institute for Astronomy, University of Hawaii
+ */
+
+#ifndef PM_WARP_MAP_H
+#define PM_WARP_MAP_H
+
+/// @addtogroup Sky
+/// @{
+
+// a single pmWarpMap converts coordinates from one image to a second image
+// the linear model is only valid over a limited range of pixels
+typedef struct
+{
+    double Xo, Xx, Xy;                  // Transformation coefficients for x
+    double Yo, Yx, Yy;                  // Transformation coefficients for y
+    int xo, yo;                         // Origin
+}
+pmWarpMap;
+
+// the pmWarpMapGrid carries a collection of pmWarpMag structures representing the
+// local value of the pmWarpMap at different locations in the image.
+typedef struct
+{
+    pmWarpMap ***maps;                  // 2D image of pointers to maps
+    int nXpts, nYpts;                   // number of x,y samples in the grid
+    int nXpix, nYpix;                   // x,y spacing in src image pixels of grid samples
+    int xMin,  yMin;                    // coordinate of first grid sample
+}
+pmWarpMapGrid;
+
+/// Allocator
+pmWarpMap *pmWarpMapAlloc(void);
+
+/// Allocator
+pmWarpMapGrid *pmWarpMapGridAlloc(int Nx, // Number of grid points in x
+                                  int Ny // Number of grid points in y
+                                 );
+
+
+// Operations follow
+
+
+// construct a grid with superpixel spacing of nXpix, nYpix
+pmWarpMapGrid *pmWarpMapGridFromImage(pmSkyCell *dest, // Destination sky cell
+                                      const pmReadout *src, // Source readout
+                                      int nXpix, // Number of pixels in x
+                                      int nYpix // Number of pixels in y
+                                     );
+
+bool pmWarpMapGridSetGrid(pmWarpMapGrid *grid,
+                          int ix, int iy,
+                          int *gridX,
+                          int *gridY
+                         );
+
+bool pmWarpMapGridNextGrid(pmWarpMapGrid *grid,
+                           int gridX,
+                           int gridY,
+                           int *nextX,
+                           int *nextY
+                          );
+
+// measure the max error accumulated in appling one grid point to its neighbors
+double pmWarpMapGridMaxError(const pmWarpMapGrid *grid
+                            );
+
+bool pmWarpMapApply(double *outX,
+                    double *outY,
+                    const pmWarpMap *map,
+                    double inX,
+                    double inY);
+
+// determine the map for the given pixel from src to dest. pixel is in src coords
+bool pmWarpMapSetLocalModel(pmWarpMap *map,
+                            const pmSkyCell *dest,
+                            const pmReadout *src,
+                            int ix, int iy
+                           );
+/// @}
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/templates/c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/templates/c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/templates/c	(revision 22157)
@@ -0,0 +1,11 @@
+/** @file  filename.c
+ *
+ *  @brief Contains the definitions for ...
+ *
+ *  @author Robert DeSonia, MHPCC
+ *
+ *  @version $Revision: 1.1.1.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2004-09-10 19:01:33 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
Index: /branches/eam_branches/eam_branch_20080706/psModules/templates/h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/templates/h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/templates/h	(revision 22157)
@@ -0,0 +1,13 @@
+/** @file  filename.h
+ *
+ *  @brief Contains the definitions for ...
+ *
+ *  Detailed Description goes here.
+ *
+ *  @author Robert DeSonia, MHPCC
+ *
+ *  @version $Revision: 1.1.1.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2004-09-10 19:01:33 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/.cvsignore	(revision 22157)
@@ -0,0 +1,15 @@
+.deps
+.libs
+Makefile
+tst_pmFlatField
+tst_pmMaskBadPixels
+tst_pmNonLinear
+tst_pmSubtractBias
+temp
+Makefile.in
+tst_pmObjects01
+tst_pmReadoutCombine
+tst_pmSubtractSky
+tst_pmImageSubtract
+tst_pmImageCombine
+tst_pmAstrometry
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/Makefile.am	(revision 22157)
@@ -0,0 +1,9 @@
+SUBDIRS = tap pstap $(SRCDIRS)
+
+TESTS = test.pl
+
+EXTRA_DIST = test.pl
+
+CLEANFILES = core core.* *~
+
+test: check
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/.cvsignore	(revision 22157)
@@ -0,0 +1,12 @@
+.deps
+.libs
+Makefile
+Makefile.in
+temp
+tst_pmAstrometry
+tst_pmAstrometry01
+tap_pmAstrometryWCS
+tap_pmAstrometryWCS_DVO
+tap_pmAstrometryWCS_DVO2
+tap_pmAstrometryWCS_DVO3
+tap_pmAstrometryWCS_DVO4
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/Makefile.am	(revision 22157)
@@ -0,0 +1,30 @@
+AM_CPPFLAGS = \
+	$(SRCINC) \
+	-I$(top_srcdir)/test/tap/src \
+	-I$(top_srcdir)/test/pstap/src \
+	$(PSMODULES_CFLAGS)
+
+AM_LDFLAGS = \
+	$(top_builddir)/src/libpsmodules.la  \
+	$(top_builddir)/test/tap/src/libtap.la \
+	$(top_builddir)/test/pstap/src/libpstap.la \
+	$(PSMODULES_LIBS)
+
+TEST_PROGS = \
+	tap_pmAstrometryWCS \
+	tap_pmAstrometryWCS_DVO \
+	tap_pmAstrometryWCS_DVO2 \
+	tap_pmAstrometryWCS_DVO3 \
+	tap_pmAstrometryWCS_DVO4
+
+if BUILD_TESTS
+bin_PROGRAMS = $(TEST_PROGS)
+TESTS = $(TEST_PROGS)
+else
+check_PROGRAMS = $(TEST_PROGS)
+endif
+
+CLEANFILES = $(check_DATA) temp/* core core.* *~ *.bb *.bbg *.da gmon.out
+
+test: check
+	$(top_srcdir)/test/test.pl
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tap_pmAstrometry.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tap_pmAstrometry.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tap_pmAstrometry.c	(revision 22157)
@@ -0,0 +1,318 @@
+    /** @file  tst_pmAstrometry.c
+     *
+ *  @brief Contains the tests: pmAstrometry.[ch].  The pmxxxAlloc()
+ *  and psFree() functionality are used here.
+ *
+ *  @author GLG, MHPCC
+ *
+ *  XXX: Untested: pmFPACheckParents()
+ *  XXX: Create the pmHDU alloc/free function, test them here
+ *
+ *  @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-06-19 18:28:38 $
+ *
+ *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+ */
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+
+#define CHIP_ALLOC_NAME "ChipName"
+#define CELL_ALLOC_NAME "CellName"
+#define MISC_NUM 32
+#define MISC_NAME "META00"
+#define MISC_NAME2 "META01"
+#define NUM_BIAS_DATA 10
+#define TEST_NUM_ROWS 32
+#define TEST_NUM_COLS 32
+
+psPlaneTransform *PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM()
+{
+    psPlaneTransform *pt = psPlaneTransformAlloc(1, 1);
+    pt->x->coeff[1][0] = 1.0;
+    pt->y->coeff[0][1] = 1.0;
+    return(pt);
+}
+
+psPlaneDistort *PS_CREATE_4D_IDENTITY_PLANE_DISTORT()
+{
+    psPlaneDistort *pd = psPlaneDistortAlloc(1, 1, 1, 1);
+    pd->x->coeff[1][0][0][0] = 1.0;
+    pd->y->coeff[0][1][0][0] = 1.0;
+    return(pd);
+}
+
+/******************************************************************************
+generateSimpleFPA(): This function generates a pmFPA data structure and then
+populates its members with real data.  We do this to ensure that the data is
+later being psFree()'ed correctly.
+ *****************************************************************************/
+pmFPA* generateSimpleFPA(psMetadata *camera)
+{
+    pmFPA* fpa = pmFPAAlloc(camera);
+    fpa->fromTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toSky = psProjectionAlloc(0.0,0.0,10.0,10.0,PS_PROJ_TAN);
+    psMetadataAddS32(fpa->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32((psMetadata *) fpa->camera, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(fpa->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    fpa->hdu = pmHDUAlloc(NULL);
+    return(fpa);
+}
+
+
+/******************************************************************************
+testFPAAlloc()
+    1: We ensure that pmFPAAlloc() properly allocates a pmFPA struct.
+    2: We populate the members with real data to ensure they are being
+       free'ed correctly.
+ *****************************************************************************/
+void testFPAAlloc(void)
+{
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = pmFPAAlloc(camera);
+        ok(fpa != NULL, "pmFPAAlloc() returned a non-NULL");
+        ok(fpa->fromTPA == NULL, "pmFPAAlloc() set ->fromTPA to NULL");
+        ok(fpa->toTPA == NULL, "pmFPAAlloc() set ->toTPA to NULL");
+        ok(fpa->toSky == NULL, "pmFPAAlloc() set ->toSky to NULL");
+        ok(fpa->concepts != NULL &&
+           psMemCheckMetadata(fpa->concepts), "pmFPAAlloc() set ->concepts correctly");
+        ok(fpa->conceptsRead == PM_CONCEPT_SOURCE_NONE, "pmFPAAlloc() set ->conceptsRead correctly");
+        ok(fpa->analysis != NULL &&
+           psMemCheckMetadata(fpa->analysis), "pmFPAAlloc() set ->analysis correctly");
+        ok(fpa->camera == camera, "pmFPAAlloc() set ->camera correctly");
+        ok(fpa->chips != NULL &&
+           psMemCheckArray(fpa->chips), "pmFPAAlloc() set ->chips correctly");
+        ok(fpa->hdu == NULL, "pmFPAAlloc() set ->hdu to NULL");
+        ok(fpa->wrote_phu == false, "pmFPAAlloc() set ->wrote_phu to FALSE");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // Populate the pmFPA struct with real data to ensure they were
+    // psFree()'ed correctly.
+    {
+        psMemId id = psMemGetId();
+        pmFPA* fpa = generateSimpleFPA(NULL);
+        psFree(fpa);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
+
+/******************************************************************************
+generateSimpleChip(): This function generates a pmChip data structure and then
+populates its members with real data.  We do this to ensure that the data is
+later being psFree()'ed correctly.
+ *****************************************************************************/
+pmChip *generateSimpleChip(pmFPA *fpa)
+{
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    chip->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    chip->fromFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    psMetadataAddS32(chip->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(chip->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    chip->hdu = pmHDUAlloc(NULL);
+    return(chip);
+}
+
+void testChipAlloc(void)
+{
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+        ok(chip != NULL, "pmChipAlloc() returned non-NULL");
+        ok(chip->toFPA == NULL, "pmChipAlloc() set chip->toChip to NULL");
+        ok(chip->fromFPA == NULL, "pmChipAlloc() set chip->fromChip to NULL");
+        ok(chip->concepts != NULL &&
+           psMemCheckMetadata(chip->concepts), "pmChipAlloc() set ->concepts correctly");
+        ok(chip->conceptsRead == PM_CONCEPT_SOURCE_NONE, "pmCellAlloc() set ->conceptsRead correctly");
+        ok(chip->analysis != NULL &&
+           psMemCheckMetadata(chip->analysis), "pmChipAlloc() set ->analysis correctly");
+        ok(chip->cells != NULL &&
+           psMemCheckArray(chip->cells), "pmChipAlloc() set ->cells correctly");
+        ok(chip->parent == fpa, "pmChipAlloc() set ->parent correctly");
+        ok(chip->process == true, "pmChipAlloc() set ->process correctly");
+        ok(chip->file_exists == false, "pmChipAlloc() set ->file_exists correctly");
+        ok(chip->data_exists == false, "pmChipAlloc() set ->data_exists correctly");
+        ok(chip->hdu == NULL, "pmChipAlloc() set ->hdu to NULL");
+        ok(chip->wrote_phu == false, "pmChipAlloc() set ->wrote_phu correctly");
+        psFree(camera);
+        psFree(fpa);
+        psFree(chip);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // Populate the pmChip struct with real data to ensure they were
+    // psFree()'ed correctly.
+    {
+        psMemId id = psMemGetId();
+        pmChip *chip = generateSimpleChip(NULL);
+        psFree(chip);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
+
+/******************************************************************************
+generateSimpleCell(): This function generates a pmCell data structure and then
+populates its members with real data.  We do this to ensure that the data is
+later being psFree()'ed correctly.
+ *****************************************************************************/
+pmCell *generateSimpleCell(pmFPA *fpa, pmChip *chip)
+{
+    pmCell *cell = pmCellAlloc(chip, CELL_ALLOC_NAME);
+    psMetadataAddS32(cell->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    cell->hdu = pmHDUAlloc(NULL);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(cell);
+}
+
+void testCellAlloc(void)
+{
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+        pmCell *cell = pmCellAlloc(chip, CELL_ALLOC_NAME);
+        ok(cell != NULL, "pmCellAlloc returned non-NULL");
+        ok(cell->concepts != NULL &&
+           psMemCheckMetadata(cell->concepts), "pmCellpAlloc() set ->concepts correctly");
+        ok(cell->conceptsRead == PM_CONCEPT_SOURCE_NONE, "pmCellAlloc() set ->conceptsRead correctly");
+        ok(cell->config == NULL, "pmCellpAlloc() set ->config to NULL");
+        ok(cell->analysis != NULL &&
+           psMemCheckMetadata(cell->analysis), "pmCellAlloc() set ->analysis correctly");
+        ok(cell->readouts != NULL &&
+           psMemCheckArray(cell->readouts), "pmCellAlloc() set ->readouts correctly");
+        ok(cell->parent == chip, "pmCellAlloc() set ->parent correctly");
+        ok(cell->process == true, "pmCellAlloc() set ->process correctly");
+        ok(cell->file_exists == false, "pmCellAlloc() set ->file_exists correctly");
+        ok(cell->data_exists == false, "pmCellAlloc() set ->data_exists correctly");
+        ok(cell->hdu == NULL, "pmCellAlloc() set ->hdu to NULL");
+        ok(cell->wrote_phu == false, "pmCellAlloc() set ->wrote_phu correctly");
+        psFree(camera);
+        psFree(fpa);
+        psFree(chip);
+        psFree(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // Populate the pmCell struct with real data to ensure they were
+    // psFree()'ed correctly.
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL, NULL);
+        psFree(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // test pmCellFreeReadouts()
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL, NULL);
+        pmCellFreeReadouts(cell);
+        ok(cell->readouts->n == 0, "pmCellFreeReadouts() correctly set cell->readouts->n to 0");
+        psFree(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks (pmCellFreeReadouts)");
+    }
+
+}
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.  We do this to ensure that the data is
+later being psFree()'ed correctly.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(readout);
+}
+
+
+void testReadoutAlloc(void)
+{
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+        pmCell *cell = pmCellAlloc(chip, CELL_ALLOC_NAME);
+        pmReadout *readout = pmReadoutAlloc(cell);
+        ok(readout != NULL, "pmReadoutAlloc() returned non-NULL");
+        ok(readout->col0 == 0, "pmReadoutAlloc() set ->col0 correctly");
+        ok(readout->row0 == 0, "pmReadoutAlloc() set ->row0 correctly");
+        ok(readout->image == NULL, "pmReadoutAlloc() set ->image correctly");
+        ok(readout->mask == NULL, "pmReadoutAlloc() set ->mask correctly");
+        ok(readout->weight == NULL, "pmReadoutAlloc() set ->weight correctly");
+        ok(readout->bias != NULL &&
+           psMemCheckList(readout->bias), "pmReadoutAlloc() set ->bias correctly");
+        ok(readout->analysis != NULL &&
+           psMemCheckMetadata(readout->analysis), "pmReadoutAlloc() set ->analysis correctly");
+        ok(readout->parent == cell, "pmReadoutAlloc() set ->parent correctly");
+        ok(readout->process == true, "pmReadoutAlloc() set ->process correctly");
+        ok(readout->file_exists == false, "pmReadoutAlloc() set ->file_exists correctly");
+        ok(readout->data_exists == false, "pmReadoutAlloc() set ->data_exists correctly");
+        psFree(camera);
+        psFree(fpa);
+        psFree(chip);
+        psFree(cell);
+        psFree(readout);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Populate the pmReadout struct with real data to ensure they were
+    // psFree()'ed correctly.
+    {
+        psMemId id = psMemGetId();
+        pmReadout *readout = generateSimpleReadout(NULL);
+        psFree(readout);
+        // XXX: The pmReadout->bias list is not being free'ed correctly.
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+/*
+    {
+        psMemId id = psMemGetId();
+        pmReadout *readout = pmReadoutAlloc(NULL);
+        readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+        readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+            psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+            psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        }
+        psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+        return(readout);
+    }
+*/
+}
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    plan_tests(56);
+
+    testFPAAlloc();
+    testChipAlloc();
+    testCellAlloc();
+    testReadoutAlloc();
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tap_pmAstrometryWCS.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tap_pmAstrometryWCS.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tap_pmAstrometryWCS.c	(revision 22157)
@@ -0,0 +1,151 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "tap.h"
+#include "pstap.h"
+
+int main (void)
+{
+    plan_tests(33);
+
+    note("pmAstrometryWCS tests");
+
+    {
+        note("test pmAstromReadWCS");
+        psMemId id = psMemGetId();
+
+        // construct a header with a simple set of WCS values
+        // convert to pmFPA components and check
+
+        psMetadata *header = psMetadataAlloc();
+
+        psMetadataAddStr (header, PS_LIST_TAIL, "CTYPE1", PS_META_REPLACE, "", "RA---TAN");
+        psMetadataAddStr (header, PS_LIST_TAIL, "CTYPE2", PS_META_REPLACE, "", "DEC--TAN");
+
+        // center coords (R,D)
+        psMetadataAddF32 (header, PS_LIST_TAIL, "CRVAL1", PS_META_REPLACE, "", 0.0);
+        psMetadataAddF32 (header, PS_LIST_TAIL, "CRVAL2", PS_META_REPLACE, "", 0.0);
+
+        // center coords (X,Y)
+        psMetadataAddF32 (header, PS_LIST_TAIL, "CRPIX1", PS_META_REPLACE, "", 0.0);
+        psMetadataAddF32 (header, PS_LIST_TAIL, "CRPIX2", PS_META_REPLACE, "", 0.0);
+
+        // degrees per pixel
+        psMetadataAddF32 (header, PS_LIST_TAIL, "CDELT1",  PS_META_REPLACE, "", 1.0/3600.0);
+        psMetadataAddF32 (header, PS_LIST_TAIL, "CDELT2",  PS_META_REPLACE, "", 1.0/3600.0);
+
+        // rotation matrix
+        psMetadataAddF32 (header, PS_LIST_TAIL, "PC001001", PS_META_REPLACE, "", 1.0);
+        psMetadataAddF32 (header, PS_LIST_TAIL, "PC001002", PS_META_REPLACE, "", 0.0);
+        psMetadataAddF32 (header, PS_LIST_TAIL, "PC002001", PS_META_REPLACE, "", 0.0);
+        psMetadataAddF32 (header, PS_LIST_TAIL, "PC002002", PS_META_REPLACE, "", 1.0);
+
+        pmFPA *fpa = pmFPAAlloc (NULL);
+        pmChip *chip = pmChipAlloc (NULL, "test");
+
+        // toFPA carries pixel scale (microns per pixel)
+        // toSky carries plate scale (radians per micron)
+        bool status = pmAstromReadWCS (fpa, chip, header, 10.0);
+        ok (status, "converted WCS keywords to FPA astrometry");
+        skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+        ok(fpa->toSky->type == PS_PROJ_TAN, "correct projection (TAN)");
+
+        // make these tests double
+        ok_float(fpa->toSky->R*PS_DEG_RAD, 0.0, "projection center RA %f", fpa->toSky->R*PS_DEG_RAD);
+        ok_float(fpa->toSky->R*PS_DEG_RAD, 0.0, "projection center DEC %f", fpa->toSky->R*PS_DEG_RAD);
+
+        ok_float(fpa->toSky->Xs, PM_RAD_DEG/3600.0/10.0, "projection X scale %f", fpa->toSky->Xs);
+        ok_float(fpa->toSky->Ys, PM_RAD_DEG/3600.0/10.0, "projection X scale %f", fpa->toSky->Ys);
+
+        ok_float(fpa->toTPA->x->coeff[1][0], 1.0, "TP scale (unity): %f", fpa->toTPA->x->coeff[1][0]);
+        ok_float(fpa->toTPA->y->coeff[0][1], 1.0, "TP scale (unity): %f", fpa->toTPA->x->coeff[1][0]);
+
+        ok_float(chip->toFPA->x->coeff[0][0], 0.0, "ref pixel X: %f", chip->toFPA->x->coeff[0][0]);
+        ok_float(chip->toFPA->y->coeff[0][0], 0.0, "ref pixel Y: %f", chip->toFPA->y->coeff[0][0]);
+
+        ok_float(chip->toFPA->x->coeff[1][0], 10.0, "CD1_1: %f", chip->toFPA->x->coeff[1][0]);
+        ok_float(chip->toFPA->x->coeff[0][1], 0.0, "CD1_2: %f", chip->toFPA->x->coeff[0][1]);
+        ok_float(chip->toFPA->y->coeff[1][0], 0.0, "CD2_1: %f", chip->toFPA->y->coeff[1][0]);
+        ok_float(chip->toFPA->y->coeff[0][1], 10.0, "CD2_2: %f", chip->toFPA->y->coeff[0][1]);
+
+        psFree (fpa);
+        psFree (chip);
+        psFree (header);
+
+        skip_end();
+        ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+    }
+
+    {
+        note("test pmAstromReadWCS");
+        psMemId id = psMemGetId();
+
+        // construct a header with a simple set of WCS values
+        // convert to pmFPA components and check
+
+        psMetadata *header = psMetadataAlloc();
+
+        psMetadataAddStr (header, PS_LIST_TAIL, "CTYPE1", PS_META_REPLACE, "", "RA---TAN");
+        psMetadataAddStr (header, PS_LIST_TAIL, "CTYPE2", PS_META_REPLACE, "", "DEC--TAN");
+
+        // center coords (R,D)
+        psMetadataAddF32 (header, PS_LIST_TAIL, "CRVAL1", PS_META_REPLACE, "", 0.0);
+        psMetadataAddF32 (header, PS_LIST_TAIL, "CRVAL2", PS_META_REPLACE, "", 0.0);
+
+        // center coords (X,Y)
+        psMetadataAddF32 (header, PS_LIST_TAIL, "CRPIX1", PS_META_REPLACE, "", 10.0);
+        psMetadataAddF32 (header, PS_LIST_TAIL, "CRPIX2", PS_META_REPLACE, "", 10.0);
+
+        // degrees per pixel
+        psMetadataAddF32 (header, PS_LIST_TAIL, "CDELT1",  PS_META_REPLACE, "", 1.0/3600.0);
+        psMetadataAddF32 (header, PS_LIST_TAIL, "CDELT2",  PS_META_REPLACE, "", 1.0/3600.0);
+
+        // rotation matrix
+        psMetadataAddF32 (header, PS_LIST_TAIL, "PC001001", PS_META_REPLACE, "", 1.0);
+        psMetadataAddF32 (header, PS_LIST_TAIL, "PC001002", PS_META_REPLACE, "", 0.0);
+        psMetadataAddF32 (header, PS_LIST_TAIL, "PC002001", PS_META_REPLACE, "", 0.0);
+        psMetadataAddF32 (header, PS_LIST_TAIL, "PC002002", PS_META_REPLACE, "", 1.0);
+
+        pmFPA *fpa = pmFPAAlloc (NULL);
+        pmChip *chip = pmChipAlloc (NULL, "test");
+
+        // toFPA carries pixel scale (pixels per micron)
+        // toTPA carries plate scale (microns per arcsecond)
+        bool status = pmAstromReadWCS (fpa, chip, header, 10.0);
+        ok (status, "converted WCS keywords to FPA astrometry");
+        skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+        ok(fpa->toSky->type == PS_PROJ_TAN, "correct projection (TAN)");
+
+        // make these tests double
+        ok_float(fpa->toSky->R*PS_DEG_RAD, 0.0, "projection center RA %f", fpa->toSky->R*PS_DEG_RAD);
+        ok_float(fpa->toSky->R*PS_DEG_RAD, 0.0, "projection center DEC %f", fpa->toSky->R*PS_DEG_RAD);
+
+        ok_float(fpa->toSky->Xs, PM_RAD_DEG/3600.0/10.0, "projection X scale %f", fpa->toSky->Xs);
+        ok_float(fpa->toSky->Ys, PM_RAD_DEG/3600.0/10.0, "projection X scale %f", fpa->toSky->Ys);
+
+        ok_float(fpa->toTPA->x->coeff[1][0], 1.0, "TP scale (unity): %f", fpa->toTPA->x->coeff[1][0]);
+        ok_float(fpa->toTPA->y->coeff[0][1], 1.0, "TP scale (unity): %f", fpa->toTPA->x->coeff[1][0]);
+
+        ok_float(chip->toFPA->x->coeff[0][0], -100.0, "ref pixel X: %f", chip->toFPA->x->coeff[0][0]);
+        ok_float(chip->toFPA->y->coeff[0][0], -100.0, "ref pixel Y: %f", chip->toFPA->y->coeff[0][0]);
+
+        ok_float(chip->toFPA->x->coeff[1][0], 10.0, "CD1_1: %f", chip->toFPA->x->coeff[1][0]);
+        ok_float(chip->toFPA->x->coeff[0][1], 0.0, "CD1_2: %f", chip->toFPA->x->coeff[0][1]);
+        ok_float(chip->toFPA->y->coeff[1][0], 0.0, "CD2_1: %f", chip->toFPA->y->coeff[1][0]);
+        ok_float(chip->toFPA->y->coeff[0][1], 10.0, "CD2_2: %f", chip->toFPA->y->coeff[0][1]);
+
+        psFree (fpa);
+        psFree (chip);
+        psFree (header);
+
+        skip_end();
+        ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+    }
+
+    return exit_status();
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tap_pmAstrometryWCS_DVO.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tap_pmAstrometryWCS_DVO.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tap_pmAstrometryWCS_DVO.c	(revision 22157)
@@ -0,0 +1,528 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "tap.h"
+#include "pstap.h"
+
+# if (HAVE_KAPA)
+    # include "dvo.h"
+
+    psMetadata *WriteCoordsToHeader (Coords *coords);
+void test1(); // basic TAN projection,
+void test2(); // small rotation
+void test3(); // 2nd order term
+
+void test1in(); // basic TAN projection,
+void test2in(); // small rotation
+void test3in(); // 2nd order term
+
+void testA(); // alloc test
+void testB(); // alloc test
+
+int main (void)
+{
+    plan_tests(992);
+
+    note("pmAstrometryWCS tests compared with DVO coords routines");
+    note("this file tests pmAstromWCS <-> Header representations");
+
+    test1in();
+    test2in();
+    test3in();
+
+    test1();
+    test2();
+    test3();
+    return exit_status();
+}
+
+void test3in()
+{
+    note("test pmAstromWCStoHeader");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style coordinate system
+    Coords coords;
+    strcpy (coords.ctype, "RA---TAN");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = 0.0;
+    coords.crpix2 = 0.0;
+    coords.cdelt1 = 1.0/3600;
+    coords.cdelt2 = 1.0/3600;
+    coords.pc1_1  = 1.0;
+    coords.pc1_2  = 0.0;
+    coords.pc2_1  = 0.0;
+    coords.pc2_2  = 1.0;
+    coords.Npolyterms = 2;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+    coords.polyterms[0][0] = 0.01; // L vs X^2
+    coords.polyterms[2][1] = 0.01; // M vs Y^2
+
+    psMetadata *header1 = WriteCoordsToHeader(&coords);
+    pmAstromWCS *wcs1 = pmAstromWCSfromHeader(header1);
+    skip_start (wcs1 == NULL, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psMetadata *header2 = psMetadataAlloc();
+    pmAstromWCStoHeader(header2, wcs1);
+    pmAstromWCS *wcs2 = pmAstromWCSfromHeader(header2);
+
+    ok (wcs2 != NULL, "converted WCS keywords to WCS astrometry");
+    skip_start (wcs2 == NULL, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psSphere *sky1 = psSphereAlloc();
+    psSphere *sky2 = psSphereAlloc();
+    psPlane *chip = psPlaneAlloc();
+
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            chip->x = x;
+            chip->y = y;
+            pmAstromWCStoSky (sky1, wcs1, chip);
+            pmAstromWCStoSky (sky2, wcs2, chip);
+            while (sky1->r > 2*M_PI)
+                sky1->r -= 2*M_PI;
+            while (sky1->r <      0)
+                sky1->r += 2*M_PI;
+            while (sky2->r > 2*M_PI)
+                sky2->r -= 2*M_PI;
+            while (sky2->r <      0)
+                sky2->r += 2*M_PI;
+
+            ok_float(sky1->r*PS_DEG_RAD, sky2->r*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", sky1->r*PS_DEG_RAD, sky2->r*PS_DEG_RAD, sky1->r*PS_DEG_RAD - sky2->r*PS_DEG_RAD);
+            ok_float(sky1->d*PS_DEG_RAD, sky2->d*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", sky1->d*PS_DEG_RAD, sky2->d*PS_DEG_RAD, sky1->d*PS_DEG_RAD - sky2->d*PS_DEG_RAD);
+        }
+    }
+    psFree (sky1);
+    psFree (sky2);
+    psFree (chip);
+
+    skip_end();
+    psFree (wcs2);
+    psFree (header2);
+
+    skip_end();
+    psFree (wcs1);
+    psFree (header1);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+void test2in()
+{
+    note("test pmAstromWCStoHeader");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style coordinate system
+    Coords coords;
+    strcpy (coords.ctype, "RA---TAN");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = 0.0;
+    coords.crpix2 = 0.0;
+    coords.cdelt1 = 1.0/3600;
+    coords.cdelt2 = 1.0/3600;
+    coords.pc1_1  = 0.9;
+    coords.pc1_2  = -0.1;
+    coords.pc2_1  = 0.1;
+    coords.pc2_2  = 0.9;
+    coords.Npolyterms = 0;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+
+    psMetadata *header1 = WriteCoordsToHeader(&coords);
+    pmAstromWCS *wcs1 = pmAstromWCSfromHeader(header1);
+    skip_start (wcs1 == NULL, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psMetadata *header2 = psMetadataAlloc();
+    pmAstromWCStoHeader(header2, wcs1);
+    pmAstromWCS *wcs2 = pmAstromWCSfromHeader(header2);
+
+    ok (wcs2 != NULL, "converted WCS keywords to WCS astrometry");
+    skip_start (wcs2 == NULL, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psSphere *sky1 = psSphereAlloc();
+    psSphere *sky2 = psSphereAlloc();
+    psPlane *chip = psPlaneAlloc();
+
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            chip->x = x;
+            chip->y = y;
+            pmAstromWCStoSky (sky1, wcs1, chip);
+            pmAstromWCStoSky (sky2, wcs2, chip);
+            while (sky1->r > 2*M_PI)
+                sky1->r -= 2*M_PI;
+            while (sky1->r <      0)
+                sky1->r += 2*M_PI;
+            while (sky2->r > 2*M_PI)
+                sky2->r -= 2*M_PI;
+            while (sky2->r <      0)
+                sky2->r += 2*M_PI;
+
+            ok_float(sky1->r*PS_DEG_RAD, sky2->r*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", sky1->r*PS_DEG_RAD, sky2->r*PS_DEG_RAD, sky1->r*PS_DEG_RAD - sky2->r*PS_DEG_RAD);
+            ok_float(sky1->d*PS_DEG_RAD, sky2->d*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", sky1->d*PS_DEG_RAD, sky2->d*PS_DEG_RAD, sky1->d*PS_DEG_RAD - sky2->d*PS_DEG_RAD);
+        }
+    }
+    psFree (sky1);
+    psFree (sky2);
+    psFree (chip);
+
+    skip_end();
+    psFree (wcs2);
+    psFree (header2);
+
+    skip_end();
+    psFree (wcs1);
+    psFree (header1);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+void test1in()
+{
+    note("test pmAstromWCStoHeader");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style coordinate system
+    Coords coords;
+    strcpy (coords.ctype, "RA---TAN");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = 0.0;
+    coords.crpix2 = 0.0;
+    coords.cdelt1 = 1.0/3600;
+    coords.cdelt2 = 1.0/3600;
+    coords.pc1_1  = 1.0;
+    coords.pc1_2  = 0.0;
+    coords.pc2_1  = 0.0;
+    coords.pc2_2  = 1.0;
+    coords.Npolyterms = 0;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+
+    psMetadata *header1 = WriteCoordsToHeader(&coords);
+    pmAstromWCS *wcs1 = pmAstromWCSfromHeader(header1);
+    skip_start (wcs1 == NULL, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psMetadata *header2 = psMetadataAlloc();
+    pmAstromWCStoHeader(header2, wcs1);
+    pmAstromWCS *wcs2 = pmAstromWCSfromHeader(header2);
+
+    ok (wcs2 != NULL, "converted WCS keywords to WCS astrometry");
+    skip_start (wcs2 == NULL, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psSphere *sky1 = psSphereAlloc();
+    psSphere *sky2 = psSphereAlloc();
+    psPlane *chip = psPlaneAlloc();
+
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            chip->x = x;
+            chip->y = y;
+            pmAstromWCStoSky (sky1, wcs1, chip);
+            pmAstromWCStoSky (sky2, wcs2, chip);
+            while (sky1->r > 2*M_PI)
+                sky1->r -= 2*M_PI;
+            while (sky1->r <      0)
+                sky1->r += 2*M_PI;
+            while (sky2->r > 2*M_PI)
+                sky2->r -= 2*M_PI;
+            while (sky2->r <      0)
+                sky2->r += 2*M_PI;
+
+            ok_float(sky1->r*PS_DEG_RAD, sky2->r*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", sky1->r*PS_DEG_RAD, sky2->r*PS_DEG_RAD, sky1->r*PS_DEG_RAD - sky2->r*PS_DEG_RAD);
+            ok_float(sky1->d*PS_DEG_RAD, sky2->d*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", sky1->d*PS_DEG_RAD, sky2->d*PS_DEG_RAD, sky2->d*PS_DEG_RAD - sky2->d*PS_DEG_RAD);
+        }
+    }
+    psFree (sky1);
+    psFree (sky2);
+    psFree (chip);
+
+    skip_end();
+    psFree (wcs2);
+    psFree (header2);
+
+    skip_end();
+    psFree (wcs1);
+    psFree (header1);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+void test3 ()
+{
+    note("test pmAstromWCSfromHeader ");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style coordinate system
+    Coords coords;
+    strcpy (coords.ctype, "RA---TAN");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = 0.0;
+    coords.crpix2 = 0.0;
+    coords.cdelt1 = 1.0/3600;
+    coords.cdelt2 = 1.0/3600;
+    coords.pc1_1  = 1.0;
+    coords.pc1_2  = 0.0;
+    coords.pc2_1  = 0.0;
+    coords.pc2_2  = 1.0;
+    coords.Npolyterms = 2;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+    coords.polyterms[0][0] = 0.01; // L vs X^2
+    coords.polyterms[2][1] = 0.01; // M vs Y^2
+
+    psMetadata *header = WriteCoordsToHeader (&coords);
+    pmAstromWCS *wcs = pmAstromWCSfromHeader (header);
+
+    ok (wcs != NULL, "converted WCS keywords to WCS astrometry");
+    skip_start (wcs == NULL, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psSphere *sky = psSphereAlloc();
+    psPlane *chip = psPlaneAlloc();
+
+    double rDVO, dDVO;
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            XY_to_RD (&rDVO, &dDVO, x, y, &coords);
+            chip->x = x;
+            chip->y = y;
+            pmAstromWCStoSky (sky, wcs, chip);
+            while (sky->r > 2*M_PI)
+                sky->r -= 2*M_PI;
+            while (sky->r <      0)
+                sky->r += 2*M_PI;
+
+            ok_float(rDVO, sky->r*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", rDVO, sky->r*PS_DEG_RAD, rDVO - sky->r*PS_DEG_RAD);
+            ok_float(dDVO, sky->d*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", dDVO, sky->d*PS_DEG_RAD, dDVO - sky->d*PS_DEG_RAD);
+        }
+    }
+    psFree (sky);
+    psFree (chip);
+
+    skip_end();
+    psFree (wcs);
+    psFree (header);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+void test2 ()
+{
+    note("test pmAstromWCSfromHeader");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style coordinate system
+    Coords coords;
+    strcpy (coords.ctype, "RA---TAN");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = 0.0;
+    coords.crpix2 = 0.0;
+    coords.cdelt1 = 1.0/3600;
+    coords.cdelt2 = 1.0/3600;
+    coords.pc1_1  = 0.9;
+    coords.pc1_2  = -0.1;
+    coords.pc2_1  = 0.1;
+    coords.pc2_2  = 0.9;
+    coords.Npolyterms = 0;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+
+    psMetadata *header = WriteCoordsToHeader (&coords);
+    pmAstromWCS *wcs = pmAstromWCSfromHeader (header);
+
+    ok (wcs != NULL, "converted WCS keywords to WCS astrometry");
+    skip_start (wcs == NULL, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psSphere *sky = psSphereAlloc();
+    psPlane *chip = psPlaneAlloc();
+
+    double rDVO, dDVO;
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            XY_to_RD (&rDVO, &dDVO, x, y, &coords);
+            chip->x = x;
+            chip->y = y;
+            pmAstromWCStoSky (sky, wcs, chip);
+            while (sky->r > 2*M_PI)
+                sky->r -= 2*M_PI;
+            while (sky->r <      0)
+                sky->r += 2*M_PI;
+
+            ok_float(rDVO, sky->r*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", rDVO, sky->r*PS_DEG_RAD, rDVO - sky->r*PS_DEG_RAD);
+            ok_float(dDVO, sky->d*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", dDVO, sky->d*PS_DEG_RAD, dDVO - sky->d*PS_DEG_RAD);
+        }
+    }
+    psFree (sky);
+    psFree (chip);
+
+    skip_end();
+    psFree (wcs);
+    psFree (header);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+void test1()
+{
+    note("test pmAstromWCSfromHeader");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style coordinate system
+    Coords coords;
+    strcpy (coords.ctype, "RA---TAN");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = 0.0;
+    coords.crpix2 = 0.0;
+    coords.cdelt1 = 1.0/3600;
+    coords.cdelt2 = 1.0/3600;
+    coords.pc1_1  = 1.0;
+    coords.pc1_2  = 0.0;
+    coords.pc2_1  = 0.0;
+    coords.pc2_2  = 1.0;
+    coords.Npolyterms = 0;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+
+    psMetadata *header = WriteCoordsToHeader (&coords);
+    pmAstromWCS *wcs = pmAstromWCSfromHeader (header);
+
+    ok (wcs != NULL, "converted WCS keywords to WCS astrometry");
+    skip_start (wcs == NULL, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psSphere *sky = psSphereAlloc();
+    psPlane *chip = psPlaneAlloc();
+
+    double rDVO, dDVO;
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            XY_to_RD (&rDVO, &dDVO, x, y, &coords);
+            chip->x = x;
+            chip->y = y;
+            pmAstromWCStoSky (sky, wcs, chip);
+            while (sky->r > 2*M_PI)
+                sky->r -= 2*M_PI;
+            while (sky->r <      0)
+                sky->r += 2*M_PI;
+
+            ok_float(rDVO, sky->r*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", rDVO, sky->r*PS_DEG_RAD, rDVO - sky->r*PS_DEG_RAD);
+            ok_float(dDVO, sky->d*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", dDVO, sky->d*PS_DEG_RAD, dDVO - sky->d*PS_DEG_RAD);
+        }
+    }
+    psFree (sky);
+    psFree (chip);
+
+    skip_end();
+    psFree (wcs);
+    psFree (header);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+void testA()
+{
+    note("test coord allocs");
+    psMemId id = psMemGetId();
+
+    psPlane *chip = psPlaneAlloc();
+    psFree (chip);
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+void testB()
+{
+    note("test coord allocs");
+    psMemId id = psMemGetId();
+
+    psSphere *sky = psSphereAlloc();
+    psFree (sky);
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+psMetadata *WriteCoordsToHeader (Coords *coords)
+{
+
+    char name[16];
+
+    // construct a header using coords as the input
+    psMetadata *header = psMetadataAlloc();
+
+    sprintf (name, "RA--%s", &coords[0].ctype[4]);
+    psMetadataAddStr (header, PS_LIST_TAIL, "CTYPE1", PS_META_REPLACE, "", name);
+    sprintf (name, "DEC-%s", &coords[0].ctype[4]);
+    psMetadataAddStr (header, PS_LIST_TAIL, "CTYPE2", PS_META_REPLACE, "", name);
+
+    // center coords (R,D)
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRVAL1", PS_META_REPLACE, "", coords[0].crval1);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRVAL2", PS_META_REPLACE, "", coords[0].crval2);
+
+    // center coords (X,Y)
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRPIX1", PS_META_REPLACE, "", coords[0].crpix1);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRPIX2", PS_META_REPLACE, "", coords[0].crpix2);
+
+    // degrees per pixel
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CDELT1",  PS_META_REPLACE, "", coords[0].cdelt1);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CDELT2",  PS_META_REPLACE, "", coords[0].cdelt2);
+
+    // rotation matrix
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PC001001", PS_META_REPLACE, "", coords[0].pc1_1);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PC001002", PS_META_REPLACE, "", coords[0].pc1_2);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PC002001", PS_META_REPLACE, "", coords[0].pc2_1);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PC002002", PS_META_REPLACE, "", coords[0].pc2_2);
+
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X2Y0", PS_META_REPLACE, "", coords[0].polyterms[0][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X1Y1", PS_META_REPLACE, "", coords[0].polyterms[1][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X0Y2", PS_META_REPLACE, "", coords[0].polyterms[2][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X2Y0", PS_META_REPLACE, "", coords[0].polyterms[0][1]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X1Y1", PS_META_REPLACE, "", coords[0].polyterms[1][1]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X0Y2", PS_META_REPLACE, "", coords[0].polyterms[2][1]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X3Y0", PS_META_REPLACE, "", coords[0].polyterms[3][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X2Y1", PS_META_REPLACE, "", coords[0].polyterms[4][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X1Y2", PS_META_REPLACE, "", coords[0].polyterms[5][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X0Y3", PS_META_REPLACE, "", coords[0].polyterms[6][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X3Y0", PS_META_REPLACE, "", coords[0].polyterms[3][1]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X2Y1", PS_META_REPLACE, "", coords[0].polyterms[4][1]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X1Y2", PS_META_REPLACE, "", coords[0].polyterms[5][1]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X0Y3", PS_META_REPLACE, "", coords[0].polyterms[6][1]);
+
+    psMetadataAddS32 (header, PS_LIST_TAIL, "NPLYTERM", PS_META_REPLACE, "", coords[0].Npolyterms);
+
+    return header;
+}
+
+# else
+
+    int main (void)
+{
+    plan_tests(2);
+
+    ok(true, "Skipping tests: (libdvo not available)");
+    note("pmAstrometryWCS tests compared with DVO coords routines : SKIPPED (libdvo not available)");
+
+    return exit_status();
+}
+
+# endif
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tap_pmAstrometryWCS_DVO2.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tap_pmAstrometryWCS_DVO2.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tap_pmAstrometryWCS_DVO2.c	(revision 22157)
@@ -0,0 +1,630 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "tap.h"
+#include "pstap.h"
+
+# if (HAVE_KAPA)
+    # include "dvo.h"
+
+    psMetadata *WriteCoordsToHeader (Coords *coords);
+void test1(); // basic TAN projection,
+void test2(); // small rotation
+void test3(); // 2nd order term
+void test1x(); // basic TAN projection with central offset
+void test2x(); // small rotation with central offset
+void test3x(); // 2nd order term with central offset
+void test3inv(); // 2nd order term with central offset
+
+int main (void)
+{
+    plan_tests(1483);
+
+    note("pmAstromReadWCS tests compared with DVO coords routines");
+
+    test1();
+    test2();
+    test3();
+    test1x();
+    test2x();
+    test3x();
+    test3inv();
+
+    return exit_status();
+}
+
+void test1()
+{
+    note("test pmAstromReadWCS");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style coordinate system
+    Coords coords;
+    strcpy (coords.ctype, "RA---TAN");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = 0.0;
+    coords.crpix2 = 0.0;
+    coords.cdelt1 = 1.0/3600;
+    coords.cdelt2 = 1.0/3600;
+    coords.pc1_1  = 1.0;
+    coords.pc1_2  = 0.0;
+    coords.pc2_1  = 0.0;
+    coords.pc2_2  = 1.0;
+    coords.Npolyterms = 0;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+
+    psMetadata *header = WriteCoordsToHeader (&coords);
+    pmFPA *fpa = pmFPAAlloc (NULL);
+    pmChip *chip = pmChipAlloc (fpa, NULL);
+
+    bool status = pmAstromReadWCS (fpa, chip, header, PM_RAD_DEG*10.0/3600.0);
+    ok (status, "converted WCS keywords to WCS astrometry");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psPlane *onChip = psPlaneAlloc();
+    psPlane *onFPA = psPlaneAlloc();
+    psPlane *onTPA = psPlaneAlloc();
+    psSphere *onSky = psSphereAlloc();
+
+    double rDVO, dDVO;
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            XY_to_RD (&rDVO, &dDVO, x, y, &coords);
+
+            onChip->x = x;
+            onChip->y = y;
+
+            psPlaneTransformApply (onFPA, chip->toFPA, onChip);
+            psPlaneTransformApply (onTPA, fpa->toTPA, onFPA);
+            psDeproject (onSky, onTPA, fpa->toSky);
+
+            while (onSky->r > 2*M_PI)
+                onSky->r -= 2*M_PI;
+            while (onSky->r <      0)
+                onSky->r += 2*M_PI;
+
+            ok_float(rDVO, onSky->r*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", rDVO, onSky->r*PS_DEG_RAD, rDVO - onSky->r*PS_DEG_RAD);
+            ok_float(dDVO, onSky->d*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", dDVO, onSky->d*PS_DEG_RAD, dDVO - onSky->d*PS_DEG_RAD);
+        }
+    }
+    psFree (onSky);
+    psFree (onTPA);
+    psFree (onFPA);
+    psFree (onChip);
+
+    skip_end();
+    psFree (fpa);
+    psFree (chip);
+    psFree (header);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+void test2()
+{
+    note("test pmAstromReadWCS");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style coordinate system
+    Coords coords;
+    strcpy (coords.ctype, "RA---TAN");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = 0.0;
+    coords.crpix2 = 0.0;
+    coords.cdelt1 = 1.0/3600;
+    coords.cdelt2 = 1.0/3600;
+    coords.pc1_1  = 0.9;
+    coords.pc1_2  = 0.1;
+    coords.pc2_1  =-0.1;
+    coords.pc2_2  = 0.9;
+    coords.Npolyterms = 0;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+
+    psMetadata *header = WriteCoordsToHeader (&coords);
+    pmFPA *fpa = pmFPAAlloc (NULL);
+    pmChip *chip = pmChipAlloc (fpa, NULL);
+
+    bool status = pmAstromReadWCS (fpa, chip, header, PM_RAD_DEG*10.0/3600.0);
+    ok (status, "converted WCS keywords to WCS astrometry");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psPlane *onChip = psPlaneAlloc();
+    psPlane *onFPA = psPlaneAlloc();
+    psPlane *onTPA = psPlaneAlloc();
+    psSphere *onSky = psSphereAlloc();
+
+    double rDVO, dDVO;
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            XY_to_RD (&rDVO, &dDVO, x, y, &coords);
+
+            onChip->x = x;
+            onChip->y = y;
+
+            psPlaneTransformApply (onFPA, chip->toFPA, onChip);
+            psPlaneTransformApply (onTPA, fpa->toTPA, onFPA);
+            psDeproject (onSky, onTPA, fpa->toSky);
+
+            while (onSky->r > 2*M_PI)
+                onSky->r -= 2*M_PI;
+            while (onSky->r <      0)
+                onSky->r += 2*M_PI;
+
+            ok_float(rDVO, onSky->r*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", rDVO, onSky->r*PS_DEG_RAD, rDVO - onSky->r*PS_DEG_RAD);
+            ok_float(dDVO, onSky->d*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", dDVO, onSky->d*PS_DEG_RAD, dDVO - onSky->d*PS_DEG_RAD);
+        }
+    }
+    psFree (onSky);
+    psFree (onTPA);
+    psFree (onFPA);
+    psFree (onChip);
+
+    skip_end();
+    psFree (fpa);
+    psFree (chip);
+    psFree (header);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+void test3()
+{
+    note("test pmAstromReadWCS");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style coordinate system
+    Coords coords;
+    strcpy (coords.ctype, "RA---TAN");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = 0.0;
+    coords.crpix2 = 0.0;
+    coords.cdelt1 = 1.0/3600;
+    coords.cdelt2 = 1.0/3600;
+    coords.pc1_1  = 1.0;
+    coords.pc1_2  = 0.0;
+    coords.pc2_1  = 0.0;
+    coords.pc2_2  = 1.0;
+    coords.Npolyterms = 2;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+    coords.polyterms[0][0] = 0.01; // L vs X^2
+    coords.polyterms[2][1] = 0.01; // M vs Y^2
+
+
+    psMetadata *header = WriteCoordsToHeader (&coords);
+    pmFPA *fpa = pmFPAAlloc (NULL);
+    pmChip *chip = pmChipAlloc (fpa, NULL);
+
+    bool status = pmAstromReadWCS (fpa, chip, header, PM_RAD_DEG*10.0/3600.0);
+    ok (status, "converted WCS keywords to WCS astrometry");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psPlane *onChip = psPlaneAlloc();
+    psPlane *onFPA = psPlaneAlloc();
+    psPlane *onTPA = psPlaneAlloc();
+    psSphere *onSky = psSphereAlloc();
+
+    double rDVO, dDVO;
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            XY_to_RD (&rDVO, &dDVO, x, y, &coords);
+
+            onChip->x = x;
+            onChip->y = y;
+
+            psPlaneTransformApply (onFPA, chip->toFPA, onChip);
+            psPlaneTransformApply (onTPA, fpa->toTPA, onFPA);
+            psDeproject (onSky, onTPA, fpa->toSky);
+
+            while (onSky->r > 2*M_PI)
+                onSky->r -= 2*M_PI;
+            while (onSky->r <      0)
+                onSky->r += 2*M_PI;
+
+            ok_float(rDVO, onSky->r*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", rDVO, onSky->r*PS_DEG_RAD, rDVO - onSky->r*PS_DEG_RAD);
+            ok_float(dDVO, onSky->d*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", dDVO, onSky->d*PS_DEG_RAD, dDVO - onSky->d*PS_DEG_RAD);
+        }
+    }
+    psFree (onSky);
+    psFree (onTPA);
+    psFree (onFPA);
+    psFree (onChip);
+
+    skip_end();
+    psFree (fpa);
+    psFree (chip);
+    psFree (header);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+void test1x()
+{
+    note("test pmAstromReadWCS");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style coordinate system
+    Coords coords;
+    strcpy (coords.ctype, "RA---TAN");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = +50.0;
+    coords.crpix2 = -20.0;
+    coords.cdelt1 = 1.0/3600;
+    coords.cdelt2 = 1.0/3600;
+    coords.pc1_1  = 1.0;
+    coords.pc1_2  = 0.0;
+    coords.pc2_1  = 0.0;
+    coords.pc2_2  = 1.0;
+    coords.Npolyterms = 0;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+
+    psMetadata *header = WriteCoordsToHeader (&coords);
+    pmFPA *fpa = pmFPAAlloc (NULL);
+    pmChip *chip = pmChipAlloc (fpa, NULL);
+
+    bool status = pmAstromReadWCS (fpa, chip, header, PM_RAD_DEG*10.0/3600.0);
+    ok (status, "converted WCS keywords to WCS astrometry");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psPlane *onChip = psPlaneAlloc();
+    psPlane *onFPA = psPlaneAlloc();
+    psPlane *onTPA = psPlaneAlloc();
+    psSphere *onSky = psSphereAlloc();
+
+    double rDVO, dDVO;
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            XY_to_RD (&rDVO, &dDVO, x, y, &coords);
+
+            onChip->x = x;
+            onChip->y = y;
+
+            psPlaneTransformApply (onFPA, chip->toFPA, onChip);
+            psPlaneTransformApply (onTPA, fpa->toTPA, onFPA);
+            psDeproject (onSky, onTPA, fpa->toSky);
+
+            while (onSky->r > 2*M_PI)
+                onSky->r -= 2*M_PI;
+            while (onSky->r <      0)
+                onSky->r += 2*M_PI;
+
+            ok_float(rDVO, onSky->r*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", rDVO, onSky->r*PS_DEG_RAD, rDVO - onSky->r*PS_DEG_RAD);
+            ok_float(dDVO, onSky->d*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", dDVO, onSky->d*PS_DEG_RAD, dDVO - onSky->d*PS_DEG_RAD);
+        }
+    }
+    psFree (onSky);
+    psFree (onTPA);
+    psFree (onFPA);
+    psFree (onChip);
+
+    skip_end();
+    psFree (fpa);
+    psFree (chip);
+    psFree (header);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+void test2x()
+{
+    note("test pmAstromReadWCS");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style coordinate system
+    Coords coords;
+    strcpy (coords.ctype, "RA---TAN");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = +50.0;
+    coords.crpix2 = -20.0;
+    coords.cdelt1 = 1.0/3600;
+    coords.cdelt2 = 1.0/3600;
+    coords.pc1_1  = 0.9;
+    coords.pc1_2  = 0.1;
+    coords.pc2_1  =-0.1;
+    coords.pc2_2  = 0.9;
+    coords.Npolyterms = 0;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+
+    psMetadata *header = WriteCoordsToHeader (&coords);
+    pmFPA *fpa = pmFPAAlloc (NULL);
+    pmChip *chip = pmChipAlloc (fpa, NULL);
+
+    bool status = pmAstromReadWCS (fpa, chip, header, PM_RAD_DEG*10.0/3600.0);
+    ok (status, "converted WCS keywords to WCS astrometry");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psPlane *onChip = psPlaneAlloc();
+    psPlane *onFPA = psPlaneAlloc();
+    psPlane *onTPA = psPlaneAlloc();
+    psSphere *onSky = psSphereAlloc();
+
+    double rDVO, dDVO;
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            XY_to_RD (&rDVO, &dDVO, x, y, &coords);
+
+            onChip->x = x;
+            onChip->y = y;
+
+            psPlaneTransformApply (onFPA, chip->toFPA, onChip);
+            psPlaneTransformApply (onTPA, fpa->toTPA, onFPA);
+            psDeproject (onSky, onTPA, fpa->toSky);
+
+            while (onSky->r > 2*M_PI)
+                onSky->r -= 2*M_PI;
+            while (onSky->r <      0)
+                onSky->r += 2*M_PI;
+
+            ok_float(rDVO, onSky->r*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", rDVO, onSky->r*PS_DEG_RAD, rDVO - onSky->r*PS_DEG_RAD);
+            ok_float(dDVO, onSky->d*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", dDVO, onSky->d*PS_DEG_RAD, dDVO - onSky->d*PS_DEG_RAD);
+        }
+    }
+    psFree (onSky);
+    psFree (onTPA);
+    psFree (onFPA);
+    psFree (onChip);
+
+    skip_end();
+    psFree (fpa);
+    psFree (chip);
+    psFree (header);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+void test3x()
+{
+    note("test pmAstromReadWCS");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style coordinate system
+    Coords coords;
+    strcpy (coords.ctype, "RA---TAN");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = +50.0;
+    coords.crpix2 = -20.0;
+    coords.cdelt1 = 1.0/3600;
+    coords.cdelt2 = 1.0/3600;
+    coords.pc1_1  = 1.0;
+    coords.pc1_2  = 0.0;
+    coords.pc2_1  = 0.0;
+    coords.pc2_2  = 1.0;
+    coords.Npolyterms = 2;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+    coords.polyterms[0][0] = 0.01; // L vs X^2
+    coords.polyterms[2][1] = 0.01; // M vs Y^2
+
+
+    psMetadata *header = WriteCoordsToHeader (&coords);
+    pmFPA *fpa = pmFPAAlloc (NULL);
+    pmChip *chip = pmChipAlloc (fpa, NULL);
+
+    bool status = pmAstromReadWCS (fpa, chip, header, PM_RAD_DEG*10.0/3600.0);
+    ok (status, "converted WCS keywords to WCS astrometry");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psPlane *onChip = psPlaneAlloc();
+    psPlane *onFPA = psPlaneAlloc();
+    psPlane *onTPA = psPlaneAlloc();
+    psSphere *onSky = psSphereAlloc();
+
+    double rDVO, dDVO;
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            XY_to_RD (&rDVO, &dDVO, x, y, &coords);
+
+            onChip->x = x;
+            onChip->y = y;
+
+            psPlaneTransformApply (onFPA, chip->toFPA, onChip);
+            psPlaneTransformApply (onTPA, fpa->toTPA, onFPA);
+            psDeproject (onSky, onTPA, fpa->toSky);
+
+            while (onSky->r > 2*M_PI)
+                onSky->r -= 2*M_PI;
+            while (onSky->r <      0)
+                onSky->r += 2*M_PI;
+
+            ok_float(rDVO, onSky->r*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", rDVO, onSky->r*PS_DEG_RAD, rDVO - onSky->r*PS_DEG_RAD);
+            ok_float(dDVO, onSky->d*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", dDVO, onSky->d*PS_DEG_RAD, dDVO - onSky->d*PS_DEG_RAD);
+        }
+    }
+    psFree (onSky);
+    psFree (onTPA);
+    psFree (onFPA);
+    psFree (onChip);
+
+    skip_end();
+    psFree (fpa);
+    psFree (chip);
+    psFree (header);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+void test3inv()
+{
+    note("test the inversion of the non-linear polynomial for toFPA -> fromFPA in pmAstromReadWCS");
+    note("note that the tolerance for these tests are rather loose");
+    note("a 2nd order polynomial is not a great approximate to 1 over a 2nd order polynomial");
+    note("unless the non-linear terms are quite small");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style coordinate system
+    Coords coords;
+    strcpy (coords.ctype, "RA---TAN");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = +50.0;
+    coords.crpix2 = -20.0;
+    coords.cdelt1 = 1.0/3600;
+    coords.cdelt2 = 1.0/3600;
+    coords.pc1_1  = 1.0;
+    coords.pc1_2  = 0.0;
+    coords.pc2_1  = 0.0;
+    coords.pc2_2  = 1.0;
+    coords.Npolyterms = 2;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+    coords.polyterms[0][0] = 0.01; // L vs X^2
+    coords.polyterms[2][1] = 0.01; // M vs Y^2
+
+
+    psMetadata *header = WriteCoordsToHeader (&coords);
+    pmFPA *fpa = pmFPAAlloc (NULL);
+    pmChip *chip = pmChipAlloc (fpa, NULL);
+
+    bool status = pmAstromReadWCS (fpa, chip, header, PM_RAD_DEG*10.0/3600.0);
+    ok (status, "converted WCS keywords to WCS astrometry");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psPlane *aChip  = psPlaneAlloc();
+    psPlane *aFPA   = psPlaneAlloc();
+    psPlane *aTPA   = psPlaneAlloc();
+    psPlane *bChip  = psPlaneAlloc();
+    psPlane *bFPA   = psPlaneAlloc();
+    psPlane *bTPA   = psPlaneAlloc();
+    psSphere *onSky = psSphereAlloc();
+
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            // convert up to sky
+            aChip->x = x;
+            aChip->y = y;
+
+            psPlaneTransformApply (aFPA, chip->toFPA, aChip);
+            psPlaneTransformApply (aTPA, fpa->toTPA, aFPA);
+            psDeproject (onSky, aTPA, fpa->toSky);
+
+            while (onSky->r > 2*M_PI)
+                onSky->r -= 2*M_PI;
+            while (onSky->r <      0)
+                onSky->r += 2*M_PI;
+
+            psProject (bTPA, onSky, fpa->toSky);
+            psPlaneTransformApply (bFPA, fpa->fromTPA, bTPA);
+            psPlaneTransformApply (bChip, chip->fromFPA, bFPA);
+
+            // calculate appropriate tol values as f(x,y)
+            ok_float_tol(aChip->x, bChip->x, 1.0, "coordinate match: %f vs %f (delta = %f)", aChip->x, bChip->x, aChip->x - bChip->x);
+            ok_float_tol(aChip->y, bChip->y, 1.0, "coordinate match: %f vs %f (delta = %f)", aChip->y, bChip->y, aChip->y - bChip->y);
+
+            ok_float(aFPA->x, bFPA->x, "coordinate match: %f vs %f (delta = %f)", aFPA->x, bFPA->x, aFPA->x - bFPA->x);
+            ok_float(aFPA->y, bFPA->y, "coordinate match: %f vs %f (delta = %f)", aFPA->y, bFPA->y, aFPA->y - bFPA->y);
+
+            // in this example, TPA coordinates are 10 arcsec/mm; the tol. below represent 1nano-arcsec
+            ok_float_tol(aTPA->x, bTPA->x, 1e-10, "coordinate match: %f vs %f (delta = %g)", aTPA->x, bTPA->x, aTPA->x - bTPA->x);
+            ok_float_tol(aTPA->y, bTPA->y, 1e-10, "coordinate match: %f vs %f (delta = %g)", aTPA->y, bTPA->y, aTPA->y - bTPA->y);
+        }
+    }
+    psFree (onSky);
+    psFree (aTPA);
+    psFree (aFPA);
+    psFree (aChip);
+    psFree (bTPA);
+    psFree (bFPA);
+    psFree (bChip);
+
+    skip_end();
+    psFree (fpa);
+    psFree (chip);
+    psFree (header);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+psMetadata *WriteCoordsToHeader (Coords *coords)
+{
+
+    char name[16];
+
+    // construct a header using coords as the input
+    psMetadata *header = psMetadataAlloc();
+
+    sprintf (name, "RA--%s", &coords[0].ctype[4]);
+    psMetadataAddStr (header, PS_LIST_TAIL, "CTYPE1", PS_META_REPLACE, "", name);
+    sprintf (name, "DEC-%s", &coords[0].ctype[4]);
+    psMetadataAddStr (header, PS_LIST_TAIL, "CTYPE2", PS_META_REPLACE, "", name);
+
+    // center coords (R,D)
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRVAL1", PS_META_REPLACE, "", coords[0].crval1);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRVAL2", PS_META_REPLACE, "", coords[0].crval2);
+
+    // center coords (X,Y)
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRPIX1", PS_META_REPLACE, "", coords[0].crpix1);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRPIX2", PS_META_REPLACE, "", coords[0].crpix2);
+
+    // degrees per pixel
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CDELT1",  PS_META_REPLACE, "", coords[0].cdelt1);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CDELT2",  PS_META_REPLACE, "", coords[0].cdelt2);
+
+    // rotation matrix
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PC001001", PS_META_REPLACE, "", coords[0].pc1_1);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PC001002", PS_META_REPLACE, "", coords[0].pc1_2);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PC002001", PS_META_REPLACE, "", coords[0].pc2_1);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PC002002", PS_META_REPLACE, "", coords[0].pc2_2);
+
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X2Y0", PS_META_REPLACE, "", coords[0].polyterms[0][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X1Y1", PS_META_REPLACE, "", coords[0].polyterms[1][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X0Y2", PS_META_REPLACE, "", coords[0].polyterms[2][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X2Y0", PS_META_REPLACE, "", coords[0].polyterms[0][1]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X1Y1", PS_META_REPLACE, "", coords[0].polyterms[1][1]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X0Y2", PS_META_REPLACE, "", coords[0].polyterms[2][1]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X3Y0", PS_META_REPLACE, "", coords[0].polyterms[3][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X2Y1", PS_META_REPLACE, "", coords[0].polyterms[4][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X1Y2", PS_META_REPLACE, "", coords[0].polyterms[5][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X0Y3", PS_META_REPLACE, "", coords[0].polyterms[6][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X3Y0", PS_META_REPLACE, "", coords[0].polyterms[3][1]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X2Y1", PS_META_REPLACE, "", coords[0].polyterms[4][1]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X1Y2", PS_META_REPLACE, "", coords[0].polyterms[5][1]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X0Y3", PS_META_REPLACE, "", coords[0].polyterms[6][1]);
+
+    psMetadataAddS32 (header, PS_LIST_TAIL, "NPLYTERM", PS_META_REPLACE, "", coords[0].Npolyterms);
+
+    return header;
+}
+
+# else
+
+    int main (void)
+{
+    plan_tests(2);
+
+    ok(true, "Skipping tests: (libdvo not available)");
+    note("pmAstrometryWCS tests compared with DVO coords routines : SKIPPED (libdvo not available)");
+
+    return exit_status();
+}
+
+# endif
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tap_pmAstrometryWCS_DVO3.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tap_pmAstrometryWCS_DVO3.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tap_pmAstrometryWCS_DVO3.c	(revision 22157)
@@ -0,0 +1,655 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "tap.h"
+#include "pstap.h"
+
+# if (HAVE_KAPA)
+    # include "dvo.h"
+
+    psMetadata *WriteCoordsToHeader (Coords *coords);
+void test1(); // basic TAN projection,
+void test2(); // small rotation
+void test3(); // 2nd order term
+void test1x(); // basic TAN projection with central offset
+void test2x(); // small rotation with central offset
+void test3x(); // 2nd order term with central offset
+
+int main (void)
+{
+    plan_tests(991);
+
+    note("pmAstromWriteWCS tests compared with DVO coords routines");
+
+    test1();
+    test2();
+    test3();
+    test1x();
+    test2x();
+    test3x();
+
+    return exit_status();
+}
+
+void test1()
+{
+    note("test pmAstromReadWCS");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style coordinate system
+    Coords coords;
+    strcpy (coords.ctype, "RA---TAN");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = 0.0;
+    coords.crpix2 = 0.0;
+    coords.cdelt1 = 1.0/3600.0;
+    coords.cdelt2 = 1.0/3600.0;
+    coords.pc1_1  = 1.0;
+    coords.pc1_2  = 0.0;
+    coords.pc2_1  = 0.0;
+    coords.pc2_2  = 1.0;
+    coords.Npolyterms = 0;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+
+    psMetadata *header1 = WriteCoordsToHeader (&coords);
+    pmFPA *fpa = pmFPAAlloc (NULL);
+    pmChip *chip = pmChipAlloc (fpa, NULL);
+
+    bool status = pmAstromReadWCS (fpa, chip, header1, 10.0);
+
+    ok (status, "converted WCS keywords to WCS astrometry");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psMetadata *header2 = psMetadataAlloc();
+    status = pmAstromWriteWCS (header2, fpa, chip, 0.001);
+    pmAstromWCS *wcs = pmAstromWCSfromHeader(header2);
+
+    psPlane  *aChip = psPlaneAlloc();
+    psPlane  *aFPA = psPlaneAlloc();
+    psPlane  *aTPA = psPlaneAlloc();
+    psSphere *aSky = psSphereAlloc();
+
+    psPlane  *bChip = psPlaneAlloc();
+    psSphere *bSky = psSphereAlloc();
+
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            aChip->x = x;
+            aChip->y = y;
+            bChip->x = x;
+            bChip->y = y;
+
+            psPlaneTransformApply (aFPA, chip->toFPA, aChip);
+            psPlaneTransformApply (aTPA, fpa->toTPA, aFPA);
+            psDeproject (aSky, aTPA, fpa->toSky);
+
+            pmAstromWCStoSky (bSky, wcs, bChip);
+
+            while (aSky->r > 2*M_PI)
+                aSky->r -= 2*M_PI;
+            while (aSky->r <      0)
+                aSky->r += 2*M_PI;
+            while (bSky->r > 2*M_PI)
+                bSky->r -= 2*M_PI;
+            while (bSky->r <      0)
+                bSky->r += 2*M_PI;
+
+            ok_float(aSky->r*PS_DEG_RAD, bSky->r*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", aSky->r*PS_DEG_RAD, bSky->r*PS_DEG_RAD, aSky->r*PS_DEG_RAD - bSky->r*PS_DEG_RAD);
+            ok_float(aSky->d*PS_DEG_RAD, bSky->d*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", aSky->d*PS_DEG_RAD, bSky->d*PS_DEG_RAD, aSky->d*PS_DEG_RAD - bSky->d*PS_DEG_RAD);
+        }
+    }
+    psFree (aSky);
+    psFree (aTPA);
+    psFree (aFPA);
+    psFree (aChip);
+
+    psFree (bSky);
+    psFree (bChip);
+
+    psFree (wcs);
+    psFree (header2);
+
+    skip_end();
+    psFree (fpa);
+    psFree (chip);
+    psFree (header1);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+void test2()
+{
+    note("test pmAstromReadWCS");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style coordinate system
+    Coords coords;
+    strcpy (coords.ctype, "RA---TAN");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = 0.0;
+    coords.crpix2 = 0.0;
+    coords.cdelt1 = 1.0/3600.0;
+    coords.cdelt2 = 1.0/3600.0;
+    coords.pc1_1  = 0.9;
+    coords.pc1_2  = 0.1;
+    coords.pc2_1  = -0.1;
+    coords.pc2_2  = 0.9;
+    coords.Npolyterms = 0;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+
+    psMetadata *header1 = WriteCoordsToHeader (&coords);
+
+    pmFPA *fpa = pmFPAAlloc (NULL);
+    pmChip *chip = pmChipAlloc (fpa, NULL);
+
+    bool status = pmAstromReadWCS (fpa, chip, header1, 10.0);
+
+    ok (status, "converted WCS keywords to WCS astrometry");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psMetadata *header2 = psMetadataAlloc();
+    status = pmAstromWriteWCS (header2, fpa, chip, 0.001);
+
+    pmAstromWCS *wcs = pmAstromWCSfromHeader(header2);
+
+    psPlane  *aChip = psPlaneAlloc();
+    psPlane  *aFPA = psPlaneAlloc();
+    psPlane  *aTPA = psPlaneAlloc();
+    psSphere *aSky = psSphereAlloc();
+
+    psPlane  *bChip = psPlaneAlloc();
+    psSphere *bSky = psSphereAlloc();
+
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            aChip->x = x;
+            aChip->y = y;
+            bChip->x = x;
+            bChip->y = y;
+
+            psPlaneTransformApply (aFPA, chip->toFPA, aChip);
+            psPlaneTransformApply (aTPA, fpa->toTPA, aFPA);
+            psDeproject (aSky, aTPA, fpa->toSky);
+
+            pmAstromWCStoSky (bSky, wcs, bChip);
+
+            while (aSky->r > 2*M_PI)
+                aSky->r -= 2*M_PI;
+            while (aSky->r <      0)
+                aSky->r += 2*M_PI;
+            while (bSky->r > 2*M_PI)
+                bSky->r -= 2*M_PI;
+            while (bSky->r <      0)
+                bSky->r += 2*M_PI;
+
+            ok_float(aSky->r*PS_DEG_RAD, bSky->r*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", aSky->r*PS_DEG_RAD, bSky->r*PS_DEG_RAD, aSky->r*PS_DEG_RAD - bSky->r*PS_DEG_RAD);
+            ok_float(aSky->d*PS_DEG_RAD, bSky->d*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", aSky->d*PS_DEG_RAD, bSky->d*PS_DEG_RAD, aSky->d*PS_DEG_RAD - bSky->d*PS_DEG_RAD);
+        }
+    }
+    psFree (aSky);
+    psFree (aTPA);
+    psFree (aFPA);
+    psFree (aChip);
+
+    psFree (bSky);
+    psFree (bChip);
+
+    psFree (wcs);
+    psFree (header2);
+
+    skip_end();
+    psFree (fpa);
+    psFree (chip);
+    psFree (header1);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+void test3()
+{
+    note("test pmAstromReadWCS");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style coordinate system
+    Coords coords;
+    strcpy (coords.ctype, "RA---TAN");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = 0.0;
+    coords.crpix2 = 0.0;
+    coords.cdelt1 = 1.0/3600.0;
+    coords.cdelt2 = 1.0/3600.0;
+    coords.pc1_1  = 1.0;
+    coords.pc1_2  = 0.0;
+    coords.pc2_1  = 0.0;
+    coords.pc2_2  = 1.0;
+    coords.Npolyterms = 2;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+    coords.polyterms[0][0] = 0.01; // L vs X^2
+    coords.polyterms[2][1] = 0.01; // M vs Y^2
+
+    psMetadata *header1 = WriteCoordsToHeader (&coords);
+    pmFPA *fpa = pmFPAAlloc (NULL);
+    pmChip *chip = pmChipAlloc (fpa, NULL);
+
+    bool status = pmAstromReadWCS (fpa, chip, header1, 10.0);
+
+    ok (status, "converted WCS keywords to WCS astrometry");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psMetadata *header2 = psMetadataAlloc();
+    status = pmAstromWriteWCS (header2, fpa, chip, 0.001);
+
+    pmAstromWCS *wcs = pmAstromWCSfromHeader(header2);
+
+    psPlane  *aChip = psPlaneAlloc();
+    psPlane  *aFPA = psPlaneAlloc();
+    psPlane  *aTPA = psPlaneAlloc();
+    psSphere *aSky = psSphereAlloc();
+
+    psPlane  *bChip = psPlaneAlloc();
+    psSphere *bSky = psSphereAlloc();
+
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            aChip->x = x;
+            aChip->y = y;
+            bChip->x = x;
+            bChip->y = y;
+
+            psPlaneTransformApply (aFPA, chip->toFPA, aChip);
+            psPlaneTransformApply (aTPA, fpa->toTPA, aFPA);
+            psDeproject (aSky, aTPA, fpa->toSky);
+
+            pmAstromWCStoSky (bSky, wcs, bChip);
+
+            while (aSky->r > 2*M_PI)
+                aSky->r -= 2*M_PI;
+            while (aSky->r <      0)
+                aSky->r += 2*M_PI;
+            while (bSky->r > 2*M_PI)
+                bSky->r -= 2*M_PI;
+            while (bSky->r <      0)
+                bSky->r += 2*M_PI;
+
+            ok_float(aSky->r*PS_DEG_RAD, bSky->r*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", aSky->r*PS_DEG_RAD, bSky->r*PS_DEG_RAD, aSky->r*PS_DEG_RAD - bSky->r*PS_DEG_RAD);
+            ok_float(aSky->d*PS_DEG_RAD, bSky->d*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", aSky->d*PS_DEG_RAD, bSky->d*PS_DEG_RAD, aSky->d*PS_DEG_RAD - bSky->d*PS_DEG_RAD);
+        }
+    }
+    psFree (aSky);
+    psFree (aTPA);
+    psFree (aFPA);
+    psFree (aChip);
+
+    psFree (bSky);
+    psFree (bChip);
+
+    psFree (wcs);
+    psFree (header2);
+
+    skip_end();
+    psFree (fpa);
+    psFree (chip);
+    psFree (header1);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+void test1x()
+{
+    note("test pmAstromReadWCS");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style coordinate system
+    Coords coords;
+    strcpy (coords.ctype, "RA---TAN");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = 20.0;
+    coords.crpix2 = 50.0;
+    coords.cdelt1 = 1.0/3600.0;
+    coords.cdelt2 = 1.0/3600.0;
+    coords.pc1_1  = 1.0;
+    coords.pc1_2  = 0.0;
+    coords.pc2_1  = 0.0;
+    coords.pc2_2  = 1.0;
+    coords.Npolyterms = 0;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+
+    psMetadata *header1 = WriteCoordsToHeader (&coords);
+    pmFPA *fpa = pmFPAAlloc (NULL);
+    pmChip *chip = pmChipAlloc (fpa, NULL);
+
+    bool status = pmAstromReadWCS (fpa, chip, header1, 10.0);
+
+    ok (status, "converted WCS keywords to WCS astrometry");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psMetadata *header2 = psMetadataAlloc();
+    status = pmAstromWriteWCS (header2, fpa, chip, 0.001);
+    pmAstromWCS *wcs = pmAstromWCSfromHeader(header2);
+
+    psPlane  *aChip = psPlaneAlloc();
+    psPlane  *aFPA = psPlaneAlloc();
+    psPlane  *aTPA = psPlaneAlloc();
+    psSphere *aSky = psSphereAlloc();
+
+    psPlane  *bChip = psPlaneAlloc();
+    psSphere *bSky = psSphereAlloc();
+
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            aChip->x = x;
+            aChip->y = y;
+            bChip->x = x;
+            bChip->y = y;
+
+            psPlaneTransformApply (aFPA, chip->toFPA, aChip);
+            psPlaneTransformApply (aTPA, fpa->toTPA, aFPA);
+            psDeproject (aSky, aTPA, fpa->toSky);
+
+            pmAstromWCStoSky (bSky, wcs, bChip);
+
+            while (aSky->r > 2*M_PI)
+                aSky->r -= 2*M_PI;
+            while (aSky->r <      0)
+                aSky->r += 2*M_PI;
+            while (bSky->r > 2*M_PI)
+                bSky->r -= 2*M_PI;
+            while (bSky->r <      0)
+                bSky->r += 2*M_PI;
+
+            ok_float(aSky->r*PS_DEG_RAD, bSky->r*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", aSky->r*PS_DEG_RAD, bSky->r*PS_DEG_RAD, aSky->r*PS_DEG_RAD - bSky->r*PS_DEG_RAD);
+            ok_float(aSky->d*PS_DEG_RAD, bSky->d*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", aSky->d*PS_DEG_RAD, bSky->d*PS_DEG_RAD, aSky->d*PS_DEG_RAD - bSky->d*PS_DEG_RAD);
+        }
+    }
+    psFree (aSky);
+    psFree (aTPA);
+    psFree (aFPA);
+    psFree (aChip);
+
+    psFree (bSky);
+    psFree (bChip);
+
+    psFree (wcs);
+    psFree (header2);
+
+    skip_end();
+    psFree (fpa);
+    psFree (chip);
+    psFree (header1);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+void test2x()
+{
+    note("test pmAstromReadWCS");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style coordinate system
+    Coords coords;
+    strcpy (coords.ctype, "RA---TAN");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = 50.0;
+    coords.crpix2 = -20.0;
+    coords.cdelt1 = 1.0/3600.0;
+    coords.cdelt2 = 1.0/3600.0;
+    coords.pc1_1  = 0.9;
+    coords.pc1_2  = 0.1;
+    coords.pc2_1  = -0.1;
+    coords.pc2_2  = 0.9;
+    coords.Npolyterms = 0;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+
+    psMetadata *header1 = WriteCoordsToHeader (&coords);
+
+    pmFPA *fpa = pmFPAAlloc (NULL);
+    pmChip *chip = pmChipAlloc (fpa, NULL);
+
+    bool status = pmAstromReadWCS (fpa, chip, header1, 10.0);
+
+    ok (status, "converted WCS keywords to WCS astrometry");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psMetadata *header2 = psMetadataAlloc();
+    status = pmAstromWriteWCS (header2, fpa, chip, 0.001);
+
+    pmAstromWCS *wcs = pmAstromWCSfromHeader(header2);
+
+    psPlane  *aChip = psPlaneAlloc();
+    psPlane  *aFPA = psPlaneAlloc();
+    psPlane  *aTPA = psPlaneAlloc();
+    psSphere *aSky = psSphereAlloc();
+
+    psPlane  *bChip = psPlaneAlloc();
+    psSphere *bSky = psSphereAlloc();
+
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            aChip->x = x;
+            aChip->y = y;
+            bChip->x = x;
+            bChip->y = y;
+
+            psPlaneTransformApply (aFPA, chip->toFPA, aChip);
+            psPlaneTransformApply (aTPA, fpa->toTPA, aFPA);
+            psDeproject (aSky, aTPA, fpa->toSky);
+
+            pmAstromWCStoSky (bSky, wcs, bChip);
+
+            while (aSky->r > 2*M_PI)
+                aSky->r -= 2*M_PI;
+            while (aSky->r <      0)
+                aSky->r += 2*M_PI;
+            while (bSky->r > 2*M_PI)
+                bSky->r -= 2*M_PI;
+            while (bSky->r <      0)
+                bSky->r += 2*M_PI;
+
+            ok_float(aSky->r*PS_DEG_RAD, bSky->r*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", aSky->r*PS_DEG_RAD, bSky->r*PS_DEG_RAD, aSky->r*PS_DEG_RAD - bSky->r*PS_DEG_RAD);
+            ok_float(aSky->d*PS_DEG_RAD, bSky->d*PS_DEG_RAD, "coordinate match: %f vs %f (delta = %f)", aSky->d*PS_DEG_RAD, bSky->d*PS_DEG_RAD, aSky->d*PS_DEG_RAD - bSky->d*PS_DEG_RAD);
+        }
+    }
+    psFree (aSky);
+    psFree (aTPA);
+    psFree (aFPA);
+    psFree (aChip);
+
+    psFree (bSky);
+    psFree (bChip);
+
+    psFree (wcs);
+    psFree (header2);
+
+    skip_end();
+    psFree (fpa);
+    psFree (chip);
+    psFree (header1);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+void test3x()
+{
+    note("test pmAstromReadWCS");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style coordinate system
+    Coords coords;
+    strcpy (coords.ctype, "RA---TAN");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = 20.0;
+    coords.crpix2 = 50.0;
+    coords.cdelt1 = 1.0/3600.0;
+    coords.cdelt2 = 1.0/3600.0;
+    coords.pc1_1  = 1.0;
+    coords.pc1_2  = 0.0;
+    coords.pc2_1  = 0.0;
+    coords.pc2_2  = 1.0;
+    coords.Npolyterms = 2;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+    coords.polyterms[0][0] = 0.01; // L vs X^2
+    coords.polyterms[2][1] = 0.01; // M vs Y^2
+
+    psMetadata *header1 = WriteCoordsToHeader (&coords);
+    pmFPA *fpa = pmFPAAlloc (NULL);
+    pmChip *chip = pmChipAlloc (fpa, NULL);
+
+    bool status = pmAstromReadWCS (fpa, chip, header1, 10.0);
+
+    ok (status, "converted WCS keywords to WCS astrometry");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psMetadata *header2 = psMetadataAlloc();
+    status = pmAstromWriteWCS (header2, fpa, chip, 0.00001);
+
+    pmAstromWCS *wcs = pmAstromWCSfromHeader(header2);
+
+    psPlane  *aChip = psPlaneAlloc();
+    psPlane  *aFPA = psPlaneAlloc();
+    psPlane  *aTPA = psPlaneAlloc();
+    psSphere *aSky = psSphereAlloc();
+
+    psPlane  *bChip = psPlaneAlloc();
+    psSphere *bSky = psSphereAlloc();
+
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            aChip->x = x;
+            aChip->y = y;
+            bChip->x = x;
+            bChip->y = y;
+
+            psPlaneTransformApply (aFPA, chip->toFPA, aChip);
+            psPlaneTransformApply (aTPA, fpa->toTPA, aFPA);
+            psDeproject (aSky, aTPA, fpa->toSky);
+
+            pmAstromWCStoSky (bSky, wcs, bChip);
+
+            while (aSky->r > 2*M_PI)
+                aSky->r -= 2*M_PI;
+            while (aSky->r <      0)
+                aSky->r += 2*M_PI;
+            while (bSky->r > 2*M_PI)
+                bSky->r -= 2*M_PI;
+            while (bSky->r <      0)
+                bSky->r += 2*M_PI;
+
+            // XXX we are getting round-off errors as a result of the wcs transformation
+            // having terms in units of pix/degree. for now require 10mas on this
+            ok_float_tol(aSky->r*PS_DEG_RAD, bSky->r*PS_DEG_RAD, 0.01/3600.0, "coordinate match: %f vs %f (delta = %f)", aSky->r*PS_DEG_RAD, bSky->r*PS_DEG_RAD, aSky->r*PS_DEG_RAD - bSky->r*PS_DEG_RAD);
+            ok_float_tol(aSky->d*PS_DEG_RAD, bSky->d*PS_DEG_RAD, 0.01/3600.0, "coordinate match: %f vs %f (delta = %f)", aSky->d*PS_DEG_RAD, bSky->d*PS_DEG_RAD, aSky->d*PS_DEG_RAD - bSky->d*PS_DEG_RAD);
+        }
+    }
+    psFree (aSky);
+    psFree (aTPA);
+    psFree (aFPA);
+    psFree (aChip);
+
+    psFree (bSky);
+    psFree (bChip);
+
+    psFree (wcs);
+    psFree (header2);
+
+    skip_end();
+    psFree (fpa);
+    psFree (chip);
+    psFree (header1);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+psMetadata *WriteCoordsToHeader (Coords *coords)
+{
+
+    char name[16];
+
+    // construct a header using coords as the input
+    psMetadata *header = psMetadataAlloc();
+
+    sprintf (name, "RA--%s", &coords[0].ctype[4]);
+    psMetadataAddStr (header, PS_LIST_TAIL, "CTYPE1", PS_META_REPLACE, "", name);
+    sprintf (name, "DEC-%s", &coords[0].ctype[4]);
+    psMetadataAddStr (header, PS_LIST_TAIL, "CTYPE2", PS_META_REPLACE, "", name);
+
+    // center coords (R,D)
+    psMetadataAddF64 (header, PS_LIST_TAIL, "CRVAL1", PS_META_REPLACE, "", coords[0].crval1);
+    psMetadataAddF64 (header, PS_LIST_TAIL, "CRVAL2", PS_META_REPLACE, "", coords[0].crval2);
+
+    // center coords (X,Y)
+    psMetadataAddF64 (header, PS_LIST_TAIL, "CRPIX1", PS_META_REPLACE, "", coords[0].crpix1);
+    psMetadataAddF64 (header, PS_LIST_TAIL, "CRPIX2", PS_META_REPLACE, "", coords[0].crpix2);
+
+    // degrees per pixel
+    psMetadataAddF64 (header, PS_LIST_TAIL, "CDELT1",  PS_META_REPLACE, "", coords[0].cdelt1);
+    psMetadataAddF64 (header, PS_LIST_TAIL, "CDELT2",  PS_META_REPLACE, "", coords[0].cdelt2);
+
+    // rotation matrix
+    psMetadataAddF64 (header, PS_LIST_TAIL, "PC001001", PS_META_REPLACE, "", coords[0].pc1_1);
+    psMetadataAddF64 (header, PS_LIST_TAIL, "PC001002", PS_META_REPLACE, "", coords[0].pc1_2);
+    psMetadataAddF64 (header, PS_LIST_TAIL, "PC002001", PS_META_REPLACE, "", coords[0].pc2_1);
+    psMetadataAddF64 (header, PS_LIST_TAIL, "PC002002", PS_META_REPLACE, "", coords[0].pc2_2);
+
+    psMetadataAddF64 (header, PS_LIST_TAIL, "PCA1X2Y0", PS_META_REPLACE, "", coords[0].polyterms[0][0]);
+    psMetadataAddF64 (header, PS_LIST_TAIL, "PCA1X1Y1", PS_META_REPLACE, "", coords[0].polyterms[1][0]);
+    psMetadataAddF64 (header, PS_LIST_TAIL, "PCA1X0Y2", PS_META_REPLACE, "", coords[0].polyterms[2][0]);
+    psMetadataAddF64 (header, PS_LIST_TAIL, "PCA2X2Y0", PS_META_REPLACE, "", coords[0].polyterms[0][1]);
+    psMetadataAddF64 (header, PS_LIST_TAIL, "PCA2X1Y1", PS_META_REPLACE, "", coords[0].polyterms[1][1]);
+    psMetadataAddF64 (header, PS_LIST_TAIL, "PCA2X0Y2", PS_META_REPLACE, "", coords[0].polyterms[2][1]);
+    psMetadataAddF64 (header, PS_LIST_TAIL, "PCA1X3Y0", PS_META_REPLACE, "", coords[0].polyterms[3][0]);
+    psMetadataAddF64 (header, PS_LIST_TAIL, "PCA1X2Y1", PS_META_REPLACE, "", coords[0].polyterms[4][0]);
+    psMetadataAddF64 (header, PS_LIST_TAIL, "PCA1X1Y2", PS_META_REPLACE, "", coords[0].polyterms[5][0]);
+    psMetadataAddF64 (header, PS_LIST_TAIL, "PCA1X0Y3", PS_META_REPLACE, "", coords[0].polyterms[6][0]);
+    psMetadataAddF64 (header, PS_LIST_TAIL, "PCA2X3Y0", PS_META_REPLACE, "", coords[0].polyterms[3][1]);
+    psMetadataAddF64 (header, PS_LIST_TAIL, "PCA2X2Y1", PS_META_REPLACE, "", coords[0].polyterms[4][1]);
+    psMetadataAddF64 (header, PS_LIST_TAIL, "PCA2X1Y2", PS_META_REPLACE, "", coords[0].polyterms[5][1]);
+    psMetadataAddF64 (header, PS_LIST_TAIL, "PCA2X0Y3", PS_META_REPLACE, "", coords[0].polyterms[6][1]);
+
+    psMetadataAddS32 (header, PS_LIST_TAIL, "NPLYTERM", PS_META_REPLACE, "", coords[0].Npolyterms);
+
+    return header;
+}
+
+# else
+
+    int main (void)
+{
+    plan_tests(2);
+
+    ok(true, "Skipping tests: (libdvo not available)");
+    note("pmAstrometryWCS tests compared with DVO coords routines : SKIPPED (libdvo not available)");
+
+    return exit_status();
+}
+
+# endif
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tap_pmAstrometryWCS_DVO4.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tap_pmAstrometryWCS_DVO4.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tap_pmAstrometryWCS_DVO4.c	(revision 22157)
@@ -0,0 +1,956 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "tap.h"
+#include "pstap.h"
+
+// HAVA_KAPA checks for Ohana libraries, needed for the Coords structure
+# if (HAVE_KAPA)
+# include "dvo.h"
+
+psMetadata *WriteCoordsToHeader (Coords *coords);
+void test1(); // basic WRP+DIS projections,
+void test2(); // small rotation
+void test3(); // 2nd order terms in WRP
+void test4(); // small rotation in WRP and DIS
+void test5(); // 2nd order terms in WRP and DIS
+void test1x(); // basic WRP+DIS projections with central offset
+void test2x(); // small rotation with central offset
+void test3x(); // 2nd order term in WRP with central offset
+
+int main (void)
+{
+    plan_tests(1329);
+
+    note("pmAstromWriteWCS tests compared with DVO coords routines");
+
+    test1();
+    test2();
+    test3();
+    test4();
+    test5();
+    test1x();
+    test2x();
+    test3x();
+
+    return exit_status();
+}
+
+// create a fake chip-level mosaic header
+void test1()
+{
+    note("test pmAstrom Read,Write BilevelChip");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style mosaic coordinate system
+    // chip-level data (chip -> fpa)
+    Coords coords;
+    strcpy (coords.ctype, "RA---WRP");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = 0.0;
+    coords.crpix2 = 0.0;
+    coords.cdelt1 = 10.0; // microns per pixel
+    coords.cdelt2 = 10.0; // microns per pixel
+    coords.pc1_1  = 1.0;
+    coords.pc1_2  = 0.0;
+    coords.pc2_1  = 0.0;
+    coords.pc2_2  = 1.0;
+    coords.Npolyterms = 0;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+
+    // mosaic-level data (fpa->sky)
+    Coords mosaic;
+    strcpy (mosaic.ctype, "RA---DIS");
+    mosaic.crval1 = 0.0;
+    mosaic.crval2 = 0.0;
+    mosaic.crpix1 = 0.0;
+    mosaic.crpix2 = 0.0;
+    mosaic.cdelt1 = 1.0 / 10.0 / 3600.0; // degrees per micron
+    mosaic.cdelt2 = 1.0 / 10.0 / 3600.0; // degrees per micron
+    mosaic.pc1_1  = 1.0;
+    mosaic.pc1_2  = 0.0;
+    mosaic.pc2_1  = 0.0;
+    mosaic.pc2_2  = 1.0;
+    mosaic.Npolyterms = 0;
+    for (int i = 0; i < 7; i++) {
+        mosaic.polyterms[i][0] = 0.0;
+        mosaic.polyterms[i][1] = 0.0;
+    }
+    RegisterMosaic (&mosaic);
+
+    psMetadata *headerChp = WriteCoordsToHeader (&coords);
+    psMetadata *headerMos = WriteCoordsToHeader (&mosaic);
+
+    pmFPA *fpa = pmFPAAlloc (NULL);
+    pmChip *chip = pmChipAlloc (fpa, NULL);
+
+    bool status = pmAstromReadBilevelChip (chip, headerChp);
+    ok (status, "read bilevel chip header");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    bool status = pmAstromReadBilevelMosaic (fpa, headerMos);
+    ok (status, "read bilevel fpa header");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psPlane  *onChip = psPlaneAlloc();
+    psPlane  *onFPA  = psPlaneAlloc();
+    psPlane  *onTPA  = psPlaneAlloc();
+    psSphere *onSky  = psSphereAlloc();
+
+    double rDVO, dDVO;
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            XY_to_RD (&rDVO, &dDVO, x, y, &coords);
+
+            onChip->x = x;
+            onChip->y = y;
+
+            psPlaneTransformApply (onFPA, chip->toFPA, onChip);
+            psPlaneTransformApply (onTPA, fpa->toTPA, onFPA);
+            psDeproject (onSky, onTPA, fpa->toSky);
+
+            while (onSky->r > 2*M_PI)
+                onSky->r -= 2*M_PI;
+            while (onSky->r <      0)
+                onSky->r += 2*M_PI;
+
+            // fprintf (stderr, "fpa x: %f vs %f : %f\n", rDVO, onFPA->x, rDVO - onFPA->x);
+            // fprintf (stderr, "fpa y: %f vs %f : %f\n", dDVO, onFPA->y, dDVO - onFPA->y);
+
+            ok_float(onSky->r*PM_DEG_RAD, rDVO, "coordinate match: %f vs %f (delta = %f)", onSky->r*PM_DEG_RAD, rDVO, 3600.0*(onSky->r*PM_DEG_RAD - rDVO));
+            ok_float(onSky->d*PM_DEG_RAD, dDVO, "coordinate match: %f vs %f (delta = %f)", onSky->d*PM_DEG_RAD, dDVO, 3600.0*(onSky->d*PM_DEG_RAD - dDVO));
+        }
+    }
+    psFree (onSky);
+    psFree (onTPA);
+    psFree (onFPA);
+    psFree (onChip);
+
+    skip_end();
+    skip_end();
+    psFree (fpa);
+    psFree (chip);
+
+    psFree (headerMos);
+    psFree (headerChp);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+// create a fake chip-level mosaic header
+void test2()
+{
+    note("test pmAstrom Read,Write BilevelChip");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style mosaic coordinate system
+    // chip-level data (chip -> fpa)
+    Coords coords;
+    strcpy (coords.ctype, "RA---WRP");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = 0.0;
+    coords.crpix2 = 0.0;
+    coords.cdelt1 = 10.0; // microns per pixel
+    coords.cdelt2 = 10.0; // microns per pixel
+    coords.pc1_1  = 0.9;
+    coords.pc1_2  = 0.1;
+    coords.pc2_1  =-0.1;
+    coords.pc2_2  = 0.9;
+    coords.Npolyterms = 0;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+
+    // mosaic-level data (fpa->sky)
+    Coords mosaic;
+    strcpy (mosaic.ctype, "RA---DIS");
+    mosaic.crval1 = 0.0;
+    mosaic.crval2 = 0.0;
+    mosaic.crpix1 = 0.0;
+    mosaic.crpix2 = 0.0;
+    mosaic.cdelt1 = 1.0 / 10.0 / 3600.0; // degrees per micron
+    mosaic.cdelt2 = 1.0 / 10.0 / 3600.0; // degrees per micron
+    mosaic.pc1_1  = 1.0;
+    mosaic.pc1_2  = 0.0;
+    mosaic.pc2_1  = 0.0;
+    mosaic.pc2_2  = 1.0;
+    mosaic.Npolyterms = 0;
+    for (int i = 0; i < 7; i++) {
+        mosaic.polyterms[i][0] = 0.0;
+        mosaic.polyterms[i][1] = 0.0;
+    }
+    RegisterMosaic (&mosaic);
+
+    psMetadata *headerChp = WriteCoordsToHeader (&coords);
+    psMetadata *headerMos = WriteCoordsToHeader (&mosaic);
+
+    pmFPA *fpa = pmFPAAlloc (NULL);
+    pmChip *chip = pmChipAlloc (fpa, NULL);
+
+    bool status = pmAstromReadBilevelChip (chip, headerChp);
+    ok (status, "read bilevel chip header");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    bool status = pmAstromReadBilevelMosaic (fpa, headerMos);
+    ok (status, "read bilevel fpa header");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psPlane  *onChip = psPlaneAlloc();
+    psPlane  *onFPA  = psPlaneAlloc();
+    psPlane  *onTPA  = psPlaneAlloc();
+    psSphere *onSky  = psSphereAlloc();
+
+    double rDVO, dDVO;
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            XY_to_RD (&rDVO, &dDVO, x, y, &coords);
+
+            onChip->x = x;
+            onChip->y = y;
+
+            psPlaneTransformApply (onFPA, chip->toFPA, onChip);
+            psPlaneTransformApply (onTPA, fpa->toTPA, onFPA);
+            psDeproject (onSky, onTPA, fpa->toSky);
+
+            while (onSky->r > 2*M_PI)
+                onSky->r -= 2*M_PI;
+            while (onSky->r <      0)
+                onSky->r += 2*M_PI;
+
+            // fprintf (stderr, "fpa x: %f vs %f : %f\n", rDVO, onFPA->x, rDVO - onFPA->x);
+            // fprintf (stderr, "fpa y: %f vs %f : %f\n", dDVO, onFPA->y, dDVO - onFPA->y);
+
+            ok_float(onSky->r*PS_DEG_RAD, rDVO, "coordinate match: %f vs %f (delta = %f)", onSky->r*PS_DEG_RAD, rDVO, 3600.0*(onSky->r*PS_DEG_RAD - rDVO));
+            ok_float(onSky->d*PS_DEG_RAD, dDVO, "coordinate match: %f vs %f (delta = %f)", onSky->d*PS_DEG_RAD, dDVO, 3600.0*(onSky->d*PS_DEG_RAD - dDVO));
+        }
+    }
+    psFree (onSky);
+    psFree (onTPA);
+    psFree (onFPA);
+    psFree (onChip);
+
+    skip_end();
+    skip_end();
+    psFree (fpa);
+    psFree (chip);
+
+    psFree (headerMos);
+    psFree (headerChp);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+// create a fake chip-level mosaic header
+void test3()
+{
+    note("test pmAstrom Read,Write BilevelChip");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style mosaic coordinate system
+    // chip-level data (chip -> fpa)
+    Coords coords;
+    strcpy (coords.ctype, "RA---WRP");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = 0.0;
+    coords.crpix2 = 0.0;
+    coords.cdelt1 = 10.0; // microns per pixel
+    coords.cdelt2 = 10.0; // microns per pixel
+    coords.pc1_1  = 1.0;
+    coords.pc1_2  = 0.0;
+    coords.pc2_1  = 0.0;
+    coords.pc2_2  = 1.0;
+    coords.Npolyterms = 2;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+    coords.polyterms[0][0] = 0.01; // L vs X^2
+    coords.polyterms[2][1] = 0.01; // M vs Y^2
+
+    // mosaic-level data (fpa->sky)
+    Coords mosaic;
+    strcpy (mosaic.ctype, "RA---DIS");
+    mosaic.crval1 = 0.0;
+    mosaic.crval2 = 0.0;
+    mosaic.crpix1 = 0.0;
+    mosaic.crpix2 = 0.0;
+    mosaic.cdelt1 = 1.0 / 10.0 / 3600.0; // degrees per micron
+    mosaic.cdelt2 = 1.0 / 10.0 / 3600.0; // degrees per micron
+    mosaic.pc1_1  = 1.0;
+    mosaic.pc1_2  = 0.0;
+    mosaic.pc2_1  = 0.0;
+    mosaic.pc2_2  = 1.0;
+    mosaic.Npolyterms = 0;
+    for (int i = 0; i < 7; i++) {
+        mosaic.polyterms[i][0] = 0.0;
+        mosaic.polyterms[i][1] = 0.0;
+    }
+    RegisterMosaic (&mosaic);
+
+    psMetadata *headerChp = WriteCoordsToHeader (&coords);
+    psMetadata *headerMos = WriteCoordsToHeader (&mosaic);
+
+    pmFPA *fpa = pmFPAAlloc (NULL);
+    pmChip *chip = pmChipAlloc (fpa, NULL);
+
+    bool status = pmAstromReadBilevelChip (chip, headerChp);
+    ok (status, "read bilevel chip header");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    bool status = pmAstromReadBilevelMosaic (fpa, headerMos);
+    ok (status, "read bilevel fpa header");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psPlane  *onChip = psPlaneAlloc();
+    psPlane  *onFPA  = psPlaneAlloc();
+    psPlane  *onTPA  = psPlaneAlloc();
+    psSphere *onSky  = psSphereAlloc();
+
+    double rDVO, dDVO;
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            XY_to_RD (&rDVO, &dDVO, x, y, &coords);
+
+            onChip->x = x;
+            onChip->y = y;
+
+            psPlaneTransformApply (onFPA, chip->toFPA, onChip);
+            psPlaneTransformApply (onTPA, fpa->toTPA, onFPA);
+            psDeproject (onSky, onTPA, fpa->toSky);
+
+            while (onSky->r > 2*M_PI)
+                onSky->r -= 2*M_PI;
+            while (onSky->r <      0)
+                onSky->r += 2*M_PI;
+
+            // fprintf (stderr, "fpa x: %f vs %f : %f\n", rDVO, onFPA->x, rDVO - onFPA->x);
+            // fprintf (stderr, "fpa y: %f vs %f : %f\n", dDVO, onFPA->y, dDVO - onFPA->y);
+
+            ok_float(onSky->r*PM_DEG_RAD, rDVO, "coordinate match: %f vs %f (delta = %f)", onSky->r*PM_DEG_RAD, rDVO, 3600.0*(onSky->r*PM_DEG_RAD - rDVO));
+            ok_float(onSky->d*PM_DEG_RAD, dDVO, "coordinate match: %f vs %f (delta = %f)", onSky->d*PM_DEG_RAD, dDVO, 3600.0*(onSky->d*PM_DEG_RAD - dDVO));
+        }
+    }
+    psFree (onSky);
+    psFree (onTPA);
+    psFree (onFPA);
+    psFree (onChip);
+
+    skip_end();
+    skip_end();
+    psFree (fpa);
+    psFree (chip);
+
+    psFree (headerMos);
+    psFree (headerChp);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+// create a fake chip-level mosaic header
+void test4()
+{
+    note("test pmAstrom Read,Write BilevelChip");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style mosaic coordinate system
+    // chip-level data (chip -> fpa)
+    Coords coords;
+    strcpy (coords.ctype, "RA---WRP");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = 0.0;
+    coords.crpix2 = 0.0;
+    coords.cdelt1 = 10.0; // microns per pixel
+    coords.cdelt2 = 10.0; // microns per pixel
+    coords.pc1_1  = 0.9;
+    coords.pc1_2  = 0.1;
+    coords.pc2_1  =-0.1;
+    coords.pc2_2  = 0.9;
+    coords.Npolyterms = 0;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+
+    // mosaic-level data (fpa->sky)
+    Coords mosaic;
+    strcpy (mosaic.ctype, "RA---DIS");
+    mosaic.crval1 = 0.0;
+    mosaic.crval2 = 0.0;
+    mosaic.crpix1 = 0.0;
+    mosaic.crpix2 = 0.0;
+    mosaic.cdelt1 = 1.0 / 10.0 / 3600.0; // degrees per micron
+    mosaic.cdelt2 = 1.0 / 10.0 / 3600.0; // degrees per micron
+    mosaic.pc1_1  = 0.9;
+    mosaic.pc1_2  =-0.1;
+    mosaic.pc2_1  = 0.1;
+    mosaic.pc2_2  = 0.9;
+    mosaic.Npolyterms = 0;
+    for (int i = 0; i < 7; i++) {
+        mosaic.polyterms[i][0] = 0.0;
+        mosaic.polyterms[i][1] = 0.0;
+    }
+    RegisterMosaic (&mosaic);
+
+    psMetadata *headerChp = WriteCoordsToHeader (&coords);
+    psMetadata *headerMos = WriteCoordsToHeader (&mosaic);
+
+    pmFPA *fpa = pmFPAAlloc (NULL);
+    pmChip *chip = pmChipAlloc (fpa, NULL);
+
+    bool status = pmAstromReadBilevelChip (chip, headerChp);
+    ok (status, "read bilevel chip header");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    bool status = pmAstromReadBilevelMosaic (fpa, headerMos);
+    ok (status, "read bilevel fpa header");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psPlane  *onChip = psPlaneAlloc();
+    psPlane  *onFPA  = psPlaneAlloc();
+    psPlane  *onTPA  = psPlaneAlloc();
+    psSphere *onSky  = psSphereAlloc();
+
+    double rDVO, dDVO;
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            XY_to_RD (&rDVO, &dDVO, x, y, &coords);
+
+            onChip->x = x;
+            onChip->y = y;
+
+            psPlaneTransformApply (onFPA, chip->toFPA, onChip);
+            psPlaneTransformApply (onTPA, fpa->toTPA, onFPA);
+            psDeproject (onSky, onTPA, fpa->toSky);
+
+            while (onSky->r > 2*M_PI)
+                onSky->r -= 2*M_PI;
+            while (onSky->r <      0)
+                onSky->r += 2*M_PI;
+
+            // fprintf (stderr, "fpa x: %f vs %f : %f\n", rDVO, onFPA->x, rDVO - onFPA->x);
+            // fprintf (stderr, "fpa y: %f vs %f : %f\n", dDVO, onFPA->y, dDVO - onFPA->y);
+
+            ok_float(onSky->r*PM_DEG_RAD, rDVO, "coordinate match: %f vs %f (delta = %f)", onSky->r*PM_DEG_RAD, rDVO, 3600.0*(onSky->r*PM_DEG_RAD - rDVO));
+            ok_float(onSky->d*PM_DEG_RAD, dDVO, "coordinate match: %f vs %f (delta = %f)", onSky->d*PM_DEG_RAD, dDVO, 3600.0*(onSky->d*PM_DEG_RAD - dDVO));
+        }
+    }
+    psFree (onSky);
+    psFree (onTPA);
+    psFree (onFPA);
+    psFree (onChip);
+
+    skip_end();
+    skip_end();
+    psFree (fpa);
+    psFree (chip);
+
+    psFree (headerMos);
+    psFree (headerChp);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+// create a fake chip-level mosaic header
+void test5()
+{
+    note("test pmAstrom Read,Write BilevelChip");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style mosaic coordinate system
+    // chip-level data (chip -> fpa)
+    Coords coords;
+    strcpy (coords.ctype, "RA---WRP");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = 0.0;
+    coords.crpix2 = 0.0;
+    coords.cdelt1 = 10.0; // microns per pixel
+    coords.cdelt2 = 10.0; // microns per pixel
+    coords.pc1_1  = 1.0;
+    coords.pc1_2  = 0.0;
+    coords.pc2_1  = 0.0;
+    coords.pc2_2  = 1.0;
+    coords.Npolyterms = 2;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+    coords.polyterms[0][0] = 0.01; // L vs X^2
+    coords.polyterms[2][1] = 0.01; // M vs Y^2
+
+    // mosaic-level data (fpa->sky)
+    Coords mosaic;
+    strcpy (mosaic.ctype, "RA---DIS");
+    mosaic.crval1 = 0.0;
+    mosaic.crval2 = 0.0;
+    mosaic.crpix1 = 0.0;
+    mosaic.crpix2 = 0.0;
+    mosaic.cdelt1 = 1.0 / 10.0 / 3600.0; // degrees per micron
+    mosaic.cdelt2 = 1.0 / 10.0 / 3600.0; // degrees per micron
+    mosaic.pc1_1  = 1.0;
+    mosaic.pc1_2  = 0.0;
+    mosaic.pc2_1  = 0.0;
+    mosaic.pc2_2  = 1.0;
+    mosaic.Npolyterms = 2;
+    for (int i = 0; i < 7; i++) {
+        mosaic.polyterms[i][0] = 0.0;
+        mosaic.polyterms[i][1] = 0.0;
+    }
+    RegisterMosaic (&mosaic);
+    mosaic.polyterms[0][0] = 0.01; // L vs X^2
+    mosaic.polyterms[2][1] = 0.01; // M vs Y^2
+
+    psMetadata *headerChp = WriteCoordsToHeader (&coords);
+    psMetadata *headerMos = WriteCoordsToHeader (&mosaic);
+
+    pmFPA *fpa = pmFPAAlloc (NULL);
+    pmChip *chip = pmChipAlloc (fpa, NULL);
+
+    bool status = pmAstromReadBilevelChip (chip, headerChp);
+    ok (status, "read bilevel chip header");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    bool status = pmAstromReadBilevelMosaic (fpa, headerMos);
+    ok (status, "read bilevel fpa header");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psPlane  *onChip = psPlaneAlloc();
+    psPlane  *onFPA  = psPlaneAlloc();
+    psPlane  *onTPA  = psPlaneAlloc();
+    psSphere *onSky  = psSphereAlloc();
+
+    double rDVO, dDVO;
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            XY_to_RD (&rDVO, &dDVO, x, y, &coords);
+
+            onChip->x = x;
+            onChip->y = y;
+
+            psPlaneTransformApply (onFPA, chip->toFPA, onChip);
+            psPlaneTransformApply (onTPA, fpa->toTPA, onFPA);
+            psDeproject (onSky, onTPA, fpa->toSky);
+
+            while (onSky->r > 2*M_PI)
+                onSky->r -= 2*M_PI;
+            while (onSky->r <      0)
+                onSky->r += 2*M_PI;
+
+            // fprintf (stderr, "fpa x: %f vs %f : %f\n", rDVO, onFPA->x, rDVO - onFPA->x);
+            // fprintf (stderr, "fpa y: %f vs %f : %f\n", dDVO, onFPA->y, dDVO - onFPA->y);
+
+            ok_float(onSky->r*PM_DEG_RAD, rDVO, "coordinate match: %f vs %f (delta = %f)", onSky->r*PM_DEG_RAD, rDVO, 3600.0*(onSky->r*PM_DEG_RAD - rDVO));
+            ok_float(onSky->d*PM_DEG_RAD, dDVO, "coordinate match: %f vs %f (delta = %f)", onSky->d*PM_DEG_RAD, dDVO, 3600.0*(onSky->d*PM_DEG_RAD - dDVO));
+        }
+    }
+    psFree (onSky);
+    psFree (onTPA);
+    psFree (onFPA);
+    psFree (onChip);
+
+    skip_end();
+    skip_end();
+    psFree (fpa);
+    psFree (chip);
+
+    psFree (headerMos);
+    psFree (headerChp);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+// create a fake chip-level mosaic header
+void test1x()
+{
+    note("test pmAstrom Read,Write BilevelChip");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style mosaic coordinate system
+    // chip-level data (chip -> fpa)
+    Coords coords;
+    strcpy (coords.ctype, "RA---WRP");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = +50.0;
+    coords.crpix2 = -20.0;
+    coords.cdelt1 = 10.0; // microns per pixel
+    coords.cdelt2 = 10.0; // microns per pixel
+    coords.pc1_1  = 1.0;
+    coords.pc1_2  = 0.0;
+    coords.pc2_1  = 0.0;
+    coords.pc2_2  = 1.0;
+    coords.Npolyterms = 0;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+
+    // mosaic-level data (fpa->sky)
+    Coords mosaic;
+    strcpy (mosaic.ctype, "RA---DIS");
+    mosaic.crval1 = 0.0;
+    mosaic.crval2 = 0.0;
+    mosaic.crpix1 = 0.0;
+    mosaic.crpix2 = 0.0;
+    mosaic.cdelt1 = 1.0 / 10.0 / 3600.0; // degrees per micron
+    mosaic.cdelt2 = 1.0 / 10.0 / 3600.0; // degrees per micron
+    mosaic.pc1_1  = 1.0;
+    mosaic.pc1_2  = 0.0;
+    mosaic.pc2_1  = 0.0;
+    mosaic.pc2_2  = 1.0;
+    mosaic.Npolyterms = 0;
+    for (int i = 0; i < 7; i++) {
+        mosaic.polyterms[i][0] = 0.0;
+        mosaic.polyterms[i][1] = 0.0;
+    }
+    RegisterMosaic (&mosaic);
+
+    psMetadata *headerChp = WriteCoordsToHeader (&coords);
+    psMetadata *headerMos = WriteCoordsToHeader (&mosaic);
+
+    pmFPA *fpa = pmFPAAlloc (NULL);
+    pmChip *chip = pmChipAlloc (fpa, NULL);
+
+    bool status = pmAstromReadBilevelChip (chip, headerChp);
+    ok (status, "read bilevel chip header");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    bool status = pmAstromReadBilevelMosaic (fpa, headerMos);
+    ok (status, "read bilevel fpa header");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psPlane  *onChip = psPlaneAlloc();
+    psPlane  *onFPA  = psPlaneAlloc();
+    psPlane  *onTPA  = psPlaneAlloc();
+    psSphere *onSky  = psSphereAlloc();
+
+    double rDVO, dDVO;
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            XY_to_RD (&rDVO, &dDVO, x, y, &coords);
+
+            onChip->x = x;
+            onChip->y = y;
+
+            psPlaneTransformApply (onFPA, chip->toFPA, onChip);
+            psPlaneTransformApply (onTPA, fpa->toTPA, onFPA);
+            psDeproject (onSky, onTPA, fpa->toSky);
+
+            while (onSky->r > 2*M_PI)
+                onSky->r -= 2*M_PI;
+            while (onSky->r <      0)
+                onSky->r += 2*M_PI;
+
+            // fprintf (stderr, "fpa x: %f vs %f : %f\n", rDVO, onFPA->x, rDVO - onFPA->x);
+            // fprintf (stderr, "fpa y: %f vs %f : %f\n", dDVO, onFPA->y, dDVO - onFPA->y);
+
+            ok_float(onSky->r*PM_DEG_RAD, rDVO, "coordinate match: %f vs %f (delta = %f)", onSky->r*PM_DEG_RAD, rDVO, 3600.0*(onSky->r*PM_DEG_RAD - rDVO));
+            ok_float(onSky->d*PM_DEG_RAD, dDVO, "coordinate match: %f vs %f (delta = %f)", onSky->d*PM_DEG_RAD, dDVO, 3600.0*(onSky->d*PM_DEG_RAD - dDVO));
+        }
+    }
+    psFree (onSky);
+    psFree (onTPA);
+    psFree (onFPA);
+    psFree (onChip);
+
+    skip_end();
+    skip_end();
+    psFree (fpa);
+    psFree (chip);
+
+    psFree (headerMos);
+    psFree (headerChp);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+// create a fake chip-level mosaic header
+void test2x()
+{
+    note("test pmAstrom Read,Write BilevelChip");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style mosaic coordinate system
+    // chip-level data (chip -> fpa)
+    Coords coords;
+    strcpy (coords.ctype, "RA---WRP");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = +50.0;
+    coords.crpix2 = -20.0;
+    coords.cdelt1 = 10.0; // microns per pixel
+    coords.cdelt2 = 10.0; // microns per pixel
+    coords.pc1_1  = 0.9;
+    coords.pc1_2  = 0.1;
+    coords.pc2_1  =-0.1;
+    coords.pc2_2  = 0.9;
+    coords.Npolyterms = 0;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+
+    // mosaic-level data (fpa->sky)
+    Coords mosaic;
+    strcpy (mosaic.ctype, "RA---DIS");
+    mosaic.crval1 = 0.0;
+    mosaic.crval2 = 0.0;
+    mosaic.crpix1 = 0.0;
+    mosaic.crpix2 = 0.0;
+    mosaic.cdelt1 = 1.0 / 10.0 / 3600.0; // degrees per micron
+    mosaic.cdelt2 = 1.0 / 10.0 / 3600.0; // degrees per micron
+    mosaic.pc1_1  = 1.0;
+    mosaic.pc1_2  = 0.0;
+    mosaic.pc2_1  = 0.0;
+    mosaic.pc2_2  = 1.0;
+    mosaic.Npolyterms = 0;
+    for (int i = 0; i < 7; i++) {
+        mosaic.polyterms[i][0] = 0.0;
+        mosaic.polyterms[i][1] = 0.0;
+    }
+    RegisterMosaic (&mosaic);
+
+    psMetadata *headerChp = WriteCoordsToHeader (&coords);
+    psMetadata *headerMos = WriteCoordsToHeader (&mosaic);
+
+    pmFPA *fpa = pmFPAAlloc (NULL);
+    pmChip *chip = pmChipAlloc (fpa, NULL);
+
+    bool status = pmAstromReadBilevelChip (chip, headerChp);
+    ok (status, "read bilevel chip header");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    bool status = pmAstromReadBilevelMosaic (fpa, headerMos);
+    ok (status, "read bilevel fpa header");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psPlane  *onChip = psPlaneAlloc();
+    psPlane  *onFPA  = psPlaneAlloc();
+    psPlane  *onTPA  = psPlaneAlloc();
+    psSphere *onSky  = psSphereAlloc();
+
+    double rDVO, dDVO;
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            XY_to_RD (&rDVO, &dDVO, x, y, &coords);
+
+            onChip->x = x;
+            onChip->y = y;
+
+            psPlaneTransformApply (onFPA, chip->toFPA, onChip);
+            psPlaneTransformApply (onTPA, fpa->toTPA, onFPA);
+            psDeproject (onSky, onTPA, fpa->toSky);
+
+            while (onSky->r > 2*M_PI)
+                onSky->r -= 2*M_PI;
+            while (onSky->r <      0)
+                onSky->r += 2*M_PI;
+
+            // fprintf (stderr, "fpa x: %f vs %f : %f\n", rDVO, onFPA->x, rDVO - onFPA->x);
+            // fprintf (stderr, "fpa y: %f vs %f : %f\n", dDVO, onFPA->y, dDVO - onFPA->y);
+
+            ok_float(onSky->r*PM_DEG_RAD, rDVO, "coordinate match: %f vs %f (delta = %f)", onSky->r*PM_DEG_RAD, rDVO, 3600.0*(onSky->r*PM_DEG_RAD - rDVO));
+            ok_float(onSky->d*PM_DEG_RAD, dDVO, "coordinate match: %f vs %f (delta = %f)", onSky->d*PM_DEG_RAD, dDVO, 3600.0*(onSky->d*PM_DEG_RAD - dDVO));
+        }
+    }
+    psFree (onSky);
+    psFree (onTPA);
+    psFree (onFPA);
+    psFree (onChip);
+
+    skip_end();
+    skip_end();
+    psFree (fpa);
+    psFree (chip);
+
+    psFree (headerMos);
+    psFree (headerChp);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+// create a fake chip-level mosaic header
+void test3x()
+{
+    note("test pmAstrom Read,Write BilevelChip");
+    psMemId id = psMemGetId();
+
+    // build a DVO-style mosaic coordinate system
+    // chip-level data (chip -> fpa)
+    Coords coords;
+    strcpy (coords.ctype, "RA---WRP");
+    coords.crval1 = 0.0;
+    coords.crval2 = 0.0;
+    coords.crpix1 = +50.0;
+    coords.crpix2 = -20.0;
+    coords.cdelt1 = 10.0; // microns per pixel
+    coords.cdelt2 = 10.0; // microns per pixel
+    coords.pc1_1  = 1.0;
+    coords.pc1_2  = 0.0;
+    coords.pc2_1  = 0.0;
+    coords.pc2_2  = 1.0;
+    coords.Npolyterms = 2;
+    for (int i = 0; i < 7; i++) {
+        coords.polyterms[i][0] = 0.0;
+        coords.polyterms[i][1] = 0.0;
+    }
+    coords.polyterms[0][0] = 0.01; // L vs X^2
+    coords.polyterms[2][1] = 0.01; // M vs Y^2
+
+    // mosaic-level data (fpa->sky)
+    Coords mosaic;
+    strcpy (mosaic.ctype, "RA---DIS");
+    mosaic.crval1 = 0.0;
+    mosaic.crval2 = 0.0;
+    mosaic.crpix1 = 0.0;
+    mosaic.crpix2 = 0.0;
+    mosaic.cdelt1 = 1.0 / 10.0 / 3600.0; // degrees per micron
+    mosaic.cdelt2 = 1.0 / 10.0 / 3600.0; // degrees per micron
+    mosaic.pc1_1  = 1.0;
+    mosaic.pc1_2  = 0.0;
+    mosaic.pc2_1  = 0.0;
+    mosaic.pc2_2  = 1.0;
+    mosaic.Npolyterms = 0;
+    for (int i = 0; i < 7; i++) {
+        mosaic.polyterms[i][0] = 0.0;
+        mosaic.polyterms[i][1] = 0.0;
+    }
+    RegisterMosaic (&mosaic);
+
+    psMetadata *headerChp = WriteCoordsToHeader (&coords);
+    psMetadata *headerMos = WriteCoordsToHeader (&mosaic);
+
+    pmFPA *fpa = pmFPAAlloc (NULL);
+    pmChip *chip = pmChipAlloc (fpa, NULL);
+
+    bool status = pmAstromReadBilevelChip (chip, headerChp);
+    ok (status, "read bilevel chip header");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    bool status = pmAstromReadBilevelMosaic (fpa, headerMos);
+    ok (status, "read bilevel fpa header");
+    skip_start (!status, 1, "*** WCS Conversion FAILS *** : skipping related tests");
+
+    psPlane  *onChip = psPlaneAlloc();
+    psPlane  *onFPA  = psPlaneAlloc();
+    psPlane  *onTPA  = psPlaneAlloc();
+    psSphere *onSky  = psSphereAlloc();
+
+    double rDVO, dDVO;
+    for (double x = -2000; x <= +2000; x+= 500.0) {
+        for (double y = -2000; y <= +2000; y+= 500.0) {
+            XY_to_RD (&rDVO, &dDVO, x, y, &coords);
+
+            onChip->x = x;
+            onChip->y = y;
+
+            psPlaneTransformApply (onFPA, chip->toFPA, onChip);
+            psPlaneTransformApply (onTPA, fpa->toTPA, onFPA);
+            psDeproject (onSky, onTPA, fpa->toSky);
+
+            while (onSky->r > 2*M_PI)
+                onSky->r -= 2*M_PI;
+            while (onSky->r <      0)
+                onSky->r += 2*M_PI;
+
+            // fprintf (stderr, "fpa x: %f vs %f : %f\n", rDVO, onFPA->x, rDVO - onFPA->x);
+            // fprintf (stderr, "fpa y: %f vs %f : %f\n", dDVO, onFPA->y, dDVO - onFPA->y);
+
+            ok_float(onSky->r*PM_DEG_RAD, rDVO, "coordinate match: %f vs %f (delta = %f)", onSky->r*PM_DEG_RAD, rDVO, 3600.0*(onSky->r*PM_DEG_RAD - rDVO));
+            ok_float(onSky->d*PM_DEG_RAD, dDVO, "coordinate match: %f vs %f (delta = %f)", onSky->d*PM_DEG_RAD, dDVO, 3600.0*(onSky->d*PM_DEG_RAD - dDVO));
+        }
+    }
+    psFree (onSky);
+    psFree (onTPA);
+    psFree (onFPA);
+    psFree (onChip);
+
+    skip_end();
+    skip_end();
+    psFree (fpa);
+    psFree (chip);
+
+    psFree (headerMos);
+    psFree (headerChp);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+}
+
+psMetadata *WriteCoordsToHeader (Coords *coords)
+{
+
+    char name[16];
+
+    // construct a header using coords as the input
+    psMetadata *header = psMetadataAlloc();
+
+    sprintf (name, "RA--%s", &coords[0].ctype[4]);
+    psMetadataAddStr (header, PS_LIST_TAIL, "CTYPE1", PS_META_REPLACE, "", name);
+    sprintf (name, "DEC-%s", &coords[0].ctype[4]);
+    psMetadataAddStr (header, PS_LIST_TAIL, "CTYPE2", PS_META_REPLACE, "", name);
+
+    // center coords (R,D)
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRVAL1", PS_META_REPLACE, "", coords[0].crval1);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRVAL2", PS_META_REPLACE, "", coords[0].crval2);
+
+    // center coords (X,Y)
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRPIX1", PS_META_REPLACE, "", coords[0].crpix1);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CRPIX2", PS_META_REPLACE, "", coords[0].crpix2);
+
+    // degrees per pixel
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CDELT1",  PS_META_REPLACE, "", coords[0].cdelt1);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "CDELT2",  PS_META_REPLACE, "", coords[0].cdelt2);
+
+    // rotation matrix
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PC001001", PS_META_REPLACE, "", coords[0].pc1_1);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PC001002", PS_META_REPLACE, "", coords[0].pc1_2);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PC002001", PS_META_REPLACE, "", coords[0].pc2_1);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PC002002", PS_META_REPLACE, "", coords[0].pc2_2);
+
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X2Y0", PS_META_REPLACE, "", coords[0].polyterms[0][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X1Y1", PS_META_REPLACE, "", coords[0].polyterms[1][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X0Y2", PS_META_REPLACE, "", coords[0].polyterms[2][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X2Y0", PS_META_REPLACE, "", coords[0].polyterms[0][1]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X1Y1", PS_META_REPLACE, "", coords[0].polyterms[1][1]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X0Y2", PS_META_REPLACE, "", coords[0].polyterms[2][1]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X3Y0", PS_META_REPLACE, "", coords[0].polyterms[3][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X2Y1", PS_META_REPLACE, "", coords[0].polyterms[4][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X1Y2", PS_META_REPLACE, "", coords[0].polyterms[5][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA1X0Y3", PS_META_REPLACE, "", coords[0].polyterms[6][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X3Y0", PS_META_REPLACE, "", coords[0].polyterms[3][1]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X2Y1", PS_META_REPLACE, "", coords[0].polyterms[4][1]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X1Y2", PS_META_REPLACE, "", coords[0].polyterms[5][1]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "PCA2X0Y3", PS_META_REPLACE, "", coords[0].polyterms[6][1]);
+
+    psMetadataAddS32 (header, PS_LIST_TAIL, "NPLYTERM", PS_META_REPLACE, "", coords[0].Npolyterms);
+
+    return header;
+}
+
+# else
+
+    int main (void)
+{
+    plan_tests(2);
+
+    ok(true, "Skipping tests: (libdvo not available)");
+    note("pmAstrometryWCS tests compared with DVO coords routines : SKIPPED (libdvo not available)");
+
+    return exit_status();
+}
+
+# endif
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tap_pmHDU.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tap_pmHDU.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tap_pmHDU.c	(revision 22157)
@@ -0,0 +1,272 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+//#include <unistd.h>
+
+#define GENIMAGE(img,c,r,TYP, valueFcn) \
+img = psImageAlloc(c,r,PS_TYPE_##TYP); \
+for (psU32 row=0;row<r;row++) { \
+    ps##TYP* imgRow = img->data.TYP[row]; \
+    for (psU32 col=0;col<c;col++) { \
+        imgRow[col] = (ps##TYP)(valueFcn); \
+    } \
+}
+const char* fitsFilename = "tmp.fits";
+const char* fitsFilename2 = "tmp2.fits";
+
+bool createFitsFile(void)
+{
+    psFits* fitsFile = psFitsOpen(fitsFilename, "w");
+
+    if (fitsFile == NULL) {
+        diag("Could not create 'multi' FITS file");
+        return false;
+    }
+
+    psImage* image = psImageAlloc(16, 16, PS_TYPE_F32);
+
+    char extname[80];
+    for (int lcv = 0; lcv < 8; lcv++) {
+        snprintf(extname, 80, "ext-%d", lcv);
+
+        psMetadata* header = psMetadataAlloc();
+
+        psMetadataAdd(header, PS_LIST_TAIL, "MYINT",
+                      PS_DATA_S32,
+                      "psS32 Item", (psS32)lcv);
+
+        psMetadataAdd(header, PS_LIST_TAIL, "MYFLT",
+                      PS_DATA_F32,
+                      "psF32 Item", (float)(1.0f/(float)(1+lcv)));
+
+        psMetadataAdd(header, PS_LIST_TAIL, "MYDBL",
+                      PS_DATA_F64,
+                      "psF64 Item", (double)(1.0/(double)(1+lcv)));
+
+        psMetadataAdd(header, PS_LIST_TAIL, "MYBOOL",
+                      PS_DATA_BOOL,
+                      "psBool Item",
+                      (lcv%2 == 0));
+
+        psMetadataAdd(header, PS_LIST_TAIL, "MYSTR",
+                      PS_DATA_STRING,
+                      "String Item",
+                      extname);
+
+        // set the pixels in the image
+        psImageInit(image, (float)lcv);
+        if (!psFitsWriteImage(fitsFile, header, image, 0, extname)) {
+            diag("Could not write image");
+        }
+        psFree(header);
+    }
+    psFree(image);
+    psFree(fitsFile);
+
+    return true;
+}
+
+
+bool createFitsFile2(void)
+{
+    psFits* fitsFile = psFitsOpen(fitsFilename2, "w");
+
+    if (fitsFile == NULL) {
+        diag("Could not create 'multi' FITS file");
+        return false;
+    }
+
+    psImage* image = psImageAlloc(16, 16, PS_TYPE_F32);
+
+    char extname[80];
+    for (int lcv = 0; lcv < 8; lcv++) {
+        snprintf(extname, 80, "ext-%d", lcv);
+        pmHDU *hdu = pmHDUAlloc(extname);
+        hdu->header = psMetadataAlloc();
+
+        psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYINT",
+                      PS_DATA_S32,
+                      "psS32 Item", (psS32)lcv);
+
+        psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYFLT",
+                      PS_DATA_F32,
+                      "psF32 Item", (float)(1.0f/(float)(1+lcv)));
+
+        psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYDBL",
+                      PS_DATA_F64,
+                      "psF64 Item", (double)(1.0/(double)(1+lcv)));
+
+        psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYBOOL",
+                      PS_DATA_BOOL,
+                      "psBool Item",
+                      (lcv%2 == 0));
+
+        psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYSTR",
+                      PS_DATA_STRING,
+                      "String Item",
+                      extname);
+
+        // set the pixels in the image
+        psImageInit(image, (float)lcv);
+        if(!pmHDUWrite(hdu, fitsFile)) {
+            diag("Could not write header");
+            psErrorStackPrint(stdout, "THIS");
+
+exit(0);
+        }
+        if (!psFitsWriteImage(fitsFile, NULL, image, 0, extname)) {
+            diag("Could not write image");
+        }
+
+        psFree(hdu);
+    }
+    psFree(image);
+    psFree(fitsFile);
+
+    return true;
+}
+
+
+
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    plan_tests(18);
+      psError(PS_ERR_IO, true, "HEY: ERROR");
+
+    psHistogram *myHist = psHistogramAlloc(10, 5, 1);
+    psFree(myHist);
+
+    // Test pmHDUAlloc()
+    // Use a NULL extname
+    {
+        psMemId id = psMemGetId();
+        pmHDU *hdu = pmHDUAlloc(NULL);
+        ok(hdu, "pmHDUAlloc(NULL) returned non-NULL");
+        skip_start(!hdu, 7, "Skipping tests because pmHDUAlloc(NULL) returned NULL");
+        ok(hdu->blankPHU == true, "pmHDUAlloc(NULL) set hdu->blankPHU correctly");
+        ok(hdu->extname == NULL, "pmHDUAlloc(NULL) set hdu->extname correctly");
+        ok(hdu->format == NULL, "pmHDUAlloc(NULL) set hdu->format correctly");
+        ok(hdu->header == NULL, "pmHDUAlloc(NULL) set hdu->header correctly");
+        ok(hdu->images == NULL, "pmHDUAlloc(NULL) set hdu->images correctly");
+        ok(hdu->weights == NULL, "pmHDUAlloc(NULL) set hdu->weights correctly");
+        ok(hdu->masks == NULL, "pmHDUAlloc(NULL) set hdu->masks correctly");
+        psFree(hdu);
+        skip_end();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Use a non-NULL extname
+    {
+        psMemId id = psMemGetId();
+        pmHDU *hdu = pmHDUAlloc("ext-0");
+        ok(hdu, "pmHDUAlloc(extname) returned non-NULL");
+        skip_start(!hdu, 7, "Skipping tests because pmHDUAlloc(extname) returned NULL");
+        ok(hdu->blankPHU == false, "pmHDUAlloc(extname) set hdu->blankPHU correctly");
+        ok(0 == strcmp(hdu->extname, "ext-0"), "pmHDUAlloc(extname) set hdu->extname correctly");
+        ok(hdu->format == NULL, "pmHDUAlloc(extname) set hdu->format correctly");
+        ok(hdu->header == NULL, "pmHDUAlloc(extname) set hdu->header correctly");
+        ok(hdu->images == NULL, "pmHDUAlloc(extname) set hdu->images correctly");
+        ok(hdu->weights == NULL, "pmHDUAlloc(extname) set hdu->weights correctly");
+        ok(hdu->masks == NULL, "pmHDUAlloc(extname) set hdu->masks correctly");
+        psFree(hdu);
+        skip_end();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // tst_psFitsReadHeader()
+    {
+        ok(createFitsFile2(), "Created test FITS file");
+        psFits* fits = psFitsOpen(fitsFilename2,"r");
+        ok(fits != NULL, "psFitsOpen returned non-NULL on existing file");
+        int numHDUs = psFitsGetSize(fits);
+        ok(numHDUs == 8, "The test FITS file has %d HDUs", numHDUs);
+
+        for (int hdunum = 0; hdunum < numHDUs; hdunum++)
+        {
+            psMemId id = psMemGetId();
+            char extname[80];
+            snprintf(extname,80,"ext-%d",hdunum);
+            psFitsMoveExtNum(fits, hdunum, false);
+            pmHDU *hdu = pmHDUAlloc(extname);
+            bool rc = pmHDUReadHeader(hdu, fits);
+            ok(rc == true, "pmHDUReadHeader() returned TRUE");
+            ok(hdu->header != NULL &&
+               psMemCheckMetadata(hdu->header), "pmHDUReadHeader() correctly returned the hdu->header member");
+
+            // check for the extra metadata items
+            psS32 intItem = psMetadataLookupS32(NULL, hdu->header, "MYINT");
+            psF32 fltItem = psMetadataLookupF32(NULL, hdu->header, "MYFLT");
+            psF64 dblItem = psMetadataLookupF64(NULL, hdu->header, "MYDBL");
+            psMetadataItem* boolItem = psMetadataLookup(hdu->header, "MYBOOL");
+            psString strItem = psMetadataLookupStr(NULL, hdu->header, "MYSTR");
+
+            ok(intItem == hdunum, "Retrieved psS32 metadata item from file");
+            ok(fabsf(fltItem - 1.0f/(float)(1+hdunum)) <= FLT_EPSILON,
+               "Retrieved psF32 metadata item from file.  Got %f vs %f",
+               fltItem,1.0f/(float)(1+hdunum));
+            ok(abs(dblItem - 1.0/(double)(1+hdunum)) <= DBL_EPSILON,
+               "Retrieved psF64 metadata item from file.  Got %g vs %g",
+               dblItem, 1.0/(double)(1+hdunum));
+            ok(boolItem != NULL && boolItem->type == PS_DATA_BOOL,
+               "Retrieved psBool metadata item from file");
+            ok(strItem != NULL && strncmp(strItem,extname,strlen(extname)) == 0,
+               "Retrieved string metadata item from file.  Got '%s' vs '%s' (%d)",
+               strItem,extname,strlen(extname));
+            psFree(hdu);
+            ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks (header %d)", hdunum);
+        }
+
+
+        // Call pmHDUReadHeader() with NULL pmHDU input.  Should returne false.
+        {
+            psMemId id = psMemGetId();
+//            pmHDU *hdu = pmHDUAlloc(NULL);
+            bool rc = pmHDUReadHeader(NULL, fits);
+            ok(rc == false, "pmHDUReadHeader() returned FALSE with NULL hdu input");
+            ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+        }
+
+        // Call pmHDUReadHeader() with NULL pmFits input.  Should returne false.
+        {
+            psMemId id = psMemGetId();
+            pmHDU *hdu = pmHDUAlloc(NULL);
+            bool rc = pmHDUReadHeader(hdu, NULL);
+            ok(rc == false, "pmHDUReadHeader() returned FALSE with NULL pmFits input");
+            psFree(hdu);
+            ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+        }
+
+        psFree(fits);
+    }
+
+
+/* HERE
+    // tst_psFitsWriteHeader()
+    {
+        psMemId id = psMemGetId();
+        ok(createFitsFile2(), "Created 'multi' FITS file");
+
+        psMetadata* header   = psMetadataAlloc();
+        psFits*     fitsFile = psFitsOpen(fitsFilename,"a+");
+
+        // Test psFitsReadWrite generates files from psFitsWriteImage which 
+        // calls psFitsWriteHeader so these additional tests check for error conditions
+        // Attempt call function with NULL metadata
+        ok(!psFitsWriteHeader(fitsFile, NULL), "Expected return of true for NULL metadata pointer");
+        psFree(fitsFile);
+
+        // Attempt to call function with NULL fits
+        ok(!psFitsWriteHeader(NULL, header), "Expected return of true for NULL fits file pointer");
+        psFree(header);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+HERE*/
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tst_pmAstrometry.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tst_pmAstrometry.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tst_pmAstrometry.c	(revision 22157)
@@ -0,0 +1,615 @@
+/** @file  tst_pmAstrometry.c
+ *
+ *  @brief Contains the tests: pmAstrometry.[ch].  The pmxxxAlloc()
+ *  and psFree() functionality are used here.
+ *
+ *  @author GLG, MHPCC
+ *
+ *  XXX: Untested: pmFPACheckParents()
+ *  XXX: Create the pmHDU alloc/free function, test them here
+ *
+ *  @version $Revision: 1.6 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-01-26 21:10:51 $
+ *
+ *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+ */
+
+#include "config.h"
+#include <math.h>
+#include <string.h>
+#include "psTest.h"
+#include "pslib_strict.h"
+#include "pmAstrometry.h"
+
+static psS32 testFPAAlloc(void);
+static psS32 testChipAlloc(void);
+static psS32 testCellAlloc(void);
+static psS32 testReadoutAlloc(void);
+
+testDescription tests[] = {
+                              {testFPAAlloc,739,"pmFPAAlloc",0,false},
+                              {testChipAlloc,740,"pmChipAlloc",0,false},
+                              {testCellAlloc,741,"pmCellAlloc",0,false},
+                              {testReadoutAlloc,742,"pmReadoutAlloc",0,false},
+                              {NULL}
+                          };
+
+#define CHIP_ALLOC_NAME "ChipName"
+#define CELL_ALLOC_NAME "CellName"
+#define MISC_NUM 32
+#define MISC_NAME "META00"
+#define MISC_NAME2 "META01"
+#define NUM_BIAS_DATA 10
+#define TEST_NUM_ROWS 32
+#define TEST_NUM_COLS 32
+
+psPlaneTransform *PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM()
+{
+    psPlaneTransform *pt = psPlaneTransformAlloc(1, 1);
+    pt->x->coeff[1][0] = 1.0;
+    pt->y->coeff[0][1] = 1.0;
+    return(pt);
+}
+
+psPlaneDistort *PS_CREATE_4D_IDENTITY_PLANE_DISTORT()
+{
+    psPlaneDistort *pd = psPlaneDistortAlloc(1, 1, 1, 1);
+    pd->x->coeff[1][0][0][0] = 1.0;
+    pd->y->coeff[0][1][0][0] = 1.0;
+    return(pd);
+}
+
+/******************************************************************************
+generateSimpleFPA(): This function generates a pmFPA data structure and then
+populates its members with real data.  We do this to ensure that the data is
+later being psFree()'ed correctly.
+ *****************************************************************************/
+pmFPA *generateSimpleFPA()
+{
+    psBool rc;
+    pmFPA* fpa = pmFPAAlloc(psMetadataAlloc());
+
+    if (fpa == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc() returned a NULL.");
+        return(NULL);
+    }
+
+    //
+    // Test and create camera metadata.
+    //
+    if (fpa->camera == NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: fpa->camera is NULL.");
+        psFree(fpa);
+        return(NULL);
+    } else {
+        rc = psMetadataAddS32((psMetadata *) fpa->camera, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+        if (rc == false) {
+            psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: could not add metadata to fpa->camera.");
+            psFree(fpa);
+            return(NULL);
+        }
+        psS32 tmpS32 = psMetadataLookupS32(&rc, fpa->camera, MISC_NAME);
+        if ((rc == false) || (tmpS32 != MISC_NUM)) {
+            psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: could not read metadata from fpa->camera.");
+            psFree(fpa);
+            return(NULL);
+        }
+    }
+
+    //
+    // Create various transforms and projections.
+    //
+    fpa->fromTangentPlane = PS_CREATE_4D_IDENTITY_PLANE_DISTORT();
+    fpa->toTangentPlane = PS_CREATE_4D_IDENTITY_PLANE_DISTORT();
+    fpa->projection = psProjectionAlloc(0.0,0.0,10.0,10.0,PS_PROJ_TAN);
+
+    //
+    // Ensure fpa concepts metadata was allocated properly.
+    //
+    if (fpa->concepts == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set fpa->concepts.");
+        psFree(fpa);
+        return(NULL);
+    } else {
+        rc = psMetadataAddS32(fpa->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+        if (rc == false) {
+            psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: could not add data to fpa->concepts.");
+            psFree(fpa);
+            return(NULL);
+        }
+        psS32 tmpS32 = psMetadataLookupS32(&rc, fpa->concepts, MISC_NAME);
+        if ((rc == false) || (tmpS32 != MISC_NUM)) {
+            psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: could not read metadata from fpa->concepts.");
+            psFree(fpa);
+            return(NULL);
+        }
+    }
+
+    //
+    // Create ->analysis metadata.
+    //
+    fpa->analysis = psMetadataAlloc();
+    rc = psMetadataAddS32(fpa->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+
+    //
+    // We test the fpa->chips array later.
+    //
+
+    //
+    // How to test the p_pmHDU *hdu member?
+    //
+
+    //
+    // Create ->phu metadata.
+    //
+    fpa->phu = psMetadataAlloc();
+    rc = psMetadataAddS32(fpa->phu, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+
+    return(fpa);
+}
+
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetLevel(PS_LOG_INFO);
+    psLogSetFormat("HLNM");
+
+    return ! runTestSuite(stderr,"pmAstrometry",tests,argc,argv);
+}
+
+/******************************************************************************
+testFPAAlloc()
+    1: We ensure that pmFPAAlloc() properly allocates a pmFPA struct.
+    2: We populate the members with real data to ensure they are being
+       free'ed correctly.
+ *****************************************************************************/
+static psS32 testFPAAlloc(void)
+{
+    psMetadata *camera = psMetadataAlloc();
+    pmFPA* fpa = pmFPAAlloc(camera);
+
+    if (fpa == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc() returned a NULL.");
+        return 1;
+    }
+
+    if (fpa->fromTangentPlane != NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set ->fromTangentPlane to NULL.");
+        return 2;
+    }
+
+    if (fpa->toTangentPlane != NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set ->toTangentPlane to NULL.");
+        return 3;
+    }
+    if (fpa->projection != NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set ->projection to NULL.");
+        return 4;
+    }
+
+    if (fpa->concepts == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set ->concepts.");
+        return 5;
+    }
+
+    if (fpa->analysis != NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set ->analysis to NULL.");
+        return 6;
+    }
+
+    if (fpa->camera != camera) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set ->camera.");
+        return 7;
+    }
+
+    if (fpa->chips == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set ->chips.");
+        return 8;
+    }
+
+    if (fpa->hdu != NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set ->hdu to NULL.");
+        return 9;
+    }
+    psFree(fpa);
+
+    //
+    // Populate the pmFPA struct with real data to ensure they were
+    // psFree()'ed correctly.
+    //
+    fpa = generateSimpleFPA();
+    if (fpa == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: generateSimpleFPA() returned NULL.");
+        return(15);
+    }
+    psFree(fpa);
+
+    return(0);
+}
+
+/******************************************************************************
+generateSimpleChip(): This function generates a pmChip data structure and then
+populates its members with real data.  We do this to ensure that the data is
+later being psFree()'ed correctly.
+ *****************************************************************************/
+pmChip *generateSimpleChip(pmFPA *fpa)
+{
+    psBool rc;
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    if (chip == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmChipAlloc returned a NULL.");
+        return(NULL);
+    }
+    chip->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    chip->fromFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    //
+    // We already ensured that chip->concepts was working properly.
+    //
+
+    //
+    // Create ->analysis metadata.
+    //
+    chip->analysis = psMetadataAlloc();
+    rc = psMetadataAddS32(chip->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+
+    //
+    // We test the chip->cells array later.
+    //
+
+    //
+    // How to test the p_pmHDU *hdu member?
+    //
+
+    return(chip);
+}
+
+static psS32 testChipAlloc(void)
+{
+    pmFPA* fpa = generateSimpleFPA();
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    if (chip == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmChipAlloc returned a NULL.");
+        return 1;
+    }
+
+    if (chip->col0 != -1) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->col0 set improperly.\n");
+        return 5;
+    }
+
+    if (chip->row0 != -1) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->row0 set improperly.\n");
+        return 6;
+    }
+
+    if (chip->toFPA != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->toChip set improperly.\n");
+        return 7;
+    }
+
+    if (chip->fromFPA != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->toFPA set improperly.\n");
+        return 8;
+    }
+
+    if (chip->concepts == NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->concepts set improperly.\n");
+        return 21;
+    } else {
+        psMetadataItem *tmpMeta = psMetadataLookup(chip->concepts, "CHIP.NAME");
+        if (0 != strcmp((char *) tmpMeta->data.V, CHIP_ALLOC_NAME)) {
+            psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: The metadata was set improperly.\n");
+            return (32);
+        }
+        // XXX: Code a test to ensure the metadata has the correct type
+    }
+
+    if (chip->analysis != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->analysis set improperly.\n");
+        return 10;
+    }
+
+    if (chip->cells == NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->cells set improperly.\n");
+        return 22;
+    }
+
+    if (chip->parent != fpa) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->parent set improperly.\n");
+        return 23;
+    }
+
+    if (chip->valid != false) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->valid set improperly.\n");
+        return 24;
+    }
+
+    if (chip->hdu != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->hdu set improperly.\n");
+        return 25;
+    }
+    psFree(fpa);
+
+    //
+    // Populate the pmChip struct with real data to ensure they were
+    // psFree()'ed correctly.
+    //
+    fpa = generateSimpleFPA();
+    chip = generateSimpleChip(fpa);
+    psFree(fpa);
+
+    return(0);
+}
+
+/******************************************************************************
+generateSimpleCell(): This function generates a pmCell data structure and then
+populates its members with real data.  We do this to ensure that the data is
+later being psFree()'ed correctly.
+ *****************************************************************************/
+pmCell *generateSimpleCell(pmFPA *fpa, pmChip *chip)
+{
+    psBool rc;
+    pmCell *cell = pmCellAlloc(chip, (psMetadata *) fpa->camera, CELL_ALLOC_NAME);
+    if (cell == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmCellAlloc returned a NULL.");
+        return(NULL);
+    }
+    cell->toChip = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    cell->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    cell->toSky = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    //
+    // We already ensured that cell->concepts was working properly.
+    //
+
+    //
+    // Test camera metadata.
+    //
+    if (cell->camera == NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->camera is NULL.");
+        psFree(fpa);
+        return(NULL);
+    } else {
+        rc = psMetadataAddS32((psMetadata *) cell->camera, PS_LIST_HEAD, MISC_NAME2, 0, NULL, MISC_NUM);
+        if (rc == false) {
+            psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: could not add metadata to cell->camera.");
+            psFree(fpa);
+            return(NULL);
+        }
+        psS32 tmpS32 = psMetadataLookupS32(&rc, cell->camera, MISC_NAME2);
+        if ((rc == false) || (tmpS32 != MISC_NUM)) {
+            psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: could not read metadata from cell->camera.");
+            psFree(fpa);
+            return(NULL);
+        }
+    }
+
+    //
+    // Create ->analysis metadata.
+    //
+    cell->analysis = psMetadataAlloc();
+    rc = psMetadataAddS32(cell->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+
+    //
+    // We test the cell->readouts array later.
+    //
+
+    //
+    // How to test the p_pmHDU *hdu member?
+    //
+
+    return(cell);
+}
+
+static psS32 testCellAlloc(void)
+{
+    pmFPA* fpa = generateSimpleFPA();
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    pmCell *cell = pmCellAlloc(chip, (psMetadata *) fpa->camera, CELL_ALLOC_NAME);
+    if (cell == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmCellAlloc returned a NULL.n");
+        return 3;
+    }
+
+    if (cell->col0 != -1) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->col0 set improperly.\n");
+        return 5;
+    }
+
+    if (cell->row0 != -1) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->row0 set improperly.\n");
+        return 6;
+    }
+
+    if (cell->toChip != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->toChip set improperly.\n");
+        return 7;
+    }
+
+    if (cell->toFPA != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->toFPA set improperly.\n");
+        return 8;
+    }
+
+    if (cell->toSky != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->toSky set improperly.\n");
+        return 9;
+    }
+
+    if (cell->concepts == NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->concepts set improperly.\n");
+        return 21;
+    } else {
+        psMetadataItem *tmpMeta = psMetadataLookup(cell->concepts, "CELL.NAME");
+        if (0 != strcmp((char *) tmpMeta->data.V, CELL_ALLOC_NAME)) {
+            psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: The metadata was set improperly.\n");
+            return (32);
+        }
+        // XXX: Code a test to ensure the metadata has the correct type
+    }
+
+    if (cell->camera != fpa->camera) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->camera set improperly.\n");
+        return 20;
+    }
+
+    if (cell->analysis != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->analysis set improperly.\n");
+        return 10;
+    }
+
+    if (cell->readouts == NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->readouts set improperly.\n");
+        return 22;
+    }
+
+    if (cell->parent != chip) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->parent set improperly.\n");
+        return 23;
+    }
+
+    if (cell->valid != false) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->valid set improperly.\n");
+        return 24;
+    }
+
+    if (cell->hdu != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->hdu set improperly.\n");
+        return 27;
+    }
+    psFree(fpa);
+
+    //
+    // Populate the pmCell struct with real data to ensure they were
+    // psFree()'ed correctly.
+    //
+    fpa = generateSimpleFPA();
+    chip = generateSimpleChip(fpa);
+    cell = generateSimpleCell(fpa, chip);
+    psFree(fpa);
+
+    return(0);
+}
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.  We do this to ensure that the data is
+later being psFree()'ed correctly.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmFPA *fpa, pmChip *chip, pmCell *cell)
+{
+    psBool rc;
+    pmReadout *readout = pmReadoutAlloc(cell);
+    if (readout == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmReadoutAlloc returned a NULL.");
+        return(NULL);
+    }
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+
+    //
+    // Create a psList of bias data.
+    //
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        if (readout->bias == NULL) {
+            readout->bias = psListAlloc(tmpImage);
+        } else {
+            psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        }
+    }
+
+    //
+    // Test readout->analysis metadata.
+    //
+    if (readout->analysis == NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: readout->analysis is NULL.");
+        psFree(fpa);
+        return(NULL);
+    } else {
+        rc = psMetadataAddS32((psMetadata *) readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+        if (rc == false) {
+            psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: could not add metadata to readout->analysis.");
+            psFree(fpa);
+            return(NULL);
+        }
+    }
+
+    return(readout);
+}
+
+
+static psS32 testReadoutAlloc(void)
+{
+    pmFPA* fpa = generateSimpleFPA();
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    pmCell *cell = pmCellAlloc(chip, (psMetadata *) fpa->camera, CELL_ALLOC_NAME);
+    pmReadout *readout = pmReadoutAlloc(cell);
+    if (readout == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmReadoutAlloc returned a NULL.\n");
+        return 4;
+    }
+
+    if (readout->col0 != -1) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->col0 set improperly.\n");
+        return 5;
+    }
+
+    if (readout->row0 != -1) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->row0 set improperly.\n");
+        return 6;
+    }
+
+    if (readout->colBins != -1) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->colBins set improperly.\n");
+        return 7;
+    }
+
+    if (readout->rowBins != -1) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->colBins set improperly.\n");
+        return 8;
+    }
+
+    if (readout->image != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->image set improperly.\n");
+        return 10;
+    }
+
+    if (readout->mask != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->mask set improperly.\n");
+        return 12;
+    }
+
+    if (readout->weight != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->weight set improperly.\n");
+        return 14;
+    }
+
+    if (readout->bias != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->bias set improperly.\n");
+        return 16;
+    }
+
+    if (readout->analysis == NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->analysis set improperly.\n");
+        return 18;
+    }
+
+    if (readout->parent != cell) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->parent set improperly.\n");
+        return 20;
+    }
+    psFree(fpa);
+
+    //
+    // Populate the pmReadout struct with real data to ensure they were
+    // psFree()'ed correctly.
+    //
+    fpa = generateSimpleFPA();
+    chip = generateSimpleChip(fpa);
+    cell = generateSimpleCell(fpa, chip);
+    readout = generateSimpleReadout(fpa, chip, cell);
+    psFree(fpa);
+
+    return(0);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tst_pmAstrometry01.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tst_pmAstrometry01.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/astrom/tst_pmAstrometry01.c	(revision 22157)
@@ -0,0 +1,675 @@
+/** @file  tst_psAstrometry01.c
+*
+*  @brief This code will test the pmFPA hierarchy transform code in psAstrometry.[ch]
+*
+*  @author GLG, MHPCC
+*
+*  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-03-06 22:53:47 $
+*
+* XXX: Add tests were the coordinate does not transform to any legitimate cell
+* or chip, or FPA, or whatever.
+*
+* XXX: For each function, add tests for bad input parameters, as well as failed transforms.
+*
+* XXX: Must test pmFPASelectChip() and pmFPAExcludeChip().
+*
+*  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+*/
+#include "config.h"
+#include <math.h>
+#include <string.h>
+#include "psTest.h"
+#include "pslib_strict.h"
+#include "pmAstrometry.h"
+static psS32 test3( void );
+static psS32 test4( void );
+static psS32 test5( void );
+
+testDescription tests[] = {
+                              {test3, 666, "pmAstrometry focal plane transformations", 0, false},
+                              {test4, 667, "pmCheckParents()", 0, false},
+                              {test5, 668, "pmFPASelectChip() and pmFPAExcludeChip()", 0, false},
+                              {NULL}
+                          };
+
+psS32 currentId = 0;
+psS32 memLeaks = 0;
+psS32 main( psS32 argc, char* argv[] )
+{
+    psLogSetLevel( PS_LOG_INFO );
+    psLogSetFormat("HLNM");
+
+    return ( ! runTestSuite( stderr, "psAstrometry", tests, argc, argv ) );
+}
+
+#define PS_PERCENT_COMPARE(X, Y, PERCENT_FRACTION) (fabs((Y)-(X))/fabs(X) < (PERCENT_FRACTION))
+#define VERBOSE 0
+#define NUM_READOUTS 1
+#define READOUT_NUM_ROWS 10
+#define READOUT_NUM_COLS 10
+
+#define NUM_CELLS 4
+#define CELL_GAP 2
+#define CELL_WIDTH READOUT_NUM_COLS
+#define CELL_HEIGHT READOUT_NUM_ROWS
+#define CELL_MIN_X 0
+#define CELL_MAX_X CELL_HEIGHT
+#define CELL_MIN_Y 0
+#define CELL_MAX_Y CELL_WIDTH
+
+#define NUM_CHIPS 2
+#define CHIP_GAP 2
+#define CHIP_MIN_X 0
+#define CHIP_MAX_X CELL_HEIGHT
+#define CHIP_MIN_Y 0
+#define CHIP_MAX_Y ((NUM_CELLS * CELL_WIDTH) + ((NUM_CELLS) * CELL_GAP))
+#define CHIP_WIDTH CHIP_MAX_Y
+#define CHIP_HEIGHT CHIP_MAX_X
+
+#define NUM_FPAS 1
+#define FPA_MIN_X 0
+#define FPA_MAX_X CHIP_HEIGHT
+#define FPA_MIN_Y 0
+#define FPA_MAX_Y (((NUM_CHIPS) * CHIP_WIDTH) + (((NUM_CHIPS)-1) * CHIP_GAP))
+
+#define PROJECTION_SCALE_X 1.0
+#define PROJECTION_SCALE_Y 1.0
+
+psPlaneTransform *PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM()
+{
+    psPlaneTransform *pt = psPlaneTransformAlloc(1, 1);
+    pt->x->coeff[1][0] = 1.0;
+    pt->y->coeff[0][1] = 1.0;
+    return(pt);
+}
+
+psPlaneDistort *PS_CREATE_4D_IDENTITY_PLANE_DISTORT()
+{
+    psPlaneDistort *pd = psPlaneDistortAlloc(1, 1, 1, 1);
+    pd->x->coeff[1][0][0][0] = 1.0;
+    pd->y->coeff[0][1][0][0] = 1.0;
+    return(pd);
+}
+
+/******************************************************************************
+genSystem(): This routine will create a system of FPAs/Chips/Cells/Readouts.  For
+simplicity, an FPA is defined as a linear array of chips, and a chip is
+defined as a linear array of cells, both in the y (cols) direction.  The
+transforms between the various layers take into account the cell/chip and
+the boundaries between each.
+ *****************************************************************************/
+pmFPA *genSystem()
+{
+    //
+    // Create top pmFPA structure.
+    //
+    const psMetadata *camera = psMetadataAlloc();
+    pmFPA* myFPA = pmFPAAlloc(camera);
+    if (myFPA == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc() returned a NULL.\n");
+        return NULL;
+    }
+    myFPA->fromTangentPlane = PS_CREATE_4D_IDENTITY_PLANE_DISTORT();
+    myFPA->toTangentPlane = PS_CREATE_4D_IDENTITY_PLANE_DISTORT();
+    myFPA->projection = psProjectionAlloc(0.0,0.0,PROJECTION_SCALE_X,PROJECTION_SCALE_X,PS_PROJ_TAN);
+
+    myFPA->chips = psArrayRealloc(myFPA->chips, NUM_CHIPS);
+    for (psS32 chipID=0 ; chipID<NUM_CHIPS ; chipID++) {
+        pmChip *myChip = pmChipAlloc(myFPA, "ChipName");
+        if (myChip == NULL) {
+            psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmChipAlloc() returned a NULL.\n");
+            return NULL;
+        }
+        myFPA->chips->data[chipID] = (psPtr *) myChip;
+        myChip->row0 = 0;
+        myChip->col0 = chipID * (CHIP_WIDTH + CHIP_GAP);
+
+        // We create the transforms between the chip and FPA.  The
+        // transform is a simple identity transform.
+        myChip->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+        myChip->toFPA->y->coeff[0][0] = (psF64) myChip->col0;
+        myChip->toFPA->y->coeff[1][1] = 0.0;
+        myChip->toFPA->x->coeff[0][0] = (psF64) myChip->row0;
+        myChip->toFPA->x->coeff[1][1] = 0.0;
+
+        myChip->fromFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+        myChip->fromFPA->y->coeff[0][0] = (psF64) (- myChip->col0);
+        myChip->fromFPA->y->coeff[1][1] = 0.0;
+        myChip->fromFPA->x->coeff[0][0] = (psF64) (- myChip->row0);
+        myChip->fromFPA->x->coeff[1][1] = 0.0;
+
+        myChip->cells = psArrayRealloc(myChip->cells, NUM_CELLS);
+        for (psS32 cellID=0 ; cellID<NUM_CELLS ; cellID++) {
+            pmCell *myCell = pmCellAlloc(myChip, NULL, "CellName");
+            if (myCell == NULL) {
+                psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmCellAlloc() returned a NULL.\n");
+                return NULL;
+            }
+            myChip->cells->data[cellID] = (psPtr *) myCell;
+            myCell->row0 = 0;
+            myCell->col0 = cellID * (CELL_WIDTH + CELL_GAP);
+            myCell->toChip = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+            myCell->toChip->y->coeff[0][0] = (psF64) myCell->col0;
+            myCell->toChip->y->coeff[1][1] = 0.0;
+            myCell->toChip->x->coeff[0][0] = (psF64) myCell->row0;
+            myCell->toChip->x->coeff[1][1] = 0.0;
+
+            myCell->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+            myCell->toFPA->y->coeff[0][0] = (psF64) (myCell->col0 + myChip->col0);
+            myCell->toFPA->y->coeff[1][1] = 0.0;
+            myCell->toFPA->x->coeff[0][0] = (psF64) (myCell->row0 + myChip->row0);
+            myCell->toFPA->x->coeff[1][1] = 0.0;
+
+            myCell->toSky = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+            myCell->toSky->y->coeff[0][0] = myCell->toFPA->y->coeff[0][0];
+            myCell->toSky->y->coeff[1][1] = 0.0;
+            myCell->toSky->x->coeff[0][0] = myCell->toFPA->x->coeff[0][0];
+            myCell->toSky->x->coeff[1][1] = 0.0;
+
+            myCell->readouts = psArrayRealloc(myCell->readouts, NUM_READOUTS);
+            for (psS32 readoutID=0 ; readoutID<NUM_READOUTS ; readoutID++) {
+                pmReadout *myReadout = pmReadoutAlloc(myCell);
+                if (myReadout == NULL) {
+                    psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmReadoutAlloc() returned a NULL.\n");
+                    return NULL;
+                }
+                myCell->readouts->data[readoutID] = (psPtr *) myReadout;
+                myReadout->image = psImageAlloc(READOUT_NUM_COLS, READOUT_NUM_ROWS, PS_TYPE_F32);
+                for (psS32 row=0;row<READOUT_NUM_ROWS;row++) {
+                    for(psS32 col=0;col<READOUT_NUM_COLS;col++) {
+                        myReadout->image->data.F32[row][col] = (psF32) ((chipID * (CHIP_WIDTH + CHIP_GAP)) +
+                                                               (cellID * (CELL_WIDTH + CELL_GAP)));
+                    }
+                }
+                myReadout->row0 = myCell->row0;
+                myReadout->col0 = myCell->col0;
+                myReadout->rowBins = 0;
+                myReadout->colBins = 0;
+            }
+            if (VERBOSE) {
+                printf("\n\n\n\nFor chip %d cell %d the cell->toFPA transform is:\n", chipID, cellID);
+                PS_PRINT_PLANE_TRANSFORM(myCell->toFPA);
+                printf("\n\n\n\nFor chip %d cell %d the cell->toChip transform is:\n", chipID, cellID);
+                PS_PRINT_PLANE_TRANSFORM(myCell->toChip);
+                printf("\n\n\n\nFor chip %d cell %d the cell->toSky transform is:\n", chipID, cellID);
+                PS_PRINT_PLANE_TRANSFORM(myCell->toSky);
+            }
+        }
+        if (VERBOSE) {
+            printf("\n\n\n\nFor chip %d the chip->toFPA transform is:\n", chipID);
+            PS_PRINT_PLANE_TRANSFORM(myChip->toFPA);
+            printf("\n\n\n\nFor chip %d the chip->fromFPA transform is:\n", chipID);
+            PS_PRINT_PLANE_TRANSFORM(myChip->fromFPA);
+        }
+    }
+
+    return(myFPA);
+}
+
+
+/******************************************************************************
+This routine tests many Astrometry functions.  It loops through all valid
+pixels of all cells of all chips and computes the corresponding (x,y)
+coordinates in the FPA plane.  It then calls the pmChipInFPA() then
+pmCellInFPA() with the FPA coordinate and determines the chip/cell that that
+coordinate corresponds to.  Following that it does a variety of tests on the
+various functions that tharnsform coordinates within the pmFPA hierarchy.
+ 
+List of tested functions:
+    pmCellInFPA()  yes
+    pmChipInFPA()  yes
+    pmCellInChip()  yes
+ 
+    pmCoordCellToFPA()  yes
+    pmCoordChipToFPA()  yes
+    pmCoordFPAToChip()  yes
+    pmCoordCellToChip()  yes
+    pmCoordChipToCell()  yes
+ 
+    pmCoordFPAToTP()  yes
+    pmCoordTPToFPA()  yes
+ 
+    pmCoordTPToSky()  yes
+    pmCoordSkyToTP()  yes
+    pmCoordSkyToCell()  yes
+    pmCoordCellToSky()  yes
+    pmCoordCellToSkyQuick() yes
+    pmCoordSkyToCellQuick() yes
+ *****************************************************************************/
+psS32 test3( void )
+{
+    psS32 x;
+    psS32 y;
+    psPlane fpaCoord;
+    pmFPA *myFPA = genSystem();
+    pmCell *myCell = NULL;
+    psPlane chipCoord;
+    psPlane cellCoord;
+    psPlane testCoord;
+    psSphere *skyCoord = psSphereAlloc();
+    // XXX: This code causes a seg fault.
+    //    psSphere skyTmp;
+    //    psMemCheckType(PS_DATA_SPHERE, &skyTmp);
+    psPlane tpCoord;
+    psS32 testStatus = 0;
+
+    //
+    // I'm not convinced that the p_psProject() and p_psDeproject() functions work
+    // correctly.  If we project a set of coordinates over a wide range of (R, D)
+    // values, then deproject them, the original (R, D) values are only produced
+    // when D is larger than 0.  This code demonstrates that.  I also created tests
+    // that currently fail in tst_psCoord01.c.  I have a workaround in the function
+    // XXXDeproject() in pmAstrometry.c.
+    //
+    if (0) {
+        // This loop goes from (R, D) -> (X, Y) -> (R, D)
+        psPlane planeCoord01;
+        psSphere skyCoord01;
+        psSphere skyCoord02;
+        #define DEG_INC 15.0
+
+        for (psF32 R = -90.0 ; R <= 90.0 ; R+= DEG_INC) {
+            for (psF32 D = -90.0 ; D <= 90.0 ; D+= DEG_INC) {
+                if ((fabs(R) != 90.0) && (fabs(D) != 90.0)) {
+                    skyCoord01.r = DEG_TO_RAD(R);
+                    skyCoord01.d = DEG_TO_RAD(D);
+                    p_psProject(&planeCoord01, &skyCoord01, myFPA->projection);
+                    p_psDeproject(&skyCoord02, &planeCoord01, myFPA->projection);
+                    printf("(%.2fr %.2fd) (%.2fr %.2fd) -> (%.2f %.2f) -> (%.2fr %.2fd)", R, D,
+                           skyCoord01.r, skyCoord01.d,
+                           planeCoord01.x, planeCoord01.y,
+                           skyCoord02.r, skyCoord02.d);
+                    if ((fabs(skyCoord01.r - skyCoord02.r) < FLT_EPSILON) &&
+                            (fabs(skyCoord01.d - skyCoord02.d) < FLT_EPSILON)) {
+                        printf(": CORRECT\n");
+                    } else {
+                        printf(": WRONG\n");
+                    }
+                }
+            }
+        }
+        psFree(myFPA);
+        return(0);
+    }
+    if (0) {
+        // This loop goes from (X, Y) -> (R, D) -> (X, Y)
+        #define SPACE_INC 4.0
+        for(testCoord.x=-CELL_HEIGHT;testCoord.x<=CELL_HEIGHT;testCoord.x+=SPACE_INC)
+        {
+            for (testCoord.y=-CELL_WIDTH;testCoord.y<=CELL_WIDTH;testCoord.y+=SPACE_INC) {
+                psPlane planeCoord01;
+                psPlane planeCoord02;
+                psSphere skyCoord01;
+                psSphere skyCoord02;
+                p_psDeproject(&skyCoord01, &testCoord, myFPA->projection);
+                p_psProject(&planeCoord01, &skyCoord01, myFPA->projection);
+                p_psDeproject(&skyCoord02, &planeCoord01, myFPA->projection);
+                p_psProject(&planeCoord02, &skyCoord02, myFPA->projection);
+                printf("Plane: (%.2f %.2f) -> (%.2fr %.2fd) -> (%.2f %.2f)\n",
+                       testCoord.x, testCoord.y,
+                       skyCoord01.r, skyCoord01.d,
+                       planeCoord01.x, planeCoord01.y);
+                /*
+                                printf("Plane: (%.2f %.2f) -> (%.2f %.2f) -> (%.2f %.2f)\n",
+                                        testCoord.x, testCoord.y, planeCoord01.x, planeCoord01.y,
+                                        planeCoord02.x, planeCoord02.y);
+                                printf("Sphere: (%.2f %.2f) -> (%.2f %.2f)\n",
+                                        skyCoord01.r, skyCoord01.d, skyCoord02.r, skyCoord02.d);
+                                printf("Plane: (%.2f %.2f) -> (%.2fd %.2fr) -> (%.2f %.2f) -> (%.2fd %.2fr) -> (%.2f %.2f)\n",
+                                        testCoord.x, testCoord.y,
+                                        skyCoord01.r, skyCoord01.d,
+                                        planeCoord01.x, planeCoord01.y,
+                                        skyCoord02.r, skyCoord02.d,
+                                        planeCoord02.x, planeCoord02.y);
+                */
+            }
+        }
+        psFree(myFPA);
+        return(0);
+    }
+    //
+    // We iterate through all cells on all chips on the fpa.  We determine
+    // the expected fpaCcoord.
+    //
+
+    for (psS32 chip=0;chip<NUM_CHIPS;chip++) {
+        for (psS32 cell=0;cell<NUM_CELLS;cell++) {
+            for(x=0;x<CELL_HEIGHT;x++) {
+                for (y=0;y<CELL_WIDTH;y++) {
+                    fpaCoord.x = (psF64) x;
+                    fpaCoord.y = (psF64) (y + (chip * (CHIP_WIDTH + CHIP_GAP)) +
+                                          (cell * (CELL_WIDTH + CELL_GAP)));
+                    if (VERBOSE) {
+                        printf("------------------ (%.2f, %.2f) ------------------\n", fpaCoord.x, fpaCoord.y);
+                        printf("(chip, cell, x, y) is (%d, %d, %d, %d)\n", chip, cell, x, y);
+                    }
+                    pmChip* tmpChip = pmChipInFPA(&fpaCoord, myFPA);
+                    myCell = pmCellInFPA(&fpaCoord, myFPA);
+
+                    if ((myCell == NULL) || (tmpChip == NULL)) {
+                        if (tmpChip == NULL) {
+                            printf("TEST ERROR: pmChipInFPA() returned NULL\n");
+                            testStatus = 1;
+                        } else if (myCell == NULL) {
+                            printf("TEST ERROR: pmCellInFPA(): returned NULL\n");
+                            testStatus = 1;
+                        }
+                    } else {
+                        pmCoordFPAToChip(&chipCoord, &fpaCoord, tmpChip);
+                        pmCoordChipToCell(&cellCoord, &chipCoord, myCell);
+
+                        if (x != (psS32) cellCoord.x) {
+                            printf("TEST ERROR: pmCoordFPAToChip()->pmCoordChipToCell(): x coord was %d (%f), should be %d\n", (psS32) cellCoord.x, cellCoord.x, x);
+                            testStatus = 1;
+                        }
+                        if (y != (psS32) cellCoord.y) {
+                            printf("TEST ERROR: pmCoordFPAToChip()->pmCoordChipToCell(): y coord was %d (%f), should be %d\n", (psS32) cellCoord.y, cellCoord.y, y);
+                            testStatus = 1;
+                        }
+
+                        pmCoordCellToChip(&testCoord, &cellCoord, myCell);
+                        if (fabs(testCoord.x - chipCoord.x) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordCellToChip() x coord was %.2f, should be %d\n", cellCoord.x, x);
+                            testStatus = 1;
+                        }
+                        if (fabs(testCoord.y - chipCoord.y) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordCellToChip() y coord was %.2f, should be %d\n", cellCoord.y, y);
+                            testStatus = 1;
+                        }
+
+                        pmCell *myCell2 = pmCellInChip(&chipCoord, tmpChip);
+                        if (myCell2 != myCell) {
+                            printf("TEST ERROR: pmCellInChip() != pmCellInChip(pmChipInFPA()) (%p %p)\n", myCell2, myCell);
+                            testStatus = 1;
+                        }
+
+                        pmCoordChipToFPA(&testCoord, &chipCoord, tmpChip);
+                        if (fabs(testCoord.x - x) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordChipToFPA() x coord was %.2f, should be %d\n", cellCoord.x, x);
+                            testStatus = 1;
+                        }
+                        if (fabs(testCoord.y - fpaCoord.y) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordChipToFPA() y coord was %.2f, should be %.2f\n", cellCoord.y, fpaCoord.y);
+                            testStatus = 1;
+                        }
+
+                        pmCoordFPAToTP(&testCoord, &fpaCoord, 0.0, 0.0, myFPA);
+                        if (fabs(testCoord.x - fpaCoord.x) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordFPAToTP() x coord was %.2f, should be %d\n", cellCoord.x, x);
+                            testStatus = 1;
+                        }
+                        if (fabs(testCoord.y - fpaCoord.y) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordFPAToTP() y coord was %.2f, should be %d\n", cellCoord.y, y);
+                            testStatus = 1;
+                        }
+
+                        //
+                        // Test pmCoordTPToSky() -> pmCoordSkyToTP()
+                        //
+                        if (1) {
+                            psSphere *rc = pmCoordTPToSky(skyCoord, &testCoord, myFPA->projection);
+                            if (rc == NULL) {
+                                printf("pmCoordTPToSky() failed.\n");
+                            } else {
+                                psPlane *rc = pmCoordSkyToTP(&tpCoord, skyCoord, myFPA->projection);
+                                if (rc == NULL) {
+                                    printf("pmCoordSkyToTP() failed.\n");
+                                } else {
+                                    if (fabs(testCoord.x - tpCoord.x) > FLT_EPSILON) {
+                                        printf("TEST ERROR: pmCoordTPToSky()/pmCoordSkyToTP() x coord was %.2f, should be %.2f\n", tpCoord.x, testCoord.x);
+                                        testStatus = 1;
+                                    }
+                                    if (fabs(testCoord.y - tpCoord.y) > FLT_EPSILON) {
+                                        printf("TEST ERROR: pmCoordTPToSky()/pmCoordSkyToTP() y coord was %.2f, should be %.2f\n", tpCoord.y, testCoord.y);
+                                        testStatus = 1;
+                                    }
+                                    if (VERBOSE) {
+                                        printf("(%.2f %.2f) -> (%.2f %.2f) -> (%.2f %.2f)\n", testCoord.x, testCoord.y, skyCoord->r, skyCoord->d, tpCoord.x, tpCoord.y);
+                                    }
+                                }
+                            }
+                        }
+
+                        //
+                        // Test pmCoordCellToSky() -> pmCoordSkyToCell()
+                        //
+                        if (1) {
+                            psPlane tmpCellCoord;
+                            psSphere *rc = pmCoordCellToSky(skyCoord, &cellCoord, 0.0, 0.0, myCell);
+                            if (rc == NULL) {
+                                printf("pmCoordCellToSky() failed.\n");
+                            } else {
+                                psPlane *rc = pmCoordSkyToCell(&tmpCellCoord, skyCoord, 0.0, 0.0, myCell);
+                                if (rc == NULL) {
+                                    printf("pmCoordSkyToCell() failed.\n");
+                                } else {
+                                    if (fabs(cellCoord.x - tmpCellCoord.x) > FLT_EPSILON) {
+                                        printf("TEST ERROR: pmCoordCellToSky()/pmCoordSkyToCell() x coord was %.2f, should be %.2f\n", tmpCellCoord.x, cellCoord.x);
+                                        testStatus = 1;
+                                    }
+                                    if (fabs(cellCoord.y - tmpCellCoord.y) > FLT_EPSILON) {
+                                        printf("TEST ERROR: pmCoordCellToSky()/pmCoordSkyToCell() y coord was %.2f, should be %.2f\n", tmpCellCoord.y, cellCoord.y);
+                                        testStatus = 1;
+                                    }
+                                    if (VERBOSE) {
+                                        printf("(%.2f %.2f) -> (%.2f %.2f) -> (%.2f %.2f)\n", testCoord.x, testCoord.y, skyCoord->r, skyCoord->d, tmpCellCoord.x, tmpCellCoord.y);
+                                    }
+                                }
+                            }
+                        }
+
+                        //
+                        // Test pmCoordCellToSkyQuick() -> pmCoordSkyToCellQuick()
+                        // I'm not sure how to test this in a system with chip and cell gaps.
+                        // There's no way to create an accurate polynomial transform from
+                        // a cell to the sky where there are cell, or chip gaps.
+                        //
+                        if ((NUM_CHIPS == 1) && (NUM_CELLS == 1)) {
+                            psPlane tmpCellCoord;
+                            psSphere *rc = pmCoordCellToSkyQuick(skyCoord, &cellCoord, myCell);
+                            if (rc == NULL) {
+                                printf("pmCoordCellToSkyQuick() failed.\n");
+                            } else {
+                                psPlane *rc = pmCoordSkyToCellQuick(&tmpCellCoord, skyCoord, myCell);
+                                if (rc == NULL) {
+                                    printf("pmCoordSkyToCellQuick() failed.\n");
+                                } else {
+                                    if (fabs(cellCoord.x - tmpCellCoord.x) > FLT_EPSILON) {
+                                        printf("TEST ERROR: pmCoordCellToSky()/pmCoordSkyToCell() x coord was %.2f, should be %.2f\n", tmpCellCoord.x, cellCoord.x);
+                                        testStatus = 1;
+                                    }
+                                    if (fabs(cellCoord.y - tmpCellCoord.y) > FLT_EPSILON) {
+                                        printf("TEST ERROR: pmCoordCellToSky()/pmCoordSkyToCell() y coord was %.2f, should be %.2f\n", tmpCellCoord.y, cellCoord.y);
+                                        testStatus = 1;
+                                    }
+                                    if (VERBOSE) {
+                                        printf("(%.2f %.2f) -> (%.2f %.2f) -> (%.2f %.2f)\n", testCoord.x, testCoord.y, skyCoord->r, skyCoord->d, tmpCellCoord.x, tmpCellCoord.y);
+                                    }
+                                }
+                            }
+                        }
+
+                        pmCoordCellToFPA(&testCoord, &cellCoord, myCell);
+                        if (fabs(testCoord.x - fpaCoord.x) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordCellToFPA() x coord was %.2f, should be %d\n", cellCoord.x, x);
+                            testStatus = 1;
+                        }
+                        if (fabs(testCoord.y - fpaCoord.y) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordCellToFPA() y coord was %.2f, should be %d\n", cellCoord.y, y);
+                            testStatus = 1;
+                        }
+
+                        pmCoordTPToFPA(&testCoord, &fpaCoord, 0.0, 0.0, myFPA);
+                        if (fabs(testCoord.x - fpaCoord.x) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordTPToFPA() x coord was %.2f, should be %d\n", cellCoord.x, x);
+                            testStatus = 1;
+                        }
+                        if (fabs(testCoord.y - fpaCoord.y) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordTPToFPA() y coord was %.2f, should be %d\n", cellCoord.y, y);
+                            testStatus = 1;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    psFree(myFPA);
+    psFree(skyCoord);
+
+    return(testStatus);
+}
+
+/******************************************************************************
+test4(): This routine wil test the pmFPACheckParents() function.  We generate
+an pmFPA hierarchy, then set the parents of each readout/cell/chip to NULL,
+then call pmFPACheckParents() to restore them, then we ensure they were
+restored.
+ *****************************************************************************/
+psS32 test4( void )
+{
+    psS32 testStatus = 0;
+
+    //
+    // Generate a pmFPA hierarchy.
+    //
+    pmFPA *tmpFPA = genSystem();
+
+    //
+    // We set the parents of each readout/cell/chip to NULL.
+    //
+    for (psS32 chipID = 0; chipID < tmpFPA->chips->n ; chipID++) {
+        pmChip *tmpChip = (pmChip *) tmpFPA->chips->data[chipID];
+        tmpChip->parent = NULL;
+
+        for (psS32 cellID = 0; cellID < tmpChip->cells->n ; cellID++) {
+            pmCell *tmpCell = (pmCell *) tmpChip->cells->data[cellID];
+            tmpCell->parent = NULL;
+
+            for (psS32 readoutID = 0; readoutID < tmpCell->readouts->n ; readoutID++) {
+                pmReadout *tmpReadout = (pmReadout *) tmpCell->readouts->data[readoutID];
+                tmpReadout->parent = NULL;
+            }
+        }
+    }
+
+    //
+    // Ensure that pmFPACheckParents() returned FALSE.
+    //
+    psBool rc = pmFPACheckParents(tmpFPA);
+    if (rc != false) {
+        printf("TEST ERROR: pmCheckParents() returned TRUE.\n");
+        testStatus = 1;
+    }
+
+    //
+    // Ensure that the parent members are right.
+    //
+    for (psS32 chipID = 0; chipID < tmpFPA->chips->n ; chipID++) {
+        pmChip *tmpChip = (pmChip *) tmpFPA->chips->data[chipID];
+        if (tmpChip->parent != tmpFPA) {
+            printf("TEST ERROR: pmCheckParents() did not restore Chip->parent.\n");
+            testStatus = 2;
+        }
+
+        for (psS32 cellID = 0; cellID < tmpChip->cells->n ; cellID++) {
+            pmCell *tmpCell = (pmCell *) tmpChip->cells->data[cellID];
+            if (tmpCell->parent != tmpChip) {
+                printf("TEST ERROR: pmCheckParents() did not restore Cell->parent.\n");
+                testStatus = 3;
+            }
+
+            for (psS32 readoutID = 0; readoutID < tmpCell->readouts->n ; readoutID++) {
+                pmReadout *tmpReadout = (pmReadout *) tmpCell->readouts->data[readoutID];
+                if (tmpReadout->parent != tmpCell) {
+                    printf("TEST ERROR: pmCheckParents() did not restore Readout->parent.\n");
+                    testStatus = 4;
+                }
+            }
+        }
+    }
+
+    psFree(tmpFPA);
+    return(testStatus);
+}
+
+/******************************************************************************
+test5(): This routine wil test the pmFPASelectChip() and pmFPAExcludeChip()
+functions.  We generate an pmFPA hierarchy, then set the ->valid members with
+those routines, then verify.
+ *****************************************************************************/
+psS32 test5( void )
+{
+    psS32 testStatus = 0;
+    pmChip *tmpChip = NULL;
+
+    //
+    // Generate a pmFPA hierarchy.
+    //
+    pmFPA *tmpFPA = genSystem();
+
+    //
+    // We test the ->valid member for each chip.
+    //
+    for (psS32 i = 0 ; i < tmpFPA->chips->n ; i++) {
+        tmpChip = (pmChip *) tmpFPA->chips->data[i];
+        if ((tmpChip == NULL) || (tmpChip->valid != false)) {
+            printf("TEST ERROR: Could not properly generate an FPA hierarchy.\n");
+            testStatus = 1;
+        }
+    }
+
+    //
+    // Exclude chip number 0, include all others, then test return value
+    //
+    psS32 numChips = pmFPAExcludeChip(tmpFPA, 0);
+    if (numChips != (NUM_CHIPS-1)) {
+        printf("TEST ERROR: pmFPAExcludeChip() did not return the correct number of chips.\n");
+        testStatus = 2;
+    }
+
+    //
+    // We test the ->valid member for each chip.
+    //
+    tmpChip = (pmChip *) tmpFPA->chips->data[0];
+    if (tmpChip->valid != false) {
+        printf("TEST ERROR: pmFPAExcludeChip() did not set the proper chip->valid to FALSE.\n");
+        testStatus = 3;
+    }
+    for (psS32 i = 1 ; i < tmpFPA->chips->n ; i++) {
+        pmChip *tmpChip = (pmChip *) tmpFPA->chips->data[i];
+        if (tmpChip->valid != true) {
+            printf("TEST ERROR: pmFPAExcludeChip() did not set the proper chip->valids to FALSE.\n");
+            testStatus = 4;
+        }
+    }
+
+
+    //
+    // Include chip number 0, exclude all others, then test return value
+    //
+    psBool tmpBool = pmFPASelectChip(tmpFPA, 0);
+    if (tmpBool != true) {
+        printf("TEST ERROR: pmFPASelectChip() returned FALSE.\n");
+        testStatus = 5;
+    }
+
+    //
+    // We test the ->valid member for each chip.
+    //
+    tmpChip = (pmChip *) tmpFPA->chips->data[0];
+    if (tmpChip->valid != true) {
+        printf("TEST ERROR: pmFPASelectChip() did not set the proper chip->valid to FALSE.\n");
+        testStatus = 6;
+    }
+    for (psS32 i = 1 ; i < tmpFPA->chips->n ; i++) {
+        pmChip *tmpChip = (pmChip *) tmpFPA->chips->data[i];
+        if (tmpChip->valid != false) {
+            printf("TEST ERROR: pmFPASelectChip() did not set the proper chip->valids to FALSE.\n");
+            testStatus = 7;
+        }
+    }
+
+    psFree(tmpFPA);
+    return(testStatus);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/.cvsignore	(revision 22157)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+temp
+tst_pmAstrometry
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/Makefile.am	(revision 22157)
@@ -0,0 +1,41 @@
+AM_CPPFLAGS = \
+	$(SRCINC) \
+	-I$(top_srcdir)/test/tap/src \
+	-I$(top_srcdir)/test/pstap/src \
+	$(PSMODULES_CFLAGS)
+
+AM_LDFLAGS = \
+	$(top_builddir)/src/libpsmodules.la  \
+	$(top_builddir)/test/tap/src/libtap.la \
+	$(top_builddir)/test/pstap/src/libpstap.la \
+	$(PSMODULES_LIBS)
+
+TEST_PROGS = \
+	tap_pmFPA \
+	tap_pmFPAReadWrite \
+	tap_pmHDU \
+	tap_pmHDUUtils \
+	tap_pmFPALevel \
+	tap_pmFPAFlags \
+	tap_pmFPAExtent \
+	tap_pmFPAUtils \
+	tap_pmFPAConstruct \
+	tap_pmFPAView \
+	tap_pmFPAHeader \
+	tap_pmFPAMaskW \
+	tap_pmFPACellSquish \
+	tap_pmReadoutStack \
+	tap_pmReadoutFake \
+	tap_pmFPACopy
+
+if BUILD_TESTS
+bin_PROGRAMS = $(TEST_PROGS)
+TESTS = $(TEST_PROGS)
+else
+check_PROGRAMS = $(TEST_PROGS)
+endif
+
+CLEANFILES = $(check_DATA) temp/* core core.* *~ *.bb *.bbg *.da gmon.out
+
+test: check
+	$(top_srcdir)/test/test.pl
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/SampleIPPConfig
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/SampleIPPConfig	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/SampleIPPConfig	(revision 22157)
@@ -0,0 +1,45 @@
+### Example .ipprc file
+    PATH            STR     .
+    DATAPATH	str	datapath
+
+### Database configuration
+    DBSERVER	STR	localhost
+    DBUSER		STR	test
+    DBPASSWORD	STR	""
+    DBNAME          STR     test
+    DBPORT		S32	0
+
+### Setups for each camera system
+    CAMERAS		METADATA
+	CAMERA0		STR	camera0/camera.config
+	CAMERA1		STR	camera1/camera.config
+    END
+
+### Setups for psLib
+    TIME		STR	time.config
+    LOGLEVEL	S32	3
+    LOGFORMAT	STR	HLNM
+    LOGDEST	STR	STDOUT
+
+### Default trace logging initializations
+    TRACE		METADATA
+	dummyTraceFacility01	S32	1
+	dummyTraceFacility02	S32	2
+    END
+
+### Predefined recipe files
+    RECIPES         METADATA                # Site-level recipes
+        R00		STR	recipes/R00.config
+        R01		STR	recipes/R01.config
+        R02		STR	recipes/R02.config
+        R03		STR	recipes/R03.config
+    END
+
+### Misc arbitraty metadata
+    ARBITRARY_STRING_S32	S32	20
+    ARBITRARY_STRING_F32	F32	21.0
+    ARBITRARY_STRING_F64	F64	22.0
+    ARBITRARY_STRING_STR	STR	19.0
+    ARBITRARY_STRING_BOOL_T	BOOL	true
+    ARBITRARY_STRING_BOOL_F	BOOL	false
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera0/#camera.config~
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera0/#camera.config~	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera0/#camera.config~	(revision 22157)
@@ -0,0 +1,22 @@
+FORMATS         METADATA
+        C0_FM0     STR     camera0/format0.config
+        C0_FM1     STR     camera0/format1.config
+END
+
+RECIPES         METADATA
+        C0_RECIPE0          STR     camera0/recipe0.config
+        C0_RECIPE1          STR     camera0/recipe1.config
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION     METADATA
+        CELL.TRIMSEC            STR     TRIMSEC
+        CELL.BIASSEC            STR     BIASSEC
+END
+
+
+FPA     METADATA
+        ccd00   STR     LeftAmp RightAmp
+        ccd01   STR     LeftAmp RightAmp
+END
+ID      STR     CAMERA0
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera0/camera.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera0/camera.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera0/camera.config	(revision 22157)
@@ -0,0 +1,24 @@
+FORMATS         METADATA
+        C0_FM0     STR     camera0/format0.config
+        C0_FM1     STR     camera0/format1.config
+END
+
+RECIPES         METADATA
+        C0_RECIPE0          STR     camera0/recipe0.config
+        C0_RECIPE1          STR     camera0/recipe1.config
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION     METADATA
+        CELL.TRIMSEC            STR     TRIMSEC
+        CELL.BIASSEC            STR     BIASSEC
+        CELL.TRIMSEC0            STR     TRIMSEC
+        CELL.BIASSEC1            STR     BIASSEC
+END
+
+
+FPA     METADATA
+        ccd00   STR     LeftAmp RightAmp
+        ccd01   STR     LeftAmp RightAmp
+END
+ID      STR     CAMERA0
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera0/format0.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera0/format0.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera0/format0.config	(revision 22157)
@@ -0,0 +1,31 @@
+RULE    METADATA
+        F0_KEY0        STR     string20
+        F0_KEY1        STR     string21
+        F0_KEY2        S32     20
+END
+
+FILE    METADATA
+        PHU             STR     FPA
+        EXTENSIONS      STR     CELL
+        FPA.NAME        STR     EXPNUM
+END
+
+DEFAULTS        METADATA
+        F0_DEF0           STR     DefString20
+        F0_DEF1           STR     DefString21
+        F0_DEF2           F32     21.0
+        F0_DEF3           S32     22
+END
+
+CELLS   METADATA
+        left    METADATA        # Left amplifier
+                CELL.BIASSEC.SOURCE     STR     HEADER
+                CELL.TRIMSEC.SOURCE     STR     HEADER
+        END
+        right   METADATA        # Right amplifier
+                CELL.BIASSEC.SOURCE     STR     HEADER
+                CELL.TRIMSEC.SOURCE     STR     HEADER
+        END
+END
+
+ID	STR	camera0/format0.config
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera0/format1.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera0/format1.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera0/format1.config	(revision 22157)
@@ -0,0 +1,29 @@
+RULE    METADATA
+        F1_KEY0        STR     string30
+        F1_KEY1        STR     string31
+        F1_KEY2        S32     30
+END
+
+FILE    METADATA
+        PHU             STR     FPA
+        EXTENSIONS      STR     CELL
+        FPA.NAME        STR     EXPNUM
+END
+
+DEFAULTS        METADATA
+        F1_DEF0           STR     DefString30
+        F1_DEF1           STR     DefString31
+        F1_DEF2           F32     31.0
+        F1_DEF3           S32     32
+END
+
+CELLS   METADATA
+        left    METADATA        # Left amplifier
+                CELL.BIASSEC.SOURCE     STR     HEADER
+                CELL.TRIMSEC.SOURCE     STR     HEADER
+        END
+        right   METADATA        # Right amplifier
+                CELL.BIASSEC.SOURCE     STR     HEADER
+                CELL.TRIMSEC.SOURCE     STR     HEADER
+        END
+END
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera0/recipe0.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera0/recipe0.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera0/recipe0.config	(revision 22157)
@@ -0,0 +1,4 @@
+R0_KEY0		STR	RecipeString0
+R0_KEY1		STR	RecipeString1
+R0_KEY2		S32	2
+R0_KEY3		F32	3
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera0/recipe1.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera0/recipe1.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera0/recipe1.config	(revision 22157)
@@ -0,0 +1,4 @@
+R1_KEY0		STR	RecipeString10
+R1_KEY1		STR	RecipeString11
+R1_KEY2		S32	12
+R1_KEY3		F32	13
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera1/#camera.config~
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera1/#camera.config~	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera1/#camera.config~	(revision 22157)
@@ -0,0 +1,14 @@
+FORMATS         METADATA
+        C1_FM0     STR     camera1/format0.config
+        C1_FM1     STR     camera1/format1.config
+END
+
+RECIPES         METADATA
+        C1_RECIPE0          STR     camera1/recipe0.config
+        C1_RECIPE1          STR     camera1/recipe1.config
+END
+
+FPA     METADATA
+        ccd00   STR     LeftAmp RightAmp
+        ccd01   STR     LeftAmp RightAmp
+END
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera1/camera.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera1/camera.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera1/camera.config	(revision 22157)
@@ -0,0 +1,20 @@
+FORMATS         METADATA
+        C1_FM0     STR     camera1/format0.config
+        C1_FM1     STR     camera1/format1.config
+END
+
+RECIPES         METADATA
+        C1_RECIPE0          STR     camera1/recipe0.config
+        C1_RECIPE1          STR     camera1/recipe1.config
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION     METADATA
+        CELL.TRIMSEC            STR     TRIMSEC
+        CELL.BIASSEC            STR     BIASSEC
+END
+
+FPA     METADATA
+        ccd00   STR     LeftAmp RightAmp
+        ccd01   STR     LeftAmp RightAmp
+END
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera1/format0.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera1/format0.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera1/format0.config	(revision 22157)
@@ -0,0 +1,29 @@
+RULE    METADATA
+        F0_KEY0        STR     string40
+        F0_KEY1        STR     string41
+        F0_KEY2        S32     420
+END
+
+FILE    METADATA
+        PHU             STR     FPA
+        EXTENSIONS      STR     CELL
+        FPA.NAME        STR     EXPNUM
+END
+
+DEFAULTS        METADATA
+        F0_DEF0           STR     DefString40
+        F0_DEF1           STR     DefString41
+        F0_DEF2           F32     41.0
+        F0_DEF3           S32     44
+END
+
+CELLS   METADATA
+        left    METADATA        # Left amplifier
+                CELL.BIASSEC.SOURCE     STR     HEADER
+                CELL.TRIMSEC.SOURCE     STR     HEADER
+        END
+        right   METADATA        # Right amplifier
+                CELL.BIASSEC.SOURCE     STR     HEADER
+                CELL.TRIMSEC.SOURCE     STR     HEADER
+        END
+END
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera1/format1.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera1/format1.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera1/format1.config	(revision 22157)
@@ -0,0 +1,29 @@
+RULE    METADATA
+        F1_KEY0        STR     string80
+        F1_KEY1        STR     string81
+        F1_KEY2        S32     80
+END
+
+FILE    METADATA
+        PHU             STR     FPA
+        EXTENSIONS      STR     CELL
+        FPA.NAME        STR     EXPNUM
+END
+
+DEFAULTS        METADATA
+        F1_DEF0           STR     DefString80
+        F1_DEF1           STR     DefString81
+        F1_DEF2           F32     81.0
+        F1_DEF3           S32     82
+END
+
+CELLS   METADATA
+        left    METADATA        # Left amplifier
+                CELL.BIASSEC.SOURCE     STR     HEADER
+                CELL.TRIMSEC.SOURCE     STR     HEADER
+        END
+        right   METADATA        # Right amplifier
+                CELL.BIASSEC.SOURCE     STR     HEADER
+                CELL.TRIMSEC.SOURCE     STR     HEADER
+        END
+END
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera1/recipe0.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera1/recipe0.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera1/recipe0.config	(revision 22157)
@@ -0,0 +1,4 @@
+R0_KEY0		STR	RecipeString120
+R0_KEY1		STR	RecipeString121
+R0_KEY2		S32	122
+R0_KEY3		F32	123
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera1/recipe1.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera1/recipe1.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/camera1/recipe1.config	(revision 22157)
@@ -0,0 +1,4 @@
+R1_KEY0		STR	RecipeString230
+R1_KEY1		STR	RecipeString2311
+R1_KEY2		S32	232
+R1_KEY3		F32	233
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/path2/SampleIPPConfig2
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/path2/SampleIPPConfig2	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/path2/SampleIPPConfig2	(revision 22157)
@@ -0,0 +1,32 @@
+### Example .ipprc file
+
+### Database configuration
+DBSERVER	STR	ippdb.ifa.hawaii.edu	# Database host name (for psDBInit)
+DBUSER		STR	ipp			# Database user name (for psDBInit)
+DBPASSWORD	STR	password		# Database password (for psDBInit)
+
+### Setups for each camera system
+CAMERAS		METADATA
+	MEGACAM_RAW	STR	megacam_raw.config
+	MEGACAM_SPLICE	STR	megacam_splice.config
+	GPC1_RAW	STR	gpc1_raw.config
+	LRIS_BLUE	STR	lris_blue.config
+	LRIS_RED	STR	lris_red.config
+END
+
+### psLib setup
+#TIME		STR	/home/mithrandir/price/pan-starrs/jhroot/i686-pc-linux-gnu/etc/pslib/psTime.config	# Time configuration file
+LOGLEVEL	S32	3			# Logging level; 3=INFO
+LOGFORMAT	STR	HLNM			# Log format
+LOGDEST	STR	STDOUT				# Log destination
+TRACE		METADATA			# Trace levels
+	dummyTraceFunc01	S32	1
+	dummyTraceFunc02	S32	2
+END
+ARBITRARY_STRING_S32	S32	20
+ARBITRARY_STRING_F32	F32	21.0
+ARBITRARY_STRING_F64	F64	22.0
+ARBITRARY_STRING_STR	STR	19.0
+ARBITRARY_STRING_BOOL_T	BOOL	true
+ARBITRARY_STRING_BOOL_F	BOOL	false
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/sampleFitsFile.fits
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/sampleFitsFile.fits	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/data/sampleFitsFile.fits	(revision 22157)
@@ -0,0 +1,1 @@
+SIMPLE  =                    T / file does conform to FITS standard             BITPIX  =                  -32 / number of bits per data pixel                  NAXIS   =                    2 / number of data axes                            NAXIS1  =                   16 / length of data axis 1                          NAXIS2  =                   16 / length of data axis 2                          EXTEND  =                    T / FITS dataset may contain extensions            COMMENT   FITS (Flexible Image Transport System) format is defined in 'AstronomyCOMMENT   and Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H MYINT   =                    0 / psS32 Item                                     MYFLT   =                   1. / psF32 Item                                     MYDBL   =                   1. / psF64 Item                                     MYBOOL  =                    T / psBool Item                                    MYSTR   = 'ext-0   '           / String Item                                    EXTNAME = 'ext-0   '                                                            END                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             XTENSION= 'IMAGE   '           / IMAGE extension                                BITPIX  =                  -32 / number of bits per data pixel                  NAXIS   =                    2 / number of data axes                            NAXIS1  =                   16 / length of data axis 1                          NAXIS2  =                   16 / length of data axis 2                          PCOUNT  =                    0 / required keyword; must = 0                     GCOUNT  =                    1 / required keyword; must = 1                     MYINT   =                    1 / psS32 Item                                     MYFLT   =                  0.5 / psF32 Item                                     MYDBL   =                  0.5 / psF64 Item                                     MYBOOL  =                    F / psBool Item                                    MYSTR   = 'ext-1   '           / String Item                                    EXTNAME = 'ext-1   '                                                            END                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  XTENSION= 'IMAGE   '           / IMAGE extension                                BITPIX  =                  -32 / number of bits per data pixel                  NAXIS   =                    2 / number of data axes                            NAXIS1  =                   16 / length of data axis 1                          NAXIS2  =                   16 / length of data axis 2                          PCOUNT  =                    0 / required keyword; must = 0                     GCOUNT  =                    1 / required keyword; must = 1                     MYINT   =                    2 / psS32 Item                                     MYFLT   =            0.3333333 / psF32 Item                                     MYDBL   =    0.333333333333333 / psF64 Item                                     MYBOOL  =                    T / psBool Item                                    MYSTR   = 'ext-2   '           / String Item                                    EXTNAME = 'ext-2   '                                                            END                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   XTENSION= 'IMAGE   '           / IMAGE extension                                BITPIX  =                  -32 / number of bits per data pixel                  NAXIS   =                    2 / number of data axes                            NAXIS1  =                   16 / length of data axis 1                          NAXIS2  =                   16 / length of data axis 2                          PCOUNT  =                    0 / required keyword; must = 0                     GCOUNT  =                    1 / required keyword; must = 1                     MYINT   =                    3 / psS32 Item                                     MYFLT   =                 0.25 / psF32 Item                                     MYDBL   =                 0.25 / psF64 Item                                     MYBOOL  =                    F / psBool Item                                    MYSTR   = 'ext-3   '           / String Item                                    EXTNAME = 'ext-3   '                                                            END                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@  @@                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  XTENSION= 'IMAGE   '           / IMAGE extension                                BITPIX  =                  -32 / number of bits per data pixel                  NAXIS   =                    2 / number of data axes                            NAXIS1  =                   16 / length of data axis 1                          NAXIS2  =                   16 / length of data axis 2                          PCOUNT  =                    0 / required keyword; must = 0                     GCOUNT  =                    1 / required keyword; must = 1                     MYINT   =                    4 / psS32 Item                                     MYFLT   =                  0.2 / psF32 Item                                     MYDBL   =                  0.2 / psF64 Item                                     MYBOOL  =                    T / psBool Item                                    MYSTR   = 'ext-4   '           / String Item                                    EXTNAME = 'ext-4   '                                                            END                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @  @                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  XTENSION= 'IMAGE   '           / IMAGE extension                                BITPIX  =                  -32 / number of bits per data pixel                  NAXIS   =                    2 / number of data axes                            NAXIS1  =                   16 / length of data axis 1                          NAXIS2  =                   16 / length of data axis 2                          PCOUNT  =                    0 / required keyword; must = 0                     GCOUNT  =                    1 / required keyword; must = 1                     MYINT   =                    5 / psS32 Item                                     MYFLT   =            0.1666667 / psF32 Item                                     MYDBL   =    0.166666666666667 / psF64 Item                                     MYBOOL  =                    F / psBool Item                                    MYSTR   = 'ext-5   '           / String Item                                    EXTNAME = 'ext-5   '                                                            END                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @   @                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   XTENSION= 'IMAGE   '           / IMAGE extension                                BITPIX  =                  -32 / number of bits per data pixel                  NAXIS   =                    2 / number of data axes                            NAXIS1  =                   16 / length of data axis 1                          NAXIS2  =                   16 / length of data axis 2                          PCOUNT  =                    0 / required keyword; must = 0                     GCOUNT  =                    1 / required keyword; must = 1                     MYINT   =                    6 / psS32 Item                                     MYFLT   =            0.1428571 / psF32 Item                                     MYDBL   =    0.142857142857143 / psF64 Item                                     MYBOOL  =                    T / psBool Item                                    MYSTR   = 'ext-6   '           / String Item                                    EXTNAME = 'ext-6   '                                                            END                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À  @À                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  XTENSION= 'IMAGE   '           / IMAGE extension                                BITPIX  =                  -32 / number of bits per data pixel                  NAXIS   =                    2 / number of data axes                            NAXIS1  =                   16 / length of data axis 1                          NAXIS2  =                   16 / length of data axis 2                          PCOUNT  =                    0 / required keyword; must = 0                     GCOUNT  =                    1 / required keyword; must = 1                     MYINT   =                    7 / psS32 Item                                     MYFLT   =                0.125 / psF32 Item                                     MYDBL   =                0.125 / psF64 Item                                     MYBOOL  =                    F / psBool Item                                    MYSTR   = 'ext-7   '           / String Item                                    EXTNAME = 'ext-7   '                                                            END                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à  @à                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmAstrometry.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmAstrometry.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmAstrometry.c	(revision 22157)
@@ -0,0 +1,667 @@
+/** @file  tst_psAstrometry01.c
+*
+* XXX: The source code that is tested here is in the pmAstrometry.c and
+* pmAstrometry.h files.  However, those files are not included in the
+* current automake configuration.  These tests are therefore, not
+* runnable.  We include them in this distribution for the future.
+*
+* XXX: These tests need to be converted to tap format.
+*
+* XXX: Add tests were the coordinate does not transform to any legitimate cell
+* or chip, or FPA, or whatever.
+*
+* XXX: For each function, add tests for bad input parameters, as well as failed transforms.
+*
+*  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+*/
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+
+#define PS_PERCENT_COMPARE(X, Y, PERCENT_FRACTION) (fabs((Y)-(X))/fabs(X) < (PERCENT_FRACTION))
+#define VERBOSE 0
+#define NUM_READOUTS 1
+#define READOUT_NUM_ROWS 10
+#define READOUT_NUM_COLS 10
+
+#define NUM_CELLS 4
+#define CELL_GAP 2
+#define CELL_WIDTH READOUT_NUM_COLS
+#define CELL_HEIGHT READOUT_NUM_ROWS
+#define CELL_MIN_X 0
+#define CELL_MAX_X CELL_HEIGHT
+#define CELL_MIN_Y 0
+#define CELL_MAX_Y CELL_WIDTH
+
+#define NUM_CHIPS 2
+#define CHIP_GAP 2
+#define CHIP_MIN_X 0
+#define CHIP_MAX_X CELL_HEIGHT
+#define CHIP_MIN_Y 0
+#define CHIP_MAX_Y ((NUM_CELLS * CELL_WIDTH) + ((NUM_CELLS) * CELL_GAP))
+#define CHIP_WIDTH CHIP_MAX_Y
+#define CHIP_HEIGHT CHIP_MAX_X
+
+#define NUM_FPAS 1
+#define FPA_MIN_X 0
+#define FPA_MAX_X CHIP_HEIGHT
+#define FPA_MIN_Y 0
+#define FPA_MAX_Y (((NUM_CHIPS) * CHIP_WIDTH) + (((NUM_CHIPS)-1) * CHIP_GAP))
+
+#define PROJECTION_SCALE_X 1.0
+#define PROJECTION_SCALE_Y 1.0
+
+psS32 currentId = 0;
+psS32 memLeaks = 0;
+
+psPlaneTransform *PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM()
+{
+    psPlaneTransform *pt = psPlaneTransformAlloc(1, 1);
+    pt->x->coeff[1][0] = 1.0;
+    pt->y->coeff[0][1] = 1.0;
+    return(pt);
+}
+
+psPlaneDistort *PS_CREATE_4D_IDENTITY_PLANE_DISTORT()
+{
+    psPlaneDistort *pd = psPlaneDistortAlloc(1, 1, 1, 1);
+    pd->x->coeff[1][0][0][0] = 1.0;
+    pd->y->coeff[0][1][0][0] = 1.0;
+    return(pd);
+}
+
+/******************************************************************************
+genSystem(): This routine will create a system of FPAs/Chips/Cells/Readouts.  For
+simplicity, an FPA is defined as a linear array of chips, and a chip is
+defined as a linear array of cells, both in the y (cols) direction.  The
+transforms between the various layers take into account the cell/chip and
+the boundaries between each.
+ *****************************************************************************/
+pmFPA *genSystem()
+{
+    //
+    // Create top pmFPA structure.
+    //
+    const psMetadata *camera = psMetadataAlloc();
+    pmFPA* myFPA = pmFPAAlloc(camera);
+    if (myFPA == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc() returned a NULL.\n");
+        return NULL;
+    }
+    myFPA->fromTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    myFPA->toTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    myFPA->projection = psProjectionAlloc(0.0,0.0,PROJECTION_SCALE_X,PROJECTION_SCALE_X,PS_PROJ_TAN);
+
+    myFPA->chips = psArrayRealloc(myFPA->chips, NUM_CHIPS);
+    for (psS32 chipID=0 ; chipID<NUM_CHIPS ; chipID++) {
+        pmChip *myChip = pmChipAlloc(myFPA, "ChipName");
+        if (myChip == NULL) {
+            psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmChipAlloc() returned a NULL.\n");
+            return NULL;
+        }
+        myFPA->chips->data[chipID] = (psPtr *) myChip;
+        int myChipRow0 = 0;
+        int myChipCol0 = chipID * (CHIP_WIDTH + CHIP_GAP);
+
+        // We create the transforms between the chip and FPA.  The
+        // transform is a simple identity transform.
+        myChip->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+        myChip->toFPA->y->coeff[0][0] = (psF64) myChipCol0;
+        myChip->toFPA->y->coeff[1][1] = 0.0;
+        myChip->toFPA->x->coeff[0][0] = (psF64) myChipRow0;
+        myChip->toFPA->x->coeff[1][1] = 0.0;
+
+        myChip->fromFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+        myChip->fromFPA->y->coeff[0][0] = (psF64) (- myChipCol0);
+        myChip->fromFPA->y->coeff[1][1] = 0.0;
+        myChip->fromFPA->x->coeff[0][0] = (psF64) (- myChipRow0);
+        myChip->fromFPA->x->coeff[1][1] = 0.0;
+
+        myChip->cells = psArrayRealloc(myChip->cells, NUM_CELLS);
+        for (psS32 cellID=0 ; cellID<NUM_CELLS ; cellID++) {
+            pmCell *myCell = pmCellAlloc(myChip, NULL, "CellName");
+            if (myCell == NULL) {
+                psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmCellAlloc() returned a NULL.\n");
+                return NULL;
+            }
+            myChip->cells->data[cellID] = (psPtr *) myCell;
+            int myCellRow0 = 0;
+            int myCellCol0 = cellID * (CELL_WIDTH + CELL_GAP);
+            myCell->toChip = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+            myCell->toChip->y->coeff[0][0] = (psF64) myCellCol0;
+            myCell->toChip->y->coeff[1][1] = 0.0;
+            myCell->toChip->x->coeff[0][0] = (psF64) myCellRow0;
+            myCell->toChip->x->coeff[1][1] = 0.0;
+
+            myCell->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+            myCell->toFPA->y->coeff[0][0] = (psF64) (myCellCol0 + myChipCol0);
+            myCell->toFPA->y->coeff[1][1] = 0.0;
+            myCell->toFPA->x->coeff[0][0] = (psF64) (myCellRow0 + myChipRow0);
+            myCell->toFPA->x->coeff[1][1] = 0.0;
+
+            myCell->toSky = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+            myCell->toSky->y->coeff[0][0] = myCell->toFPA->y->coeff[0][0];
+            myCell->toSky->y->coeff[1][1] = 0.0;
+            myCell->toSky->x->coeff[0][0] = myCell->toFPA->x->coeff[0][0];
+            myCell->toSky->x->coeff[1][1] = 0.0;
+
+            myCell->readouts = psArrayRealloc(myCell->readouts, NUM_READOUTS);
+            for (psS32 readoutID=0 ; readoutID<NUM_READOUTS ; readoutID++) {
+                pmReadout *myReadout = pmReadoutAlloc(myCell);
+                if (myReadout == NULL) {
+                    psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmReadoutAlloc() returned a NULL.\n");
+                    return NULL;
+                }
+                myCell->readouts->data[readoutID] = (psPtr *) myReadout;
+                myReadout->image = psImageAlloc(READOUT_NUM_COLS, READOUT_NUM_ROWS, PS_TYPE_F32);
+                for (psS32 row=0;row<READOUT_NUM_ROWS;row++) {
+                    for(psS32 col=0;col<READOUT_NUM_COLS;col++) {
+                        myReadout->image->data.F32[row][col] = (psF32) ((chipID * (CHIP_WIDTH + CHIP_GAP)) +
+                                                               (cellID * (CELL_WIDTH + CELL_GAP)));
+                    }
+                }
+                int myReadoutRow0 = myCellRow0;
+                int myReadoutCol0 = myCellCol0;
+                myReadout->rowBins = 0;
+                myReadout->colBins = 0;
+            }
+            if (VERBOSE) {
+                printf("\n\n\n\nFor chip %d cell %d the cell->toFPA transform is:\n", chipID, cellID);
+                PS_PRINT_PLANE_TRANSFORM(myCell->toFPA);
+                printf("\n\n\n\nFor chip %d cell %d the cell->toChip transform is:\n", chipID, cellID);
+                PS_PRINT_PLANE_TRANSFORM(myCell->toChip);
+                printf("\n\n\n\nFor chip %d cell %d the cell->toSky transform is:\n", chipID, cellID);
+                PS_PRINT_PLANE_TRANSFORM(myCell->toSky);
+            }
+        }
+        if (VERBOSE) {
+            printf("\n\n\n\nFor chip %d the chip->toFPA transform is:\n", chipID);
+            PS_PRINT_PLANE_TRANSFORM(myChip->toFPA);
+            printf("\n\n\n\nFor chip %d the chip->fromFPA transform is:\n", chipID);
+            PS_PRINT_PLANE_TRANSFORM(myChip->fromFPA);
+        }
+    }
+
+    return(myFPA);
+}
+
+
+/******************************************************************************
+This routine tests many Astrometry functions.  It loops through all valid
+pixels of all cells of all chips and computes the corresponding (x,y)
+coordinates in the FPA plane.  It then calls the pmChipInFPA() then
+pmCellInFPA() with the FPA coordinate and determines the chip/cell that that
+coordinate corresponds to.  Following that it does a variety of tests on the
+various functions that tharnsform coordinates within the pmFPA hierarchy.
+ 
+List of tested functions:
+    pmCellInFPA()  yes
+    pmChipInFPA()  yes
+    pmCellInChip()  yes
+ 
+    pmCoordCellToFPA()  yes
+    pmCoordChipToFPA()  yes
+    pmCoordFPAToChip()  yes
+    pmCoordCellToChip()  yes
+    pmCoordChipToCell()  yes
+ 
+    pmCoordFPAToTP()  yes
+    pmCoordTPToFPA()  yes
+ 
+    pmCoordTPToSky()  yes
+    pmCoordSkyToTP()  yes
+    pmCoordSkyToCell()  yes
+    pmCoordCellToSky()  yes
+    pmCoordCellToSkyQuick() yes
+    pmCoordSkyToCellQuick() yes
+ *****************************************************************************/
+psS32 test3( void )
+{
+    psS32 x;
+    psS32 y;
+    psPlane fpaCoord;
+    pmFPA *myFPA = genSystem();
+    pmCell *myCell = NULL;
+    psPlane chipCoord;
+    psPlane cellCoord;
+    psPlane testCoord;
+    psSphere *skyCoord = psSphereAlloc();
+    // XXX: This code causes a seg fault.
+    //    psSphere skyTmp;
+    //    psMemCheckType(PS_DATA_SPHERE, &skyTmp);
+    psPlane tpCoord;
+    psS32 testStatus = 0;
+
+    //
+    // I'm not convinced that the p_psProject() and p_psDeproject() functions work
+    // correctly.  If we project a set of coordinates over a wide range of (R, D)
+    // values, then deproject them, the original (R, D) values are only produced
+    // when D is larger than 0.  This code demonstrates that.  I also created tests
+    // that currently fail in tst_psCoord01.c.  I have a workaround in the function
+    // XXXDeproject() in pmAstrometry.c.
+    //
+    if (0) {
+        // This loop goes from (R, D) -> (X, Y) -> (R, D)
+        psPlane planeCoord01;
+        psSphere skyCoord01;
+        psSphere skyCoord02;
+        #define DEG_INC 15.0
+
+        for (psF32 R = -90.0 ; R <= 90.0 ; R+= DEG_INC) {
+            for (psF32 D = -90.0 ; D <= 90.0 ; D+= DEG_INC) {
+                if ((fabs(R) != 90.0) && (fabs(D) != 90.0)) {
+                    skyCoord01.r = DEG_TO_RAD(R);
+                    skyCoord01.d = DEG_TO_RAD(D);
+                    p_psProject(&planeCoord01, &skyCoord01, myFPA->projection);
+                    p_psDeproject(&skyCoord02, &planeCoord01, myFPA->projection);
+                    printf("(%.2fr %.2fd) (%.2fr %.2fd) -> (%.2f %.2f) -> (%.2fr %.2fd)", R, D,
+                           skyCoord01.r, skyCoord01.d,
+                           planeCoord01.x, planeCoord01.y,
+                           skyCoord02.r, skyCoord02.d);
+                    if ((fabs(skyCoord01.r - skyCoord02.r) < FLT_EPSILON) &&
+                            (fabs(skyCoord01.d - skyCoord02.d) < FLT_EPSILON)) {
+                        printf(": CORRECT\n");
+                    } else {
+                        printf(": WRONG\n");
+                    }
+                }
+            }
+        }
+        psFree(myFPA);
+        return(0);
+    }
+    if (0) {
+        // This loop goes from (X, Y) -> (R, D) -> (X, Y)
+        #define SPACE_INC 4.0
+        for(testCoord.x=-CELL_HEIGHT;testCoord.x<=CELL_HEIGHT;testCoord.x+=SPACE_INC)
+        {
+            for (testCoord.y=-CELL_WIDTH;testCoord.y<=CELL_WIDTH;testCoord.y+=SPACE_INC) {
+                psPlane planeCoord01;
+                psPlane planeCoord02;
+                psSphere skyCoord01;
+                psSphere skyCoord02;
+                p_psDeproject(&skyCoord01, &testCoord, myFPA->projection);
+                p_psProject(&planeCoord01, &skyCoord01, myFPA->projection);
+                p_psDeproject(&skyCoord02, &planeCoord01, myFPA->projection);
+                p_psProject(&planeCoord02, &skyCoord02, myFPA->projection);
+                printf("Plane: (%.2f %.2f) -> (%.2fr %.2fd) -> (%.2f %.2f)\n",
+                       testCoord.x, testCoord.y,
+                       skyCoord01.r, skyCoord01.d,
+                       planeCoord01.x, planeCoord01.y);
+                /*
+                                printf("Plane: (%.2f %.2f) -> (%.2f %.2f) -> (%.2f %.2f)\n",
+                                        testCoord.x, testCoord.y, planeCoord01.x, planeCoord01.y,
+                                        planeCoord02.x, planeCoord02.y);
+                                printf("Sphere: (%.2f %.2f) -> (%.2f %.2f)\n",
+                                        skyCoord01.r, skyCoord01.d, skyCoord02.r, skyCoord02.d);
+                                printf("Plane: (%.2f %.2f) -> (%.2fd %.2fr) -> (%.2f %.2f) -> (%.2fd %.2fr) -> (%.2f %.2f)\n",
+                                        testCoord.x, testCoord.y,
+                                        skyCoord01.r, skyCoord01.d,
+                                        planeCoord01.x, planeCoord01.y,
+                                        skyCoord02.r, skyCoord02.d,
+                                        planeCoord02.x, planeCoord02.y);
+                */
+            }
+        }
+        psFree(myFPA);
+        return(0);
+    }
+    //
+    // We iterate through all cells on all chips on the fpa.  We determine
+    // the expected fpaCcoord.
+    //
+
+    for (psS32 chip=0;chip<NUM_CHIPS;chip++) {
+        for (psS32 cell=0;cell<NUM_CELLS;cell++) {
+            for(x=0;x<CELL_HEIGHT;x++) {
+                for (y=0;y<CELL_WIDTH;y++) {
+                    fpaCoord.x = (psF64) x;
+                    fpaCoord.y = (psF64) (y + (chip * (CHIP_WIDTH + CHIP_GAP)) +
+                                          (cell * (CELL_WIDTH + CELL_GAP)));
+                    if (VERBOSE) {
+                        printf("------------------ (%.2f, %.2f) ------------------\n", fpaCoord.x, fpaCoord.y);
+                        printf("(chip, cell, x, y) is (%d, %d, %d, %d)\n", chip, cell, x, y);
+                    }
+                    pmChip* tmpChip = pmChipInFPA(&fpaCoord, myFPA);
+                    myCell = pmCellInFPA(&fpaCoord, myFPA);
+
+                    if ((myCell == NULL) || (tmpChip == NULL)) {
+                        if (tmpChip == NULL) {
+                            printf("TEST ERROR: pmChipInFPA() returned NULL\n");
+                            testStatus = 1;
+                        } else if (myCell == NULL) {
+                            printf("TEST ERROR: pmCellInFPA(): returned NULL\n");
+                            testStatus = 1;
+                        }
+                    } else {
+                        pmCoordFPAToChip(&chipCoord, &fpaCoord, tmpChip);
+                        pmCoordChipToCell(&cellCoord, &chipCoord, myCell);
+
+                        if (x != (psS32) cellCoord.x) {
+                            printf("TEST ERROR: pmCoordFPAToChip()->pmCoordChipToCell(): x coord was %d (%f), should be %d\n", (psS32) cellCoord.x, cellCoord.x, x);
+                            testStatus = 1;
+                        }
+                        if (y != (psS32) cellCoord.y) {
+                            printf("TEST ERROR: pmCoordFPAToChip()->pmCoordChipToCell(): y coord was %d (%f), should be %d\n", (psS32) cellCoord.y, cellCoord.y, y);
+                            testStatus = 1;
+                        }
+
+                        pmCoordCellToChip(&testCoord, &cellCoord, myCell);
+                        if (fabs(testCoord.x - chipCoord.x) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordCellToChip() x coord was %.2f, should be %d\n", cellCoord.x, x);
+                            testStatus = 1;
+                        }
+                        if (fabs(testCoord.y - chipCoord.y) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordCellToChip() y coord was %.2f, should be %d\n", cellCoord.y, y);
+                            testStatus = 1;
+                        }
+
+                        pmCell *myCell2 = pmCellInChip(&chipCoord, tmpChip);
+                        if (myCell2 != myCell) {
+                            printf("TEST ERROR: pmCellInChip() != pmCellInChip(pmChipInFPA()) (%p %p)\n", myCell2, myCell);
+                            testStatus = 1;
+                        }
+
+                        pmCoordChipToFPA(&testCoord, &chipCoord, tmpChip);
+                        if (fabs(testCoord.x - x) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordChipToFPA() x coord was %.2f, should be %d\n", cellCoord.x, x);
+                            testStatus = 1;
+                        }
+                        if (fabs(testCoord.y - fpaCoord.y) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordChipToFPA() y coord was %.2f, should be %.2f\n", cellCoord.y, fpaCoord.y);
+                            testStatus = 1;
+                        }
+
+                        pmCoordFPAToTP(&testCoord, &fpaCoord, 0.0, 0.0, myFPA);
+                        if (fabs(testCoord.x - fpaCoord.x) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordFPAToTP() x coord was %.2f, should be %d\n", cellCoord.x, x);
+                            testStatus = 1;
+                        }
+                        if (fabs(testCoord.y - fpaCoord.y) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordFPAToTP() y coord was %.2f, should be %d\n", cellCoord.y, y);
+                            testStatus = 1;
+                        }
+
+                        //
+                        // Test pmCoordTPToSky() -> pmCoordSkyToTP()
+                        //
+                        if (1) {
+                            psSphere *rc = pmCoordTPToSky(skyCoord, &testCoord, myFPA->projection);
+                            if (rc == NULL) {
+                                printf("pmCoordTPToSky() failed.\n");
+                            } else {
+                                psPlane *rc = pmCoordSkyToTP(&tpCoord, skyCoord, myFPA->projection);
+                                if (rc == NULL) {
+                                    printf("pmCoordSkyToTP() failed.\n");
+                                } else {
+                                    if (fabs(testCoord.x - tpCoord.x) > FLT_EPSILON) {
+                                        printf("TEST ERROR: pmCoordTPToSky()/pmCoordSkyToTP() x coord was %.2f, should be %.2f\n", tpCoord.x, testCoord.x);
+                                        testStatus = 1;
+                                    }
+                                    if (fabs(testCoord.y - tpCoord.y) > FLT_EPSILON) {
+                                        printf("TEST ERROR: pmCoordTPToSky()/pmCoordSkyToTP() y coord was %.2f, should be %.2f\n", tpCoord.y, testCoord.y);
+                                        testStatus = 1;
+                                    }
+                                    if (VERBOSE) {
+                                        printf("(%.2f %.2f) -> (%.2f %.2f) -> (%.2f %.2f)\n", testCoord.x, testCoord.y, skyCoord->r, skyCoord->d, tpCoord.x, tpCoord.y);
+                                    }
+                                }
+                            }
+                        }
+
+                        //
+                        // Test pmCoordCellToSky() -> pmCoordSkyToCell()
+                        //
+                        if (1) {
+                            psPlane tmpCellCoord;
+                            psSphere *rc = pmCoordCellToSky(skyCoord, &cellCoord, 0.0, 0.0, myCell);
+                            if (rc == NULL) {
+                                printf("pmCoordCellToSky() failed.\n");
+                            } else {
+                                psPlane *rc = pmCoordSkyToCell(&tmpCellCoord, skyCoord, 0.0, 0.0, myCell);
+                                if (rc == NULL) {
+                                    printf("pmCoordSkyToCell() failed.\n");
+                                } else {
+                                    if (fabs(cellCoord.x - tmpCellCoord.x) > FLT_EPSILON) {
+                                        printf("TEST ERROR: pmCoordCellToSky()/pmCoordSkyToCell() x coord was %.2f, should be %.2f\n", tmpCellCoord.x, cellCoord.x);
+                                        testStatus = 1;
+                                    }
+                                    if (fabs(cellCoord.y - tmpCellCoord.y) > FLT_EPSILON) {
+                                        printf("TEST ERROR: pmCoordCellToSky()/pmCoordSkyToCell() y coord was %.2f, should be %.2f\n", tmpCellCoord.y, cellCoord.y);
+                                        testStatus = 1;
+                                    }
+                                    if (VERBOSE) {
+                                        printf("(%.2f %.2f) -> (%.2f %.2f) -> (%.2f %.2f)\n", testCoord.x, testCoord.y, skyCoord->r, skyCoord->d, tmpCellCoord.x, tmpCellCoord.y);
+                                    }
+                                }
+                            }
+                        }
+
+                        //
+                        // Test pmCoordCellToSkyQuick() -> pmCoordSkyToCellQuick()
+                        // I'm not sure how to test this in a system with chip and cell gaps.
+                        // There's no way to create an accurate polynomial transform from
+                        // a cell to the sky where there are cell, or chip gaps.
+                        //
+                        if ((NUM_CHIPS == 1) && (NUM_CELLS == 1)) {
+                            psPlane tmpCellCoord;
+                            psSphere *rc = pmCoordCellToSkyQuick(skyCoord, &cellCoord, myCell);
+                            if (rc == NULL) {
+                                printf("pmCoordCellToSkyQuick() failed.\n");
+                            } else {
+                                psPlane *rc = pmCoordSkyToCellQuick(&tmpCellCoord, skyCoord, myCell);
+                                if (rc == NULL) {
+                                    printf("pmCoordSkyToCellQuick() failed.\n");
+                                } else {
+                                    if (fabs(cellCoord.x - tmpCellCoord.x) > FLT_EPSILON) {
+                                        printf("TEST ERROR: pmCoordCellToSky()/pmCoordSkyToCell() x coord was %.2f, should be %.2f\n", tmpCellCoord.x, cellCoord.x);
+                                        testStatus = 1;
+                                    }
+                                    if (fabs(cellCoord.y - tmpCellCoord.y) > FLT_EPSILON) {
+                                        printf("TEST ERROR: pmCoordCellToSky()/pmCoordSkyToCell() y coord was %.2f, should be %.2f\n", tmpCellCoord.y, cellCoord.y);
+                                        testStatus = 1;
+                                    }
+                                    if (VERBOSE) {
+                                        printf("(%.2f %.2f) -> (%.2f %.2f) -> (%.2f %.2f)\n", testCoord.x, testCoord.y, skyCoord->r, skyCoord->d, tmpCellCoord.x, tmpCellCoord.y);
+                                    }
+                                }
+                            }
+                        }
+
+                        pmCoordCellToFPA(&testCoord, &cellCoord, myCell);
+                        if (fabs(testCoord.x - fpaCoord.x) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordCellToFPA() x coord was %.2f, should be %d\n", cellCoord.x, x);
+                            testStatus = 1;
+                        }
+                        if (fabs(testCoord.y - fpaCoord.y) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordCellToFPA() y coord was %.2f, should be %d\n", cellCoord.y, y);
+                            testStatus = 1;
+                        }
+
+                        pmCoordTPToFPA(&testCoord, &fpaCoord, 0.0, 0.0, myFPA);
+                        if (fabs(testCoord.x - fpaCoord.x) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordTPToFPA() x coord was %.2f, should be %d\n", cellCoord.x, x);
+                            testStatus = 1;
+                        }
+                        if (fabs(testCoord.y - fpaCoord.y) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordTPToFPA() y coord was %.2f, should be %d\n", cellCoord.y, y);
+                            testStatus = 1;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    psFree(myFPA);
+    psFree(skyCoord);
+
+    return(testStatus);
+}
+
+/******************************************************************************
+test4(): This routine wil test the pmFPACheckParents() function.  We generate
+an pmFPA hierarchy, then set the parents of each readout/cell/chip to NULL,
+then call pmFPACheckParents() to restore them, then we ensure they were
+restored.
+ *****************************************************************************/
+psS32 test4( void )
+{
+    psS32 testStatus = 0;
+
+    //
+    // Generate a pmFPA hierarchy.
+    //
+    pmFPA *tmpFPA = genSystem();
+
+    //
+    // We set the parents of each readout/cell/chip to NULL.
+    //
+    for (psS32 chipID = 0; chipID < tmpFPA->chips->n ; chipID++) {
+        pmChip *tmpChip = (pmChip *) tmpFPA->chips->data[chipID];
+        tmpChip->parent = NULL;
+
+        for (psS32 cellID = 0; cellID < tmpChip->cells->n ; cellID++) {
+            pmCell *tmpCell = (pmCell *) tmpChip->cells->data[cellID];
+            tmpCell->parent = NULL;
+
+            for (psS32 readoutID = 0; readoutID < tmpCell->readouts->n ; readoutID++) {
+                pmReadout *tmpReadout = (pmReadout *) tmpCell->readouts->data[readoutID];
+                tmpReadout->parent = NULL;
+            }
+        }
+    }
+
+    //
+    // Ensure that pmFPACheckParents() returned FALSE.
+    //
+    psBool rc = pmFPACheckParents(tmpFPA);
+    if (rc != false) {
+        printf("TEST ERROR: pmCheckParents() returned TRUE.\n");
+        testStatus = 1;
+    }
+
+    //
+    // Ensure that the parent members are right.
+    //
+    for (psS32 chipID = 0; chipID < tmpFPA->chips->n ; chipID++) {
+        pmChip *tmpChip = (pmChip *) tmpFPA->chips->data[chipID];
+        if (tmpChip->parent != tmpFPA) {
+            printf("TEST ERROR: pmCheckParents() did not restore Chip->parent.\n");
+            testStatus = 2;
+        }
+
+        for (psS32 cellID = 0; cellID < tmpChip->cells->n ; cellID++) {
+            pmCell *tmpCell = (pmCell *) tmpChip->cells->data[cellID];
+            if (tmpCell->parent != tmpChip) {
+                printf("TEST ERROR: pmCheckParents() did not restore Cell->parent.\n");
+                testStatus = 3;
+            }
+
+            for (psS32 readoutID = 0; readoutID < tmpCell->readouts->n ; readoutID++) {
+                pmReadout *tmpReadout = (pmReadout *) tmpCell->readouts->data[readoutID];
+                if (tmpReadout->parent != tmpCell) {
+                    printf("TEST ERROR: pmCheckParents() did not restore Readout->parent.\n");
+                    testStatus = 4;
+                }
+            }
+        }
+    }
+
+    psFree(tmpFPA);
+    return(testStatus);
+}
+
+/******************************************************************************
+test5(): This routine wil test the pmFPASelectChip() and pmFPAExcludeChip()
+functions.  We generate an pmFPA hierarchy, then set the ->valid members with
+those routines, then verify.
+ *****************************************************************************/
+psS32 test5( void )
+{
+    psS32 testStatus = 0;
+    pmChip *tmpChip = NULL;
+
+    //
+    // Generate a pmFPA hierarchy.
+    //
+    pmFPA *tmpFPA = genSystem();
+
+    //
+    // We test the ->valid member for each chip.
+    //
+    for (psS32 i = 0 ; i < tmpFPA->chips->n ; i++) {
+        tmpChip = (pmChip *) tmpFPA->chips->data[i];
+        if ((tmpChip == NULL) || (tmpChip->valid != false)) {
+            printf("TEST ERROR: Could not properly generate an FPA hierarchy.\n");
+            testStatus = 1;
+        }
+    }
+
+    //
+    // Exclude chip number 0, include all others, then test return value
+    //
+    psS32 numChips = pmFPAExcludeChip(tmpFPA, 0);
+    if (numChips != (NUM_CHIPS-1)) {
+        printf("TEST ERROR: pmFPAExcludeChip() did not return the correct number of chips.\n");
+        testStatus = 2;
+    }
+
+    //
+    // We test the ->valid member for each chip.
+    //
+    tmpChip = (pmChip *) tmpFPA->chips->data[0];
+    if (tmpChip->valid != false) {
+        printf("TEST ERROR: pmFPAExcludeChip() did not set the proper chip->valid to FALSE.\n");
+        testStatus = 3;
+    }
+    for (psS32 i = 1 ; i < tmpFPA->chips->n ; i++) {
+        pmChip *tmpChip = (pmChip *) tmpFPA->chips->data[i];
+        if (tmpChip->valid != true) {
+            printf("TEST ERROR: pmFPAExcludeChip() did not set the proper chip->valids to FALSE.\n");
+            testStatus = 4;
+        }
+    }
+
+
+    //
+    // Include chip number 0, exclude all others, then test return value
+    //
+    psBool tmpBool = pmFPASelectChip(tmpFPA, 0);
+    if (tmpBool != true) {
+        printf("TEST ERROR: pmFPASelectChip() returned FALSE.\n");
+        testStatus = 5;
+    }
+
+    //
+    // We test the ->valid member for each chip.
+    //
+    tmpChip = (pmChip *) tmpFPA->chips->data[0];
+    if (tmpChip->valid != true) {
+        printf("TEST ERROR: pmFPASelectChip() did not set the proper chip->valid to FALSE.\n");
+        testStatus = 6;
+    }
+    for (psS32 i = 1 ; i < tmpFPA->chips->n ; i++) {
+        pmChip *tmpChip = (pmChip *) tmpFPA->chips->data[i];
+        if (tmpChip->valid != false) {
+            printf("TEST ERROR: pmFPASelectChip() did not set the proper chip->valids to FALSE.\n");
+            testStatus = 7;
+        }
+    }
+
+    psFree(tmpFPA);
+    return(testStatus);
+}
+
+psS32 main( psS32 argc, char* argv[] )
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    plan_tests(92);
+
+    test3();
+    test4();
+    test5();
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPA.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPA.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPA.c	(revision 22157)
@@ -0,0 +1,492 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS:
+    All functions are tested.
+*/
+
+#define CHIP_ALLOC_NAME "ChipName"
+#define CELL_ALLOC_NAME "CellName"
+#define MISC_NUM 32
+#define MISC_NAME "META00"
+#define MISC_NAME2 "META01"
+#define NUM_BIAS_DATA 10
+#define TEST_NUM_ROWS 32
+#define TEST_NUM_COLS 32
+#define NUM_READOUTS	4
+#define NUM_CELLS	6
+#define NUM_CHIPS	8
+
+psPlaneTransform *PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM()
+{
+    psPlaneTransform *pt = psPlaneTransformAlloc(1, 1);
+    pt->x->coeff[1][0] = 1.0;
+    pt->y->coeff[0][1] = 1.0;
+    return(pt);
+}
+
+psPlaneDistort *PS_CREATE_4D_IDENTITY_PLANE_DISTORT()
+{
+    psPlaneDistort *pd = psPlaneDistortAlloc(1, 1, 1, 1);
+    pd->x->coeff[1][0][0][0] = 1.0;
+    pd->y->coeff[0][1][0][0] = 1.0;
+    return(pd);
+}
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(readout);
+}
+
+/******************************************************************************
+generateSimpleCell(): This function generates a pmCell data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmCell *generateSimpleCell(pmChip *chip)
+{
+    pmCell *cell = pmCellAlloc(chip, CELL_ALLOC_NAME);
+    psMetadataAddS32(cell->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    cell->hdu = pmHDUAlloc(NULL);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(cell->readouts, NUM_READOUTS);
+    for (int i = 0 ; i < NUM_READOUTS ; i++) {
+        cell->readouts->data[i] = psMemDecrRefCounter((psPtr) generateSimpleReadout(cell));
+    }
+    return(cell);
+}
+
+/******************************************************************************
+generateSimpleChip(): This function generates a pmChip data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmChip *generateSimpleChip(pmFPA *fpa)
+{
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    chip->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    chip->fromFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    psMetadataAddS32(chip->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(chip->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    chip->hdu = pmHDUAlloc(NULL);
+    psArrayRealloc(chip->cells, NUM_CELLS);
+    for (int i = 0 ; i < NUM_CELLS ; i++) {
+        chip->cells->data[i] = psMemDecrRefCounter((psPtr) generateSimpleCell(chip));
+    }
+    return(chip);
+}
+
+/******************************************************************************
+generateSimpleFPA(): This function generates a pmFPA data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmFPA* generateSimpleFPA(psMetadata *camera)
+{
+    pmFPA* fpa = pmFPAAlloc(camera);
+    fpa->fromTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toSky = psProjectionAlloc(0.0,0.0,10.0,10.0,PS_PROJ_TAN);
+    psMetadataAddS32(fpa->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    if (camera != NULL) {
+        psMetadataAddS32((psMetadata *) fpa->camera, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    }
+    psMetadataAddS32(fpa->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    fpa->hdu = pmHDUAlloc(NULL);
+    psArrayRealloc(fpa->chips, NUM_CHIPS);
+    for (int i = 0 ; i < NUM_CHIPS ; i++) {
+        fpa->chips->data[i] = psMemDecrRefCounter((psPtr) generateSimpleChip(fpa));
+    }
+
+    return(fpa);
+}
+
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    plan_tests(92);
+
+    // ------------------------------------------------------------------------
+    // pmFPAAlloc() tests
+    // Call pmFPAAlloc() with NULL pmCamera input.
+    {
+        psMemId id = psMemGetId();
+        pmFPA* fpa = pmFPAAlloc(NULL);
+        ok(fpa != NULL && psMemCheckFPA(fpa), "pmFPAAlloc() returned a non-NULL pmFPA with a NULL pmCamera input");
+        psFree(fpa);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // Call pmFPAAlloc() with acceptable input parameters.
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = pmFPAAlloc(camera);
+        ok(fpa != NULL, "pmFPAAlloc() returned a non-NULL");
+        ok(fpa->fromTPA == NULL, "pmFPAAlloc() set ->fromTPA to NULL");
+        ok(fpa->toTPA == NULL, "pmFPAAlloc() set ->toTPA to NULL");
+        ok(fpa->toSky == NULL, "pmFPAAlloc() set ->toSky to NULL");
+        ok(fpa->concepts != NULL &&
+           psMemCheckMetadata(fpa->concepts), "pmFPAAlloc() set ->concepts correctly");
+        ok(fpa->conceptsRead == PM_CONCEPT_SOURCE_NONE, "pmFPAAlloc() set ->conceptsRead correctly");
+        ok(fpa->analysis != NULL &&
+           psMemCheckMetadata(fpa->analysis), "pmFPAAlloc() set ->analysis correctly");
+        ok(fpa->camera == camera, "pmFPAAlloc() set ->camera correctly");
+        ok(fpa->chips != NULL &&
+           psMemCheckArray(fpa->chips), "pmFPAAlloc() set ->chips correctly");
+        ok(fpa->hdu == NULL, "pmFPAAlloc() set ->hdu to NULL");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Populate the pmFPA struct with real data to ensure they were psFree()'ed correctly.
+    {
+        psMemId id = psMemGetId();
+        pmFPA* fpa = generateSimpleFPA(NULL);
+        psFree(fpa);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks (Populate the pmFPA struct with real data)");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // pmFPAFreeData() tests
+    // Call pmFPAFreeData() with NULL pmFPA input parameter
+    {
+        psMemId id = psMemGetId();
+        pmFPAFreeData(NULL);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks (Call pmFPAFreeData() with NULL pmFPA input parameter)");
+    }
+
+
+    // Call pmFPAFreeData() with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        pmFPA* fpa = generateSimpleFPA(NULL);
+        fpa->hdu->images = psArrayAlloc(10);
+        fpa->hdu->weights = psArrayAlloc(10);
+        fpa->hdu->masks = psArrayAlloc(10);
+        pmFPAFreeData(fpa);
+        ok(fpa->hdu->images == NULL, "pmFPAFreeData() correctly set fpa->hdu->images to NULL");
+        ok(fpa->hdu->weights == NULL, "pmFPAFreeData() correctly set fpa->hdu->weights to NULL");
+        ok(fpa->hdu->masks == NULL, "pmFPAFreeData() correctly set fpa->hdu->masks to NULL");
+        psFree(fpa);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // pmChipAlloc() tests
+    // Call pmChipAlloc() with NULL input parameters
+    {
+        psMemId id = psMemGetId();
+        pmChip *chip = pmChipAlloc(NULL, CHIP_ALLOC_NAME);
+        ok(chip != NULL, "pmChipAlloc() returned non-NULL with NULL pmFPA input parameter");
+        psFree(chip);
+        pmFPA* fpa = generateSimpleFPA(NULL);
+        chip = pmChipAlloc(fpa, NULL);
+        ok(chip != NULL, "pmChipAlloc() returned non-NULL with NULL chip name input parameter");
+        psFree(fpa);
+        psFree(chip);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmChipAlloc() tests
+    // XXX: Add tests for NULL inputs.
+    {
+        psMemId id = psMemGetId();
+        pmFPA* fpa = generateSimpleFPA(NULL);
+        pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+        ok(chip != NULL, "pmChipAlloc() returned non-NULL");
+        ok(chip->toFPA == NULL, "pmChipAlloc() set chip->toChip to NULL");
+        ok(chip->fromFPA == NULL, "pmChipAlloc() set chip->fromChip to NULL");
+        ok(chip->concepts != NULL &&
+           psMemCheckMetadata(chip->concepts), "pmChipAlloc() set ->concepts correctly");
+        ok(chip->conceptsRead == PM_CONCEPT_SOURCE_NONE, "pmCellAlloc() set ->conceptsRead correctly");
+        ok(chip->analysis != NULL &&
+           psMemCheckMetadata(chip->analysis), "pmChipAlloc() set ->analysis correctly");
+        ok(chip->cells != NULL &&
+           psMemCheckArray(chip->cells), "pmChipAlloc() set ->cells correctly");
+        ok(chip->parent == fpa, "pmChipAlloc() set ->parent correctly");
+        ok(chip->process == true, "pmChipAlloc() set ->process correctly");
+        ok(chip->file_exists == false, "pmChipAlloc() set ->file_exists correctly");
+        ok(chip->data_exists == false, "pmChipAlloc() set ->data_exists correctly");
+        ok(chip->hdu == NULL, "pmChipAlloc() set ->hdu to NULL");
+        psFree(fpa);
+        psFree(chip);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Populate the pmChip struct with real data to ensure they were free'ed correctly.
+    {
+        psMemId id = psMemGetId();
+        pmChip *chip = generateSimpleChip(NULL);
+        psFree(chip);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks (Populate the pmChip struct with real data)");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // pmChipFreeData() tests
+    // Call pmChipFreeData() with NULL pmFPA input parameter
+    {
+        psMemId id = psMemGetId();
+        pmChipFreeData(NULL);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks (Call pmChipFreeData() with NULL pmFPA input parameter)");
+    }
+
+
+    // Call pmChipFreeData() with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        pmChip* chip = generateSimpleChip(NULL);
+        chip->hdu->images = psArrayAlloc(10);
+        chip->hdu->weights = psArrayAlloc(10);
+        chip->hdu->masks = psArrayAlloc(10);
+        pmChipFreeData(chip);
+        ok(chip->hdu->images == NULL, "pmChipFreeData() correctly set chip->hdu->images to NULL");
+        ok(chip->hdu->weights == NULL, "pmChipFreeData() correctly set chip->hdu->weights to NULL");
+        ok(chip->hdu->masks == NULL, "pmChipFreeData() correctly set chip->hdu->masks to NULL");
+        psFree(chip);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // pmChipFreeCells() tests
+    // Call pmChipFreeCells() with NULL pmFPA input parameter
+    {
+        psMemId id = psMemGetId();
+        pmChipFreeCells(NULL);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks (Call pmChipFreeCells() with NULL pmFPA input parameter)");
+    }
+
+
+    // Call pmChipFreeCells() with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        pmChip* chip = generateSimpleChip(NULL);
+        pmChipFreeCells(chip);
+        ok(chip->cells->n == 0, "pmChipFreeCells() free'ed chip->cells correctly");
+        psFree(chip);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // pmCellAlloc() tests
+    // Call pmCellAlloc() with NULL input parameters
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = pmCellAlloc(NULL, CELL_ALLOC_NAME);
+        ok(cell != NULL, "pmCellAlloc returned non-NULL with NULL pmChip input parameter");
+        psFree(cell);
+
+        pmChip *chip = pmChipAlloc(NULL, NULL);
+        cell = pmCellAlloc(chip, NULL);
+        ok(cell != NULL, "pmCellAlloc returned non-NULL with NULL cell name input parameter");
+        psFree(chip);
+        psFree(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmCellAlloc() tests
+    // Call pmCellAlloc() with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+        pmCell *cell = pmCellAlloc(chip, CELL_ALLOC_NAME);
+        ok(cell != NULL, "pmCellAlloc returned non-NULL");
+        ok(cell->concepts != NULL &&
+           psMemCheckMetadata(cell->concepts), "pmCellpAlloc() set ->concepts correctly");
+        ok(cell->conceptsRead == PM_CONCEPT_SOURCE_NONE, "pmCellAlloc() set ->conceptsRead correctly");
+        ok(cell->config == NULL, "pmCellpAlloc() set ->config to NULL");
+        ok(cell->analysis != NULL &&
+           psMemCheckMetadata(cell->analysis), "pmCellAlloc() set ->analysis correctly");
+        ok(cell->readouts != NULL &&
+           psMemCheckArray(cell->readouts), "pmCellAlloc() set ->readouts correctly");
+        ok(cell->parent == chip, "pmCellAlloc() set ->parent correctly");
+        ok(cell->process == true, "pmCellAlloc() set ->process correctly");
+        ok(cell->file_exists == false, "pmCellAlloc() set ->file_exists correctly");
+        ok(cell->data_exists == false, "pmCellAlloc() set ->data_exists correctly");
+        ok(cell->hdu == NULL, "pmCellAlloc() set ->hdu to NULL");
+        psFree(camera);
+        psFree(fpa);
+        psFree(chip);
+        psFree(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Populate the pmCell struct with real data to ensure they were
+    // psFree()'ed correctly.
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        psFree(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks (Populate the pmCell struct with real data)");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // pmCellFreeData() tests
+    // Call pmCellFreeData() with NULL pmFPA input parameter
+    {
+        psMemId id = psMemGetId();
+        pmCellFreeData(NULL);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks (Call pmCellFreeData() with NULL pmFPA input parameter)");
+    }
+
+
+    // Call pmCellFreeData() with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        cell->hdu->images = psArrayAlloc(10);
+        cell->hdu->weights = psArrayAlloc(10);
+        cell->hdu->masks = psArrayAlloc(10);
+        pmCellFreeData(cell);
+        ok(cell->hdu->images == NULL, "pmCellFreeData() correctly set cell->hdu->images to NULL");
+        ok(cell->hdu->weights == NULL, "pmCellFreeData() correctly set cell->hdu->weights to NULL");
+        ok(cell->hdu->masks == NULL, "pmCellFreeData() correctly set cell->hdu->masks to NULL");
+        psFree(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // test pmCellFreeReadouts()
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmCellFreeReadouts(cell);
+        ok(cell->readouts->n == 0, "pmCellFreeReadouts() correctly set cell->readouts->n to 0");
+        psFree(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks (pmCellFreeReadouts)");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // pmReadoutAlloc() tests
+    // Call pmReadoutAlloc() with NULL input parameters
+    {
+        psMemId id = psMemGetId();
+        pmReadout *readout = pmReadoutAlloc(NULL);
+        ok(readout != NULL, "pmReadoutAlloc() returned non-NULL with NULL pmCell input parameter");
+        psFree(readout);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmReadoutAlloc() with acceptable parameters
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+        pmCell *cell = pmCellAlloc(chip, CELL_ALLOC_NAME);
+        pmReadout *readout = pmReadoutAlloc(cell);
+        ok(readout != NULL, "pmReadoutAlloc() returned non-NULL");
+        ok(readout->col0 == 0, "pmReadoutAlloc() set ->col0 correctly");
+        ok(readout->row0 == 0, "pmReadoutAlloc() set ->row0 correctly");
+        ok(readout->image == NULL, "pmReadoutAlloc() set ->image correctly");
+        ok(readout->mask == NULL, "pmReadoutAlloc() set ->mask correctly");
+        ok(readout->weight == NULL, "pmReadoutAlloc() set ->weight correctly");
+        ok(readout->bias != NULL &&
+           psMemCheckList(readout->bias), "pmReadoutAlloc() set ->bias correctly");
+        ok(readout->analysis != NULL &&
+           psMemCheckMetadata(readout->analysis), "pmReadoutAlloc() set ->analysis correctly");
+        ok(readout->parent == cell, "pmReadoutAlloc() set ->parent correctly");
+        ok(readout->process == true, "pmReadoutAlloc() set ->process correctly");
+        ok(readout->file_exists == false, "pmReadoutAlloc() set ->file_exists correctly");
+        ok(readout->data_exists == false, "pmReadoutAlloc() set ->data_exists correctly");
+        psFree(camera);
+        psFree(fpa);
+        psFree(chip);
+        psFree(cell);
+        psFree(readout);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Populate the pmReadout struct with real data to ensure they were
+    // psFree()'ed correctly.
+    {
+        psMemId id = psMemGetId();
+        pmReadout *readout = generateSimpleReadout(NULL);
+        psFree(readout);
+        // XXX: The pmReadout->bias list is not being free'ed correctly.
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // pmReadoutFreeData() tests
+    // Call pmReadoutFreeData() with NULL pmFPA input parameter
+    {
+        psMemId id = psMemGetId();
+        pmReadoutFreeData(NULL);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks (Call pmReadoutFreeData() with NULL pmFPA input parameter)");
+    }
+
+
+    // Call pmReadoutFreeData() with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        pmReadout *readout = generateSimpleReadout(NULL);
+        pmReadoutFreeData(readout);
+        ok(readout->image == NULL, "pmReadoutFreeData() correctly set readout->image to NULL");
+        ok(readout->weight == NULL, "pmReadoutFreeData() correctly set readout->weight to NULL");
+        ok(readout->mask == NULL, "pmReadoutFreeData() correctly set readout->mask to NULL");
+        psFree(readout);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // pmFPACheckParents() tests
+    // psBool pmFPACheckParents(pmFPA *fpa)
+    // Call pmFPACheckParents() with NULL pmFPA input parameter
+    {
+        psMemId id = psMemGetId();
+        bool rc = pmFPACheckParents(NULL);
+        ok(rc == false, "pmFPACheckParents() returned FALSE with NULL pmFPA input parameter");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmFPACheckParents() tests
+    // Call pmFPACheckParents() with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        pmFPA* fpa = generateSimpleFPA(NULL);
+        bool rc = pmFPACheckParents(fpa);
+        ok(rc == true, "pmFPACheckParents() returned FALSE with acceptable input parameters");
+        psFree(fpa);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPACellSquish.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPACellSquish.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPACellSquish.c	(revision 22157)
@@ -0,0 +1,193 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+
+// XXX: Use better name for the temporary FITS file
+// XXX: The code to generate and free the FPA hierarchy was copied from
+// tap-pmFPA.c.  EIther include it directly, or library, or something.
+// Also, get rid of the manual free functions and use psFree() once
+// it correctly frees child members
+// XXX: For the genSimpleFPA() code, add IDs to each function so that
+// the values set in each chip-?cell-?hdu-?image are unique
+// XXX: For the genSimpleFPA() code, write masks and weights as well
+
+#define CHIP_ALLOC_NAME        "ChipName"
+#define CELL_ALLOC_NAME        "CellName"
+#define MISC_NUM                32
+#define MISC_NAME              "META00"
+#define MISC_NAME2             "META01"
+#define NUM_BIAS_DATA           10
+#define TEST_NUM_ROWS           4
+#define TEST_NUM_COLS           4
+#define NUM_READOUTS            3
+#define NUM_CELLS               10
+#define NUM_CHIPS               8
+#define NUM_HDUS                5
+#define BASE_IMAGE              10
+#define BASE_MASK               40
+#define BASE_WEIGHT             70
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+
+psPlaneTransform *PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM()
+{
+    psPlaneTransform *pt = psPlaneTransformAlloc(1, 1);
+    pt->x->coeff[1][0] = 1.0;
+    pt->y->coeff[0][1] = 1.0;
+    return(pt);
+}
+
+psPlaneDistort *PS_CREATE_4D_IDENTITY_PLANE_DISTORT()
+{
+    psPlaneDistort *pd = psPlaneDistortAlloc(1, 1, 1, 1);
+    pd->x->coeff[1][0][0][0] = 1.0;
+    pd->y->coeff[0][1][0][0] = 1.0;
+    return(pd);
+}
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(readout);
+}
+
+/******************************************************************************
+generateSimpleCell(): This function generates a pmCell data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmCell *generateSimpleCell(pmChip *chip)
+{
+    pmCell *cell = pmCellAlloc(chip, CELL_ALLOC_NAME);
+
+    psMetadataAddS32(cell->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(cell->readouts, NUM_READOUTS);
+    cell->hdu = pmHDUAlloc("cellExtName");
+    for (int i = 0 ; i < NUM_READOUTS ; i++) {
+        cell->readouts->data[i] = psMemDecrRefCounter((psPtr) generateSimpleReadout(cell));
+    }
+
+    // First try to read data from ../dataFiles, then try dataFiles.
+    bool rc = pmConfigFileRead(&cell->hdu->format, "../dataFiles/camera0/format0.config", "Camera format 0");
+    if (!rc) {
+        rc = pmConfigFileRead(&cell->hdu->format, "dataFiles/camera0/format0.config", "Camera format 0");
+        if (!rc) {
+            diag("pmConfigFileRead() was unsuccessful (from generateSimpleCell())");
+	}
+    }
+
+    cell->hdu->images = psArrayAlloc(NUM_HDUS);
+    cell->hdu->masks = psArrayAlloc(NUM_HDUS);
+    cell->hdu->weights = psArrayAlloc(NUM_HDUS);
+    for (int k = 0 ; k < NUM_HDUS ; k++) {
+        cell->hdu->images->data[k]  = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        cell->hdu->masks->data[k]   = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        cell->hdu->weights->data[k] = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(cell->hdu->images->data[k], (float) (BASE_IMAGE+k));
+        psImageInit(cell->hdu->masks->data[k], (psU8) (BASE_MASK+k));
+        psImageInit(cell->hdu->weights->data[k], (float) (BASE_WEIGHT+k));
+    }
+
+    //XXX: Should the region be set some other way?  Like through the various config files?
+//    psRegion *region = psRegionAlloc(0.0, TEST_NUM_COLS-1, 0.0, TEST_NUM_ROWS-1);
+    psRegion *region = psRegionAlloc(0.0, 0.0, 0.0, 0.0);
+    // You shouldn't have to remove the key from the metadata.  Find out how to simply change the key value.
+    psMetadataRemoveKey(cell->concepts, "CELL.TRIMSEC");
+    psMetadataAddPtr(cell->concepts, PS_LIST_TAIL|PS_META_REPLACE, "CELL.TRIMSEC", PS_DATA_REGION, "I am a region", region);
+    psFree(region);
+    return(cell);
+}
+
+/******************************************************************************
+generateSimpleChip(): This function generates a pmChip data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmChip *generateSimpleChip(pmFPA *fpa)
+{
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    chip->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    chip->fromFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    psMetadataAddS32(chip->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(chip->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+
+    psArrayRealloc(chip->cells, NUM_CELLS);
+    for (int i = 0 ; i < NUM_CELLS ; i++) {
+        chip->cells->data[i] = psMemDecrRefCounter((psPtr) generateSimpleCell(chip));
+    }
+    return(chip);
+}
+
+/******************************************************************************
+generateSimpleFPA(): This function generates a pmFPA data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmFPA* generateSimpleFPA(psMetadata *camera)
+{
+    pmFPA* fpa = pmFPAAlloc(camera);
+    fpa->fromTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toSky = psProjectionAlloc(0.0,0.0,10.0,10.0,PS_PROJ_TAN);
+    psMetadataAddS32(fpa->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    if (camera != NULL) {
+        psMetadataAddS32((psMetadata *) fpa->camera, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    }
+    psMetadataAddS32(fpa->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(fpa->chips, NUM_CHIPS);
+    for (int i = 0 ; i < NUM_CHIPS ; i++) {
+        fpa->chips->data[i] = psMemDecrRefCounter((psPtr) generateSimpleChip(fpa));
+    }
+
+    pmConceptsBlankFPA(fpa);
+    return(fpa);
+}
+
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(4);
+
+
+    // ----------------------------------------------------------------------
+    // pmCellSquish() tests
+    // bool pmCellSquish(pmCell *cell, psMaskType maskVal, bool useShifts)
+    {
+        psMemId id = psMemGetId();
+        ok(!pmCellSquish(NULL, 0, false), "pmCellSquish(NULL) returned NULL");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // ----------------------------------------------------------------------
+    // pmCellSquish() tests
+    // bool pmCellSquish(pmCell *cell, psMaskType maskVal, bool useShifts)
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(pmCellSquish(cell, 0, false), "pmCellSquish(NULL) returned NULL");
+        psFree(camera);
+        psFree(fpa);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPAConstruct.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPAConstruct.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPAConstruct.c	(revision 22157)
@@ -0,0 +1,72 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(9);
+
+    // ----------------------------------------------------------------------
+    // pmFPAConstruct() tests
+    // pmFPA *pmFPAConstruct(const psMetadata *camera)
+    // test will NULL camera input param
+    {
+        psMemId id = psMemGetId();
+        pmFPA* fpa = pmFPAConstruct(NULL);
+        ok(fpa == NULL, "pmFPAConstruct() NULL will NULL camera input param");
+        psFree(fpa);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // pmFPA *pmFPAConstruct(const psMetadata *camera)
+    // test will acceptable data
+    // XXX: The memory leak code is put outside the call to pmConfigFileRead() because of
+    // a memory leak in pmConfigFileRead().
+    {
+        psMetadata *camera = psMetadataAlloc();
+        bool rc = pmConfigFileRead(&camera, "dataFiles/camera0/camera.config", "CAMERA 0 config file");
+        if (!rc) {
+             rc = pmConfigFileRead(&camera, "../dataFiles/camera0/camera.config", "CAMERA 0 config file");
+	}
+
+        // Generate the pmFPA heirarchy
+        psMemId id = psMemGetId();
+        ok(rc, "Succesfully read camera format file");
+        pmFPA* fpa = pmFPAConstruct(camera);
+        ok(fpa != NULL, "pmFPAConstruct() returned non-NULL");
+        if (VERBOSE) {
+            pmFPAPrint(stdout, fpa, true, true);
+	}
+        bool errorFlag = false;
+        ok(fpa->chips->n == 2, "pmFPAConstruct() set fpa->chips->n (%d)", fpa->chips->n);
+        for (int chipID = 0 ; chipID < fpa->chips->n ; chipID++) {
+            pmChip *chip = fpa->chips->data[chipID];
+            ok(chip->cells->n == 2, "pmFPAConstruct() set chip->cells->n (%d)", chip->cells->n);
+            for (int cellID = 0 ; cellID < chip->cells->n ; cellID++) {
+                pmCell *cell = chip->cells->data[cellID];
+                for (int readoutID = 0 ; readoutID < cell->readouts->n ; readoutID++) {
+                    pmReadout *readout = cell->readouts->data[readoutID];
+                    readout = readout;
+		}
+	    }
+	}
+        ok(!errorFlag, "pmFPAConstruct() read the pmFPA structure correctly");
+
+
+
+
+        psFree(fpa);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+
+        psFree(camera);
+    }
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPACopy.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPACopy.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPACopy.c	(revision 22157)
@@ -0,0 +1,601 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+
+#define CHIP_ALLOC_NAME		"ChipName"
+#define CELL_ALLOC_NAME		"CellName"
+#define MISC_NUM_SOURCE		32
+#define MISC_NUM_TARGET		16
+#define MISC_NAME_SOURCE	"META00_SOURCE"
+#define MISC_NAME_TARGET	"META00_TARGET"
+#define NUM_BIAS_DATA		10
+#define SOURCE_NUM_ROWS		16
+#define SOURCE_NUM_COLS		8
+#define TARGET_NUM_ROWS		15
+#define TARGET_NUM_COLS		5
+#define NUM_READOUTS		4
+#define NUM_CELLS		6
+#define NUM_CHIPS		8
+#define SOURCE_BASE		10
+#define BASE_INC		20
+#define TARGET_BASE		(SOURCE_BASE + BASE_INC)
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+
+psPlaneTransform *PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM()
+{
+    psPlaneTransform *pt = psPlaneTransformAlloc(1, 1);
+    pt->x->coeff[1][0] = 1.0;
+    pt->y->coeff[0][1] = 1.0;
+    return(pt);
+}
+
+psPlaneDistort *PS_CREATE_4D_IDENTITY_PLANE_DISTORT()
+{
+    psPlaneDistort *pd = psPlaneDistortAlloc(1, 1, 1, 1);
+    pd->x->coeff[1][0][0][0] = 1.0;
+    pd->y->coeff[0][1][0][0] = 1.0;
+    return(pd);
+}
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadoutSource(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(SOURCE_NUM_COLS, SOURCE_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(SOURCE_NUM_COLS, SOURCE_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(SOURCE_NUM_COLS, SOURCE_NUM_ROWS, PS_TYPE_F32);
+    for (int i = 0 ; i < SOURCE_NUM_ROWS ; i++) {
+        for (int j = 0 ; j < SOURCE_NUM_COLS ; j++) {
+            readout->image->data.F32[i][j] = (float) (i + j + SOURCE_BASE);
+            readout->mask->data.U8[i][j] = (psU8) (i + j + SOURCE_BASE);
+            readout->weight->data.F32[i][j] = (float) (i + j + SOURCE_BASE);
+	}
+    }
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(SOURCE_NUM_COLS, SOURCE_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME_SOURCE, 0, NULL, MISC_NUM_SOURCE);
+    return(readout);
+}
+
+/******************************************************************************
+generateSimpleCell(): This function generates a pmCell data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmCell *generateSimpleCellSource(pmChip *chip)
+{
+    pmCell *cell = pmCellAlloc(chip, CELL_ALLOC_NAME);
+    psMetadataAddS32(cell->analysis, PS_LIST_HEAD, MISC_NAME_SOURCE, 0, NULL, MISC_NUM_SOURCE);
+    cell->hdu = pmHDUAlloc(NULL);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, MISC_NAME_SOURCE, 0, NULL, MISC_NUM_SOURCE);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, "CELL.XPARITY", PS_META_REPLACE, NULL, 1);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, "CELL.YPARITY", PS_META_REPLACE, NULL, 1);
+
+    psArrayRealloc(cell->readouts, NUM_READOUTS);
+    for (int i = 0 ; i < NUM_READOUTS ; i++) {
+        cell->readouts->data[i] = psMemDecrRefCounter((psPtr) generateSimpleReadoutSource(cell));
+    }
+    cell->data_exists = true;
+    return(cell);
+}
+
+/******************************************************************************
+generateSimpleChip(): This function generates a pmChip data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmChip *generateSimpleChipSource(pmFPA *fpa)
+{
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    chip->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    chip->fromFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    psMetadataAddS32(chip->analysis, PS_LIST_HEAD, MISC_NAME_SOURCE, 0, NULL, MISC_NUM_SOURCE);
+    psMetadataAddS32(chip->concepts, PS_LIST_HEAD, MISC_NAME_SOURCE, 0, NULL, MISC_NUM_SOURCE);
+    chip->hdu = pmHDUAlloc(NULL);
+    psArrayRealloc(chip->cells, NUM_CELLS);
+    for (int i = 0 ; i < NUM_CELLS ; i++) {
+        chip->cells->data[i] = psMemDecrRefCounter((psPtr) generateSimpleCellSource(chip));
+    }
+    chip->data_exists = true;
+    return(chip);
+}
+
+/******************************************************************************
+generateSimpleFPA(): This function generates a pmFPA data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmFPA* generateSimpleFPASource(psMetadata *camera)
+{
+    pmFPA* fpa = pmFPAAlloc(camera);
+    fpa->fromTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toSky = psProjectionAlloc(0.0,0.0,10.0,10.0,PS_PROJ_TAN);
+    psMetadataAddS32(fpa->analysis, PS_LIST_HEAD, MISC_NAME_SOURCE, 0, NULL, MISC_NUM_SOURCE);
+    if (camera != NULL) {
+        psMetadataAddS32((psMetadata *) fpa->camera, PS_LIST_HEAD, MISC_NAME_SOURCE, 0, NULL, MISC_NUM_SOURCE);
+    }
+    psMetadataAddS32(fpa->concepts, PS_LIST_HEAD, MISC_NAME_SOURCE, 0, NULL, MISC_NUM_SOURCE);
+    fpa->hdu = pmHDUAlloc(NULL);
+    psArrayRealloc(fpa->chips, NUM_CHIPS);
+    for (int i = 0 ; i < NUM_CHIPS ; i++) {
+        fpa->chips->data[i] = psMemDecrRefCounter((psPtr) generateSimpleChipSource(fpa));
+    }
+
+    return(fpa);
+}
+
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadoutTarget(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TARGET_NUM_COLS, TARGET_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(TARGET_NUM_COLS, TARGET_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TARGET_NUM_COLS, TARGET_NUM_ROWS, PS_TYPE_F32);
+    for (int i = 0 ; i < TARGET_NUM_ROWS ; i++) {
+        for (int j = 0 ; j < TARGET_NUM_COLS ; j++) {
+            readout->image->data.F32[i][j] = (float) (i + j + TARGET_BASE);
+            readout->mask->data.U8[i][j] = (psU8) (i + j + TARGET_BASE);
+            readout->weight->data.F32[i][j] = (float) (i + j + TARGET_BASE);
+	}
+    }
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TARGET_NUM_COLS, TARGET_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME_TARGET, 0, NULL, MISC_NUM_TARGET);
+    return(readout);
+}
+
+/******************************************************************************
+generateSimpleCell(): This function generates a pmCell data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmCell *generateSimpleCellTarget(pmChip *chip)
+{
+    pmCell *cell = pmCellAlloc(chip, CELL_ALLOC_NAME);
+    psMetadataAddS32(cell->analysis, PS_LIST_HEAD, MISC_NAME_TARGET, 0, NULL, MISC_NUM_TARGET);
+    cell->hdu = pmHDUAlloc(NULL);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, MISC_NAME_TARGET, 0, NULL, MISC_NUM_TARGET);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, "CELL.XPARITY", PS_META_REPLACE, NULL, 1);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, "CELL.YPARITY", PS_META_REPLACE, NULL, 1);
+    psArrayRealloc(cell->readouts, 0);
+    for (int i = 0 ; i < 0 ; i++) {
+        cell->readouts->data[i] = psMemDecrRefCounter((psPtr) generateSimpleReadoutTarget(cell));
+    }
+    return(cell);
+}
+
+/******************************************************************************
+generateSimpleChip(): This function generates a pmChip data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmChip *generateSimpleChipTarget(pmFPA *fpa)
+{
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    chip->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    chip->fromFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    psMetadataAddS32(chip->analysis, PS_LIST_HEAD, MISC_NAME_TARGET, 0, NULL, MISC_NUM_TARGET);
+    psMetadataAddS32(chip->concepts, PS_LIST_HEAD, MISC_NAME_TARGET, 0, NULL, MISC_NUM_TARGET);
+    chip->hdu = pmHDUAlloc(NULL);
+    psArrayRealloc(chip->cells, NUM_CELLS);
+    for (int i = 0 ; i < NUM_CELLS ; i++) {
+        chip->cells->data[i] = psMemDecrRefCounter((psPtr) generateSimpleCellTarget(chip));
+    }
+    return(chip);
+}
+
+/******************************************************************************
+generateSimpleFPA(): This function generates a pmFPA data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmFPA* generateSimpleFPATarget(psMetadata *camera)
+{
+    pmFPA* fpa = pmFPAAlloc(camera);
+    fpa->fromTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toSky = psProjectionAlloc(0.0,0.0,10.0,10.0,PS_PROJ_TAN);
+    psMetadataAddS32(fpa->analysis, PS_LIST_HEAD, MISC_NAME_TARGET, 0, NULL, MISC_NUM_TARGET);
+    if (camera != NULL) {
+        psMetadataAddS32((psMetadata *) fpa->camera, PS_LIST_HEAD, MISC_NAME_TARGET, 0, NULL, MISC_NUM_TARGET);
+    }
+    psMetadataAddS32(fpa->concepts, PS_LIST_HEAD, MISC_NAME_TARGET, 0, NULL, MISC_NUM_TARGET);
+    fpa->hdu = pmHDUAlloc(NULL);
+    psArrayRealloc(fpa->chips, NUM_CHIPS);
+    for (int i = 0 ; i < NUM_CHIPS ; i++) {
+        fpa->chips->data[i] = psMemDecrRefCounter((psPtr) generateSimpleChipTarget(fpa));
+    }
+
+    return(fpa);
+}
+
+bool testCellCopy(pmCell* cellTarget, pmCell *cellSource) {
+    bool errorFlag = false;
+    for (int readoutID = 0 ; readoutID < cellTarget->readouts->n ; readoutID++) {
+        pmReadout *readoutTarget = cellTarget->readouts->data[readoutID];
+        pmReadout *readoutSource = cellSource->readouts->data[readoutID];
+
+        psImage *imageTarget = readoutTarget->image;
+        psImage *imageSource = readoutSource->image;
+        for (int i = 0 ; i < SOURCE_NUM_ROWS ; i++) {
+            for (int j = 0 ; j < SOURCE_NUM_COLS ; j++) {
+                if (imageSource->data.F32[i][j] != imageTarget->data.F32[i][j]) {
+                    diag("ERROR: target readout[%d] image[%d][%d] is %.2f, should be %.2f",
+                          readoutID, i, j, imageTarget->data.F32[i][j], imageSource->data.F32[i][j]);
+                    errorFlag = true;
+		}
+	    }
+	}
+        if (errorFlag) {
+            diag("ERROR: pmCellCopy() did not set the data for readout %d, image correctly", readoutID);
+	}
+
+        psImage *maskTarget = readoutTarget->mask;
+        psImage *maskSource = readoutSource->mask;
+        for (int i = 0 ; i < SOURCE_NUM_ROWS ; i++) {
+            for (int j = 0 ; j < SOURCE_NUM_COLS ; j++) {
+                if (maskTarget->data.U8[i][j] != maskSource->data.U8[i][j]) {
+                    diag("ERROR: target readout[%d] mask[%d][%d] is %d, should be %d",
+                          readoutID, i, j, maskTarget->data.U8[i][j], maskSource->data.U8[i][j]);
+                    errorFlag = true;
+		}
+	    }
+	}
+        if (errorFlag) {
+            diag("ERROR: pmCellCopy() did not set the data for readout %d, mask correctly", readoutID);
+	}
+
+        psImage *weightTarget = readoutTarget->weight;
+        psImage *weightSource = readoutSource->weight;
+        for (int i = 0 ; i < SOURCE_NUM_ROWS ; i++) {
+            for (int j = 0 ; j < SOURCE_NUM_COLS ; j++) {
+                if (weightTarget->data.F32[i][j] != weightSource->data.F32[i][j]) {
+                    diag("ERROR: target readout[%d] weight[%d][%d] is %.2f, should be %.2f",
+                          readoutID, i, j, weightTarget->data.F32[i][j], weightSource->data.F32[i][j]);
+                    errorFlag = true;
+		}
+	    }
+	}
+        if (errorFlag) {
+            diag("ERROR: pmCellCopy() did not set the data for readout %d, weight correctly", readoutID);
+	}
+    }
+    return(errorFlag);
+}
+
+
+bool testChipCopy(pmChip* chipTarget, pmChip *chipSource) {
+    bool errorFlag = false;
+    for (int cellID = 0 ; cellID < chipSource->cells->n ; cellID++) {
+        pmCell *cellTarget = chipTarget->cells->data[cellID];
+        pmCell *cellSource = chipSource->cells->data[cellID];
+        errorFlag|= testCellCopy(cellTarget, cellSource);
+    }
+    return(errorFlag);
+}
+
+bool testFPACopy(pmFPA* fpaTarget, pmFPA *fpaSource) {
+    bool errorFlag = false;
+    for (int chipID = 0 ; chipID < fpaSource->chips->n ; chipID++) {
+        pmChip *chipTarget = fpaTarget->chips->data[chipID];
+        pmChip *chipSource = fpaSource->chips->data[chipID];
+        errorFlag|= testChipCopy(chipTarget, chipSource);
+    }
+    return(errorFlag);
+}
+
+
+bool testCellCopyStructure(pmCell* cellTarget, pmCell *cellSource) {
+    bool errorFlag = false;
+    for (int readoutID = 0 ; readoutID < cellTarget->readouts->n ; readoutID++) {
+        pmReadout *readoutTarget = cellTarget->readouts->data[readoutID];
+        if (readoutTarget->image->numRows != SOURCE_NUM_ROWS ||
+            readoutTarget->image->numCols != SOURCE_NUM_COLS) {
+            diag("ERROR: readoutTarget->image size is (%d by %d)\n", readoutTarget->image->numRows, 
+                  readoutTarget->image->numCols);
+            errorFlag = true;
+	}
+
+        if (readoutTarget->mask->numRows != SOURCE_NUM_ROWS ||
+            readoutTarget->mask->numCols != SOURCE_NUM_COLS) {
+            diag("ERROR: readoutTarget->mask size is (%d by %d)\n", readoutTarget->mask->numRows, 
+                  readoutTarget->mask->numCols);
+            errorFlag = true;
+	}
+        if (readoutTarget->weight->numRows != SOURCE_NUM_ROWS ||
+            readoutTarget->weight->numCols != SOURCE_NUM_COLS) {
+            diag("ERROR: readoutTarget->weight size is (%d by %d)\n", readoutTarget->weight->numRows, 
+                  readoutTarget->weight    ->numCols);
+            errorFlag = true;
+	}
+    }
+    return(errorFlag);
+}
+
+
+bool testChipCopyStructure(pmChip* chipTarget, pmChip *chipSource) {
+    bool errorFlag = false;
+    for (int cellID = 0 ; cellID < chipSource->cells->n ; cellID++) {
+        pmCell *cellTarget = chipTarget->cells->data[cellID];
+        pmCell *cellSource = chipSource->cells->data[cellID];
+        errorFlag|= testCellCopyStructure(cellTarget, cellSource);
+    }
+    return(errorFlag);
+}
+
+bool testFPACopyStructure(pmFPA* fpaTarget, pmFPA *fpaSource) {
+    bool errorFlag = false;
+    for (int chipID = 0 ; chipID < fpaSource->chips->n ; chipID++) {
+        pmChip *chipTarget = fpaTarget->chips->data[chipID];
+        pmChip *chipSource = fpaSource->chips->data[chipID];
+        errorFlag|= testChipCopyStructure(chipTarget, chipSource);
+    }
+    return(errorFlag);
+}
+
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(92);
+
+
+    // ------------------------------------------------------------------------
+    // pmFPACopy() tests
+    // Call pmFPACopy() with bad input parameters.
+    if (1) {
+        psMemId id = psMemGetId();
+        pmFPA* fpaSource = generateSimpleFPASource(NULL);
+        pmFPA* fpaTarget = generateSimpleFPATarget(NULL);
+        bool rc = pmFPACopy(NULL, fpaSource);
+        ok(rc == FALSE, "pmFPACopy() returned FALSE with NULL target pmFPA input parameter");
+        rc = pmFPACopy(fpaTarget, NULL);
+        ok(rc == FALSE, "pmFPACopy() returned FALSE with NULL source pmFPA input parameter");
+        psFree(fpaSource);
+        psFree(fpaTarget);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmFPACopy() with acceptable input parameters.
+    if (1) {
+        psMemId id = psMemGetId();
+        pmFPA* fpaSource = generateSimpleFPASource(NULL);
+        pmFPA* fpaTarget = generateSimpleFPATarget(NULL);
+        bool rc = pmFPACopy(fpaTarget, fpaSource);
+        ok(rc == true, "pmFPACopy() returned TRUE with acceptable input parameters");
+        int tmpS32 = psMetadataLookupS32(&rc, fpaTarget->concepts, MISC_NAME_TARGET);
+        ok(rc, "psMetadataLookupStr(NULL, fpaTarget->concepts, MISC_NAME_TARGET) was successful");
+        ok(tmpS32 == MISC_NUM_TARGET, "pmFPACopy() copied the source FPA concepts correctly");
+
+        tmpS32 = psMetadataLookupS32(&rc, fpaTarget->concepts, MISC_NAME_SOURCE);
+        ok(rc, "psMetadataLookupStr(NULL, fpaTarget->concepts, MISC_NAME_SOURCE) was successful");
+        ok(tmpS32 == MISC_NUM_SOURCE, "pmFPACopy() copied the source FPA concepts correctly");
+        ok(!testFPACopy(fpaTarget, fpaSource), "pmFPACopy() set the pmReadout data correctly");
+
+        psFree(fpaSource);
+        psFree(fpaTarget);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // pmChipCopy() tests
+    // Call pmChipCopy() with bad input parameters.
+    if (1) {
+        psMemId id = psMemGetId();
+        pmChip* chipSource = generateSimpleChipSource(NULL);
+        pmChip* chipTarget = generateSimpleChipTarget(NULL);
+        bool rc = pmChipCopy(NULL, chipSource);
+        ok(rc == FALSE, "pmChipCopy() returned FALSE with NULL target pmChip input parameter");
+        rc = pmChipCopy(chipTarget, NULL);
+        ok(rc == FALSE, "pmChipCopy() returned FALSE with NULL source pmChip input parameter");
+        psFree(chipSource);
+        psFree(chipTarget);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmChipCopy() with acceptable input parameters.
+    if (1) {
+        psMemId id = psMemGetId();
+        pmFPA *fpaSource = generateSimpleFPASource(NULL);
+        pmFPA *fpaTarget = generateSimpleFPATarget(NULL);
+        pmChip* chipSource = generateSimpleChipSource(fpaSource);
+        pmChip* chipTarget = generateSimpleChipTarget(fpaTarget);
+        bool rc = pmChipCopy(chipTarget, chipSource);
+        ok(rc == true, "pmChipCopy() returned TRUE with acceptable input parameters");
+        ok(!testChipCopy(chipTarget, chipSource), "pmChipCopy() set the pmReadout data correctly");
+        psFree(chipSource);
+        psFree(chipTarget);
+        psFree(fpaSource);
+        psFree(fpaTarget);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // pmCellCopy() tests
+    // Call pmCellCopy() with bad input parameters.
+    if (1) {
+        psMemId id = psMemGetId();
+        pmCell* cellSource = generateSimpleCellSource(NULL);
+        pmCell* cellTarget = generateSimpleCellTarget(NULL);
+        bool rc = pmCellCopy(NULL, cellSource);
+        ok(rc == FALSE, "pmCellCopy() returned FALSE with NULL target pmCell input parameter");
+        rc = pmCellCopy(cellTarget, NULL);
+        ok(rc == FALSE, "pmCellCopy() returned FALSE with NULL source pmCell input parameter");
+        psFree(cellSource);
+        psFree(cellTarget);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+    // Call pmCellCopy() with acceptable input parameters.
+    if (1) {
+        psMemId id = psMemGetId();
+        pmChip *parentSource = generateSimpleChipSource(NULL);
+        pmChip *parentTarget = generateSimpleChipSource(NULL);
+        pmCell* cellSource = generateSimpleCellSource(parentSource);
+        pmCell* cellTarget = generateSimpleCellTarget(parentTarget);
+        bool rc = pmCellCopy(cellTarget, cellSource);
+        ok(rc == true, "pmCellCopy() returned TRUE with NULL target pmCell input parameter");
+        ok(!testCellCopy(cellTarget, cellSource), "pmChipCopy() set the pmReadout data correctly");
+        psFree(cellSource);
+        psFree(cellTarget);
+        psFree(parentSource);
+        psFree(parentTarget);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // pmFPACopyStructure() tests
+    // bool pmFPACopyStructure(pmFPA *target, const pmFPA *source, int xBin, int yBin)
+    // Call pmFPACopyStructure() with bad input parameters.
+    if (1) {
+        psMemId id = psMemGetId();
+        pmFPA* fpaSource = generateSimpleFPASource(NULL);
+        pmFPA* fpaTarget = generateSimpleFPATarget(NULL);
+        bool rc = pmFPACopyStructure(NULL, fpaSource, 1.0, 1.0);
+        ok(rc == FALSE, "pmFPACopyStructure() returned FALSE with NULL target pmFPA input parameter");
+        rc = pmFPACopyStructure(fpaTarget, NULL, 1.0, 1.0);
+        ok(rc == FALSE, "pmFPACopyStructure() returned FALSE with NULL source pmFPA input parameter");
+        rc = pmFPACopyStructure(fpaTarget, fpaSource, 0.0, 1.0);
+        ok(rc == FALSE, "pmFPACopyStructure() returned FALSE with non-positive xBin input parameter");
+        rc = pmFPACopyStructure(fpaTarget, fpaSource, 1.0, 0.0);
+        ok(rc == FALSE, "pmFPACopyStructure() returned FALSE with non-positive yBin input parameter");
+        psFree(fpaSource);
+        psFree(fpaTarget);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmFPACopyStructure() with acceptable input parameters.
+    if (1) {
+        psMemId id = psMemGetId();
+        pmFPA* fpaSource = generateSimpleFPASource(NULL);
+        pmFPA* fpaTarget = generateSimpleFPATarget(NULL);
+        bool rc = pmFPACopyStructure(fpaTarget, fpaSource, 1.0, 1.0);
+        ok(rc == true, "pmFPACopyStructure() returned TRUE with acceptable input parameters");
+        ok(!testFPACopyStructure(fpaTarget, fpaSource), "pmFPACopyStructure() set the pmReadout data correctly");
+        int tmpS32 = psMetadataLookupS32(&rc, fpaTarget->concepts, MISC_NAME_TARGET);
+        ok(tmpS32 == MISC_NUM_TARGET, "pmFPACopy() copied the source FPA concepts correctly");
+        psFree(fpaSource);
+        psFree(fpaTarget);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // pmChipCopyStructure() tests
+    // bool pmChipCopyStructure(pmChip *target, const pmChip *source, int xBin, int yBin)
+    // Call pmChipCopyStructure() with bad input parameters.
+    if (1) {
+        psMemId id = psMemGetId();
+        pmChip* chipSource = generateSimpleChipSource(NULL);
+        pmChip* chipTarget = generateSimpleChipTarget(NULL);
+        bool rc = pmChipCopyStructure(NULL, chipSource, 1.0, 1.0);
+        ok(rc == FALSE, "pmChipCopyStructure() returned FALSE with NULL target pmChip input parameter");
+        rc = pmChipCopyStructure(chipTarget, NULL, 1.0, 1.0);
+        ok(rc == FALSE, "pmChipCopyStructure() returned FALSE with NULL source pmChip input parameter");
+        rc = pmChipCopyStructure(chipTarget, chipSource, 0.0, 1.0);
+        ok(rc == FALSE, "pmChipCopyStructure() returned FALSE with non-positive xBin input parameter");
+        rc = pmChipCopyStructure(chipTarget, chipSource, 1.0, 0.0);
+        ok(rc == FALSE, "pmChipCopyStructure() returned FALSE with non-positive yBin input parameter");
+        psFree(chipSource);
+        psFree(chipTarget);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmChipCopyStructure() with acceptable parameters.
+    if (1) {
+        psMemId id = psMemGetId();
+        pmFPA *fpaSource = generateSimpleFPASource(NULL);
+        pmFPA *fpaTarget = generateSimpleFPATarget(NULL);
+        pmChip* chipSource = generateSimpleChipSource(fpaSource);
+        pmChip* chipTarget = generateSimpleChipTarget(fpaTarget);
+        bool rc = pmChipCopyStructure(chipTarget, chipSource, 1.0, 1.0);
+        ok(rc == true, "pmChipCopyStructure() returned TRUE with acceptable input parameters");
+        ok(!testChipCopyStructure(chipTarget, chipSource), "pmChipCopyStructure() set the pmReadout data correctly");
+        psFree(chipSource);
+        psFree(chipTarget);
+        psFree(fpaSource);
+        psFree(fpaTarget);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // pmCellCopyStructure() tests
+    // bool pmCellCopyStructure(pmCell *target, const pmCell *source, int xBin, int yBin)
+    // Call pmCellCopyStructure() with bad input parameters.
+    if (1) {
+        psMemId id = psMemGetId();
+        pmCell* cellSource = generateSimpleCellSource(NULL);
+        pmCell* cellTarget = generateSimpleCellTarget(NULL);
+        bool rc = pmCellCopyStructure(NULL, cellSource, 1.0, 1.0);
+        ok(rc == FALSE, "pmCellCopyStructure() returned FALSE with NULL target pmCell input parameter");
+        rc = pmCellCopyStructure(cellTarget, NULL, 1.0, 1.0);
+        ok(rc == FALSE, "pmCellCopyStructure() returned FALSE with NULL source pmCell input parameter");
+        rc = pmCellCopyStructure(cellTarget, cellSource, 0.0, 1.0);
+        ok(rc == FALSE, "pmCellCopyStructure() returned FALSE with non-positive xBin input parameter");
+        rc = pmCellCopyStructure(cellTarget, cellSource, 1.0, 0.0);
+        ok(rc == FALSE, "pmCellCopyStructure() returned FALSE with non-positive yBin input parameter");
+        psFree(cellSource);
+        psFree(cellTarget);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmCellCopyStructure() with acceptable input parameters.
+    if (1) {
+        psMemId id = psMemGetId();
+        pmChip *parentSource = generateSimpleChipSource(NULL);
+        pmChip *parentTarget = generateSimpleChipSource(NULL);
+        pmCell* cellSource = generateSimpleCellSource(parentSource);
+        pmCell* cellTarget = generateSimpleCellTarget(parentTarget);
+        bool rc = pmCellCopyStructure(cellTarget, cellSource, 1.0, 1.0);
+        ok(rc == true, "pmCellCopyStructure() returned TRUE with NULL target pmCell input parameter");
+        ok(!testCellCopyStructure(cellTarget, cellSource), "pmCellCopyStructure() set the pmReadout data correctly");
+        psFree(cellSource);
+        psFree(cellTarget);
+        psFree(parentSource);
+        psFree(parentTarget);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // pmChipDuplicate() tests
+    // pmChip *pmChipDuplicate(pmFPA *fpa, const pmChip *source);
+    // Call pmChipDuplicate() with bad input parameters.
+    if (0) {
+        psMemId id = psMemGetId();
+        pmChip *chipSource = generateSimpleChipSource(NULL);
+        pmChip *chipTarget = pmChipDuplicate(NULL, NULL);
+        ok(chipTarget == NULL, "pmChipDuplicate() returned NULL with NULL pmChip input parameter");
+        psFree(chipSource);
+        psFree(chipTarget);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPAExtent.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPAExtent.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPAExtent.c	(revision 22157)
@@ -0,0 +1,425 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS:
+    All functions are tested.
+*/
+
+// XXX: For the genSimpleFPA() code, add IDs to each function so that
+// the values set in each chip-?cell-?hdu-?image are unique
+// XXX: For the genSimpleFPA() code, write masks and weights as well
+
+#define CHIP_ALLOC_NAME        "ChipName"
+#define CELL_ALLOC_NAME        "CellName"
+#define MISC_NUM                32
+#define MISC_NAME              "META00"
+#define MISC_NAME2             "META01"
+#define NUM_BIAS_DATA           10
+#define TEST_NUM_ROWS           4
+#define TEST_NUM_COLS           4
+#define NUM_READOUTS            3
+#define NUM_CELLS               10
+#define NUM_CHIPS               8
+#define NUM_HDUS                5
+#define BASE_IMAGE              10
+#define BASE_MASK               40
+#define BASE_WEIGHT             70
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+
+psPlaneTransform *PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM()
+{
+    psPlaneTransform *pt = psPlaneTransformAlloc(1, 1);
+    pt->x->coeff[1][0] = 1.0;
+    pt->y->coeff[0][1] = 1.0;
+    return(pt);
+}
+
+psPlaneDistort *PS_CREATE_4D_IDENTITY_PLANE_DISTORT()
+{
+    psPlaneDistort *pd = psPlaneDistortAlloc(1, 1, 1, 1);
+    pd->x->coeff[1][0][0][0] = 1.0;
+    pd->y->coeff[0][1][0][0] = 1.0;
+    return(pd);
+}
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(readout);
+}
+
+/******************************************************************************
+generateSimpleCell(): This function generates a pmCell data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmCell *generateSimpleCell(pmChip *chip)
+{
+    pmCell *cell = pmCellAlloc(chip, CELL_ALLOC_NAME);
+
+    psMetadataAddS32(cell->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(cell->readouts, NUM_READOUTS);
+    cell->hdu = pmHDUAlloc("cellExtName");
+    for (int i = 0 ; i < NUM_READOUTS ; i++) {
+        cell->readouts->data[i] = psMemDecrRefCounter((psPtr) generateSimpleReadout(cell));
+    }
+
+    bool rc = pmConfigFileRead(&cell->hdu->format, "dataFiles/camera0/format0.config", "Camera format 0");
+    if (!rc) {
+        rc = pmConfigFileRead(&cell->hdu->format, "../camera0/format0.config", "Camera format 0");
+        if (!rc) {
+            diag("pmConfigFileRead() was unsuccessful (from generateSimpleCell())");
+        }
+    }
+
+    cell->hdu->images = psArrayAlloc(NUM_HDUS);
+    cell->hdu->masks = psArrayAlloc(NUM_HDUS);
+    cell->hdu->weights = psArrayAlloc(NUM_HDUS);
+    for (int k = 0 ; k < NUM_HDUS ; k++) {
+        cell->hdu->images->data[k]  = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        cell->hdu->masks->data[k]   = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        cell->hdu->weights->data[k] = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(cell->hdu->images->data[k], (float) (BASE_IMAGE+k));
+        psImageInit(cell->hdu->masks->data[k], (psU8) (BASE_MASK+k));
+        psImageInit(cell->hdu->weights->data[k], (float) (BASE_WEIGHT+k));
+    }
+
+    //XXX: Should the region be set some other way?  Like through the various config files?
+    psRegion *region = psRegionAlloc(0.0, 0.0, 0.0, 0.0);
+    // You shouldn't have to remove the key from the metadata.  Find out how to simply change the key value.
+    psMetadataRemoveKey(cell->concepts, "CELL.TRIMSEC");
+    psMetadataAddPtr(cell->concepts, PS_LIST_TAIL|PS_META_REPLACE, "CELL.TRIMSEC", PS_DATA_REGION, "I am a region", region);
+    psFree(region);
+    return(cell);
+}
+
+/******************************************************************************
+generateSimpleChip(): This function generates a pmChip data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmChip *generateSimpleChip(pmFPA *fpa)
+{
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    chip->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    chip->fromFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    psMetadataAddS32(chip->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(chip->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(chip->cells, NUM_CELLS);
+    for (int i = 0 ; i < NUM_CELLS ; i++) {
+        chip->cells->data[i] = psMemDecrRefCounter((psPtr) generateSimpleCell(chip));
+    }
+
+    // XXX: Add code to initialize chip pmConcepts
+
+
+    return(chip);
+}
+
+/******************************************************************************
+generateSimpleFPA(): This function generates a pmFPA data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmFPA* generateSimpleFPA(psMetadata *camera)
+{
+    pmFPA* fpa = pmFPAAlloc(camera);
+    fpa->fromTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toSky = psProjectionAlloc(0.0,0.0,10.0,10.0,PS_PROJ_TAN);
+    psMetadataAddS32(fpa->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    if (camera != NULL) {
+        psMetadataAddS32((psMetadata *) fpa->camera, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    }
+    psMetadataAddS32(fpa->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+
+    psArrayRealloc(fpa->chips, NUM_CHIPS);
+    for (int i = 0 ; i < NUM_CHIPS ; i++) {
+        fpa->chips->data[i] = psMemDecrRefCounter((psPtr) generateSimpleChip(fpa));
+    }
+
+    pmConceptsBlankFPA(fpa);
+    return(fpa);
+}
+
+
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(25);
+
+
+    // ----------------------------------------------------------------------
+    // pmReadoutExtent() tests: NULL input
+    {
+        psMemId id = psMemGetId();
+        ok(NULL == pmReadoutExtent(NULL), "pmReadoutExtent(NULL) returned NULL");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // pmReadoutExtent() tests: acceptable inputs
+    // XXX: We should probably test when the images are NULL, and use different size
+    // images in each readout.
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        bool errorFlag = false;
+        for (int chipID = 0 ; chipID < fpa->chips->n ; chipID++) {
+            pmChip *chip = fpa->chips->data[chipID];
+            for (int cellID = 0 ; cellID < chip->cells->n ; cellID++) {
+                pmCell *cell = chip->cells->data[cellID];
+                for (int readoutID = 0 ; readoutID < cell->readouts->n ; readoutID++) {
+                    pmReadout *readout = cell->readouts->data[readoutID];
+                    psRegion *region = pmReadoutExtent(readout);
+                    int xWindow = psMetadataLookupS32(NULL, readout->parent->concepts, "CELL.XWINDOW");
+                    int yWindow = psMetadataLookupS32(NULL, readout->parent->concepts, "CELL.YWINDOW");
+                    if (!region || 
+                         region->x0 != xWindow ||
+                         region->x1 != xWindow + readout->image->numCols ||
+                         region->y0 != yWindow ||
+                         region->y1 != yWindow + readout->image->numRows) {
+                        diag("ERROR: pmReadoutExtent() did not set the psRegion correctly for chip/cell/readout (%d/%d/%d)\n", chipID, cellID, readoutID);
+                        errorFlag = true;
+		    }
+                    psFree(region);
+		}
+	    }
+	}
+        ok(!errorFlag, "pmReadoutExtent() passed all tests");
+
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmCellExtent() tests: NULL input
+    {
+        psMemId id = psMemGetId();
+        ok(NULL == pmCellExtent(NULL), "pmCellExtent(NULL) returned NULL");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmCellExtent() tests: acceptable inputs
+    // XX: We should probably set different region sizes to better test the min/max code
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        bool errorFlag = false;
+        for (int chipID = 0 ; chipID < fpa->chips->n ; chipID++) {
+            pmChip *chip = fpa->chips->data[chipID];
+            for (int cellID = 0 ; cellID < chip->cells->n ; cellID++) {
+                pmCell *cell = chip->cells->data[cellID];
+                // Determine the actual extent
+                psRegion *cellExtent = psRegionAlloc(INFINITY, 0, INFINITY, 0); // Extent of cell
+                for (int readoutID = 0 ; readoutID < cell->readouts->n ; readoutID++) {
+                    pmReadout *readout = cell->readouts->data[readoutID];
+                    psRegion *roExtent = pmReadoutExtent(readout); // Extent of readout
+                    cellExtent->x0 = PS_MIN(cellExtent->x0, roExtent->x0);
+                    cellExtent->x1 = PS_MAX(cellExtent->x1, roExtent->x1);
+                    cellExtent->y0 = PS_MIN(cellExtent->y0, roExtent->y0);
+                    cellExtent->y1 = PS_MAX(cellExtent->y1, roExtent->y1);
+                    psFree(roExtent);
+		}
+                bool mdok;
+                int x0 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.X0"); // Cell x offset
+                int y0 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.Y0"); // Cell y offset
+                cellExtent->x0 += x0;
+                cellExtent->x1 += x0;
+                cellExtent->y0 += y0;
+                cellExtent->y1 += y0;
+    
+                psRegion *tstExtent = pmCellExtent(cell);
+                if (!cell ||
+                     tstExtent->x0 != cellExtent->x0 ||
+                     tstExtent->x1 != cellExtent->x1 ||
+                     tstExtent->y0 != cellExtent->y0 ||
+                     tstExtent->y1 != cellExtent->y1) {
+                     diag("ERROR: psRegion set incorrectly for chip/cell (%d/%d)", chipID, cellID);
+                     errorFlag = true;
+		}
+                psFree(tstExtent);
+                psFree(cellExtent);
+	    }
+	}
+        ok(!errorFlag, "pmCellExtent() passed all tests");
+
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+    // ----------------------------------------------------------------------
+    // pmChipExtent() tests: NULL input
+    {
+        psMemId id = psMemGetId();
+        ok(NULL == pmChipExtent(NULL), "pmChipExtent(NULL) returned NULL");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmChipExtent() tests: acceptable inputs
+    // XX: We should probably set different region sizes to better test the min/max code
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        bool errorFlag = false;
+        for (int chipID = 0 ; chipID < fpa->chips->n ; chipID++) {
+            pmChip *chip = fpa->chips->data[chipID];
+            psRegion *chipExtent = pmChipPixels(chip);
+            bool rc;
+            int x0 = psMetadataLookupS32(&rc, chip->concepts, "CHIP.X0"); // Chip x offset
+            int y0 = psMetadataLookupS32(&rc, chip->concepts, "CHIP.Y0"); // Chip y offset
+            chipExtent->x0 += x0;
+            chipExtent->x1 += x0;
+            chipExtent->y0 += y0;
+            chipExtent->y1 += y0;
+            psRegion *tstExtent = pmChipExtent(chip);
+            if (!chip ||
+                tstExtent->x0 != chipExtent->x0 ||
+                tstExtent->x1 != chipExtent->x1 ||
+                tstExtent->y0 != chipExtent->y0 ||
+                tstExtent->y1 != chipExtent->y1) {
+                diag("ERROR: psRegion set incorrectly for chip (%d)", chipID);
+                errorFlag = true;
+	    }
+            psFree(tstExtent);
+            psFree(chipExtent);
+	}
+        ok(!errorFlag, "pmChipExtent() passed all tests");
+
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmChipPixels() tests: NULL input
+    {
+        psMemId id = psMemGetId();
+        ok(NULL == pmChipPixels(NULL), "pmChipPixels(NULL) returned NULL");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmChipPixels() tests: acceptable inputs
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+
+        bool errorFlag = false;
+        for (int chipID = 0 ; chipID < fpa->chips->n ; chipID++) {
+            pmChip *chip = fpa->chips->data[chipID];
+            // Determine the actual pixels
+            psRegion *actualExtent = psRegionAlloc(INFINITY, 0, INFINITY, 0);
+            for (int cellID = 0 ; cellID < chip->cells->n ; cellID++) {
+                pmCell *cell = chip->cells->data[cellID];
+                psRegion *cellExtent = pmCellExtent(cell);
+                actualExtent->x0 = PS_MIN(actualExtent->x0, cellExtent->x0);
+                actualExtent->x1 = PS_MAX(actualExtent->x1, cellExtent->x1);
+                actualExtent->y0 = PS_MIN(actualExtent->y0, cellExtent->y0);
+                actualExtent->y1 = PS_MAX(actualExtent->y1, cellExtent->y1);
+                psFree(cellExtent);
+	    }
+
+            // Now test if pmChipPixels() determines the same pixels
+            psRegion *tstExtent = pmChipPixels(chip);
+            if (!tstExtent ||
+                 tstExtent->x0 != actualExtent->x0 ||
+                 tstExtent->x1 != actualExtent->x1 ||
+                 tstExtent->y0 != actualExtent->y0 ||
+                 tstExtent->y1 != actualExtent->y1) {
+                diag("ERROR: pixels set incorrectly for chip %d", chipID);
+                errorFlag = true;
+	    }
+
+            // Free temp memory
+            psFree(tstExtent);
+            psFree(actualExtent);
+	}
+        ok(!errorFlag, "pmChipPixels() passed all tests");
+
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmFPAPixels() tests: NULL input
+    {
+        psMemId id = psMemGetId();
+        ok(NULL == pmFPAPixels(NULL), "pmFPAPixels(NULL) returned NULL");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmFPAPixels() tests: acceptable inputs
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+
+        psRegion *actualExtent = psRegionAlloc(INFINITY, 0, INFINITY, 0); // Extent of fpa
+        for (int chipID = 0 ; chipID < fpa->chips->n ; chipID++) {
+            pmChip *chip = fpa->chips->data[chipID];
+            psRegion *chipExtent = pmChipExtent(chip); // Extent of chip
+            actualExtent->x0 = PS_MIN(actualExtent->x0, chipExtent->x0);
+            actualExtent->x1 = PS_MAX(actualExtent->x1, chipExtent->x1);
+            actualExtent->y0 = PS_MIN(actualExtent->y0, chipExtent->y0);
+            actualExtent->y1 = PS_MAX(actualExtent->y1, chipExtent->y1);
+            psFree(chipExtent);
+        }
+
+        bool errorFlag = false;
+        psRegion *tstExtent = pmFPAPixels(fpa);
+        if (!tstExtent ||
+             tstExtent->x0 != actualExtent->x0 ||
+             tstExtent->x1 != actualExtent->x1 ||
+             tstExtent->y0 != actualExtent->y0 ||
+             tstExtent->y1 != actualExtent->y1) {
+            diag("ERROR: pmFPAPixels() set the pixels incorrectly");
+            errorFlag = true;
+	}
+        ok(!errorFlag, "pmFPAPixels() set the pixels psRegion correctly");
+
+        psFree(tstExtent);
+        psFree(actualExtent);
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPAFlags.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPAFlags.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPAFlags.c	(revision 22157)
@@ -0,0 +1,1021 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS
+    TESTED:
+        pmFPASetFileStatus()
+        pmChipSetFileStatus()
+        pmCellSetFileStatus()
+
+        pmFPACheckFileStatus()
+        pmChipCheckFileStatus()
+        pmCellCheckFileStatus()
+
+        pmFPASetDataStatus()
+        pmChipSetDataStatus()
+        pmCellSetDataStatus()
+
+        pmFPACheckDataStatus()
+        pmChipCheckDataStatus()
+        pmCellCheckDataStatus()
+        pmReadoutCheckDataStatus()
+    MUST TEST:
+        pmFPAviewCheckDataStatus()
+        pmFPASelectChip()
+        pmChipSelectCell()
+        pmFPAExcludeChip()
+        pmChipExcludeCell()
+*/
+
+// XXX: For the genSimpleFPA() code, add IDs to each function so that
+// the values set in each chip-?cell-?hdu-?image are unique
+// XXX: For the genSimpleFPA() code, write masks and weights as well
+// XXX: Add in the associated CheckStatus tests
+
+#define CHIP_ALLOC_NAME        "ChipName"
+#define CELL_ALLOC_NAME        "CellName"
+#define MISC_NUM                32
+#define MISC_NAME              "META00"
+#define MISC_NAME2             "META01"
+#define NUM_BIAS_DATA           10
+#define TEST_NUM_ROWS           4
+#define TEST_NUM_COLS           4
+#define NUM_READOUTS            3
+#define NUM_CELLS               10
+#define NUM_CHIPS               8
+#define NUM_HDUS                5
+#define BASE_IMAGE              10
+#define BASE_MASK               40
+#define BASE_WEIGHT             70
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+
+psPlaneTransform *PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM()
+{
+    psPlaneTransform *pt = psPlaneTransformAlloc(1, 1);
+    pt->x->coeff[1][0] = 1.0;
+    pt->y->coeff[0][1] = 1.0;
+    return(pt);
+}
+
+psPlaneDistort *PS_CREATE_4D_IDENTITY_PLANE_DISTORT()
+{
+    psPlaneDistort *pd = psPlaneDistortAlloc(1, 1, 1, 1);
+    pd->x->coeff[1][0][0][0] = 1.0;
+    pd->y->coeff[0][1][0][0] = 1.0;
+    return(pd);
+}
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(readout);
+}
+
+/******************************************************************************
+generateSimpleCell(): This function generates a pmCell data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmCell *generateSimpleCell(pmChip *chip)
+{
+    pmCell *cell = pmCellAlloc(chip, CELL_ALLOC_NAME);
+
+    psMetadataAddS32(cell->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(cell->readouts, NUM_READOUTS);
+    cell->hdu = pmHDUAlloc("cellExtName");
+    for (int i = 0 ; i < NUM_READOUTS ; i++) {
+        cell->readouts->data[i] = psMemDecrRefCounter((psPtr) generateSimpleReadout(cell));
+    }
+
+    bool rc = pmConfigFileRead(&cell->hdu->format, "dataFiles/camera0/format0.config", "Camera format 0");
+    if (!rc) {
+        rc = pmConfigFileRead(&cell->hdu->format, "../dataFiles/camera0/format0.config", "Camera format 0");
+        if (!rc) {
+            diag("pmConfigFileRead() was unsuccessful (from generateSimpleCell())");
+        }
+    }
+
+    cell->hdu->images = psArrayAlloc(NUM_HDUS);
+    cell->hdu->masks = psArrayAlloc(NUM_HDUS);
+    cell->hdu->weights = psArrayAlloc(NUM_HDUS);
+    for (int k = 0 ; k < NUM_HDUS ; k++) {
+        cell->hdu->images->data[k]  = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        cell->hdu->masks->data[k]   = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        cell->hdu->weights->data[k] = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(cell->hdu->images->data[k], (float) (BASE_IMAGE+k));
+        psImageInit(cell->hdu->masks->data[k], (psU8) (BASE_MASK+k));
+        psImageInit(cell->hdu->weights->data[k], (float) (BASE_WEIGHT+k));
+    }
+
+    //XXX: Should the region be set some other way?  Like through the various config files?
+    psRegion *region = psRegionAlloc(0.0, 0.0, 0.0, 0.0);
+    // You shouldn't have to remove the key from the metadata.  Find out how to simply change the key value.
+    psMetadataRemoveKey(cell->concepts, "CELL.TRIMSEC");
+    psMetadataAddPtr(cell->concepts, PS_LIST_TAIL|PS_META_REPLACE, "CELL.TRIMSEC", PS_DATA_REGION, "I am a region", region);
+    psFree(region);
+    return(cell);
+}
+
+/******************************************************************************
+generateSimpleChip(): This function generates a pmChip data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmChip *generateSimpleChip(pmFPA *fpa)
+{
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    chip->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    chip->fromFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    psMetadataAddS32(chip->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(chip->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(chip->cells, NUM_CELLS);
+    for (int i = 0 ; i < NUM_CELLS ; i++) {
+        chip->cells->data[i] = psMemDecrRefCounter((psPtr) generateSimpleCell(chip));
+    }
+
+    // XXX: Add code to initialize chip pmConcepts
+
+
+    return(chip);
+}
+
+/******************************************************************************
+generateSimpleFPA(): This function generates a pmFPA data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmFPA* generateSimpleFPA(psMetadata *camera)
+{
+    pmFPA* fpa = pmFPAAlloc(camera);
+    fpa->fromTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toSky = psProjectionAlloc(0.0,0.0,10.0,10.0,PS_PROJ_TAN);
+    psMetadataAddS32(fpa->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    if (camera != NULL) {
+        psMetadataAddS32((psMetadata *) fpa->camera, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    }
+    psMetadataAddS32(fpa->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(fpa->chips, NUM_CHIPS);
+    for (int i = 0 ; i < NUM_CHIPS ; i++) {
+        fpa->chips->data[i] = psMemDecrRefCounter((psPtr) generateSimpleChip(fpa));
+    }
+
+    // XXX: Eventually, when you finish the pmConcepts tests, add full concept
+    // reading code from wherever.
+    pmConceptsBlankFPA(fpa);
+    return(fpa);
+}
+
+void SetCellFileExists(pmCell *cell) {
+    cell->file_exists = true;
+    for (int i = 0 ; i < cell->readouts->n ; i++) {
+        pmReadout *readout = cell->readouts->data[i];
+        readout->file_exists = true;
+    }
+}
+
+void SetChipFileExists(pmChip *chip) {
+    chip->file_exists = true;
+    for (int i = 0 ; i < chip->cells->n ; i++) {
+        pmCell *cell = chip->cells->data[i];
+        cell->file_exists = true;
+        SetCellFileExists(cell);
+    }
+}
+
+void SetFPAFileExists(pmFPA *fpa) {
+    for (int i = 0 ; i < fpa->chips->n ; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        chip->file_exists = true;
+        SetChipFileExists(chip);
+    }
+}
+
+void SetReadoutDataExists(pmReadout *readout) {
+    readout->data_exists = true;
+}
+
+void SetCellDataExists(pmCell *cell) {
+    cell->data_exists = true;
+    for (int i = 0 ; i < cell->readouts->n ; i++) {
+        pmReadout *readout = cell->readouts->data[i];
+        readout->data_exists = true;
+    }
+}
+
+void SetChipDataExists(pmChip *chip) {
+    chip->data_exists = true;
+    for (int i = 0 ; i < chip->cells->n ; i++) {
+        pmCell *cell = chip->cells->data[i];
+        cell->data_exists = true;
+        SetCellDataExists(cell);
+    }
+}
+
+void SetFPADataExists(pmFPA *fpa) {
+    for (int i = 0 ; i < fpa->chips->n ; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        chip->data_exists = true;
+        SetChipDataExists(chip);
+    }
+}
+
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(107);
+
+
+    // ----------------------------------------------------------------------
+    // pmFPASetFileStatus() tests: verify with NULL pmFPA param
+    // bool pmFPASetFileStatus(pmFPA *fpa, bool status)
+    {
+        psMemId id = psMemGetId();
+        bool rc = pmFPASetFileStatus(NULL, false);
+        ok(!rc, "pmFPASetFileStatus() returned FALSE with NULL pmFPA param");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmFPASetFileStatus() tests: verify with acceptable data
+    // bool pmFPASetFileStatus(pmFPA *fpa, bool status)
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+
+        // First, set all flags to FALSE
+        bool correctStatus = false;
+        bool rc = pmFPASetFileStatus(fpa, correctStatus);
+        ok(rc, "pmFPASetFileStatus() returned successfully with acceptable input params");
+        bool errorFlag = false;
+        for (int k = 0 ; k < fpa->chips->n ; k++) {
+            pmChip *chip = fpa->chips->data[k];
+            for (int j = 0 ; j < chip->cells->n ; j++) {
+                pmCell *cell = chip->cells->data[j];
+                if (cell->file_exists != correctStatus) {
+                    diag("TEST ERROR: pmFPASetFileStatus() failed to set file status for chip %d cell %d\n", k, j);
+                    errorFlag = true;
+                }
+    
+                for (int i = 0; i < cell->readouts->n; i++) {
+                    pmReadout *readout = cell->readouts->data[i];
+                    if (readout->file_exists != correctStatus) {
+                        diag("TEST ERROR: pmFPASetFileStatus() failed to set file status for chip %d cell %d readout %d\n", k, j, i);
+                        errorFlag = true;
+                    }
+                }
+            }
+        }
+        ok(!errorFlag, "pmFPASetFileStatus() set file status in all cells to FALSE");
+
+        // Second, set all flags to TRUE
+        correctStatus = true;
+        rc = pmFPASetFileStatus(fpa, correctStatus);
+        ok(rc, "pmFPASetFileStatus() returned successfully with acceptable input params");
+        errorFlag = false;
+        for (int k = 0 ; k < fpa->chips->n ; k++) {
+            pmChip *chip = fpa->chips->data[k];
+            for (int j = 0 ; j < chip->cells->n ; j++) {
+                pmCell *cell = chip->cells->data[j];
+                if (cell->file_exists != correctStatus) {
+                    diag("TEST ERROR: pmFPASetFileStatus() failed to set file status for chip %d cell %d\n", k, j);
+                    errorFlag = true;
+                }
+    
+                for (int i = 0; i < cell->readouts->n; i++) {
+                    pmReadout *readout = cell->readouts->data[i];
+                    if (readout->file_exists != correctStatus) {
+                        diag("TEST ERROR: pmFPASetFileStatus() failed to set file status for chip %d cell %d readout %d\n", k, j, i);
+                        errorFlag = true;
+                    }
+                }
+            }
+        }
+        ok(!errorFlag, "pmFPASetFileStatus() set file status in all cells to TRUE");
+
+        // Third, set all flags to FALSE
+        correctStatus = false;
+        rc = pmFPASetFileStatus(fpa, correctStatus);
+        ok(rc, "pmFPASetFileStatus() returned successfully with acceptable input params");
+        errorFlag = false;
+        for (int k = 0 ; k < fpa->chips->n ; k++) {
+            pmChip *chip = fpa->chips->data[k];
+            for (int j = 0 ; j < chip->cells->n ; j++) {
+                pmCell *cell = chip->cells->data[j];
+                if (cell->file_exists != correctStatus) {
+                    diag("TEST ERROR: pmFPASetFileStatus() failed to set file status for chip %d cell %d\n", k, j);
+                    errorFlag = true;
+                }
+    
+                for (int i = 0; i < cell->readouts->n; i++) {
+                    pmReadout *readout = cell->readouts->data[i];
+                    if (readout->file_exists != correctStatus) {
+                        diag("TEST ERROR: pmFPASetFileStatus() failed to set file status for chip %d cell %d readout %d\n", k, j, i);
+                        errorFlag = true;
+                    }
+                }
+            }
+        }
+        ok(!errorFlag, "pmFPASetFileStatus() set file status in all cells to FALSE");
+
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmChipSetFileStatus() tests: verify with NULL pmChip param
+    // bool pmChipSetFileStatus(pmChip *chip, bool status)
+    {
+        psMemId id = psMemGetId();
+        bool rc = pmChipSetFileStatus(NULL, false);
+        ok(!rc, "pmChipSetFileStatus() returned FALSE with NULL pmChip param");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmChipSetFileStatus() tests: verify with acceptable data
+    // bool pmChipSetFileStatus(pmChip *chip, bool status)
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+
+        // First, set all flags to FALSE
+        bool correctStatus = false;
+        bool rc = pmChipSetFileStatus(chip, correctStatus);
+        ok(rc, "pmChipSetFileStatus() returned successfully with acceptable input params");
+        bool errorFlag = false;
+        if (chip->file_exists != correctStatus) {
+            diag("TEST ERROR: pmChipSetFileStatus() failed to set file status for chip param");
+            errorFlag = true;
+        }
+        for (int j = 0 ; j < chip->cells->n ; j++) {
+            pmCell *cell = chip->cells->data[j];
+            if (cell->file_exists != correctStatus) {
+                diag("TEST ERROR: pmChipSetFileStatus() failed to set file status for cell %d\n", j);
+                errorFlag = true;
+            }
+
+            for (int i = 0; i < cell->readouts->n; i++) {
+                pmReadout *readout = cell->readouts->data[i];
+                if (readout->file_exists != correctStatus) {
+                    diag("TEST ERROR: pmChipSetFileStatus() failed to set file status for cell %d readout %d\n", j, i);
+                    errorFlag = true;
+                }
+            }
+        }
+        ok(!errorFlag, "pmChipSetFileStatus() set file status in all cells to FALSE");
+
+        // Second, set all flags to TRUE
+        correctStatus = true;
+        rc = pmChipSetFileStatus(chip, correctStatus);
+        ok(rc, "pmChipSetFileStatus() returned successfully with acceptable input params");
+        errorFlag = false;
+        if (chip->file_exists != correctStatus) {
+            diag("TEST ERROR: pmChipSetFileStatus() failed to set file status for chip param");
+            errorFlag = true;
+        }
+        for (int j = 0 ; j < chip->cells->n ; j++) {
+            pmCell *cell = chip->cells->data[j];
+            if (cell->file_exists != correctStatus) {
+                diag("TEST ERROR: pmChipSetFileStatus() failed to set file status for cell %d\n", j);
+                errorFlag = true;
+            }
+
+            for (int i = 0; i < cell->readouts->n; i++) {
+                pmReadout *readout = cell->readouts->data[i];
+                if (readout->file_exists != correctStatus) {
+                    diag("TEST ERROR: pmChipSetFileStatus() failed to set file status for cell %d readout %d\n", j, i);
+                    errorFlag = true;
+                }
+            }
+        }
+        ok(!errorFlag, "pmChipSetFileStatus() set file status in all cells to TRUE");
+
+        // Third, set all flags to FALSE
+        correctStatus = false;
+        rc = pmChipSetFileStatus(chip, correctStatus);
+        ok(rc, "pmChipSetFileStatus() returned successfully with acceptable input params");
+        errorFlag = false;
+        if (chip->file_exists != correctStatus) {
+            diag("TEST ERROR: pmChipSetFileStatus() failed to set file status for chip param");
+            errorFlag = true;
+        }
+        for (int j = 0 ; j < chip->cells->n ; j++) {
+            pmCell *cell = chip->cells->data[j];
+            if (cell->file_exists != correctStatus) {
+                diag("TEST ERROR: pmChipSetFileStatus() failed to set file status for cell %d\n", j);
+                errorFlag = true;
+            }
+
+            for (int i = 0; i < cell->readouts->n; i++) {
+                pmReadout *readout = cell->readouts->data[i];
+                if (readout->file_exists != correctStatus) {
+                    diag("TEST ERROR: pmChipSetFileStatus() failed to set file status for cell %d readout %d\n", j, i);
+                    errorFlag = true;
+                }
+            }
+        }
+        ok(!errorFlag, "pmChipSetFileStatus() set file status in all cells to FALSE");
+
+
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmCellSetFileStatus() tests: verify with NULL pmCell param
+    {
+        psMemId id = psMemGetId();
+        bool rc = pmCellSetFileStatus(NULL, false);
+        ok(!rc, "pmCellSetFileStatus() returned FALSE with NULL pmCell param");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmCellSetFileStatus() tests: verify with acceptable data
+    // bool pmCellSetFileStatus(pmCell *cell, bool status)
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+
+        // First, set all flags to FALSE
+        bool correctStatus = false;
+        bool rc = pmCellSetFileStatus(cell, correctStatus);
+        ok(rc, "pmCellSetFileStatus() returned successfully with acceptable input params");
+        bool errorFlag = false;
+        if (cell->file_exists != correctStatus) {
+            diag("TEST ERROR: pmCellSetFileStatus() failed to set file status for cell param\n");
+            errorFlag = true;
+        }
+        for (int i = 0; i < cell->readouts->n; i++) {
+
+            pmReadout *readout = cell->readouts->data[i];
+            if (readout->file_exists != correctStatus) {
+                diag("TEST ERROR: pmCellSetFileStatus() failed to set file status for cell %d\n", i);
+                errorFlag = true;
+            }
+        }
+        ok(!errorFlag, "pmCellSetFileStatus() set file status in all cells to FALSE");
+
+        // Second, set all flags to TRUE
+        correctStatus = true;
+        rc = pmCellSetFileStatus(cell, correctStatus);
+        ok(rc, "pmCellSetFileStatus() returned successfully with acceptable input params");
+        errorFlag = false;
+        if (cell->file_exists != correctStatus) {
+            diag("TEST ERROR: pmCellSetFileStatus() failed to set file status for cell param\n");
+            errorFlag = true;
+        }
+        for (int i = 0; i < cell->readouts->n; i++) {
+            pmReadout *readout = cell->readouts->data[i];
+            if (readout->file_exists != correctStatus) {
+                diag("TEST ERROR: pmCellSetFileStatus() failed to set file status for cell %d\n", i);
+                errorFlag = true;
+            }
+        }
+        ok(!errorFlag, "pmCellSetFileStatus() set file status in all cells to TRUE");
+
+        // Third, set all flags to FALSE
+        correctStatus = false;
+        rc = pmCellSetFileStatus(cell, correctStatus);
+        ok(rc, "pmCellSetFileStatus() returned successfully with acceptable input params");
+        errorFlag = false;
+        if (cell->file_exists != correctStatus) {
+            diag("TEST ERROR: pmCellSetFileStatus() failed to set file status for cell param\n");
+            errorFlag = true;
+        }
+        for (int i = 0; i < cell->readouts->n; i++) {
+            pmReadout *readout = cell->readouts->data[i];
+            if (readout->file_exists != correctStatus) {
+                diag("TEST ERROR: pmCellSetFileStatus() failed to set file status for readout %d\n", i);
+                errorFlag = true;
+            }
+        }
+        ok(!errorFlag, "pmCellSetFileStatus() set file status in all cells to FALSE");
+
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmFPACheckFileStatus() tests
+    // bool pmFPACheckFileStatus(const pmFPA *fpa)
+    // Call with NULL pmFPA input parameter
+    {
+        psMemId id = psMemGetId();
+        bool rc = pmFPACheckFileStatus(NULL);
+        ok(rc == false, "pmFPACheckFileStatus() returned FALSE with NULL pmFPA input parameter");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        pmFPA *fpa = generateSimpleFPA(NULL);
+        bool rc = pmFPACheckFileStatus(fpa);
+        ok(rc == false, "pmFPACheckFileStatus() returned FALSE with NULL pmFPA input parameter");
+        SetFPAFileExists(fpa);
+        rc = pmFPACheckFileStatus(fpa);
+        ok(rc == true, "pmFPACheckFileStatus() returned TRUE with NULL pmFPA input parameter");
+        psFree(fpa);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmChipCheckFileStatus() tests
+    // Call with NULL pmChip input parameter
+    {
+        psMemId id = psMemGetId();
+        bool rc = pmChipCheckFileStatus(NULL);
+        ok(rc == false, "pmChipCheckFileStatus() returned FALSE with NULL pmChip input parameter");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call with acceptable input parameter
+    {
+        psMemId id = psMemGetId();
+        pmChip *chip = generateSimpleChip(NULL);
+        bool rc = pmChipCheckFileStatus(chip);
+        ok(rc == false, "pmChipCheckFileStatus() returned FALSE with NULL pmChip input parameter");
+        SetChipFileExists(chip);
+        rc = pmChipCheckFileStatus(chip);
+        ok(rc == true, "pmChipCheckFileStatus() returned TRUE with NULL pmChip input parameter");
+        psFree(chip);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmCellCheckFileStatus() tests
+    // bool pmCellCheckFileStatus(const pmCell *cell)
+    // Call with NULL pmCell input parameter
+    {
+        psMemId id = psMemGetId();
+        bool rc = pmCellCheckFileStatus(NULL);
+        ok(rc == false, "pmCellCheckFileStatus() returned FALSE with NULL pmCell input parameter");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        bool rc = pmCellCheckFileStatus(cell);
+        ok(rc == false, "pmCellCheckFileStatus() returned FALSE with acceptable input parameters");
+        SetCellFileExists(cell);
+        rc = pmCellCheckFileStatus(cell);
+        ok(rc == true, "pmCellCheckFileStatus() returned TRUE with acceptable input parameters");
+        psFree(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmFPASetDataStatus() tests: verify with NULL pmFPA param
+    {
+        psMemId id = psMemGetId();
+        bool rc = pmFPASetDataStatus(NULL, false);
+        ok(!rc, "pmFPASetDataStatus() returned FALSE with NULL pmFPA param");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmFPASetDataStatus() tests: verify with acceptable data
+    // bool pmFPASetDataStatus(pmFPA *fpa, bool status)
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+
+        // First, set all flags to FALSE
+        bool correctStatus = false;
+        bool rc = pmFPASetDataStatus(fpa, correctStatus);
+        ok(rc, "pmFPASetDataStatus() returned successfully with acceptable input params");
+        bool errorFlag = false;
+        for (int k = 0 ; k < fpa->chips->n ; k++) {
+            pmChip *chip = fpa->chips->data[k];
+            for (int j = 0 ; j < chip->cells->n ; j++) {
+                pmCell *cell = chip->cells->data[j];
+                if (cell->data_exists != correctStatus) {
+                    diag("TEST ERROR: pmFPASetDataStatus() failed to set file status for chip %d cell %d\n", k, j);
+                    errorFlag = true;
+                }
+    
+                for (int i = 0; i < cell->readouts->n; i++) {
+                    pmReadout *readout = cell->readouts->data[i];
+                    if (readout->data_exists != correctStatus) {
+                        diag("TEST ERROR: pmFPASetDataStatus() failed to set file status for chip %d cell %d readout %d\n", k, j, i);
+                        errorFlag = true;
+                    }
+                }
+            }
+        }
+        ok(!errorFlag, "pmFPASetDataStatus() set file status in all cells to FALSE");
+
+        // Second, set all flags to TRUE
+        correctStatus = true;
+        rc = pmFPASetDataStatus(fpa, correctStatus);
+        ok(rc, "pmFPASetDataStatus() returned successfully with acceptable input params");
+        errorFlag = false;
+        for (int k = 0 ; k < fpa->chips->n ; k++) {
+            pmChip *chip = fpa->chips->data[k];
+            for (int j = 0 ; j < chip->cells->n ; j++) {
+                pmCell *cell = chip->cells->data[j];
+                if (cell->data_exists != correctStatus) {
+                    diag("TEST ERROR: pmFPASetDataStatus() failed to set file status for chip %d cell %d\n", k, j);
+                    errorFlag = true;
+                }
+    
+                for (int i = 0; i < cell->readouts->n; i++) {
+                    pmReadout *readout = cell->readouts->data[i];
+                    if (readout->data_exists != correctStatus) {
+                        diag("TEST ERROR: pmFPASetDataStatus() failed to set file status for chip %d cell %d readout %d\n", k, j, i);
+                        errorFlag = true;
+                    }
+                }
+            }
+        }
+        ok(!errorFlag, "pmFPASetDataStatus() set file status in all cells to TRUE");
+
+        // Third, set all flags to FALSE
+        correctStatus = false;
+        rc = pmFPASetDataStatus(fpa, correctStatus);
+        ok(rc, "pmFPASetDataStatus() returned successfully with acceptable input params");
+        errorFlag = false;
+        for (int k = 0 ; k < fpa->chips->n ; k++) {
+            pmChip *chip = fpa->chips->data[k];
+            for (int j = 0 ; j < chip->cells->n ; j++) {
+                pmCell *cell = chip->cells->data[j];
+                if (cell->data_exists != correctStatus) {
+                    diag("TEST ERROR: pmFPASetDataStatus() failed to set file status for chip %d cell %d\n", k, j);
+                    errorFlag = true;
+                }
+    
+                for (int i = 0; i < cell->readouts->n; i++) {
+                    pmReadout *readout = cell->readouts->data[i];
+                    if (readout->data_exists != correctStatus) {
+                        diag("TEST ERROR: pmFPASetDataStatus() failed to set file status for chip %d cell %d readout %d\n", k, j, i);
+                        errorFlag = true;
+                    }
+                }
+            }
+        }
+        ok(!errorFlag, "pmFPASetDataStatus() set file status in all cells to FALSE");
+
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmChipSetDataStatus() tests: verify with NULL pmChip param
+    // bool pmChipSetDataStatus(pmChip *chip, bool status)
+    {
+        psMemId id = psMemGetId();
+        bool rc = pmChipSetDataStatus(NULL, false);
+        ok(!rc, "pmChipSetDataStatus() returned FALSE with NULL pmChip param");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmChipSetDataStatus() tests: verify with acceptable data
+    // bool pmChipSetDataStatus(pmChip *chip, bool status)
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+
+        // First, set all flags to FALSE
+        bool correctStatus = false;
+        bool rc = pmChipSetDataStatus(chip, correctStatus);
+        ok(rc, "pmChipSetDataStatus() returned successfully with acceptable input params");
+        bool errorFlag = false;
+        if (chip->data_exists != correctStatus) {
+            diag("TEST ERROR (a): pmChipSetDataStatus() failed to set file status for chip param\n");
+            errorFlag = true;
+        }
+        for (int j = 0 ; j < chip->cells->n ; j++) {
+            pmCell *cell = chip->cells->data[j];
+            if (cell->data_exists != correctStatus) {
+                diag("TEST ERROR (b): pmChipSetDataStatus() failed to set file status for cell %d\n", j);
+                errorFlag = true;
+            }
+
+            for (int i = 0; i < cell->readouts->n; i++) {
+                pmReadout *readout = cell->readouts->data[i];
+                if (readout->data_exists != correctStatus) {
+                    diag("TEST ERROR (c): pmChipSetDataStatus() failed to set file status for cell %d readout %d\n", j, i);
+                    errorFlag = true;
+                }
+            }
+        }
+        ok(!errorFlag, "pmChipSetDataStatus() set data status in all cells to FALSE");
+
+        // Second, set all flags to TRUE
+        correctStatus = true;
+        rc = pmChipSetDataStatus(chip, correctStatus);
+        ok(rc, "pmChipSetDataStatus() returned successfully with acceptable input params");
+        errorFlag = false;
+        if (chip->data_exists != correctStatus) {
+            diag("TEST ERROR (a): pmChipSetDataStatus() failed to set file status for chip param\n");
+            errorFlag = true;
+        }
+        for (int j = 0 ; j < chip->cells->n ; j++) {
+            pmCell *cell = chip->cells->data[j];
+            if (cell->data_exists != correctStatus) {
+                diag("TEST ERROR (b): pmChipSetDataStatus() failed to set file status for cell %d\n", j);
+                errorFlag = true;
+            }
+
+            for (int i = 0; i < cell->readouts->n; i++) {
+                pmReadout *readout = cell->readouts->data[i];
+                if (readout->data_exists != correctStatus) {
+                    diag("TEST ERROR (c): pmChipSetDataStatus() failed to set file status for cell %d readout %d\n", j, i);
+                    errorFlag = true;
+                }
+            }
+        }
+        ok(!errorFlag, "pmChipSetDataStatus() set data status in all cells to TRUE");
+
+        // ThirdSecond, set all flags to FALSE
+        correctStatus = false;
+        rc = pmChipSetDataStatus(chip, correctStatus);
+        ok(rc, "pmChipSetDataStatus() returned successfully with acceptable input params");
+        errorFlag = false;
+        if (chip->data_exists != correctStatus) {
+            diag("TEST ERROR (a): pmChipSetDataStatus() failed to set file status for chip param\n");
+            errorFlag = true;
+        }
+        for (int j = 0 ; j < chip->cells->n ; j++) {
+            pmCell *cell = chip->cells->data[j];
+            if (cell->data_exists != correctStatus) {
+                diag("TEST ERROR (b): pmChipSetDataStatus() failed to set file status for cell %d\n", j);
+                errorFlag = true;
+            }
+
+            for (int i = 0; i < cell->readouts->n; i++) {
+                pmReadout *readout = cell->readouts->data[i];
+                if (readout->data_exists != correctStatus) {
+                    diag("TEST ERROR (c): pmChipSetDataStatus() failed to set file status for cell %d readout %d\n", j, i);
+                    errorFlag = true;
+                }
+            }
+        }
+        ok(!errorFlag, "pmChipSetDataStatus() set data status in all cells to FALSE");
+
+
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmCellSetDataStatus() tests: verify with NULL pmCell param
+    // bool pmCellSetDataStatus(pmCell *cell, bool status)
+    {
+        psMemId id = psMemGetId();
+        bool rc = pmCellSetDataStatus(NULL, false);
+        ok(!rc, "pmCellSetDataStatus() returned FALSE with NULL pmCell param");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmCellSetDataStatus() tests: verify with acceptable data
+    // bool pmCellSetDataStatus(pmCell *cell, bool status)
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+
+        // First, set all flags to FALSE
+        bool correctStatus = false;
+        bool rc = pmCellSetDataStatus(cell, correctStatus);
+        ok(rc, "pmCellSetDataStatus() returned successfully with acceptable input params");
+        bool errorFlag = false;
+        if (cell->data_exists != correctStatus) {
+            diag("TEST ERROR: pmCellSetDataStatus() failed to set file status for cell param\n");
+            errorFlag = true;
+        }
+        for (int i = 0; i < cell->readouts->n; i++) {
+            pmReadout *readout = cell->readouts->data[i];
+            if (readout->data_exists != correctStatus) {
+                diag("TEST ERROR: pmCellSetDataStatus() failed to set file status for cell %d\n", i);
+                errorFlag = true;
+            }
+        }
+        ok(!errorFlag, "pmCellSetDataStatus() set data status in all cells to FALSE");
+
+        // Second, set all flags to TRUE
+        correctStatus = true;
+        rc = pmCellSetDataStatus(cell, correctStatus);
+        ok(rc, "pmCellSetDataStatus() returned successfully with acceptable input params");
+        errorFlag = false;
+        if (cell->data_exists != correctStatus) {
+            diag("TEST ERROR: pmCellSetDataStatus() failed to set file status for cell param\n");
+            errorFlag = true;
+        }
+        for (int i = 0; i < cell->readouts->n; i++) {
+            pmReadout *readout = cell->readouts->data[i];
+            if (readout->data_exists != correctStatus) {
+                diag("TEST ERROR: pmCellSetDataStatus() failed to set file status for cell %d\n", i);
+                errorFlag = true;
+            }
+        }
+        ok(!errorFlag, "pmCellSetDataStatus() set data status in all cells to TRUE");
+
+        // Third, set all flags to FALSE
+        correctStatus = false;
+        rc = pmCellSetDataStatus(cell, correctStatus);
+        ok(rc, "pmCellSetDataStatus() returned successfully with acceptable input params");
+        errorFlag = false;
+        if (cell->data_exists != correctStatus) {
+            diag("TEST ERROR: pmCellSetDataStatus() failed to set file status for cell param\n");
+            errorFlag = true;
+        }
+        for (int i = 0; i < cell->readouts->n; i++) {
+            pmReadout *readout = cell->readouts->data[i];
+            if (readout->data_exists != correctStatus) {
+                diag("TEST ERROR: pmCellSetDataStatus() failed to set file status for readout %d\n", i);
+                errorFlag = true;
+            }
+        }
+        ok(!errorFlag, "pmCellSetDataStatus() set data status in all cells to FALSE");
+
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmFPACheckDataStatus() tests
+    // bool pmFPACheckDataStatus(const pmFPA *fpa)
+    // Call with NULL pmFPA input parameter
+    {
+        psMemId id = psMemGetId();
+        bool rc = pmFPACheckDataStatus(NULL);
+        ok(rc == false, "pmFPACheckDataStatus() returned FALSE with NULL pmFPA input parameter");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        pmFPA *fpa = generateSimpleFPA(NULL);
+        bool rc = pmFPACheckDataStatus(fpa);
+        ok(rc == false, "pmFPACheckDataStatus() returned FALSE with NULL pmFPA input parameter");
+        SetFPADataExists(fpa);
+        rc = pmFPACheckDataStatus(fpa);
+        ok(rc == true, "pmFPACheckDataStatus() returned TRUE with NULL pmFPA input parameter");
+        psFree(fpa);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmChipCheckDataStatus() tests
+    // Call with NULL pmChip input parameter
+    {
+        psMemId id = psMemGetId();
+        bool rc = pmChipCheckDataStatus(NULL);
+        ok(rc == false, "pmChipCheckDataStatus() returned FALSE with NULL pmChip input parameter");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call with acceptable input parameter
+    {
+        psMemId id = psMemGetId();
+        pmChip *chip = generateSimpleChip(NULL);
+        bool rc = pmChipCheckDataStatus(chip);
+        ok(rc == false, "pmChipCheckDataStatus() returned FALSE with NULL pmChip input parameter");
+        SetChipDataExists(chip);
+        rc = pmChipCheckDataStatus(chip);
+        ok(rc == true, "pmChipCheckDataStatus() returned TRUE with NULL pmChip input parameter");
+        psFree(chip);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmCellCheckDataStatus() tests
+    // bool pmCellCheckDataStatus(const pmCell *cell)
+    // Call with NULL pmCell input parameter
+    {
+        psMemId id = psMemGetId();
+        bool rc = pmCellCheckDataStatus(NULL);
+        ok(rc == false, "pmCellCheckDataStatus() returned FALSE with NULL pmCell input parameter");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        bool rc = pmCellCheckDataStatus(cell);
+        ok(rc == false, "pmCellCheckDataStatus() returned FALSE with acceptable input parameters");
+        SetCellDataExists(cell);
+        rc = pmCellCheckDataStatus(cell);
+        ok(rc == true, "pmCellCheckDataStatus() returned TRUE with acceptable input parameters");
+        psFree(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmReadoutCheckDataStatus() tests
+    // bool pmReadoutCheckDataStatus(const pmReadout *readout)
+    // Call with NULL pmReadout input parameter
+    {
+        psMemId id = psMemGetId();
+        bool rc = pmReadoutCheckDataStatus(NULL);
+        ok(rc == false, "pmReadoutCheckDataStatus() returned FALSE with NULL pmReadout input parameter");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        pmReadout *readout = generateSimpleReadout(NULL);
+        bool rc = pmReadoutCheckDataStatus(readout);
+        ok(rc == false, "pmReadoutCheckDataStatus() returned FALSE with acceptable input parameters");
+        SetReadoutDataExists(readout);
+        rc = pmReadoutCheckDataStatus(readout);
+        ok(rc == true, "pmReadoutCheckDataStatus() returned TRUE with acceptable input parameters");
+        psFree(readout);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPAHeader.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPAHeader.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPAHeader.c	(revision 22157)
@@ -0,0 +1,449 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS:
+    All functions are tested.
+*/
+
+#define CHIP_ALLOC_NAME        "ChipName"
+#define CELL_ALLOC_NAME        "CellName"
+#define MISC_NUM                32
+#define MISC_NAME              "META00"
+#define MISC_NAME2             "META01"
+#define NUM_BIAS_DATA           10
+#define TEST_NUM_ROWS           4
+#define TEST_NUM_COLS           4
+#define NUM_READOUTS            3
+#define NUM_CELLS               10
+#define NUM_CHIPS               8
+#define NUM_HDUS                5
+#define BASE_IMAGE              10
+#define BASE_MASK               40
+#define BASE_WEIGHT             70
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+char *fitsFilename = "tmp.fits";
+
+psPlaneTransform *PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM()
+{
+    psPlaneTransform *pt = psPlaneTransformAlloc(1, 1);
+    pt->x->coeff[1][0] = 1.0;
+    pt->y->coeff[0][1] = 1.0;
+    return(pt);
+}
+
+psPlaneDistort *PS_CREATE_4D_IDENTITY_PLANE_DISTORT()
+{
+    psPlaneDistort *pd = psPlaneDistortAlloc(1, 1, 1, 1);
+    pd->x->coeff[1][0][0][0] = 1.0;
+    pd->y->coeff[0][1][0][0] = 1.0;
+    return(pd);
+}
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(readout);
+}
+
+/******************************************************************************
+generateSimpleCell(): This function generates a pmCell data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmCell *generateSimpleCell(pmChip *chip, int cellID)
+{
+    pmCell *cell = pmCellAlloc(chip, CELL_ALLOC_NAME);
+
+    psMetadataAddS32(cell->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(cell->readouts, NUM_READOUTS);
+    char extname[80];
+    snprintf(extname,80, "ext-%d", cellID);
+    cell->hdu = pmHDUAlloc(extname);
+    for (int i = 0 ; i < NUM_READOUTS ; i++) {
+        cell->readouts->data[i] = psMemDecrRefCounter((psPtr) generateSimpleReadout(cell));
+    }
+
+    // First try to read data from ../dataFiles, then try dataFiles.
+    bool rc = pmConfigFileRead(&cell->hdu->format, "../dataFiles/camera0/format0.config", "Camera format 0");
+    if (!rc) {
+        rc = pmConfigFileRead(&cell->hdu->format, "dataFiles/camera0/format0.config", "Camera format 0");
+        if (!rc) {
+            diag("pmConfigFileRead() was unsuccessful (from generateSimpleCell())");
+	}
+    }
+
+    cell->hdu->images = psArrayAlloc(NUM_HDUS);
+    cell->hdu->masks = psArrayAlloc(NUM_HDUS);
+    cell->hdu->weights = psArrayAlloc(NUM_HDUS);
+    for (int k = 0 ; k < NUM_HDUS ; k++) {
+        cell->hdu->images->data[k]  = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        cell->hdu->masks->data[k]   = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        cell->hdu->weights->data[k] = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(cell->hdu->images->data[k], (float) (BASE_IMAGE+k));
+        psImageInit(cell->hdu->masks->data[k], (psU8) (BASE_MASK+k));
+        psImageInit(cell->hdu->weights->data[k], (float) (BASE_WEIGHT+k));
+    }
+
+    psRegion *region = psRegionAlloc(0.0, 0.0, 0.0, 0.0);
+    // You shouldn't have to remove the key from the metadata.
+    psMetadataRemoveKey(cell->concepts, "CELL.TRIMSEC");
+    psMetadataAddPtr(cell->concepts, PS_LIST_TAIL|PS_META_REPLACE, "CELL.TRIMSEC", PS_DATA_REGION, "I am a region", region);
+    psFree(region);
+    return(cell);
+}
+
+/******************************************************************************
+generateSimpleChip(): This function generates a pmChip data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmChip *generateSimpleChip(pmFPA *fpa, int chipID)
+{
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    chip->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    chip->fromFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    psMetadataAddS32(chip->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(chip->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+
+    if (0) {
+        char extname[80];
+        snprintf(extname,80, "ext-%d", chipID);
+        chip->hdu = pmHDUAlloc(extname);
+    }
+
+    psArrayRealloc(chip->cells, NUM_CELLS);
+    for (int i = 0 ; i < NUM_CELLS ; i++) {
+        chip->cells->data[i] = psMemDecrRefCounter((psPtr) generateSimpleCell(chip, i));
+    }
+    return(chip);
+}
+
+/******************************************************************************
+generateSimpleFPA(): This function generates a pmFPA data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmFPA* generateSimpleFPA(psMetadata *camera)
+{
+    pmFPA* fpa = pmFPAAlloc(camera);
+    fpa->fromTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toSky = psProjectionAlloc(0.0,0.0,10.0,10.0,PS_PROJ_TAN);
+    psMetadataAddS32(fpa->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    if (camera != NULL) {
+        psMetadataAddS32((psMetadata *) fpa->camera, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    }
+    psMetadataAddS32(fpa->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+
+    psArrayRealloc(fpa->chips, NUM_CHIPS);
+    for (int i = 0 ; i < NUM_CHIPS ; i++) {
+        fpa->chips->data[i] = psMemDecrRefCounter((psPtr) generateSimpleChip(fpa, i));
+    }
+    pmConceptsBlankFPA(fpa);
+    return(fpa);
+}
+
+
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(75);
+
+
+    // ----------------------------------------------------------------------
+    // pmCellReadHeader() tests: NULL input fits file
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA *fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        ok(!pmCellReadHeader(cell, NULL), "pmCellReadHeader(cell, NULL) returned FALSE");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmCellReadHeader() tests: NULL input pmCell
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(fitsFilename, "w");
+        ok(fitsFileW != NULL, "psFitsOpen() opened the FITS file");
+        ok(!pmCellReadHeader(NULL, fitsFileW), "pmCellReadHeader(NULL, fitsFile) returned FALSE");
+        psFree(fitsFileW);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmCellReadHeader() tests: acceptable data
+    {
+        psMemId id = psMemGetId();
+        // Create a FITS file for this test
+        psFits* fitsFileW = psFitsOpen(fitsFilename, "w");
+        ok(fitsFileW != NULL, "psFitsOpen() opened the FITS file");
+        char extname[80];
+        for (int lcv = 0; lcv < NUM_HDUS; lcv++) {
+            snprintf(extname, 80, "ext-%d", lcv);
+            pmHDU *hdu = pmHDUAlloc(extname);
+            hdu->header = psMetadataAlloc();
+            psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYINT", PS_DATA_S32,
+                         "psS32 Item", (psS32)lcv);
+            psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYFLT", PS_DATA_F32,
+                         "psF32 Item", (float)(1.0f/(float)(1+lcv)));
+            psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYDBL", PS_DATA_F64,
+                         "psF64 Item", (double)(1.0/(double)(1+lcv)));
+            psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYBOOL", PS_DATA_BOOL,
+                         "psBool Item", (lcv%2 == 0));
+            psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYSTR", PS_DATA_STRING,
+                         "String Item", extname);
+            bool rc = pmConfigFileRead(&hdu->format, "../dataFiles/camera0/format0.config", "Camera 0 Config Format");
+            if (!rc) {
+                rc = pmConfigFileRead(&hdu->format, "dataFiles/camera0/format0.config", "Camera 0 Config Format");
+	    }
+            ok(rc == true, "pmConfigFileRead() was successful");
+            rc = pmHDUWrite(hdu, fitsFileW);
+            ok(rc == true, "pmHDUWrite() successfully wrote the header");
+            psFree(hdu);
+        }
+        psFitsClose(fitsFileW);
+
+        // Now, open that FITS file, and create an pmFPA hierarchy
+        psFits* fitsFileR = psFitsOpen(fitsFilename, "r");
+        ok(fitsFileR != NULL, "psFitsOpen returned non-NULL on existing file");
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA *fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+
+        ok(pmCellReadHeader(cell, fitsFileR), "pmCellReadHeader() returned TRUE with acceptable data");
+
+        // XXX: It's not clear if we should test if the HDU and pmConcepts actually
+        // get rid, since pmCellReadHeader() simply calls functions that are tested
+        // elsewhere.  However, if we should test it, test it here.
+
+        psFree(fpa);
+        psFree(camera);
+        psFree(fitsFileR);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmChipReadHeader() tests: NULL input fits file
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA *fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        ok(!pmChipReadHeader(chip, NULL), "pmChipReadHeader(chip, NULL) returned FALSE");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmChipReadHeader() tests: NULL input pmCell
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(fitsFilename, "w");
+        ok(fitsFileW != NULL, "psFitsOpen() opened the FITS file");
+        ok(!pmChipReadHeader(NULL, fitsFileW), "pmChipReadHeader(NULL, fitsFile) returned FALSE");
+        psFree(fitsFileW);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmChipReadHeader() tests: acceptable data
+    {
+        psMemId id = psMemGetId();
+
+        // Create a FITS file for this test
+        psFits* fitsFileW = psFitsOpen(fitsFilename, "w");
+        ok(fitsFileW != NULL, "psFitsOpen() opened the FITS file");
+        char extname[80];
+        for (int lcv = 0; lcv < NUM_HDUS; lcv++) {
+            snprintf(extname, 80, "ext-%d", lcv);
+            pmHDU *hdu = pmHDUAlloc(extname);
+            hdu->header = psMetadataAlloc();
+            psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYINT", PS_DATA_S32,
+                         "psS32 Item", (psS32)lcv);
+            psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYFLT", PS_DATA_F32,
+                         "psF32 Item", (float)(1.0f/(float)(1+lcv)));
+            psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYDBL", PS_DATA_F64,
+                         "psF64 Item", (double)(1.0/(double)(1+lcv)));
+            psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYBOOL", PS_DATA_BOOL,
+                         "psBool Item", (lcv%2 == 0));
+            psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYSTR", PS_DATA_STRING,
+                         "String Item", extname);
+            bool rc = pmConfigFileRead(&hdu->format, "../dataFiles/camera0/format0.config", "Camera 0 Config Format");
+            if (!rc) {
+               rc = pmConfigFileRead(&hdu->format, "dataFiles/camera0/format0.config", "Camera 0 Config Format");
+	    }
+            ok(rc == true, "pmConfigFileRead() was successful");
+            rc = pmHDUWrite(hdu, fitsFileW);
+            ok(rc == true, "pmHDUWrite() successfully wrote the header");
+            psFree(hdu);
+        }
+        psFitsClose(fitsFileW);
+
+        // Now, open that FITS file, and create an pmFPA hierarchy
+        psFits* fitsFileR = psFitsOpen(fitsFilename, "r");
+        ok(fitsFileR != NULL, "psFitsOpen returned non-NULL on existing file");
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA *fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+
+        // Set the correct extension names for the chips (if we put this in the
+        // generateChip() code, then psMOdules aborts.
+        for (int chipID = 0 ; chipID < fpa->chips->n ; chipID++) {
+            pmChip *chip = fpa->chips->data[chipID];
+            char extname[80];
+            snprintf(extname,80, "ext-%d", chipID);
+            chip->hdu = pmHDUAlloc(extname);
+	}
+
+        ok(pmChipReadHeader(chip, fitsFileR), "pmChipReadHeader() returned TRUE with acceptable data");
+
+
+        // XXX: It's not clear if we should test if the HDU and pmConcepts actually
+        // get rid, since pmCellReadHeader() simply calls functions that are tested
+        // elsewhere.  However, if we should test it, test it here.
+
+        psFree(fpa);
+        psFree(camera);
+        psFree(fitsFileR);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmFPAReadHeader() tests: NULL input fits file
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA *fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        ok(!pmFPAReadHeader(fpa, NULL), "pmFPAReadHeader(fpa, NULL) returned FALSE");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmFPAReadHeader() tests: NULL input pmCell
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(fitsFilename, "w");
+        ok(fitsFileW != NULL, "psFitsOpen() opened the FITS file");
+        ok(!pmFPAReadHeader(NULL, fitsFileW), "pmFPAReadHeader(NULL, fitsFile) returned FALSE");
+        psFree(fitsFileW);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmFPAReadHeader() tests: acceptable data
+    {
+        psMemId id = psMemGetId();
+
+        // Create a FITS file for this test
+        psFits* fitsFileW = psFitsOpen(fitsFilename, "w");
+        ok(fitsFileW != NULL, "psFitsOpen() opened the FITS file");
+        char extname[80];
+        for (int lcv = 0; lcv < NUM_HDUS; lcv++) {
+            snprintf(extname, 80, "ext-%d", lcv);
+            pmHDU *hdu = pmHDUAlloc(extname);
+            hdu->header = psMetadataAlloc();
+            psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYINT", PS_DATA_S32,
+                         "psS32 Item", (psS32)lcv);
+            psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYFLT", PS_DATA_F32,
+                         "psF32 Item", (float)(1.0f/(float)(1+lcv)));
+            psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYDBL", PS_DATA_F64,
+                         "psF64 Item", (double)(1.0/(double)(1+lcv)));
+            psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYBOOL", PS_DATA_BOOL,
+                         "psBool Item", (lcv%2 == 0));
+            psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYSTR", PS_DATA_STRING,
+                         "String Item", extname);
+            bool rc = pmConfigFileRead(&hdu->format, "../dataFiles/camera0/format0.config", "Camera 0 Config Format");
+            if (!rc) {
+                rc = pmConfigFileRead(&hdu->format, "dataFiles/camera0/format0.config", "Camera 0 Config Format");
+	    }
+            ok(rc == true, "pmConfigFileRead() was successful");
+            rc = pmHDUWrite(hdu, fitsFileW);
+            ok(rc == true, "pmHDUWrite() successfully wrote the header");
+            psFree(hdu);
+        }
+        psFitsClose(fitsFileW);
+
+        // Now, open that FITS file, and create an pmFPA hierarchy
+        psFits* fitsFileR = psFitsOpen(fitsFilename, "r");
+        ok(fitsFileR != NULL, "psFitsOpen returned non-NULL on existing file");
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA *fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+
+        // Set the correct extension names for the chips (if we put this in the
+        // generateChip() code, then psMOdules aborts.
+        for (int chipID = 0 ; chipID < fpa->chips->n ; chipID++) {
+            pmChip *chip = fpa->chips->data[chipID];
+            char extname[80];
+            snprintf(extname,80, "ext-%d", chipID);
+            chip->hdu = pmHDUAlloc(extname);
+	}
+        snprintf(extname,80, "ext-%d", 0);
+        fpa->hdu = pmHDUAlloc(extname);
+
+        ok(pmFPAReadHeader(fpa, fitsFileR), "pmFPAReadHeader() returned TRUE with acceptable data");
+
+        // XXX: It's not clear if we should test if the HDU and pmConcepts actually
+        // get rid, since pmCellReadHeader() simply calls functions that are tested
+        // elsewhere.  However, if we should test it, test it here.
+
+        psFree(fpa);
+        psFree(camera);
+        psFree(fitsFileR);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPALevel.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPALevel.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPALevel.c	(revision 22157)
@@ -0,0 +1,76 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS:
+    All functions are tested.
+    XXX: Add tests for bad input parameters.
+*/
+
+#define	ERR_TRACE_LEVEL		0
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(14);
+
+    // ----------------------------------------------------------------------
+    // pmFPALevelToName(): tests
+    // const char *pmFPALevelToName(pmFPALevel level)
+    {
+        psMemId id = psMemGetId();
+        char *str = (char *) pmFPALevelToName(PM_FPA_LEVEL_NONE);
+        ok(!strcmp(str, "NONE"), "pmFPALevelToName(PM_FPA_LEVEL_NONE)");
+
+        str = (char *) pmFPALevelToName(PM_FPA_LEVEL_FPA);
+        ok(!strcmp(str, "FPA"), "pmFPALevelToName(PM_FPA_LEVEL_FPA)");
+
+        str = (char *) pmFPALevelToName(PM_FPA_LEVEL_CHIP);
+        ok(!strcmp(str, "CHIP"), "pmFPALevelToName(PM_FPA_LEVEL_CHIP)");
+
+        str = (char *) pmFPALevelToName(PM_FPA_LEVEL_CELL);
+        ok(!strcmp(str, "CELL"), "pmFPALevelToName(PM_FPA_LEVEL_CELL)");
+
+        str = (char *) pmFPALevelToName(PM_FPA_LEVEL_READOUT);
+        ok(!strcmp(str, "READOUT"), "pmFPALevelToName(PM_FPA_LEVEL_READOUT)");
+
+        // XXX: We avoid this because pmFPALevelToName() aborts
+        if (0) {
+            str = (char *) pmFPALevelToName(-1);
+            ok(str == NULL, "pmFPALevelToName(-1)");
+	}
+
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmFPALevelFromName(): tests
+    {
+        psMemId id = psMemGetId();
+        pmFPALevel lev = pmFPALevelFromName("NONE");
+        ok(lev == PM_FPA_LEVEL_NONE, "pmFPALevelToName(NONE)");
+
+        lev = pmFPALevelFromName("FPA");
+        ok(lev == PM_FPA_LEVEL_FPA, "pmFPALevelToName(FPA)");
+
+        lev = pmFPALevelFromName("CHIP");
+        ok(lev == PM_FPA_LEVEL_CHIP, "pmFPALevelToName(CHIP)");
+
+        lev = pmFPALevelFromName("CELL");
+        ok(lev == PM_FPA_LEVEL_CELL, "pmFPALevelToName(CELL)");
+
+        lev = pmFPALevelFromName("READOUT");
+        ok(lev == PM_FPA_LEVEL_READOUT, "pmFPALevelToName(READOUT)");
+
+        lev = pmFPALevelFromName("BOGUS");
+        ok(lev == PM_FPA_LEVEL_NONE, "pmFPALevelToName(BOGUS)");
+
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPAMaskW.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPAMaskW.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPAMaskW.c	(revision 22157)
@@ -0,0 +1,426 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+// XXX: Use better name for the temporary FITS file
+// XXX: For the genSimpleFPA() code, add IDs to each function so that
+// the values set in each chip-?cell-?hdu-?image are unique
+// XXX: For the genSimpleFPA() code, write masks and weights as well
+// XXX: Must add tests for pmReadoutGenerateWeight()
+// XXX: We don't test pmReadoutGenerateMaskWeight() and pmCellGenerateMaskWeight()
+// because they are simply calls to the above tested functions
+
+#define CHIP_ALLOC_NAME        "ChipName"
+#define CELL_ALLOC_NAME        "CellName"
+#define MISC_NUM                32
+#define MISC_NAME              "META00"
+#define MISC_NAME2             "META01"
+#define NUM_BIAS_DATA           10
+#define TEST_NUM_ROWS           4
+#define TEST_NUM_COLS           4
+#define NUM_READOUTS            3
+#define NUM_CELLS               10
+#define NUM_CHIPS               8
+#define NUM_HDUS                5
+#define BASE_IMAGE              10
+#define BASE_MASK               40
+#define BASE_WEIGHT             70
+#define SATURATION_LEVEL	10000.0
+#define BAD_LEVEL		100.0
+#define SATURATION_MASK		1
+#define BAD_MASK		2
+#define CELL_GAIN		1.0
+#define CELL_READNOISE		2.0
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+
+psPlaneTransform *PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM()
+{
+    psPlaneTransform *pt = psPlaneTransformAlloc(1, 1);
+    pt->x->coeff[1][0] = 1.0;
+    pt->y->coeff[0][1] = 1.0;
+    return(pt);
+}
+
+psPlaneDistort *PS_CREATE_4D_IDENTITY_PLANE_DISTORT()
+{
+    psPlaneDistort *pd = psPlaneDistortAlloc(1, 1, 1, 1);
+    pd->x->coeff[1][0][0][0] = 1.0;
+    pd->y->coeff[0][1][0][0] = 1.0;
+    return(pd);
+}
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (int i = 0 ; i < TEST_NUM_ROWS ; i++) {
+        for (int j = 0 ; j < TEST_NUM_COLS ; j++) {
+            readout->image->data.F32[i][j] = 32.0;
+            readout->mask->data.U8[i][j] = 0;
+            readout->weight->data.F32[i][j] = 1.0;
+	}
+    }
+
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(readout);
+}
+
+/******************************************************************************
+generateSimpleCell(): This function generates a pmCell data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmCell *generateSimpleCell(pmChip *chip)
+{
+    pmCell *cell = pmCellAlloc(chip, CELL_ALLOC_NAME);
+
+    psMetadataAddS32(cell->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(cell->readouts, NUM_READOUTS);
+    cell->hdu = pmHDUAlloc("cellExtName");
+    for (int i = 0 ; i < NUM_READOUTS ; i++) {
+        cell->readouts->data[i] = psMemDecrRefCounter((psPtr) generateSimpleReadout(cell));
+    }
+
+    // First try to read data from ../dataFiles, then try dataFiles.
+    bool rc = pmConfigFileRead(&cell->hdu->format, "../dataFiles/camera0/format0.config", "Camera format 0");
+    if (!rc) {
+        rc = pmConfigFileRead(&cell->hdu->format, "dataFiles/camera0/format0.config", "Camera format 0");
+        if (!rc) {
+            diag("pmConfigFileRead() was unsuccessful (from generateSimpleCell())");
+	}
+    }
+
+    cell->hdu->images = psArrayAlloc(NUM_HDUS);
+    cell->hdu->masks = psArrayAlloc(NUM_HDUS);
+    cell->hdu->weights = psArrayAlloc(NUM_HDUS);
+    for (int k = 0 ; k < NUM_HDUS ; k++) {
+        cell->hdu->images->data[k]  = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        cell->hdu->masks->data[k]   = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        cell->hdu->weights->data[k] = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(cell->hdu->images->data[k], (float) (BASE_IMAGE+k));
+        psImageInit(cell->hdu->masks->data[k], (psU8) (BASE_MASK+k));
+        psImageInit(cell->hdu->weights->data[k], (float) (BASE_WEIGHT+k));
+    }
+
+    //XXX: Should the region be set some other way?  Like through the various config files?
+    psRegion *region = psRegionAlloc(0.0, 0.0, 0.0, 0.0);
+    // You shouldn't have to remove the key from the metadata.
+    psMetadataRemoveKey(cell->concepts, "CELL.TRIMSEC");
+    psMetadataAddPtr(cell->concepts, PS_LIST_TAIL|PS_META_REPLACE, "CELL.TRIMSEC", PS_DATA_REGION, "I am a region", region);
+    psFree(region);
+    return(cell);
+}
+
+/******************************************************************************
+generateSimpleChip(): This function generates a pmChip data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmChip *generateSimpleChip(pmFPA *fpa)
+{
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    chip->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    chip->fromFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    psMetadataAddS32(chip->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(chip->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+
+    psArrayRealloc(chip->cells, NUM_CELLS);
+    for (int i = 0 ; i < NUM_CELLS ; i++) {
+        chip->cells->data[i] = psMemDecrRefCounter((psPtr) generateSimpleCell(chip));
+    }
+    return(chip);
+}
+
+/******************************************************************************
+generateSimpleFPA(): This function generates a pmFPA data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmFPA* generateSimpleFPA(psMetadata *camera)
+{
+    pmFPA* fpa = pmFPAAlloc(camera);
+    fpa->fromTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toSky = psProjectionAlloc(0.0,0.0,10.0,10.0,PS_PROJ_TAN);
+    psMetadataAddS32(fpa->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    if (camera != NULL) {
+        psMetadataAddS32((psMetadata *) fpa->camera, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    }
+    psMetadataAddS32(fpa->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+
+    psArrayRealloc(fpa->chips, NUM_CHIPS);
+    for (int i = 0 ; i < NUM_CHIPS ; i++) {
+        fpa->chips->data[i] = psMemDecrRefCounter((psPtr) generateSimpleChip(fpa));
+    }
+    pmConceptsBlankFPA(fpa);
+    return(fpa);
+}
+
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(18);
+
+    // ----------------------------------------------------------------------
+    // pmReadoutSetMask() tests: NULL inputs
+    // bool pmReadoutSetMask(pmReadout *readout, psMaskType satMask, psMaskType badMask)
+    if (1) {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        pmReadout *readout = cell->readouts->data[0];
+        bool rc;
+
+        // Set readout == NULL, ensure pmReadoutSetMask() returnes FALSE, with no seg faults, memory leaks
+        rc = pmReadoutSetMask(NULL, SATURATION_MASK, BAD_MASK);
+        ok(!rc, "pmReadoutSetMask(NULL, SATURATION_MASK, BAD_MASK) returned FALSE with null pmReadout input");
+
+        // Set readout->image, ensure pmReadoutSetMask() returnes FALSE, with no seg faults, memory leaks
+        psImage *saveImg = readout->image;
+        readout->image = NULL;
+        rc = pmReadoutSetMask(readout, SATURATION_MASK, BAD_MASK);
+        ok(!rc, "pmReadoutSetMask(readout, SATURATION_MASK, BAD_MASK) returned FALSE with null pmReadout->image input");
+        readout->image = saveImg;
+
+        // Set pixels in the upper-left quadrant to values [10000:11000] range
+        // Set pixels in the upper-right quadrant to values [0:1000] range
+        // Set pixels in the lower-left quadrant to values [100000:1000000] range
+        // Set pixels in the lower-right quadrant to values [100000:1000000] range
+
+        for (int i = 0 ; i < readout->image->numRows ; i++) {
+            for (int j = 0 ; j < readout->image->numCols ; j++) {
+                if (i < readout->image->numRows/2) {
+                    if (j < readout->image->numCols/2) {
+                        readout->image->data.F32[i][j] = 10000.0 + (float) (i + j);
+		    } else {
+                        readout->image->data.F32[i][j] = (float) (i + j);
+		    }
+		} else {
+                    readout->image->data.F32[i][j] = 100000.0 + (float) (i + j);
+		}
+	    }
+	}
+
+        // Set the acceptable pixel range to [100.0 : 20000.0]
+        rc = psMetadataAddF32(readout->parent->concepts, PS_LIST_HEAD, "CELL.SATURATION", PS_META_REPLACE, NULL, 20000.0);
+        rc|= psMetadataAddF32(readout->parent->concepts, PS_LIST_HEAD, "CELL.BAD", PS_META_REPLACE, NULL, 100.0);
+        ok(rc, "Set pixel range in cell->concepts successfully");
+
+        // Call pmReadoutSetMask() and then verify that the mask data was set correctly
+        rc = pmReadoutSetMask(readout, SATURATION_MASK, BAD_MASK);
+        ok(rc, "pmReadoutSetMask(readout, SATURATION_MASK, BAD_MASK) returned TRUE with acceptable input data");
+        bool errorFlag = false;
+        for (int i = 0 ; i < readout->image->numRows ; i++) {
+            for (int j = 0 ; j < readout->image->numCols ; j++) {
+                if (i < readout->image->numRows/2) {
+                    if (j < readout->image->numCols/2) {
+                        if(readout->mask->data.U8[i][j] != 0) {
+                            if (VERBOSE) {
+                                diag("TEST ERROR: mask[%d][%d] is %d, should be 0\n",
+                                      i, j, readout->mask->data.U8[i][j]);
+			    }
+                            errorFlag = true;
+			}
+		    } else {
+                        if(readout->mask->data.U8[i][j] != BAD_MASK) {
+                            if (VERBOSE) {
+                                diag("TEST ERROR: mask[%d][%d] is %d, should be %d\n",
+                                      i, j, readout->mask->data.U8[i][j], BAD_MASK);
+			    }
+                            errorFlag = true;
+			}
+		    }
+		} else {
+                    if(readout->mask->data.U8[i][j] != SATURATION_MASK) {
+                        if (VERBOSE) {
+                            diag("TEST ERROR: mask[%d][%d] is %d, should be %d\n",
+                                  i, j, readout->mask->data.U8[i][j], SATURATION_MASK);
+                        }
+                        errorFlag = true;
+		    }
+		}
+	    }
+	}
+        ok(!errorFlag, "pmReadoutSetMask() set the mask values correctly");
+        psFree(fpa);    
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+    // ----------------------------------------------------------------------
+    // pmReadoutGenerateMask() tests: NULL inputs
+    // bool pmReadoutGenerateMask(pmReadout *readout, psMaskType satMask, psMaskType badMask)
+    // XXX: This test is a duplicate of the above pmReadoutSetMask() test since the actual
+    // code is almost the same.  Must test with the readout->mask == NULL.
+    if (1) {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        pmReadout *readout = cell->readouts->data[0];
+        bool rc;
+
+        // Set readout == NULL, ensure pmReadoutGenerateMask() returnes FALSE, with no seg faults, memory leaks
+        rc = pmReadoutGenerateMask(NULL, SATURATION_MASK, BAD_MASK);
+        ok(!rc, "pmReadoutGenerateMask(NULL, SATURATION_MASK, BAD_MASK) returned FALSE with null pmReadout input");
+
+        // Set pixels in the upper-left quadrant to values [10000:11000] range
+        // Set pixels in the upper-right quadrant to values [0:1000] range
+        // Set pixels in the lower-left quadrant to values [100000:1000000] range
+        // Set pixels in the lower-right quadrant to values [100000:1000000] range
+
+        for (int i = 0 ; i < readout->image->numRows ; i++) {
+            for (int j = 0 ; j < readout->image->numCols ; j++) {
+                if (i < readout->image->numRows/2) {
+                    if (j < readout->image->numCols/2) {
+                        readout->image->data.F32[i][j] = 10000.0 + (float) (i + j);
+		    } else {
+                        readout->image->data.F32[i][j] = (float) (i + j);
+		    }
+		} else {
+                    readout->image->data.F32[i][j] = 100000.0 + (float) (i + j);
+		}
+	    }
+	}
+
+        // Set the acceptable pixel range to [100.0 : 20000.0]
+        rc = psMetadataAddF32(readout->parent->concepts, PS_LIST_HEAD, "CELL.SATURATION", PS_META_REPLACE, NULL, 20000.0);
+        rc|= psMetadataAddF32(readout->parent->concepts, PS_LIST_HEAD, "CELL.BAD", PS_META_REPLACE, NULL, 100.0);
+        ok(rc, "Set pixel range in cell->concepts successfully");
+
+        // Call pmReadoutGenerateMask() and then verify that the mask data was set correctly
+        rc = pmReadoutGenerateMask(readout, SATURATION_MASK, BAD_MASK);
+        ok(rc, "pmReadoutGenerateMask(readout, SATURATION_MASK, BAD_MASK) returned TRUE with acceptable input data");
+        bool errorFlag = false;
+        for (int i = 0 ; i < readout->image->numRows ; i++) {
+            for (int j = 0 ; j < readout->image->numCols ; j++) {
+                if (i < readout->image->numRows/2) {
+                    if (j < readout->image->numCols/2) {
+                        if(readout->mask->data.U8[i][j] != 0) {
+                            if (VERBOSE) {
+                                diag("TEST ERROR: mask[%d][%d] is %d, should be 0\n",
+                                      i, j, readout->mask->data.U8[i][j]);
+			    }
+                            errorFlag = true;
+			}
+		    } else {
+                        if(readout->mask->data.U8[i][j] != BAD_MASK) {
+                            if (VERBOSE) {
+                                diag("TEST ERROR: mask[%d][%d] is %d, should be %d\n",
+                                      i, j, readout->mask->data.U8[i][j], BAD_MASK);
+                                errorFlag = true;
+			    }
+			}
+		    }
+		} else {
+                    if(readout->mask->data.U8[i][j] != SATURATION_MASK) {
+                        if (VERBOSE) {
+                            diag("TEST ERROR: mask[%d][%d] is %d, should be %d\n",
+                                  i, j, readout->mask->data.U8[i][j], SATURATION_MASK);
+                        }
+                        errorFlag = true;
+		    }
+		}
+	    }
+	}
+        ok(!errorFlag, "pmReadoutGenerateMask() set the mask values correctly");
+
+        psFree(fpa);    
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+    // ----------------------------------------------------------------------
+    // pmReadoutSetWeight() tests: NULL inputs
+    // bool pmReadoutSetWeight(pmReadout *readout, bool poisson)
+    if (1) {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        pmReadout *readout = cell->readouts->data[0];
+        bool rc;
+
+        // Set readout == NULL, ensure pmReadoutSetWeight() returnes FALSE, with no seg faults, memory leaks
+        rc = pmReadoutSetWeight(NULL, false);
+        ok(!rc, "pmReadoutSetWeight(NULL, false) returned FALSE with null pmReadout input");
+
+
+        // Set the acceptable pixel range to [100.0 : 20000.0]
+        rc = psMetadataAddF32(readout->parent->concepts, PS_LIST_HEAD, "CELL.GAIN", PS_META_REPLACE, NULL, CELL_GAIN);
+        rc|= psMetadataAddF32(readout->parent->concepts, PS_LIST_HEAD, "CELL.READNOISE", PS_META_REPLACE, NULL, CELL_READNOISE);
+        ok(rc, "Set GAIN and READNOISE in cell->concepts successfully");
+
+        // Call pmReadoutSetWeight() and then verify that the mask data was set correctly
+        rc = pmReadoutSetWeight(readout, false);
+        ok(rc, "pmReadoutSetWeight(readout, false) returned TRUE with acceptable input data");
+        bool errorFlag = false;
+        for (int i = 0 ; i < readout->weight->numRows ; i++) {
+            for (int j = 0 ; j < readout->weight->numCols ; j++) {
+                psF32 exp = CELL_READNOISE * CELL_READNOISE / CELL_GAIN / CELL_GAIN;
+                if(abs(readout->weight->data.F32[i][j] - exp) > 1e-4) {
+                    if (VERBOSE) {
+                        diag("TEST ERROR: weight[%d][%d] is %.2f, should be %.2f\n",
+                              i, j, readout->weight->data.F32[i][j], exp);
+		    }
+                    errorFlag = true;
+		}
+	    }
+	}
+        ok(!errorFlag, "pmReadoutSetWeight() set the weight values correctly (non-Poisson)");
+
+        for (int i = 0 ; i < readout->image->numRows ; i++) {
+            for (int j = 0 ; j < readout->image->numCols ; j++) {
+               readout->image->data.F32[i][j] = 100.0 + (float) (i + j);
+	    }
+	}
+        // Call pmReadoutSetWeight() and then verify that the mask data was set correctly
+        rc = pmReadoutSetWeight(readout, true);
+        ok(rc, "pmReadoutSetWeight(readout, true) returned TRUE with acceptable input data");
+        errorFlag = false;
+        for (int i = 0 ; i < readout->weight->numRows ; i++) {
+            for (int j = 0 ; j < readout->weight->numCols ; j++) {
+                psF32 exp = abs(readout->image->data.F32[i][j] / CELL_GAIN); 
+                if (exp < 1.0) exp = 1.0;
+                exp+= CELL_READNOISE * CELL_READNOISE / CELL_GAIN / CELL_GAIN;
+                if(abs(readout->weight->data.F32[i][j] - exp) > 1e-4) {
+                    if (VERBOSE) {
+                        diag("TEST ERROR: weight[%d][%d] is %.2f, should be %.2f\n",
+                              i, j, readout->weight->data.F32[i][j], exp);
+		    }
+                    errorFlag = true;
+		}
+	    }
+	}
+
+        ok(!errorFlag, "pmReadoutSetWeight() set the weight values correctly (Poisson)");
+        psFree(fpa);    
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPAReadWrite.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPAReadWrite.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPAReadWrite.c	(revision 22157)
@@ -0,0 +1,1218 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+// XXX: Use better name for the temporary FITS file
+// XXX: The code to generate and free the FPA hierarchy was copied from
+// tap-pmFPA.c.  EIther include it directly, or library, or something.
+// Also, get rid of the manual free functions and use psFree() once
+// it correctly frees child members
+// XXX: For the genSimpleFPA() code, add IDs to each function so that
+// the values set in each chip-?cell-?hdu-?image are unique
+
+#define CHIP_ALLOC_NAME		"ChipName"
+#define CELL_ALLOC_NAME		"CellName"
+#define MISC_NUM		32
+#define MISC_NAME		"META00"
+#define MISC_NAME2		"META01"
+#define NUM_BIAS_DATA		10
+#define TEST_NUM_ROWS		4
+#define TEST_NUM_COLS		4
+#define NUM_READOUTS		3
+#define NUM_CELLS		10
+#define NUM_CHIPS		8
+#define NUM_HDUS		5
+#define BASE_IMAGE		10
+#define BASE_MASK		40
+#define BASE_WEIGHT		70
+#define VERBOSE			0
+#define ERR_TRACE_LEVEL		10
+
+psPlaneTransform *PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM()
+{
+    psPlaneTransform *pt = psPlaneTransformAlloc(1, 1);
+    pt->x->coeff[1][0] = 1.0;
+    pt->y->coeff[0][1] = 1.0;
+    return(pt);
+}
+
+psPlaneDistort *PS_CREATE_4D_IDENTITY_PLANE_DISTORT()
+{
+    psPlaneDistort *pd = psPlaneDistortAlloc(1, 1, 1, 1);
+    pd->x->coeff[1][0][0][0] = 1.0;
+    pd->y->coeff[0][1][0][0] = 1.0;
+    return(pd);
+}
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(readout);
+}
+
+/******************************************************************************
+generateSimpleCell(): This function generates a pmCell data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmCell *generateSimpleCell(pmChip *chip)
+{
+    pmCell *cell = pmCellAlloc(chip, CELL_ALLOC_NAME);
+
+    psMetadataAddS32(cell->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(cell->readouts, NUM_READOUTS);
+    cell->hdu = pmHDUAlloc("cellExtName");
+    for (int i = 0 ; i < NUM_READOUTS ; i++) {
+        cell->readouts->data[i] = psMemDecrRefCounter((psPtr) generateSimpleReadout(cell));
+    }
+
+    // First try to read data from ../dataFiles, then try dataFiles.
+    bool rc = pmConfigFileRead(&cell->hdu->format, "../dataFiles/camera0/format0.config", "Camera format 0");
+    if (!rc) {
+        rc = pmConfigFileRead(&cell->hdu->format, "dataFiles/camera0/format0.config", "Camera format 0");
+        if (!rc) {
+            diag("pmConfigFileRead() was unsuccessful (from generateSimpleCell())");
+	}
+    }
+
+    cell->hdu->images = psArrayAlloc(NUM_HDUS);
+    cell->hdu->masks = psArrayAlloc(NUM_HDUS);
+    cell->hdu->weights = psArrayAlloc(NUM_HDUS);
+    for (int k = 0 ; k < NUM_HDUS ; k++) {
+        cell->hdu->images->data[k]  = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        cell->hdu->masks->data[k]   = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        cell->hdu->weights->data[k] = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(cell->hdu->images->data[k], (float) (BASE_IMAGE+k));
+        psImageInit(cell->hdu->masks->data[k], (psU8) (BASE_MASK+k));
+        psImageInit(cell->hdu->weights->data[k], (float) (BASE_WEIGHT+k));
+    }
+
+    //XXX: Should the region be set some other way?  Like through the various config files?
+    psRegion *region = psRegionAlloc(0.0, 0.0, 0.0, 0.0);
+    // You shouldn't have to remove the key from the metadata.  Find out how to simply change the key value.
+    psMetadataRemoveKey(cell->concepts, "CELL.TRIMSEC");
+    psMetadataAddPtr(cell->concepts, PS_LIST_TAIL|PS_META_REPLACE, "CELL.TRIMSEC", PS_DATA_REGION, "I am a region", region);
+    psFree(region);
+    return(cell);
+}
+
+/******************************************************************************
+generateSimpleChip(): This function generates a pmChip data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmChip *generateSimpleChip(pmFPA *fpa)
+{
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    chip->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    chip->fromFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    psMetadataAddS32(chip->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(chip->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+
+    psArrayRealloc(chip->cells, NUM_CELLS);
+    for (int i = 0 ; i < NUM_CELLS ; i++) {
+        chip->cells->data[i] = psMemDecrRefCounter((psPtr) generateSimpleCell(chip));
+    }
+
+    // XXX: Add code to initialize chip pmConcepts
+
+
+    return(chip);
+}
+
+/******************************************************************************
+generateSimpleFPA(): This function generates a pmFPA data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmFPA* generateSimpleFPA(psMetadata *camera)
+{
+    pmFPA* fpa = pmFPAAlloc(camera);
+    fpa->fromTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toSky = psProjectionAlloc(0.0,0.0,10.0,10.0,PS_PROJ_TAN);
+    psMetadataAddS32(fpa->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    if (camera != NULL) {
+        psMetadataAddS32((psMetadata *) fpa->camera, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    }
+    psMetadataAddS32(fpa->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+
+    psArrayRealloc(fpa->chips, NUM_CHIPS);
+    for (int i = 0 ; i < NUM_CHIPS ; i++) {
+        fpa->chips->data[i] = psMemDecrRefCounter((psPtr) generateSimpleChip(fpa));
+    }
+    pmConceptsBlankFPA(fpa);
+    return(fpa);
+}
+
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(22);
+
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+    // pmCellWrite(): tests
+    // Verify pmCellWrite() with NULL pmCell arg
+    if (0) {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp00", "w");
+        ok(!pmCellWrite(NULL, fitsFileW, NULL, false), "pmCellWrite() returned FALSE with NULL pmCell input");
+        psFitsClose(fitsFileW);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmCellWrite() with NULL pmCell arg
+    // XXXX: Big problem: Without the next code, everything else fails.  Why?
+    if (0) {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp00", "w");
+        ok(!pmCellWrite(NULL, fitsFileW, NULL, false), "pmCellWrite() returned FALSE with NULL pmCell input");
+        psFitsClose(fitsFileW);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmCellWrite() with NULL pmFits arg
+    if (0) {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        ok(!pmCellWrite(cell, NULL, NULL, false), "pmCellWrite() returned FALSE with NULL psFits param");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmCellWrite() with acceptable input params
+    // We first write a FITS file with the pmCellWrite(), then we read it and verify.
+    // First call pmCellWrite()
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp00", "w");
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(cell != NULL, "Allocated a pmCell successfully");
+
+        //  Use pmCellWrite() to write image data to the FITS file
+        bool rc = pmCellWrite(cell, fitsFileW, NULL, false);
+        ok(rc, "pmCellWrite() returned TRUE");
+
+        //  Close the FITS file, free memory
+        psFitsClose(fitsFileW);
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // ----------------------------------------------------------------------
+    // pmCellRead() tests 
+    // Verify pmCellRead() with NULL pmCell param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        ok(!pmCellRead(NULL, fitsFileR, NULL), "pmCellRead() returned FALSE with NULL pmCell param");
+        psFitsClose(fitsFileR);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmCellRead() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(!pmCellRead(cell, NULL, NULL), "pmCellRead() returned FALSE with NULL pmFits param");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmCellRead() with acceptable data (using the FITS file created above)
+    {
+        psMemId id = psMemGetId();
+        bool rc;
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        // Free the existing cell hdu image data (so we can verify that pmCellRead() actually reads the data
+        psFree(cell->hdu->images);
+        cell->hdu->images = NULL;
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+
+        rc = pmCellRead(cell, fitsFileR, NULL);
+        ok(rc, "pmCellRead() returned TRUE");
+        skip_start(!rc, 1, "Skipping tests because pmCellRead returned NULL");
+        for (int k = 0 ; k < cell->hdu->images->n ; k++) {
+            bool errorFlag = false;
+            psImage *img = cell->hdu->images->data[k];
+            for (int i = 0 ; i < img->numRows ; i++) {
+                for (int j = 0 ; j < img->numCols ; j++) {
+                    if (((float) (BASE_IMAGE+k)) != img->data.F32[i][j]) {
+                        diag("TEST ERROR: img[%d][%d] is %.2f, should be %.2f\n", i, j,
+                              img->data.F32[i][j], ((float) (BASE_IMAGE+k)));
+                        errorFlag = true;
+		    }
+		}
+	    }
+            ok(!errorFlag, "pmCellWrite()/pmCellRead() properly set the image data (image %d)", k);
+        }
+        skip_end();
+        psFitsClose(fitsFileR);
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+    // pmCellWriteWeight(): tests
+    // Verify pmCellWriteWeight() with NULL pmCell arg
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp00", "w");
+        ok(!pmCellWriteWeight(NULL, fitsFileW, NULL, false), "pmCellWriteWeight() returned FALSE with NULL pmCell input");
+        psFitsClose(fitsFileW);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmCellWriteWeight() with NULL pmFits arg
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        ok(!pmCellWriteWeight(cell, NULL, NULL, false), "pmCellWriteWeight() returned FALSE with NULL psFits param");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmCellWriteWeight() with acceptable input params
+    // We first write a FITS file with the pmCellWriteWeight(), then we read it and verify.
+    // First call pmCellWriteWeight()
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp00", "w");
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(cell != NULL, "Allocated a pmCell successfully");
+
+        //  Use pmCellWriteWeight() to write weight data to the FITS file
+        bool rc = pmCellWriteWeight(cell, fitsFileW, NULL, false);
+        ok(rc, "pmCellWriteWeight() returned TRUE");
+
+        //  Close the FITS file, free memory
+        psFitsClose(fitsFileW);
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmCellReadWeight() tests 
+    // Verify pmCellReadWeight() with NULL pmCell param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        ok(!pmCellReadWeight(NULL, fitsFileR, NULL), "pmCellReadWeight() returned FALSE with NULL pmCell param");
+        psFitsClose(fitsFileR);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmCellReadWeight() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(!pmCellReadWeight(cell, NULL, NULL), "pmCellReadWeight() returned FALSE with NULL pmFits param");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmCellReadWeight() with acceptable data (using the FITS file created above)
+    {
+        psMemId id = psMemGetId();
+        bool rc;
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        // Free the existing cell hdu weight data (so we can verify that pmCellReadWeight() actually reads the data
+        psFree(cell->hdu->weights);
+        cell->hdu->weights = NULL;
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+
+        rc = pmCellReadWeight(cell, fitsFileR, NULL);
+        ok(rc, "pmCellReadWeight() returned TRUE");
+        for (int k = 0 ; k < cell->hdu->weights->n ; k++) {
+            bool errorFlag = false;
+            psImage *msk = cell->hdu->weights->data[k];
+            for (int i = 0 ; i < msk->numRows ; i++) {
+                for (int j = 0 ; j < msk->numCols ; j++) {
+                    if (((float) (BASE_WEIGHT+k)) != msk->data.F32[i][j]) {
+                        diag("TEST ERROR: msk[%d][%d] is %.2f, should be %.2f\n", i, j,
+                              msk->data.F32[i][j], ((float) (BASE_WEIGHT+k)));
+                        errorFlag = true;
+		    }
+		}
+	    }
+            ok(!errorFlag, "pmCellWriteWeight()/pmCellReadWeight() properly set the weight data (image %d)", k);
+        }
+        psFitsClose(fitsFileR);
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+    // pmCellWriteMask(): tests
+    // Verify pmCellWriteMask() with NULL pmCell arg
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp00", "w");
+        ok(!pmCellWriteMask(NULL, fitsFileW, NULL, false), "pmCellWriteMask() returned FALSE with NULL pmCell input");
+        psFitsClose(fitsFileW);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmCellWriteMask() with NULL pmFits arg
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        ok(!pmCellWriteMask(cell, NULL, NULL, false), "pmCellWriteMask() returned FALSE with NULL psFits param");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmCellWriteMask() with acceptable input params
+    // We first write a FITS file with the pmCellWriteMask(), then we read it and verify.
+    // First call pmCellWriteMask()
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp00", "w");
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(cell != NULL, "Allocated a pmCell successfully");
+
+        //  Use pmCellWriteMask() to write mask data to the FITS file
+        bool rc = pmCellWriteMask(cell, fitsFileW, NULL, false);
+        ok(rc, "pmCellWriteMask() returned TRUE");
+
+        //  Close the FITS file, free memory
+        psFitsClose(fitsFileW);
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmCellReadMask() tests 
+    // Verify pmCellReadMask() with NULL pmCell param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        ok(!pmCellReadMask(NULL, fitsFileR, NULL), "pmCellReadMask() returned FALSE with NULL pmCell param");
+        psFitsClose(fitsFileR);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmCellReadMask() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(!pmCellReadMask(cell, NULL, NULL), "pmCellReadMask() returned FALSE with NULL pmFits param");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmCellReadMask() with acceptable data (using the FITS file created above)
+    {
+        psMemId id = psMemGetId();
+        bool rc;
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        // Free the existing cell hdu mask data (so we can verify that pmCellReadMask() actually reads the data
+        psFree(cell->hdu->masks);
+        cell->hdu->masks = NULL;
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+
+        rc = pmCellReadMask(cell, fitsFileR, NULL);
+        ok(rc, "pmCellReadMask() returned TRUE");
+        for (int k = 0 ; k < cell->hdu->masks->n ; k++) {
+            bool errorFlag = false;
+            psImage *msk = cell->hdu->masks->data[k];
+            for (int i = 0 ; i < msk->numRows ; i++) {
+                for (int j = 0 ; j < msk->numCols ; j++) {
+                    if (0) {
+                        if (((float) (BASE_WEIGHT+k)) != msk->data.F32[i][j]) {
+                            diag("TEST ERROR: msk[%d][%d] is %.2f, should be %.2f\n", i, j,
+                                  msk->data.F32[i][j], ((float) (BASE_WEIGHT+k)));
+                            errorFlag = true;
+                        }
+		    }
+                    if (1) {
+                        if (((BASE_MASK+k)) != msk->data.U8[i][j]) {
+                            diag("TEST ERROR: msk[%d][%d] is %.2f, should be %.2f\n", i, j,
+                                  msk->data.F32[i][j], ((float) (BASE_MASK+k)));
+                            errorFlag = true;
+                        }
+		    }
+    		}
+	    }
+            ok(!errorFlag, "pmCellWriteMask()/pmCellReadMask() properly set the mask data (image %d)", k);
+        }
+        psFitsClose(fitsFileR);
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+    // pmChipWrite() tests
+    // Verify pmChipWrite() with NULL pmChip param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp01", "w");
+        ok(!pmChipWrite(NULL, fitsFileW, NULL, false, true), "pmChipWrite() returned NULL with NULL pmChip param");
+        psFitsClose(fitsFileW);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmChipWrite() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(!pmChipWrite(chip, NULL, NULL, false, true), "pmChipWrite() returned NULL with NULL pmFits param");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmChipWrite() with acceptable data
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp01", "w");
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        ok(chip != NULL, "Allocated a pmChip successfully");
+
+        //  Use pmChipWrite() to write image data to the FITS file
+        bool rc = pmChipWrite(chip, fitsFileW, NULL, false, true);
+        ok(rc, "pmChipWrite() returned TRUE");
+
+        //  Close the FITS file, free memory
+        psFitsClose(fitsFileW);
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmChipRead() tests
+    // Verify pmChipRead() with NULL pmChip param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        ok(!pmChipRead(NULL, fitsFileR, NULL), "pmChipRead() returned NULL with NULL pmChip param");
+        psFitsClose(fitsFileR);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmChipRead() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        ok(!pmChipRead(chip, NULL, NULL), "pmChipRead() returned NULL with NULL pmFits param");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmChipRead() with acceptable input data
+    {
+        psMemId id = psMemGetId();
+        bool rc;
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        // Free the cells for chip 0 so we can verify that pmChipRead() actually reads the data from file
+        for (int chipID = 0 ; chipID < chip->cells->n ; chipID++) {
+            pmCell *cell = (pmCell *) chip->cells->data[chipID];
+            psFree(cell);
+            cell = NULL;
+	}
+
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        rc = pmChipRead(chip, fitsFileR, NULL);
+        ok(rc, "pmChipRead() returned TRUE");
+        bool errorFlag = false;
+        // XXX: chipID should be cellID
+        for (int chipID = 0 ; chipID < chip->cells->n ; chipID++) {
+            if (VERBOSE) diag("Reading cell %d\n", chipID);
+            pmCell *cell = (pmCell *) chip->cells->data[chipID];
+            for (int k = 0 ; k < cell->hdu->images->n ; k++) {
+                if (VERBOSE) diag("NOTE: image %d\n", k);
+                psImage *img = cell->hdu->images->data[k];
+                for (int i = 0 ; i < img->numRows ; i++) {
+                    for (int j = 0 ; j < img->numCols ; j++) {
+                        if (((float) (BASE_IMAGE+k)) != img->data.F32[i][j]) {
+                            diag("TEST ERROR: img[%d][%d] is %.2f, should be %.2f\n", i, j,
+                                  img->data.F32[i][j], ((float) (BASE_IMAGE+k)));
+                            errorFlag = true;
+			}
+		    }
+		}
+	    }
+            ok(!errorFlag, "pmChipWrite()/pmChipRead() properly set the image data (cell %d)", chipID);
+	}
+
+        psFitsClose(fitsFileR);
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmChipWriteWeight() tests
+    // Verify pmChipWriteWeight() with NULL pmChip param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp01", "w");
+        ok(!pmChipWriteWeight(NULL, fitsFileW, NULL, false, true), "pmChipWriteWeight() returned NULL with NULL pmChip param");
+        psFitsClose(fitsFileW);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmChipWriteWeight() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(!pmChipWriteWeight(chip, NULL, NULL, false, true), "pmChipWriteWeight() returned NULL with NULL pmFits param");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmChipWriteWeight() with acceptable data
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp01", "w");
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        ok(chip != NULL, "Allocated a pmChip successfully");
+
+        //  Use pmChipWriteWeight() to write image data to the FITS file
+        bool rc = pmChipWriteWeight(chip, fitsFileW, NULL, false, true);
+        ok(rc, "pmChipWriteWeight() returned TRUE");
+
+        //  Close the FITS file, free memory
+        psFitsClose(fitsFileW);
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmChipReadMask() tests
+    // Verify pmChipReadMask() with NULL pmChip param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        ok(!pmChipReadMask(NULL, fitsFileR, NULL), "pmChipReadMask() returned NULL with NULL pmChip param");
+        psFitsClose(fitsFileR);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmChipReadMask() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        ok(!pmChipReadMask(chip, NULL, NULL), "pmChipReadMask() returned NULL with NULL pmFits param");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmChipReadMask() with acceptable input data
+    {
+        psMemId id = psMemGetId();
+        bool rc;
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        // Free the cells for chip 0 so we can verify that pmChipReadMask() actually reads the data from file
+        for (int chipID = 0 ; chipID < chip->cells->n ; chipID++) {
+            pmCell *cell = (pmCell *) chip->cells->data[chipID];
+            psFree(cell);
+            cell = NULL;
+	}
+
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        rc = pmChipReadMask(chip, fitsFileR, NULL);
+        ok(rc, "pmChipReadMask() returned TRUE");
+        bool errorFlag = false;
+        // XXX: chipID should be cellID
+        for (int chipID = 0 ; chipID < chip->cells->n ; chipID++) {
+            if (VERBOSE) diag("Reading cell %d\n", chipID);
+            pmCell *cell = (pmCell *) chip->cells->data[chipID];
+            for (int k = 0 ; k < cell->hdu->weights->n ; k++) {
+                if (VERBOSE) diag("NOTE: image %d\n", k);
+                psImage *wgt = cell->hdu->weights->data[k];
+                for (int i = 0 ; i < wgt->numRows ; i++) {
+                    for (int j = 0 ; j < wgt->numCols ; j++) {
+                        if (((float) (BASE_WEIGHT+k)) != wgt->data.F32[i][j]) {
+                            diag("TEST ERROR: wgt[%d][%d] is %.2f, should be %.2f\n", i, j,
+                                  wgt->data.F32[i][j], ((float) (BASE_WEIGHT+k)));
+                            errorFlag = true;
+			}
+		    }
+		}
+	    }
+            ok(!errorFlag, "pmChipWriteWeight()/pmChipReadWeight() properly set the weight data (cell %d)", chipID);
+	}
+
+        psFitsClose(fitsFileR);
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmChipReadMask() tests
+    // Verify pmChipReadMask() with NULL pmChip param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        ok(!pmChipReadMask(NULL, fitsFileR, NULL), "pmChipReadMask() returned NULL with NULL pmChip param");
+        psFitsClose(fitsFileR);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmChipReadMask() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        ok(!pmChipReadMask(chip, NULL, NULL), "pmChipReadMask() returned NULL with NULL pmFits param");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmChipReadMask() with acceptable input data
+    {
+        psMemId id = psMemGetId();
+        bool rc;
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        // Free the cells for chip 0 so we can verify that pmChipReadMask() actually reads the data from file
+        for (int chipID = 0 ; chipID < chip->cells->n ; chipID++) {
+            pmCell *cell = (pmCell *) chip->cells->data[chipID];
+            psFree(cell);
+            cell = NULL;
+	}
+
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        rc = pmChipReadMask(chip, fitsFileR, NULL);
+        ok(rc, "pmChipReadMask() returned TRUE");
+        bool errorFlag = false;
+        // XXX: chipID should be cellID
+        for (int chipID = 0 ; chipID < chip->cells->n ; chipID++) {
+            if (VERBOSE) diag("Reading cell %d\n", chipID);
+            pmCell *cell = (pmCell *) chip->cells->data[chipID];
+            for (int k = 0 ; k < cell->hdu->masks->n ; k++) {
+                if (VERBOSE) diag("NOTE: image %d\n", k);
+                psImage *msk = cell->hdu->masks->data[k];
+                for (int i = 0 ; i < msk->numRows ; i++) {
+                    for (int j = 0 ; j < msk->numCols ; j++) {
+                        if (((BASE_MASK+k)) != msk->data.U8[i][j]) {
+                            diag("TEST ERROR: msk[%d][%d] is %.2f, should be %.2f\n", i, j,
+                                  msk->data.F32[i][j], ((float) (BASE_MASK+k)));
+                            errorFlag = true;
+			}
+		    }
+		}
+	    }
+            ok(!errorFlag, "pmChipWriteMask()/pmChipReadMask() properly set the mask data (cell %d)", chipID);
+	}
+
+        psFitsClose(fitsFileR);
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+    // pmFPAWrite() tests
+    // pmFPAWrite(pmFPA *fpa, psFits *fits, psDB *db, bool blank, bool recurse)
+    // Verify pmFPAWrite() with NULL pmFPA param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp01", "w");
+        ok(!pmFPAWrite(NULL, fitsFileW, NULL, false, true), "pmFPAWrite() returned NULL with NULL pmFPA param");
+        psFitsClose(fitsFileW);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmFPAWrite() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(!pmFPAWrite(fpa, NULL, NULL, false, true), "pmFPAWrite() returned NULL with NULL pmFits param");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmFPAWrite() with acceptable data
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp01", "w");
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        bool rc = pmFPAWrite(fpa, fitsFileW, NULL, false, true);
+        ok(rc, "pmFPAWrite() returned TRUE");
+        //  Close the FITS file, free memory
+        psFitsClose(fitsFileW);
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmFPARead() tests
+    // Verify pmFPARead() with NULL pmFPA param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        ok(!pmFPARead(NULL, fitsFileR, NULL), "pmFPARead() returned NULL with NULL pmFPA param");
+        psFitsClose(fitsFileR);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmFPARead() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        ok(!pmFPARead(fpa, NULL, NULL), "pmFPARead() returned NULL with NULL pmFits param");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmFPARead() with acceptable input data
+    {
+        psMemId id = psMemGetId();
+        bool rc;
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        // Free the cells for chip 0 so we can verify that pmFPARead() actually reads the data from file
+        if (0) {
+            for (int chipID = 0 ; chipID < chip->cells->n ; chipID++) {
+                pmCell *cell = (pmCell *) chip->cells->data[chipID];
+                psFree(cell);
+                cell = NULL;
+            }
+	}
+
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        rc = pmFPARead(fpa, fitsFileR, NULL);
+        ok(rc, "pmFPARead() returned TRUE");
+        bool errorFlag = false;
+        // XXX: fpaID should be chipID
+        // XXX: chipID should be cellID
+        for (int fpaID = 0 ; fpaID < fpa->chips->n ; fpaID++) {
+            pmChip *chip = fpa->chips->data[fpaID];
+            for (int chipID = 0 ; chipID < chip->cells->n ; chipID++) {
+                if (VERBOSE) diag("Reading cell %d\n", chipID);
+                pmCell *cell = (pmCell *) chip->cells->data[chipID];
+                for (int k = 0 ; k < cell->hdu->images->n ; k++) {
+                    if (VERBOSE) diag("NOTE: image %d\n", k);
+                    psImage *img = cell->hdu->images->data[k];
+                    for (int i = 0 ; i < img->numRows ; i++) {
+                        for (int j = 0 ; j < img->numCols ; j++) {
+                            if (((float) (BASE_IMAGE+k)) != img->data.F32[i][j]) {
+                                diag("TEST ERROR: img[%d][%d] is %.2f, should be %.2f\n", i, j,
+                                      img->data.F32[i][j], ((float) (BASE_IMAGE+k)));
+                                errorFlag = true;
+        			}
+        		    }
+        		}
+        	    }
+                ok(!errorFlag, "pmFPAWrite()/pmFPARead() properly set the image data (chip %d, cell %d)", fpaID, chipID);
+    	    }
+	}
+
+        psFitsClose(fitsFileR);
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+    // pmFPAWriteWeight() tests
+    // pmFPAWriteWeight(pmFPA *fpa, psFits *fits, psDB *db, bool blank, bool recurse)
+    // Verify pmFPAWriteWeight() with NULL pmFPA param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp01", "w");
+        ok(!pmFPAWriteWeight(NULL, fitsFileW, NULL, false, true), "pmFPAWriteWeight() returned NULL with NULL pmFPA param");
+        psFitsClose(fitsFileW);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmFPAWriteWeight() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(!pmFPAWriteWeight(fpa, NULL, NULL, false, true), "pmFPAWriteWeight() returned NULL with NULL pmFits param");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmFPAWriteWeight() with acceptable data
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp01", "w");
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        bool rc = pmFPAWriteWeight(fpa, fitsFileW, NULL, false, true);
+        ok(rc, "pmFPAWriteWeight() returned TRUE");
+        //  Close the FITS file, free memory
+        psFitsClose(fitsFileW);
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmFPAReadWeight() tests
+    // Verify pmFPAReadWeight() with NULL pmFPA param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        ok(!pmFPARead(NULL, fitsFileR, NULL), "pmFPAReadWeight() returned NULL with NULL pmFPA param");
+        psFitsClose(fitsFileR);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmFPAReadWeight() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        ok(!pmFPARead(fpa, NULL, NULL), "pmFPAReadWeight() returned NULL with NULL pmFits param");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmFPAReadWeight() with acceptable input data
+    {
+        psMemId id = psMemGetId();
+        bool rc;
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        // Free the cells for chip 0 so we can verify that pmFPAReadWeight() actually reads the data from file
+        if (0) {
+            for (int chipID = 0 ; chipID < chip->cells->n ; chipID++) {
+                pmCell *cell = (pmCell *) chip->cells->data[chipID];
+                psFree(cell);
+                cell = NULL;
+            }
+	}
+
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        rc = pmFPARead(fpa, fitsFileR, NULL);
+        ok(rc, "pmFPAReadWeight() returned TRUE");
+        bool errorFlag = false;
+        // XXX: fpaID should be chipID
+        // XXX: chipID should be cellID
+        for (int fpaID = 0 ; fpaID < fpa->chips->n ; fpaID++) {
+            pmChip *chip = fpa->chips->data[fpaID];
+            for (int chipID = 0 ; chipID < chip->cells->n ; chipID++) {
+                if (VERBOSE) diag("Reading cell %d\n", chipID);
+                pmCell *cell = (pmCell *) chip->cells->data[chipID];
+                for (int k = 0 ; k < cell->hdu->weights->n ; k++) {
+                    if (VERBOSE) diag("NOTE: image %d\n", k);
+                    psImage *wgt = cell->hdu->weights->data[k];
+                    for (int i = 0 ; i < wgt->numRows ; i++) {
+                        for (int j = 0 ; j < wgt->numCols ; j++) {
+                            if (((float) (BASE_WEIGHT+k)) != wgt->data.F32[i][j]) {
+                                diag("TEST ERROR: wgt[%d][%d] is %.2f, should be %.2f\n", i, j,
+                                      wgt->data.F32[i][j], ((float) (BASE_WEIGHT+k)));
+                                errorFlag = true;
+        			}
+        		    }
+        		}
+        	    }
+                ok(!errorFlag, "pmFPAWriteWeight()/pmFPAReadWeight() properly set the image data (chip %d, cell %d)", fpaID, chipID);
+    	    }
+	}
+
+        psFitsClose(fitsFileR);
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // ----------------------------------------------------------------------
+    // pmFPAWriteMask() tests
+    // pmFPAWriteMask(pmFPA *fpa, psFits *fits, psDB *db, bool blank, bool recurse)
+    // Verify pmFPAWriteMask() with NULL pmFPA param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp01", "w");
+        ok(!pmFPAWriteMask(NULL, fitsFileW, NULL, false, true), "pmFPAWriteMask() returned NULL with NULL pmFPA param");
+        psFitsClose(fitsFileW);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmFPAWriteMask() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(!pmFPAWriteMask(fpa, NULL, NULL, false, true), "pmFPAWriteMask() returned NULL with NULL pmFits param");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmFPAWriteMask() with acceptable data
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(".tmp01", "w");
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        bool rc = pmFPAWriteMask(fpa, fitsFileW, NULL, false, true);
+        ok(rc, "pmFPAWriteMask() returned TRUE");
+        //  Close the FITS file, free memory
+        psFitsClose(fitsFileW);
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmFPAReadMask() tests
+    // Verify pmFPAReadMask() with NULL pmFPA param
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        ok(!pmFPARead(NULL, fitsFileR, NULL), "pmFPAReadMask() returned NULL with NULL pmFPA param");
+        psFitsClose(fitsFileR);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmFPAReadMask() with NULL pmFits param
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        ok(!pmFPARead(fpa, NULL, NULL), "pmFPAReadMask() returned NULL with NULL pmFits param");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Verify pmFPAReadMask() with acceptable input data
+    {
+        psMemId id = psMemGetId();
+        bool rc;
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        // Free the cells for chip 0 so we can verify that pmFPAReadMask() actually reads the data from file
+        if (0) {
+            for (int chipID = 0 ; chipID < chip->cells->n ; chipID++) {
+                pmCell *cell = (pmCell *) chip->cells->data[chipID];
+                psFree(cell);
+                cell = NULL;
+            }
+	}
+
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        rc = pmFPARead(fpa, fitsFileR, NULL);
+        ok(rc, "pmFPAReadMask() returned TRUE");
+        bool errorFlag = false;
+        // XXX: fpaID should be chipID
+        // XXX: chipID should be cellID
+        for (int fpaID = 0 ; fpaID < fpa->chips->n ; fpaID++) {
+            pmChip *chip = fpa->chips->data[fpaID];
+            for (int chipID = 0 ; chipID < chip->cells->n ; chipID++) {
+                if (VERBOSE) diag("Reading cell %d\n", chipID);
+                pmCell *cell = (pmCell *) chip->cells->data[chipID];
+                for (int k = 0 ; k < cell->hdu->masks->n ; k++) {
+                    if (VERBOSE) diag("NOTE: image %d\n", k);
+                    psImage *msk = cell->hdu->masks->data[k];
+                    for (int i = 0 ; i < msk->numRows ; i++) {
+                        for (int j = 0 ; j < msk->numCols ; j++) {
+                            if (((BASE_MASK+k)) != msk->data.U8[i][j]) {
+                                diag("TEST ERROR: msk[%d][%d] is %.2f, should be %.2f\n", i, j,
+                                      msk->data.F32[i][j], ((float) (BASE_MASK+k)));
+                                errorFlag = true;
+        			}
+        		    }
+        		}
+        	    }
+                ok(!errorFlag, "pmFPAWriteMask()/pmFPAReadMask() properly set the image data (chip %d, cell %d)", fpaID, chipID);
+    	    }
+	}
+
+        psFitsClose(fitsFileR);
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPAUtils.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPAUtils.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPAUtils.c	(revision 22157)
@@ -0,0 +1,295 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS:
+    All functions are tested.
+*/
+
+#define CHIP_ALLOC_NAME        "ChipName"
+#define CELL_ALLOC_NAME        "CellName"
+#define MISC_NUM                32
+#define MISC_NAME              "META00"
+#define MISC_NAME2             "META01"
+#define NUM_BIAS_DATA           10
+#define TEST_NUM_ROWS           4
+#define TEST_NUM_COLS           4
+#define NUM_READOUTS            3
+#define NUM_CELLS               10
+#define NUM_CHIPS               8
+#define NUM_HDUS                5
+#define BASE_IMAGE              10
+#define BASE_MASK               40
+#define BASE_WEIGHT             70
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+
+psPlaneTransform *PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM()
+{
+    psPlaneTransform *pt = psPlaneTransformAlloc(1, 1);
+    pt->x->coeff[1][0] = 1.0;
+    pt->y->coeff[0][1] = 1.0;
+    return(pt);
+}
+
+psPlaneDistort *PS_CREATE_4D_IDENTITY_PLANE_DISTORT()
+{
+    psPlaneDistort *pd = psPlaneDistortAlloc(1, 1, 1, 1);
+    pd->x->coeff[1][0][0][0] = 1.0;
+    pd->y->coeff[0][1][0][0] = 1.0;
+    return(pd);
+}
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(readout);
+}
+
+/******************************************************************************
+generateSimpleCell(): This function generates a pmCell data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmCell *generateSimpleCell(pmChip *chip)
+{
+    pmCell *cell = pmCellAlloc(chip, CELL_ALLOC_NAME);
+
+    psMetadataAddS32(cell->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(cell->readouts, NUM_READOUTS);
+    cell->hdu = pmHDUAlloc("cellExtName");
+    for (int i = 0 ; i < NUM_READOUTS ; i++) {
+        cell->readouts->data[i] = psMemDecrRefCounter((psPtr) generateSimpleReadout(cell));
+    }
+
+    // First try to read data from ../dataFiles, then try dataFiles.
+    bool rc = pmConfigFileRead(&cell->hdu->format, "dataFiles/camera0/format0.config", "Camera format 0");
+    if (!rc) {
+        rc = pmConfigFileRead(&cell->hdu->format, "../dataFiles/camera0/format0.config", "Camera format 0");
+        if (!rc) {
+            diag("pmConfigFileRead() was unsuccessful (from generateSimpleCell())");
+	}
+    }
+
+    cell->hdu->images = psArrayAlloc(NUM_HDUS);
+    cell->hdu->masks = psArrayAlloc(NUM_HDUS);
+    cell->hdu->weights = psArrayAlloc(NUM_HDUS);
+    for (int k = 0 ; k < NUM_HDUS ; k++) {
+        cell->hdu->images->data[k]  = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        cell->hdu->masks->data[k]   = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        cell->hdu->weights->data[k] = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(cell->hdu->images->data[k], (float) (BASE_IMAGE+k));
+        psImageInit(cell->hdu->masks->data[k], (psU8) (BASE_MASK+k));
+        psImageInit(cell->hdu->weights->data[k], (float) (BASE_WEIGHT+k));
+    }
+
+    //XXX: Should the region be set some other way?  Like through the various config files?
+    psRegion *region = psRegionAlloc(0.0, 0.0, 0.0, 0.0);
+    // You shouldn't have to remove the key from the metadata.
+    psMetadataRemoveKey(cell->concepts, "CELL.TRIMSEC");
+    psMetadataAddPtr(cell->concepts, PS_LIST_TAIL|PS_META_REPLACE, "CELL.TRIMSEC", PS_DATA_REGION, "I am a region", region);
+    psFree(region);
+    return(cell);
+}
+
+/******************************************************************************
+generateSimpleChip(): This function generates a pmChip data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmChip *generateSimpleChip(pmFPA *fpa)
+{
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    chip->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    chip->fromFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    psMetadataAddS32(chip->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(chip->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+
+    psArrayRealloc(chip->cells, NUM_CELLS);
+    for (int i = 0 ; i < NUM_CELLS ; i++) {
+        chip->cells->data[i] = psMemDecrRefCounter((psPtr) generateSimpleCell(chip));
+    }
+    return(chip);
+}
+
+/******************************************************************************
+generateSimpleFPA(): This function generates a pmFPA data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmFPA* generateSimpleFPA(psMetadata *camera)
+{
+    pmFPA* fpa = pmFPAAlloc(camera);
+    fpa->fromTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toSky = psProjectionAlloc(0.0,0.0,10.0,10.0,PS_PROJ_TAN);
+    psMetadataAddS32(fpa->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    if (camera != NULL) {
+        psMetadataAddS32((psMetadata *) fpa->camera, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    }
+    psMetadataAddS32(fpa->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+
+    psArrayRealloc(fpa->chips, NUM_CHIPS);
+    for (int i = 0 ; i < NUM_CHIPS ; i++) {
+        fpa->chips->data[i] = psMemDecrRefCounter((psPtr) generateSimpleChip(fpa));
+    }
+    pmConceptsBlankFPA(fpa);
+    return(fpa);
+}
+
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(40);
+
+
+    // ----------------------------------------------------------------------
+    // pmFPAFindChip() tests
+    // Call pmFPAFindChip() with NULL pmFPA input parameter
+    {
+        psMemId id = psMemGetId();
+        int rc = pmFPAFindChip(NULL, "chip-0");
+        ok(-1 == rc, "pmFPAFindChip() returns -1 with NULL pmFPA input parameter");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmFPAFindChip() with bogus chip name
+    {
+        psMemId id = psMemGetId();
+        pmFPA* fpa = generateSimpleFPA(NULL);
+        int rc = pmFPAFindChip(fpa, "bogus");
+        ok(-1 == rc, "pmFPAFindChip() returns -1 with bogus chip name");
+        psFree(fpa);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmFPAFindChip() with NULL chip name
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        pmFPA* fpa = generateSimpleFPA(NULL);
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        int rc = pmFPAFindChip(fpa, NULL);
+        ok(-1 == rc, "pmFPAFindChip() returns -1 with NULL name input param");
+        psFree(fpa);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmFPAFindChip() with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        pmFPA* fpa = generateSimpleFPA(NULL);
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        // Set unique chip names
+        for (int chipID = 0 ; chipID < fpa->chips->n ; chipID++) {
+            pmChip *chip = fpa->chips->data[chipID];
+            char tmpName[100];
+            sprintf(tmpName, "chip-%d", chipID);
+            psMetadataAddStr(chip->concepts, PS_LIST_TAIL, "CHIP.NAME", PS_META_REPLACE, NULL, tmpName);
+	}
+
+        // Verify that pmFPAFindChip() finds those chips correctly
+        for (int chipID = 0 ; chipID < fpa->chips->n ; chipID++) {
+            char tmpName[100];
+            sprintf(tmpName, "chip-%d", chipID);
+            int rc = pmFPAFindChip(fpa, tmpName);
+            ok(rc == chipID, "pmFPAFindChip() correctly found chip %s (%d)", tmpName, rc);
+	}
+
+        psFree(fpa);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+    // ----------------------------------------------------------------------
+    // pmChipFindCell() tests
+    // Call pmChipFindCell() with bogus cell name
+    {
+        psMemId id = psMemGetId();
+        int rc = pmChipFindCell(NULL, "cell-0");
+        ok(-1 == rc, "pmChipFindCell() returns -1 with NULL pmChip input param");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // Call pmChipFindCell() with bogus cell name
+    {
+        psMemId id = psMemGetId();
+        pmFPA* fpa = generateSimpleFPA(NULL);
+        pmChip *chip = fpa->chips->data[0];
+        int rc = pmChipFindCell(chip, "bogus");
+        ok(-1 == rc, "pmChipFindCell() returns -1 with bogus cell name");
+        psFree(fpa);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmChipFindCell() with NULL cell name
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmChip heirarchy
+        pmFPA* fpa = generateSimpleFPA(NULL);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        int rc = pmChipFindCell(chip, NULL);
+        ok(-1 == rc, "pmChipFindCell() returns -1 with NULL name input param");
+        psFree(fpa);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmChipFindCell() with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmChip heirarchy
+        pmFPA* fpa = generateSimpleFPA(NULL);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        // Set unique cell names
+        for (int cellID = 0 ; cellID < chip->cells->n ; cellID++) {
+            pmCell *cell = chip->cells->data[cellID];
+            char tmpName[100];
+            sprintf(tmpName, "cell-%d", cellID);
+            psMetadataAddStr(cell->concepts, PS_LIST_TAIL, "CELL.NAME", PS_META_REPLACE, NULL, tmpName);
+	}
+
+        // Verify that pmChipFindCell() finds those cells correctly
+        for (int cellID = 0 ; cellID < chip->cells->n ; cellID++) {
+            char tmpName[100];
+            sprintf(tmpName, "cell-%d", cellID);
+            int rc = pmChipFindCell(chip, tmpName);
+            ok(rc == cellID, "pmChipFindCell() correctly found cell %s (%d)", tmpName, rc);
+	}
+
+        psFree(fpa);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPAView.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPAView.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmFPAView.c	(revision 22157)
@@ -0,0 +1,1023 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS:
+    All functions are tested.
+*/
+
+#define CHIP_ALLOC_NAME        "ChipName"
+#define CELL_ALLOC_NAME        "CellName"
+#define MISC_NUM                32
+#define MISC_NAME              "META00"
+#define MISC_NAME2             "META01"
+#define NUM_BIAS_DATA           10
+#define TEST_NUM_ROWS           4
+#define TEST_NUM_COLS           4
+#define NUM_READOUTS            3
+#define NUM_CELLS               10
+#define NUM_CHIPS               8
+#define NUM_HDUS                5
+#define BASE_IMAGE              10
+#define BASE_MASK               40
+#define BASE_WEIGHT             70
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+
+psPlaneTransform *PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM()
+{
+    psPlaneTransform *pt = psPlaneTransformAlloc(1, 1);
+    pt->x->coeff[1][0] = 1.0;
+    pt->y->coeff[0][1] = 1.0;
+    return(pt);
+}
+
+psPlaneDistort *PS_CREATE_4D_IDENTITY_PLANE_DISTORT()
+{
+    psPlaneDistort *pd = psPlaneDistortAlloc(1, 1, 1, 1);
+    pd->x->coeff[1][0][0][0] = 1.0;
+    pd->y->coeff[0][1][0][0] = 1.0;
+    return(pd);
+}
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(readout);
+}
+
+/******************************************************************************
+generateSimpleCell(): This function generates a pmCell data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmCell *generateSimpleCell(pmChip *chip)
+{
+    pmCell *cell = pmCellAlloc(chip, CELL_ALLOC_NAME);
+
+    psMetadataAddS32(cell->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(cell->readouts, NUM_READOUTS);
+    cell->hdu = pmHDUAlloc("cellExtName");
+    for (int i = 0 ; i < NUM_READOUTS ; i++) {
+        cell->readouts->data[i] = psMemDecrRefCounter((psPtr) generateSimpleReadout(cell));
+    }
+
+    // First try to read data from ../dataFiles, then try dataFiles.
+    bool rc = pmConfigFileRead(&cell->hdu->format, "dataFiles/camera0/format0.config", "Camera format 0");
+    if (!rc) {
+        rc = pmConfigFileRead(&cell->hdu->format, "../dataFiles/camera0/format0.config", "Camera format 0");
+        if (!rc) {
+            diag("pmConfigFileRead() was unsuccessful (from generateSimpleCell())");
+	}
+    }
+
+    cell->hdu->images = psArrayAlloc(NUM_HDUS);
+    cell->hdu->masks = psArrayAlloc(NUM_HDUS);
+    cell->hdu->weights = psArrayAlloc(NUM_HDUS);
+    for (int k = 0 ; k < NUM_HDUS ; k++) {
+        cell->hdu->images->data[k]  = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        cell->hdu->masks->data[k]   = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        cell->hdu->weights->data[k] = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(cell->hdu->images->data[k], (float) (BASE_IMAGE+k));
+        psImageInit(cell->hdu->masks->data[k], (psU8) (BASE_MASK+k));
+        psImageInit(cell->hdu->weights->data[k], (float) (BASE_WEIGHT+k));
+    }
+    cell->hdu->blankPHU = true;
+
+    //XXX: Should the region be set some other way?  Like through the various config files?
+    psRegion *region = psRegionAlloc(0.0, 0.0, 0.0, 0.0);
+    // You shouldn't have to remove the key from the metadata.  Find out how to simply change the key value.
+    psMetadataRemoveKey(cell->concepts, "CELL.TRIMSEC");
+    psMetadataAddPtr(cell->concepts, PS_LIST_TAIL|PS_META_REPLACE, "CELL.TRIMSEC", PS_DATA_REGION, "I am a region", region);
+    psFree(region);
+    return(cell);
+}
+
+/******************************************************************************
+generateSimpleChip(): This function generates a pmChip data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmChip *generateSimpleChip(pmFPA *fpa)
+{
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    chip->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    chip->fromFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    psMetadataAddS32(chip->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(chip->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+
+    chip->hdu = pmHDUAlloc("chipExtName");
+    bool rc = pmConfigFileRead(&chip->hdu->format, "dataFiles/camera0/format0.config", "Camera format 0");
+    if (!rc) {
+        rc = pmConfigFileRead(&chip->hdu->format, "../dataFiles/camera0/format0.config", "Camera format 0");
+        if (!rc) {
+            diag("pmConfigFileRead() was unsuccessful (from generateSimpleChip())");
+        }
+    }
+    chip->hdu->blankPHU = true;
+
+    psArrayRealloc(chip->cells, NUM_CELLS);
+    for (int i = 0 ; i < NUM_CELLS ; i++) {
+        chip->cells->data[i] = psMemDecrRefCounter((psPtr) generateSimpleCell(chip));
+    }
+
+    return(chip);
+}
+
+/******************************************************************************
+generateSimpleFPA(): This function generates a pmFPA data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmFPA* generateSimpleFPA(psMetadata *camera)
+{
+    pmFPA* fpa = pmFPAAlloc(camera);
+    fpa->fromTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toSky = psProjectionAlloc(0.0,0.0,10.0,10.0,PS_PROJ_TAN);
+    psMetadataAddS32(fpa->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    if (camera != NULL) {
+        psMetadataAddS32((psMetadata *) fpa->camera, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    }
+    psMetadataAddS32(fpa->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    fpa->hdu = pmHDUAlloc("fpaExtName");
+    bool rc = pmConfigFileRead(&fpa->hdu->format, "dataFiles/camera0/format0.config", "Camera format 0");
+    if (!rc) {
+        rc = pmConfigFileRead(&fpa->hdu->format, "../dataFiles/camera0/format0.config", "Camera format 0");
+        if (!rc) {
+            diag("pmConfigFileRead() was unsuccessful (from generateSimpleFPA())");
+        }
+    }
+    fpa->hdu->blankPHU = true;
+
+    psArrayRealloc(fpa->chips, NUM_CHIPS);
+    for (int i = 0 ; i < NUM_CHIPS ; i++) {
+        fpa->chips->data[i] = psMemDecrRefCounter((psPtr) generateSimpleChip(fpa));
+    }
+    pmConceptsBlankFPA(fpa);
+    return(fpa);
+}
+
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(174);
+
+
+    // ----------------------------------------------------------------------
+    // pmFPAViewAlloc() tests
+    {
+        psMemId id = psMemGetId();
+        pmFPAview *tmpView = pmFPAviewAlloc(32);
+        ok(tmpView != NULL && psMemCheckFPAview(tmpView), "pmFPAviewAlloc() returned non-NULL");
+        ok(tmpView->nRows == 32, "pmFPAviewAlloc() set ->nRows properly");
+        psFree(tmpView);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmFPAViewReset() tests: NULL input
+    {
+        psMemId id = psMemGetId();
+        ok(!pmFPAviewReset(NULL), "pmFPAviewReset() returned FALSE with NULL input");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmFPAViewReset() tests: acceptable input
+    {
+        psMemId id = psMemGetId();
+        pmFPAview *tmpView = pmFPAviewAlloc(32);
+        ok(tmpView != NULL, "pmFPAviewAlloc() returned non-NULL");
+        tmpView->chip = 16;
+        tmpView->cell = 16;
+        tmpView->readout = 16;
+        tmpView->iRows = 16;
+        ok(pmFPAviewReset(tmpView), "pmFPAviewReset() returned TRUE");
+        ok(tmpView->chip == -1, "pmFPAviewReset() set tmpView->chip to -1");
+        ok(tmpView->cell == -1, "pmFPAviewReset() set tmpView->cell to -1");
+        ok(tmpView->readout == -1, "pmFPAviewReset() set tmpView->readout to 0");
+        ok(tmpView->iRows == 0, "pmFPAviewReset() set tmpView->iRows to 0");
+        psFree(tmpView);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmFPAViewForLevel() tests: NULL input
+    // pmFPAview *pmFPAviewForLevel(pmFPALevel level, const pmFPAview *input)
+
+    {
+        psMemId id = psMemGetId();
+        ok(!pmFPAviewForLevel(PM_FPA_LEVEL_FPA, NULL), "pmFPAviewForLevel() returned FALSE with NULL input");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmFPAViewForLevel() tests: acceptable input
+    {
+        psMemId id = psMemGetId();
+        pmFPAview *tmpView = pmFPAviewAlloc(32);
+        ok(tmpView != NULL, "pmFPAviewAlloc() returned non-NULL");
+        tmpView->chip = 16;
+        tmpView->cell = 16;
+        tmpView->readout = 16;
+        tmpView->iRows = 16;
+
+        pmFPAview *newView = pmFPAviewForLevel(PM_FPA_LEVEL_FPA, tmpView);
+        ok(newView, "pmFPAviewReset(PM_FPA_LEVEL_FPA) returned non-NULL");
+        ok(newView->chip == -1, "pmFPAviewReset() set tmpView->chip to -1");
+        psFree(newView);
+
+        newView = pmFPAviewForLevel(PM_FPA_LEVEL_CHIP, tmpView);
+        ok(newView, "pmFPAviewReset(PM_FPA_LEVEL_CHIP) returned non-NULL");
+        ok(newView->cell == -1, "pmFPAviewReset() set tmpView->cell to -1");
+        psFree(newView);
+
+        newView = pmFPAviewForLevel(PM_FPA_LEVEL_CELL, tmpView);
+        ok(newView, "pmFPAviewReset(PM_FPA_LEVEL_CELL) returned non-NULL");
+        ok(newView->readout == -1, "pmFPAviewReset() set tmpView->readout to 0");
+        psFree(newView);
+
+        psFree(tmpView);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmFPAViewLevel() tests: NULL input
+    // pmFPALevel pmFPAviewLevel(const pmFPAview *view)
+    {
+        psMemId id = psMemGetId();
+        ok(!pmFPAviewLevel(NULL), "pmFPAviewLevel() returned NULL with NULL input");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmFPAViewLevel() tests: acceptable input
+    {
+        psMemId id = psMemGetId();
+        pmFPAview *tmpView = pmFPAviewAlloc(32);
+        ok(tmpView != NULL, "pmFPAviewAlloc() returned non-NULL");
+        tmpView->chip = 16;
+        tmpView->cell = 16;
+        tmpView->readout = 16;
+        tmpView->iRows = 16;
+
+        tmpView->chip = -1;
+        ok(PM_FPA_LEVEL_FPA == pmFPAviewLevel(tmpView), "pmFPAviewReset() returned PM_FPA_LEVEL_FPA");
+        tmpView->chip = 16;
+
+        tmpView->cell = -1;
+        ok(PM_FPA_LEVEL_CHIP == pmFPAviewLevel(tmpView), "pmFPAviewReset() returned PM_FPA_LEVEL_CHIP");
+        tmpView->cell = 16;
+
+        tmpView->readout = -1;
+        ok(PM_FPA_LEVEL_CELL == pmFPAviewLevel(tmpView), "pmFPAviewReset() returned PM_FPA_LEVEL_CELL");
+        tmpView->readout = 16;
+
+        psFree(tmpView);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+    // ----------------------------------------------------------------------
+    // pmFPAviewThisChip() tests: NULL view input
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        ok(NULL == pmFPAviewThisChip(NULL, fpa), "pmFPAviewThisChip(NULL, fpa) returned NULL");
+
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmFPAviewThisChip() tests: NULL pmFPA input
+    {
+        psMemId id = psMemGetId();
+        pmFPAview *tmpView = pmFPAviewAlloc(32);
+        ok(NULL == pmFPAviewThisChip(tmpView, NULL), "pmFPAviewThisChip(pmFPAView, NULL) returned NULL");
+        psFree(tmpView);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmFPAviewThisChip() tests: NULL fpa->chips input
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        psArray *chips = fpa->chips;
+        fpa->chips = NULL;
+        pmFPAview *tmpView = pmFPAviewAlloc(32);
+        ok(NULL == pmFPAviewThisChip(tmpView, fpa), "pmFPAviewThisChip(pmFPAView, fpa) returned NULL view input");
+        fpa->chips = chips;
+        psFree(fpa);
+        psFree(camera);
+        psFree(tmpView);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // pmFPAviewThisChip() tests: acceptable input
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        pmFPAview *tmpView = pmFPAviewAlloc(32);
+
+        // Test with tmpView->chip too low
+        tmpView->chip = -1;
+        ok(NULL == pmFPAviewThisChip(tmpView, fpa), "pmFPAviewThisChip(pmFPAView, fpa) returned NULL with pmFPAView->chip == -1");
+
+        // Test with tmpView->chip too high
+        tmpView->chip = fpa->chips->n;
+        ok(NULL == pmFPAviewThisChip(tmpView, fpa), "pmFPAviewThisChip(pmFPAView, fpa) returned NULL with pmFPAView->chip larger than the number of chips");
+    
+        // Test with various tmpView->chip 
+        bool errorFlag = false;
+        for (int i = 0 ; i < NUM_CHIPS ; i++) {
+            tmpView->chip = i;
+            pmChip *tmpChip = pmFPAviewThisChip(tmpView, fpa);
+            if (!tmpChip || tmpChip != fpa->chips->data[i]) {
+                diag("ERROR: pmFPAviewThisChip() returned the incorrect chip (%d)", i);
+                errorFlag = true;
+	    }
+	}
+        ok(!errorFlag, "pmFPAviewThisChip() returned the correct pmChip for all tests");
+        psFree(fpa);
+        psFree(camera);
+        psFree(tmpView);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+    // ----------------------------------------------------------------------
+    // pmFPAviewNextChip() tests: NULL view input
+    // pmChip *pmFPAviewNextChip(pmFPAview *view, const pmFPA *fpa, int nStep)
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        ok(NULL == pmFPAviewNextChip(NULL, fpa, 0), "pmFPAviewNextChip(NULL, fpa, 0) returned NULL");
+
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmFPAviewNextChip() tests: NULL pmFPA input
+    {
+        psMemId id = psMemGetId();
+        pmFPAview *tmpView = pmFPAviewAlloc(32);
+        ok(NULL == pmFPAviewNextChip(tmpView, NULL, 0), "pmFPAviewNextChip(pmFPAView, NULL, 0) returned NULL");
+        psFree(tmpView);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmFPAviewNextChip() tests: acceptable input
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        pmFPAview *tmpView = pmFPAviewAlloc(32);
+
+        // Test with tmpView->chip too low
+        // XXX: Source code is wrong: must guard against input tmpView->chip = -1
+        tmpView->chip = -1;
+        ok(fpa->chips->data[0] == pmFPAviewNextChip(tmpView, fpa, 1), "pmFPAviewNextChip(pmFPAView, fpa, 1) returned NULL with pmFPAView->chip == -1");
+
+        // Test with tmpView->chip too high
+        tmpView->chip = fpa->chips->n;
+        ok(NULL == pmFPAviewNextChip(tmpView, fpa, 0), "pmFPAviewNextChip(pmFPAView, fpa, 0) returned NULL with pmFPAView->chip larger than the number of chips");
+    
+        // Test with various tmpView->chip, step = 0
+        bool errorFlag = false;
+        for (int i = 0 ; i < NUM_CHIPS ; i++) {
+            tmpView->chip = i;
+            pmChip *tmpChip = pmFPAviewNextChip(tmpView, fpa, 0);
+            if (!tmpChip || tmpChip != fpa->chips->data[i]) {
+                diag("ERROR: pmFPAviewNextChip() returned the incorrect chip (%d)", i);
+                errorFlag = true;
+	    }
+	}
+        ok(!errorFlag, "pmFPAviewNextChip() returned the correct pmChip for all tests (step = 0)");
+
+        // Test with various tmpView->chip, step = 1
+        errorFlag = false;
+        for (int i = 0 ; i < NUM_CHIPS-1 ; i++) {
+            tmpView->chip = i;
+            pmChip *tmpChip = pmFPAviewNextChip(tmpView, fpa, 1);
+            if (!tmpChip || tmpChip != fpa->chips->data[i+1]) {
+                diag("ERROR: pmFPAviewNextChip() returned the incorrect chip (%d)", i);
+                errorFlag = true;
+	    }
+	}
+        ok(!errorFlag, "pmFPAviewNextChip() returned the correct pmChip for all tests (step = 1)");
+
+        psFree(fpa);
+        psFree(camera);
+        psFree(tmpView);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmFPAviewThisCell() tests: NULL view input
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        ok(NULL == pmFPAviewThisCell(NULL, fpa), "pmFPAviewThisCell(NULL, fpa) returned NULL");
+
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmFPAviewThisCell() tests: NULL pmFPA input
+    {
+        psMemId id = psMemGetId();
+        pmFPAview *tmpView = pmFPAviewAlloc(32);
+        ok(NULL == pmFPAviewThisCell(tmpView, NULL), "pmFPAviewThisCell(pmFPAView, NULL) returned NULL");
+        psFree(tmpView);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmFPAviewThisCell() tests: NULL fpa->chips input
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        psArray *chips = fpa->chips;
+        fpa->chips = NULL;
+        pmFPAview *tmpView = pmFPAviewAlloc(32);
+        ok(NULL == pmFPAviewThisCell(tmpView, fpa), "pmFPAviewThisCell(pmFPAView, fpa) returned NULL view input");
+        fpa->chips = chips;
+        psFree(fpa);
+        psFree(camera);
+        psFree(tmpView);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // pmFPAviewThisCell() tests: acceptable input
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        pmFPAview *tmpView = pmFPAviewAlloc(32);
+
+        // Test with tmpView->chip too low
+        tmpView->chip = -1;
+        ok(NULL == pmFPAviewThisCell(tmpView, fpa), "pmFPAviewThisCell(pmFPAView, fpa) returned NULL with pmFPAView->chip == -1");
+
+        // Test with tmpView->chip too high
+        tmpView->chip = fpa->chips->n;
+        ok(NULL == pmFPAviewThisCell(tmpView, fpa), "pmFPAviewThisCell(pmFPAView, fpa) returned NULL with pmFPAView->chip larger than the number of chips");
+    
+        // Test with various tmpView->chip 
+        bool errorFlag = false;
+        for (int i = 0 ; i < NUM_CHIPS ; i++) {
+            pmChip *chip = fpa->chips->data[i];
+            for (int j = 0 ; j < NUM_CELLS ; j++) {
+                tmpView->chip = i;
+                tmpView->cell = j;
+                pmCell *tmpCell = pmFPAviewThisCell(tmpView, fpa);
+                if (!tmpCell || tmpCell != chip->cells->data[j]) {
+                    diag("ERROR: pmFPAviewThisCell() returned the incorrect chip/cell (%d, %d)", i, j);
+                    errorFlag = true;
+		}
+
+    	    }
+	}
+        ok(!errorFlag, "pmFPAviewThisCell() returned the correct pmCell for all tests");
+        psFree(fpa);
+        psFree(camera);
+        psFree(tmpView);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmFPAviewNextCell() tests: NULL view input
+    // pmChip *pmFPAviewNextCell(pmFPAview *view, const pmFPA *fpa, int nStep)
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        ok(NULL == pmFPAviewNextCell(NULL, fpa, 0), "pmFPAviewNextCell(NULL, fpa, 0) returned NULL");
+
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmFPAviewNextCell() tests: NULL pmFPA input
+    {
+        psMemId id = psMemGetId();
+        pmFPAview *tmpView = pmFPAviewAlloc(32);
+        ok(NULL == pmFPAviewNextCell(tmpView, NULL, 0), "pmFPAviewNextCell(pmFPAView, NULL, 0) returned NULL");
+        psFree(tmpView);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmFPAviewNextCell() tests: acceptable input
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        pmFPAview *tmpView = pmFPAviewAlloc(32);
+
+        // Test with tmpView->chip too low
+        tmpView->chip = -1;
+        ok(NULL == pmFPAviewNextCell(tmpView, fpa, 0), "pmFPAviewNextCell(pmFPAView, fpa) returned NULL with pmFPAView->chip == -1");
+
+        // Test with tmpView->chip too high
+        tmpView->chip = fpa->chips->n;
+        ok(NULL == pmFPAviewNextCell(tmpView, fpa, 0), "pmFPAviewNextCell(pmFPAView, fpa) returned NULL with pmFPAView->chip larger than the number of chips");
+    
+        // Test with various tmpView->chip (step = 0)
+        bool errorFlag = false;
+        for (int i = 0 ; i < NUM_CHIPS ; i++) {
+            pmChip *chip = fpa->chips->data[i];
+            for (int j = 0 ; j < NUM_CELLS ; j++) {
+                tmpView->chip = i;
+                tmpView->cell = j;
+                pmCell *tmpCell = pmFPAviewNextCell(tmpView, fpa, 0);
+                if (!tmpCell || tmpCell != chip->cells->data[j]) {
+                    diag("ERROR: pmFPAviewNextCell() returned the incorrect chip/cell (%d, %d)", i, j);
+                    errorFlag = true;
+		}
+
+    	    }
+	}
+        ok(!errorFlag, "pmFPAviewNextCell() returned the correct pmCell for all tests (step = 0)");
+
+        // Test with various tmpView->chip (step = 1)
+        errorFlag = false;
+        for (int i = 0 ; i < NUM_CHIPS ; i++) {
+            pmChip *chip = fpa->chips->data[i];
+            for (int j = 0 ; j < NUM_CELLS-1 ; j++) {
+                tmpView->chip = i;
+                tmpView->cell = j;
+                pmCell *tmpCell = pmFPAviewNextCell(tmpView, fpa, 1);
+                if (!tmpCell || tmpCell != chip->cells->data[j+1]) {
+                    diag("ERROR: pmFPAviewNextCell() returned the incorrect chip/cell (%d, %d)", i, j);
+                    errorFlag = true;
+		}
+
+    	    }
+	}
+        ok(!errorFlag, "pmFPAviewNextCell() returned the correct pmCell for all tests (step = 1)");
+
+        psFree(fpa);
+        psFree(camera);
+        psFree(tmpView);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+    // ----------------------------------------------------------------------
+    // pmFPAviewThisReadout() tests: NULL view input
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        ok(NULL == pmFPAviewThisReadout(NULL, fpa), "pmFPAviewThisReadout(NULL, fpa) returned NULL");
+
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmFPAviewThisReadout() tests: NULL pmFPA input
+    {
+        psMemId id = psMemGetId();
+        pmFPAview *tmpView = pmFPAviewAlloc(32);
+        ok(NULL == pmFPAviewThisReadout(tmpView, NULL), "pmFPAviewThisReadout(pmFPAView, NULL) returned NULL");
+        psFree(tmpView);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmFPAviewThisReadout() tests: NULL fpa->chips input
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        psArray *chips = fpa->chips;
+        fpa->chips = NULL;
+        pmFPAview *tmpView = pmFPAviewAlloc(32);
+        ok(NULL == pmFPAviewThisReadout(tmpView, fpa), "pmFPAviewThisReadout(pmFPAView, fpa) returned NULL view input");
+        fpa->chips = chips;
+        psFree(fpa);
+        psFree(camera);
+        psFree(tmpView);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // pmFPAviewThisReadout() tests: acceptable input
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        pmFPAview *tmpView = pmFPAviewAlloc(32);
+
+        // Test with tmpView->chip too low
+        tmpView->chip = -1;
+        ok(NULL == pmFPAviewThisReadout(tmpView, fpa), "pmFPAviewThisReadout(pmFPAView, fpa) returned NULL with pmFPAView->chip == -1");
+
+        // Test with tmpView->chip too high
+        tmpView->chip = fpa->chips->n;
+        ok(NULL == pmFPAviewThisReadout(tmpView, fpa), "pmFPAviewThisReadout(pmFPAView, fpa) returned NULL with pmFPAView->chip larger than the number of chips");
+    
+        // Test with various tmpView->chip 
+        bool errorFlag = false;
+        for (int i = 0 ; i < NUM_CHIPS ; i++) {
+            pmChip *chip = fpa->chips->data[i];
+            for (int j = 0 ; j < NUM_CELLS ; j++) {
+                pmCell *cell = chip->cells->data[j];
+                for (int k = 0 ; k < NUM_READOUTS ; k++) {
+                    tmpView->chip = i;
+                    tmpView->cell = j;
+                    tmpView->readout = k;
+                    pmReadout *tmpReadout = pmFPAviewThisReadout(tmpView, fpa);
+                    if (!tmpReadout || tmpReadout != cell->readouts->data[k]) {
+                        diag("ERROR: pmFPAviewThisReadout() returned the incorrect chip/cell/readout (%d, %d, %d)", i, j, k);
+                        errorFlag = true;
+		    }
+		}
+    	    }
+	}
+        ok(!errorFlag, "pmFPAviewThisReadout() returned the correct pmCell for all tests");
+
+        psFree(fpa);
+        psFree(camera);
+        psFree(tmpView);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmFPAviewNextReadout() tests: NULL view input
+    // pmChip *pmFPAviewNextReadout(pmFPAview *view, const pmFPA *fpa, int nStep)
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        ok(NULL == pmFPAviewNextReadout(NULL, fpa, 0), "pmFPAviewNextReadout(NULL, fpa, 0) returned NULL");
+
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmFPAviewNextReadout() tests: NULL pmFPA input
+    {
+        psMemId id = psMemGetId();
+        pmFPAview *tmpView = pmFPAviewAlloc(32);
+        ok(NULL == pmFPAviewNextReadout(tmpView, NULL, 0), "pmFPAviewNextReadout(pmFPAView, NULL, 0) returned NULL");
+        psFree(tmpView);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // pmFPAviewNextReadout() tests: acceptable input
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        pmFPAview *tmpView = pmFPAviewAlloc(32);
+
+        // Test with tmpView->chip too low
+        tmpView->chip = -1;
+        ok(NULL == pmFPAviewNextReadout(tmpView, fpa, 0), "pmFPAviewNextReadout(pmFPAView, fpa) returned NULL with pmFPAView->chip == -1");
+
+        // Test with tmpView->chip too high
+        tmpView->chip = fpa->chips->n;
+        ok(NULL == pmFPAviewNextReadout(tmpView, fpa, 0), "pmFPAviewNextReadout(pmFPAView, fpa) returned NULL with pmFPAView->chip larger than the number of chips");
+    
+        // Test with various tmpView->chip (step = 0)
+        bool errorFlag = false;
+        for (int i = 0 ; i < NUM_CHIPS ; i++) {
+            pmChip *chip = fpa->chips->data[i];
+            for (int j = 0 ; j < NUM_CELLS ; j++) {
+                pmCell *cell = chip->cells->data[j];
+                for (int k = 0 ; k < NUM_READOUTS ; k++) {
+                    tmpView->chip = i;
+                    tmpView->cell = j;
+                    tmpView->readout = k;
+                    pmReadout *tmpReadout = pmFPAviewNextReadout(tmpView, fpa, 0);
+                    if (!tmpReadout || tmpReadout != cell->readouts->data[k]) {
+                        diag("ERROR: pmFPAviewNextReadout() returned the incorrect chip/cell/readout (%d, %d, %d)", i, j, k);
+                        errorFlag = true;
+		    }
+		}
+    	    }
+	}
+        ok(!errorFlag, "pmFPAviewNextReadout() returned the correct pmCell for all tests (step = 0)");
+
+        // Test with various tmpView->chip (step = 1)
+        errorFlag = false;
+        for (int i = 0 ; i < NUM_CHIPS ; i++) {
+            pmChip *chip = fpa->chips->data[i];
+            for (int j = 0 ; j < NUM_CELLS ; j++) {
+                pmCell *cell = chip->cells->data[j];
+                for (int k = 0 ; k < NUM_READOUTS-1 ; k++) {
+                    tmpView->chip = i;
+                    tmpView->cell = j;
+                    tmpView->readout = k;
+                    pmReadout *tmpReadout = pmFPAviewNextReadout(tmpView, fpa, 1);
+                    if (!tmpReadout || tmpReadout != cell->readouts->data[k+1]) {
+                        diag("ERROR: pmFPAviewNextReadout() returned the incorrect chip/cell/readout (%d, %d, %d)", i, j, k);
+                        errorFlag = true;
+		    }
+		}
+    	    }
+	}
+        ok(!errorFlag, "pmFPAviewNextReadout() returned the correct pmCell for all tests (step = 1)");
+
+        psFree(fpa);
+        psFree(camera);
+        psFree(tmpView);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmFPAviewThisHDU() tests: NULL view input
+    // pmHDU *pmFPAviewThisHDU(const pmFPAview *view, const pmFPA *fpa)
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        ok(NULL == pmFPAviewThisHDU(NULL, fpa), "pmFPAviewThisHDU(NULL, fpa) returned NULL");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // pmFPAviewThisHDU() tests: NULL pmFPA input
+    // pmHDU *pmFPAviewThisHDU(const pmFPAview *view, const pmFPA *fpa)
+    {
+        psMemId id = psMemGetId();
+        pmFPAview *tmpView = pmFPAviewAlloc(32);
+        ok(NULL == pmFPAviewThisHDU(tmpView, NULL), "pmFPAviewThisHDU(pmFPAView, NULL) returned NULL");
+        psFree(tmpView);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmFPAviewThisHDU() tests: acceptable input
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        pmFPAview *tmpView = pmFPAviewAlloc(32);
+
+        tmpView->chip = -1;
+        pmHDU *hdu = pmFPAviewThisHDU(tmpView, fpa);
+        ok(hdu, "pmFPAviewThisHDU() returned non-NULL with tmpView->chip = -1");
+        ok(hdu == pmHDUFromFPA(fpa), "pmFPAviewThisHDU() returned the correct HDU");
+        tmpView->chip = NUM_CHIPS/2;
+
+        tmpView->cell = -1;
+        hdu = pmFPAviewThisHDU(tmpView, fpa);
+        ok(hdu, "pmFPAviewThisHDU() returned non-NULL with tmpView->cell = -1");
+        ok(hdu == pmHDUFromChip(pmFPAviewThisChip(tmpView, fpa)), "pmFPAviewThisHDU() returned the correct HDU");
+        tmpView->cell = NUM_CELLS/2;
+
+        tmpView->readout = -1;
+        hdu = pmFPAviewThisHDU(tmpView, fpa);
+        ok(hdu, "pmFPAviewThisHDU() returned non-NULL with tmpView->readout = -1");
+        ok(hdu == pmHDUFromCell(pmFPAviewThisCell(tmpView, fpa)), "pmFPAviewThisHDU() returned the correct HDU");
+        tmpView->readout = NUM_READOUTS/2;
+
+        hdu = pmFPAviewThisHDU(tmpView, fpa);
+        ok(hdu, "pmFPAviewThisHDU() returned non-NULL");
+        ok(hdu == pmHDUFromReadout(pmFPAviewThisReadout(tmpView, fpa)), "pmFPAviewThisHDU() returned the correct HDU");
+
+        psFree(fpa);
+        psFree(camera);
+        psFree(tmpView);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmFPAviewThisPHU() tests: NULL view input
+    // pmHDU *pmFPAviewThisPHU(const pmFPAview *view, const pmFPA *fpa)
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        ok(NULL == pmFPAviewThisPHU(NULL, fpa), "pmFPAviewThisPHU(NULL, fpa) returned NULL");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // pmFPAviewThisPHU() tests: NULL pmFPA input
+    // pmHDU *pmFPAviewThisPHU(const pmFPAview *view, const pmFPA *fpa)
+    {
+        psMemId id = psMemGetId();
+        pmFPAview *tmpView = pmFPAviewAlloc(32);
+        ok(NULL == pmFPAviewThisPHU(tmpView, NULL), "pmFPAviewThisPHU(pmFPAView, NULL) returned NULL");
+        psFree(tmpView);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmFPAviewThisPHU() tests: acceptable input
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        pmFPAview *tmpView = pmFPAviewAlloc(32);
+
+        tmpView->chip = -1;
+        pmHDU *hdu = pmFPAviewThisPHU(tmpView, fpa);
+        ok(hdu, "pmFPAviewThisPHU() returned non-NULL with tmpView->chip = -1");
+        ok(hdu == pmHDUFromFPA(fpa), "pmFPAviewThisPHU() returned the correct HDU");
+        tmpView->chip = NUM_CHIPS/2;
+
+        tmpView->cell = -1;
+        hdu = pmFPAviewThisPHU(tmpView, fpa);
+        ok(hdu, "pmFPAviewThisPHU() returned non-NULL with tmpView->cell = -1");
+        ok(hdu == pmHDUFromChip(pmFPAviewThisChip(tmpView, fpa)), "pmFPAviewThisPHU() returned the correct HDU");
+        tmpView->cell = NUM_CELLS/2;
+
+        tmpView->readout = -1;
+        hdu = pmFPAviewThisPHU(tmpView, fpa);
+        ok(hdu, "pmFPAviewThisPHU() returned non-NULL with tmpView->readout = -1");
+        ok(hdu == pmHDUFromCell(pmFPAviewThisCell(tmpView, fpa)), "pmFPAviewThisPHU() returned the correct HDU");
+        tmpView->readout = NUM_READOUTS/2;
+
+        hdu = pmFPAviewThisPHU(tmpView, fpa);
+        ok(hdu == NULL, "pmFPAviewThisPHU() returned NULL");
+
+        psFree(fpa);
+        psFree(camera);
+        psFree(tmpView);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmFPAviewGenerate() tests: NULL pmFPA input
+    {
+        psMemId id = psMemGetId();
+        int chipID = NUM_CHIPS/2;
+        int cellID = NUM_CELLS/2;
+        int readoutID = NUM_READOUTS/2;
+        pmFPA* fpa = generateSimpleFPA(NULL);
+        pmChip *chip = fpa->chips->data[chipID];
+        pmCell *cell = chip->cells->data[cellID];
+        pmReadout *readout = cell->readouts->data[readoutID];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        ok(readout != NULL, "Allocated a pmReadout successfully");
+        pmFPAview *view = pmFPAviewGenerate(NULL, chip, cell, readout);
+        ok(view == NULL, "pmFPAviewGenerate(NULL, chip, cell, readout) returned NULL");
+        psFree(fpa);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmFPAviewGenerate() tests: acceptable input parameters
+    // XX: Add more input combinations.
+    {
+        psMemId id = psMemGetId();
+        int chipID = NUM_CHIPS/2;
+        int cellID = NUM_CELLS/2;
+        int readoutID = NUM_READOUTS/2;
+        pmFPA* fpa = generateSimpleFPA(NULL);
+        pmChip *chip = fpa->chips->data[chipID];
+        pmCell *cell = chip->cells->data[cellID];
+        pmReadout *readout = cell->readouts->data[readoutID];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        ok(readout != NULL, "Allocated a pmReadout successfully");
+        pmFPAview *view = pmFPAviewGenerate(fpa, chip, cell, readout);
+        ok(view != NULL && psMemCheckFPAview(view), "pmFPAviewGenerate() returned an pmFPAviw with acceptable input parameters");
+        ok(view->chip == chipID, "pmFPAviewGenerate() set pmFPAview->chip correctly");
+        ok(view->cell == cellID, "pmFPAviewGenerate() set pmFPAview->cell correctly");
+        ok(view->readout == readoutID, "pmFPAviewGenerate() set pmFPAview->readout correctly");
+        psFree(view);
+        psFree(fpa);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmHDU.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmHDU.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmHDU.c	(revision 22157)
@@ -0,0 +1,571 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS:
+    All functions are tested.
+*/
+
+#define NUM_HDUS 8
+char *fitsFilename = "tmp.fits";
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", 0);
+    plan_tests(159);
+
+
+    // ----------------------------------------------------------------------
+    // pmHDUAlloc() tests
+    // Test pmHDUAlloc() with a NULL extname
+    {
+        psMemId id = psMemGetId();
+        pmHDU *hdu = pmHDUAlloc(NULL);
+        ok(hdu, "pmHDUAlloc(NULL) returned non-NULL");
+        skip_start(!hdu, 7, "Skipping tests because pmHDUAlloc(NULL) returned NULL");
+        ok(hdu->blankPHU == true, "pmHDUAlloc(NULL) set hdu->blankPHU correctly");
+        ok(hdu->extname == NULL, "pmHDUAlloc(NULL) set hdu->extname correctly");
+        ok(hdu->format == NULL, "pmHDUAlloc(NULL) set hdu->format correctly");
+        ok(hdu->header == NULL, "pmHDUAlloc(NULL) set hdu->header correctly");
+        ok(hdu->images == NULL, "pmHDUAlloc(NULL) set hdu->images correctly");
+        ok(hdu->weights == NULL, "pmHDUAlloc(NULL) set hdu->weights correctly");
+        ok(hdu->masks == NULL, "pmHDUAlloc(NULL) set hdu->masks correctly");
+        psFree(hdu);
+        skip_end();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmHDUAlloc() with a non-NULL extname
+    {
+        psMemId id = psMemGetId();
+        pmHDU *hdu = pmHDUAlloc("ext-0");
+        ok(hdu, "pmHDUAlloc(extname) returned non-NULL");
+        skip_start(!hdu, 7, "Skipping tests because pmHDUAlloc(extname) returned NULL");
+        ok(hdu->blankPHU == false, "pmHDUAlloc(extname) set hdu->blankPHU correctly");
+        ok(0 == strcmp(hdu->extname, "ext-0"), "pmHDUAlloc(extname) set hdu->extname correctly");
+        ok(hdu->format == NULL, "pmHDUAlloc(extname) set hdu->format correctly");
+        ok(hdu->header == NULL, "pmHDUAlloc(extname) set hdu->header correctly");
+        ok(hdu->images == NULL, "pmHDUAlloc(extname) set hdu->images correctly");
+        ok(hdu->weights == NULL, "pmHDUAlloc(extname) set hdu->weights correctly");
+        ok(hdu->masks == NULL, "pmHDUAlloc(extname) set hdu->masks correctly");
+        psFree(hdu);
+        skip_end();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmHDUWrite(), pmHDURead tests
+    // Test pmHDUWrite() with NULL pmHDU input argument
+    {
+        psMemId id = psMemGetId();
+        // Create simple FITS file
+        psFits* fitsFile = psFitsOpen(".tmp00", "w");
+        ok(fitsFile != NULL, "psFitsOpen() was successful");
+        psMetadata* header = psMetadataAlloc();
+        psImage* image = psImageAlloc(16, 16, PS_TYPE_F32);
+        ok(psFitsWriteImage(fitsFile, header, image, 0, "extname"), "psFitsWriteImage() successful");
+        psFree(header);
+        psFree(image);
+        bool rc = pmHDUWrite(NULL, fitsFile);
+        ok(rc == false, "pmHDUWrite() returned FALSE with NULL psHDU as input");
+        psFitsClose(fitsFile);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmHDUWrite() with NULL psFits input argument
+    {
+        psMemId id = psMemGetId();
+        pmHDU *hdu = pmHDUAlloc("extname");
+        hdu->header = psMetadataAlloc();
+        psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYINT", PS_DATA_S32, "psS32 Item", 0);
+        bool rc = pmConfigFileRead(&hdu->format, "../dataFiles/camera0/format0.config", "Camera 0 Config Format");
+        if (!rc) {
+            rc = pmConfigFileRead(&hdu->format, "dataFiles/camera0/format0.config", "Camera 0 Config Format");
+	}
+        ok(rc == true, "pmConfigFileRead() was successful");
+        rc = pmHDUWrite(hdu, NULL);
+        ok(rc == false, "pmHDUWrite() returned FALSE with NULL psFits file as input");
+        psFree(hdu);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmHDURead() with NULL pmHDU input argument
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen("../dataFiles/sampleFitsFile.fits", "r");
+        if (!fitsFile) {
+            fitsFile = psFitsOpen("dataFiles/sampleFitsFile.fits", "r");
+	}
+        ok(fitsFile != NULL, "psFitsOpen() was successful");
+        bool rc = pmHDURead(NULL, fitsFile);
+        ok(rc == false, "pmHDURead() returned FALSE with NULL psHDU as input");
+        psFitsClose(fitsFile);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmHDURead() with NULL pmHDU input argument
+    {
+        psMemId id = psMemGetId();
+        pmHDU *hdu = pmHDUAlloc("extname");
+        hdu->header = psMetadataAlloc();
+        bool rc = pmHDURead(hdu, NULL);
+        ok(rc == false, "pmHDURead() returned FALSE with NULL psFits file as input");
+        psFree(hdu);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // 1. Create a simple FITS file
+    // 2. Create a simple pmHDU header
+    // 3. Use pmHDUWrite() to write that header to the FITS file
+    // 4. Close the FITS file
+    // 5. Read that fits file from disk
+    // 6. Create another pmHDU
+    // 7. Call pmHDURead()which will read the FITS data into hdu->images
+    // 8. Verify that hdu->images was set correctly.
+    // 
+    // XXX: Add code to delete .tmp00
+    // XXX: Should we try with multiple images in the psArray hdu->images?
+    #define BASE 20
+    {
+        psMemId id = psMemGetId();
+        // 1. Create a simple FITS file
+        psFits* fitsFileW = psFitsOpen(".tmp00", "w");
+        ok(fitsFileW != NULL, "psFitsOpen() was successful");
+        // 2. Create a simple pmHDU header
+        //    Allocate and format hdu->header
+        //    Set hdu->images
+        pmHDU *hdu = pmHDUAlloc("extname");
+        bool rc = pmConfigFileRead(&hdu->format, "../dataFiles/camera0/format0.config", "Camera format 0");
+        if (!rc) {
+            rc = pmConfigFileRead(&hdu->format, "dataFiles/camera0/format0.config", "Camera format 0");
+	}
+        ok(rc, "pmConfigFileRead() was successful");
+        hdu->images = psArrayAlloc(NUM_HDUS);
+        for (int k = 0 ; k < NUM_HDUS ; k++) {
+            hdu->images->data[k] = psImageAlloc(4, 4, PS_TYPE_F32);
+            psImageInit(hdu->images->data[k], (float) (BASE+k));
+	}
+        // 3. Use pmHDUWrite() to write that header to the FITS file
+        rc = pmHDUWrite(hdu, fitsFileW);
+        ok(rc == true, "pmHDUWrite() returned TRUE");
+        // 4. Close the FITS file, free memory
+        psFree(hdu);
+        psFitsClose(fitsFileW);
+
+        // Now, try to read that FITS file from disk.
+        // 5. Read that fits file from disk
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        ok(fitsFileR != NULL, "psFitsOpen() was successful");
+        // 6. Create another pmHDU
+        hdu = pmHDUAlloc("extname");
+        hdu->images = NULL;
+        // 7. Call pmHDURead()which will read the FITS data into hdu->images
+        rc = pmHDURead(hdu, fitsFileR);
+        ok(rc == true, "pmHDURead() returned TRUE");
+        ok(hdu->images != NULL, "pmHDURead() created hdu->images");
+        // 8. Verify that hdu->images was set correctly.
+        bool errorFlag = false;
+        for (int k = 0 ; k < NUM_HDUS ; k++) {
+            psImage *img = (psImage *) hdu->images->data[k];
+            for (psS32 i = 0 ; i < img->numRows ; i++) {
+                for (psS32 j = 0 ; j < img->numCols ; j++) {
+                    if (((float) k+BASE) != img->data.F32[i][j]) {
+                        errorFlag = true;
+                        diag("TEST ERROR (image %d): img[%d][%d] is %f, should be %f\n",
+                              k, i, j, img->data.F32[i][j], ((float)k+BASE));
+        	    }
+            	}
+	    }
+	}
+        ok(!errorFlag, "pmHDURead() correctly returned the image data");
+        psFitsClose(fitsFileR);
+        psFree(hdu);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmHDUWriteWeight(), pmHDUReadWeight() tests
+    // Test pmHDUWriteWeight() with NULL pmHDU input argument
+    {
+        psMemId id = psMemGetId();
+        // Create simple FITS file
+        psFits* fitsFile = psFitsOpen(".tmp00", "w");
+        ok(fitsFile != NULL, "psFitsOpen() was successful");
+        psMetadata* header = psMetadataAlloc();
+        psImage* image = psImageAlloc(16, 16, PS_TYPE_F32);
+        ok(psFitsWriteImage(fitsFile, header, image, 0, "extname"), "psFitsWriteImage() successful");
+        psFree(header);
+        psFree(image);
+        bool rc = pmHDUWriteWeight(NULL, fitsFile);
+        ok(rc == false, "pmHDUWriteWeight() returned FALSE with NULL psHDU as input");
+        psFitsClose(fitsFile);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmHDUWriteWeight() with NULL psFits input argument
+    {
+        psMemId id = psMemGetId();
+        pmHDU *hdu = pmHDUAlloc("extname");
+        hdu->header = psMetadataAlloc();
+        psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYINT", PS_DATA_S32, "psS32 Item", 0);
+        bool rc = pmConfigFileRead(&hdu->format, "../dataFiles/camera0/format0.config", "Camera 0 Config Format");
+        if (!rc) {
+            rc = pmConfigFileRead(&hdu->format, "dataFiles/camera0/format0.config", "Camera 0 Config Format");
+	}
+        ok(rc == true, "pmConfigFileRead() was successful");
+        rc = pmHDUWriteWeight(hdu, NULL);
+        ok(rc == false, "pmHDUWriteWeight    () returned FALSE with NULL psFits file as input");
+        psFree(hdu);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmHDUReadWeight() with NULL pmHDU input argument
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen("../dataFiles/sampleFitsFile.fits", "r");
+        if (!fitsFile) {
+            fitsFile = psFitsOpen("dataFiles/sampleFitsFile.fits", "r");
+	}
+        ok(fitsFile != NULL, "psFitsOpen() was successful");
+        bool rc = pmHDUReadWeight(NULL, fitsFile);
+        ok(rc == false, "pmHDUReadWeight() returned FALSE with NULL psHDU as input");
+        psFitsClose(fitsFile);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmHDUReadWeight() with NULL pmHDU input argument
+    {
+        psMemId id = psMemGetId();
+        pmHDU *hdu = pmHDUAlloc("extname");
+        hdu->header = psMetadataAlloc();
+        bool rc = pmHDUReadWeight(hdu, NULL);
+        ok(rc == false, "pmHDUReadWeight    () returned FALSE with NULL psFits file as input");
+        psFree(hdu);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // 1. Create a simple FITS file
+    // 2. Create a simple pmHDU header
+    // 3. Use pmHDUWriteWeight() to write that header to the FITS file
+    // 4. Close the FITS file
+    // 5. Read that fits file from disk
+    // 6. Create another pmHDU
+    // 7. Call pmHDUReadWeight()which will read the FITS data into hdu->images
+    // 8. Verify that hdu->images was set correctly.
+    // 
+    // XXX: Add code to delete .tmp00
+    // XXX: Should we try with multiple images in the psArray hdu->images?
+    #define BASE 20
+    {
+        psMemId id = psMemGetId();
+        // 1. Create a simple FITS file
+        psFits* fitsFileW = psFitsOpen(".tmp00", "w");
+        ok(fitsFileW != NULL, "psFitsOpen() was successful");
+        // 2. Create a simple pmHDU header
+        //    Allocate and format hdu->header
+        //    Set hdu->images
+        pmHDU *hdu = pmHDUAlloc("extname");
+        bool rc = pmConfigFileRead(&hdu->format, "../dataFiles/camera0/format0.config", "Camera format 0");
+        if (!rc) {
+            rc = pmConfigFileRead(&hdu->format, "dataFiles/camera0/format0.config", "Camera format 0");
+	}
+        ok(rc, "pmConfigFileRead() was successful");
+        hdu->weights = psArrayAlloc(NUM_HDUS);
+        for (int k = 0 ; k < NUM_HDUS ; k++) {
+            hdu->weights->data[k] = psImageAlloc(4, 4, PS_TYPE_F32);
+            psImageInit(hdu->weights->data[k], (float) (BASE+k));
+	}
+        // 3. Use pmHDUWriteWeight() to write that header to the FITS file
+        rc = pmHDUWriteWeight(hdu, fitsFileW);
+        ok(rc == true, "pmHDUWriteWeight() returned TRUE");
+        // 4. Close the FITS file, free memory
+        psFree(hdu);
+        psFitsClose(fitsFileW);
+
+        // Now, try to read that FITS file from disk.
+        // 5. Read that fits file from disk
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        ok(fitsFileR != NULL, "psFitsOpen() was successful");
+        // 6. Create another pmHDU
+        hdu = pmHDUAlloc("extname");
+        hdu->weights = NULL;
+        // 7. Call pmHDUReadWeight()which will read the FITS data into hdu->weights
+        rc = pmHDUReadWeight(hdu, fitsFileR);
+        ok(rc == true, "pmHDUReadWeight() returned TRUE");
+        ok(hdu->weights != NULL, "pmHDUReadWeight() created hdu->weights");
+        // 8. Verify that hdu->weights was set correctly.
+        bool errorFlag = false;
+        for (int k = 0 ; k < NUM_HDUS ; k++) {
+            psImage *img = (psImage *) hdu->weights->data[k];
+            for (psS32 i = 0 ; i < img->numRows ; i++) {
+                for (psS32 j = 0 ; j < img->numCols ; j++) {
+                    if (((float) k+BASE) != img->data.F32[i][j]) {
+                        errorFlag = true;
+                        diag("TEST ERROR (image %d): img[%d][%d] is %f, should be %f\n",
+                              k, i, j, img->data.F32[i][j], ((float)k+BASE));
+        	    }
+            	}
+	    }
+	}
+        ok(!errorFlag, "pmHDUReadWeight() correctly returned the image data");
+        psFitsClose(fitsFileR);
+        psFree(hdu);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmHDUWriteMask(), pmHDUReadMask() tests
+    // Test pmHDUWriteMask() with NULL pmHDU input argument
+    {
+        psMemId id = psMemGetId();
+        // Create simple FITS file
+        psFits* fitsFile = psFitsOpen(".tmp00", "w");
+        ok(fitsFile != NULL, "psFitsOpen() was successful");
+        psMetadata* header = psMetadataAlloc();
+        psImage* image = psImageAlloc(16, 16, PS_TYPE_F32);
+        ok(psFitsWriteImage(fitsFile, header, image, 0, "extname"), "psFitsWriteImage() successful");
+        psFree(header);
+        psFree(image);
+        bool rc = pmHDUWriteMask(NULL, fitsFile);
+        ok(rc == false, "pmHDUWriteMask() returned FALSE with NULL psHDU as input");
+        psFitsClose(fitsFile);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmHDUWriteMask() with NULL psFits input argument
+    {
+        psMemId id = psMemGetId();
+        pmHDU *hdu = pmHDUAlloc("extname");
+        hdu->header = psMetadataAlloc();
+        psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYINT", PS_DATA_S32, "psS32 Item", 0);
+        bool rc = pmConfigFileRead(&hdu->format, "dataFiles/camera0/format0.config", "Camera 0 Config Format");
+        if (!rc) {
+             pmConfigFileRead(&hdu->format, "../dataFiles/camera0/format0.config", "Camera 0 Config Format");
+	}
+        ok(rc == true, "pmConfigFileRead() was successful");
+        rc = pmHDUWriteMask(hdu, NULL);
+        ok(rc == false, "pmHDUWriteMask() returned FALSE with NULL psFits file as input");
+        psFree(hdu);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmHDUReadMask() with NULL pmHDU input argument
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen("../dataFiles/sampleFitsFile.fits", "r");
+        if (!fitsFile) {
+            fitsFile = psFitsOpen("dataFiles/sampleFitsFile.fits", "r");
+	}
+        ok(fitsFile != NULL, "psFitsOpen() was successful");
+        bool rc = pmHDUReadMask(NULL, fitsFile);
+        ok(rc == false, "pmHDUReadMask() returned FALSE with NULL psHDU as input");
+        psFitsClose(fitsFile);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmHDUReadMask() with NULL pmHDU input argument
+    {
+        psMemId id = psMemGetId();
+        pmHDU *hdu = pmHDUAlloc("extname");
+        hdu->header = psMetadataAlloc();
+        bool rc = pmHDUReadMask(hdu, NULL);
+        ok(rc == false, "pmHDUReadMask() returned FALSE with NULL psFits file as input");
+        psFree(hdu);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // 1. Create a simple FITS file
+    // 2. Create a simple pmHDU header
+    // 3. Use pmHDUWriteMask() to write that header to the FITS file
+    // 4. Close the FITS file
+    // 5. Read that fits file from disk
+    // 6. Create another pmHDU
+    // 7. Call pmHDUReadMask()which will read the FITS data into hdu->images
+    // 8. Verify that hdu->images was set correctly.
+    // 
+    // XXX: Add code to delete .tmp00
+    // XXX: Should we try with multiple images in the psArray hdu->images?
+    #define BASE 20
+    {
+        psMemId id = psMemGetId();
+        // 1. Create a simple FITS file
+        psFits* fitsFileW = psFitsOpen(".tmp00", "w");
+        ok(fitsFileW != NULL, "psFitsOpen() was successful");
+        // 2. Create a simple pmHDU header
+        //    Allocate and format hdu->header
+        //    Set hdu->images
+        pmHDU *hdu = pmHDUAlloc("extname");
+        bool rc = pmConfigFileRead(&hdu->format, "../dataFiles/camera0/format0.config", "Camera format 0");
+        if (!rc) {
+            rc = pmConfigFileRead(&hdu->format, "dataFiles/camera0/format0.config", "Camera format 0");
+	}
+        ok(rc, "pmConfigFileRead() was successful");
+        hdu->masks = psArrayAlloc(NUM_HDUS);
+        for (int k = 0 ; k < NUM_HDUS ; k++) {
+            hdu->masks->data[k] = psImageAlloc(4, 4, PS_TYPE_F32);
+            psImageInit(hdu->masks->data[k], (float) (BASE+k));
+	}
+        // 3. Use pmHDUWriteMask() to write that header to the FITS file
+        rc = pmHDUWriteMask(hdu, fitsFileW);
+        ok(rc == true, "pmHDUWriteMask() returned TRUE");
+        // 4. Close the FITS file, free memory
+        psFree(hdu);
+        psFitsClose(fitsFileW);
+
+        // Now, try to read that FITS file from disk.
+        // 5. Read that fits file from disk
+        psFits* fitsFileR = psFitsOpen(".tmp00", "r");
+        ok(fitsFileR != NULL, "psFitsOpen() was successful");
+        // 6. Create another pmHDU
+        hdu = pmHDUAlloc("extname");
+        hdu->masks = NULL;
+        // 7. Call pmHDUReadMask()which will read the FITS data into hdu->masks
+        rc = pmHDUReadMask(hdu, fitsFileR);
+        ok(rc == true, "pmHDUReadMask() returned TRUE");
+        ok(hdu->masks != NULL, "pmHDUReadMask() created hdu->masks");
+        // 8. Verify that hdu->masks was set correctly.
+        bool errorFlag = false;
+        for (int k = 0 ; k < NUM_HDUS ; k++) {
+            psImage *img = (psImage *) hdu->masks->data[k];
+            for (psS32 i = 0 ; i < img->numRows ; i++) {
+                for (psS32 j = 0 ; j < img->numCols ; j++) {
+                    if (((float) k+BASE) != img->data.F32[i][j]) {
+                        errorFlag = true;
+                        diag("TEST ERROR (image %d): img[%d][%d] is %f, should be %f\n",
+                              k, i, j, img->data.F32[i][j], ((float)k+BASE));
+        	    }
+            	}
+	    }
+	}
+        ok(!errorFlag, "pmHDUReadMask() correctly returned the image data");
+        psFitsClose(fitsFileR);
+        psFree(hdu);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmHDUReadHeader(), pmHDUWriteHeader() tests
+    // Test pmHDUReadHeader() with NULL pmHDU input argument
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen("../dataFiles/sampleFitsFile.fits", "r");
+        if (!fitsFile) {
+            fitsFile = psFitsOpen("dataFiles/sampleFitsFile.fits", "r");
+	}
+        ok(fitsFile != NULL, "psFitsOpen() was successful");
+        bool rc = pmHDUReadHeader(NULL, fitsFile);
+        ok(rc == false, "pmHDUReadHeader() returned FALSE with NULL psHDU as input");
+        psFitsClose(fitsFile);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmHDUReadHeader() with NULL psFits input argument
+    {
+        psMemId id = psMemGetId();
+        pmHDU *hdu = pmHDUAlloc("extname");
+        hdu->header = psMetadataAlloc();
+        bool rc = pmHDUReadHeader(hdu, NULL);
+        ok(rc == false, "pmHDUReadHeader() returned FALSE with NULL psFits file as input");
+        psFree(hdu);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmHDUReadHeader() and pmHDUWriteHeader()
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFileW = psFitsOpen(fitsFilename, "w");
+        ok(fitsFileW != NULL, "psFitsOpen() opened the FITS file");
+        char extname[80];
+        for (int lcv = 0; lcv < NUM_HDUS; lcv++) {
+            snprintf(extname, 80, "ext-%d", lcv);
+            pmHDU *hdu = pmHDUAlloc(extname);
+            hdu->header = psMetadataAlloc();
+            psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYINT", PS_DATA_S32,
+                         "psS32 Item", (psS32)lcv);
+            psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYFLT", PS_DATA_F32,
+                         "psF32 Item", (float)(1.0f/(float)(1+lcv)));
+            psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYDBL", PS_DATA_F64,
+                         "psF64 Item", (double)(1.0/(double)(1+lcv)));
+            psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYBOOL", PS_DATA_BOOL,
+                         "psBool Item", (lcv%2 == 0));
+            psMetadataAdd(hdu->header, PS_LIST_TAIL, "MYSTR", PS_DATA_STRING,
+                         "psStr Item", extname);
+
+            bool rc = pmConfigFileRead(&hdu->format, "../dataFiles/camera0/format0.config", "Camera 0 Config Format");
+            if (!rc) {
+                rc = pmConfigFileRead(&hdu->format, "dataFiles/camera0/format0.config", "Camera 0 Config Format");
+	    }
+            ok(rc == true, "pmConfigFileRead() was successful");
+            rc = pmHDUWrite(hdu, fitsFileW);
+            ok(rc == true, "pmHDUWrite() successfully wrote the header");
+            psFree(hdu);
+        }
+        psFitsClose(fitsFileW);
+
+        psFits* fitsFileR = psFitsOpen(fitsFilename, "r");
+        ok(fitsFileR != NULL, "psFitsOpen returned non-NULL on existing file");
+        int numHDUs = psFitsGetSize(fitsFileR);
+        ok(numHDUs == NUM_HDUS, "The test FITS file has %d HDUs", numHDUs);
+
+        for (int hdunum = 0; hdunum < NUM_HDUS; hdunum++)
+        {
+            char extname[80];
+            snprintf(extname,80, "ext-%d", hdunum);
+            psFitsMoveExtNum(fitsFileR, hdunum, false);
+            pmHDU *hdu = pmHDUAlloc(extname);
+            bool rc = pmHDUReadHeader(hdu, fitsFileR);
+            ok(rc == true, "pmHDUReadHeader() returned TRUE");
+            ok(hdu->header != NULL && psMemCheckMetadata(hdu->header),
+              "pmHDUReadHeader() correctly returned the hdu->header member");
+
+            psS32 intItem = psMetadataLookupS32(NULL, hdu->header, "MYINT");
+            ok(intItem == hdunum, "Retrieved psS32 metadata item from file");
+
+            psF32 fltItem = psMetadataLookupF32(NULL, hdu->header, "MYFLT");
+            ok(fabsf(fltItem - 1.0f/(float)(1+hdunum)) <= FLT_EPSILON,
+               "Retrieved psF32 metadata item from file.  Got %f vs %f",
+               fltItem,1.0f/(float)(1+hdunum));
+
+            psF64 dblItem = psMetadataLookupF64(NULL, hdu->header, "MYDBL");
+            ok(abs(dblItem - 1.0/(double)(1+hdunum)) <= DBL_EPSILON,
+               "Retrieved psF64 metadata item from file.  Got %g vs %g",
+               dblItem, 1.0/(double)(1+hdunum));
+
+            psMetadataItem* boolItem = psMetadataLookup(hdu->header, "MYBOOL");
+            ok(boolItem != NULL && boolItem->type == PS_DATA_BOOL,
+               "Retrieved psBool metadata item from file");
+
+            psString strItem = psMetadataLookupStr(NULL, hdu->header, "MYSTR");
+            ok(strItem != NULL && strncmp(strItem,extname,strlen(extname)) == 0,
+               "Retrieved string metadata item from file.  Got '%s' vs '%s' (%d)",
+               strItem,extname,strlen(extname));
+
+            psFree(hdu);
+        }
+        psFitsClose(fitsFileR);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmHDUUtils.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmHDUUtils.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmHDUUtils.c	(revision 22157)
@@ -0,0 +1,480 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS:
+    All functions are tested.
+*/
+
+#define CHIP_ALLOC_NAME        "ChipName"
+#define CELL_ALLOC_NAME        "CellName"
+#define MISC_NUM                32
+#define MISC_NAME              "META00"
+#define MISC_NAME2             "META01"
+#define NUM_BIAS_DATA           10
+#define TEST_NUM_ROWS           4
+#define TEST_NUM_COLS           4
+#define NUM_READOUTS            3
+#define NUM_CELLS               10
+#define NUM_CHIPS               8
+#define NUM_HDUS                5
+#define BASE_IMAGE              10
+#define BASE_MASK               40
+#define BASE_WEIGHT             70
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+
+psPlaneTransform *PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM()
+{
+    psPlaneTransform *pt = psPlaneTransformAlloc(1, 1);
+    pt->x->coeff[1][0] = 1.0;
+    pt->y->coeff[0][1] = 1.0;
+    return(pt);
+}
+
+psPlaneDistort *PS_CREATE_4D_IDENTITY_PLANE_DISTORT()
+{
+    psPlaneDistort *pd = psPlaneDistortAlloc(1, 1, 1, 1);
+    pd->x->coeff[1][0][0][0] = 1.0;
+    pd->y->coeff[0][1][0][0] = 1.0;
+    return(pd);
+}
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(readout);
+}
+
+/******************************************************************************
+generateSimpleCell(): This function generates a pmCell data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmCell *generateSimpleCell(pmChip *chip)
+{
+    pmCell *cell = pmCellAlloc(chip, CELL_ALLOC_NAME);
+
+    psMetadataAddS32(cell->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(cell->readouts, NUM_READOUTS);
+    cell->hdu = pmHDUAlloc("cellExtName");
+    for (int i = 0 ; i < NUM_READOUTS ; i++) {
+        cell->readouts->data[i] = psMemDecrRefCounter((psPtr) generateSimpleReadout(cell));
+    }
+
+    // First try to read data from ../dataFiles, then try dataFiles.
+    bool rc = pmConfigFileRead(&cell->hdu->format, "../dataFiles/camera0/format0.config", "Camera format 0");
+    if (!rc) {
+        rc = pmConfigFileRead(&cell->hdu->format, "dataFiles/camera0/format0.config", "Camera format 0");
+        if (!rc) {
+            diag("pmConfigFileRead() was unsuccessful (from generateSimpleCell())");
+	}
+    }
+
+    cell->hdu->images = psArrayAlloc(NUM_HDUS);
+    cell->hdu->masks = psArrayAlloc(NUM_HDUS);
+    cell->hdu->weights = psArrayAlloc(NUM_HDUS);
+    for (int k = 0 ; k < NUM_HDUS ; k++) {
+        cell->hdu->images->data[k]  = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        cell->hdu->masks->data[k]   = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        cell->hdu->weights->data[k] = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(cell->hdu->images->data[k], (float) (BASE_IMAGE+k));
+        psImageInit(cell->hdu->masks->data[k], (psU8) (BASE_MASK+k));
+        psImageInit(cell->hdu->weights->data[k], (float) (BASE_WEIGHT+k));
+    }
+
+    //XXX: Should the region be set some other way?  Like through the various config files?
+    psRegion *region = psRegionAlloc(0.0, 0.0, 0.0, 0.0);
+    // You shouldn't have to remove the key from the metadata.  Find out how to simply change the key value.
+    psMetadataRemoveKey(cell->concepts, "CELL.TRIMSEC");
+    psMetadataAddPtr(cell->concepts, PS_LIST_TAIL|PS_META_REPLACE, "CELL.TRIMSEC", PS_DATA_REGION, "I am a region", region);
+    psFree(region);
+    return(cell);
+}
+
+/******************************************************************************
+generateSimpleChip(): This function generates a pmChip data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmChip *generateSimpleChip(pmFPA *fpa)
+{
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    chip->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    chip->fromFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    psMetadataAddS32(chip->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(chip->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    chip->hdu = pmHDUAlloc("chipExtName");
+
+    bool rc = pmConfigFileRead(&chip->hdu->format, "dataFiles/camera0/format0.config", "Camera format 0");
+    if (!rc) {
+        rc = pmConfigFileRead(&chip->hdu->format, "../dataFiles/camera0/format0.config", "Camera format 0");
+        if (!rc) {
+            diag("pmConfigFileRead() was unsuccessful (from generateSimpleChip())");
+	}
+    }
+
+    psArrayRealloc(chip->cells, NUM_CELLS);
+    for (int i = 0 ; i < NUM_CELLS ; i++) {
+        chip->cells->data[i] = psMemDecrRefCounter((psPtr) generateSimpleCell(chip));
+    }
+    return(chip);
+}
+
+/******************************************************************************
+generateSimpleFPA(): This function generates a pmFPA data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmFPA* generateSimpleFPA(psMetadata *camera)
+{
+    pmFPA* fpa = pmFPAAlloc(camera);
+    fpa->fromTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toSky = psProjectionAlloc(0.0,0.0,10.0,10.0,PS_PROJ_TAN);
+    psMetadataAddS32(fpa->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    if (camera != NULL) {
+        psMetadataAddS32((psMetadata *) fpa->camera, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    }
+    psMetadataAddS32(fpa->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    fpa->hdu = pmHDUAlloc("fpaExtName");
+    bool rc = pmConfigFileRead(&fpa->hdu->format, "dataFiles/camera0/format0.config", "Camera format 0");
+    if (!rc) {
+        rc = pmConfigFileRead(&fpa->hdu->format, "../dataFiles/camera0/format0.config", "Camera format 0");
+        if (!rc) {
+            diag("pmConfigFileRead() was unsuccessful (from generateSimpleFPA())");
+	}
+    }
+
+    psArrayRealloc(fpa->chips, NUM_CHIPS);
+    for (int i = 0 ; i < NUM_CHIPS ; i++) {
+        fpa->chips->data[i] = psMemDecrRefCounter((psPtr) generateSimpleChip(fpa));
+    }
+    pmConceptsBlankFPA(fpa);
+    return(fpa);
+}
+
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(73);
+
+
+    // ----------------------------------------------------------------------
+    // pmHDUFromFPA() tests
+    // Call pmHDUFromFPA() with NULL input params
+    {
+        psMemId id = psMemGetId();
+        pmHDU *hdu = pmHDUFromFPA(NULL);
+        ok(hdu == NULL, "pmHDUFromFPA(NULL) returned NULL");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmHDUFromFPA() with acceptable input params
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        pmHDU *hdu = pmHDUFromFPA(fpa);
+        ok(hdu == fpa->hdu, "pmHDUFromFPA(NULL) returned the correct pmHDU of an pmFPA struct");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmHDUFromChip() tests
+    // Call pmHDUFromChip() with NULL input params
+    {
+        psMemId id = psMemGetId();
+        pmHDU *hdu = pmHDUFromChip(NULL);
+        ok(hdu == NULL, "pmHDUFromChip(NULL) returned NULL");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmHDUFromChip() with acceptable input params
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        pmHDU *hdu = pmHDUFromChip(chip);
+        ok(hdu == chip->hdu, "pmHDUFromChip() returned the correct pmHDU of an pmChip struct");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmHDUFromChip() with acceptable input params
+    // Set chip->hdu to NULL, verify chip->parent->hdu is returned
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        psFree(chip->hdu);
+        chip->hdu = NULL;
+        pmHDU *hdu = pmHDUFromChip(chip);
+        ok(hdu == chip->parent->hdu, "pmHDUFromChip() returned the correct pmHDU of an pmChip struct");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmHDUFromCell() tests
+    // Call pmHDUFromCell() with NULL input params
+    {
+        psMemId id = psMemGetId();
+        pmHDU *hdu = pmHDUFromCell(NULL);
+        ok(hdu == NULL, "pmHDUFromCell(NULL) returned NULL");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmHDUFromCell() with acceptable input params
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        pmHDU *hdu = pmHDUFromCell(cell);
+        ok(hdu == cell->hdu, "pmHDUFromCell() returned the correct pmHDU of an pmCell struct");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmHDUFromCell() with acceptable input params
+    // Set cell->hdu to NULL, verify cell->parent->hdu is returned
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        psFree(cell->hdu);
+        cell->hdu = NULL;
+        pmHDU *hdu = pmHDUFromCell(cell);
+        ok(hdu == cell->parent->hdu, "pmHDUFromCell() returned the correct pmHDU of an pmCell struct");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmHDUFromReadout() tests
+    // Call pmHDUFromReadout() with NULL input params
+    {
+        psMemId id = psMemGetId();
+        pmHDU *hdu = pmHDUFromReadout(NULL);
+        ok(hdu == NULL, "pmHDUFromReadout(NULL) returned NULL");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmHDUFromReadout() with acceptable input params
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        pmReadout *readout = cell->readouts->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        ok(readout != NULL, "Allocated a pmReadout successfully");
+        pmHDU *hdu = pmHDUFromReadout(readout);
+        ok(hdu == readout->parent->hdu, "pmHDUFromReadout() returned the correct pmHDU of an pmReadout struct");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmHDUGetLowest() tests
+    // Call pmHDUGetLowest() with all NULL inputs
+    {
+        psMemId id = psMemGetId();
+        pmHDU *hdu = pmHDUGetLowest(NULL, NULL, NULL);
+        ok(hdu == NULL, "pmHDUFromReadout(NULL, NULL, NULL)");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmHDUGetLowest() with all acceptable inputs
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        pmHDU *hdu = pmHDUGetLowest(fpa, chip, cell);
+        ok(hdu == cell->hdu, "pmHDUGetLowest(fpa, chip, cell)");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmHDUGetLowest() with all (fpa, chip, NULL) inputs
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        pmHDU *hdu = pmHDUGetLowest(fpa, chip, NULL);
+        ok(hdu == chip->hdu, "pmHDUGetLowest(fpa, chip, NULL)");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmHDUGetLowest() with all (fpa, NULL, NULL) inputs
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        pmHDU *hdu = pmHDUGetLowest(fpa, NULL, NULL);
+        ok(hdu == fpa->hdu, "pmHDUGetLowest(fpa, NULL, NULL)");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    //pmHDUGetHighest() tests
+    // Call pmHDUGetHighest() with all NULL inputs
+    {
+        psMemId id = psMemGetId();
+        pmHDU *hdu = pmHDUGetHighest(NULL, NULL, NULL);
+        ok(hdu == NULL, "pmHDUFromReadout(NULL, NULL, NULL)");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmHDUGetHighest() with all acceptable inputs
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        pmHDU *hdu = pmHDUGetHighest(fpa, chip, cell);
+        ok(hdu == fpa->hdu, "pmHDUGetHighest(fpa, chip, cell)");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmHDUGetHighest() with (NULL, chip, cell) inputs
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        pmHDU *hdu = pmHDUGetHighest(NULL, chip, cell);
+        ok(hdu == chip->hdu, "pmHDUGetHighest(NULL, chip, cell)");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmHDUGetHighest() with (NULL, NULL, cell) inputs
+    {
+        psMemId id = psMemGetId();
+        // Generate the pmFPA heirarchy
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA* fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        ok(fpa != NULL, "Allocated a pmFPA successfully");
+        ok(chip != NULL, "Allocated a pmChip successfully");
+        ok(cell != NULL, "Allocated a pmCell successfully");
+        pmHDU *hdu = pmHDUGetHighest(NULL, NULL, cell);
+        ok(hdu == cell->hdu, "pmHDUGetHighest(NULL, NULL, cell)");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmReadoutFake.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmReadoutFake.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmReadoutFake.c	(revision 22157)
@@ -0,0 +1,177 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS:
+    Only one function in the source code: pmReadoutFakeFromSources()
+
+    pmReadoutFakeFromSources() is only tested with bad input parameters.
+    Tests must be written to exercise it with legitimate input sources, and
+    verify the output.
+*/
+
+#define MISC_NUM                32
+#define MISC_NAME               "META00"
+#define NUM_BIAS_DATA           10
+#define TEST_NUM_ROWS           5
+#define TEST_NUM_COLS           8
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+#define NUM_SOURCES		5
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    psImageInit(readout->image, 1.0);
+    psImageInit(readout->mask, 2);
+    psImageInit(readout->weight, 3.0);
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(readout);
+}
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(11);
+
+    // ------------------------------------------------------------------------
+    // pmReadoutFakeFromSources() tests
+    // bool pmReadoutFakeFromSources(pmReadout *readout, int numCols, int numRows, const psArray *sources,
+    //                               const psVector *xOffset, const psVector *yOffset, const pmPSF *psf,
+    //                               float minFlux, int radius, bool circularise)
+    //
+    // Call pmReadoutFakeFromSources() with bad input parameters.
+    {
+        psMemId id = psMemGetId();
+        pmReadout *readout = generateSimpleReadout(NULL);
+        psVector *xOffset = psVectorAlloc(NUM_SOURCES, PS_TYPE_S32);
+        psVector *yOffset = psVectorAlloc(NUM_SOURCES, PS_TYPE_S32);
+        psVector *xOffsetBig = psVectorAlloc(NUM_SOURCES*2, PS_TYPE_S32);
+        psVector *xOffsetF32 = psVectorAlloc(NUM_SOURCES, PS_TYPE_F32);
+        psVector *yOffsetF32 = psVectorAlloc(NUM_SOURCES, PS_TYPE_F32);
+        psArray *sources = psArrayAlloc(NUM_SOURCES);
+        for (int i = 0; i < sources->n ; i++) {
+            sources->data[i] = pmSourceAlloc();
+        }
+        pmPSFOptions *psfOptions = pmPSFOptionsAlloc();
+        psfOptions->psfTrendNx = 1;
+        psfOptions->psfTrendNy = 2;
+        psfOptions->psfFieldNx = 3;
+        psfOptions->psfFieldNy = 4;
+        psfOptions->psfFieldXo = 5;
+        psfOptions->psfFieldYo = 6;
+        pmModelClassInit();
+        psfOptions->type = pmModelClassGetType("PS_MODEL_GAUSS");
+        pmPSF *psf = pmPSFAlloc(psfOptions);
+
+        // NULL pmReadout input parameter
+        bool rc = pmReadoutFakeFromSources(NULL, TEST_NUM_COLS, TEST_NUM_ROWS, sources,
+                                           xOffset, yOffset, psf, 0.0, 1.0, false);
+        ok(rc == false, "pmReadoutFakeFromSources() returned FALSE with pmReadout input parameter");
+
+        // Non-positive numCols input parameter
+        rc = pmReadoutFakeFromSources(readout, 0, TEST_NUM_ROWS, sources,
+                                           xOffset, yOffset, psf, 0.0, 1.0, false);
+        ok(rc == false, "pmReadoutFakeFromSources() returned FALSE with Non-positive numCols input parameter");
+
+        // Non-positive numRows input parameter
+        rc = pmReadoutFakeFromSources(readout, TEST_NUM_COLS, 0, sources,
+                                           xOffset, yOffset, psf, 0.0, 1.0, false);
+        ok(rc == false, "pmReadoutFakeFromSources() returned FALSE with Non-positive numRow input parameter");
+
+        // NULL pmSource input parameter
+        rc = pmReadoutFakeFromSources(readout, TEST_NUM_COLS, TEST_NUM_ROWS, NULL,
+                                           xOffset, yOffset, psf, 0.0, 1.0, false);
+        ok(rc == false, "pmReadoutFakeFromSources() returned FALSE with pmSource input parameter");
+
+        // NULL pmPSF input parameter
+        rc = pmReadoutFakeFromSources(readout, TEST_NUM_COLS, TEST_NUM_ROWS, sources,
+                                           xOffset, yOffset, NULL, 0.0, 1.0, false);
+        ok(rc == false, "pmReadoutFakeFromSources() returned FALSE with input parameter");
+
+        // NULL incorrect type xOffset input parameter
+        rc = pmReadoutFakeFromSources(readout, TEST_NUM_COLS, TEST_NUM_ROWS, sources,
+                                           xOffsetF32, yOffset, psf, 0.0, 1.0, false);
+        ok(rc == false, "pmReadoutFakeFromSources() returned FALSE with incorrect type xOffset input parameter");
+
+        // NULL incorrect type yOffset input parameter
+        rc = pmReadoutFakeFromSources(readout, TEST_NUM_COLS, TEST_NUM_ROWS, sources,
+                                           xOffset, yOffsetF32, psf, 0.0, 1.0, false);
+        ok(rc == false, "pmReadoutFakeFromSources() returned FALSE with incorrect type yOffset input parameter");
+
+        // NULL incorrect size xOffset input parameter
+        rc = pmReadoutFakeFromSources(readout, TEST_NUM_COLS, TEST_NUM_ROWS, sources,
+                                           xOffsetBig, yOffset, psf, 0.0, 1.0, false);
+        ok(rc == false, "pmReadoutFakeFromSources() returned FALSE with incorrect size xOffset input parameter");
+
+        psFree(readout);
+        psFree(xOffset);
+        psFree(yOffset);
+        psFree(xOffsetBig);
+        psFree(xOffsetF32);
+        psFree(yOffsetF32);
+        psFree(sources);
+        psFree(psfOptions);
+        psFree(psf);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmReadoutFakeFromSources() with acceptable input parameters.
+    {
+        psMemId id = psMemGetId();
+        pmReadout *readout = generateSimpleReadout(NULL);
+        psVector *xOffset = psVectorAlloc(NUM_SOURCES, PS_TYPE_S32);
+        psVector *yOffset = psVectorAlloc(NUM_SOURCES, PS_TYPE_S32);
+        psVector *xOffsetF32 = psVectorAlloc(NUM_SOURCES, PS_TYPE_F32);
+        psVector *yOffsetF32 = psVectorAlloc(NUM_SOURCES, PS_TYPE_F32);
+        psArray *sources = psArrayAlloc(NUM_SOURCES);
+        for (int i = 0; i < sources->n ; i++) {
+            sources->data[i] = pmSourceAlloc();
+        }
+        pmPSFOptions *psfOptions = pmPSFOptionsAlloc();
+        psfOptions->psfTrendNx = 1;
+        psfOptions->psfTrendNy = 2;
+        psfOptions->psfFieldNx = 3;
+        psfOptions->psfFieldNy = 4;
+        psfOptions->psfFieldXo = 5;
+        psfOptions->psfFieldYo = 6;
+        pmModelClassInit();
+        psfOptions->type = pmModelClassGetType("PS_MODEL_GAUSS");
+        pmPSF *psf = pmPSFAlloc(psfOptions);
+
+        bool rc = pmReadoutFakeFromSources(readout, TEST_NUM_COLS, TEST_NUM_ROWS, sources,
+                                           xOffset, yOffset, psf, 0.0, 1.0, false);
+        ok(rc == true, "pmReadoutFakeFromSources() returned TRUE with acceptable input parameters");
+
+        psFree(readout);
+        psFree(xOffset);
+        psFree(yOffset);
+        psFree(xOffsetF32);
+        psFree(yOffsetF32);
+        psFree(sources);
+        psFree(psfOptions);
+        psFree(psf);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmReadoutStack.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmReadoutStack.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/camera/tap_pmReadoutStack.c	(revision 22157)
@@ -0,0 +1,250 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS:
+    All functions are tested.
+*/
+
+#define CELL_ALLOC_NAME		"CellName"
+#define MISC_NUM		32
+#define MISC_NAME		"META00"
+#define NUM_BIAS_DATA		10
+#define TEST_NUM_ROWS		5
+#define TEST_NUM_COLS		8
+#define NUM_INPUTS		10
+#define NUM_READOUTS		4
+#define VERBOSE			0
+#define ERR_TRACE_LEVEL		10
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    psImageInit(readout->image, 1.0);
+    psImageInit(readout->mask, 2);
+    psImageInit(readout->weight, 3.0);
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(readout);
+}
+
+/******************************************************************************
+generateSimpleCell(): This function generates a pmCell data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmCell *generateSimpleCell(int ID)
+{
+    pmCell *cell = pmCellAlloc(NULL, CELL_ALLOC_NAME);
+    psMetadataAddS32(cell->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    cell->hdu = pmHDUAlloc(NULL);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psRegion *region = psRegionAlloc(0.0, (float) (10 + ID), 0.0, (float) (20 + ID));
+    psMetadataRemoveKey(cell->concepts, "CELL.TRIMSEC");
+    psMetadataAddPtr(cell->concepts, PS_LIST_TAIL|PS_META_REPLACE, "CELL.TRIMSEC", PS_DATA_REGION, "I am a region", region);
+    psFree(region);
+    psArrayRealloc(cell->readouts, NUM_READOUTS);
+    for (int i = 0 ; i < NUM_READOUTS ; i++) {
+        cell->readouts->data[i] = psMemDecrRefCounter((psPtr) generateSimpleReadout(cell));
+    }
+
+    return(cell);
+}
+
+
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(29);
+
+    // ------------------------------------------------------------------------
+    // pmReadoutUpdateSize() tests
+    // Call pmReadoutUpdateSize() with NULL pmReadout input parameter.
+    {
+        psMemId id = psMemGetId();
+        bool rc = pmReadoutUpdateSize(NULL, 0, 0, TEST_NUM_COLS, TEST_NUM_ROWS, false);
+        ok(rc == false, "pmReadoutUpdateSize() returned FALSE with NULL pmReadout input parameter");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmReadoutUpdateSize() with acceptable input parameters (mask == false).
+    {
+        psMemId id = psMemGetId();
+        pmReadout *readout = generateSimpleReadout(NULL);
+        bool rc = pmReadoutUpdateSize(readout, 0, 0, 2*TEST_NUM_COLS, 2*TEST_NUM_ROWS, false);
+        ok(rc == true, "pmReadoutUpdateSize() returned TRUE with acceptable input parameters");
+        ok(readout->image->numCols == (2*TEST_NUM_COLS) &&
+           readout->image->numRows == (2*TEST_NUM_ROWS), "pmReadoutUpdateSize() generated the correct size pmReadout->image");
+        bool errorFlag = false;
+        for (int i = 0 ; i < readout->image->numRows ; i++) {
+            for (int j = 0 ; j < readout->image->numCols ; j++) {
+                psF32 correctF32;
+                if (i < TEST_NUM_ROWS && j < TEST_NUM_COLS) {
+                   correctF32 = 1.0;
+                } else {
+                   correctF32 = 0.0;
+                }
+                if (readout->image->data.F32[i][j] != correctF32) {
+                    diag("ERROR: readout->image[%d][%d] is %.2f, should be %.2f", i, j, readout->image->data.F32[i][j], correctF32);
+                    errorFlag = true;
+                }
+            }
+        }
+        ok(!errorFlag, "pmReadoutUpdateSize() initialized pmReadout->image to zero");
+        ok(readout->mask->numCols == (TEST_NUM_COLS) &&
+           readout->mask->numRows == (TEST_NUM_ROWS), "pmReadoutUpdateSize() generated the correct size pmReadout->mask");
+
+        psFree(readout);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmReadoutUpdateSize() with acceptable input parameters (mask == true).
+    {
+        psMemId id = psMemGetId();
+        pmReadout *readout = generateSimpleReadout(NULL);
+        bool rc = pmReadoutUpdateSize(readout, 0, 0, 2*TEST_NUM_COLS, 2*TEST_NUM_ROWS, true);
+        ok(rc == true, "pmReadoutUpdateSize() returned TRUE with acceptable input parameters");
+        ok(readout->image->numCols == (2*TEST_NUM_COLS) &&
+           readout->image->numRows == (2*TEST_NUM_ROWS), "pmReadoutUpdateSize() generated the correct size pmReadout->image");
+        bool errorFlag = false;
+        for (int i = 0 ; i < readout->image->numRows ; i++) {
+            for (int j = 0 ; j < readout->image->numCols ; j++) {
+                psF32 correctF32;
+                if (i < TEST_NUM_ROWS && j < TEST_NUM_COLS) {
+                   correctF32 = 1.0;
+                } else {
+                   correctF32 = 0.0;
+                }
+                if (readout->image->data.F32[i][j] != correctF32) {
+                    diag("ERROR: readout->image[%d][%d] is %.2f, should be %.2f", i, j, readout->image->data.F32[i][j], correctF32);
+                    errorFlag = true;
+                }
+            }
+        }
+        ok(!errorFlag, "pmReadoutUpdateSize() initialized pmReadout->image to zero");
+        ok(readout->mask->numCols == (2*TEST_NUM_COLS) &&
+           readout->mask->numRows == (2*TEST_NUM_ROWS), "pmReadoutUpdateSize() generated the correct size pmReadout->mask");
+        errorFlag = false;
+        for (int i = 0 ; i < readout->mask->numRows ; i++) {
+            for (int j = 0 ; j < readout->mask->numCols ; j++) {
+                psF32 correctU8;
+                if (i < TEST_NUM_ROWS && j < TEST_NUM_COLS) {
+                   correctU8 = 2;
+                } else {
+                   correctU8 = 0;
+                }
+                if (readout->mask->data.U8[i][j] != correctU8) {
+                    diag("ERROR: readout->mask[%d][%d] is %d, should be %d", i, j, readout->mask->data.U8[i][j], correctU8);
+                    errorFlag = true;
+                }
+            }
+        }
+        ok(!errorFlag, "pmReadoutUpdateSize() initialized pmReadout->mask to zero");
+
+        psFree(readout);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+    // ------------------------------------------------------------------------
+    // pmReadoutStackValidate() tests
+    // Call pmReadoutStackValidate() with bad input parameters.
+    {
+        psMemId id = psMemGetId();
+        int minInputCols, maxInputCols, minInputRows, maxInputRows, numCols, numRows;
+        minInputCols = maxInputCols = minInputRows = maxInputRows = numCols = numRows = 0;
+        bool rc = pmReadoutStackValidate(&minInputCols, &maxInputCols, &minInputRows, &maxInputRows,
+                                         &numCols, &numRows, NULL);
+        psArray *inputs = psArrayAlloc(NUM_INPUTS);
+        for (int i = 0 ; i < NUM_INPUTS ; i++) {
+            inputs->data[i] = (psPtr *) generateSimpleReadout(NULL);
+        }
+
+        // NULL psArray input parameter
+        rc = pmReadoutStackValidate(&minInputCols, &maxInputCols, &minInputRows, &maxInputRows,
+                                   &numCols, &numRows, NULL);
+        ok(rc == false, "pmReadoutStackValidate() returned FALSE with NULL pmArray input parameter");
+
+        // NULL minInputColsPtr
+        rc = pmReadoutStackValidate(NULL, &maxInputCols, &minInputRows, &maxInputRows,
+                                    &numCols, &numRows, inputs);
+        ok(rc == false, "pmReadoutStackValidate() returned FALSE with NULL minInputColsPtr input parameter");
+
+        // NULL maxInputColsPtr
+        rc = pmReadoutStackValidate(&minInputCols, NULL, &minInputRows, &maxInputRows,
+                                    &numCols, &numRows, inputs);
+        ok(rc == false, "pmReadoutStackValidate() returned FALSE with NULL maxInputColsPtr input parameter");
+
+        // NULL minInputRowsPtr
+        rc = pmReadoutStackValidate(&minInputCols, &maxInputCols, NULL, &maxInputRows,
+                                    &numCols, &numRows, inputs);
+        ok(rc == false, "pmReadoutStackValidate() returned FALSE with NULL minInputRowsPtr input parameter");
+
+        // NULL maxInputRowsPtr
+        rc = pmReadoutStackValidate(&minInputCols, &maxInputCols, &minInputRows, NULL,
+                                    &numCols, &numRows, inputs);
+        ok(rc == false, "pmReadoutStackValidate() returned FALSE with NULL maxInputRowsPtr input parameter");
+
+        // NULL numColsPtr
+        rc = pmReadoutStackValidate(&minInputCols, &maxInputCols, &minInputRows, &maxInputRows,
+                                    NULL, &numRows, inputs);
+        ok(rc == false, "pmReadoutStackValidate() returned FALSE with NULL numColsPtr input parameter");
+
+        // NULL numRowsPtr
+        rc = pmReadoutStackValidate(&minInputCols, &maxInputCols, &minInputRows, &maxInputRows,
+                                    &numCols, NULL, inputs);
+        ok(rc == false, "pmReadoutStackValidate() returned FALSE with NULL numRowsPtr input parameter");
+
+        psFree(inputs);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmReadoutStackValidate() with acceptable input parameters.
+    {
+        psMemId id = psMemGetId();
+        int minInputCols, maxInputCols, minInputRows, maxInputRows, numCols, numRows;
+        minInputCols = maxInputCols = minInputRows = maxInputRows = numCols = numRows = 0;
+        pmCell *cells[NUM_INPUTS];
+        psArray *inputs = psArrayAlloc(NUM_INPUTS);
+        for (int i = 0 ; i < NUM_INPUTS ; i++) {
+            cells[i] = generateSimpleCell(i);
+            inputs->data[i] = (psPtr *) generateSimpleReadout(cells[i]);
+        }
+        bool rc = pmReadoutStackValidate(&minInputCols, &maxInputCols, &minInputRows, &maxInputRows,
+                                   &numCols, &numRows, inputs);
+        ok(rc == true, "pmReadoutStackValidate() returned TRUE with acceptable input parameters");
+        ok(minInputCols == 0, "pmReadoutStackValidate() set minInputCols correctly");
+        ok(maxInputCols == TEST_NUM_COLS, "pmReadoutStackValidate() set maxInputCols correctly");
+        ok(minInputRows == 0, "pmReadoutStackValidate() set minInputRows correctly");
+        ok(maxInputRows == TEST_NUM_ROWS, "pmReadoutStackValidate() set maxInputRows correctly");
+        ok(numCols == (10 + NUM_INPUTS - 1), "pmReadoutStackValidate() set numCols correctly");
+        ok(numRows == (20 + NUM_INPUTS - 1), "pmReadoutStackValidate() set numRows correctly");
+
+        for (int i = 0 ; i < NUM_INPUTS ; i++) {
+            psFree(cells[i]);
+        }
+        psFree(inputs);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/concepts/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/concepts/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/concepts/.cvsignore	(revision 22157)
@@ -0,0 +1,4 @@
+.deps
+.libs
+Makefile
+Makefile.in
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/concepts/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/concepts/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/concepts/Makefile.am	(revision 22157)
@@ -0,0 +1,29 @@
+AM_CPPFLAGS = \
+	$(SRCINC) \
+	-I$(top_srcdir)/test/tap/src \
+	-I$(top_srcdir)/test/pstap/src \
+	$(PSMODULES_CFLAGS)
+
+AM_LDFLAGS = \
+	$(top_builddir)/src/libpsmodules.la  \
+	$(top_builddir)/test/tap/src/libtap.la \
+	$(top_builddir)/test/pstap/src/libpstap.la \
+	$(PSMODULES_LIBS)
+
+TEST_PROGS = \
+	tap_pmConcepts \
+	tap_pmConceptsUpdate \
+	tap_pmConceptsPhotcode \
+	tap_pmConceptsAverage
+
+if BUILD_TESTS
+bin_PROGRAMS = $(TEST_PROGS)
+TESTS = $(TEST_PROGS)
+else
+check_PROGRAMS = $(TEST_PROGS)
+endif
+
+CLEANFILES = $(check_DATA) temp/* core core.* *~ *.bb *.bbg *.da gmon.out
+
+test: check
+	$(top_srcdir)/test/test.pl
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/concepts/tap_pmConcepts.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/concepts/tap_pmConcepts.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/concepts/tap_pmConcepts.c	(revision 22157)
@@ -0,0 +1,499 @@
+    /** @file tst_pmConcepts.c
+ *
+ *  @brief Contains the tests for pmConcepts.c:
+ *
+ *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-01-02 20:49:56 $
+*   pmConceptSpecAlloc()
+    pmConceptsList()
+*   pmConceptGetRequired()
+*   pmConceptSetRequired()
+    pmConceptRegister()
+    pmConceptsRead()
+*   pmConceptsBlankFPA()
+    pmConceptsReadFPA()
+    pmConceptsWriteFPA()
+*   pmConceptsBlankChip()
+    pmConceptsReadChip()
+    pmConceptsWriteChip()
+*   pmConceptsBlankCell()
+    pmConceptsReadCell()
+    pmConceptsWriteCell()
+*   pmConceptsInit()
+*   pmConceptsDone()
+    pmFPACopyConcepts()
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+
+psMetadataItem *dummyConceptParser(
+    const psMetadataItem *concept,
+    const psMetadataItem *pattern,
+    pmConceptSource source,
+    const psMetadata *cameraFormat,
+    const pmFPA *fpa,
+    const pmChip *chip,
+    const pmCell *cell)
+{
+    if (concept == NULL ||
+        pattern == NULL ||
+        source == PM_CONCEPT_SOURCE_NONE ||
+        cameraFormat == NULL ||
+        fpa == NULL ||
+        chip == NULL ||
+        cell == NULL) {
+        printf("dummyConceptParser() args are NULL\n");
+    }
+    return(NULL);
+}
+
+// FPA.RA and FPA.DEC
+psMetadataItem *dummyConceptFormatter(
+    const psMetadataItem *concept,
+    pmConceptSource source,
+    const psMetadata *cameraFormat,
+    const pmFPA *fpa,
+    const pmChip *chip,
+    const pmCell *cell)
+{
+    if (concept == NULL ||
+        source == PM_CONCEPT_SOURCE_NONE ||
+        cameraFormat == NULL ||
+        fpa == NULL ||
+        chip == NULL ||
+        cell == NULL) {
+        printf("dummyConceptFormatter() args are NULL\n");
+    }
+    return(NULL);
+}
+
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel(".", ERR_TRACE_LEVEL);
+    plan_tests(126);
+    
+    // --------------------------------------------------------------------
+    // Tests for pmConceptSpecAlloc()
+    // Acceptable input parameters.
+    {
+        psMemId id = psMemGetId();
+        psMetadataItem *blank = psMetadataItemAlloc("myItem1", PS_DATA_BOOL, "I am a boolean", true);
+        pmConceptSpec *tmp = pmConceptSpecAlloc(blank, dummyConceptParser,
+            dummyConceptFormatter, true);
+        ok(tmp != NULL, "pmConceptSpecAlloc() returned non-NULL");
+        skip_start(tmp == NULL, 4, "Skipping tests because pmConceptSpecAlloc() returned NULL");
+        ok(tmp->blank == blank, "pmConceptSpecAlloc() set the ->blank member correctly");
+        ok(tmp->parse == dummyConceptParser, "pmConceptSpecAlloc() set the ->parse member correctly");
+        ok(tmp->format == dummyConceptFormatter, "pmConceptSpecAlloc() set the ->format member correctly");
+        ok(tmp->required == true, "pmConceptSpecAlloc() set the ->required member correctly");
+        skip_end();
+        psFree(tmp);
+        psFree(blank);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // NULL input parameters.
+    {
+        psMemId id = psMemGetId();
+        pmConceptSpec *tmp = pmConceptSpecAlloc(NULL, NULL, NULL, false);
+        ok(tmp != NULL, "pmConceptSpecAlloc() returned non-NULL with NULL inputs");
+        psFree(tmp);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // --------------------------------------------------------------------
+    // Tests for pmConceptGetRequired() and pmConceptSetRequired()
+    // Acceptable input parameters.
+    // We get the "required" for FPA.TELESCOPE, ensure that it is false
+    // then set it to true, then ensure that it is true.
+    {
+        psMemId id = psMemGetId();
+        bool tmpBool = pmConceptGetRequired("FPA.TELESCOPE", PM_FPA_LEVEL_FPA);
+        ok (tmpBool == false, "pmConceptGetRequired() returned true for FPA.TELESCOPE");
+        tmpBool = pmConceptSetRequired("FPA.TELESCOPE", PM_FPA_LEVEL_FPA, true);
+        ok (tmpBool == true, "pmConceptSetRequired() returned true for FPA.TELESCOPE");
+        tmpBool = pmConceptGetRequired("FPA.TELESCOPE", PM_FPA_LEVEL_FPA);
+        ok (tmpBool == true, "pmConceptGetRequired() returned true for FPA.TELESCOPE");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // Test pmConceptGetRequired() with a few incorrect concept names
+    // and/or levels.
+    {
+        psMemId id = psMemGetId();
+        bool tmpBool = pmConceptGetRequired("FPA.TELESCOPE", PM_FPA_LEVEL_CHIP);
+        ok (tmpBool == false, "pmConceptGetRequired() returned false for FPA.TELESCOPE with wrong level (CHIP)");
+        tmpBool = pmConceptGetRequired("FPA.TELESCOPE", PM_FPA_LEVEL_CELL);
+        ok (tmpBool == false, "pmConceptGetRequired() returned false for FPA.TELESCOPE with wrong level (CELL)");
+        tmpBool = pmConceptGetRequired("FPA.BADCONCEPTNAME", PM_FPA_LEVEL_FPA);
+        ok (tmpBool == false, "pmConceptGetRequired() returned false for FPA.BADCONCEPTNAME");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // --------------------------------------------------------------------
+    // Tests for pmConceptsInit(), pmConceptsDone()
+    // We determine if pmConceptsInit() was successful by looking at the
+    // "required" for the CHIP.XPARITY concept at level PM_FPA_LEVEL_CHIP
+    {
+        psMemId id = psMemGetId();
+        pmConceptsDone();
+        // pmConceptsInit() should return TRUE after pmConceptsDone() is called
+        ok(true == pmConceptsInit(), "pmConceptsInit() returned TRUE");
+
+        // FPA concepts
+        ok(false == pmConceptGetRequired("FPA.TELESCOPE", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.TELESCOPE at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.INSTRUMENT", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.INSTRUMENT at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.DETECTOR", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.DETECTOR at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.CAMERA", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.CAMERA at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.FOCUS", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.FOCUS at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.AIRMASS", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.AIRMASS at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.FILTERID", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.FILTERID at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.FILTER", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.FILTER at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.POSANGLE", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.POSANGLE at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.RADECSYS", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.RADECSYS at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.RA", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.RA at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.DEC", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.DEC at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.OBSTYPE", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.OBSTYPE at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.OBJECT", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.OBJECT at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.ALT", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.ALT at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.AZ", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.AZ at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.TIMESYS", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.TIMESYS at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.TIME", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.TIME at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.TEMP", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.TEMP at level: FPA");
+        ok(false == pmConceptGetRequired("FPA.EXPOSURE", PM_FPA_LEVEL_FPA),
+          "pmConceptGetRequired() returned false for FPA.EXPOSURE at level: FPA");
+
+        // Chip concepts
+        ok(true == pmConceptGetRequired("CHIP.XPARITY", PM_FPA_LEVEL_CHIP),
+          "pmConceptGetRequired() returned true for CHIP.XPARITY at level: CHIP");
+        ok(true == pmConceptGetRequired("CHIP.YPARITY", PM_FPA_LEVEL_CHIP),
+          "pmConceptGetRequired() returned true for CHIP.YPARITY at level: CHIP");
+        ok(true == pmConceptGetRequired("CHIP.X0", PM_FPA_LEVEL_CHIP),
+          "pmConceptGetRequired() returned true for CHIP.X0 at level: CHIP");
+        ok(true == pmConceptGetRequired("CHIP.Y0", PM_FPA_LEVEL_CHIP),
+          "pmConceptGetRequired() returned true for CHIP.Y0 at level: CHIP");
+        ok(true == pmConceptGetRequired("CHIP.XSIZE", PM_FPA_LEVEL_CHIP),
+          "pmConceptGetRequired() returned true for CHIP.XSIZE at level: CHIP");
+        ok(true == pmConceptGetRequired("CHIP.YSIZE", PM_FPA_LEVEL_CHIP),
+          "pmConceptGetRequired() returned true for CHIP.YSIZE at level: CHIP");
+        ok(false == pmConceptGetRequired("CHIP.TEMP", PM_FPA_LEVEL_CHIP),
+          "pmConceptGetRequired() returned true for CHIP.TEMP at level: CHIP");
+
+        // Cell concepts
+        ok(true == pmConceptGetRequired("CELL.GAIN", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.GAIN at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.READNOISE", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.READNOISE at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.SATURATION", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.SATURATION at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.BAD", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.BAD at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.XPARITY", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.XPARITY at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.YPARITY", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.YPARITY at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.READDIR", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.READDIR at level: CELL");
+        ok(false == pmConceptGetRequired("CELL.EXPOSURE", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned false for CELL.EXPOSURE at level: CELL");
+        ok(false == pmConceptGetRequired("CELL.DARKTIME", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned false for CELL.DARKTIME at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.TRIMSEC", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.TRIMSEC at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.BIASSEC", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.BIASSEC at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.XBIN", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.XBIN at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.YBIN", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.YBIN at level: CELL");
+        ok(false == pmConceptGetRequired("CELL.TIMESYS", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned false for CELL.TIMESYS at level: CELL");
+        ok(false == pmConceptGetRequired("CELL.TIME", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned false for CELL.TIME at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.X0", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.X0 at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.Y0", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.Y0 at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.XSIZE", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.XSIZE at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.YSIZE", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.YSIZE at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.XWINDOW", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.XWINDOW at level: CELL");
+        ok(true == pmConceptGetRequired("CELL.YWINDOW", PM_FPA_LEVEL_CELL),
+          "pmConceptGetRequired() returned true for CELL.YWINDOW at level: CELL");
+
+        // The 2nd pmConceptsInit() should return FALSE after pmConceptsInit() is called
+        ok(false == pmConceptsInit(), "pmConceptsInit() returned FALSE");
+
+        pmConceptsDone();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+    // --------------------------------------------------------------------
+    // Tests for pmConceptsBlankFPA()
+    // Verify error with NULL input.
+    {
+        psMemId id = psMemGetId();
+        bool rc = pmConceptsBlankFPA(NULL);
+        ok(rc == false, "pmConceptsBlankFPA() returned FALSE with NULL input");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // -----------------------------------------------------------------------------
+    // Tests for pmConceptsBlankFPA()
+    // Call with valid data.  We test by ensuring the first 5 metadata items were
+    // added to fpa->concepts.
+    {
+        psMemId id = psMemGetId();
+        bool mdok;
+        char *tmpStr;
+        psF32 tmpF32;
+        psF64 tmpF64;
+        psS32 tmpS32;
+        pmFPA *fpa = pmFPAAlloc(NULL);
+        bool rc = false;
+
+        // First junk items to fpa->concepts so that we know they are later blanked.
+        rc|= psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "FPA.TELESCOPE", PS_META_REPLACE, "", "JUNK");
+        rc|= psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "FPA.INSTRUMENT", PS_META_REPLACE, "", "JUNK");
+        rc|= psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "FPA.DETECTOR", PS_META_REPLACE, "", "JUNK");
+        rc|= psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "FPA.CAMERA", PS_META_REPLACE, "", "JUNK");
+        rc|= psMetadataAddF32(fpa->concepts, PS_LIST_TAIL, "FPA.FOCUS", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddF32(fpa->concepts, PS_LIST_TAIL, "FPA.AIRMASS", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "FPA.FILTERID", PS_META_REPLACE, "", "JUNK");
+        rc|= psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "FPA.FILTER", PS_META_REPLACE, "", "JUNK");
+        rc|= psMetadataAddF32(fpa->concepts, PS_LIST_TAIL, "FPA.POSANGLE", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "FPA.RADECSYS", PS_META_REPLACE, "", "JUNK");
+        rc|= psMetadataAddF64(fpa->concepts, PS_LIST_TAIL, "FPA.RA", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddF64(fpa->concepts, PS_LIST_TAIL, "FPA.DEC", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "FPA.OBSTYPE", PS_META_REPLACE, "", "JUNK");
+        rc|= psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "FPA.OBJECT", PS_META_REPLACE, "", "JUNK");
+        rc|= psMetadataAddF64(fpa->concepts, PS_LIST_TAIL, "FPA.ALT", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddF64(fpa->concepts, PS_LIST_TAIL, "FPA.AZ", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddS32(fpa->concepts, PS_LIST_TAIL, "FPA.TIMESYS", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddF32(fpa->concepts, PS_LIST_TAIL, "FPA.TEMP", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddF32(fpa->concepts, PS_LIST_TAIL, "FPA.EXPOSURE", PS_META_REPLACE, "", 22.0);
+        ok(rc, "Set dummy data in fpa->concepts");
+
+        rc = pmConceptsBlankFPA(fpa);
+        ok(rc == true, "pmConceptsBlankFPA() returned TRUE with valid input data");
+        tmpStr = psMetadataLookupStr(&mdok, fpa->concepts, "FPA.TELESCOPE");
+        ok(mdok && !strcmp(tmpStr, ""), "FPA.TELESCOPE was cleared (%s)", tmpStr);
+        tmpStr = psMetadataLookupStr(&mdok, fpa->concepts, "FPA.INSTRUMENT");
+        ok(mdok && !strcmp(tmpStr, ""), "FPA.INSTRUMENT was cleared (%s)", tmpStr);
+        tmpStr = psMetadataLookupStr(&mdok, fpa->concepts, "FPA.DETECTOR");
+        ok(mdok && !strcmp(tmpStr, ""), "FPA.DETECTOR was cleared (%s)", tmpStr);
+        tmpStr = psMetadataLookupStr(&mdok, fpa->concepts, "FPA.CAMERA");
+        ok(mdok && !strcmp(tmpStr, ""), "FPA.CAMERA was cleared (%s)", tmpStr);
+        tmpF32 = psMetadataLookupF32(&mdok, fpa->concepts, "FPA.FOCUS");
+        ok(mdok && isnan(tmpF32), "FPA.FOCUS was cleared (%f)", tmpF32);
+        tmpF32 = psMetadataLookupF32(&mdok, fpa->concepts, "FPA.AIRMASS");
+        ok(mdok && isnan(tmpF32), "FPA.AIRMASS was cleared (%f)", tmpF32);
+        tmpStr = psMetadataLookupStr(&mdok, fpa->concepts, "FPA.FILTERID");
+        ok(mdok && !strcmp(tmpStr, ""), "FPA.FILTERID was cleared (%s)", tmpStr);
+        tmpStr = psMetadataLookupStr(&mdok, fpa->concepts, "FPA.FILTER");
+        ok(mdok && !strcmp(tmpStr, ""), "FPA.FILTER was cleared (%s)", tmpStr);
+        tmpF32 = psMetadataLookupF32(&mdok, fpa->concepts, "FPA.POSANGLE");
+        ok(mdok && isnan(tmpF32), "FPA.POSANGLE was cleared (%f)", tmpF32);
+        tmpStr = psMetadataLookupStr(&mdok, fpa->concepts, "FPA.RADECSYS");
+        ok(mdok && !strcmp(tmpStr, ""), "FPA.RADECSYS was cleared (%s)", tmpStr);
+        tmpF64 = psMetadataLookupF64(&mdok, fpa->concepts, "FPA.RA");
+        ok(mdok && isnan(tmpF64), "FPA.RA was cleared (%f)", tmpF64);
+        tmpF64 = psMetadataLookupF64(&mdok, fpa->concepts, "FPA.DEC");
+        ok(mdok && isnan(tmpF64), "FPA.DEC was cleared (%f)", tmpF64);
+        tmpStr = psMetadataLookupStr(&mdok, fpa->concepts, "FPA.OBSTYPE");
+        ok(mdok && !strcmp(tmpStr, ""), "FPA.OBSTYPE was cleared (%s)", tmpStr);
+        tmpStr = psMetadataLookupStr(&mdok, fpa->concepts, "FPA.OBJECT");
+        ok(mdok && !strcmp(tmpStr, ""), "FPA.OBJECT was cleared (%s)", tmpStr);
+        tmpF64 = psMetadataLookupF64(&mdok, fpa->concepts, "FPA.ALT");
+        ok(mdok && isnan(tmpF64), "FPA.ALT was cleared (%f)", tmpF64);
+        tmpF64 = psMetadataLookupF64(&mdok, fpa->concepts, "FPA.AZ");
+        ok(mdok && isnan(tmpF64), "FPA.AZ was cleared (%f)", tmpF64);
+        tmpS32 = psMetadataLookupS32(&mdok, fpa->concepts, "FPA.TIMESYS");
+        ok(mdok && -1 == tmpS32, "FPA.TIMESYS was cleared (%d)", tmpS32);
+        // XXX: Add code to make sure it was cleared.
+        psMetadataItem *tmpMI = psMetadataLookup(fpa->concepts, "FPA.TIME");
+        ok(tmpMI != NULL, "FPA.TIME was cleared");
+        tmpF32 = psMetadataLookupF32(&mdok, fpa->concepts, "FPA.TEMP");
+        ok(mdok && isnan(tmpF32), "FPA.TEMP was cleared (%f)", tmpF32);
+        tmpF32 = psMetadataLookupF32(&mdok, fpa->concepts, "FPA.TEMP");
+        ok(mdok && isnan(tmpF32), "FPA.TEMP was cleared (%f)", tmpF32);
+
+        psFree(fpa);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // -----------------------------------------------------------------------------
+    // Tests for pmConceptsBlankChip()
+    // Call with valid data.  We test by ensuring the first 5 metadata items were
+    // added to chip->concepts.
+    {
+        psMemId id = psMemGetId();
+        bool mdok;
+        psF32 tmpF32;
+        psS32 tmpS32;
+        pmChip *chip = pmChipAlloc(NULL, NULL);
+        bool rc = false;
+
+        // First junk items to chip->concepts so that we know they are later blanked.
+        rc|= psMetadataAddS32(chip->concepts, PS_LIST_TAIL, "CHIP.XPARITY", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(chip->concepts, PS_LIST_TAIL, "CHIP.YPARITY", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(chip->concepts, PS_LIST_TAIL, "CHIP.X0", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(chip->concepts, PS_LIST_TAIL, "CHIP.Y0", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(chip->concepts, PS_LIST_TAIL, "CHIP.XSIZE", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(chip->concepts, PS_LIST_TAIL, "CHIP.YSIZE", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddF32(chip->concepts, PS_LIST_TAIL, "CHIP.TEMP", PS_META_REPLACE, "", 22.0);
+        ok(rc, "Set dummy data in chip->concepts");
+
+        rc = pmConceptsBlankChip(chip);
+        tmpS32 = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.XPARITY");
+        ok(mdok && 0 == tmpS32, "CHIP.XPARITY was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.YPARITY");
+        ok(mdok && 0 == tmpS32, "CHIP.YPARITY was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.X0");
+        ok(mdok && 0 == tmpS32, "CHIP.X0 was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.Y0");
+        ok(mdok && 0 == tmpS32, "CHIP.Y0 was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.XSIZE");
+        ok(mdok && 0 == tmpS32, "CHIP.XSIZE was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.YSIZE");
+        ok(mdok && 0 == tmpS32, "CHIP.YSIZE was cleared (%d)", tmpS32);
+        tmpF32 = psMetadataLookupF32(&mdok, chip->concepts, "CHIP.TEMP");
+        ok(mdok && isnan(tmpF32), "CHIP.TEMP was cleared (%d)", tmpF32);
+
+        psFree(chip);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+    // -----------------------------------------------------------------------------
+    // Tests for pmConceptsBlankCell()
+    // Call with valid data.  We test by ensuring the first 5 metadata items were
+    // added to cell->concepts.
+    {
+        psMemId id = psMemGetId();
+        bool mdok;
+        psF32 tmpF32;
+        psS32 tmpS32;
+        pmCell *cell = pmCellAlloc(NULL, NULL);
+        bool rc = false;
+
+        // First junk items to cell->concepts so that we know they are later blanked.
+        rc|= psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.GAIN", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.READNOISE", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.XPARITY", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.YPARITY", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.READDIR", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.SATURATION", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.BAD", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.EXPOSURE", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.DARKTIME", PS_META_REPLACE, "", 22.0);
+        rc|= psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.XBIN", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.YBIN", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.TIMESYS", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.X0", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.Y0", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.XSIZE", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.YSIZE", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.XWINDOW", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.YWINDOW", PS_META_REPLACE, "", 22);
+        rc|= psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.", PS_META_REPLACE, "", 22);
+
+
+        ok(rc, "Set dummy data in cell->concepts");
+
+        rc = pmConceptsBlankCell(cell);
+        ok(rc == true, "pmConceptsBlankCELL() returned TRUE with valid input data");
+
+        tmpF32 = psMetadataLookupF32(&mdok, cell->concepts, "CELL.GAIN");
+        ok(mdok && isnan(tmpF32), "CELL.GAIN was cleared (%f)", tmpF32);
+        tmpF32 = psMetadataLookupF32(&mdok, cell->concepts, "CELL.READNOISE");
+        ok(mdok && isnan(tmpF32), "CELL.READNOISE was cleared (%f)", tmpF32);
+        tmpF32 = psMetadataLookupF32(&mdok, cell->concepts, "CELL.SATURATION");
+        ok(mdok && isnan(tmpF32), "CELL.SATURATION was cleared (%f)", tmpF32);
+        tmpF32 = psMetadataLookupF32(&mdok, cell->concepts, "CELL.BAD");
+        ok(mdok && isnan(tmpF32), "CELL.BAD was cleared (%f)", tmpF32);
+
+        tmpS32 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.XPARITY");
+        ok(mdok && 0 == tmpS32, "CELL.XPARITY was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.YPARITY");
+        ok(mdok && 0 == tmpS32, "CELL.YPARITY was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.READDIR");
+        ok(mdok && 0 == tmpS32, "CELL.READDIR was cleared (%d)", tmpS32);
+        tmpF32 = psMetadataLookupF32(&mdok, cell->concepts, "CELL.EXPOSURE");
+        ok(mdok && isnan(tmpF32), "CELL.EXPOSURE was cleared (%f)", tmpF32);
+        tmpF32 = psMetadataLookupF32(&mdok, cell->concepts, "CELL.DARKTIME");
+        ok(mdok && isnan(tmpF32), "CELL.DARKTIME was cleared (%f)", tmpF32);
+        // XXX: Add code to make sure it was cleared.
+        psMetadataItem *tmpMI = psMetadataLookup(cell->concepts, "CELL.TRIMSEC");
+        ok(tmpMI != NULL, "CELL.TRIMSEC was cleared");
+        tmpMI = psMetadataLookup(cell->concepts, "CELL.BIASSEC");
+        ok(tmpMI != NULL, "CELL.BIASSEC was cleared");
+        tmpS32 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.XBIN");
+        ok(mdok && 0 == tmpS32, "CELL.XBIN was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.YBIN");
+        ok(mdok && 0 == tmpS32, "CELL.YBIN was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.TIMESYS");
+        ok(mdok && -1 == tmpS32, "CELL.TIMESYS was cleared (%d)", tmpS32);
+        // XXX: Add code to make sure it was cleared.
+        tmpMI = psMetadataLookup(cell->concepts, "CELL.TIME");
+        ok(tmpMI != NULL, "CELL.TIME was cleared");
+        tmpS32 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.X0");
+        ok(mdok && 0 == tmpS32, "CELL.X0 was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.Y0");
+        ok(mdok && 0 == tmpS32, "CELL.Y0 was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.XSIZE");
+        ok(mdok && 0 == tmpS32, "CELL.XSIZE was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.YSIZE");
+        ok(mdok && 0 == tmpS32, "CELL.YSIZE was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.XWINDOW");
+        ok(mdok && 0 == tmpS32, "CELL.XWINDOW was cleared (%d)", tmpS32);
+        tmpS32 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.YWINDOW");
+        ok(mdok && 0 == tmpS32, "CELL.YWINDOW was cleared (%d)", tmpS32);
+
+        psFree(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/concepts/tap_pmConceptsAverage.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/concepts/tap_pmConceptsAverage.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/concepts/tap_pmConceptsAverage.c	(revision 22157)
@@ -0,0 +1,443 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+// XXX: Use better name for the temporary FITS file
+// XXX: The code to generate and free the FPA hierarchy was copied from
+// tap-pmFPA.c.  EIther include it directly, or library, or something.
+// Also, get rid of the manual free functions and use psFree() once
+// it correctly frees child members
+// XXX: For the genSimpleFPA() code, add IDs to each function so that
+// the values set in each chip-?cell-?hdu-?image are unique
+// XXX: For the genSimpleFPA() code, write masks and weights as well
+
+#define CHIP_ALLOC_NAME        "ChipName"
+#define CELL_ALLOC_NAME        "CellName"
+#define MISC_NUM                32
+#define MISC_NAME              "META00"
+#define MISC_NAME2             "META01"
+#define NUM_BIAS_DATA           10
+#define TEST_NUM_ROWS           4
+#define TEST_NUM_COLS           4
+#define NUM_READOUTS            3
+#define NUM_CELLS               10
+#define NUM_CHIPS               8
+#define NUM_FPAS		4
+#define NUM_HDUS                5
+#define BASE_IMAGE              10
+#define BASE_MASK               40
+#define BASE_WEIGHT             70
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+
+psPlaneTransform *PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM()
+{
+    psPlaneTransform *pt = psPlaneTransformAlloc(1, 1);
+    pt->x->coeff[1][0] = 1.0;
+    pt->y->coeff[0][1] = 1.0;
+    return(pt);
+}
+
+psPlaneDistort *PS_CREATE_4D_IDENTITY_PLANE_DISTORT()
+{
+    psPlaneDistort *pd = psPlaneDistortAlloc(1, 1, 1, 1);
+    pd->x->coeff[1][0][0][0] = 1.0;
+    pd->y->coeff[0][1][0][0] = 1.0;
+    return(pd);
+}
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(readout);
+}
+
+/******************************************************************************
+generateSimpleCell(): This function generates a pmCell data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmCell *generateSimpleCell(pmChip *chip)
+{
+    pmCell *cell = pmCellAlloc(chip, CELL_ALLOC_NAME);
+
+    psMetadataAddS32(cell->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(cell->readouts, NUM_READOUTS);
+    cell->hdu = pmHDUAlloc("cellExtName");
+    for (int i = 0 ; i < NUM_READOUTS ; i++) {
+        cell->readouts->data[i] = psMemDecrRefCounter((psPtr) generateSimpleReadout(cell));
+    }
+
+    // First try to read data from ../dataFiles, then try dataFiles.
+    bool rc = pmConfigFileRead(&cell->hdu->format, "../dataFiles/camera0/format0.config", "Camera format 0");
+    if (!rc) {
+        rc = pmConfigFileRead(&cell->hdu->format, "dataFiles/camera0/format0.config", "Camera format 0");
+        if (!rc) {
+            diag("pmConfigFileRead() was unsuccessful (from generateSimpleCell())");
+	}
+    }
+
+    cell->hdu->images = psArrayAlloc(NUM_HDUS);
+    cell->hdu->masks = psArrayAlloc(NUM_HDUS);
+    cell->hdu->weights = psArrayAlloc(NUM_HDUS);
+    for (int k = 0 ; k < NUM_HDUS ; k++) {
+        cell->hdu->images->data[k]  = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        cell->hdu->masks->data[k]   = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        cell->hdu->weights->data[k] = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(cell->hdu->images->data[k], (float) (BASE_IMAGE+k));
+        psImageInit(cell->hdu->masks->data[k], (psU8) (BASE_MASK+k));
+        psImageInit(cell->hdu->weights->data[k], (float) (BASE_WEIGHT+k));
+    }
+
+    //XXX: Should the region be set some other way?  Like through the various config files?
+//    psRegion *region = psRegionAlloc(0.0, TEST_NUM_COLS-1, 0.0, TEST_NUM_ROWS-1);
+    psRegion *region = psRegionAlloc(0.0, 0.0, 0.0, 0.0);
+    // You shouldn't have to remove the key from the metadata.  Find out how to simply change the key value.
+    psMetadataRemoveKey(cell->concepts, "CELL.TRIMSEC");
+    psMetadataAddPtr(cell->concepts, PS_LIST_TAIL|PS_META_REPLACE, "CELL.TRIMSEC", PS_DATA_REGION, "I am a region", region);
+    psFree(region);
+    return(cell);
+}
+
+/******************************************************************************
+generateSimpleChip(): This function generates a pmChip data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmChip *generateSimpleChip(pmFPA *fpa)
+{
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    chip->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    chip->fromFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    psMetadataAddS32(chip->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(chip->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(chip->cells, NUM_CELLS);
+    for (int i = 0 ; i < NUM_CELLS ; i++) {
+        chip->cells->data[i] = psMemDecrRefCounter((psPtr) generateSimpleCell(chip));
+    }
+    return(chip);
+}
+
+/******************************************************************************
+generateSimpleFPA(): This function generates a pmFPA data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmFPA* generateSimpleFPA(psMetadata *camera)
+{
+    pmFPA* fpa = pmFPAAlloc(camera);
+    fpa->fromTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toSky = psProjectionAlloc(0.0,0.0,10.0,10.0,PS_PROJ_TAN);
+    psMetadataAddS32(fpa->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    if (camera != NULL) {
+        psMetadataAddS32((psMetadata *) fpa->camera, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    }
+    psMetadataAddS32(fpa->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(fpa->chips, NUM_CHIPS);
+    for (int i = 0 ; i < NUM_CHIPS ; i++) {
+        fpa->chips->data[i] = psMemDecrRefCounter((psPtr) generateSimpleChip(fpa));
+    }
+    pmConceptsBlankFPA(fpa);
+    return(fpa);
+}
+
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(50);
+
+    // ----------------------------------------------------------------------
+    // pmConceptsAverageFPAs() tests: NULL input pmFPA *target
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA *targetFPA = generateSimpleFPA(camera);
+        pmFPA *sourceFPA[NUM_FPAS];
+
+        sourceFPA[0] = generateSimpleFPA(camera);
+        psList *sources = psListAlloc(sourceFPA[0]);
+        for (int fpaID = 1 ; fpaID < NUM_FPAS ; fpaID++) {
+            sourceFPA[fpaID] = generateSimpleFPA(camera);
+            bool rc = psListAdd(sources, PS_LIST_HEAD, sourceFPA[fpaID]);
+            ok(rc, "Successfully added FPA %d to list", fpaID);
+	}
+        ok(!pmConceptsAverageFPAs(NULL, sources), "pmConceptsAverage(NULL, sources) returned FALSE");
+
+        for (int fpaID = 0 ; fpaID < NUM_FPAS ; fpaID++) {
+            psFree(sourceFPA[fpaID]);
+	}
+        psFree(sources);
+        psFree(targetFPA);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmConceptsAverageFPAs() tests: NULL input psList *sources
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA *targetFPA = generateSimpleFPA(camera);
+        pmFPA *sourceFPA[NUM_FPAS];
+
+        sourceFPA[0] = generateSimpleFPA(camera);
+        psList *sources = psListAlloc(sourceFPA[0]);
+        for (int fpaID = 1 ; fpaID < NUM_FPAS ; fpaID++) {
+            sourceFPA[fpaID] = generateSimpleFPA(camera);
+            bool rc = psListAdd(sources, PS_LIST_HEAD, sourceFPA[fpaID]);
+            ok(rc, "Successfully added FPA %d to list", fpaID);
+	}
+        ok(!pmConceptsAverageFPAs(targetFPA, NULL), "pmConceptsAverage(NULL, sources) returned FALSE");
+
+        for (int fpaID = 0 ; fpaID < NUM_FPAS ; fpaID++) {
+            psFree(sourceFPA[fpaID]);
+	}
+        psFree(sources);
+        psFree(targetFPA);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmConceptsAverageFPAs() tests: acceptable inputs
+    // XXX: There's a memory leak somewhere in this test, not sure where.
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA *targetFPA = generateSimpleFPA(camera);
+        pmFPA *sourceFPA[NUM_FPAS];
+        psMetadata *cameras[NUM_FPAS];
+
+        // Ensure that the FPA.TIME average is computed correctly
+        psList *sources = NULL;
+        psF64 actualTime = 0.0;
+        for (int fpaID = 0 ; fpaID < NUM_FPAS ; fpaID++) {
+            cameras[fpaID] = psMetadataAlloc();
+            sourceFPA[fpaID] = generateSimpleFPA(cameras[fpaID]);
+            psTime *fpaTime = psMetadataLookupPtr(NULL, (sourceFPA[fpaID])->concepts, "FPA.TIME");
+            // Add a small value to the psTime so that we can test/ensure that pmConceptsAverageFPAs()
+            // is actually calculating an average.
+            fpaTime->sec += (double) (fpaID * 1000);
+            actualTime+= psTimeToMJD(fpaTime);
+            if (0 == fpaID) {
+                sources = psListAlloc(sourceFPA[fpaID]);
+	    } else {
+                bool rc = psListAdd(sources, PS_LIST_HEAD, sourceFPA[fpaID]);
+                ok(rc, "Successfully added FPA %d to list", fpaID);
+	    }
+	}
+        // XXX: The memory leak occurs during the following single call
+        ok(pmConceptsAverageFPAs(targetFPA, sources), "pmConceptsAverage(targetFPA, sources) returned TRUE");
+        actualTime/= (float) NUM_FPAS;
+        psTime *fpaTime = psMetadataLookupPtr(NULL, targetFPA->concepts, "FPA.TIME");
+        ok(abs(actualTime - psTimeToMJD(fpaTime)) < 1e-4, "pmConceptsAverageFPAs() calculated the average time correctly");
+
+        // Replace the FPA.TIMESYS with a non-conforming value, verify that pmConceptsAverageFPAs() returns an error
+
+        psTimeType timeSys = psMetadataLookupS32(NULL, sourceFPA[0]->concepts, "FPA.TIMESYS");
+        psMetadataAddS32(sourceFPA[NUM_FPAS-1]->concepts, PS_LIST_HEAD, "FPA.TIMESYS", PS_META_REPLACE, NULL, timeSys+10);
+        ok(!pmConceptsAverageFPAs(targetFPA, sources), "pmConceptsAverage(NULL, sources) returned FALSE with nonequal FPA.TIMESYS metadata");
+        psMetadataAddS32(sourceFPA[NUM_FPAS-1]->concepts, PS_LIST_HEAD, "FPA.TIMESYS", PS_META_REPLACE, NULL, timeSys);
+        ok(pmConceptsAverageFPAs(targetFPA, sources), "pmConceptsAverage(NULL, sources) returned TRUE with equal FPA.TIMESYS metadata");
+
+
+        // Replace the FPA.TIMESYS with a non-conforming value, verify that pmConceptsAverageFPAs() returnes an error        
+        psString filter = psMetadataLookupStr(NULL, sourceFPA[0]->concepts, "FPA.FILTER");
+        psMetadataAddStr(sourceFPA[NUM_FPAS-1]->concepts, PS_LIST_HEAD, "FPA.FILTER", PS_META_REPLACE, NULL, "BOGUS STRING");
+        ok(!pmConceptsAverageFPAs(targetFPA, sources), "pmConceptsAverage(NULL, sources) returned FALSE with nonequal FPA.FILTER metadata");
+        psMetadataAddStr(sourceFPA[NUM_FPAS-1]->concepts, PS_LIST_HEAD, "FPA.FILTER", PS_META_REPLACE, NULL, filter);
+        ok(pmConceptsAverageFPAs(targetFPA, sources), "pmConceptsAverage(NULL, sources) returned TRUE with equal FPA.FILTER metadata");
+
+        // Free data, check for memory leaks
+        for (int fpaID = 0 ; fpaID < NUM_FPAS ; fpaID++) {
+            psFree(sourceFPA[fpaID]);
+            psFree(cameras[fpaID]);
+	}
+        psFree(sources);
+//        psFree(filter);
+        psFree(targetFPA);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // ----------------------------------------------------------------------
+    // pmConceptsAverageCells() tests: NULL input pmFPA *target
+    // bool pmConceptsAverageCells(pmCell *target, psList *sources, psRegion *trimsec, psRegion *biassec, bool same)
+    {
+        psMemId id = psMemGetId();
+        psMetadata *tgtCamera = psMetadataAlloc();
+        pmFPA *tgtFPA = generateSimpleFPA(tgtCamera);
+        pmChip *tgtChip = tgtFPA->chips->data[0];
+        pmCell *tgtCell = tgtChip->cells->data[0];
+        psMetadata *srcCamera = psMetadataAlloc();
+        pmFPA *srcFPA = generateSimpleFPA(srcCamera);
+        pmChip *srcChip = srcFPA->chips->data[0];
+
+        psList *sources = NULL;
+        psF32 tstGain = 0.0;
+        psF32 tstReadnoise = 0.0;
+        psF32 tstExposure = 0.0;
+        psF32 tstDarktime = 0.0;
+        psF32 tstSaturation = 0.0;
+        psF32 tstBad = 0.0;
+        
+        for (int cellID = 0 ; cellID < srcChip->cells->n ; cellID++) {
+            pmCell *cell = srcChip->cells->data[cellID];
+            // Set the various concepts which we will test later
+            psMetadataAddF32(cell->concepts, PS_LIST_HEAD, "CELL.GAIN", PS_META_REPLACE, NULL, 0.0 + (float) cellID);
+            tstGain+= 0.0 + (float) cellID;
+            psMetadataAddF32(cell->concepts, PS_LIST_HEAD, "CELL.READNOISE", PS_META_REPLACE, NULL, 10.0 + (float) cellID);
+            tstReadnoise+= 10.0 + (float) cellID;
+            psMetadataAddF32(cell->concepts, PS_LIST_HEAD, "CELL.EXPOSURE", PS_META_REPLACE, NULL, 20.0 + (float) cellID);
+            tstExposure+= 20.0 + (float) cellID;
+            psMetadataAddF32(cell->concepts, PS_LIST_HEAD, "CELL.DARKTIME", PS_META_REPLACE, NULL, 30.0 + (float) cellID);
+            tstDarktime+= 30.0 + (float) cellID;
+            psMetadataAddF32(cell->concepts, PS_LIST_HEAD, "CELL.SATURATION", PS_META_REPLACE, NULL, 40.0 + (float) cellID);
+            if (cellID == 0)
+                tstSaturation = 40.0 + (float) cellID;
+            psMetadataAddF32(cell->concepts, PS_LIST_HEAD, "CELL.BAD", PS_META_REPLACE, NULL, 50.0 + (float) cellID);
+            if (cellID == (srcChip->cells->n - 1))
+                tstBad = 50.0 + (float) cellID;
+            if (cellID == 0) {
+                sources = psListAlloc(srcChip->cells->data[cellID]);
+	    } else {
+                bool rc = psListAdd(sources, PS_LIST_HEAD, srcChip->cells->data[cellID]);
+                ok(rc, "Successfully added cell %d to list", cellID);
+	    }
+	}
+        tstGain /= (psF64) srcChip->cells->n;
+        tstReadnoise /= (psF64) srcChip->cells->n;
+        tstExposure /= (psF64) srcChip->cells->n;
+        tstDarktime /= (psF64) srcChip->cells->n;
+
+        psRegion *trimsec = psRegionAlloc(0, 1, 2, 3);
+        psRegion *biassec = psRegionAlloc(4, 5, 6, 7);
+
+        // Ensure pmConceptsAverageCells() returns NULL with NULL tgtCell input
+        bool rc = pmConceptsAverageCells(NULL, sources, trimsec, biassec, false);
+        ok(!rc, "pmConceptsAverageCells() returned FALSE with NULL sources input");
+
+        // Ensure pmConceptsAverageCells() returns NULL with NULL sources input
+        rc = pmConceptsAverageCells(tgtCell, NULL, trimsec, biassec, false);
+        ok(!rc, "pmConceptsAverageCells() returned FALSE with NULL targetCell input");
+
+        // Ensure pmConceptsAverageCells() returns NULL with sources->n = 0
+        sources->n = 0;
+        rc = pmConceptsAverageCells(tgtCell, sources, trimsec, biassec, false);
+        ok(!rc, "pmConceptsAverageCells() returned FALSE with NULL sources->n = 0");
+        sources->n = NUM_CELLS;
+
+        // Call pmConceptsAverageCells() with acceptable input data
+        rc = pmConceptsAverageCells(tgtCell, sources, trimsec, biassec, false);
+        ok(rc, "pmConceptsAverageCells() returned TRUE with acceptable input data");
+        psF32 tmpF32;
+        tmpF32 = psMetadataLookupF32(NULL, tgtCell->concepts, "CELL.GAIN");
+        ok(abs(tmpF32 - tstGain) < 1e-4, "pmConceptsAverageCells() calculated the average CELL.GAIN correctly");
+        tmpF32 = psMetadataLookupF32(NULL, tgtCell->concepts, "CELL.READNOISE");
+        ok(abs(tmpF32 - tstReadnoise) < 1e-4, "pmConceptsAverageCells() calculated the average CELL.READNOISE correctly");
+        tmpF32 = psMetadataLookupF32(NULL, tgtCell->concepts, "CELL.EXPOSURE");
+        ok(abs(tmpF32 - tstExposure) < 1e-4, "pmConceptsAverageCells() calculated the average CELL.EXPOSURE correctly");
+        tmpF32 = psMetadataLookupF32(NULL, tgtCell->concepts, "CELL.DARKTIME");
+        ok(abs(tmpF32 - tstDarktime) < 1e-4, "pmConceptsAverageCells() calculated the average CELL.DARKTIME correctly");
+        tmpF32 = psMetadataLookupF32(NULL, tgtCell->concepts, "CELL.SATURATION");
+        ok(abs(tmpF32 - tstSaturation) < 1e-4, "pmConceptsAverageCells() calculated the average CELL.SATURATION correctly (%f %f)", tmpF32, tstSaturation);
+        tmpF32 = psMetadataLookupF32(NULL, tgtCell->concepts, "CELL.BAD");
+        ok(abs(tmpF32 - tstBad) < 1e-4, "pmConceptsAverageCells() calculated the average CELL.BAD correctly");
+        psRegion *tstTrimsec = psMetadataLookupPtr(NULL, tgtCell->concepts, "CELL.TRIMSEC");
+        ok(tstTrimsec->x0 == 0.0 && tstTrimsec->x1 == 1.0 && tstTrimsec->y0 == 2.0 && tstTrimsec->y1 == 3.0,
+           "pmConceptsAverageCells() set the CELL>TRIMSEC region correctly");
+        psRegion *tstBiassec = psMetadataLookupPtr(NULL, tgtCell->concepts, "CELL.BIASSEC");
+        ok(tstBiassec->x0 == 4.0 && tstBiassec->x1 == 5.0 && tstBiassec->y0 == 6.0 && tstBiassec->y1 == 7.0,
+           "pmConceptsAverageCells() set the CELL>TRIMSEC region correctly");
+
+        psS32 tmpS32;
+        pmCell *srcCell = srcChip->cells->data[NUM_CELLS - 1];
+
+        // Set the CELL.TIMESYS metadata unequal, verify that error occurs
+        rc = psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.TIMESYS", PS_META_REPLACE, NULL, 232);
+        tmpS32 = psMetadataLookupS32(&rc, srcCell->concepts, "CELL.TIMESYS");
+        rc = pmConceptsAverageCells(tgtCell, sources, trimsec, biassec, false);
+        ok(!rc, "pmConceptsAverageCells() returned FALSE with non equal CELL.TIMESYS metadata");
+        psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.TIMESYS", PS_META_REPLACE, NULL, tmpS32);
+
+        // Set the CELL.READDIR metadata unequal, verify that error occurs
+        rc = psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.READDIR", PS_META_REPLACE, NULL, 232);
+        tmpS32 = psMetadataLookupS32(&rc, srcCell->concepts, "CELL.READDIR");
+        rc = pmConceptsAverageCells(tgtCell, sources, trimsec, biassec, false);
+        ok(!rc, "pmConceptsAverageCells() returned FALSE with non equal CELL.READDIR metadata");
+        psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.READDIR", PS_META_REPLACE, NULL, tmpS32);
+
+        // Set the CELL.XBIN metadata unequal, verify that error occurs
+        rc = psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.XBIN", PS_META_REPLACE, NULL, 232);
+        tmpS32 = psMetadataLookupS32(&rc, srcCell->concepts, "CELL.XBIN");
+        rc = pmConceptsAverageCells(tgtCell, sources, trimsec, biassec, false);
+        ok(!rc, "pmConceptsAverageCells() returned FALSE with non equal CELL.XBIN metadata");
+        psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.XBIN", PS_META_REPLACE, NULL, tmpS32);
+
+        // Set the CELL.YBIN metadata unequal, verify that error occurs
+        rc = psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.YBIN", PS_META_REPLACE, NULL, 232);
+        tmpS32 = psMetadataLookupS32(&rc, srcCell->concepts, "CELL.YBIN");
+        rc = pmConceptsAverageCells(tgtCell, sources, trimsec, biassec, false);
+        ok(!rc, "pmConceptsAverageCells() returned FALSE with non equal CELL.YBIN metadata");
+        psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.YBIN", PS_META_REPLACE, NULL, tmpS32);
+
+        // Set the CELL.X0 metadata unequal, verify that error occurs
+        rc = psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.X0", PS_META_REPLACE, NULL, 232);
+        tmpS32 = psMetadataLookupS32(&rc, srcCell->concepts, "CELL.X0");
+        rc = pmConceptsAverageCells(tgtCell, sources, trimsec, biassec, true);
+        ok(!rc, "pmConceptsAverageCells() returned FALSE with non equal CELL.X0 metadata (same = true)");
+        psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.X0", PS_META_REPLACE, NULL, tmpS32);
+
+        // Set the CELL.Y0 metadata unequal, verify that error occurs
+        rc = psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.Y0", PS_META_REPLACE, NULL, 232);
+        tmpS32 = psMetadataLookupS32(&rc, srcCell->concepts, "CELL.Y0");
+        rc = pmConceptsAverageCells(tgtCell, sources, trimsec, biassec, true);
+        ok(!rc, "pmConceptsAverageCells() returned FALSE with non equal CELL.Y0 metadata (same = true)");
+        psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.Y0", PS_META_REPLACE, NULL, tmpS32);
+
+        // Set the CELL.X0 metadata unequal, verify that no error occurs with same==false
+        rc = psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.X0", PS_META_REPLACE, NULL, 232);
+        tmpS32 = psMetadataLookupS32(&rc, srcCell->concepts, "CELL.X0");
+        rc = pmConceptsAverageCells(tgtCell, sources, trimsec, biassec, false);
+        ok(!rc, "pmConceptsAverageCells() returned FALSE with non equal CELL.X0 metadata (same = false)");
+        psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.X0", PS_META_REPLACE, NULL, tmpS32);
+
+        // Set the CELL.Y0 metadata unequal, verify that no error occurs with same==false
+        rc = psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.Y0", PS_META_REPLACE, NULL, 232);
+        tmpS32 = psMetadataLookupS32(&rc, srcCell->concepts, "CELL.Y0");
+        rc = pmConceptsAverageCells(tgtCell, sources, trimsec, biassec, false);
+        ok(!rc, "pmConceptsAverageCells() returned FALSE with non equal CELL.Y0 metadata (same = false)");
+        psMetadataAddS32(srcCell->concepts, PS_LIST_HEAD, "CELL.Y0", PS_META_REPLACE, NULL, tmpS32);
+
+        psFree(tgtFPA);
+        psFree(srcFPA);
+        psFree(tgtCamera);
+        psFree(srcCamera);
+        psFree(biassec);
+        psFree(trimsec);
+        psFree(sources);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+}
+
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/concepts/tap_pmConceptsPhotcode.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/concepts/tap_pmConceptsPhotcode.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/concepts/tap_pmConceptsPhotcode.c	(revision 22157)
@@ -0,0 +1,116 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(25);
+
+
+    // ----------------------------------------------------------------------
+    // pmConceptsPhotcodeForView() tests: NULL pmConfig input
+    // psString pmConceptsPhotcodeForView(pmConfig *config, pmFPAfile *file, const pmFPAview *view)
+    {
+        psMemId id = psMemGetId();
+        pmConfig *config =pmConfigAlloc();
+        pmFPAfile *file = pmFPAfileAlloc();
+        pmFPAview *view = pmFPAviewAlloc(32);
+        ok(NULL == pmConceptsPhotcodeForView(NULL, file, view),
+          "pmConceptsPhotcodeForView(NULL, file, view) returned NULL");
+        psFree(config);
+        psFree(file);
+        psFree(view);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmConceptsPhotcodeForView() tests: NULL pmConfig input
+    // psString pmConceptsPhotcodeForView(pmConfig *config, pmFPAfile *file, const pmFPAview *view)
+    {
+        psMemId id = psMemGetId();
+        pmConfig *config =pmConfigAlloc();
+        pmFPAfile *file = pmFPAfileAlloc();
+        pmFPAview *view = pmFPAviewAlloc(32);
+        ok(NULL == pmConceptsPhotcodeForView(NULL, file, view),
+          "pmConceptsPhotcodeForView(NULL, file, view) returned NULL");
+        psFree(config);
+        psFree(file);
+        psFree(view);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmConceptsPhotcodeForView() tests: NULL pmFPAfile input
+    // psString pmConceptsPhotcodeForView(pmConfig *config, pmFPAfile *file, const pmFPAview *view)
+    {
+        psMemId id = psMemGetId();
+        pmConfig *config =pmConfigAlloc();
+        pmFPAfile *file = pmFPAfileAlloc();
+        pmFPAview *view = pmFPAviewAlloc(32);
+        ok(NULL == pmConceptsPhotcodeForView(config, NULL, view),
+          "pmConceptsPhotcodeForView(config, NULL, view) returned NULL");
+        psFree(config);
+        psFree(file);
+        psFree(view);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmConceptsPhotcodeForView() tests: NULL pmFPAview input
+    // psString pmConceptsPhotcodeForView(pmConfig *config, pmFPAfile *file, const pmFPAview *view)
+    {
+        psMemId id = psMemGetId();
+        pmConfig *config =pmConfigAlloc();
+        pmFPAfile *file = pmFPAfileAlloc();
+        pmFPAview *view = pmFPAviewAlloc(32);
+        ok(NULL == pmConceptsPhotcodeForView(config, file, NULL),
+          "pmConceptsPhotcodeForView(config, file, NULL) returned NULL");
+        psFree(config);
+        psFree(file);
+        psFree(view);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmConceptsPhotcodeForView() tests: acceptable inputs
+    // psString pmConceptsPhotcodeForView(pmConfig *config, pmFPAfile *file, const pmFPAview *view)
+    {
+        psMemId id = psMemGetId();
+        psString str[3];
+        str[0] = "ARGS:";
+        str[1] = "-site";
+//        str[2] = "../dataFiles/SampleIPPConfig";
+        str[2] = "../config/data/SampleIPPConfig";
+        psS32 argc = 3;
+        pmConfig *config = pmConfigRead(&argc, str, "RecipeName");
+        ok(config, "pmConfigRead() returned non-NULL");
+        pmFPAfile *file = pmFPAfileAlloc();
+        // XXX: Insert code to read a pmFPAfile correctly
+        pmFPAview *view = pmFPAviewAlloc(0);
+
+        skip_start(!config, 2, "Skipping tests because pmConfigRead() failed");        
+        bool rc;
+        psMetadata *recipe  = psMetadataLookupPtr(&rc, config->recipes, "PPIMAGE");
+        char *rule = psMetadataLookupStr(&rc, recipe, "PHOTCODE.RULE");
+        psString goodPhotcode = pmFPAfileNameFromRule(rule, file, view);
+
+        psString testPhotcode = pmConceptsPhotcodeForView(config, file, view);
+        ok(testPhotcode, "pmConceptsPhotcodeForView(config, file, view) returned non-NULL");
+        ok(!strcmp(goodPhotcode, testPhotcode), "pmConceptsPhotcodeForView() produced the correct string");
+        skip_end();
+        psFree(config);
+        psFree(file);
+        psFree(view);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/concepts/tap_pmConceptsUpdate.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/concepts/tap_pmConceptsUpdate.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/concepts/tap_pmConceptsUpdate.c	(revision 22157)
@@ -0,0 +1,248 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+// XXX: Use better name for the temporary FITS file
+// XXX: The code to generate and free the FPA hierarchy was copied from
+// tap-pmFPA.c.  EIther include it directly, or library, or something.
+// Also, get rid of the manual free functions and use psFree() once
+// it correctly frees child members
+// XXX: For the genSimpleFPA() code, add IDs to each function so that
+// the values set in each chip-?cell-?hdu-?image are unique
+// XXX: For the genSimpleFPA() code, write masks and weights as well
+
+#define CHIP_ALLOC_NAME        "ChipName"
+#define CELL_ALLOC_NAME        "CellName"
+#define MISC_NUM                32
+#define MISC_NAME              "META00"
+#define MISC_NAME2             "META01"
+#define NUM_BIAS_DATA           10
+#define TEST_NUM_ROWS           4
+#define TEST_NUM_COLS           4
+#define NUM_READOUTS            3
+#define NUM_CELLS               10
+#define NUM_CHIPS               8
+#define NUM_HDUS                5
+#define BASE_IMAGE              10
+#define BASE_MASK               40
+#define BASE_WEIGHT             70
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+
+psPlaneTransform *PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM()
+{
+    psPlaneTransform *pt = psPlaneTransformAlloc(1, 1);
+    pt->x->coeff[1][0] = 1.0;
+    pt->y->coeff[0][1] = 1.0;
+    return(pt);
+}
+
+psPlaneDistort *PS_CREATE_4D_IDENTITY_PLANE_DISTORT()
+{
+    psPlaneDistort *pd = psPlaneDistortAlloc(1, 1, 1, 1);
+    pd->x->coeff[1][0][0][0] = 1.0;
+    pd->y->coeff[0][1][0][0] = 1.0;
+    return(pd);
+}
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(readout);
+}
+
+/******************************************************************************
+generateSimpleCell(): This function generates a pmCell data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmCell *generateSimpleCell(pmChip *chip)
+{
+    pmCell *cell = pmCellAlloc(chip, CELL_ALLOC_NAME);
+
+    psMetadataAddS32(cell->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(cell->readouts, NUM_READOUTS);
+    cell->hdu = pmHDUAlloc("cellExtName");
+    for (int i = 0 ; i < NUM_READOUTS ; i++) {
+        cell->readouts->data[i] = psMemDecrRefCounter((psPtr) generateSimpleReadout(cell));
+    }
+
+    // First try to read data from ../dataFiles, then try dataFiles.
+    bool rc = pmConfigFileRead(&cell->hdu->format, "../dataFiles/camera0/format0.config", "Camera format 0");
+    if (!rc) {
+        rc = pmConfigFileRead(&cell->hdu->format, "dataFiles/camera0/format0.config", "Camera format 0");
+        if (!rc) {
+            diag("pmConfigFileRead() was unsuccessful (from generateSimpleCell())");
+	}
+    }
+
+    cell->hdu->images = psArrayAlloc(NUM_HDUS);
+    cell->hdu->masks = psArrayAlloc(NUM_HDUS);
+    cell->hdu->weights = psArrayAlloc(NUM_HDUS);
+    for (int k = 0 ; k < NUM_HDUS ; k++) {
+        cell->hdu->images->data[k]  = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        cell->hdu->masks->data[k]   = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        cell->hdu->weights->data[k] = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(cell->hdu->images->data[k], (float) (BASE_IMAGE+k));
+        psImageInit(cell->hdu->masks->data[k], (psU8) (BASE_MASK+k));
+        psImageInit(cell->hdu->weights->data[k], (float) (BASE_WEIGHT+k));
+    }
+
+    psRegion *region = psRegionAlloc(0.0, 0.0, 0.0, 0.0);
+    // You shouldn't have to remove the key from the metadata.
+    psMetadataRemoveKey(cell->concepts, "CELL.TRIMSEC");
+    psMetadataAddPtr(cell->concepts, PS_LIST_TAIL|PS_META_REPLACE, "CELL.TRIMSEC", PS_DATA_REGION, "I am a region", region);
+    psFree(region);
+    return(cell);
+}
+
+/******************************************************************************
+generateSimpleChip(): This function generates a pmChip data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmChip *generateSimpleChip(pmFPA *fpa)
+{
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    chip->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    chip->fromFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    psMetadataAddS32(chip->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(chip->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(chip->cells, NUM_CELLS);
+    for (int i = 0 ; i < NUM_CELLS ; i++) {
+        chip->cells->data[i] = psMemDecrRefCounter((psPtr) generateSimpleCell(chip));
+    }
+
+    // XXX: Add code to initialize chip pmConcepts
+
+
+    return(chip);
+}
+
+/******************************************************************************
+generateSimpleFPA(): This function generates a pmFPA data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmFPA* generateSimpleFPA(psMetadata *camera)
+{
+    pmFPA* fpa = pmFPAAlloc(camera);
+    fpa->fromTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toSky = psProjectionAlloc(0.0,0.0,10.0,10.0,PS_PROJ_TAN);
+    psMetadataAddS32(fpa->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    if (camera != NULL) {
+        psMetadataAddS32((psMetadata *) fpa->camera, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    }
+    psMetadataAddS32(fpa->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+
+    psArrayRealloc(fpa->chips, NUM_CHIPS);
+    for (int i = 0 ; i < NUM_CHIPS ; i++) {
+        fpa->chips->data[i] = psMemDecrRefCounter((psPtr) generateSimpleChip(fpa));
+    }
+    pmConceptsBlankFPA(fpa);
+    return(fpa);
+}
+
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(7);
+
+
+    // ----------------------------------------------------------------------
+    // pmConceptsUpdate() tests: NULL inputs
+    // bool pmConceptsUpdate(const pmFPA *fpa, const pmChip *chip, const pmCell *cell)
+    {
+        psMemId id = psMemGetId();
+        ok(pmConceptsUpdate(NULL, NULL, NULL), "pmConceptsUpdate(NULL, NULL, NULL) returned TRUE");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+    
+    // pmConceptsUpdate() tests: acceptable inputs
+    // XXX: Must add tests for the BIASSEC concepts
+    {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA *fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        pmCell *cell = chip->cells->data[0];
+        bool rc = pmConceptsUpdate(NULL, NULL, cell);
+
+        // Calculate expected myTrimsec
+        bool xStatus, yStatus; // Status of MD lookups
+        psImageBinning *binning = psImageBinningAlloc();
+        binning->nXbin = psMetadataLookupS32(&xStatus, cell->concepts, "CELL.XBIN");
+        binning->nYbin = psMetadataLookupS32(&yStatus, cell->concepts, "CELL.YBIN");
+        psRegion *goodTrimsec = psMetadataLookupPtr(NULL, cell->concepts, "CELL.TRIMSEC");
+        double goodTrimsecX0 = goodTrimsec->x0;
+        double goodTrimsecX1 = goodTrimsec->x1;
+        double goodTrimsecY0 = goodTrimsec->y0;
+        double goodTrimsecY1 = goodTrimsec->y1;
+        goodTrimsecX0/= binning->nXbin;
+        goodTrimsecX1/= binning->nXbin;
+        goodTrimsecY0/= binning->nYbin;
+        goodTrimsecY1/= binning->nYbin;
+        goodTrimsecX0 = (int) goodTrimsec->x0;
+        if (goodTrimsec->x1 > (int)goodTrimsec->x1) {
+            goodTrimsecX1 = (int)goodTrimsec->x1 + 1;
+         } else {
+            goodTrimsecX1 = (int)goodTrimsec->x1;
+	 }
+        goodTrimsecY0 = (int)goodTrimsec->y0;
+        if (goodTrimsec->y1 > (int)goodTrimsec->y1) {
+            goodTrimsecY1 = (int)goodTrimsec->y1 + 1;
+	} else {
+            goodTrimsecY1 = (int)goodTrimsec->y1;
+	}
+        psFree(binning);
+
+        // Add CELL.TRIMSEC.UPDATE concept to cell->concepts
+        psMetadataAddS32(cell->concepts, PS_LIST_HEAD, "CELL.TRIMSEC.UPDATE", 0, NULL, 32);
+        psS32 num = psMetadataLookupS32(&rc, cell->concepts, "CELL.TRIMSEC.UPDATE");
+        ok(rc, "Successfully added CELL.TRIMSEC.UPDATE to cell->concepts (%d)", num);
+
+        // Add CELL.BIASSEC.UPDATE concept to cell->concepts
+        psMetadataAddS32(cell->concepts, PS_LIST_HEAD, "CELL.BIASSEC.UPDATE", 0, NULL, 32);
+        num = psMetadataLookupS32(&rc, cell->concepts, "CELL.BIASSEC.UPDATE");
+        ok(rc, "Successfully added CELL.BIASSEC.UPDATE to cell->concepts (%d)", num);
+
+        // Call pmConceptsUpdate to update the CELL.TRIMSEC concept
+        ok(rc, "pmConceptsUpdate(NULL, NULL, pmCell) returned TRUE");
+
+        psRegion *testTrimsec = psMetadataLookupPtr(NULL, cell->concepts, "CELL.TRIMSEC"); // Trim section
+        ok(testTrimsec->x1 == goodTrimsecX1 && testTrimsec->y1 == goodTrimsecY1 &&
+           testTrimsec->x0 == goodTrimsecX0 && testTrimsec->y0 == goodTrimsecY0,
+           "pmConceptsUpdate() updated CELL.TRIMSEC correctly (%f %f)",
+            testTrimsec->x1, testTrimsec->y1);
+
+        // XXX: This fails but the problem might be with the test code
+        if (0) {
+            num = psMetadataLookupS32(&rc, cell->concepts, "CELL.TRIMSEC.UPDATE");
+            ok(!rc, "pmConceptsUpdate() removed CELL.TRIMSEC.UPDATE to cell->concepts (%d)", num);
+        }
+
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/.cvsignore	(revision 22157)
@@ -0,0 +1,7 @@
+.deps
+.libs
+Makefile
+Makefile.in
+temp
+tst_pmConfig
+SampleIPPConfig
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/Makefile.am	(revision 22157)
@@ -0,0 +1,35 @@
+AM_CPPFLAGS = \
+	$(SRCINC) \
+	-I$(top_srcdir)/test/tap/src \
+	-I$(top_srcdir)/test/pstap/src \
+	$(PSMODULES_CFLAGS)
+
+AM_LDFLAGS = \
+	$(top_builddir)/src/libpsmodules.la  \
+	$(top_builddir)/test/tap/src/libtap.la \
+	$(top_builddir)/test/pstap/src/libpstap.la \
+	$(PSMODULES_LIBS)
+
+TEST_PROGS = \
+	tap_pmConfig \
+	tap_pmConfigCommand \
+	tap_pmConfigMask \
+	tap_pmErrorCodes \
+	tap_pmVersion
+
+check_DATA =
+
+
+EXTRA_DIST = data
+
+if BUILD_TESTS
+bin_PROGRAMS = $(TEST_PROGS)
+TESTS = $(TEST_PROGS)
+else
+check_PROGRAMS = $(TEST_PROGS)
+endif
+
+CLEANFILES = $(check_DATA) temp/* core core.* *~ *.bb *.bbg *.da gmon.out
+
+test: check
+	$(top_srcdir)/test/test.pl
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/SampleIPPConfig
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/SampleIPPConfig	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/SampleIPPConfig	(revision 22157)
@@ -0,0 +1,74 @@
+### Example .ipprc file
+    PATH            STR     .
+    DATAPATH	str	datapath
+
+PATH            STR     .
+
+### Database configuration
+DBSERVER	STR	localhost		# Database host name (for psDBInit)
+DBUSER		STR	test			# Database user name (for psDBInit)
+DBPASSWORD	STR	DB-PW			# Database password (for psDBInit)
+DBNAME          STR     test                    # ????
+DBPORT		S32	0
+
+
+### Setups for each camera system
+CAMERAS		METADATA
+	CAMERA0		STR	camera0/camera.config
+	CAMERA1		STR	camera1/camera.config
+END
+
+### psLib setup
+#TIME		STR	/home/mithrandir/price/pan-starrs/jhroot/i686-pc-linux-gnu/etc/pslib/psTime.config	# Time configuration file
+LOGLEVEL	S32	3			# Logging level; 3=INFO
+LOGFORMAT	STR	HLNM			# Log format
+LOGDEST	STR	STDOUT				# Log destination
+TRACE		METADATA			# Trace levels
+	dummyTraceFunc01	S32	1
+	dummyTraceFunc02	S32	2
+END
+ARBITRARY_STRING_S32	S32	20
+ARBITRARY_STRING_F32	F32	21.0
+ARBITRARY_STRING_F64	F64	22.0
+ARBITRARY_STRING_STR	STR	19.0
+ARBITRARY_STRING_BOOL_T	BOOL	true
+ARBITRARY_STRING_BOOL_F	BOOL	false
+=======
+    CAMERAS		METADATA
+	CAMERA0		STR	camera0/camera.config
+	CAMERA1		STR	camera1/camera.config
+    END
+
+### Setups for psLib
+    TIME		STR	time.config
+    LOGLEVEL	S32	3
+    LOGFORMAT	STR	HLNM
+    LOGDEST	STR	STDOUT
+
+### Default trace logging initializations
+    TRACE		METADATA
+	dummyTraceFacility01	S32	1
+	dummyTraceFacility02	S32	2
+    END
+
+### Predefined recipe files
+    RECIPES         METADATA                # Site-level recipes
+        R00		STR	recipes/R00.config
+        R01		STR	recipes/R01.config
+        R02		STR	recipes/R02.config
+        R03		STR	recipes/R03.config
+    END
+
+### Misc arbitraty metadata
+    ARBITRARY_STRING_S32	S32	20
+    ARBITRARY_STRING_F32	F32	21.0
+    ARBITRARY_STRING_F64	F64	22.0
+    ARBITRARY_STRING_STR	STR	19.0
+    ARBITRARY_STRING_BOOL_T	BOOL	true
+    ARBITRARY_STRING_BOOL_F	BOOL	false
+
+RECIPES         METADATA
+	MASKS			STR	recipes_masks.config
+        JUNK_RECIPE_00		STR	recipe_file00
+        JUNK_RECIPE_01		STR	recipe_file01
+END
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/basicConfig
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/basicConfig	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/basicConfig	(revision 22157)
@@ -0,0 +1,25 @@
+
+TEST MULTI
+TEST STR FOO
+TEST STR BAR
+
+PATH              STR     .
+
+# load the site-specific information from here
+SITE              STR     site.config
+
+# load the system configuration information from here
+SYSTEM            STR     system.config
+
+### psLib setup
+LOGLEVEL	S32	5			# Logging level; 3=INFO
+LOGFORMAT	STR	M			# Log format
+LOGDEST		STR	STDERR			# Log destination
+TRACEDEST	STR	STDERR			# Trace destination
+
+# place default trace lines here
+TRACE		METADATA			# Trace levels
+  err		S32	10
+# psLib.db      S32	10
+END
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera0/camera.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera0/camera.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera0/camera.config	(revision 22157)
@@ -0,0 +1,15 @@
+FORMATS         METADATA
+        C0_FM0     STR     camera0/format0.config
+        C0_FM1     STR     camera0/format1.config
+END
+
+RECIPES         METADATA
+        C0_RECIPE0          STR     camera0/recipe0.config
+        C0_RECIPE1          STR     camera0/recipe1.config
+END
+
+FPA     METADATA
+        ccd00   STR     LeftAmp RightAmp
+        ccd01   STR     LeftAmp RightAmp
+END
+ID      STR     CAMERA0
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera0/format0.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera0/format0.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera0/format0.config	(revision 22157)
@@ -0,0 +1,36 @@
+RULE    METADATA
+        F0_KEY0        STR     string20
+        F0_KEY1        STR     string21
+        F0_KEY2        S32     20
+END
+
+FILE    METADATA
+        PHU             STR     FPA
+        EXTENSIONS      STR     CELL
+        FPA.NAME        STR     EXPNUM
+END
+
+DEFAULTS        METADATA
+        F0_DEF0           STR     DefString20
+        F0_DEF1           STR     DefString21
+        F0_DEF2           F32     21.0
+        F0_DEF3           S32     22
+END
+
+CELLS   METADATA
+        left    METADATA        # Left amplifier
+                CELL.BIASSEC.SOURCE     STR     HEADER
+                CELL.TRIMSEC.SOURCE     STR     HEADER
+        END
+        right   METADATA        # Right amplifier
+                CELL.BIASSEC.SOURCE     STR     HEADER
+                CELL.TRIMSEC.SOURCE     STR     HEADER
+        END
+END
+
+ID	STR	camera0/format0.config
+
+TRANSLATION     METADATA
+        FPA.TELESCOPE   STR     TELESCOP
+        FPA.INSTRUMENT  STR     INSTRUME
+END
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera0/format1.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera0/format1.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera0/format1.config	(revision 22157)
@@ -0,0 +1,34 @@
+RULE    METADATA
+        F1_KEY0        STR     string30
+        F1_KEY1        STR     string31
+        F1_KEY2        S32     30
+END
+
+FILE    METADATA
+        PHU             STR     FPA
+        EXTENSIONS      STR     CELL
+        FPA.NAME        STR     EXPNUM
+END
+
+DEFAULTS        METADATA
+        F1_DEF0           STR     DefString30
+        F1_DEF1           STR     DefString31
+        F1_DEF2           F32     31.0
+        F1_DEF3           S32     32
+END
+
+CELLS   METADATA
+        left    METADATA        # Left amplifier
+                CELL.BIASSEC.SOURCE     STR     HEADER
+                CELL.TRIMSEC.SOURCE     STR     HEADER
+        END
+        right   METADATA        # Right amplifier
+                CELL.BIASSEC.SOURCE     STR     HEADER
+                CELL.TRIMSEC.SOURCE     STR     HEADER
+        END
+END
+
+TRANSLATION     METADATA
+        FPA.TELESCOPE   STR     TELESCOP
+        FPA.INSTRUMENT  STR     INSTRUME
+END
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera0/recipe0.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera0/recipe0.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera0/recipe0.config	(revision 22157)
@@ -0,0 +1,4 @@
+R0_KEY0		STR	RecipeString0
+R0_KEY1		STR	RecipeString1
+R0_KEY2		S32	2
+R0_KEY3		F32	3
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera0/recipe1.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera0/recipe1.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera0/recipe1.config	(revision 22157)
@@ -0,0 +1,4 @@
+R1_KEY0		STR	RecipeString10
+R1_KEY1		STR	RecipeString11
+R1_KEY2		S32	12
+R1_KEY3		F32	13
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera1/camera.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera1/camera.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera1/camera.config	(revision 22157)
@@ -0,0 +1,14 @@
+FORMATS         METADATA
+        C1_FM0     STR     camera1/format0.config
+        C1_FM1     STR     camera1/format1.config
+END
+
+RECIPES         METADATA
+        C1_RECIPE0          STR     camera1/recipe0.config
+        C1_RECIPE1          STR     camera1/recipe1.config
+END
+
+FPA     METADATA
+        ccd00   STR     LeftAmp RightAmp
+        ccd01   STR     LeftAmp RightAmp
+END
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera1/format0.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera1/format0.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera1/format0.config	(revision 22157)
@@ -0,0 +1,34 @@
+RULE    METADATA
+        F0_KEY0        STR     string40
+        F0_KEY1        STR     string41
+        F0_KEY2        S32     420
+END
+
+FILE    METADATA
+        PHU             STR     FPA
+        EXTENSIONS      STR     CELL
+        FPA.NAME        STR     EXPNUM
+END
+
+DEFAULTS        METADATA
+        F0_DEF0           STR     DefString40
+        F0_DEF1           STR     DefString41
+        F0_DEF2           F32     41.0
+        F0_DEF3           S32     44
+END
+
+CELLS   METADATA
+        left    METADATA        # Left amplifier
+                CELL.BIASSEC.SOURCE     STR     HEADER
+                CELL.TRIMSEC.SOURCE     STR     HEADER
+        END
+        right   METADATA        # Right amplifier
+                CELL.BIASSEC.SOURCE     STR     HEADER
+                CELL.TRIMSEC.SOURCE     STR     HEADER
+        END
+END
+
+TRANSLATION     METADATA
+        FPA.TELESCOPE   STR     TELESCOP
+        FPA.INSTRUMENT  STR     INSTRUME
+END
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera1/format1.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera1/format1.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera1/format1.config	(revision 22157)
@@ -0,0 +1,34 @@
+RULE    METADATA
+        F1_KEY0        STR     string80
+        F1_KEY1        STR     string81
+        F1_KEY2        S32     80
+END
+
+FILE    METADATA
+        PHU             STR     FPA
+        EXTENSIONS      STR     CELL
+        FPA.NAME        STR     EXPNUM
+END
+
+DEFAULTS        METADATA
+        F1_DEF0           STR     DefString80
+        F1_DEF1           STR     DefString81
+        F1_DEF2           F32     81.0
+        F1_DEF3           S32     82
+END
+
+CELLS   METADATA
+        left    METADATA        # Left amplifier
+                CELL.BIASSEC.SOURCE     STR     HEADER
+                CELL.TRIMSEC.SOURCE     STR     HEADER
+        END
+        right   METADATA        # Right amplifier
+                CELL.BIASSEC.SOURCE     STR     HEADER
+                CELL.TRIMSEC.SOURCE     STR     HEADER
+        END
+END
+
+TRANSLATION     METADATA
+        FPA.TELESCOPE   STR     TELESCOP
+        FPA.INSTRUMENT  STR     INSTRUME
+END
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera1/recipe0.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera1/recipe0.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera1/recipe0.config	(revision 22157)
@@ -0,0 +1,4 @@
+R0_KEY0		STR	RecipeString120
+R0_KEY1		STR	RecipeString121
+R0_KEY2		S32	122
+R0_KEY3		F32	123
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera1/recipe1.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera1/recipe1.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/camera1/recipe1.config	(revision 22157)
@@ -0,0 +1,4 @@
+R1_KEY0		STR	RecipeString230
+R1_KEY1		STR	RecipeString2311
+R1_KEY2		S32	232
+R1_KEY3		F32	233
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/path2/SampleIPPConfig2
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/path2/SampleIPPConfig2	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/path2/SampleIPPConfig2	(revision 22157)
@@ -0,0 +1,32 @@
+### Example .ipprc file
+
+### Database configuration
+DBSERVER	STR	ippdb.ifa.hawaii.edu	# Database host name (for psDBInit)
+DBUSER		STR	ipp			# Database user name (for psDBInit)
+DBPASSWORD	STR	password		# Database password (for psDBInit)
+
+### Setups for each camera system
+CAMERAS		METADATA
+	MEGACAM_RAW	STR	megacam_raw.config
+	MEGACAM_SPLICE	STR	megacam_splice.config
+	GPC1_RAW	STR	gpc1_raw.config
+	LRIS_BLUE	STR	lris_blue.config
+	LRIS_RED	STR	lris_red.config
+END
+
+### psLib setup
+#TIME		STR	/home/mithrandir/price/pan-starrs/jhroot/i686-pc-linux-gnu/etc/pslib/psTime.config	# Time configuration file
+LOGLEVEL	S32	3			# Logging level; 3=INFO
+LOGFORMAT	STR	HLNM			# Log format
+LOGDEST	STR	STDOUT				# Log destination
+TRACE		METADATA			# Trace levels
+	dummyTraceFunc01	S32	1
+	dummyTraceFunc02	S32	2
+END
+ARBITRARY_STRING_S32	S32	20
+ARBITRARY_STRING_F32	F32	21.0
+ARBITRARY_STRING_F64	F64	22.0
+ARBITRARY_STRING_STR	STR	19.0
+ARBITRARY_STRING_BOOL_T	BOOL	true
+ARBITRARY_STRING_BOOL_F	BOOL	false
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/recipe_file00
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/recipe_file00	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/recipe_file00	(revision 22157)
@@ -0,0 +1,2 @@
+recipe00		BOOL    TRUE
+recipe01		BOOL    FALSE
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/recipe_file01
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/recipe_file01	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/recipe_file01	(revision 22157)
@@ -0,0 +1,2 @@
+recipe00		BOOL    FALSE
+recipe01		BOOL    TRUE
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/recipes/recipe1.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/recipes/recipe1.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/recipes/recipe1.config	(revision 22157)
@@ -0,0 +1,12 @@
+
+KEY1	STR	VALUE1
+KEY2	STR	VALUE2
+
+RECIPE1_ALT METADATA
+  KEY2  STR     VALUE2_ALT
+END
+
+KEY3 MULTI 
+KEY3 STR V1
+KEY3 STR V2
+KEY3 STR V3
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/recipes_masks.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/recipes_masks.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/recipes_masks.config	(revision 22157)
@@ -0,0 +1,10 @@
+### Recipe specifying values for various mask concepts
+BLANK           U8      0x01            # The pixel is blank or has no (valid) data
+FLAT            U8      0x02            # The pixel is non-positive in the flat-field
+DETECTOR        U8      0x02            # The detector pixel is bad (e.g., bad column, charge trap)
+SAT             U8      0x04            # The pixel is saturated in the image of interest
+BAD             U8      0x04            # The pixel is low in the image of interest
+RANGE           U8      0x04            # The pixel is out of range in the image of interest
+CR              U8      0x08            # The pixel is probably a CR
+SUSPECT         U8      0x40            # The pixel is suspected of being bad, but may not be
+MARK            U8      0x80            # The pixel is marked as temporarily ignored
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/site.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/site.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/site.config	(revision 22157)
@@ -0,0 +1,32 @@
+
+# place your data directories here and refer to as path://PATH/remainder
+DATAPATH	METADATA
+	TEST1	STR	dataTest1
+	TEST2	STR	dataTest2
+END
+
+# List of tessellations, and their DVO CATDIR
+TESSELLATIONS	METADATA
+	TESS1		STR	path://TEST1/skycells/
+	TESS2		STR	path://TEST2/skycells/
+END
+
+# dvo databases used for output
+DVO.CATDIRS	METADATA
+	CATDIR1		STR	path://TEST1/catdir/
+END
+
+# dvo databases used for psastro reference
+PSASTRO.CATDIRS	METADATA
+	CATDIR1		STR	path://TEST1/catdir/
+END
+
+# nebulous server
+NEB_SERVER	STR	http://alala:80/nebulous	# Nebulous server
+
+# Database configuration
+DBSERVER	STR	ipp000			# Database host name (for psDBInit)
+DBNAME		STR	XXX			# Database name (for psDBInit)
+DBUSER		STR	XXX			# Database user name (for psDBInit)
+DBPASSWORD	STR	XXX			# Database password (for psDBInit)
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/system.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/system.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/system.config	(revision 22157)
@@ -0,0 +1,24 @@
+## system-wide options : these are concepts not specific to any camera or recipe
+
+### Setups for each camera system
+CAMERAS		METADATA
+	TEST1			STR	testCamera1/camera.config
+END
+
+### camera names as expected by DVO
+DVO.CAMERAS		METADATA
+	TEST1			STR	testCamera1
+END
+
+# Header keywords for skycell concepts; required because DVO doesn't read HIERARCH
+SKYCELLS	METADATA
+	FPA.TIME	STR	MJD-OBS
+	CELL.TIME	STR	MJD-OBS
+	FPA.EXPOSURE	STR	EXPTIME
+	CELL.EXPOSURE	STR	EXPTIME
+	FPA.AIRMASS	STR	AIRMASS
+END
+
+RECIPES		METADATA		# Site-level recipes
+	RECIPE1		STR		recipes/recipe1.config  # Simple recipe
+END
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/testCamera1/camera.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/testCamera1/camera.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/testCamera1/camera.config	(revision 22157)
@@ -0,0 +1,12 @@
+
+RECIPES	METADATA	# Site-level recipes
+	RECIPE1	STR	testCamera1/recipe1.config  # Simple recipe
+END
+
+FORMATS METADATA
+  	RAW  	STR	testCamera1/format.config
+END
+
+FPA	METADATA
+	chip	STR	cell
+END
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/testCamera1/format.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/testCamera1/format.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/testCamera1/format.config	(revision 22157)
@@ -0,0 +1,53 @@
+# sample MEF format with HDU = cell
+
+# How to identify this type
+RULE	METADATA
+	TELESCOP	STR	TestTelescope
+	DETECTOR	STR	TestDetector
+	NAXIS           S32     0
+	EXTEND		BOOL	T
+END
+
+# How to read this data
+FILE	METADATA
+	PHU		STR	FPA	# The FITS file represents an entire FPA
+	EXTENSIONS	STR	CELL	# The extensions represent cells
+	FPA.NAME	STR	EXPNUM	# A PHU keyword for unique identifier within the hierarchy level
+END
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	# Extension name, chip name:type
+	amp		STR	chip:LeftAmp:left
+END
+
+# Specify the cell data
+CELLS	METADATA
+	left	METADATA	# Left amplifier
+		CELL.BIASSEC.SOURCE	STR	HEADER
+		CELL.TRIMSEC.SOURCE	STR	HEADER
+		CELL.BIASSEC		STR	BIASSEC
+		CELL.TRIMSEC		STR	DATASEC
+		CELL.XPARITY		S32	1 # We could have specified this as a DEFAULT, but this works
+		CELL.X0			S32	1
+		CELL.Y0			S32	1
+	END
+END
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	FPA.AIRMASS		STR	AIRMASS
+	FPA.FILTERID		STR	FILTER
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	FPA.TELESCOPE		STR	CFHT
+	FPA.INSTRUMENT		STR	MEGACAM
+END
+
+# Where there might be some ambiguity, specify the format
+FORMATS		METADATA
+	FPA.RA		STR	HOURS
+	FPA.DEC		STR	DEGREES
+END
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/testCamera1/recipe1.config
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/testCamera1/recipe1.config	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/data/testCamera1/recipe1.config	(revision 22157)
@@ -0,0 +1,10 @@
+
+KEY1	STR	VALUE1_CAMERA
+# KEY2	STR	VALUE2_CAMERA
+
+RECIPE1_ALT METADATA
+  KEY1  STR     VALUE1_CAMERA_ALT
+END
+
+KEY3 MULTI RESET
+KEY3 STR V4
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/tap_pmConfig.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/tap_pmConfig.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/tap_pmConfig.c	(revision 22157)
@@ -0,0 +1,1284 @@
+/** @file tst_pmConfig.c
+ *
+ *  @brief Contains the tests for pmConfig.c:
+ *
+ * This code will test the pmConfig() routine.
+*       pmConfigReadParamsSet
+*       pmConfigAlloc		
+*       pmConfigSet
+*       pmConfigDone
+*       pmConfigFileRead
+*        pmConfigValidateCameraFormat
+*        pmConfigCameraFormatFromHeader
+*        pmConfigCameraByName
+?        pmConfigDB
+*        pmConfigConformHeader
+-        pmConfigFileSets
+-        pmConfigFileSetsMD
+*        pmConfigConvertFilename
+*        pmConfigRead
+ * XXXX: Must determine what to do with NULL arguments, then test it.
+ *
+ *  @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-06-10 20:58:28 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+#define ERR_TRACE_LEVEL         10
+#define	VERBOSE			0
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel(".", 0);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(181);
+    
+    // --------------------------------------------------------------------
+    // --------------------------------------------------------------------
+    // Tests for pmConfigReadParamsSet()
+    {
+        psMemId id = psMemGetId();
+        bool oldReadCameraConfig = pmConfigReadParamsSet(false);
+        ok(oldReadCameraConfig == true, "pmConfigReadParamsSet() returned old value correctly");
+        oldReadCameraConfig = pmConfigReadParamsSet(true);
+        ok(oldReadCameraConfig == false, "pmConfigReadParamsSet() returned old value correctly");
+        oldReadCameraConfig = pmConfigReadParamsSet(true);
+        ok(oldReadCameraConfig == true, "pmConfigReadParamsSet() returned new value correctly");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+    
+    
+    // --------------------------------------------------------------------
+    // --------------------------------------------------------------------
+    // Tests for pmConfigAlloc()
+    // XXX: Add a MemCheckConfig() function to verify the return type
+    {
+        psMemId id = psMemGetId();
+
+        pmConfig *myConfig = pmConfigAlloc();
+        ok(myConfig != NULL, "pmConfigAlloc() returned non-NULL");
+        skip_start(myConfig == NULL, 10, "skipping tests because pmConfigAlloc() returned NULL");
+        ok(myConfig->site == NULL, "pmConfigAlloc() initialized pmConfig->site properly");
+        ok(myConfig->camera == NULL, "pmConfigAlloc() initialized pmConfig->camera properly");
+        ok(myConfig->cameraName == NULL, "pmConfigAlloc() initialized pmConfig->cameraName properly");
+        ok(myConfig->format == NULL, "pmConfigAlloc() initialized pmConfig->format properly");
+        ok(myConfig->formatName == NULL, "pmConfigAlloc() initialized pmConfig->formatName properly");
+        ok(myConfig->recipes != NULL && psMemCheckMetadata(myConfig->recipes),
+            "pmConfigAlloc() initialized pmConfig->recipes properly");
+        ok(myConfig->recipesRead == PM_RECIPE_SOURCE_NONE, "pmConfigAlloc() initialized pmConfig->recipesRead properly");
+        ok(myConfig->recipeSymbols != NULL && psMemCheckMetadata(myConfig->recipeSymbols),
+            "pmConfigAlloc() initialized pmConfig->recipesSymbols properly");
+        ok(myConfig->arguments != NULL && psMemCheckMetadata(myConfig->arguments),
+            "pmConfigAlloc() initialized pmConfig->arguments properly");
+        ok(myConfig->database == NULL, "pmConfigAlloc() initialized pmConfig->database properly");
+        ok(myConfig->defaultRecipe == NULL, "pmConfigAlloc() initialized pmConfig->defaultRecipe properly");
+
+        skip_end();
+        psFree(myConfig);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    
+    
+    // --------------------------------------------------------------------
+    // --------------------------------------------------------------------
+    // Test pmConfigSet()
+    // Test with NULL input
+    // XX: Test with empty string?
+    {
+        psMemId id = psMemGetId();
+        pmConfigSet(NULL);
+        ok(true, "called pmConfigSet() with NULL input pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_VALUE == tmpErr->code,
+          "pmConfigSet(NULL) created the PS_ERR_BAD_PARAMETER_VALUE error");
+        psFree(tmpErr);
+        psErrorClear();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+    
+    
+    // Test with acceptable config file name, with a simple path
+    {
+        pmConfigDone();
+        psMemId id = psMemGetId();
+        psMetadata *config = NULL;
+        bool rc = pmConfigFileRead(&config, "SampleIPPConfig2", "DESCRIPTION");
+        ok(rc == false, "pmConfigFileRead() returned FALSE before calling pmConfigSet()");
+        pmConfigSet("../dataFiles/path2:dataFiles/path2");
+        rc = pmConfigFileRead(&config, "SampleIPPConfig2", "DESCRIPTION");
+        ok(rc == true, "pmConfigFileRead() returned TRUE after calling pmConfigSet() (test 1)");
+        psFree(config);
+        pmConfigDone();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test with acceptable config file name, with a compound path
+    {
+        pmConfigDone();
+        psMemId id = psMemGetId();
+        psMetadata *config = NULL;
+        bool rc = pmConfigFileRead(&config, "SampleIPPConfig2", "DESCRIPTION");
+        ok(rc == false, "pmConfigFileRead() returned FALSE before calling pmConfigSet()");
+        pmConfigSet("junk:../dataFiles/path2:dataFiles/path2:data/path5");
+        rc = pmConfigFileRead(&config, "SampleIPPConfig2", "DESCRIPTION");
+        ok(rc == true, "pmConfigFileRead() returned TRUE after calling pmConfigSet() (test 2)");
+        psFree(config);
+        pmConfigDone();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+    // --------------------------------------------------------------------
+    // --------------------------------------------------------------------
+    // Test pmConfigDone(): ensure it frees memory allocated by pmConfigSet();
+    // This also ensures that pmConfigSet() psFrees memory between calls
+    {
+        psMemId id = psMemGetId();
+        pmConfigSet("junk01");
+        pmConfigSet("junk:../dataFiles/path2:dataFiles/path2:data/path5");
+        pmConfigSet("junk02");
+        pmConfigDone();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks: pmConfigDone()");
+    }
+
+
+
+    // --------------------------------------------------------------------
+    // --------------------------------------------------------------------
+    // Test pmConfigFileRead()
+    // Test with NULL config pointer
+    // XXX: There's a memory leak if the first argument points to data that's
+    // already allocated (pmConfigFileRead() frees the first argument)
+    {
+        psMemId id = psMemGetId();
+        psString name = "foo1";
+        psString desc = "foo2";
+        bool rc = pmConfigFileRead(NULL, name, desc);
+        ok(rc == false, "pmConfigFileRead() returned FALSE with NULL config pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_NULL == tmpErr->code,
+          "pmConfigFileRead() created the PS_ERR_BAD_PARAMETER_NULL error");
+        psFree(tmpErr);
+        psErrorClear();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test with NULL name pointer
+    // XXX: Test with empty name string?
+    {
+        psMemId id = psMemGetId();
+        psMetadata *config = psMetadataAlloc();
+        psString desc = "foo2";
+        bool rc = pmConfigFileRead(&config, NULL, desc);
+        ok(rc == false, "pmConfigFileRead() returned FALSE with NULL name pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_VALUE == tmpErr->code,
+          "pmConfigFileRead() created the PS_ERR_BAD_PARAMETER_VALUE error");
+        psFree(tmpErr);
+        psErrorClear();
+        psFree(config);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test with NULL desc pointer
+    // XXX: Test with empty desc string and otherwise acceptable inputs.
+    // That generated errors elsewhere.
+    {
+        psMemId id = psMemGetId();
+        psMetadata *config = psMetadataAlloc();
+        psString name = "foo1";
+        bool rc = pmConfigFileRead(&config, name, NULL);
+        ok(rc == false, "pmConfigFileRead() returned FALSE with NULL desc pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_VALUE == tmpErr->code,
+          "pmConfigFileRead() created the PS_ERR_BAD_PARAMETER_VALUE error");
+        psFree(tmpErr);
+        psErrorClear();
+        psFree(config);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // Test with acceptable config file name, no paths
+    {
+        psMemId id = psMemGetId();
+        psMetadata *config = NULL;
+        psString name = "../dataFiles/SampleIPPConfig";
+        psString name2 = "dataFiles/SampleIPPConfig";
+        psString desc = "DESCRIPTION";
+        // First try to read data from ../dataFiles, then try dataFiles.
+        bool rc = pmConfigFileRead(&config, name, desc);
+        if (!rc) {
+            rc = pmConfigFileRead(&config, name2, desc);
+	}
+        ok(rc == true, "pmConfigFileRead() returned TRUE with acceptable inputs");
+
+        // Test the several arbitrary config file strings.
+        psS32 tmpInt = psMetadataLookupS32(&rc, config, "ARBITRARY_STRING_S32");
+        ok(rc == true && tmpInt == 20, "pmConfigFileRead() properly set metadata (S32)");
+        psF32 tmpFloat = psMetadataLookupS32(&rc, config, "ARBITRARY_STRING_F32");
+        ok(rc == true && tmpFloat == 21.0, "pmConfigFileRead() properly set metadata (F32)");
+        psF64 tmpDub = psMetadataLookupS32(&rc, config, "ARBITRARY_STRING_F64");
+        ok(rc == true && tmpDub == 22.0, "pmConfigFileRead() properly set metadata (F64)");
+        psBool tmpBool = psMetadataLookupS32(&rc, config, "ARBITRARY_STRING_BOOL_T");
+        ok(rc == true && tmpBool == true, "pmConfigFileRead() properly set metadata (bool)");
+        tmpBool = psMetadataLookupS32(&rc, config, "ARBITRARY_STRING_BOOL_F");
+        ok(rc == true && tmpBool == false, "pmConfigFileRead() properly set metadata (bool)");
+        psFree(config);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test with acceptable config file name, no paths
+    {
+        psMemId id = psMemGetId();
+        psMetadata *config = NULL;
+        psString name = "../dataFiles/SampleIPPConfig";
+        psString name2 = "dataFiles/SampleIPPConfig";
+        psString desc = "DESCRIPTION";
+        bool rc = pmConfigFileRead(&config, name, desc);
+        if (!rc) {
+            rc = pmConfigFileRead(&config, name2, desc);
+	}
+        ok(rc == true, "pmConfigFileRead() returned TRUE with acceptable inputs");
+
+        // Test the several arbitrary config file strings.
+        psS32 tmpInt = psMetadataLookupS32(&rc, config, "ARBITRARY_STRING_S32");
+        ok(rc == true && tmpInt == 20, "pmConfigFileRead() properly set metadata (S32)");
+        psF32 tmpFloat = psMetadataLookupS32(&rc, config, "ARBITRARY_STRING_F32");
+        ok(rc == true && tmpFloat == 21.0, "pmConfigFileRead() properly set metadata (F32)");
+        psF64 tmpDub = psMetadataLookupS32(&rc, config, "ARBITRARY_STRING_F64");
+        ok(rc == true && tmpDub == 22.0, "pmConfigFileRead() properly set metadata (F64)");
+        psBool tmpBool = psMetadataLookupS32(&rc, config, "ARBITRARY_STRING_BOOL_T");
+        ok(rc == true && tmpBool == true, "pmConfigFileRead() properly set metadata (bool)");
+        tmpBool = psMetadataLookupS32(&rc, config, "ARBITRARY_STRING_BOOL_F");
+        ok(rc == true && tmpBool == false, "pmConfigFileRead() properly set metadata (bool)");
+        psFree(config);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test with acceptable config file name, no paths
+    {
+        psMemId id = psMemGetId();
+        psMetadata *config = NULL;
+        psString name = "../dataFiles/SampleIPPConfig";
+        psString name2 = "dataFiles/SampleIPPConfig";
+        psString desc = "DESCRIPTION";
+        bool rc = pmConfigFileRead(&config, name, desc);
+        if (!rc) {
+            rc = pmConfigFileRead(&config, name2, desc);
+	}
+        ok(rc == true, "pmConfigFileRead() returned TRUE with acceptable inputs");
+
+        // Test the several arbitrary config file strings.
+        psS32 tmpInt = psMetadataLookupS32(&rc, config, "ARBITRARY_STRING_S32");
+        ok(rc == true && tmpInt == 20, "pmConfigFileRead() properly set metadata (S32)");
+        psF32 tmpFloat = psMetadataLookupS32(&rc, config, "ARBITRARY_STRING_F32");
+        ok(rc == true && tmpFloat == 21.0, "pmConfigFileRead() properly set metadata (F32)");
+        psF64 tmpDub = psMetadataLookupS32(&rc, config, "ARBITRARY_STRING_F64");
+        ok(rc == true && tmpDub == 22.0, "pmConfigFileRead() properly set metadata (F64)");
+        psBool tmpBool = psMetadataLookupS32(&rc, config, "ARBITRARY_STRING_BOOL_T");
+        ok(rc == true && tmpBool == true, "pmConfigFileRead() properly set metadata (bool)");
+        tmpBool = psMetadataLookupS32(&rc, config, "ARBITRARY_STRING_BOOL_F");
+        ok(rc == true && tmpBool == false, "pmConfigFileRead() properly set metadata (bool)");
+        psFree(config);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test with acceptable config file name, with path
+    // Note: This also tests the pmConfigSet() function.
+    {
+        psMemId id = psMemGetId();
+        psMetadata *config = NULL;
+        psString name = "SampleIPPConfig2";
+        psString desc = "DESCRIPTION";
+        pmConfigSet("../dataFiles/path2:dataFiles/path2");
+        bool rc = pmConfigFileRead(&config, name, desc);
+        ok(rc == true, "pmConfigFileRead() returned TRUE using paths");
+        // Test the several arbitrary config file strings.
+        psS32 tmpInt = psMetadataLookupS32(&rc, config, "ARBITRARY_STRING_S32");
+        ok(rc == true && tmpInt == 20, "pmConfigFileRead() properly set metadata (S32)");
+        psF32 tmpFloat = psMetadataLookupS32(&rc, config, "ARBITRARY_STRING_F32");
+        ok(rc == true && tmpFloat == 21.0, "pmConfigFileRead() properly set metadata (F32)");
+        psF64 tmpDub = psMetadataLookupS32(&rc, config, "ARBITRARY_STRING_F64");
+        ok(rc == true && tmpDub == 22.0, "pmConfigFileRead() properly set metadata (F64)");
+        psBool tmpBool = psMetadataLookupS32(&rc, config, "ARBITRARY_STRING_BOOL_T");
+        ok(rc == true && tmpBool == true, "pmConfigFileRead() properly set metadata (bool)");
+        tmpBool = psMetadataLookupS32(&rc, config, "ARBITRARY_STRING_BOOL_F");
+        ok(rc == true && tmpBool == false, "pmConfigFileRead() properly set metadata (bool)");
+        psFree(config);
+        pmConfigDone();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+    // --------------------------------------------------------------------
+    // --------------------------------------------------------------------
+    // Test pmConfigValidateCameraFormat()
+    // Test with NULL valid pointer
+    // XXX: This is commented out because of a seg-fault
+    if (0) {
+        psMemId id = psMemGetId();
+        psMetadata *cameraFormat = psMetadataAlloc();
+        psMetadata *header = psMetadataAlloc();
+        bool rc = pmConfigValidateCameraFormat(NULL, cameraFormat, header);
+        ok(rc == false, "pmConfigValidateCameraFormat() returned FALSE with NULL valid pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_NULL == tmpErr->code,
+          "pmConfigValidateCameraFormat() created the PS_ERR_BAD_PARAMETER_NULL error");
+        psFree(tmpErr);
+        psErrorClear();
+        psFree(cameraFormat);
+        psFree(header);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test with NULL cameraFormat pointer
+    {
+        psMemId id = psMemGetId();
+        bool valid;
+        psMetadata *cameraFormat = psMetadataAlloc();
+        psMetadata *header = psMetadataAlloc();
+        bool rc = pmConfigValidateCameraFormat(&valid, NULL, header);
+        ok(rc == false, "pmConfigValidateCameraFormat() returned FALSE with NULL valid pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_NULL == tmpErr->code,
+          "pmConfigValidateCameraFormat() created the PS_ERR_BAD_PARAMETER_NULL error");
+        psFree(tmpErr);
+        psErrorClear();
+        psFree(cameraFormat);
+        psFree(header);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test with NULL header pointer
+    {
+        psMemId id = psMemGetId();
+        bool valid;
+        psMetadata *cameraFormat = psMetadataAlloc();
+        psMetadata *header = psMetadataAlloc();
+        bool rc = pmConfigValidateCameraFormat(&valid, cameraFormat, NULL);
+        ok(rc == false, "pmConfigValidateCameraFormat() returned FALSE with NULL valid pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_NULL == tmpErr->code,
+          "pmConfigValidateCameraFormat() created the PS_ERR_BAD_PARAMETER_NULL error");
+        psFree(tmpErr);
+        psErrorClear();
+        psFree(cameraFormat);
+        psFree(header);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // Test with acceptable input
+    // psTraceSetLevel("psModules.config", 10);
+    {
+        psMemId id = psMemGetId();
+        bool valid;
+        psMetadata *header = psMetadataAlloc();
+        psMetadata *camera = psMetadataAlloc();
+        // Test with unitialized camera metadata
+        bool rc = pmConfigValidateCameraFormat(&valid, camera, header);
+        ok(rc == false && valid == false, "pmConfigValidateCameraFormat() returned FALSE with uninitialized psMetadata");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_UNKNOWN == tmpErr->code,
+          "pmConfigValidateCameraFormat() created the PS_ERR_UNKNOWN error");
+        psFree(tmpErr);
+        psErrorClear();
+        psFree(camera);
+        psFree(header);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test with acceptable input
+    // psTraceSetLevel("psModules.config", 10);
+    // Add a test that sets the metadata value incorrectly
+    {
+        psMemId id = psMemGetId();
+        bool valid;
+        psMetadata *header = psMetadataAlloc();
+        psMetadata *camera = NULL;
+        bool rc = pmConfigFileRead(&camera, "../dataFiles/camera0/format0.config", "Camera 0 Config File");
+        if (!rc) {
+            rc = pmConfigFileRead(&camera, "dataFiles/camera0/format0.config", "Camera 0 Config File");
+	}
+        ok(rc == true, "pmConfigFileRead() read ../dataFiles/camera0/format0.config");
+        psMetadataAddStr(header, PS_LIST_TAIL, "F0_KEY0", 0, "", "string20");
+        rc = pmConfigValidateCameraFormat(&valid, camera, header);
+        ok(rc == true, "pmConfigValidateCameraFormat() returned FALSE with acceptable input, wrong values");
+        ok(valid == false, "pmConfigValidateCameraFormat() rejected header correctly");
+        psMetadataAddStr(header, PS_LIST_TAIL, "F0_KEY1", 0, "", "string21");
+        rc = pmConfigValidateCameraFormat(&valid, camera, header);
+        ok(rc == true, "pmConfigValidateCameraFormat() returned FALSE with acceptable input, wrong values");
+        ok(valid == false, "pmConfigValidateCameraFormat() rejected header correctly");
+        psMetadataAddS32(header, PS_LIST_TAIL, "F0_KEY2", 0, "", 20);
+        rc = pmConfigValidateCameraFormat(&valid, camera, header);
+        ok(rc == true, "pmConfigValidateCameraFormat() returned TRUE with acceptable input, correct values");
+        ok(valid == true, "pmConfigValidateCameraFormat() accepted header correctly");
+        psFree(camera);
+        psFree(header);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // --------------------------------------------------------------------
+    // --------------------------------------------------------------------
+    // Test pmConfigCameraFormatFromHeader()
+    //
+    // Given a FITS header, check it against all known cameras (unless we
+    // already know which camera, from pmConfigRead) and all known formats
+    // for those cameras in order to identify which is appropriate.
+    //
+    // psMetadata *pmConfigCameraFormatFromHeader(
+    //    pmConfig *config,
+    //    const psMetadata *header,
+    //    bool readRecipes)
+    // Test with NULL config pointer
+    {
+        psMemId id = psMemGetId();
+        pmConfig *config = pmConfigAlloc();
+        psMetadata *header = psMetadataAlloc();
+        bool readRecipes = false;
+        psMetadata *camera = pmConfigCameraFormatFromHeader(NULL, header, readRecipes);
+        ok(camera == NULL, "pmConfigCameraFormatFromHeader() returned NULL with NULL config pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_NULL == tmpErr->code,
+          "pmConfigCameraFormatFromHeader() created the PS_ERR_BAD_PARAMETER_NULL error");
+        psFree(tmpErr);
+        psErrorClear();
+        psFree(config);
+        psFree(header);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test with NULL header pointer
+    {
+        psMemId id = psMemGetId();
+        pmConfig *config = pmConfigAlloc();
+        psMetadata *header = psMetadataAlloc();
+        bool readRecipes = false;
+        psMetadata *camera = pmConfigCameraFormatFromHeader(config, NULL, readRecipes);
+        ok(camera == NULL, "pmConfigCameraFormatFromHeader() returned NULL with NULL header pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_NULL == tmpErr->code,
+          "pmConfigCameraFormatFromHeader() created the PS_ERR_BAD_PARAMETER_NULL error");
+        psFree(tmpErr);
+        psErrorClear();
+        psFree(config);
+        psFree(header);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // Test with acceptable inputs
+    if (1) {
+        psMemId id = psMemGetId();
+        pmConfig *config = pmConfigAlloc();
+        psMetadata *header = psMetadataAlloc();
+        bool readRecipes = false;
+        // Load a sample config file
+        bool rc = pmConfigFileRead(&config->site, "../dataFiles/SampleIPPConfig", "DESCRIPTION");
+        if (!rc) {
+            rc = pmConfigFileRead(&config->site, "dataFiles/SampleIPPConfig", "DESCRIPTION");
+	}
+        ok(rc == true && !config, "pmConfigFileRead() was successful");
+        // Set metadata tags for a simulated FITS header
+        psMetadataAddStr(header, PS_LIST_TAIL, "F0_KEY0", 0, "", "string20");
+        psMetadataAddStr(header, PS_LIST_TAIL, "F0_KEY1", 0, "", "string21");
+        psMetadataAddS32(header, PS_LIST_TAIL, "F0_KEY2", 0, "", 20);
+        psMetadata *camera = pmConfigCameraFormatFromHeader(config, header, readRecipes);
+
+        ok(camera != NULL,
+          "pmConfigCameraFormatFromHeader() returned non-NULL with acceptable inputs");
+        ok(camera != NULL && psMemCheckMetadata(camera),
+          "pmConfigCameraFormatFromHeader() returned non-NULL and correct type with acceptable inputs");
+        psFree(config);
+        psFree(header);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // --------------------------------------------------------------------
+    // --------------------------------------------------------------------
+    // Test pmConfigCameraByName()
+    //    psMetadata *pmConfigCameraByName(pmConfig *config, const char *cameraName)
+    // Test with NULL config pointer
+    {
+        psMemId id = psMemGetId();
+        pmConfig *config = pmConfigAlloc();
+        psString cameraName = "CameraName";
+        psMetadata *camera = pmConfigCameraByName(NULL, cameraName);
+        ok(camera == NULL, "pmConfigCameraByName() returned NULL with NULL config pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_NULL == tmpErr->code,
+          "pmConfigCameraByName() created the PS_ERR_BAD_PARAMETER_NULL error");
+        psFree(tmpErr);
+        psErrorClear();
+        psFree(config);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test with NULL camera Name
+    {
+        psMemId id = psMemGetId();
+        pmConfig *config = pmConfigAlloc();
+        // psString cameraName = "CameraName";
+        psMetadata *camera = pmConfigCameraByName(config, NULL);
+        ok(camera == NULL, "pmConfigCameraByName() returned NULL with NULL camera name");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_VALUE == tmpErr->code,
+          "pmConfigCameraByName() created the PS_ERR_BAD_PARAMETER_VALUE error");
+        psFree(tmpErr);
+        psErrorClear();
+        psFree(config);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    psTraceSetLevel("err", 10);
+    // Test with acceptable data
+    if (1) {
+        psMemId id = psMemGetId();
+        pmConfigSet("data");
+        pmConfig *config = pmConfigAlloc();
+        bool rc = pmConfigFileRead(&config->site, "../dataFiles/SampleIPPConfig", "DESCRIPTION");
+        if (!rc) {
+            rc = pmConfigFileRead(&config->site, "dataFiles/SampleIPPConfig", "DESCRIPTION");
+	}
+        ok(rc == true, "pmConfigFileRead() was successful");
+        psString cameraName = "CAMERA0";
+        psMetadata *camera = pmConfigCameraByName(config, cameraName);
+        ok(camera != NULL && psMemCheckMetadata(camera),
+          "pmConfigCameraByName() returned non-NULL with acceptable");
+        char *tmpStr = psMetadataLookupStr(&rc, camera, "ID");
+        ok(tmpStr != NULL && !strcmp(tmpStr, "CAMERA0"), "pmConfigCameraByName() returned non-NULL(CAMERA0)");
+        pmConfigDone();
+        psFree(config);
+        psFree(tmpStr);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // --------------------------------------------------------------------
+    // --------------------------------------------------------------------
+    // Test pmConfigDB()
+    // psDB *pmConfigDB(pmConfig *config)
+    // Test with NULL config pointer
+    {
+        psMemId id = psMemGetId();
+        pmConfig *config = pmConfigAlloc();
+        psDB *db = pmConfigDB(NULL);
+        ok(db == NULL, "pmConfigDB() returned NULL with NULL config pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_NULL == tmpErr->code,
+          "pmConfigDB()() created the PS_ERR_BAD_PARAMETER_NULL error");
+        psFree(tmpErr);
+        psErrorClear();
+        psFree(config);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test with NULL config->site pointer
+    {
+        psMemId id = psMemGetId();
+        pmConfig *config = pmConfigAlloc();
+        config->site = NULL;
+        psDB *db = pmConfigDB(config);
+        ok(db == NULL, "pmConfigDB() returned NULL with NULL config->site pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_NULL == tmpErr->code,
+          "pmConfigDB()() created the PS_ERR_BAD_PARAMETER_NULL error");
+        psFree(tmpErr);
+        psErrorClear();
+        psFree(config);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // Test with acceptable data
+    // XXX: Not tested.
+    if (1) {
+        psMemId id = psMemGetId();
+        pmConfig *config = pmConfigAlloc();
+        config->site = psMetadataAlloc();
+        bool rc = pmConfigFileRead(&config->site, "../dataFiles/SampleIPPConfig", "DESCRIPTION");
+        if (!rc) {
+            rc = pmConfigFileRead(&config->site, "dataFiles/SampleIPPConfig", "DESCRIPTION");
+	}
+        ok(rc == true, "pmConfigFileRead() was successful");
+        skip_start(rc == false, 1, "Skipping pmConfigDB() tests because we could not read config file");
+        psDB *db = pmConfigDB(config);
+        ok(db != NULL, "pmConfigDB() returned non NULL with acceptable data");
+        psFree(db);
+        skip_end();
+        psFree(config);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+    // --------------------------------------------------------------------
+    // --------------------------------------------------------------------
+    // Test pmConfigConformHeader()
+    // Test with NULL header pointer
+    {
+        psMemId id = psMemGetId();
+        psMetadata *header = psMetadataAlloc();
+        psMetadata *format = psMetadataAlloc();
+        bool rc = pmConfigConformHeader(NULL, (const psMetadata *) format);
+        ok(rc == false, "pmConfigConformHeader() returned FALSE with NULL header pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_NULL == tmpErr->code,
+          "pmConfigConformHeader() created the PS_ERR_BAD_PARAMETER_NULL error");
+        psFree(tmpErr);
+        psErrorClear();
+        psFree(header);
+        psFree(format);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test with NULL format pointer
+    {
+        psMemId id = psMemGetId();
+        psMetadata *header = psMetadataAlloc();
+        psMetadata *format = psMetadataAlloc();
+        bool rc = pmConfigConformHeader(header, NULL);
+        ok(rc == false, "pmConfigConformHeader() returned FALSE with NULL header pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_NULL == tmpErr->code,
+          "pmConfigConformHeader() created the PS_ERR_BAD_PARAMETER_NULL error");
+        psFree(tmpErr);
+        psErrorClear();
+        psFree(header);
+        psFree(format);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // Test with acceptable data
+    {
+        psMemId id = psMemGetId();
+        bool valid;
+        psMetadata *header = psMetadataAlloc();
+        psMetadata *format = psMetadataAlloc();
+        bool rc = pmConfigFileRead(&format, "../dataFiles/camera0/format0.config", "Camera 0 Config File");
+        if (!rc) {
+            rc = pmConfigFileRead(&format, "dataFiles/camera0/format0.config", "Camera 0 Config File");
+	}
+        ok(rc == true, "pmConfigFileRead() read camera format correctly");
+
+        // First ensure that the header is not accepted
+        rc = pmConfigValidateCameraFormat(&valid, format, header);
+        ok(rc == true && valid == false, "pmConfigValidateCameraFormat() rejected header with uninitialized psMetadata");
+
+        // Now call pmConfigConformHeader() and ensure that the header is accepted
+        rc = pmConfigConformHeader(header, format);
+        ok(rc == true, "pmConfigConformHeader() returned TRUE");
+        rc = pmConfigValidateCameraFormat(&valid, format, header);
+        ok(rc == true, "pmConfigValidateCameraFormat() returned TRUE");
+        ok(valid == true, "pmConfigValidateCameraFormat() validated camera format");
+        psFree(header);
+        psFree(format);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // --------------------------------------------------------------------
+    // --------------------------------------------------------------------
+    // Test pmConfigFileSets()
+    // test with NULL argc pointer
+    {
+        psMemId id = psMemGetId();
+        char *str[3];
+        str[0] = "ARGS:";
+        str[1] = "-site";
+        str[2] = "../dataFiles/SampleIPPConfig";
+        char *filename = "FILENAME";
+        char *list = "LIST";
+        psArray *array = pmConfigFileSets(NULL, str, filename, list);
+        if (!array) {
+            str[2] = "dataFiles/SampleIPPConfig";
+            array = pmConfigFileSets(NULL, str, filename, list);
+	}
+        ok(array == NULL, "pmConfigFileSets() returned NULL with NULL argc pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_NULL == tmpErr->code,
+          "pmConfigFileSets() created the PS_ERR_BAD_PARAMETER_NULL error");
+        psFree(tmpErr);
+        psErrorClear();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // test with negative argc pointer
+    {
+        psMemId id = psMemGetId();
+        char *str[3];
+        str[0] = "ARGS:";
+        str[1] = "-site";
+        str[2] = "../dataFiles/SampleIPPConfig";
+        int myArgc = -1;
+        char *filename = "FILENAME";
+        char *list = "LIST";
+        psArray *array = pmConfigFileSets(&myArgc, str, filename, list);
+        if (!array) {
+            str[2] = "dataFiles/SampleIPPConfig";
+            array = pmConfigFileSets(&myArgc, str, filename, list);
+	}
+        ok(array == NULL, "pmConfigFileSets() returned NULL with negative argc pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_VALUE == tmpErr->code,
+          "pmConfigFileSets() created the PS_ERR_BAD_PARAMETER_VALUE error");
+        psFree(tmpErr);
+        psErrorClear();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // test with NULL argv pointer
+    {
+        psMemId id = psMemGetId();
+        char *str[3];
+        str[0] = "ARGS:";
+        str[1] = "-site";
+        str[2] = "../dataFiles/SampleIPPConfig";
+        int myArgc = 3;
+        char *filename = "FILENAME";
+        char *list = "LIST";
+        psArray *array = pmConfigFileSets(&myArgc, NULL, filename, list);
+        ok(array == NULL, "pmConfigFileSets() returned NULL with NULL argv pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_NULL == tmpErr->code,
+          "pmConfigFileSets() created the PS_ERR_BAD_PARAMETER_NULL error");
+        psFree(tmpErr);
+        psErrorClear();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // test with NULL filename pointer
+    {
+        psMemId id = psMemGetId();
+        char *str[3];
+        str[0] = "ARGS:";
+        str[1] = "-site";
+        str[2] = "../dataFiles/SampleIPPConfig";
+        int myArgc = 3;
+        char *list = "LIST";
+        psArray *array = pmConfigFileSets(&myArgc, str, NULL, list);
+        if (!array) {
+            str[2] = "dataFiles/SampleIPPConfig";
+            array = pmConfigFileSets(&myArgc, str, NULL, list);
+	}
+        ok(array == NULL, "pmConfigFileSets() returned NULL with NULL filename pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_VALUE == tmpErr->code,
+          "pmConfigFileSets() created the PS_ERR_BAD_PARAMETER_VALUE error");
+        psFree(tmpErr);
+        psErrorClear();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // test with NULL list pointer
+    {
+        psMemId id = psMemGetId();
+        char *str[3];
+        str[0] = "ARGS:";
+        str[1] = "-site";
+        str[2] = "../dataFiles/SampleIPPConfig";
+        int myArgc = 3;
+        char *filename = "FILENAME";
+        psArray *array = pmConfigFileSets(&myArgc, str, filename, NULL);
+        if (!array) {
+            str[2] = "dataFiles/SampleIPPConfig";
+            array = pmConfigFileSets(&myArgc, str, filename, NULL);
+	}
+        ok(array == NULL, "pmConfigFileSets() returned NULL with NULL list pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_VALUE == tmpErr->code,
+          "pmConfigFileSets() created the PS_ERR_BAD_PARAMETER_VALUE error");
+        psFree(tmpErr);
+        psErrorClear();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // test with acceptable data
+    if (0) {
+        psMemId id = psMemGetId();
+        char *str[3];
+        str[0] = "ARGS:";
+        str[1] = "-site";
+        str[2] = "../dataFiles/SampleIPPConfig";
+        int myArgc = 3;
+        char *filename = "FILENAME";
+        char *list = "LIST";
+        psArray *array = pmConfigFileSets(&myArgc, str, filename, list);
+        if (!array) {
+            str[2] = "dataFiles/SampleIPPConfig";
+            array = pmConfigFileSets(&myArgc, str, filename, list);
+	}
+        ok(array != NULL && psMemCheckArray(array),
+          "pmConfigFileSets() returned non-NULL with acceptable input data");
+        psErrorClear();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // --------------------------------------------------------------------
+    // --------------------------------------------------------------------
+    // Test pmConfigFileSetsMD()
+    // bool pmConfigFileSetsMD(psMetadata *metadata, int *argc, char **argv, const char *name,
+    //                         const char *file, const char *list)
+    // XX: Should we test/check for bad argv/argc params?
+    // test with NULL metadata pointer
+    {
+        psMemId id = psMemGetId();
+        psMetadata *metadata = psMetadataAlloc();
+        char *str[3];
+        str[0] = "ARGS:";
+        str[1] = "-site";
+        str[2] = "../dataFiles/SampleIPPConfig";
+        int myArgc = 3;
+        char *name = "NAME";
+        char *file = "FILENAME";
+        char *list = "LIST";
+        bool rc = pmConfigFileSetsMD(NULL, &myArgc, str, name, file, list);
+        if (!rc) {
+            str[2] = "dataFiles/SampleIPPConfig";
+            rc = pmConfigFileSetsMD(NULL, &myArgc, str, name, file, list);
+	}
+        ok(rc == false, "pmConfigFileSetsMD() returned FALSE with NULL metadata pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_NULL == tmpErr->code,
+          "pmConfigFileSetsMD() created the PS_ERR_BAD_PARAMETER_NULL error");
+        psFree(tmpErr);
+        psFree(metadata);
+        psErrorClear();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // test with NULL name pointer
+    {
+        psMemId id = psMemGetId();
+        psMetadata *metadata = psMetadataAlloc();
+        char *str[3];
+        str[0] = "ARGS:";
+        str[1] = "-site";
+        str[2] = "../dataFiles/SampleIPPConfig";
+        int myArgc = 3;
+        char *file = "FILENAME";
+        char *list = "LIST";
+        bool rc = pmConfigFileSetsMD(metadata, &myArgc, str, NULL, file, list);
+        if (!rc) {
+            str[2] = "dataFiles/SampleIPPConfig";
+            rc = pmConfigFileSetsMD(metadata, &myArgc, str, NULL, file, list);
+	}
+        ok(rc == false, "pmConfigFileSetsMD() returned FALSE with NULL name pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_VALUE == tmpErr->code,
+          "pmConfigFileSetsMD() created the PS_ERR_BAD_PARAMETER_VALUE error");
+        psFree(tmpErr);
+        psFree(metadata);
+        psErrorClear();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // test with NULL file pointer
+    {
+        psMemId id = psMemGetId();
+        psMetadata *metadata = psMetadataAlloc();
+        char *str[3];
+        str[0] = "ARGS:";
+        str[1] = "-site";
+        str[2] = "../dataFiles/SampleIPPConfig";
+        int myArgc = 3;
+        char *name = "NAME";
+        char *list = "LIST";
+        bool rc = pmConfigFileSetsMD(metadata, &myArgc, str, name, NULL, list);
+        if (!rc) {
+            str[2] = "dataFiles/SampleIPPConfig";
+            rc = pmConfigFileSetsMD(metadata, &myArgc, str, name, NULL, list);
+	}
+        ok(rc == false, "pmConfigFileSetsMD() returned FALSE with NULL file pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_VALUE == tmpErr->code,
+          "pmConfigFileSetsMD() created the PS_ERR_BAD_PARAMETER_VALUE error");
+        psFree(tmpErr);
+        psFree(metadata);
+        psErrorClear();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // test with NULL list pointer
+    {
+        psMemId id = psMemGetId();
+        psMetadata *metadata = psMetadataAlloc();
+        char *str[3];
+        str[0] = "ARGS:";
+        str[1] = "-site";
+        str[2] = "../dataFiles/SampleIPPConfig";
+        int myArgc = 3;
+        char *name = "NAME";
+        char *file = "FILENAME";
+        bool rc = pmConfigFileSetsMD(metadata, &myArgc, str, name, file, NULL);
+        if (!rc) {
+            str[2] = "dataFiles/SampleIPPConfig";
+            rc = pmConfigFileSetsMD(metadata, &myArgc, str, name, file, NULL);
+	}
+        ok(rc == false, "pmConfigFileSetsMD() returned FALSE with NULL list pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_VALUE == tmpErr->code,
+          "pmConfigFileSetsMD() created the PS_ERR_BAD_PARAMETER_VALUE error");
+        psFree(tmpErr);
+        psFree(metadata);
+        psErrorClear();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // test with acceptable input data
+    if (0) {
+        psMemId id = psMemGetId();
+        psMetadata *metadata = psMetadataAlloc();
+        char *str[3];
+        str[0] = "ARGS:";
+        str[1] = "-site";
+        str[2] = "../dataFiles/SampleIPPConfig";
+        int myArgc = 3;
+        char *name = "NAME";
+        char *file = "FILENAME";
+        char *list = "LIST";
+        bool rc = pmConfigFileSetsMD(metadata, &myArgc, str, name, file, list);
+        if (!rc) {
+            str[2] = "dataFiles/SampleIPPConfig";
+            rc = pmConfigFileSetsMD(metadata, &myArgc, str, name, file, list);
+	}
+        ok(rc == true, "pmConfigFileSetsMD() returned TRUE with acceptable input data");
+        psFree(metadata);
+        psErrorClear();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // --------------------------------------------------------------------
+    // --------------------------------------------------------------------
+    // Test pmConfigConvertFilename()
+    // convert the supplied name, create a new output psString
+    // psString pmConfigConvertFilename(const char *filename, const pmConfig *config,
+    //                                  bool create)
+    // test with NULL filename pointer
+    {
+        psMemId id = psMemGetId();
+        pmConfig *config = pmConfigAlloc();
+        bool create = false;
+        bool rc = pmConfigConvertFilename(NULL, (const pmConfig *) config, create, false);
+        ok(rc == false, "pmConfigConvertFilename() returned FALSE with NULL filename pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_VALUE == tmpErr->code,
+          "pmConfigConvertFilename() created the PS_ERR_BAD_PARAMETER_VALUE error");
+        psFree(tmpErr);
+        psErrorClear();
+        psFree(config);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // test with NULL config pointer
+    {
+        psMemId id = psMemGetId();
+        char *name = "NAME";
+        pmConfig *config = pmConfigAlloc();
+        bool create = false;
+        bool rc = pmConfigConvertFilename(name, NULL, create, false);
+        ok(rc == false, "pmConfigConvertFilename() returned FALSE with NULL config pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_NULL == tmpErr->code,
+          "pmConfigConvertFilename() created the PS_ERR_BAD_PARAMETER_NULL error");
+        psFree(tmpErr);
+        psErrorClear();
+        psFree(config);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // test with acceptable input data
+    // First call with create==FALSE, and a filename that does not exist.
+    // Should return NULL.  Then set create==TRUE, and call; should return
+    // "JUNK".  Then call again with create==FALSE; should return "JUNK"
+    if (1) {
+        psMemId id = psMemGetId();
+        char *filename = "file:///////JUNK";
+        pmConfig *config = pmConfigAlloc();
+        bool rc = pmConfigFileRead(&config->site, "../dataFiles/SampleIPPConfig", "DESCRIPTION");
+        if (!rc) {
+            rc = pmConfigFileRead(&config->site, "dataFiles/SampleIPPConfig", "DESCRIPTION");
+	}
+        ok(rc == true, "pmConfigFileRead() was successful");
+        bool create = false;
+        psString tmpStr = pmConfigConvertFilename(filename, (const pmConfig *) config, create, false);
+        ok(NULL == tmpStr, "pmConfigConvertFilename() returned NULL with create==FALSE");
+        create = true;
+        tmpStr = pmConfigConvertFilename(filename, (const pmConfig *) config, create, false);
+        ok(tmpStr != NULL, "pmConfigConvertFilename() returned non-NULL with create==TRUE");
+        ok(!strcmp(tmpStr, "/JUNK"), "pmConfigConvertFilename() returned correct filename (%s)", tmpStr);
+        psFree(tmpStr);
+        psFree(config);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+    // --------------------------------------------------------------------
+    // --------------------------------------------------------------------
+    // Test pmConfigRead()
+    // Test with NULL argc pointer
+    {
+        psMemId id = psMemGetId();
+        psString str[3];
+        str[0] = "ARGS:";
+        str[1] = "-site";
+        str[2] = "../dataFiles/SampleIPPConfig";
+        pmConfig *myConfig = pmConfigRead(NULL, str, "RecipeName");
+        if (!myConfig) {
+            str[2] = "dataFiles/SampleIPPConfig";
+            myConfig = pmConfigRead(NULL, str, "RecipeName");
+	}
+        ok(myConfig == NULL, "pmConfigRead() returned NULL with NULL argc pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_NULL == tmpErr->code,
+          "pmConfigValidateCameraFormat() created the PS_ERR_BAD_PARAMETER_NULL error");
+        psFree(tmpErr);
+        psErrorClear();
+        psFree(myConfig);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test with NULL argv pointer
+    {
+        psMemId id = psMemGetId();
+        pmConfig *myConfig = pmConfigRead(&argc, NULL, "RecipeName");
+        ok(myConfig == NULL, "pmConfigRead() returned NULL with NULL argv pointer");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_NULL == tmpErr->code,
+          "pmConfigValidateCameraFormat() created the PS_ERR_BAD_PARAMETER_NULL error");
+        psFree(tmpErr);
+        psErrorClear();
+        psFree(myConfig);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test with NULL recipe string (should not cause error)
+    // XXX: Not working, debug
+    if (0) {
+        psMemId id = psMemGetId();
+        char *str[3];
+        str[0] = "ARGS:";
+        str[1] = "-site";
+        str[2] = "../dataFiles/SampleIPPConfig";
+        int numArgs = 3;
+        pmConfig *myConfig = pmConfigRead(&numArgs, str, NULL);
+        if (!myConfig) {
+            str[2] = "dataFiles/SampleIPPConfig";
+            myConfig = pmConfigRead(&numArgs, str, NULL);
+	}
+        ok(myConfig != NULL, "pmConfigRead() returned non-NULL with NULL recipe");
+        psErr *tmpErr = psErrorLast();
+        ok(PS_ERR_BAD_PARAMETER_NULL == tmpErr->code,
+          "pmConfigValidateCameraFormat() created the PS_ERR_BAD_PARAMETER_NULL error");
+        psFree(tmpErr);
+        psErrorClear();
+        psFree(myConfig);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmConfigRead() with acceptable data.
+    if (1) {
+        psMemId id = psMemGetId();
+        bool testStatus = true;
+        psMetadata *site = psMetadataAlloc();
+        psMetadata *camera = psMetadataAlloc();
+        psMetadata *recipe = psMetadataAlloc();
+        psString str[3];
+        str[0] = "ARGS:";
+        str[1] = "-site";
+        str[2] = "../dataFiles/SampleIPPConfig";
+        psS32 argc = 3;
+        printf("----------------------------------------------------------------\n");
+        printf("Calling pmConfigRead() with acceptable arguments.\n");
+        bool rc;
+        pmConfig *myConfig = pmConfigRead(&argc, str, "RecipeName");
+        if (!myConfig) {
+            str[2] = "dataFiles/SampleIPPConfig";
+            myConfig = pmConfigRead(&argc, str, "RecipeName");
+	}
+        ok(myConfig, "pmConfigRead() returned non-NULL");
+        if (myConfig == NULL) {
+            diag("TEST ERROR: pmConfigRead() returned NULL\n");
+            testStatus = false;
+        } else {
+            //
+            // Ensure that the various trace and logging values are set properly
+            //
+            if (1 != psTraceGetLevel("dummyTraceFunc01")) {
+                diag("TEST ERROR: failed to properly set tracelevel for dummyTraceFunc01\n");
+                diag("    tracelevel was %d, should be 1.\n", psTraceGetLevel("dummyTraceFunc01"));
+                testStatus = false;
+            }
+
+            if (2 != psTraceGetLevel("dummyTraceFunc02")) {
+                diag("TEST ERROR: failed to properly set tracelevel for dummyTraceFunc02\n");
+                diag("    tracelevel was %d, should be 2.\n", psTraceGetLevel("dummyTraceFunc02"));
+                testStatus = false;
+            }
+
+            if (1 != psLogGetDestination()) {
+                diag("TEST ERROR: failed to properly set log destination.\n");
+                diag("    it was %d, should be 1\n", psLogGetDestination());
+                testStatus = false;
+            }
+
+            if (3 != psLogGetLevel()) {
+                diag("TEST ERROR: failed to properly set log level.\n");
+                diag("    it was %d, should be 3\n", psLogGetLevel());
+                testStatus = false;
+            }
+
+            //
+            // Test the several arbitrary config file strings.
+            //
+            psS32 tmpInt = psMetadataLookupS32(&rc, site, "ARBITRARY_STRING_S32");
+            if ((rc == false) || (tmpInt != 20)) {
+                diag("TEST ERROR: failed to properly set metadata integer ARBITRARY_STRING_S32.\n");
+                testStatus = false;
+            }
+
+            psF32 tmpFloat = psMetadataLookupS32(&rc, site, "ARBITRARY_STRING_F32");
+            if ((rc == false) || (tmpFloat != 21.0)) {
+                diag("TEST ERROR: failed to properly set metadata float ARBITRARY_STRING_F32.\n");
+                testStatus = false;
+            }
+
+            psF64 tmpDub = psMetadataLookupS32(&rc, site, "ARBITRARY_STRING_F64");
+            if ((rc == false) || (tmpDub != 22.0)) {
+                diag("TEST ERROR: failed to properly set metadata double ARBITRARY_STRING_F64.\n");
+                testStatus = false;
+            }
+
+            psBool tmpBool;
+            tmpBool = psMetadataLookupS32(&rc, site, "ARBITRARY_STRING_BOOL_T");
+            if ((rc == false) || (tmpBool != true)) {
+                diag("TEST ERROR: failed to properly set metadata double ARBITRARY_STRING_BOOL_T.\n");
+                testStatus = false;
+            }
+            tmpBool = psMetadataLookupS32(&rc, site, "ARBITRARY_STRING_BOOL_F");
+            if ((rc == false) || (tmpBool != false)) {
+                diag("TEST ERROR: failed to properly set metadata double ARBITRARY_STRING_BOOL_F.\n");
+                testStatus = false;
+            }
+
+            //
+            // Test the database camera metadata keywords.
+            //
+            psMetadata *tmpMeta = psMetadataLookupMetadata(&rc, site, "CAMERAS");
+            if (rc == false) {
+                diag("TEST ERROR: failed to properly set metadata metadata CAMERAS.\n");
+                testStatus = false;
+            } else {
+                psString tmpStr;
+                tmpStr = psMetadataLookupStr(&rc, tmpMeta, "MEGACAM_RAW");
+                if ((rc == false) || (0 != strcmp(tmpStr, "megacam_raw.config"))) {
+                    diag("TEST ERROR: failed to properly set metadata metadata CAMERAS->MEGACAM_RAW.\n");
+                    testStatus = false;
+                }
+
+                tmpStr = psMetadataLookupStr(&rc, tmpMeta, "MEGACAM_SPLICE");
+                if ((rc == false) || (0 != strcmp(tmpStr, "megacam_splice.config"))) {
+                    diag("TEST ERROR: failed to properly set metadata metadata CAMERAS->MEGACAM_SPLICE.\n");
+                    testStatus = false;
+                }
+
+                tmpStr = psMetadataLookupStr(&rc, tmpMeta, "GPC1_RAW");
+                if ((rc == false) || (0 != strcmp(tmpStr, "gpc1_raw.config"))) {
+                    diag("TEST ERROR: failed to properly set metadata metadata CAMERAS->GPC1_RAW.\n");
+                    testStatus = false;
+                }
+
+                tmpStr = psMetadataLookupStr(&rc, tmpMeta, "LRIS_BLUE");
+                if ((rc == false) || (0 != strcmp(tmpStr, "lris_blue.config"))) {
+                    diag("TEST ERROR: failed to properly set metadata metadata CAMERAS->LRIS_BLUE.\n");
+                    testStatus = false;
+                }
+
+                tmpStr = psMetadataLookupStr(&rc, tmpMeta, "LRIS_RED");
+                if ((rc == false) || (0 != strcmp(tmpStr, "lris_red.config"))) {
+                    diag("TEST ERROR: failed to properly set metadata metadata CAMERAS->LRIS_RED.\n");
+                    testStatus = false;
+                }
+            }
+
+            //
+            // Test the database metadata keywords.  This is somewhat redundant since
+            // we already test metadat strings.
+            //
+            psString tmpStr;
+            tmpStr = psMetadataLookupStr(&rc, site, "DBSERVER");
+            if ((rc == false) || (0 != strcmp(tmpStr, "ippdb.ifa.hawaii.edu"))) {
+                diag("TEST ERROR: failed to properly set metadata string DBSERVER.\n");
+                testStatus = false;
+            }
+
+            tmpStr = psMetadataLookupStr(&rc, site, "DBUSER");
+            if ((rc == false) || (0 != strcmp(tmpStr, "ipp"))) {
+                diag("TEST ERROR: failed to properly set metadata string DBUSER.\n");
+                testStatus = false;
+            }
+
+            tmpStr = psMetadataLookupStr(&rc, site, "DBPASSWORD");
+            if ((rc == false) || (0 != strcmp(tmpStr, "password"))) {
+                diag("TEST ERROR: failed to properly set metadata string DBPASSWORD.\n");
+                testStatus = false;
+            }
+        }
+        psFree(site);
+        psFree(camera);
+        psFree(recipe);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/tap_pmConfigCommand.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/tap_pmConfigCommand.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/tap_pmConfigCommand.c	(revision 22157)
@@ -0,0 +1,155 @@
+/** @file tst_pmConfigCommand.c
+ *
+ *  @brief Contains the tests for pmConfigCommand.c:
+ *
+ * This code will test the pmConfigCommand() routine.
+ *
+ *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-01-02 20:49:09 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+#define ERR_TRACE_LEVEL         0
+#define VERBOSE			0
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel(".", 0);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(14);
+
+
+    // --------------------------------------------------------------------
+    // pmConfigDatabaseCommand() tests
+    // Test pmConfigDatabaseCommand() with NULL command input param
+    // XXX: I think the memory leak is in pmConfigRead()
+    {
+        psMemId id = psMemGetId();
+        psString str[3];
+        str[0] = "ARGS:";
+        str[1] = "-site";
+        str[2] = "../dataFiles/SampleIPPConfig";
+        psS32 argc = 3;
+        pmConfig *config = pmConfigRead(&argc, str, "RecipeName");
+        if (!config) {
+            str[2] = "dataFiles/SampleIPPConfig";
+            config = pmConfigRead(&argc, str, "RecipeName");
+	}
+        ok(config != NULL, "pmConfigRead() successful");
+        ok(!pmConfigDatabaseCommand(NULL, config), "pmConfigDatabaseCommand() returned NULL with NULL command input param");
+        psFree(config);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmConfigDatabaseCommand() with NULL pmConfig input param
+    // XXX: I think the memory leak is in pmConfigRead()
+    {
+        psMemId id = psMemGetId();
+        psString testCmd = psStringCopy("my command");
+        ok(!pmConfigDatabaseCommand(&testCmd, NULL), "pmConfigDatabaseCommand() returned NULL with NULL command input param");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmConfigDatabaseCommand() with command arg generated by other than psStringDup()
+    // XXX: This seg-faults because pmConfigDatabaseCommand(), or whichever function,
+    // does not verify that the command was allocated as a psString.
+    if (0) {
+        psMemId id = psMemGetId();
+        psString str[3];
+        str[0] = "ARGS:";
+        str[1] = "-site";
+        str[2] = "../dataFiles/SampleIPPConfig";
+        psS32 argc = 3;
+        pmConfig *config = pmConfigRead(&argc, str, "RecipeName");
+        if (!config) {
+            str[2] = "dataFiles/SampleIPPConfig";
+            config = pmConfigRead(&argc, str, "RecipeName");
+	}
+        ok(config != NULL, "pmConfigRead() successful");
+        char *testCmd = "command";
+        ok(!pmConfigDatabaseCommand(&testCmd, config), "pmConfigDatabaseCommand() returned NULL with NULL command input param");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmConfigDatabaseCommand() with acceptable input params
+    // XXX: I think the memory leak is in pmConfigRead()
+    {
+        psMemId id = psMemGetId();
+        psString str[3];
+        str[0] = "ARGS:";
+        str[1] = "-site";
+        str[2] = "../dataFiles/SampleIPPConfig";
+        psS32 argc = 3;
+        pmConfig *config = pmConfigRead(&argc, str, "RecipeName");
+        if (!config) {
+            str[2] = "dataFiles/SampleIPPConfig";
+            config = pmConfigRead(&argc, str, "RecipeName");
+	}
+        ok(config != NULL, "pmConfigRead() successful");
+        psString testCmd = psStringCopy("my command");
+        psString verifyCmd = psStringCopy(testCmd);
+        // Generate the verify test string
+        {
+            bool mdok;
+            psString dbserver = psMetadataLookupStr(&mdok, config->site, "DBSERVER");
+            psString dbname = psMetadataLookupStr(&mdok, config->site, "DBNAME");
+            psString dbuser = psMetadataLookupStr(&mdok, config->site, "DBUSER");
+            psString dbpassword = psMetadataLookupStr(&mdok, config->site, "DBPASSWORD");
+            psStringAppend(&verifyCmd, " -dbserver %s -dbname %s -dbuser %s -dbpassword %s",
+                           dbserver, dbname, dbuser, dbpassword);
+	}
+
+        ok(pmConfigDatabaseCommand(&testCmd, config), "pmConfigDatabaseCommand() returned non-NULL with acceptable input params");
+        ok(!strcmp(testCmd, verifyCmd), "pmConfigDatabaseCommand() generated the correct command string");
+        psFree(config);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // --------------------------------------------------------------------
+    // pmConfigTraceCommand() tests
+    // Test pmConfigTraceCommand() with NULL command input param
+    {
+        psMemId id = psMemGetId();
+        ok(!pmConfigTraceCommand(NULL), "pmConfigTraceCommand() returned NULL with NULL command input param");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmConfigTraceCommand() with acceptable input command input param
+    {
+        psMemId id = psMemGetId();
+        psTraceSetLevel("dummyTraceLevel", 0);
+        psString testCmd = psStringCopy("my command");
+        psString verifyCmd = psStringCopy(testCmd);
+        // Generate the verify test string
+        {
+            psMetadata *levels = psTraceLevels();
+            psMetadataIterator *iter = psMetadataIteratorAlloc(levels, PS_LIST_HEAD, NULL);
+            psMetadataItem *item;
+            while ((item = psMetadataGetAndIncrement(iter))) {
+                assert(item->type == PS_TYPE_S32);
+                psStringAppend(&verifyCmd, " -trace %s %d", item->name, item->data.S32);
+            }
+            psFree(iter);
+            psFree(levels);
+	}
+        ok(pmConfigTraceCommand(&testCmd), "pmConfigTraceCommand() returned non-NULL with acceptable input param");
+        ok(!strcmp(testCmd, verifyCmd), "pmConfigTraceCommand() generated the correct command string");
+        psFree(testCmd);
+        psFree(verifyCmd);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
+    
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/tap_pmConfigMask.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/tap_pmConfigMask.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/tap_pmConfigMask.c	(revision 22157)
@@ -0,0 +1,89 @@
+/** @file tst_pmConfigMask.c
+ *
+ *  @brief Contains the tests for pmConfigMask.c:
+ *
+ * This code will test the pmConfigMask() routine.
+ *
+ *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2008-01-02 20:49:10 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+#define ERR_TRACE_LEVEL         0
+#define VERBOSE			0
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel(".", 0);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(7);
+
+
+    // --------------------------------------------------------------------
+    // pmConfigMask() tests
+    // Test pmConfigMask() with NULL masks input param
+    // XXX: I think the memory leak is in pmConfigRead()
+    {
+        psMemId id = psMemGetId();
+        psString str[3];
+        str[0] = "ARGS:";
+        str[1] = "-site";
+        str[2] = "dataFiles/SampleIPPConfig";
+        psS32 argc = 3;
+        pmConfig *config = pmConfigRead(&argc, str, "RecipeName");
+        if (!config) {
+            str[2] = "../dataFiles/SampleIPPConfig";
+            config = pmConfigRead(&argc, str, "RecipeName");
+	}
+        ok(config != NULL, "pmConfigRead() successful");
+        ok(!pmConfigMask(NULL, config), "pmConfigMask() returned NULL with NULL masks input param");
+        psFree(config);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmConfigMask() with NULL pmConfig input param
+    // XXX: This fails on current CVS code because there is no assert in pmConfigMask() for a null pmConfig param
+    if (0) {
+        psMemId id = psMemGetId();
+        char *masks = "Mask0 Mask1 Mask2";
+        ok(!pmConfigMask(masks, NULL), "pmConfigMask() returned NULL with NULL pmConfig input param");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmConfigMask() with acceptable input params
+    // XXX: I think the memory leak is in pmConfigRead()
+    {
+        psMemId id = psMemGetId();
+        // See file ../dataFiles/recipes_masks.config (
+        char *masks = "DETECTOR RANGE";
+        psMaskType correctMask = 0x02 | 0x04;
+        psString str[3];
+        str[0] = "ARGS:";
+        str[1] = "-site";
+        str[2] = "dataFiles/SampleIPPConfig";
+        psS32 argc = 3;
+        pmConfig *config = pmConfigRead(&argc, str, "RecipeName");
+        if (!config) {
+            str[2] = "../dataFiles/SampleIPPConfig";
+            config = pmConfigRead(&argc, str, "RecipeName");
+	}
+        ok(config != NULL, "pmConfigRead() successful");
+        skip_start(config == NULL, 2, "Skipping tests because pmConfigRead() failed");
+        psMaskType mask = pmConfigMask(masks, config);
+        ok(mask, "pmConfigMask returned non-zero with acceptable input params");
+        ok(mask == correctMask, "pmConfigMask() generated the correct output mask (%x).  Should be (%x)", mask, correctMask);
+        skip_end();
+        psFree(config);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/tap_pmErrorCodes.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/tap_pmErrorCodes.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/tap_pmErrorCodes.c	(revision 22157)
@@ -0,0 +1,98 @@
+/** @file tst_pmErrorCodes.c
+ *
+ *  @brief Contains the tests for pmErrorCodes.c:
+ *
+ *  @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-09-18 18:58:58 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+#define ERR_TRACE_LEVEL         0
+#define VERBOSE			0
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel(".", 0);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(16);
+
+
+    // --------------------------------------------------------------------
+    // pmErrorRegister() tests
+    // Test pmErrorRegister() with currently coded error types
+    {
+        psMemId id = psMemGetId();
+        pmErrorRegister();
+        char *errStr;
+        errStr = (char *) psErrorCodeString(PM_ERR_BASE);
+        ok(!strcmp("First value we use; lower values belong to psLib", errStr),
+           "psErrorCodeString(PM_ERR_BASE)");
+
+        errStr = (char *) psErrorCodeString(PM_ERR_UNKNOWN);
+        ok(!strcmp("Unknown psModules error code", errStr),
+           "psErrorCodeString(PM_ERR_UNKNOWN)");
+
+        errStr = (char *) psErrorCodeString(PM_ERR_PHOTOM);
+        ok(!strcmp("Problem in photometry", errStr),
+           "psErrorCodeString(PM_ERR_PHOTOM)");
+
+        errStr = (char *) psErrorCodeString(PM_ERR_PSF);
+        ok(!strcmp("Problem in PSF", errStr),
+           "psErrorCodeString(PM_ERR_PSF)");
+
+        errStr = (char *) psErrorCodeString(PM_ERR_ASTROM);
+        ok(!strcmp("Problem in astrometry", errStr),
+           "psErrorCodeString(PM_ERR_ASTROM)");
+
+        errStr = (char *) psErrorCodeString(PM_ERR_CAMERA);
+        ok(!strcmp("Problem in camera", errStr),
+           "psErrorCodeString(PM_ERR_CAMERA)");
+
+        errStr = (char *) psErrorCodeString(PM_ERR_CONCEPTS);
+        ok(!strcmp("Problem in concepts", errStr),
+           "psErrorCodeString(PM_ERR_CONCEPTS)");
+
+        errStr = (char *) psErrorCodeString(PM_ERR_IMCOMBINE);
+        ok(!strcmp("Problem in imcombine", errStr),
+           "psErrorCodeString(PM_ERR_IMCOMBINE)");
+
+        errStr = (char *) psErrorCodeString(PM_ERR_OBJECTS);
+        ok(!strcmp("Problem in objects", errStr),
+           "psErrorCodeString(PM_ERR_OBJECTS)");
+
+        errStr = (char *) psErrorCodeString(PM_ERR_SKY);
+        ok(!strcmp("Problem in sky", errStr),
+           "psErrorCodeString(PM_ERR_SKY)");
+
+        errStr = (char *) psErrorCodeString(PM_ERR_ARGUMENTS);
+        ok(!strcmp("Incorrect arguments", errStr),
+           "psErrorCodeString(PM_ERR_ARGUMENTS)");
+
+        errStr = (char *) psErrorCodeString(PM_ERR_SYS);
+        ok(!strcmp("System error", errStr),
+           "psErrorCodeString(PM_ERR_SYS)");
+
+        errStr = (char *) psErrorCodeString(PM_ERR_CONFIG);
+        ok(!strcmp("Problem in configure files", errStr),
+           "psErrorCodeString(PM_ERR_CONFIG)");
+
+        errStr = (char *) psErrorCodeString(PM_ERR_PROG);
+        ok(!strcmp("Programming error", errStr),
+           "psErrorCodeString(PM_ERR_PROG)");
+
+        errStr = (char *) psErrorCodeString(PM_ERR_DATA);
+        ok(!strcmp("invalid data", errStr),
+           "psErrorCodeString(PM_ERR_DATA)");
+
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/tap_pmVersion.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/tap_pmVersion.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/tap_pmVersion.c	(revision 22157)
@@ -0,0 +1,58 @@
+/** @file tst_pmVersion.c
+ *
+ *  @brief Contains the tests for pmVersion.c:
+ *
+ *  @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-09-18 18:59:00 $
+ *
+ *  XX: The output version string for psModulesVersionLong() is not verified.
+ *  Not sure how to do this since this psModulesVersionLong() produces the
+ *  time that the source code, not the test code, was compiled.
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+#include "config.h"
+#define ERR_TRACE_LEVEL         0
+#define VERBOSE			0
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel(".", 0);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(4);
+
+
+    // --------------------------------------------------------------------
+    // psModulesVersion() tests
+    // XX: The output string is not verified
+    {
+        psMemId id = psMemGetId();
+        psString tstStr = NULL;
+        psStringAppend(&tstStr, "%s-%s",PACKAGE_NAME,PACKAGE_VERSION);
+        psString str = psModulesVersion();
+        ok(!strcmp(str, tstStr), "psModulesVersion()");
+        psFree(str);
+        psFree(tstStr);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // --------------------------------------------------------------------
+    // psModulesVersionLong() tests
+    // XX: The output string is not verified
+    {
+        psMemId id = psMemGetId();
+        psString str = psModulesVersionLong();
+        ok(str, "psModulesVersionLong()");
+        psFree(str);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/config/tst_pmConfig.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/config/tst_pmConfig.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/config/tst_pmConfig.c	(revision 22157)
@@ -0,0 +1,277 @@
+/** @file tst_pmConfig.c
+ *
+ *  @brief Contains the tests for pmConfig.c:
+ *
+ * test00: This code will test the pmConfig() routine.
+ *
+ *  @author GLG, MHPCC
+ *
+ * XXX: Untested:
+ * pmConfigValidateCamera()
+ * pmConfigCameraFromHeader()
+ * pmConfigRecipeFromCamera()
+ * pmConfigDB()
+ *
+ * XXXX: Must determine what to do with NULL arguments, then test it.
+ *
+ *  @version $Revision: 1.8 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-10-13 21:15:45 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+#include "psTest.h"
+#include "pslib.h"
+#include "pmConfig.h"
+
+static int test00(void);
+testDescription tests[] = {
+                              {test00, 000, "pmConfig:", true, false},
+                              {NULL}
+                          };
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    //
+    // We include the function names here in psTraceSetLevel() commands for
+    // debugging convenience.  There is no guarantee that this list of functions
+    // is complete.
+    //
+    psTraceSetLevel(".", 0);
+    psTraceSetLevel("readConfig", 0);
+    psTraceSetLevel("pmConfigRead", 0);
+    psTraceSetLevel("pmConfigValidateCamera", 0);
+    psTraceSetLevel("pmConfigCameraFromHeader", 0);
+    psTraceSetLevel("pmConfigRecipeFromCamera", 0);
+    psTraceSetLevel("pmConfigDB", 0);
+    return !runTestSuite(stderr, "Test Point Driver", tests, argc, argv);
+}
+
+/******************************************************************************
+test00():
+XXX: untested:
+    TIME:
+    LOGFORMAT: tested implicitly via the stdout files.
+    Command line arguments for logging shall override config file defaults.
+ *****************************************************************************/
+int test00( void )
+{
+    psTraceSetLevel(".", 0);
+    bool testStatus = true;
+    psBool rc = 0;
+    psString str[3];
+    str[0] = "ARGS:";
+    str[1] = "-site";
+    str[2] = "SampleIPPConfig";
+
+    psMetadata *site = psMetadataAlloc();
+    psMetadata *camera = psMetadataAlloc();
+    psMetadata *recipe = psMetadataAlloc();
+    psS32 argc = 3;
+
+
+    psMetadata *nullMD = NULL;
+    if (0) {
+        printf("----------------------------------------------------------------\n");
+        printf("Calling pmConfigRead() with NULL site pointer.  Should generate ERROR, return false.\n");
+        rc = pmConfigRead(NULL, &camera, &recipe, &argc, str, "RecipeName");
+        if (rc == true) {
+            printf("TEST ERROR: pmConfigRead() returned true\n");
+            testStatus = false;
+        }
+
+        printf("----------------------------------------------------------------\n");
+        printf("Calling pmConfigRead() with NULL *site pointer.  Should generate ERROR, return false.\n");
+        rc = pmConfigRead(&nullMD, &camera, &recipe, &argc, str, "RecipeName");
+        if (rc == true) {
+            printf("TEST ERROR: pmConfigRead() returned true\n");
+            testStatus = false;
+        }
+
+        printf("----------------------------------------------------------------\n");
+        printf("Calling pmConfigRead() with NULL camera pointer.  Should generate ERROR, return false.\n");
+        rc = pmConfigRead(&site, NULL, &recipe, &argc, str, "RecipeName");
+        if (rc == true) {
+            printf("TEST ERROR: pmConfigRead() returned true\n");
+            testStatus = false;
+        }
+
+        printf("----------------------------------------------------------------\n");
+        printf("Calling pmConfigRead() with NULL *camera pointer.  Should generate ERROR, return false.\n");
+        rc = pmConfigRead(&site, &nullMD, &recipe, &argc, str, "RecipeName");
+        if (rc == true) {
+            printf("TEST ERROR: pmConfigRead() returned true\n");
+            testStatus = false;
+        }
+
+        printf("----------------------------------------------------------------\n");
+        printf("Calling pmConfigRead() with NULL recipe pointer.  Should generate ERROR, return false.\n");
+        rc = pmConfigRead(&site, &camera, NULL, &argc, str, "RecipeName");
+        if (rc == true) {
+            printf("TEST ERROR: pmConfigRead() returned true\n");
+            testStatus = false;
+        }
+
+        printf("----------------------------------------------------------------\n");
+        printf("Calling pmConfigRead() with NULL *recipe pointer.  Should generate ERROR, return false.\n");
+        rc = pmConfigRead(&site, &camera, &nullMD, &argc, str, "RecipeName");
+        if (rc == true) {
+            printf("TEST ERROR: pmConfigRead() returned true\n");
+            testStatus = false;
+        }
+
+        printf("----------------------------------------------------------------\n");
+        printf("Calling pmConfigRead() with NULL argv pointer.  Should generate ERROR, return false.\n");
+        rc = pmConfigRead(&site, &camera, &recipe, &argc, NULL, "RecipeName");
+        if (rc == true) {
+            printf("TEST ERROR: pmConfigRead() returned true\n");
+            testStatus = false;
+        }
+
+        printf("----------------------------------------------------------------\n");
+        printf("Calling pmConfigRead() with negative argc.  Should generate ERROR, return false.\n");
+        psS32 tmpArgc = -1;
+        rc = pmConfigRead(&site, &camera, &recipe, &tmpArgc, str, "RecipeName");
+        if (rc == true) {
+            printf("TEST ERROR: pmConfigRead() returned true\n");
+            testStatus = false;
+        }
+    }
+
+    printf("----------------------------------------------------------------\n");
+    printf("Calling pmConfigRead() with acceptable arguments.\n");
+
+    rc = pmConfigRead(&site, &camera, &recipe, &argc, str, "RecipeName");
+    if (rc == false) {
+        printf("TEST ERROR: pmConfigRead() returned False\n");
+        testStatus = false;
+    } else {
+        //
+        // Ensure that the various trace and logging values are set properly
+        //
+        if (1 != psTraceGetLevel("dummyTraceFunc01")) {
+            printf("TEST ERROR: failed to properly set tracelevel for dummyTraceFunc01\n");
+            printf("    tracelevel was %d, should be 1.\n", psTraceGetLevel("dummyTraceFunc01"));
+            testStatus = false;
+        }
+
+        if (2 != psTraceGetLevel("dummyTraceFunc02")) {
+            printf("TEST ERROR: failed to properly set tracelevel for dummyTraceFunc02\n");
+            printf("    tracelevel was %d, should be 2.\n", psTraceGetLevel("dummyTraceFunc02"));
+            testStatus = false;
+        }
+
+        if (1 != psLogGetDestination()) {
+            printf("TEST ERROR: failed to properly set log destination.\n");
+            printf("    it was %d, should be 1\n", psLogGetDestination());
+            testStatus = false;
+        }
+
+        if (3 != psLogGetLevel()) {
+            printf("TEST ERROR: failed to properly set log level.\n");
+            printf("    it was %d, should be 3\n", psLogGetLevel());
+            testStatus = false;
+        }
+
+        //
+        // Test the several arbitrary config file strings.
+        //
+        psS32 tmpInt = psMetadataLookupS32(&rc, site, "ARBITRARY_STRING_S32");
+        if ((rc == false) || (tmpInt != 20)) {
+            printf("TEST ERROR: failed to properly set metadata integer ARBITRARY_STRING_S32.\n");
+            testStatus = false;
+        }
+
+        psF32 tmpFloat = psMetadataLookupS32(&rc, site, "ARBITRARY_STRING_F32");
+        if ((rc == false) || (tmpFloat != 21.0)) {
+            printf("TEST ERROR: failed to properly set metadata float ARBITRARY_STRING_F32.\n");
+            testStatus = false;
+        }
+
+        psF64 tmpDub = psMetadataLookupS32(&rc, site, "ARBITRARY_STRING_F64");
+        if ((rc == false) || (tmpDub != 22.0)) {
+            printf("TEST ERROR: failed to properly set metadata double ARBITRARY_STRING_F64.\n");
+            testStatus = false;
+        }
+
+        psBool tmpBool;
+        tmpBool = psMetadataLookupS32(&rc, site, "ARBITRARY_STRING_BOOL_T");
+        if ((rc == false) || (tmpBool != true)) {
+            printf("TEST ERROR: failed to properly set metadata double ARBITRARY_STRING_BOOL_T.\n");
+            testStatus = false;
+        }
+        tmpBool = psMetadataLookupS32(&rc, site, "ARBITRARY_STRING_BOOL_F");
+        if ((rc == false) || (tmpBool != false)) {
+            printf("TEST ERROR: failed to properly set metadata double ARBITRARY_STRING_BOOL_F.\n");
+            testStatus = false;
+        }
+
+        //
+        // Test the database camera metadata keywords.
+        //
+        psMetadata *tmpMeta = psMetadataLookupMetadata(&rc, site, "CAMERAS");
+        if (rc == false) {
+            printf("TEST ERROR: failed to properly set metadata metadata CAMERAS.\n");
+            testStatus = false;
+        } else {
+            psString tmpStr;
+            tmpStr = psMetadataLookupStr(&rc, tmpMeta, "MEGACAM_RAW");
+            if ((rc == false) || (0 != strcmp(tmpStr, "megacam_raw.config"))) {
+                printf("TEST ERROR: failed to properly set metadata metadata CAMERAS->MEGACAM_RAW.\n");
+                testStatus = false;
+            }
+
+            tmpStr = psMetadataLookupStr(&rc, tmpMeta, "MEGACAM_SPLICE");
+            if ((rc == false) || (0 != strcmp(tmpStr, "megacam_splice.config"))) {
+                printf("TEST ERROR: failed to properly set metadata metadata CAMERAS->MEGACAM_SPLICE.\n");
+                testStatus = false;
+            }
+
+            tmpStr = psMetadataLookupStr(&rc, tmpMeta, "GPC1_RAW");
+            if ((rc == false) || (0 != strcmp(tmpStr, "gpc1_raw.config"))) {
+                printf("TEST ERROR: failed to properly set metadata metadata CAMERAS->GPC1_RAW.\n");
+                testStatus = false;
+            }
+
+            tmpStr = psMetadataLookupStr(&rc, tmpMeta, "LRIS_BLUE");
+            if ((rc == false) || (0 != strcmp(tmpStr, "lris_blue.config"))) {
+                printf("TEST ERROR: failed to properly set metadata metadata CAMERAS->LRIS_BLUE.\n");
+                testStatus = false;
+            }
+
+            tmpStr = psMetadataLookupStr(&rc, tmpMeta, "LRIS_RED");
+            if ((rc == false) || (0 != strcmp(tmpStr, "lris_red.config"))) {
+                printf("TEST ERROR: failed to properly set metadata metadata CAMERAS->LRIS_RED.\n");
+                testStatus = false;
+            }
+        }
+
+        //
+        // Test the database metadata keywords.  This is somewhat redundant since
+        // we already test metadat strings.
+        //
+        psString tmpStr;
+        tmpStr = psMetadataLookupStr(&rc, site, "DBSERVER");
+        if ((rc == false) || (0 != strcmp(tmpStr, "ippdb.ifa.hawaii.edu"))) {
+            printf("TEST ERROR: failed to properly set metadata string DBSERVER.\n");
+            testStatus = false;
+        }
+
+        tmpStr = psMetadataLookupStr(&rc, site, "DBUSER");
+        if ((rc == false) || (0 != strcmp(tmpStr, "ipp"))) {
+            printf("TEST ERROR: failed to properly set metadata string DBUSER.\n");
+            testStatus = false;
+        }
+
+        tmpStr = psMetadataLookupStr(&rc, site, "DBPASSWORD");
+        if ((rc == false) || (0 != strcmp(tmpStr, "password"))) {
+            printf("TEST ERROR: failed to properly set metadata string DBPASSWORD.\n");
+            testStatus = false;
+        }
+    }
+
+    psFree(site);
+    psFree(camera);
+    psFree(recipe);
+    return(testStatus);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/.cvsignore	(revision 22157)
@@ -0,0 +1,9 @@
+.deps
+.libs
+Makefile
+Makefile.in
+temp
+tst_pmFlatField
+tst_pmMaskBadPixels
+tst_pmNonLinear
+.tmp_tst_pmNonLinearLookupFile
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/Makefile.am	(revision 22157)
@@ -0,0 +1,26 @@
+AM_CPPFLAGS = \
+	$(SRCINC) \
+	-I$(top_srcdir)/test/tap/src \
+	-I$(top_srcdir)/test/pstap/src \
+	$(PSMODULES_CFLAGS)
+
+AM_LDFLAGS = \
+	$(top_builddir)/src/libpsmodules.la  \
+	$(top_builddir)/test/tap/src/libtap.la \
+	$(top_builddir)/test/pstap/src/libpstap.la \
+	$(PSMODULES_LIBS)
+
+TEST_PROGS = \
+	tap_pmShutterCorrection
+
+if BUILD_TESTS
+bin_PROGRAMS = $(TEST_PROGS)
+TESTS = $(TEST_PROGS)
+else
+check_PROGRAMS = $(TEST_PROGS)
+endif
+
+CLEANFILES = $(check_DATA) temp/* core core.* *~ *.bb *.bbg *.da gmon.out
+
+test: check
+	$(top_srcdir)/test/test.pl
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tap_pmBias.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tap_pmBias.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tap_pmBias.c	(revision 22157)
@@ -0,0 +1,529 @@
+/** @file tap_pmBias.c
+ *
+ * XXX: pmBiasSubtractFrame(): Must add tests for CELL.X0, CELL.Y0 stuff:
+ * XXX: pmBiasSubtractFrame(): Must add tests with scale != 1.0
+ * XXX: pmBiasSubtract(): must test with acceptable data
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+
+#define BADFLAT			0
+#define VERBOSE                 1
+#define ERR_TRACE_LEVEL         0
+#define TEST_NUM_ROWS		8
+#define TEST_NUM_COLS		8
+#define NUM_BIAS_DATA		2
+#define NUM_HDUS		8
+#define MISC_NUM                32
+#define MISC_NAME              "META00"
+#define CELL_ALLOC_NAME        "CellName"
+#define NUM_READOUTS            3
+#define BASE_IMAGE              10
+#define BASE_MASK               40
+#define BASE_WEIGHT             70
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (int i=0;i<TEST_NUM_ROWS;i++) {
+        for (int j=0;j<TEST_NUM_COLS;j++) {
+            readout->image->data.F32[i][j] = (float) (i + j);
+        }
+    }
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(readout);
+}
+
+/******************************************************************************
+generateSimpleCell(): This function generates a pmCell data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmCell *generateSimpleCell(pmChip *chip)
+{
+    pmCell *cell = pmCellAlloc(chip, CELL_ALLOC_NAME);
+
+    psMetadataAddS32(cell->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(cell->readouts, NUM_READOUTS);
+    cell->hdu = pmHDUAlloc("cellExtName");
+    for (int i = 0 ; i < NUM_READOUTS ; i++) {
+        cell->readouts->data[i] = generateSimpleReadout(cell);
+    }
+
+    bool rc = pmConfigFileRead(&cell->hdu->format, "../camera/data/camera0/format0.config", "Camera format 0");
+    if (!rc) {
+        diag("pmConfigFileRead() was unsuccessful (from generateSimpleCell())");
+    }
+
+    cell->hdu->images = psArrayAlloc(NUM_HDUS);
+    cell->hdu->masks = psArrayAlloc(NUM_HDUS);
+    cell->hdu->weights = psArrayAlloc(NUM_HDUS);
+    for (int k = 0 ; k < NUM_HDUS ; k++) {
+        cell->hdu->images->data[k]  = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        cell->hdu->masks->data[k]   = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        cell->hdu->weights->data[k] = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(cell->hdu->images->data[k], (float) (BASE_IMAGE+k));
+        psImageInit(cell->hdu->masks->data[k], (psU8) (BASE_MASK+k));
+        psImageInit(cell->hdu->weights->data[k], (float) (BASE_WEIGHT+k));
+    }
+
+    //XXX: Should the region be set some other way?  Like through the various config files?
+//    psRegion *region = psRegionAlloc(0.0, TEST_NUM_COLS-1, 0.0, TEST_NUM_ROWS-1);
+    psRegion *region = psRegionAlloc(0.0, 0.0, 0.0, 0.0);
+    // You shouldn't have to remove the key from the metadata.  Find out how to simply change the key value.
+    psMetadataRemoveKey(cell->concepts, "CELL.TRIMSEC");
+    psMetadataAddPtr(cell->concepts, PS_LIST_TAIL|PS_META_REPLACE, "CELL.TRIMSEC", PS_DATA_REGION, "I am a region", region);
+    psFree(region);
+    return(cell);
+}
+
+void myFreeCell(pmCell *cell)
+{
+    for (int k = 0 ; k < cell->readouts->n ; k++) {
+        psFree(cell->readouts->data[k]);
+    }
+    psFree(cell);
+}
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(1);
+
+
+    // ----------------------------------------------------------------------
+    // pmBiasSubtractFrame() tests
+    // bool pmBiasSubtractFrame(pmReadout *in, pmReadout *sub, float scale)
+    // Call pmBiasSubtractFrame() with NULL pmReadout input
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        pmReadout *sub = generateSimpleReadout(cell);
+
+        ok(!pmBiasSubtractFrame(NULL, sub, 1.0), "pmBiasSubtractFrame(NULL, sub, 1.0) returned FALSE");
+        psFree(in);
+        psFree(sub);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // bool pmBiasSubtractFrame(pmReadout *in, pmReadout *sub, float scale)
+    // Call pmBiasSubtractFrame() with NULL pmReadout input->image
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        psFree(in->image);
+        in->image = NULL;
+        pmReadout *sub = generateSimpleReadout(cell);
+
+        ok(!pmBiasSubtractFrame(in, sub, 1.0), "pmBiasSubtractFrame() returned FALSE with NULL pmReadout input->image");
+        psFree(in);
+        psFree(sub);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmBiasSubtractFrame() with empty pmReadout input->image
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        psFree(in->image);
+        in->image = psImageAlloc(0, 0, PS_TYPE_F32);
+        pmReadout *sub = generateSimpleReadout(cell);
+
+        ok(!pmBiasSubtractFrame(in, sub, 1.0), "pmBiasSubtractFrame() returned FALSE with empty input->image");
+        psFree(in);
+        psFree(sub);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmBiasSubtractFrame() with bad type for pmReadout input->image
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        psFree(in->image);
+        in->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_S32);
+        pmReadout *sub = generateSimpleReadout(cell);
+
+        ok(!pmBiasSubtractFrame(in, sub, 1.0), "pmBiasSubtractFrame() returned FALSE with bad type for pmReadout input->image");
+        psFree(in);
+        psFree(sub);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmBiasSubtractFrame() with NULL pmReadout sub
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        pmReadout *sub = generateSimpleReadout(cell);
+
+        ok(!pmBiasSubtractFrame(in, NULL, 1.0), "pmBiasSubtractFrame(in, NULL, 1.0) returned FALSE");
+        psFree(in);
+        psFree(sub);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmBiasSubtractFrame() with NULL pmReadout sub->image
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        pmReadout *sub = generateSimpleReadout(cell);
+        psFree(sub->image);
+        sub->image = NULL;
+
+        ok(!pmBiasSubtractFrame(in, sub, 1.0), "pmBiasSubtractFrame() returned FALSE with NULL pmReadout sub->image");
+        psFree(in);
+        psFree(sub);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmBiasSubtractFrame() with empty pmReadout sub->image
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        pmReadout *sub = generateSimpleReadout(cell);
+        psFree(sub->image);
+        sub->image = psImageAlloc(0, 0, PS_TYPE_F32);
+
+        ok(!pmBiasSubtractFrame(in, sub, 1.0), "pmBiasSubtractFrame() returned FALSE with NULL pmReadout sub->image");
+        psFree(in);
+        psFree(sub);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmBiasSubtractFrame() with bad type for pmReadout sub->image
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        pmReadout *sub = generateSimpleReadout(cell);
+        psFree(sub->image);
+        sub->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_S32);
+
+        ok(!pmBiasSubtractFrame(in, sub, 1.0), "pmBiasSubtractFrame() returned FALSE with bad type for pmReadout sub->image");
+        psFree(in);
+        psFree(sub);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmBiasSubtractFrame() with differing input image sizes
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        pmReadout *sub = generateSimpleReadout(cell);
+        psFree(sub->image);
+        sub->image = psImageAlloc(TEST_NUM_COLS/2, TEST_NUM_ROWS/2, PS_TYPE_S32);
+
+        ok(!pmBiasSubtractFrame(in, sub, 1.0), "pmBiasSubtractFrame() returned FALSE with with differing input image sizes");
+        psFree(in);
+        psFree(sub);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmBiasSubtractFrame() with differing image X parity
+    {
+        psMemId id = psMemGetId();
+        pmCell *cellIn = generateSimpleCell(NULL);
+        pmCell *cellSub = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cellIn);
+        pmReadout *sub = generateSimpleReadout(cellSub);
+
+        // Set the X parity differently for each pmReadout's parent cell
+        psMetadataAddS32(in->parent->concepts, PS_LIST_TAIL, "CELL.XPARITY", PS_META_REPLACE, "", -1);
+        psMetadataAddS32(sub->parent->concepts, PS_LIST_TAIL, "CELL.XPARITY", PS_META_REPLACE, "", 1);
+        ok(!pmBiasSubtractFrame(in, sub, 1.0), "pmBiasSubtractFrame() returned FALSE with differing image X parity");
+        psFree(in);
+        psFree(sub);
+        myFreeCell(cellIn);
+        myFreeCell(cellSub);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmBiasSubtractFrame() with differing image Y parity
+    {
+        psMemId id = psMemGetId();
+        pmCell *cellIn = generateSimpleCell(NULL);
+        pmCell *cellSub = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cellIn);
+        pmReadout *sub = generateSimpleReadout(cellSub);
+
+        // Set the Y parity differently for each pmReadout's parent cell
+        psMetadataAddS32(in->parent->concepts, PS_LIST_TAIL, "CELL.YPARITY", PS_META_REPLACE, "", -1);
+        psMetadataAddS32(sub->parent->concepts, PS_LIST_TAIL, "CELL.YPARITY", PS_META_REPLACE, "", 1);
+        ok(!pmBiasSubtractFrame(in, sub, 1.0), "pmBiasSubtractFrame() returned FALSE with differing image Y parity");
+        psFree(in);
+        psFree(sub);
+        myFreeCell(cellIn);
+        myFreeCell(cellSub);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmBiasSubtractFrame() with differing image Y parity
+    {
+        psMemId id = psMemGetId();
+        pmCell *cellIn = generateSimpleCell(NULL);
+        pmCell *cellSub = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cellIn);
+        pmReadout *sub = generateSimpleReadout(cellSub);
+        for (int i = 0 ; i < in->image->numRows ; i++) {
+            for (int j = 0 ; j < in->image->numCols ; j++) {
+                in->image->data.F32[i][j] = (float) (i + j + 10);
+	    }
+	}
+
+        for (int i = 0 ; i < sub->image->numRows ; i++) {
+            for (int j = 0 ; j < sub->image->numCols ; j++) {
+                sub->image->data.F32[i][j] = 10.0;
+	    }
+	}
+
+        ok(pmBiasSubtractFrame(in, sub, 1.0), "pmBiasSubtractFrame() returned TRUE");
+        bool errorFlag = false;
+        for (int i = 0 ; i < in->image->numRows ; i++) {
+            for (int j = 0 ; j < in->image->numCols ; j++) {
+                if (in->image->data.F32[i][j] != (float) (i + j)) {
+                    if (VERBOSE) diag("ERROR: image[%d][%d] is %.2f, should be %.2f\n",
+                        i, j, in->image->data.F32[i][j], (float) (i + j));
+                    errorFlag = true;
+		}
+	    }
+	}
+
+        ok(!errorFlag, "pmBiasSubtractFrame() set the pixels correctly");
+        psFree(in);
+        psFree(sub);
+        myFreeCell(cellIn);
+        myFreeCell(cellSub);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmBiasSubtract() tests
+    // bool pmBiasSubtract(pmReadout *in, pmOverscanOptions *overscanOpts,
+    //               const pmReadout *bias, const pmReadout *dark, const pmFPAview *view)
+    // Call pmBiasSubtract() with NULL pmReadout input
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        pmReadout *bias = generateSimpleReadout(cell);
+        pmReadout *dark = generateSimpleReadout(cell);
+        pmFPAview *view = pmFPAviewAlloc(TEST_NUM_ROWS);
+        // XXX: Must set the following:
+        pmOverscanOptions *overscanOpts = NULL;
+
+        ok(!pmBiasSubtract(NULL, overscanOpts, bias, dark, view), "pmBiasSubtract() returned FALSE with NULL input pmReadout: in");
+        psFree(in);
+        psFree(bias);
+        psFree(dark);
+        myFreeCell(cell);
+        psFree(view);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmBiasSubtract() with NULL pmReadout input->image
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        psFree(in->image);
+        in->image = NULL;
+        pmReadout *bias = generateSimpleReadout(cell);
+        pmReadout *dark = generateSimpleReadout(cell);
+        pmFPAview *view = pmFPAviewAlloc(TEST_NUM_ROWS);
+        // XXX: Must set the following:
+        pmOverscanOptions *overscanOpts = NULL;
+
+        ok(!pmBiasSubtract(in, overscanOpts, bias, dark, view), "pmBiasSubtract() returned FALSE with NULL input image");
+        psFree(in);
+        psFree(bias);
+        psFree(dark);
+        myFreeCell(cell);
+        psFree(view);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmBiasSubtract() with wrong type for pmReadout input->image
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        psFree(in->image);
+        in->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_S32);
+        pmReadout *bias = generateSimpleReadout(cell);
+        pmReadout *dark = generateSimpleReadout(cell);
+        pmFPAview *view = pmFPAviewAlloc(TEST_NUM_ROWS);
+        // XXX: Must set the following:
+        pmOverscanOptions *overscanOpts = NULL;
+
+        ok(!pmBiasSubtract(in, overscanOpts, bias, dark, view), "pmBiasSubtract() returned FALSE with wrong type for pmReadout input->image");
+        psFree(in);
+        psFree(bias);
+        psFree(dark);
+        myFreeCell(cell);
+        psFree(view);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmBiasSubtract() with NULL image for input pmReadout bias
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        pmReadout *bias = generateSimpleReadout(cell);
+        psFree(bias->image);
+        bias->image = NULL;
+        pmReadout *dark = generateSimpleReadout(cell);
+        pmFPAview *view = pmFPAviewAlloc(TEST_NUM_ROWS);
+        // XXX: Must set the following:
+        pmOverscanOptions *overscanOpts = NULL;
+
+        ok(!pmBiasSubtract(in, overscanOpts, bias, dark, view), "pmBiasSubtract() returned FALSE with NULL image for input pmReadout bias");
+        psFree(in);
+        psFree(bias);
+        psFree(dark);
+        myFreeCell(cell);
+        psFree(view);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmBiasSubtract() with wrong type image for input pmReadout bias
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        pmReadout *bias = generateSimpleReadout(cell);
+        psFree(bias->image);
+        bias->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_S32);
+        pmReadout *dark = generateSimpleReadout(cell);
+        pmFPAview *view = pmFPAviewAlloc(TEST_NUM_ROWS);
+        // XXX: Must set the following:
+        pmOverscanOptions *overscanOpts = NULL;
+
+        ok(!pmBiasSubtract(in, overscanOpts, bias, dark, view), "pmBiasSubtract() returned FALSE with wrong type image for input pmReadout bias");
+        psFree(in);
+        psFree(bias);
+        psFree(dark);
+        myFreeCell(cell);
+        psFree(view);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmBiasSubtract() with NULL image for input pmReadout dark
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        pmReadout *bias = generateSimpleReadout(cell);
+        pmReadout *dark = generateSimpleReadout(cell);
+        psFree(dark->image);
+        dark->image = NULL;
+        pmFPAview *view = pmFPAviewAlloc(TEST_NUM_ROWS);
+        // XXX: Must set the following:
+        pmOverscanOptions *overscanOpts = NULL;
+
+        ok(!pmBiasSubtract(in, overscanOpts, bias, dark, view), "pmBiasSubtract() returned FALSE with wrong type image for input pmReadout bias");
+        psFree(in);
+        psFree(bias);
+        psFree(dark);
+        myFreeCell(cell);
+        psFree(view);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmBiasSubtract() with wrong type image for input pmReadout dark
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        pmReadout *bias = generateSimpleReadout(cell);
+        pmReadout *dark = generateSimpleReadout(cell);
+        psFree(dark->image);
+        dark->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_S32);
+        pmFPAview *view = pmFPAviewAlloc(TEST_NUM_ROWS);
+        // XXX: Must set the following:
+        pmOverscanOptions *overscanOpts = NULL;
+
+        ok(!pmBiasSubtract(in, overscanOpts, bias, dark, view), "pmBiasSubtract() returned FALSE with wrong type image for input pmReadout dark");
+        psFree(in);
+        psFree(bias);
+        psFree(dark);
+        myFreeCell(cell);
+        psFree(view);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmBiasSubtract() with NULL pmView (while pmReadout dark non-NULL)
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        pmReadout *bias = generateSimpleReadout(cell);
+        pmReadout *dark = generateSimpleReadout(cell);
+        pmFPAview *view = pmFPAviewAlloc(TEST_NUM_ROWS);
+        // XXX: Must set the following:
+        pmOverscanOptions *overscanOpts = NULL;
+
+        ok(!pmBiasSubtract(in, overscanOpts, bias, dark, NULL), "pmBiasSubtract() returned FALSE with NULL pmView (while pmReadout dark non-NULL)");
+        psFree(in);
+        psFree(bias);
+        psFree(dark);
+        myFreeCell(cell);
+        psFree(view);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tap_pmFlatField.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tap_pmFlatField.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tap_pmFlatField.c	(revision 22157)
@@ -0,0 +1,348 @@
+/** @file tap_pmFlatField.c
+ *
+ * XXX: Added a mask argument to pmFlatField().  Must add tests.  For now, all
+ * masks are NULL.
+ *
+ *  XXX: I added the CELL.TRIMSEC region code but there are not tests for it.
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+#define BADFLAT			0
+#define VERBOSE                 1
+#define ERR_TRACE_LEVEL         10
+#define TEST_NUM_ROWS		8
+#define TEST_NUM_COLS		8
+#define NUM_BIAS_DATA		2
+#define NUM_HDUS		8
+#define MISC_NUM                32
+#define MISC_NAME              "META00"
+#define CELL_ALLOC_NAME        "CellName"
+#define NUM_READOUTS            3
+#define BASE_IMAGE              10
+#define BASE_MASK               40
+#define BASE_WEIGHT             70
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (int i=0;i<TEST_NUM_ROWS;i++) {
+        for (int j=0;j<TEST_NUM_COLS;j++) {
+            readout->image->data.F32[i][j] = (float) (i + j);
+        }
+    }
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(readout);
+}
+
+/******************************************************************************
+generateSimpleCell(): This function generates a pmCell data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmCell *generateSimpleCell(pmChip *chip)
+{
+    pmCell *cell = pmCellAlloc(chip, CELL_ALLOC_NAME);
+
+    psMetadataAddS32(cell->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(cell->readouts, NUM_READOUTS);
+    cell->hdu = pmHDUAlloc("cellExtName");
+    for (int i = 0 ; i < NUM_READOUTS ; i++) {
+        cell->readouts->data[i] = generateSimpleReadout(cell);
+    }
+
+    bool rc = pmConfigFileRead(&cell->hdu->format, "../camera/data/camera0/format0.config", "Camera format 0");
+    if (!rc) {
+        diag("pmConfigFileRead() was unsuccessful (from generateSimpleCell())");
+    }
+
+    cell->hdu->images = psArrayAlloc(NUM_HDUS);
+    cell->hdu->masks = psArrayAlloc(NUM_HDUS);
+    cell->hdu->weights = psArrayAlloc(NUM_HDUS);
+    for (int k = 0 ; k < NUM_HDUS ; k++) {
+        cell->hdu->images->data[k]  = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        cell->hdu->masks->data[k]   = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        cell->hdu->weights->data[k] = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(cell->hdu->images->data[k], (float) (BASE_IMAGE+k));
+        psImageInit(cell->hdu->masks->data[k], (psU8) (BASE_MASK+k));
+        psImageInit(cell->hdu->weights->data[k], (float) (BASE_WEIGHT+k));
+    }
+
+    //XXX: Should the region be set some other way?  Like through the various config files?
+//    psRegion *region = psRegionAlloc(0.0, TEST_NUM_COLS-1, 0.0, TEST_NUM_ROWS-1);
+    psRegion *region = psRegionAlloc(0.0, 0.0, 0.0, 0.0);
+    // You shouldn't have to remove the key from the metadata.  Find out how to simply change the key value.
+    psMetadataRemoveKey(cell->concepts, "CELL.TRIMSEC");
+    psMetadataAddPtr(cell->concepts, PS_LIST_TAIL|PS_META_REPLACE, "CELL.TRIMSEC", PS_DATA_REGION, "I am a region", region);
+    psFree(region);
+    return(cell);
+}
+
+void myFreeCell(pmCell *cell)
+{
+    for (int k = 0 ; k < cell->readouts->n ; k++) {
+        psFree(cell->readouts->data[k]);
+    }
+    psFree(cell);
+}
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    plan_tests(28);
+
+
+    // bool pmFlatField(pmReadout *in, pmReadout *flat, psMaskType badFlat)
+    // Test pmFlatField() with NULL input psReadout
+    {
+        psMemId id = psMemGetId();
+        pmReadout *in = generateSimpleReadout(NULL);
+        pmReadout *flat = generateSimpleReadout(NULL);
+        ok(!pmFlatField(NULL, flat, 0), "pmFlatField(NULL, flat, 0) returned FALSE");
+        psFree(in);
+        psFree(flat);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // bool pmFlatField(pmReadout *in, pmReadout *flat, psMaskType badFlat)
+    // Test pmFlatField() with NULL input psReadout->image
+    {
+        psMemId id = psMemGetId();
+        pmReadout *in = generateSimpleReadout(NULL);
+        psFree(in->image);
+        in->image = NULL;
+        pmReadout *flat = generateSimpleReadout(NULL);
+        ok(!pmFlatField(in, flat, 0), "pmFlatField(in, flat, 0) returned FALSE with NULL input psReadout->image");
+        psFree(in);
+        psFree(flat);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // bool pmFlatField(pmReadout *in, pmReadout *flat, psMaskType badFlat)
+    // Test pmFlatField() with NULL input psReadout->image
+    {
+        psMemId id = psMemGetId();
+        pmReadout *in = generateSimpleReadout(NULL);
+        psFree(in->image);
+        in->image = psImageAlloc(0, 0, PS_TYPE_F32);
+        pmReadout *flat = generateSimpleReadout(NULL);
+        ok(!pmFlatField(in, flat, 0), "pmFlatField(in, flat, 0) returned FALSE with empty input psReadout->image");
+        psFree(in);
+        psFree(flat);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // bool pmFlatField(pmReadout *in, pmReadout *flat, psMaskType badFlat)
+    // Test pmFlatField() with NULL input flat
+    {
+        psMemId id = psMemGetId();
+        pmReadout *in = generateSimpleReadout(NULL);
+        pmReadout *flat = generateSimpleReadout(NULL);
+        ok(!pmFlatField(in, NULL, 0), "pmFlatField(in, NULL, 0) returned FALSE");
+        psFree(in);
+        psFree(flat);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // bool pmFlatField(pmReadout *in, pmReadout *flat, psMaskType badFlat)
+    // Test pmFlatField() with NULL input flat->image
+    {
+        psMemId id = psMemGetId();
+        pmReadout *in = generateSimpleReadout(NULL);
+        pmReadout *flat = generateSimpleReadout(NULL);
+        psFree(flat->image);
+        flat->image = NULL;
+        ok(!pmFlatField(in, NULL, 0), "pmFlatField(in, flat, 0) returned FALSE with NULL input flat->image");
+        psFree(in);
+        psFree(flat);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // bool pmFlatField(pmReadout *in, pmReadout *flat, psMaskType badFlat)
+    // Test pmFlatField() with NULL input flat->image
+    {
+        psMemId id = psMemGetId();
+        pmReadout *in = generateSimpleReadout(NULL);
+        pmReadout *flat = generateSimpleReadout(NULL);
+        psFree(flat->image);
+        flat->image = psImageAlloc(0, 0, PS_TYPE_F32);
+        ok(!pmFlatField(in, NULL, 0), "pmFlatField(in, flat, 0) returned FALSE with empty input flat->image");
+        psFree(in);
+        psFree(flat);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // bool pmFlatField(pmReadout *in, pmReadout *flat, psMaskType badFlat)
+    // Test pmFlatField() with differing types of flat/in image
+    {
+        psMemId id = psMemGetId();
+        pmReadout *in = generateSimpleReadout(NULL);
+        pmReadout *flat = generateSimpleReadout(NULL);
+        psFree(flat->image);
+        flat->image = psImageAlloc(0, 0, PS_TYPE_F64);
+        ok(!pmFlatField(in, NULL, 0), "pmFlatField(in, flat, 0) returned FALSE with differing types of flat/in image");
+        psFree(in);
+        psFree(flat);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmFlatField() with input image larger than flat image
+    {
+        psMemId id = psMemGetId();
+        pmReadout *in = generateSimpleReadout(NULL);
+        pmReadout *flat = generateSimpleReadout(NULL);
+        psFree(flat->image);
+        flat->image = psImageAlloc(TEST_NUM_ROWS/2, TEST_NUM_COLS/2, PS_TYPE_F32);
+        ok(!pmFlatField(in, flat, 1), "pmFlatField(in, flat, 0) returned FALSE with input image larger than flat image");
+        psFree(in);
+        psFree(flat);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmFlatField() with input image and flat image of differing types
+    {
+        psMemId id = psMemGetId();
+        pmReadout *in = generateSimpleReadout(NULL);
+        pmReadout *flat = generateSimpleReadout(NULL);
+        psFree(flat->image);
+        flat->image = psImageAlloc(TEST_NUM_ROWS, TEST_NUM_COLS, PS_TYPE_F64);
+        ok(!pmFlatField(in, flat, 1), "pmFlatField(in, flat, 0) returned FALSE with input image and flat image of differing types");
+        psFree(in);
+        psFree(flat);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmFlatField() with input image mask smaller than input image
+    {
+        psMemId id = psMemGetId();
+        pmReadout *in = generateSimpleReadout(NULL);
+        pmReadout *flat = generateSimpleReadout(NULL);
+        psFree(in->mask);
+        in->mask = psImageAlloc(TEST_NUM_ROWS/2, TEST_NUM_COLS/2, PS_TYPE_MASK);
+        ok(!pmFlatField(in, flat, 1), "pmFlatField(in, flat, 0) returned FALSE with input image mask smaller than input image");
+        psFree(in);
+        psFree(flat);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmFlatField() with input image mask of incorrect type
+    {
+        psMemId id = psMemGetId();
+        pmReadout *in = generateSimpleReadout(NULL);
+        pmReadout *flat = generateSimpleReadout(NULL);
+        psFree(in->mask);
+        in->mask = psImageAlloc(TEST_NUM_ROWS, TEST_NUM_COLS, PS_TYPE_F32);
+        ok(!pmFlatField(in, flat, 1), "pmFlatField(in, flat, 0) returned FALSE with input image mask of incorrect type");
+        psFree(in);
+        psFree(flat);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmFlatField() with offset greater than input image
+    // XXX: Must rewrite with offsets coming from metadata
+    if (0) {
+        psMemId id = psMemGetId();
+        pmReadout *in = generateSimpleReadout(NULL);
+        pmReadout *flat = generateSimpleReadout(NULL);
+        *(int*)&in->col0 = 50;
+        *(int*)&in->row0 = 50;
+        ok(!pmFlatField(in, flat, 1), "pmFlatField(in, flat, 0) returned FALSE with offset greater than input image");
+        psFree(in);
+        psFree(flat);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmFlatField() with acceptable input data
+    // Set the flat field to 1
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        pmReadout *flat = generateSimpleReadout(cell);
+        psImageInit(in->image, 6.0);
+        psImageInit(in->mask, 0);
+        psImageInit(flat->image, 2.0);
+        ok(pmFlatField(in, flat, 1), "pmFlatField(in, flat, 0) returned TRUE with acceptable input data");
+        bool errorFlag = false;
+        for (int i = 0 ; i < in->image->numRows ; i++) {
+            for (int j = 0 ; j < in->image->numCols ; j++) {
+                if (in->image->data.F32[i][j] != 3.0) {
+                    if (VERBOSE) diag("ERROR: image[%d][%d] is %.2f, should be 3.0", i, j,
+                        in->image->data.F32[i][j]);
+		}
+                if (in->mask->data.PS_TYPE_MASK_DATA[i][j] != 0) {
+                    if (VERBOSE) diag("ERROR: mask[%d][%d] is %d, should be 0", i, j,
+                        in->image->data.PS_TYPE_MASK_DATA[i][j]);
+		}
+	    }
+	}
+        ok(!errorFlag, "pmFlatField() set the image data correctly");
+
+        psFree(in);
+        psFree(flat);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmFlatField() with acceptable input data
+    // Set the flat field to -1
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        pmReadout *flat = generateSimpleReadout(cell);
+        psImageInit(in->image, 6.0);
+        psImageInit(in->mask, 0);
+        psImageInit(flat->image, -1.0);
+        ok(pmFlatField(in, flat, 1), "pmFlatField(in, flat, 0) returned TRUE with acceptable input data");
+        bool errorFlag = false;
+        for (int i = 0 ; i < in->image->numRows ; i++) {
+            for (int j = 0 ; j < in->image->numCols ; j++) {
+                if (!isnan(in->image->data.F32[i][j])) {
+                    if (VERBOSE) diag("ERROR: image[%d][%d] is %.2f, should be NAN", i, j,
+                        in->image->data.F32[i][j]);
+		}
+                if (in->mask->data.PS_TYPE_MASK_DATA[i][j] != 1) {
+                    if (VERBOSE) diag("ERROR: mask[%d][%d] is %d, should be 0", i, j,
+                        in->image->data.PS_TYPE_MASK_DATA[i][j]);
+		}
+	    }
+	}
+        ok(!errorFlag, "pmFlatField() set the image data correctly");
+
+        psFree(in);
+        psFree(flat);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tap_pmMaskBadPixels.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tap_pmMaskBadPixels.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tap_pmMaskBadPixels.c	(revision 22157)
@@ -0,0 +1,520 @@
+/** @file tap_pmMaskBadPixels.c
+ *
+ * XXX: must test pmMaskFlagSuspectPixels() with acceptable input data
+ * XXX: must test pmMaskIdentifyBadPixels() with acceptable input data
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+
+#define BADFLAT			0
+#define VERBOSE                 1
+#define ERR_TRACE_LEVEL         0
+#define TEST_NUM_ROWS		8
+#define TEST_NUM_COLS		8
+#define NUM_BIAS_DATA		2
+#define NUM_HDUS		8
+#define MISC_NUM                32
+#define MISC_NAME              "META00"
+#define CELL_ALLOC_NAME        "CellName"
+#define NUM_READOUTS            3
+#define BASE_IMAGE              10
+#define BASE_MASK               40
+#define BASE_WEIGHT             70
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (int i=0;i<TEST_NUM_ROWS;i++) {
+        for (int j=0;j<TEST_NUM_COLS;j++) {
+            readout->image->data.F32[i][j] = (float) (i + j);
+        }
+    }
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(readout);
+}
+
+/******************************************************************************
+generateSimpleCell(): This function generates a pmCell data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmCell *generateSimpleCell(pmChip *chip)
+{
+    pmCell *cell = pmCellAlloc(chip, CELL_ALLOC_NAME);
+
+    psMetadataAddS32(cell->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(cell->readouts, NUM_READOUTS);
+    cell->hdu = pmHDUAlloc("cellExtName");
+    for (int i = 0 ; i < NUM_READOUTS ; i++) {
+        cell->readouts->data[i] = generateSimpleReadout(cell);
+    }
+
+    bool rc = pmConfigFileRead(&cell->hdu->format, "../camera/data/camera0/format0.config", "Camera format 0");
+    if (!rc) {
+        diag("pmConfigFileRead() was unsuccessful (from generateSimpleCell())");
+    }
+
+    cell->hdu->images = psArrayAlloc(NUM_HDUS);
+    cell->hdu->masks = psArrayAlloc(NUM_HDUS);
+    cell->hdu->weights = psArrayAlloc(NUM_HDUS);
+    for (int k = 0 ; k < NUM_HDUS ; k++) {
+        cell->hdu->images->data[k]  = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        cell->hdu->masks->data[k]   = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        cell->hdu->weights->data[k] = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(cell->hdu->images->data[k], (float) (BASE_IMAGE+k));
+        psImageInit(cell->hdu->masks->data[k], (psU8) (BASE_MASK+k));
+        psImageInit(cell->hdu->weights->data[k], (float) (BASE_WEIGHT+k));
+    }
+
+    //XXX: Should the region be set some other way?  Like through the various config files?
+//    psRegion *region = psRegionAlloc(0.0, TEST_NUM_COLS-1, 0.0, TEST_NUM_ROWS-1);
+    psRegion *region = psRegionAlloc(0.0, 0.0, 0.0, 0.0);
+    // You shouldn't have to remove the key from the metadata.  Find out how to simply change the key value.
+    psMetadataRemoveKey(cell->concepts, "CELL.TRIMSEC");
+    psMetadataAddPtr(cell->concepts, PS_LIST_TAIL|PS_META_REPLACE, "CELL.TRIMSEC", PS_DATA_REGION, "I am a region", region);
+    psFree(region);
+    return(cell);
+}
+
+void myFreeCell(pmCell *cell)
+{
+    for (int k = 0 ; k < cell->readouts->n ; k++) {
+        psFree(cell->readouts->data[k]);
+    }
+    psFree(cell);
+}
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(1);
+
+
+    // ----------------------------------------------------------------------
+    // pmMaskBadPixels() tests
+    // Call pmMaskBadPixels() with NULL pmReadout input
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        pmReadout *mask = generateSimpleReadout(cell);
+
+        ok(!pmMaskBadPixels(NULL, mask, 0), "pmMaskBadPixels(NULL, mask, 0) returned FALSE");
+        psFree(in);
+        psFree(mask);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmMaskBadPixels() with NULL pmReadout input->mask
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        psFree(in->mask);
+        in->mask = NULL;
+        pmReadout *mask = generateSimpleReadout(cell);
+
+        ok(!pmMaskBadPixels(in, mask, 0), "pmMaskBadPixels() returned FALSE with NULL pmReadout input->mask");
+        psFree(in);
+        psFree(mask);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmMaskBadPixels() with incorrect type for input->mask
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        psFree(in->mask);
+        in->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        pmReadout *mask = generateSimpleReadout(cell);
+
+        ok(!pmMaskBadPixels(in, mask, 0), "pmMaskBadPixels() returned FALSE with NULL pmReadout input->mask");
+        psFree(in);
+        psFree(mask);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmMaskBadPixels() with NULL pmReadout mask
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        pmReadout *mask = generateSimpleReadout(cell);
+
+        ok(!pmMaskBadPixels(in, NULL, 0), "pmMaskBadPixels(in, NULL, 0) returned FALSE");
+        psFree(in);
+        psFree(mask);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmMaskBadPixels() with NULL pmReadout mask->mask
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        pmReadout *mask = generateSimpleReadout(cell);
+        psFree(mask->mask);
+        mask->mask = NULL;
+
+        ok(!pmMaskBadPixels(in, mask, 0), "pmMaskBadPixels() returned FALSE with NULL pmReadout mask->mask");
+        psFree(in);
+        psFree(mask);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmMaskBadPixels() with incorrect type for mask->mask
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        pmReadout *mask = generateSimpleReadout(cell);
+        psFree(mask->mask);
+        mask->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+
+        ok(!pmMaskBadPixels(in, mask, 0), "pmMaskBadPixels() returned FALSE with incorrect type for mask->mask");
+        psFree(in);
+        psFree(mask);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmMaskBadPixels() with input mask larger than mask->mask
+    {
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        pmReadout *mask = generateSimpleReadout(cell);
+        psFree(mask->mask);
+        mask->mask = psImageAlloc(TEST_NUM_COLS/2, TEST_NUM_ROWS/2, PS_TYPE_MASK);
+
+        ok(!pmMaskBadPixels(in, mask, 0), "pmMaskBadPixels() returned FALSE with input mask larger than mask->mask");
+        psFree(in);
+        psFree(mask);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmMaskBadPixels() with acceptable input data
+    {
+        #define MASK_VAL 1
+        psMemId id = psMemGetId();
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        pmReadout *mask = generateSimpleReadout(cell);
+        for (int i = 0 ; i < in->mask->numRows ; i++) {
+            for (int j = 0 ; j < in->mask->numCols ; j++) {
+                in->mask->data.PS_TYPE_MASK_DATA[i][j] = 0;
+                mask->mask->data.PS_TYPE_MASK_DATA[i][j] = 0;
+                if (i >= in->mask->numRows/2 && j >= in->mask->numCols/2) {
+                    mask->mask->data.PS_TYPE_MASK_DATA[i][j] = MASK_VAL;
+		}
+	    }
+	}
+
+        ok(pmMaskBadPixels(in, mask, MASK_VAL), "pmMaskBadPixels() returned TRUE with acceptable input data");
+        bool errorFlag = false;     
+        for (int i = 0 ; i < in->mask->numRows ; i++) {
+            for (int j = 0 ; j < in->mask->numCols ; j++) {
+                psU8 expect;
+                if (i >= in->mask->numRows/2 && j >= in->mask->numCols/2) {
+                    expect = MASK_VAL;
+		} else {
+                    expect = 0;
+		}
+                if (in->mask->data.PS_TYPE_MASK_DATA[i][j] != expect) {
+                    if (VERBOSE) diag("ERROR: in->mask[%d][%d] is %d, should be %d",
+                                       i, j, in->mask->data.PS_TYPE_MASK_DATA[i][j], expect);
+                    errorFlag = true;
+		}
+	    }
+	}
+        ok(!errorFlag, "pmMaskBadPixels() set the input mask correctly");
+
+        psFree(in);
+        psFree(mask);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+
+    // ----------------------------------------------------------------------
+    // pmMaskFlagSuspectPixels() tests
+    // Call pmMaskFlagSuspectPixels() with NULL pmReadout input
+    {
+        psMemId id = psMemGetId();
+        psImage *out = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_S32);
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, 0);
+
+        ok(!pmMaskFlagSuspectPixels(out, NULL, 1.0, 1, 1.0, rng), "pmMaskFlagSuspectPixels() returned NULL with NULL input pmReadout");
+        psFree(rng);
+        psFree(out);
+        psFree(in);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmMaskFlagSuspectPixels() with NULL pmReadout->image
+    {
+        psMemId id = psMemGetId();
+        psImage *out = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_S32);
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        psFree(in->image);
+        in->image = NULL;
+        psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, 0);
+        ok(!pmMaskFlagSuspectPixels(out, NULL, 1.0, 1, 1.0, rng), "pmMaskFlagSuspectPixels() returned NULL with NULL input pmReadout->image");
+        psFree(rng);
+        psFree(out);
+        psFree(in);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmMaskFlagSuspectPixels() with empty pmReadout->image
+    {
+        psMemId id = psMemGetId();
+        psImage *out = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_S32);
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        psFree(in->image);
+        in->image = psImageAlloc(0, 0, PS_TYPE_F32);
+        psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, 0);
+        ok(!pmMaskFlagSuspectPixels(out, NULL, 1.0, 1, 1.0, rng), "pmMaskFlagSuspectPixels() returned NULL with empty input pmReadout->image");
+        psFree(rng);
+        psFree(out);
+        psFree(in);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmMaskFlagSuspectPixels() with incorrect type for pmReadout->image
+    {
+        psMemId id = psMemGetId();
+        psImage *out = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_S32);
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        psFree(in->image);
+        in->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_S32);
+        psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, 0);
+        ok(!pmMaskFlagSuspectPixels(out, NULL, 1.0, 1, 1.0, rng), "pmMaskFlagSuspectPixels() returned NULL with incorrect type for pmReadout->image");
+        psFree(rng);
+        psFree(out);
+        psFree(in);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmMaskFlagSuspectPixels() with empty in->mask
+    {
+        psMemId id = psMemGetId();
+        psImage *out = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_S32);
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        psFree(in->mask);
+        in->mask = psImageAlloc(0, 0, PS_TYPE_MASK);
+        psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, 0);
+        ok(!pmMaskFlagSuspectPixels(out, NULL, 1.0, 1, 1.0, rng), "pmMaskFlagSuspectPixels() returned NULL with incorrect type for pmReadout->image");
+        psFree(rng);
+        psFree(out);
+        psFree(in);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmMaskFlagSuspectPixels() with bad size for in->mask
+    {
+        psMemId id = psMemGetId();
+        psImage *out = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_S32);
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        psFree(in->mask);
+        in->mask = psImageAlloc(TEST_NUM_COLS/2, TEST_NUM_ROWS/2, PS_TYPE_MASK);
+        psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, 0);
+        ok(!pmMaskFlagSuspectPixels(out, NULL, 1.0, 1, 1.0, rng), "pmMaskFlagSuspectPixels() returned NULL with bad size for in->mask");
+        psFree(rng);
+        psFree(out);
+        psFree(in);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmMaskFlagSuspectPixels() with bad type for in->mask
+    {
+        psMemId id = psMemGetId();
+        psImage *out = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_S32);
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        psFree(in->mask);
+        in->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_S32);
+        psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, 0);
+        ok(!pmMaskFlagSuspectPixels(out, NULL, 1.0, 1, 1.0, rng), "pmMaskFlagSuspectPixels() returned NULL with bad type for in->mask");
+        psFree(rng);
+        psFree(out);
+        psFree(in);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmMaskFlagSuspectPixels() with empty out image
+    {
+        psMemId id = psMemGetId();
+        psImage *out = psImageAlloc(0, 0, PS_TYPE_S32);
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, 0);
+        ok(!pmMaskFlagSuspectPixels(out, NULL, 1.0, 1, 1.0, rng), "pmMaskFlagSuspectPixels() returned NULL with empty out image");
+        psFree(rng);
+        psFree(out);
+        psFree(in);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmMaskFlagSuspectPixels() with incorrect type for out image
+    {
+        psMemId id = psMemGetId();
+        psImage *out = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, 0);
+        ok(!pmMaskFlagSuspectPixels(out, NULL, 1.0, 1, 1.0, rng), "pmMaskFlagSuspectPixels() returned NULL with incorrect type for out image");
+        psFree(rng);
+        psFree(out);
+        psFree(in);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmMaskFlagSuspectPixels() with incorrect size for out image
+    {
+        psMemId id = psMemGetId();
+        psImage *out = psImageAlloc(TEST_NUM_COLS/2, TEST_NUM_ROWS/2, PS_TYPE_S32);
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, 0);
+        ok(!pmMaskFlagSuspectPixels(out, NULL, 1.0, 1, 1.0, rng), "pmMaskFlagSuspectPixels() returned NULL with incorrect size for out image");
+        psFree(rng);
+        psFree(out);
+        psFree(in);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmMaskFlagSuspectPixels() with rej input <= 0.0
+    {
+        psMemId id = psMemGetId();
+        psImage *out = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_S32);
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, 0);
+
+        ok(!pmMaskFlagSuspectPixels(out, in, 0.0, 1, 1.0, rng), "pmMaskFlagSuspectPixels() returned NULL with rej input <= 0.0");
+        psFree(rng);
+        psFree(out);
+        psFree(in);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmMaskFlagSuspectPixels() with frac input outside range [0.0:1.0]
+    {
+        psMemId id = psMemGetId();
+        psImage *out = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_S32);
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *in = generateSimpleReadout(cell);
+        psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, 0);
+
+        ok(!pmMaskFlagSuspectPixels(out, in, 1.0, 1, -1.0, rng), "pmMaskFlagSuspectPixels() returned NULL with frac input < 0.0");
+        ok(!pmMaskFlagSuspectPixels(out, in, 1.0, 1, 2.0, rng), "pmMaskFlagSuspectPixels() returned NULL with frac input > 1.0");
+        psFree(rng);
+        psFree(out);
+        psFree(in);
+        myFreeCell(cell);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+
+    // ----------------------------------------------------------------------
+    // pmMaskIdentifyBadPixels() tests
+    // Call pmMaskIdentifyBadPixels() with NULL input image
+    {
+        psMemId id = psMemGetId();
+        psImage *suspects = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_S32);
+        ok(!pmMaskIdentifyBadPixels(NULL, 1.0, 0), "pmMaskIdentifyBadPixels() returned NULL with NULL input image");
+        psFree(suspects);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmMaskIdentifyBadPixels() with empty input image
+    {
+        psMemId id = psMemGetId();
+        psImage *suspects = psImageAlloc(0, 0, PS_TYPE_S32);
+        ok(!pmMaskIdentifyBadPixels(NULL, 1.0, 0), "pmMaskIdentifyBadPixels() returned NULL with empty input image");
+        psFree(suspects);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmMaskIdentifyBadPixels() with incorrect type for input image
+    {
+        psMemId id = psMemGetId();
+        psImage *suspects = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        ok(!pmMaskIdentifyBadPixels(NULL, 1.0, 0), "pmMaskIdentifyBadPixels() returned NULL with incorrect type for input image");
+        psFree(suspects);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tap_pmNonLinear.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tap_pmNonLinear.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tap_pmNonLinear.c	(revision 22157)
@@ -0,0 +1,237 @@
+/* @file tap_pmNonLinear.c
+ *
+ *  XXX: Add tests 
+ *      Input psVectors and psImages have incorrect type
+ *      Input psVectors are wrong size
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+
+#define TEST_NUM_ROWS 8
+#define TEST_NUM_COLS 8
+#define NUM_BIAS_DATA 2
+#define MISC_NUM                32
+#define MISC_NAME              "META00"
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (int i=0;i<TEST_NUM_ROWS;i++) {
+        for (int j=0;j<TEST_NUM_COLS;j++) {
+            readout->image->data.F32[i][j] = (float) (i + j);
+        }
+    }
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(readout);
+}
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(72);
+
+
+    // ----------------------------------------------------------------------
+    // pmNonLinearityPolynomial() tests
+    // Call pmNonLinearityPolynomial() with NULL input readout.
+    {
+        psMemId id = psMemGetId();
+        psPolynomial1D *myPoly = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 2);
+        myPoly->coeff[1] = 1.0;
+        pmReadout *rc = pmNonLinearityPolynomial(NULL, myPoly);
+        ok(!rc, "pmNonLinearityPolynomial() returned NULL with a NULL input pmReadout");
+        psFree(myPoly);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+    
+     
+    // Call pmNonLinearityPolynomial() with NULL input readout->image.
+    {
+        psMemId id = psMemGetId();
+        pmReadout *myReadout = generateSimpleReadout(NULL);
+        psPolynomial1D *myPoly = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 2);
+        myPoly->coeff[1] = 1.0;
+        psImage *tmpImage = myReadout->image;
+        myReadout->image = NULL;
+        pmReadout *rc = pmNonLinearityPolynomial(myReadout, myPoly);
+        ok(!rc, "pmNonLinearityPolynomial() returned NULL with a NULL input pmReadout");
+        myReadout->image = tmpImage;
+        psFree(myReadout);
+        psFree(myPoly);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+    
+     
+    // Call pmNonLinearityPolynomial() with NULL polynomial.
+    {
+        psMemId id = psMemGetId();
+        pmReadout *myReadout = generateSimpleReadout(NULL);
+        pmReadout *rc = pmNonLinearityPolynomial(myReadout, NULL);
+        ok(!rc, "pmNonLinearityPolynomial() returned NULL with a NULL input psPolynomial");
+        psFree(myReadout);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+    
+     
+    // Call pmNonLinearityPolynomial() with acceptable input data
+    { 
+        psMemId id = psMemGetId();
+        pmReadout *myReadout = generateSimpleReadout(NULL);
+        psPolynomial1D *myPoly = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 2);
+        myPoly->coeff[1] = 1.0;
+        myReadout = pmNonLinearityPolynomial(myReadout, myPoly);
+        bool errorFlag = false;
+        for (int i=0;i<TEST_NUM_ROWS;i++) {
+            for (int j=0;j<TEST_NUM_COLS;j++) {
+                float expect = psPolynomial1DEval(myPoly, (float) (i + j));
+                float actual = myReadout->image->data.F32[i][j];
+                if (FLT_EPSILON < fabs(expect - actual)) {
+                    if (VERBOSE) diag("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                    errorFlag = true;
+                }
+            }
+        }
+        ok(!errorFlag, "pmNonLinearityPolynomial() set the image data correctly");
+    
+        psFree(myReadout);
+        psFree(myPoly);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmNonLinearityLookup() tests
+    // Call pmNonLinearityLookup() with NULL input pmReadout.
+    {
+        psMemId id = psMemGetId();
+        psVector *inVec = psVectorAlloc(PS_MAX(TEST_NUM_COLS, TEST_NUM_ROWS)*3, PS_TYPE_F32);
+        psVector *outVec = psVectorAlloc(PS_MAX(TEST_NUM_COLS, TEST_NUM_ROWS)*3, PS_TYPE_F32);
+        for (psS32 i=0;i<PS_MAX(TEST_NUM_COLS, TEST_NUM_ROWS)*3;i++) {
+            inVec->data.F32[i] = (float) i;
+            outVec->data.F32[i] = (float) (2 * i);
+        }
+        pmReadout *rc = pmNonLinearityLookup(NULL, inVec, outVec);
+        ok(!rc, "pmNonLinearityLookup() returned NULL with NULL input pmReadout");
+        psFree(inVec);
+        psFree(outVec);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmNonLinearityLookup() with NULL input pmReadout->image
+    {
+        psMemId id = psMemGetId();
+        pmReadout *myReadout = generateSimpleReadout(NULL);
+        psImage *tmpImage = myReadout->image;
+        myReadout->image = NULL;
+        psVector *inVec = psVectorAlloc(PS_MAX(TEST_NUM_COLS, TEST_NUM_ROWS)*3, PS_TYPE_F32);
+        psVector *outVec = psVectorAlloc(PS_MAX(TEST_NUM_COLS, TEST_NUM_ROWS)*3, PS_TYPE_F32);
+        for (psS32 i=0;i<PS_MAX(TEST_NUM_COLS, TEST_NUM_ROWS)*3;i++) {
+            inVec->data.F32[i] = (float) i;
+            outVec->data.F32[i] = (float) (2 * i);
+        }
+        pmReadout *rc = pmNonLinearityLookup(myReadout, inVec, outVec);
+        ok(!rc, "pmNonLinearityLookup() returned NULL with NULL input pmReadout->image");
+        myReadout->image = tmpImage;
+        psFree(myReadout);
+        psFree(inVec);
+        psFree(outVec);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmNonLinearityLookup() with NULL input inFlux
+    {
+        psMemId id = psMemGetId();
+        pmReadout *myReadout = generateSimpleReadout(NULL);
+        psVector *inVec = psVectorAlloc(PS_MAX(TEST_NUM_COLS, TEST_NUM_ROWS)*3, PS_TYPE_F32);
+        psVector *outVec = psVectorAlloc(PS_MAX(TEST_NUM_COLS, TEST_NUM_ROWS)*3, PS_TYPE_F32);
+        for (psS32 i=0;i<PS_MAX(TEST_NUM_COLS, TEST_NUM_ROWS)*3;i++) {
+            inVec->data.F32[i] = (float) i;
+            outVec->data.F32[i] = (float) (2 * i);
+        }
+        pmReadout *rc = pmNonLinearityLookup(myReadout, NULL, outVec);
+        ok(!rc, "pmNonLinearityLookup() returned NULL with input inFlux");
+        psFree(myReadout);
+        psFree(inVec);
+        psFree(outVec);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // Call pmNonLinearityLookup() with NULL input outFlux
+    {
+        psMemId id = psMemGetId();
+        pmReadout *myReadout = generateSimpleReadout(NULL);
+        psVector *inVec = psVectorAlloc(PS_MAX(TEST_NUM_COLS, TEST_NUM_ROWS)*3, PS_TYPE_F32);
+        psVector *outVec = psVectorAlloc(PS_MAX(TEST_NUM_COLS, TEST_NUM_ROWS)*3, PS_TYPE_F32);
+        for (psS32 i=0;i<PS_MAX(TEST_NUM_COLS, TEST_NUM_ROWS)*3;i++) {
+            inVec->data.F32[i] = (float) i;
+            outVec->data.F32[i] = (float) (2 * i);
+        }
+        pmReadout *rc = pmNonLinearityLookup(myReadout, inVec, NULL);
+        ok(!rc, "pmNonLinearityLookup() returned NULL with input outFlux");
+        psFree(myReadout);
+        psFree(inVec);
+        psFree(outVec);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmNonLinearityLookup() with acceptable input data
+    {
+        psMemId id = psMemGetId();
+        pmReadout *myReadout = generateSimpleReadout(NULL);
+        psVector *inVec = psVectorAlloc(PS_MAX(TEST_NUM_COLS, TEST_NUM_ROWS)*3, PS_TYPE_F32);
+        psVector *outVec = psVectorAlloc(PS_MAX(TEST_NUM_COLS, TEST_NUM_ROWS)*3, PS_TYPE_F32);
+        for (psS32 i=0;i<PS_MAX(TEST_NUM_COLS, TEST_NUM_ROWS)*3;i++) {
+            inVec->data.F32[i] = (float) i;
+            outVec->data.F32[i] = (float) (2 * i);
+        }
+        {
+            myReadout = pmNonLinearityLookup(myReadout, inVec, outVec);
+            ok(myReadout, "pmNonLinearityLookup() returned non-NULL with acceptable input data");
+            bool errorFlag = false;
+            if (myReadout) {
+                for (int i=0;i<TEST_NUM_ROWS;i++) {
+                    for (int j=0;j<TEST_NUM_COLS;j++) {
+                        float expect = (float) (2 * (i + j));
+                        float actual = myReadout->image->data.F32[i][j];
+                        if (FLT_EPSILON < fabs(expect - actual)) {
+                            if (VERBOSE) diag("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                            errorFlag = true;
+                        }
+                    }
+                }
+                ok(!errorFlag, "pmNonLinearityLookup() set the image data correctly");
+	    }
+	}
+        psFree(myReadout);
+        psFree(inVec);
+        psFree(outVec);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tap_pmShutterCorrection.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tap_pmShutterCorrection.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tap_pmShutterCorrection.c	(revision 22157)
@@ -0,0 +1,430 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "tap.h"
+
+int main (void)
+{
+    plan_tests(46);
+
+    diag("pmShutterCorrection tests");
+
+    // test allocation, free of pmShutterCorrPars
+    diag("pmShutterCorrParsAlloc tests");
+    {
+        psMemId id = psMemGetId();
+        pmShutterCorrection *pars = pmShutterCorrectionAlloc ();
+
+        ok(pars != NULL, "pmShutterCorrPars successfully allocated");
+        skip_start(pars == NULL, 0, "Skipping tests because pmShutterCorrParsAlloc() failed");
+        skip_end();
+
+        psFree(pars);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // test parameter guess (linearly spaced exptimes, TK/TO < 1)
+    diag("pmShutterCorrectionGuess tests : coarse linear-spaced exptimes");
+    {
+        psMemId id = psMemGetId();
+
+        int NPTS = 10;
+        float AK = 5.0;
+        float TK = 0.1;
+        float TO = 0.2;
+        psVector *exptime = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *counts  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        exptime->n = counts->n = NPTS;
+
+        for (int i = 0; i < exptime->n; i++) {
+            exptime->data.F32[i] = i*0.25;
+            counts->data.F32[i] = AK*(exptime->data.F32[i] + TK) / (exptime->data.F32[i] + TO);
+        }
+
+        pmShutterCorrection *pars = pmShutterCorrectionGuess (exptime, counts);
+
+        ok(pars != NULL, "pmShutterCorrPars successfully allocated");
+        skip_start(pars == NULL, 0, "Skipping tests because pmShutterCorrParsAlloc() failed");
+
+        // with coarse linearly-spaced times large compared to TO and TK,
+        // we can't expect very accurate guesses.  the exptime guess is fairly good because
+        // the largest exptime is much longer than TK or TO
+        ok(fabs(pars->scale  - AK) < 0.5, "scale guess is close enough (got %f vs %f)",  pars->scale, AK);
+        ok(fabs(pars->offset - TK) < 0.5, "offset guess is close enough (got %f vs %f)", pars->offset, TK);
+        ok(fabs(pars->offref - TO) < 0.5, "offref guess is close enough (got %f vs %f)", pars->offref, TO);
+        skip_end();
+
+        psFree(pars);
+        psFree(exptime);
+        psFree(counts);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // test parameter guess (linearly spaced exptimes, TK/TO < 1)
+    diag("pmShutterCorrectionGuess tests : fine linear-spaced exptimes");
+    {
+        psMemId id = psMemGetId();
+
+        int NPTS = 20;
+        float AK = 5.0;
+        float TK = 0.1;
+        float TO = 0.2;
+        psVector *exptime = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *counts  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        exptime->n = counts->n = NPTS;
+
+        for (int i = 0; i < exptime->n; i++) {
+            exptime->data.F32[i] = i*0.1;
+            counts->data.F32[i] = AK*(exptime->data.F32[i] + TK) / (exptime->data.F32[i] + TO);
+        }
+
+        pmShutterCorrection *pars = pmShutterCorrectionGuess (exptime, counts);
+
+        ok(pars != NULL, "pmShutterCorrection successfully allocated");
+        skip_start(pars == NULL, 0, "Skipping tests because pmShutterCorrectionAlloc() failed");
+
+        // with fine linearly-spaced times large compared to TO and TK,
+        // we get a good guess to TK and TO, but since the largest exptime is not
+        // many times larger than TO, we don't do very well with the AK
+        ok(fabs(pars->scale  - AK) < 0.5, "scale guess is close enough (got %f vs %f)",  pars->scale, AK);
+        ok(fabs(pars->offset - TK) < 0.05, "offset guess is close enough (got %f vs %f)", pars->offset, TK);
+        ok(fabs(pars->offref - TO) < 0.05, "offref guess is close enough (got %f vs %f)", pars->offref, TO);
+        skip_end();
+
+        psFree(pars);
+        psFree(exptime);
+        psFree(counts);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // test parameter guess (log spaced exptimes, TK/TO < 1)
+    diag("pmShutterCorrectionGuess tests : log-spaced exptimes");
+    {
+        psMemId id = psMemGetId();
+
+        int NPTS = 40;
+        float AK = 5.0;
+        float TK = 0.1;
+        float TO = 0.2;
+        psVector *exptime = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *counts  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        exptime->n = counts->n = NPTS;
+
+        for (int i = 0; i < exptime->n; i++) {
+            exptime->data.F32[i] = pow(10.0, -2 + i*0.1);
+            counts->data.F32[i] = AK*(exptime->data.F32[i] + TK) / (exptime->data.F32[i] + TO);
+        }
+
+        pmShutterCorrection *pars = pmShutterCorrectionGuess (exptime, counts);
+
+        // with fine log-spaced times well-sampling TO and TK,
+        // we can expect accurate guesses
+        ok(pars != NULL, "pmShutterCorrectionsuccessfully allocated");
+        skip_start(pars == NULL, 0, "Skipping tests because pmShutterCorrectionAlloc() failed");
+        ok(fabs(pars->scale  - AK) < 0.01, "scale guess is close enough (got %f vs %f)",  pars->scale, AK);
+        ok(fabs(pars->offset - TK) < 0.01, "offset guess is close enough (got %f vs %f)", pars->offset, TK);
+        ok(fabs(pars->offref - TO) < 0.01, "offref guess is close enough (got %f vs %f)", pars->offref, TO);
+        skip_end();
+
+        psFree(pars);
+        psFree(exptime);
+        psFree(counts);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // test parameter guess (linearly spaced exptimes, TK/TO > 1)
+    diag("pmShutterCorrectionGuess tests : coarse linear-spaced exptimes, TK/TO > 1");
+    {
+        psMemId id = psMemGetId();
+
+        int NPTS = 10;
+        float AK = 5.0;
+        float TK = 0.2;
+        float TO = 0.1;
+        psVector *exptime = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *counts  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        exptime->n = counts->n = NPTS;
+
+        for (int i = 0; i < exptime->n; i++) {
+            exptime->data.F32[i] = i*0.25;
+            counts->data.F32[i] = AK*(exptime->data.F32[i] + TK) / (exptime->data.F32[i] + TO);
+        }
+
+        pmShutterCorrection *pars = pmShutterCorrectionGuess (exptime, counts);
+
+        ok(pars != NULL, "pmShutterCorrection successfully allocated");
+        skip_start(pars == NULL, 0, "Skipping tests because pmShutterCorrectionAlloc() failed");
+
+        // with coarse linearly-spaced times large compared to TO and TK,
+        // we can't expect very accurate guesses.  the exptime guess is fairly good because
+        // the largest exptime is much longer than TK or TO
+        ok(fabs(pars->scale  - AK) < 0.5, "scale guess is close enough (got %f vs %f)",  pars->scale, AK);
+        ok(fabs(pars->offset - TK) < 0.5, "offset guess is close enough (got %f vs %f)", pars->offset, TK);
+        ok(fabs(pars->offref - TO) < 0.5, "offref guess is close enough (got %f vs %f)", pars->offref, TO);
+        skip_end();
+
+        psFree(pars);
+        psFree(exptime);
+        psFree(counts);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // test parameter guess (linearly spaced exptimes, TK/TO > 1)
+    diag("pmShutterCorrectionGuess tests : fine linear-spaced exptimes, TK/TO > 1");
+    {
+        psMemId id = psMemGetId();
+
+        int NPTS = 20;
+        float AK = 5.0;
+        float TK = 0.2;
+        float TO = 0.1;
+        psVector *exptime = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *counts  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        exptime->n = counts->n = NPTS;
+
+        for (int i = 0; i < exptime->n; i++) {
+            exptime->data.F32[i] = i*0.1;
+            counts->data.F32[i] = AK*(exptime->data.F32[i] + TK) / (exptime->data.F32[i] + TO);
+        }
+
+        pmShutterCorrection *pars = pmShutterCorrectionGuess (exptime, counts);
+
+        ok(pars != NULL, "pmShutterCorrection successfully allocated");
+        skip_start(pars == NULL, 0, "Skipping tests because pmShutterCorrectionsAlloc() failed");
+
+        // with fine linearly-spaced times large compared to TO and TK,
+        // we get a good guess to TK and TO, but since the largest exptime is not
+        // many times larger than TO, we don't do very well with the AK
+        ok(fabs(pars->scale  - AK) < 0.5, "scale guess is close enough (got %f vs %f)",  pars->scale, AK);
+        ok(fabs(pars->offset - TK) < 0.05, "offset guess is close enough (got %f vs %f)", pars->offset, TK);
+        ok(fabs(pars->offref - TO) < 0.05, "offref guess is close enough (got %f vs %f)", pars->offref, TO);
+        skip_end();
+
+        psFree(pars);
+        psFree(exptime);
+        psFree(counts);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // test parameter guess (log spaced exptimes, TK/TO > 1)
+    diag("pmShutterCorrectionGuess tests : log-spaced exptimes, TK/TO > 1");
+    {
+        psMemId id = psMemGetId();
+
+        int NPTS = 40;
+        float AK = 5.0;
+        float TK = 0.2;
+        float TO = 0.1;
+        psVector *exptime = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *counts  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        exptime->n = counts->n = NPTS;
+
+        for (int i = 0; i < exptime->n; i++) {
+            exptime->data.F32[i] = pow(10.0, -2 + i*0.1);
+            counts->data.F32[i] = AK*(exptime->data.F32[i] + TK) / (exptime->data.F32[i] + TO);
+        }
+
+        pmShutterCorrection *pars = pmShutterCorrectionGuess (exptime, counts);
+
+        // with fine log-spaced times well-sampling TO and TK,
+        // we can expect accurate guesses
+        ok(pars != NULL, "pmShutterCorrection successfully allocated");
+        skip_start(pars == NULL, 0, "Skipping tests because pmShutterCorrectionAlloc() failed");
+        ok(fabs(pars->scale  - AK) < 0.01, "scale guess is close enough (got %f vs %f)",  pars->scale, AK);
+        ok(fabs(pars->offset - TK) < 0.01, "offset guess is close enough (got %f vs %f)", pars->offset, TK);
+        ok(fabs(pars->offref - TO) < 0.01, "offref guess is close enough (got %f vs %f)", pars->offref, TO);
+        skip_end();
+
+        psFree(pars);
+        psFree(exptime);
+        psFree(counts);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // test non-linear fitting
+    diag("pmShutterCorrectionFullFit tests : linear-spaced exptimes");
+    {
+        psMemId id = psMemGetId();
+
+        int NPTS = 20;
+        float FL = 10000.0;
+        float AK = 5.0;
+        float TK = 0.2;
+        float TO = 0.1;
+        psVector *exptime = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *counts  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *cntErr  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        exptime->n = counts->n = cntErr->n = NPTS;
+
+        for (int i = 0; i < exptime->n; i++) {
+            exptime->data.F32[i] = i*0.1;
+            counts->data.F32[i] = AK*(exptime->data.F32[i] + TK) / (exptime->data.F32[i] + TO);
+            cntErr->data.F32[i] = AK*sqrt(FL*(exptime->data.F32[i] + TK)) / (exptime->data.F32[i] + TO);
+        }
+
+        pmShutterCorrection *guess = pmShutterCorrectionGuess (exptime, counts);
+        skip_start(guess == NULL, 0, "Skipping tests because pmShutterCorrectionGuess() failed");
+        pmShutterCorrection *pars = pmShutterCorrectionFullFit (exptime, counts, cntErr, guess);
+
+        // with fine log-spaced times well-sampling TO and TK,
+        // we can expect accurate guesses
+        ok(pars != NULL, "pmShutterCorrection successfully allocated by FullFit");
+        skip_start(pars == NULL, 0, "Skipping tests because pmShutterCorrectionsAlloc() failed");
+        ok(fabs(pars->scale  - AK) < 0.01, "scale fit is close enough (got %f vs %f)",  pars->scale, AK);
+        ok(fabs(pars->offset - TK) < 0.01, "offset fit is close enough (got %f vs %f)", pars->offset, TK);
+        ok(fabs(pars->offref - TO) < 0.01, "offref fit is close enough (got %f vs %f)", pars->offref, TO);
+        skip_end();
+
+        psFree(pars);
+        skip_end();
+
+        psFree(guess);
+        psFree(exptime);
+        psFree(counts);
+        psFree(cntErr);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // test non-linear fitting
+    diag("pmShutterCorrectionFullFit tests : log-spaced exptimes");
+    {
+        psMemId id = psMemGetId();
+
+        int NPTS = 40;
+        float FL = 10000.0;
+        float AK = 1.0;
+        float TK = 0.2;
+        float TO = 0.1;
+        psVector *exptime = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *counts  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *cntErr  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        exptime->n = counts->n = cntErr->n = NPTS;
+
+        for (int i = 0; i < exptime->n; i++) {
+            exptime->data.F32[i] = pow(10.0, -2 + i*0.1);
+            counts->data.F32[i] = AK*(exptime->data.F32[i] + TK) / (exptime->data.F32[i] + TO);
+            cntErr->data.F32[i] = AK*sqrt(FL*(exptime->data.F32[i] + TK)) / (exptime->data.F32[i] + TO);
+        }
+
+        pmShutterCorrection*guess = pmShutterCorrectionGuess (exptime, counts);
+        skip_start(guess == NULL, 0, "Skipping tests because pmShutterCorrectionGuess() failed");
+        pmShutterCorrection *pars = pmShutterCorrectionFullFit (exptime, counts, cntErr, guess);
+
+        // with fine log-spaced times well-sampling TO and TK,
+        // we can expect accurate guesses
+        ok(pars != NULL, "pmShutterCorrection successfully allocated by FullFit");
+        skip_start(pars == NULL, 0, "Skipping tests because pmShutterCorrectionAlloc() failed");
+        ok(fabs(pars->scale  - AK) < 0.01, "scale fit is close enough (got %f vs %f)",  pars->scale, AK);
+        ok(fabs(pars->offset - TK) < 0.01, "offset fit is close enough (got %f vs %f)", pars->offset, TK);
+        ok(fabs(pars->offref - TO) < 0.01, "offref fit is close enough (got %f vs %f)", pars->offref, TO);
+        skip_end();
+
+        psFree(pars);
+        skip_end();
+
+        psFree(guess);
+        psFree(exptime);
+        psFree(counts);
+        psFree(cntErr);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // XXX should add tests with the input counts scattered with GaussDev...
+
+    // test linear fitting
+    diag("pmShutterCorrectionLinFit tests : linear-spaced exptimes");
+    {
+        psMemId id = psMemGetId();
+
+        int NPTS = 20;
+        float FL = 10000.0;
+        float AK = 5.0;
+        float TK = 0.2;
+        float TO = 0.1;
+        psVector *exptime = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *counts  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *cntErr  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        exptime->n = counts->n = cntErr->n = NPTS;
+
+        for (int i = 0; i < exptime->n; i++) {
+            exptime->data.F32[i] = i*0.1;
+            counts->data.F32[i] = AK*(exptime->data.F32[i] + TK) / (exptime->data.F32[i] + TO);
+            cntErr->data.F32[i] = AK*sqrt(FL*(exptime->data.F32[i] + TK)) / (exptime->data.F32[i] + TO);
+        }
+
+        pmShutterCorrection*guess = pmShutterCorrectionGuess (exptime, counts);
+        skip_start(guess == NULL, 0, "Skipping tests because pmShutterCorrectionGuess() failed");
+        pmShutterCorrection *full = pmShutterCorrectionFullFit (exptime, counts, cntErr, guess);
+        pmShutterCorrection *pars = pmShutterCorrectionLinFit (exptime, counts, cntErr, NULL, full->offref, 1, 5, 0);
+
+        // with fine log-spaced times well-sampling TO and TK,
+        // we can expect accurate guesses
+        ok(pars != NULL, "pmShutterCorrection successfully allocated by FullFit");
+        skip_start(pars == NULL, 0, "Skipping tests because pmShutterCorrectionAlloc() failed");
+        ok(fabs(pars->scale  - AK) < 0.01, "scale fit is close enough (got %f vs %f)",  pars->scale, AK);
+        ok(fabs(pars->offset - TK) < 0.01, "offset fit is close enough (got %f vs %f)", pars->offset, TK);
+        ok(fabs(pars->offref - TO) < 0.01, "offref fit is close enough (got %f vs %f)", pars->offref, TO);
+        skip_end();
+
+        psFree(pars);
+        psFree(full);
+        skip_end();
+
+        psFree(guess);
+        psFree(exptime);
+        psFree(counts);
+        psFree(cntErr);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // test linear fitting
+    diag("pmShutterCorrectionLinFit tests : log-spaced exptimes");
+    {
+        psMemId id = psMemGetId();
+
+        int NPTS = 40;
+        float FL = 10000.0;
+        float AK = 1.0;
+        float TK = 0.2;
+        float TO = 0.1;
+        psVector *exptime = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *counts  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *cntErr  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        exptime->n = counts->n = cntErr->n = NPTS;
+
+        for (int i = 0; i < exptime->n; i++) {
+            exptime->data.F32[i] = pow(10.0, -2 + i*0.1);
+            counts->data.F32[i] = AK*(exptime->data.F32[i] + TK) / (exptime->data.F32[i] + TO);
+            cntErr->data.F32[i] = AK*sqrt(FL*(exptime->data.F32[i] + TK)) / (exptime->data.F32[i] + TO);
+        }
+
+        pmShutterCorrection *guess = pmShutterCorrectionGuess (exptime, counts);
+        skip_start(guess == NULL, 0, "Skipping tests because pmShutterCorrectionGuess() failed");
+        pmShutterCorrection *full = pmShutterCorrectionFullFit (exptime, counts, cntErr, guess);
+        pmShutterCorrection *pars = pmShutterCorrectionLinFit (exptime, counts, cntErr, NULL, full->offref, 1, 5, 0);
+
+        // with fine log-spaced times well-sampling TO and TK,
+        // we can expect accurate guesses
+        ok(pars != NULL, "pmShutterCorrection successfully allocated by FullFit");
+        skip_start(pars == NULL, 0, "Skipping tests because pmShutterCorrectionAlloc() failed");
+        ok(fabs(pars->scale  - AK) < 0.01, "scale fit is close enough (got %f vs %f)",  pars->scale, AK);
+        ok(fabs(pars->offset - TK) < 0.01, "offset fit is close enough (got %f vs %f)", pars->offset, TK);
+        ok(fabs(pars->offref - TO) < 0.01, "offref fit is close enough (got %f vs %f)", pars->offref, TO);
+        skip_end();
+
+        psFree(pars);
+        psFree(full);
+        skip_end();
+
+        psFree(guess);
+        psFree(exptime);
+        psFree(counts);
+        psFree(cntErr);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    return exit_status();
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tst_pmFlatField.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tst_pmFlatField.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tst_pmFlatField.c	(revision 22157)
@@ -0,0 +1,290 @@
+/** @file tst_pmFlatField.c
+ *
+ *  @brief Contains the tests for pmFlatField.c:
+ *
+ *    Test A - Divide input image by flat image
+ *    Test B - Mask flat image data
+ *    Test C - Mask flat image data starting with non-null mask
+ *    Test E - Attempt to use null input image
+ *    Test F - Attempt tp use null flat image
+ *    Test G - Attempt to use input image bigger than flat image
+ *    Test H - Attempt to use input image mask bigger than flat image
+ *    Test I - Attempt to use offset greater than input image
+ *    Test J - Attempt to use complex input image
+ *    Test K - Attempt to use complex flat image
+ *    Test L - Attempt to use non-equal input and flat image types
+ *    Test M - Attempt to use non-mask type mask image
+ *
+ * XXX: Added a mask argument to pmFlatField().  Must add tests.  For now, all
+ * masks are NULL.
+ *
+ *  @author Ross Harman, MHPCC
+ *
+ *  XXX: I added the CELL.TRIMSEC region code but there are not tests for it.
+ *
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2005-11-15 20:09:03 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+
+#include "psTest.h"
+#include "pslib.h"
+#include "pmFlatField.h"
+
+
+#define PRINT_MATRIX(IMAGE,TYPE,STRING)                                                                      \
+printf(STRING);                                                                                              \
+printf("\n");                                                                                                \
+for(int i=IMAGE->numRows-1; i>-1; i--) {                                                                     \
+    for(int j=0; j<IMAGE->numCols; j++) {                                                                    \
+        if(PS_IS_PSELEMTYPE_COMPLEX(IMAGE->type.type)) {                                                     \
+            printf("%f+%fi ", creal(IMAGE->data.TYPE[i][j]), cimag(IMAGE->data.TYPE[i][j]));                 \
+        } else if(PS_IS_PSELEMTYPE_INT(IMAGE->type.type)) {                                                  \
+            printf("%d ", (int)IMAGE->data.TYPE[i][j]);                                                      \
+        } else {                                                                                             \
+            printf("%f ", (double)IMAGE->data.TYPE[i][j]);                                                   \
+        }                                                                                                    \
+    }                                                                                                        \
+    printf("\n");                                                                                            \
+}                                                                                                            \
+printf("\n");
+
+
+#define CREATE_AND_SET_IMAGE(NAME,TYPE,VALUE,NROWS,NCOLS)                                                    \
+psImage *NAME = (psImage*)psImageAlloc(NCOLS,NROWS,PS_TYPE_##TYPE);                                          \
+for(int i=0; i<NAME->numRows; i++) {                                                                         \
+    for(int j=0; j<NAME->numCols; j++) {                                                                     \
+        NAME->data.TYPE[i][j] = VALUE;                                                                       \
+    }                                                                                                        \
+}
+
+
+static int testFlatField(void);
+
+
+testDescription tests[] = {
+                              {testFlatField, 753, "pmFlatField", 0, false},
+                              {NULL}
+                          };
+
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    return !runTestSuite(stderr, "Test Point Driver", tests, argc, argv);
+}
+
+
+int testFlatField( void )
+{
+    // Test A - Divide input image by flat image
+    printPositiveTestHeader(stdout, "pmFlatField", "Test A - Divide input image by flat image");
+    CREATE_AND_SET_IMAGE(inImage,F64,6.0,3,3)
+    pmReadout *inReadout = pmReadoutAlloc(NULL);
+    inReadout->image = inImage;
+    inReadout->row0 = 0;
+    inReadout->col0 = 0;
+    CREATE_AND_SET_IMAGE(inMask, U8, 0, 3,3);
+    inReadout->mask = inMask;
+    PRINT_MATRIX((inReadout->mask),U8,"Input mask:");
+    PRINT_MATRIX(inImage,F64,"Input image:");
+
+    CREATE_AND_SET_IMAGE(flatImage1,F64,2.0,3,3)
+    pmReadout *flatReadout = pmReadoutAlloc(NULL);
+    flatReadout->row0 = 0;
+    flatReadout->col0 = 0;
+    flatReadout->image = flatImage1;
+    PRINT_MATRIX(flatImage1,F64,"Flat image:");
+
+    if ( !pmFlatField(inReadout, flatReadout) ) {
+        psError(PS_ERR_UNKNOWN,true,"Test A - Returned false should be true");
+        return 1;
+    }
+    PRINT_MATRIX(inImage,F64, "Resulting image:");
+    printFooter(stdout, "pmFlatField", "Test A - Divide input image by flat image", true);
+    printf("\n\n\n");
+
+
+    // Test B - Mask flat image data
+    printPositiveTestHeader(stdout, "pmFlatField", "Test B - Mask flat image data");
+    PRINT_MATRIX(inImage, F64, "Input image:");
+    CREATE_AND_SET_IMAGE(flatImage2,F64,0.0,3,3)
+    PRINT_MATRIX(flatImage2, F64, "Flat image:");
+    flatReadout->image = flatImage2;
+    if ( !pmFlatField(inReadout, flatReadout) ) {
+        psError(PS_ERR_UNKNOWN,true,"Test B - Returned false should be true");
+        return 2;
+    }
+    PRINT_MATRIX(inReadout->mask, PS_TYPE_MASK_DATA, "Resulting mask:");
+    PRINT_MATRIX(inImage,F64,"Resulting image:");
+    printFooter(stdout, "pmFlatField", "Test B - Mask flat image data", true);
+    printf("\n\n\n");
+
+
+    // Test C - Mask flat image data starting with non-null mask
+    printPositiveTestHeader(stdout, "pmFlatField", "Test C - Mask flat image data starting with non-null mask");
+    PRINT_MATRIX(inImage, F64, "Input image:");
+    flatImage2->data.F64[0][0] = 3.0;
+    flatImage2->data.F64[0][1] = -3.0;
+    PRINT_MATRIX(flatImage2, F64, "Flat image:");
+    CREATE_AND_SET_IMAGE(mask1,U8,0,3,3);
+    psFree(inReadout->mask);
+    inReadout->mask = mask1;
+    if ( !pmFlatField(inReadout, flatReadout) ) {
+        psError(PS_ERR_UNKNOWN,true,"Test C - Returned false should be true");
+        return 3;
+    }
+    PRINT_MATRIX(flatImage2, F64, "Flat image out:");
+    PRINT_MATRIX(inReadout->mask, PS_TYPE_MASK_DATA,"Resulting mask:");
+    PRINT_MATRIX(inImage,F64,"Resulting image:");
+    printFooter(stdout, "pmFlatField", "Test C - Mask flat image data starting with non-null mask", true);
+    printf("\n\n\n");
+
+
+    // Test D - Attempt to use null flat readout
+    printNegativeTestHeader(stdout,"pmFlatField", "Test D - Attempt to use null flat readout",
+                            "Null not allowed for flat readout", 0);
+    if( pmFlatField(inReadout, NULL) ) {
+        psError(PS_ERR_UNKNOWN,true,"Test D - Returned true should be false");
+        return 4;
+    }
+    printFooter(stdout, "pmFlatField", "Test D - Attempt to use null flat readout", true);
+    printf("\n\n\n");
+
+
+    // Test E - Attempt to use null input image
+    printNegativeTestHeader(stdout,"pmFlatField", "Test E - Attempt to use null input image",
+                            "Null not allowed for input image", 0);
+    psImage *temp = inReadout->image;
+    inReadout->image = NULL;
+    if ( pmFlatField(inReadout, flatReadout) ) {
+        psError(PS_ERR_UNKNOWN,true,"Test E - Returned true should be false" );
+        return 5;
+    }
+    inReadout->image = temp    ;
+    printFooter(stdout, "pmFlatField", "Test E - Attempt to use null input image", true);
+    printf("\n\n\n");
+
+
+    // Test F - Attempt tp use null flat image
+    printNegativeTestHeader(stdout,"pmFlatField", "Test F - Attempt tp use null flat image",
+                            "Null not allowed for flat image", 0);
+    temp = flatReadout->image;
+    flatReadout->image = NULL;
+    if ( pmFlatField(inReadout, flatReadout) ) {
+        psError(PS_ERR_UNKNOWN,true,"Test F - Returned true should be false" );
+        return 6;
+    }
+    flatReadout->image = temp;
+    printFooter(stdout, "pmFlatField", "Test F - Attempt tp use null flat image", true);
+    printf("\n\n\n");
+
+
+    // Test G - Attempt to use input image bigger than flat image
+    printNegativeTestHeader(stdout,"pmFlatField", "Test G - Attempt to use input image bigger than flat image",
+                            "Input image size exceeds that of flat image", 0);
+    CREATE_AND_SET_IMAGE(smallFlat,F64,0.0,2,2);
+    temp = flatReadout->image;
+    flatReadout->image = smallFlat;
+    if ( pmFlatField(inReadout, flatReadout) ) {
+        psError(PS_ERR_UNKNOWN,true,"Test G - Returned true should be false");
+        return 7;
+    }
+    flatReadout->image = temp;
+    printFooter(stdout, "pmFlatField", "Test G - Attempt to use input image bigger than flat image", true);
+    printf("\n\n\n");
+
+    // Test H - Attempt to use input image mask bigger than flat image
+    printNegativeTestHeader(stdout,"pmFlatField", "Test H - Attempt to use input image mask bigger than flat image",
+                            "Input image mask size exceeds that of flat image", 0);
+    CREATE_AND_SET_IMAGE(largeMask,F64,0.0,5,5);
+    inReadout->mask = largeMask;
+    if ( pmFlatField(inReadout, flatReadout) ) {
+        psError(PS_ERR_UNKNOWN,true,"Test H - Returned true should be false");
+        return 8;
+    }
+    printFooter(stdout, "pmFlatField", "Test H - Attempt to use input image mask bigger than flat image", true);
+    printf("\n\n\n");
+    inReadout->mask = mask1;
+
+    // Test I - Attempt to use offset greater than input image
+    printNegativeTestHeader(stdout,"pmFlatField", "Test I - Attempt to use offset greater than input image",
+                            "Total offset >= input image size", 0);
+    *(int*)&inReadout->col0 = 50;
+    *(int*)&inReadout->row0 = 50;
+    if ( pmFlatField(inReadout, flatReadout) ) {
+        psError(PS_ERR_UNKNOWN,true,"Test I - Returned true should be false");
+        return 9;
+    }
+    *(int*)&inReadout->col0 = 0;
+    *(int*)&inReadout->row0 = 0;
+    printFooter(stdout, "pmFlatField", "Test I - Attempt to use offset greater than input image", true);
+    printf("\n\n\n");
+
+
+    // Test J - Attempt to use complex input image
+    printNegativeTestHeader(stdout,"pmFlatField", "Test J - Attempt to use complex input image",
+                            "Complex types not allowed for input image", 0);
+    *(psElemType* ) & inReadout->image->type.type = PS_TYPE_C64;
+    if ( pmFlatField(inReadout, flatReadout) ) {
+        psError(PS_ERR_UNKNOWN,true,"Test J - Returned true should be false");
+        return 10;
+    }
+    *(psElemType* ) & inReadout->image->type.type = PS_TYPE_F64;
+    printFooter(stdout, "pmFlatField", "Test J - Attempt to use complex input image", true);
+    printf("\n\n\n");
+
+
+    // Test K - Attempt to use complex flat image
+    printNegativeTestHeader(stdout,"pmFlatField", "Test K - Attempt to use complex flat image",
+                            "Complex types not allowed for flat image", 0);
+    *(psElemType* ) & flatReadout->image->type.type = PS_TYPE_C64;
+    if ( pmFlatField(inReadout, flatReadout) ) {
+        psError(PS_ERR_UNKNOWN,true,"Test K - Returned ture should be false");
+        return 11;
+    }
+    *(psElemType* ) & flatReadout->image->type.type = PS_TYPE_F64;
+    printFooter(stdout, "pmFlatField", "Test K - Attempt to use complex flat image", true);
+    printf("\n\n\n");
+
+
+    // Test L - Attempt to use non-equal input and flat image types
+    printNegativeTestHeader(stdout,"pmFlatField", "Test L - Attempt to use non-equal input and flat image types",
+                            "Input and flat image types differ", 0);
+    *(psElemType* ) & flatReadout->image->type.type = PS_TYPE_F32;
+    if ( pmFlatField(inReadout, flatReadout) ) {
+        psError(PS_ERR_UNKNOWN,true,"Test L - Returned true should be false");
+        return 12;
+    }
+    *(psElemType* ) & flatReadout->image->type.type = PS_TYPE_F64;
+    printFooter(stdout, "pmFlatField", "Test L - Attempt to use non-equal input and flat image types", true);
+    printf("\n\n\n");
+
+
+    // Test M - Attempt to use non-mask type mask image
+    printNegativeTestHeader(stdout,"pmFlatField", "Test M - Attempt to use non-mask type mask image",
+                            "Mask must be PS_TYPE_MASK type", 0);
+    *(psElemType* ) & inReadout->mask->type.type = PS_TYPE_F32;
+    if ( pmFlatField(inReadout, flatReadout) ) {
+        psError(PS_ERR_UNKNOWN,true,"Test M - Returned true should be false");
+        return 13;
+    }
+    *(psElemType* ) & inReadout->mask->type.type = PS_TYPE_MASK;
+    printFooter(stdout, "pmFlatField", "Test M - Attempt to use non-mask type mask image", true);
+    printf("\n\n\n");
+
+
+    // Free memory
+    psFree(inReadout);
+    psFree(flatReadout);
+    //psFree(inImage);
+    psFree(flatImage1);
+    //psFree(flatImage1);
+    psFree(smallFlat);
+    psFree(largeMask);
+
+    return 0;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tst_pmMaskBadPixels.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tst_pmMaskBadPixels.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tst_pmMaskBadPixels.c	(revision 22157)
@@ -0,0 +1,381 @@
+/** @file tst_pmMaskBadPixels.c
+ *
+ *  @brief Contains the tests for pmMaskBadPixels:
+ *
+ *    Test A - Create mask based on maskVal argument
+ *    Test B - Create mask based on saturation argument
+ *    Test C - Create mask based on growVal and grow arguments
+ *    Test D - Auto Create mask based on maskVal argument
+ *    Test E - Attempt to use null mask
+ *    Test F - Attempt tp use null input image
+ *    Test G - Attempt to use input image bigger than mask
+ *    Test H - Attempt to use input image mask bigger than mask
+ *    Test I - Attempt to use offset greater than input image
+ *    Test J - Attempt to use complex input image
+ *    Test K - Attempt to use non-mask type mask image
+ *
+ *  @author Ross Harman, MHPCC
+ *
+ *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2005-11-15 20:09:03 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+
+#include "psTest.h"
+#include "pslib.h"
+#include "pmMaskBadPixels.h"
+
+
+#define PRINT_MATRIX(IMAGE,TYPE,STRING)                                                                      \
+printf(STRING);                                                                                              \
+printf("\n");                                                                                                \
+for(int i=(IMAGE)->numRows-1; i>-1; i--) {                                                                     \
+    for(int j=0; j<(IMAGE)->numCols; j++) {                                                                    \
+        if(PS_IS_PSELEMTYPE_COMPLEX((IMAGE)->type.type)) {                                                     \
+            printf("%f+%fi ", creal((IMAGE)->data.TYPE[i][j]), cimag((IMAGE)->data.TYPE[i][j]));                 \
+        } else if(PS_IS_PSELEMTYPE_INT((IMAGE)->type.type)) {                                                  \
+            printf("%d", (int)(IMAGE)->data.TYPE[i][j]);                                                       \
+        } else {                                                                                             \
+            printf("%f", (double)(IMAGE)->data.TYPE[i][j]);                                                    \
+        }                                                                                                    \
+    }                                                                                                        \
+    printf("\n");                                                                                            \
+}                                                                                                            \
+printf("\n");
+
+
+#define CREATE_AND_SET_IMAGE(NAME,TYPE,VALUE,NROWS,NCOLS)                                                    \
+(NAME) = (psImage*)psImageAlloc(NCOLS,NROWS,PS_TYPE_##TYPE);                                             \
+for(int i=0; i<(NAME)->numRows; i++) {                                                                         \
+    for(int j=0; j<(NAME)->numCols; j++) {                                                                     \
+        (NAME)->data.TYPE[i][j] = VALUE;                                                                       \
+    }                                                                                                        \
+}
+
+static int testMaskBadPixels1(void);
+static int testMaskBadPixels2(void);
+static int testMaskBadPixels3(void);
+static int testMaskBadPixels4(void);
+static int testMaskBadPixels5(void);
+static int testMaskBadPixels6(void);
+static int testMaskBadPixels7(void);
+static int testMaskBadPixels8(void);
+static int testMaskBadPixels9(void);
+static int testMaskBadPixels10(void);
+static int testMaskBadPixels11(void);
+
+
+testDescription tests[] = {
+                              {testMaskBadPixels1, 885, "pmMaskBadPixels - Create mask based on maskVal argument", 0, false},
+                              {testMaskBadPixels2, 885, "pmMaskBadPixels - Create mask based on saturation argument", 0, false},
+                              {testMaskBadPixels3, 885, "pmMaskBadPixels - Create mask based on growVal and grow arguments", 0, false},
+                              {testMaskBadPixels4, 885, "pmMaskBadPixels - Auto create mask based on maskVal argument", 0, false},
+                              {testMaskBadPixels5, 885, "pmMaskBadPixels - Attempt to use null mask", 0, false},
+                              {testMaskBadPixels6, 885, "pmMaskBadPixels - Attempt tp use null input image", 0, false},
+                              {testMaskBadPixels7, 885, "pmMaskBadPixels - Attempt to use input image bigger than mask", 0, false},
+                              {testMaskBadPixels8, 885, "pmMaskBadPixels - Attempt to use input image mask bigger than mask", 0, false},
+                              {testMaskBadPixels9, 885, "pmMaskBadPixels - Attempt to use offset greater than input image", 0, false},
+                              {testMaskBadPixels10, 885, "pmMaskBadPixels - Attempt to use complex input image", 0, false},
+                              {testMaskBadPixels11, 885, "pmMaskBadPixels - Attempt to use non-mask type mask image", 0, false        },
+                              {NULL}
+                          };
+
+/*
+    #define PS_TYPE_MASK PS_TYPE_U8
+    #define PS_TYPE_MASK_DATA U8
+    #define PS_TYPE_MASK_NAME "psU8"
+*/
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    return !runTestSuite(stderr, "Test Point Driver", tests, argc, argv);
+}
+
+
+#define NUM_ROWS 50
+#define NUM_COLS 50
+#define DEFAULT_IMAGE_VAL 0.0
+#define DEFAULT_MASK_VAL 0
+#define MASK_VAL 1
+#define SAT_VAL  100.0
+#define GROW_VAL 1
+#define GROW_RAD 10
+int testMaskBadPixels1( void )
+{
+    //
+    // Test A - Create mask based on maskVal argument
+    //
+
+    pmReadout *inReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(inReadout->image, F32, DEFAULT_IMAGE_VAL, NUM_ROWS, NUM_COLS);
+    inReadout->image->row0 = 0;
+    inReadout->image->col0 = 0;
+    inReadout->row0 = 0;
+    inReadout->col0 = 0;
+
+    pmReadout *maskReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(maskReadout->image, U8, DEFAULT_MASK_VAL, NUM_ROWS, NUM_COLS)
+    maskReadout->image->data.PS_TYPE_MASK_DATA[NUM_ROWS/2][NUM_COLS/2]=1;
+    maskReadout->image->row0 = 0;
+    maskReadout->image->col0 = 0;
+
+    PRINT_MATRIX(maskReadout->image, U8, "Data mask:");
+
+    pmMaskBadPixels(inReadout, maskReadout, MASK_VAL, SAT_VAL, GROW_VAL, GROW_RAD);
+    PRINT_MATRIX(inReadout->mask, PS_TYPE_MASK_DATA, "Resulting mask:");
+
+    psFree(inReadout);
+    psFree(maskReadout);
+
+    return 0;
+}
+
+int testMaskBadPixels2( void )
+{
+    //
+    // Test B - Create mask based on saturation argument
+    //
+
+    pmReadout *inReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(inReadout->image, F32, DEFAULT_IMAGE_VAL, NUM_ROWS, NUM_COLS);
+    inReadout->image->row0 = 0;
+    inReadout->image->col0 = 0;
+    inReadout->row0 = 0;
+    inReadout->col0 = 0;
+    inReadout->image->data.F32[NUM_ROWS/2][NUM_COLS/2] = 150.0;
+
+    pmReadout *maskReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(maskReadout->image, U8, DEFAULT_MASK_VAL, NUM_ROWS, NUM_COLS)
+
+    //PS_IMAGE_PRINT_F32(inReadout->image);
+    PRINT_MATRIX(maskReadout->image, U8, "Data mask:");
+    pmMaskBadPixels(inReadout, maskReadout, MASK_VAL, SAT_VAL, GROW_VAL, GROW_RAD);
+    PRINT_MATRIX(inReadout->mask, U8, "Resulting mask:");
+
+    psFree(inReadout);
+    psFree(maskReadout);
+
+    return 0;
+}
+
+int testMaskBadPixels3( void )
+{
+    //
+    // Test C - Create mask based on growVal and grow arguments
+    //
+
+    pmReadout *inReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(inReadout->image, F32, DEFAULT_IMAGE_VAL, NUM_ROWS, NUM_COLS);
+    inReadout->image->row0 = 0;
+    inReadout->image->col0 = 0;
+    inReadout->row0 = 0;
+    inReadout->col0 = 0;
+    inReadout->image->data.F32[NUM_ROWS/2][NUM_COLS/2]=GROW_VAL;
+    inReadout->image->data.F32[NUM_ROWS/4][NUM_COLS/4]=GROW_VAL;
+    inReadout->image->data.F32[NUM_ROWS/4][NUM_COLS-(NUM_COLS/4)]=GROW_VAL;
+
+    pmReadout *maskReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(maskReadout->image, U8, DEFAULT_MASK_VAL, NUM_ROWS, NUM_COLS);
+
+    PRINT_MATRIX(maskReadout->image, U8, "Data mask:");
+    //PS_IMAGE_PRINT_F32(inReadout->image);
+    pmMaskBadPixels(inReadout, maskReadout, MASK_VAL, SAT_VAL, GROW_VAL, GROW_RAD);
+    PRINT_MATRIX(inReadout->mask, U8, "Resulting mask:");
+
+    psFree(inReadout);
+    psFree(maskReadout);
+
+    return 0;
+}
+
+int testMaskBadPixels4( void )
+{
+    //
+    // Test D - Auto Create mask based on maskVal argument
+    //
+
+    pmReadout *inReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(inReadout->image, F32, DEFAULT_IMAGE_VAL, NUM_ROWS, NUM_COLS);
+    inReadout->image->row0 = 0;
+    inReadout->image->col0 = 0;
+    inReadout->row0 = 0;
+    inReadout->col0 = 0;
+
+    pmReadout *maskReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(maskReadout->image, U8, DEFAULT_MASK_VAL, NUM_ROWS, NUM_COLS)
+    maskReadout->image->data.PS_TYPE_MASK_DATA[NUM_ROWS/2][NUM_COLS/2]=1;
+
+    PRINT_MATRIX(maskReadout->image, U8, "Data mask:");
+    pmMaskBadPixels(inReadout, maskReadout, MASK_VAL, SAT_VAL, GROW_VAL, GROW_RAD);
+    PRINT_MATRIX(inReadout->mask, U8, "Resulting mask:");
+
+    psFree(inReadout);
+    psFree(maskReadout);
+
+    return 0;
+}
+
+int testMaskBadPixels5( void )
+{
+    //
+    // Test E - Attempt to use null mask
+    //
+
+    pmReadout *inReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(inReadout->image, F32, DEFAULT_IMAGE_VAL, NUM_ROWS, NUM_COLS);
+    inReadout->image->row0 = 0;
+    inReadout->image->col0 = 0;
+    inReadout->row0 = 0;
+    inReadout->col0 = 0;
+
+    pmMaskBadPixels(inReadout, NULL, MASK_VAL, SAT_VAL, GROW_VAL, GROW_RAD);
+    psFree(inReadout);
+
+    return 0;
+}
+
+int testMaskBadPixels6( void )
+{
+    //
+    // Test F - Attempt tp use null input image
+    //
+
+    pmReadout *inReadout = pmReadoutAlloc(NULL);
+    inReadout->row0 = 0;
+    inReadout->col0 = 0;
+
+    pmReadout *maskReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(maskReadout->image, U8, DEFAULT_MASK_VAL, NUM_ROWS, NUM_COLS)
+
+    pmMaskBadPixels(inReadout, maskReadout, MASK_VAL, SAT_VAL, GROW_VAL, GROW_RAD);
+
+    psFree(inReadout);
+    psFree(maskReadout);
+
+    return 0;
+}
+
+int testMaskBadPixels7( void )
+{
+    //
+    // Test G - Attempt to use input image bigger than mask
+    //
+
+    pmReadout *inReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(inReadout->image, F32, DEFAULT_IMAGE_VAL, NUM_ROWS+10, NUM_COLS+10);
+    inReadout->image->row0 = 0;
+    inReadout->image->col0 = 0;
+    inReadout->row0 = 0;
+    inReadout->col0 = 0;
+
+    pmReadout *maskReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(maskReadout->image, U8, DEFAULT_MASK_VAL, NUM_ROWS, NUM_COLS)
+
+    pmMaskBadPixels(inReadout, maskReadout, MASK_VAL, SAT_VAL, GROW_VAL, GROW_RAD);
+
+    psFree(inReadout);
+    psFree(maskReadout);
+
+    return 0;
+}
+
+int testMaskBadPixels8( void )
+{
+    //
+    // Test H - Attempt to use mask bigger than image
+    //
+
+    pmReadout *inReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(inReadout->image, F32, DEFAULT_IMAGE_VAL, NUM_ROWS, NUM_COLS);
+    inReadout->image->row0 = 0;
+    inReadout->image->col0 = 0;
+    inReadout->row0 = 0;
+    inReadout->col0 = 0;
+
+    pmReadout *maskReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(maskReadout->image, U8, DEFAULT_MASK_VAL, NUM_ROWS+10, NUM_COLS+10)
+
+    pmMaskBadPixels(inReadout, maskReadout, MASK_VAL, SAT_VAL, GROW_VAL, GROW_RAD);
+
+    psFree(inReadout);
+    psFree(maskReadout);
+
+    return 0;
+}
+
+int testMaskBadPixels9( void )
+{
+    //
+    // Test I - Attempt to use offset greater than input image
+    //
+
+    pmReadout *inReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(inReadout->image, F32, DEFAULT_IMAGE_VAL, NUM_ROWS, NUM_COLS);
+    inReadout->image->row0 = 0;
+    inReadout->image->col0 = 0;
+    inReadout->row0 = 0;
+    inReadout->col0 = 0;
+    *(int*)&inReadout->image->col0 = 150;
+    *(int*)&inReadout->image->row0 = 150;
+
+    pmReadout *maskReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(maskReadout->image, U8, DEFAULT_MASK_VAL, NUM_ROWS, NUM_COLS)
+    pmMaskBadPixels(inReadout, maskReadout, MASK_VAL, SAT_VAL, GROW_VAL, GROW_RAD);
+
+    psFree(inReadout);
+    psFree(maskReadout);
+
+    return 0;
+}
+
+int testMaskBadPixels10( void )
+{
+    //
+    // Test J - Attempt to use complex input image
+    //
+
+    pmReadout *inReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(inReadout->image, C64, DEFAULT_IMAGE_VAL, NUM_ROWS, NUM_COLS);
+    inReadout->image->row0 = 0;
+    inReadout->image->col0 = 0;
+    inReadout->row0 = 0;
+    inReadout->col0 = 0;
+
+    pmReadout *maskReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(maskReadout->image, U8, DEFAULT_MASK_VAL, NUM_ROWS, NUM_COLS)
+
+    pmMaskBadPixels(inReadout, maskReadout, MASK_VAL, SAT_VAL, GROW_VAL, GROW_RAD);
+
+    psFree(inReadout);
+    psFree(maskReadout);
+
+    return 0;
+}
+
+int testMaskBadPixels11( void )
+{
+    //
+    // Test K - Attempt to use mask image with wrong data type.
+    //
+
+    pmReadout *inReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(inReadout->image, F32, DEFAULT_IMAGE_VAL, NUM_ROWS, NUM_COLS);
+    inReadout->image->row0 = 0;
+    inReadout->image->col0 = 0;
+    inReadout->row0 = 0;
+    inReadout->col0 = 0;
+
+    pmReadout *maskReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(maskReadout->image, F64, DEFAULT_MASK_VAL, NUM_ROWS, NUM_COLS)
+
+    pmMaskBadPixels(inReadout, maskReadout, MASK_VAL, SAT_VAL, GROW_VAL, GROW_RAD);
+
+    psFree(inReadout);
+    psFree(maskReadout);
+
+    return 0;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tst_pmNonLinear.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tst_pmNonLinear.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tst_pmNonLinear.c	(revision 22157)
@@ -0,0 +1,268 @@
+/* @file tst_pmNonLinear.c
+ *
+ *  @brief Contains the tests for pmNonLinear.c:
+ *
+ * test00: This code will create a simple polynomial, and call
+ * pmNonLinearityPolynomial() for a variety of image sizes [(1, 1), (1,
+ * N), (N, 1), (N, N)].  
+ *
+ * test01: This code will create simple table lookup vectors, and call
+ * pmNonLinearityPolynomial() for a variety of image sizes [(1, 1), (1,
+ * N), (N, 1), (N, N)].  
+ *
+ * test02, test03: This code tests the functions with various unallowable
+ * input parameters (NULLS) and incorrect vector sizes.
+ *
+ *  @author GLG, MHPCC
+ *
+ *  XXX: Add tests in which the lookup file has incorrect number of entries,
+ *  and where the data is outside the pmReadout range.
+ *
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-01-26 21:10:51 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+#include "psTest.h"
+#include "pslib.h"
+#include "pmNonLinear.h"
+static int test00(void);
+static int test01(void);
+static int test02(void);
+static int test03(void);
+testDescription tests[] = {
+                              {test00, 000, "pmNonLinearityPolynomial", true, false},
+                              {test01, 000, "pmNonLinearityLookup", true, false},
+                              {test02, 000, "pmNonLinearityPolynomial(): error/warning conditions", true, false},
+                              {test03, 000, "pmNonLinearityLookup(): error/warning conditions", true, false},
+                              {NULL}
+                          };
+
+#define NUM_ROWS 8
+#define NUM_COLS 8
+#define LOOKUP_FILENAME ".tmp_tst_pmNonLinearLookupFile"
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    //
+    // We generate a lookup file for future tests.  We should probably remove
+    // it when we're done.
+    //
+    FILE *fp = fopen(LOOKUP_FILENAME, "w");
+    ;
+    for (psS32 i=0;i<PS_MAX(NUM_COLS, NUM_ROWS)*3;i++) {
+        fprintf(fp, "%f %f\n", (float) i, (float) (2 * i));
+    }
+    fclose(fp);
+
+    //    system("rm LOOKUP_FILENAME");
+    return !runTestSuite(stderr, "Test Point Driver", tests, argc, argv);
+}
+
+int doNonLinearityPolynomialTest(int numCols, int numRows)
+{
+    int i;
+    int j;
+    float actual;
+    float expect;
+    int testStatus = true;
+    psImage *myImage = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    pmReadout *myReadout = pmReadoutAlloc(NULL);
+    myReadout->image = myImage;
+    psPolynomial1D *myPoly = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 2);
+    myPoly->coeff[1] = 1.0;
+
+    printPositiveTestHeader(stdout, "pmNonLinear", "doNonLinearityPolynomialTest");
+
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            myReadout->image->data.F32[i][j] = (float) (i + j);
+        }
+    }
+
+    myReadout = pmNonLinearityPolynomial(myReadout, myPoly);
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            expect = psPolynomial1DEval(myPoly, (float) (i + j));
+            actual = myReadout->image->data.F32[i][j];
+            if (FLT_EPSILON < fabs(expect - actual)) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                testStatus = false;
+            }
+        }
+    }
+
+
+    psFree(myReadout);
+    psFree(myPoly);
+    printFooter(stdout, "pmNonLinear", "doNonLinearityPolynomialTest", true);
+    return(testStatus);
+}
+
+int test00( void )
+{
+    int testStatus = 0;
+
+    testStatus |= doNonLinearityPolynomialTest(1, 1);
+    testStatus |= doNonLinearityPolynomialTest(NUM_COLS, 1);
+    testStatus |= doNonLinearityPolynomialTest(1, NUM_ROWS);
+    testStatus |= doNonLinearityPolynomialTest(NUM_COLS, NUM_ROWS);
+
+    return(testStatus);
+}
+
+int doNonLinearityLookupTest(int numCols, int numRows)
+{
+    int i;
+    int j;
+    float actual;
+    float expect;
+    int testStatus = true;
+    psImage *myImage = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    pmReadout *myReadout = pmReadoutAlloc(NULL);
+    myReadout->image = myImage;
+
+    printPositiveTestHeader(stdout, "pmNonLinear", "doNonLinearityLookupTest");
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            myReadout->image->data.F32[i][j] = (float) (i + j);
+        }
+    }
+
+    myReadout = pmNonLinearityLookup(myReadout, LOOKUP_FILENAME);
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            expect = (float) (2 * (i + j));
+            actual = myReadout->image->data.F32[i][j];
+            if (FLT_EPSILON < fabs(expect - actual)) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                testStatus = false;
+            }
+        }
+    }
+
+    psFree(myReadout);
+    printFooter(stdout, "pmNonLinear", "doNonLinearityLookupTest", true);
+    return(testStatus);
+}
+
+int test01( void )
+{
+    int testStatus = 0;
+
+    testStatus |= doNonLinearityLookupTest(1, 1);
+    testStatus |= doNonLinearityLookupTest(NUM_COLS, 1);
+    testStatus |= doNonLinearityLookupTest(1, NUM_ROWS);
+    testStatus |= doNonLinearityLookupTest (NUM_COLS, NUM_ROWS);
+
+    return(testStatus);
+}
+
+int test02( void )
+{
+    int i;
+    int j;
+    int testStatus = true;
+    psImage *myImage = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+    pmReadout *myReadout = pmReadoutAlloc(NULL);
+    pmReadout *rc = NULL;
+    myReadout->image = myImage;
+    psPolynomial1D *myPoly = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 2);
+    myPoly->coeff[1] = 1.0;
+
+    printPositiveTestHeader(stdout, "pmNonLinear", "Testing bad input parameter conditions.");
+    for (i=0;i<NUM_ROWS;i++) {
+        for (j=0;j<NUM_COLS;j++) {
+            myReadout->image->data.F32[i][j] = (float) (i + j);
+        }
+    }
+
+    printf("------------------------------------------------------------\n");
+    printf("Calling pmNonLinearityPolynomial() with NULL input readout.  Should generate error, return NULL.\n");
+    rc = pmNonLinearityPolynomial(NULL, myPoly);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmNonLinearityPolynomial() returned a non-NULL pmReadout\n");
+        testStatus = false;
+    }
+
+    printf("------------------------------------------------------------\n");
+    printf("Calling pmNonLinearityPolynomial() with NULL input readout->image.  Should generate error, return NULL.\n");
+    psImage *tmpImage = myReadout->image;
+    myReadout->image = NULL;
+    rc = pmNonLinearityPolynomial(myReadout, myPoly);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmNonLinearityPolynomial() returned a non-NULL pmReadout\n");
+        testStatus = false;
+    }
+    myReadout->image = tmpImage;
+
+    printf("------------------------------------------------------------\n");
+    printf("Calling pmNonLinearityPolynomial() with NULL polynomial.  Should generate error, return NULL.\n");
+    rc = pmNonLinearityPolynomial(myReadout, NULL);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmNonLinearityPolynomial() returned a non-NULL pmReadout\n");
+        testStatus = false;
+    }
+
+    psFree(myReadout);
+    psFree(myPoly);
+    return(testStatus);
+}
+
+
+int test03Init(pmReadout *myReadout)
+{
+    for (psS32 i=0;i<NUM_ROWS;i++) {
+        for (psS32 j=0;j<NUM_COLS;j++) {
+            myReadout->image->data.F32[i][j] = (float) (i + j);
+        }
+    }
+    return(0);
+}
+
+int test03()
+{
+    int testStatus = true;
+    psImage *myImage = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+    pmReadout *myReadout = pmReadoutAlloc(NULL);
+    pmReadout *rc = NULL;
+    myReadout->image = myImage;
+
+    test03Init(myReadout);
+    printf("------------------------------------------------------------\n");
+    printf("Calling pmNonLinearityLookup() with NULL input pmReadout.  Should generate error, return NULL.\n");
+    rc = pmNonLinearityLookup(NULL, LOOKUP_FILENAME);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmNonLinearityPolynomial() returned a non-NULL pmReadout\n");
+        testStatus = false;
+    }
+
+    printf("------------------------------------------------------------\n");
+    printf("Calling pmNonLinearityLookup() with NULL input pmReadout->image.  Should generate error, return NULL.\n");
+    psImage *tmpImage = myReadout->image;
+    myReadout->image = NULL;
+    rc = pmNonLinearityLookup(myReadout, LOOKUP_FILENAME);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmNonLinearityPolynomial() returned a non-NULL pmReadout\n");
+        testStatus = false;
+    }
+    myReadout->image = tmpImage;
+
+    printf("------------------------------------------------------------\n");
+    printf("Calling pmNonLinearityLookup() with non-existent lookup file.\n");
+    rc = pmNonLinearityLookup(myReadout, "I_DONT_EXIST");
+    if (rc == NULL) {
+        printf("TEST ERROR: pmNonLinearityPolynomial() returned a NULL pmReadout\n");
+        testStatus = false;
+    }
+
+
+    printf("------------------------------------------------------------\n");
+    printf("Calling pmNonLinearityLookup() with one pixels outside inFlux range.  Should generate warnings.\n");
+
+    psFree(myReadout);
+
+    printFooter(stdout, "pmNonLinear", "Testing bad input parameter conditions.", true);
+    return(testStatus);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tst_pmSubtractBias.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tst_pmSubtractBias.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tst_pmSubtractBias.c	(revision 22157)
@@ -0,0 +1,1129 @@
+/** @file tst_pmSubtractBias.c
+ *
+ *  @brief Contains the tests for pmSubtractBias.c:
+ *
+ * test00a: This code will subtract full bias frames from the input image.
+ * XXX: Must test:
+ *  Various image offsets.
+ *  Various image size combinations.
+ *  Various data types for the bias and input images.
+ *  Ensure code works when CELL.TRIMSEC is not set.
+ * test00b: This code will subtract full dark frames from the input image.
+ * XXX: Must test:
+ *  Various image offsets.
+ *  Various image size combinations.
+ *  Various data types for the bias and input images.
+ *  Code properly determines CELL.DARKTIME from cell metadata.
+ *  Ensure code works when CELL.DARKTIME is not set.
+ *  Ensure code works when CELL.TRIMSEC is not set.
+ *  test03: Calculate a row overscan vector and subtract it from each
+ *  row in the input image.
+ * test05:
+ *
+ *  @author GLG, MHPCC
+ *
+ *  XXX: Memory leaks are not being detected.
+ *
+ *  @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-05-25 22:02:23 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+#include "psTest.h"
+#include "pslib.h"
+#include "pmSubtractBias.h"
+static int test00a(void);
+static int test00b(void);
+//static int test01(void);
+//static int test02(void);
+//static int test03(void);
+//static int test04(void);
+static int test05(void);
+testDescription tests[] = {
+                              {test00a, 000, "doSubtractBiasFullFrame", 0, true},
+                              {test00b, 000, "doSubtractDarkFullFrame", 0, true},
+                              //                              {test01, 000, "pmSubtractBias", 0, true},
+                              //                              {test02, 000, "pmSubtractBias", 0, true},
+                              //                              {test03, 000, "pmSubtractBias", 0, true},
+                              //                              {test04, 000, "pmSubtractBias", 0, true},
+                              {test05, 000, "pmSubtractBias", 0, false},
+                              {NULL}
+                          };
+
+psS32 currentId = 0;
+psS32 memLeaks = 0;             // XXX: remove
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+
+    psTraceSetLevel(".", 0);
+    psTraceSetLevel("spline1DFree", 0);
+    psTraceSetLevel("calculateSecondDerivs", 0);
+    psTraceSetLevel("vectorBinDisectF32", 0);
+    psTraceSetLevel("vectorBinDisectF64", 0);
+    psTraceSetLevel("p_psVectorBinDisect", 0);
+    psTraceSetLevel("psSpline1DAlloc", 0);
+    psTraceSetLevel("psVectorFitSpline1D", 0);
+    psTraceSetLevel("psSpline1DEval", 0);
+    psTraceSetLevel("psSpline1DEvalVector", 0);
+
+    psS32 currentId = psMemGetId(); // XXX: remove
+    psS32 memLeaks = 0;             // XXX: remove
+    if (0) {
+        PRINT_MEMLEAKS(0);
+    }
+
+    return !runTestSuite(stderr, "Test Point Driver", tests, argc, argv);
+}
+
+#define NUM_ROWS 8
+#define NUM_COLS 8
+#define MAX_HEADER_MSG_LENGTH 1000
+#define POLYNOMIAL_FIT_ORDER 2
+#define NUM_OVERSCANS 2
+/******************************************************************************
+doSubtractBiasFullFrame(): a sample pmReadout as well as a bias image are
+created and the bias image is subtracted from the pmReadout.
+ *****************************************************************************/
+int doSubtractBiasFullFrame(int numCols, int numRows)
+{
+    int i;
+    int j;
+    float actual;
+    float expect;
+    int testStatus = 0;
+    psImage *tmpImage1 = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    psImage *tmpImage2 = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    pmReadout *myReadout = pmReadoutAlloc(NULL);
+    pmReadout *myBias = pmReadoutAlloc(NULL);
+    myReadout->image = tmpImage1;
+    myBias->image = tmpImage2;
+
+    char *HeaderMessageStr = (char *) psAlloc(MAX_HEADER_MSG_LENGTH);
+    sprintf(HeaderMessageStr, "doSubtractBiasFullFrame(%d, %d)", numRows, numCols);
+    printPositiveTestHeader(stdout, "pmSubtractBias", HeaderMessageStr);
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            myReadout->image->data.F32[i][j] = (float) (i + j);
+            myBias->image->data.F32[i][j] = 1.0;
+        }
+    }
+
+    myReadout = pmSubtractBias(myReadout, NULL, PM_FIT_NONE, false,
+                               NULL, 0, myBias, NULL);
+
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            expect = ((float) (i + j)) - 1.0;
+            actual = myReadout->image->data.F32[i][j];
+            if (FLT_EPSILON < fabs(expect - actual)) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                testStatus = 1;
+            }
+        }
+    }
+
+
+    psFree(myReadout);
+    psFree(myBias);
+    printFooter(stdout, "pmSubtractBias", HeaderMessageStr, true);
+    psFree(HeaderMessageStr);
+    return(testStatus);
+}
+
+
+int test00a( void )
+{
+    int testStatus = 0;
+
+    testStatus |= doSubtractBiasFullFrame(1, 1);
+    testStatus |= doSubtractBiasFullFrame(NUM_COLS, 1);
+    testStatus |= doSubtractBiasFullFrame(1, NUM_ROWS);
+    testStatus |= doSubtractBiasFullFrame(NUM_COLS, NUM_ROWS);
+    return(testStatus);
+}
+
+
+/******************************************************************************
+doSubtractDarkFullFrame(): a sample pmReadout as well as a dark image are
+created and the dark image is subtracted from the pmReadout.
+ *****************************************************************************/
+int doSubtractDarkFullFrame(int numCols, int numRows)
+{
+    int i;
+    int j;
+    float actual;
+    float expect;
+    int testStatus = 0;
+    psImage *tmpImage1 = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    psImage *tmpImage2 = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    pmReadout *myReadout = pmReadoutAlloc(NULL);
+    pmReadout *myDark = pmReadoutAlloc(NULL);
+    myReadout->image = tmpImage1;
+    myDark->image = tmpImage2;
+
+    char *HeaderMessageStr = (char *) psAlloc(MAX_HEADER_MSG_LENGTH);
+    sprintf(HeaderMessageStr, "doSubtractDarkFullFrame(%d, %d)", numRows, numCols);
+    printPositiveTestHeader(stdout, "pmSubtractBias", HeaderMessageStr);
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            myReadout->image->data.F32[i][j] = (float) (i + j);
+            myDark->image->data.F32[i][j] = 1.0;
+        }
+    }
+
+    myReadout = pmSubtractBias(myReadout, NULL, PM_FIT_NONE, false,
+                               NULL, 0, NULL, myDark);
+
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            expect = ((float) (i + j)) - 1.0;
+            actual = myReadout->image->data.F32[i][j];
+            if (FLT_EPSILON < fabs(expect - actual)) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                testStatus = 1;
+            }
+        }
+    }
+
+
+    psFree(myReadout);
+    psFree(myDark);
+    printFooter(stdout, "pmSubtractBias", HeaderMessageStr, true);
+    psFree(HeaderMessageStr);
+    return(testStatus);
+}
+
+
+int test00b( void )
+{
+    int testStatus = 0;
+
+    testStatus |= doSubtractDarkFullFrame(1, 1);
+    testStatus |= doSubtractDarkFullFrame(NUM_COLS, 1);
+    testStatus |= doSubtractDarkFullFrame(1, NUM_ROWS);
+    testStatus |= doSubtractDarkFullFrame(NUM_COLS, NUM_ROWS);
+    return(testStatus);
+}
+
+
+/*
+int doSubtractOverscansTestInputCases(int numCols, int numRows)
+{
+    int i;
+    int j;
+    int testStatus = 0;
+    psImage *tmpImage1 = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    psImage *tmpImage2 = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    psImage *tmpImage3 = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    psImage *tmpImage4 = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    psImage *tmpImage2Short = psImageAlloc(numCols-1, numRows-1, PS_TYPE_F32);
+    psImage *tmpImage3Short = psImageAlloc(numCols-1, numRows-1, PS_TYPE_F32);
+    psImage *tmpImage4Short = psImageAlloc(numCols-1, numRows-1, PS_TYPE_F32);
+    pmReadout *myReadout = pmReadoutAlloc(NULL);
+    myReadout->image = tmpImage1;
+    pmReadout *rc = NULL;
+    psList *list;
+    psList *listShort;
+    psStats *stat = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+    psImage *tmpImage5 = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    pmReadout *myBias = pmReadoutAlloc(NULL);
+    myBias->image = tmpImage5;
+    printPositiveTestHeader(stdout, "pmSubtractBias", "Testing input parameter error conditions");
+ 
+    psImage *tmpImage5ShortRows = psImageAlloc(numCols, numRows-1, PS_TYPE_F32);
+    pmReadout *myBiasShortRows = pmReadoutAlloc(NULL);
+    myBiasShortRows->image = tmpImage5ShortRows;
+    psImage *tmpImage5ShortCols = psImageAlloc(numCols-1, numRows, PS_TYPE_F32);
+    pmReadout *myBiasShortCols = pmReadoutAlloc(NULL);
+    myBiasShortCols->image = tmpImage5ShortCols;
+ 
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            myReadout->image->data.F32[i][j] = (float) (i + j);
+            tmpImage2->data.F32[i][j] = 3.0;
+            tmpImage3->data.F32[i][j] = 4.0;
+            tmpImage4->data.F32[i][j] = 5.0;
+            myBias->image->data.F32[i][j] = 1.0;
+        }
+    }
+    list = psListAlloc(tmpImage2);
+    psListAdd(list, PS_LIST_HEAD, tmpImage3);
+    psListAdd(list, PS_LIST_HEAD, tmpImage4);
+ 
+    for (i=0;i<numRows-1;i++) {
+        for (j=0;j<numCols-1;j++) {
+            tmpImage2Short->data.F32[i][j] = 3.0;
+            tmpImage3Short->data.F32[i][j] = 4.0;
+            tmpImage4Short->data.F32[i][j] = 5.0;
+        }
+    }
+    listShort = psListAlloc(tmpImage2Short);
+    psListAdd(listShort, PS_LIST_HEAD, tmpImage3Short);
+    psListAdd(listShort, PS_LIST_HEAD, tmpImage4Short);
+    for (i=0;i<numRows-1;i++) {
+        for (j=0;j<numCols;j++) {
+            myBiasShortRows->image->data.F32[i][j] = 1.0;
+        }
+    }
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols-1;j++) {
+            myBiasShortCols->image->data.F32[i][j] = 1.0;
+        }
+    }
+ 
+ 
+    printf("------------------------------------------------------------------\n");
+    printf("Calling pmSubtractBias() with NULL overscan list and PM_OVERSCAN_ALL.  Should generate error.\n");
+    rc = pmSubtractBias(myReadout, NULL, NULL, PM_OVERSCAN_ALL, stat, 0, PM_FIT_NONE, NULL);
+    if (rc != myReadout) {
+        printf("TEST ERROR: pmSubtractBias() did not return input pmReadout.\n");
+        testStatus = false;
+    }
+ 
+    printf("------------------------------------------------------------------\n");
+    printf("Calling pmSubtractBias() with NULL overscan list and PM_OVERSCAN_ROWS.  Should generate error.\n");
+    rc = pmSubtractBias(myReadout, NULL, NULL, PM_OVERSCAN_ROWS, stat, 0, PM_FIT_NONE, NULL);
+    if (rc != myReadout) {
+        printf("TEST ERROR: pmSubtractBias() did not return input pmReadout.\n");
+        testStatus = false;
+        psFree(rc);
+    }
+ 
+    printf("------------------------------------------------------------------\n");
+    printf("Calling pmSubtractBias() with NULL overscan list and PM_OVERSCAN_COLUMNS.  Should generate error.\n");
+    rc = pmSubtractBias(myReadout, NULL, NULL, PM_OVERSCAN_COLUMNS, stat, 0, PM_FIT_NONE, NULL);
+    if (rc != myReadout) {
+        printf("TEST ERROR: pmSubtractBias() did not return input pmReadout.\n");
+        testStatus = false;
+        psFree(rc);
+    }
+ 
+    printf("------------------------------------------------------------------\n");
+    printf("Calling pmSubtractBias() with non-NULL overscan list and PM_OVERSCAN_NONE.  Should generate warning.\n");
+    rc = pmSubtractBias(myReadout, NULL, list, PM_OVERSCAN_NONE, stat, 0, PM_FIT_NONE, myBias);
+ 
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            psF32 expect = ((float) (i + j)) - 1.0;
+            psF32 actual = rc->image->data.F32[i][j];
+            if (FLT_EPSILON < fabs(expect - actual)) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                testStatus = 1;
+            }
+ 
+            // Restore myReadout for next test.
+            myReadout->image->data.F32[i][j] = (float) (i + j);
+        }
+    }
+ 
+    // XXX: This does not seem to be a requirement.
+    if (0) {
+        printf("------------------------------------------------------------------\n");
+        printf("Calling pmSubtractBias() with NULL overscan list and PM_OVERSCAN_NONE.  Should generate warning.\n");
+        rc = pmSubtractBias(myReadout, NULL, NULL, PM_OVERSCAN_NONE, stat,
+                            0, PM_FIT_NONE, myBias);
+ 
+        for (i=0;i<numRows;i++) {
+            for (j=0;j<numCols;j++) {
+                psF32 expect = ((float) (i + j)) - 1.0;
+                psF32 actual = rc->image->data.F32[i][j];
+                if (FLT_EPSILON < fabs(expect - actual)) {
+                    printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                    testStatus = 1;
+                }
+ 
+                // Restore myReadout for next test.
+                myReadout->image->data.F32[i][j] = (float) (i + j);
+            }
+        }
+    }
+ 
+    printf("------------------------------------------------------------------\n");
+    printf("Calling pmSubtractBias() with PM_OVERSCAN_NONE and PM_FIT_POLYNOMIAL.  Should generate Warning.\n");
+    rc = pmSubtractBias(myReadout, NULL, NULL, PM_OVERSCAN_NONE, stat, 0, PM_FIT_POLYNOMIAL, myBias);
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            psF32 expect = ((float) (i + j)) - 1.0;
+            psF32 actual = rc->image->data.F32[i][j];
+            if (FLT_EPSILON < fabs(expect - actual)) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                testStatus = 1;
+            }
+ 
+            // Restore myReadout for next test.
+            myReadout->image->data.F32[i][j] = (float) (i + j);
+        }
+    }
+ 
+ 
+    printf("------------------------------------------------------------------\n");
+    printf("Calling pmSubtractBias() with PM_OVERSCAN_ALL and PM_FIT_SPLINE.  Should generate Warning.\n");
+    rc = pmSubtractBias(myReadout, NULL, NULL, PM_OVERSCAN_NONE, stat, 0, PM_FIT_SPLINE, myBias);
+    if (rc != myReadout) {
+        printf("TEST ERROR: pmSubtractBias() did not return input pmReadout.\n");
+        testStatus = false;
+        psFree(rc);
+    }
+ 
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            psF32 expect = ((float) (i + j)) - 1.0;
+            psF32 actual = rc->image->data.F32[i][j];
+            if (FLT_EPSILON < fabs(expect - actual)) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                testStatus = 1;
+            }
+ 
+            // Restore myReadout for next test.
+            myReadout->image->data.F32[i][j] = (float) (i + j);
+        }
+    }
+ 
+ 
+    printf("------------------------------------------------------------------\n");
+    printf("Calling pmSubtractBias() with multiple stats->options.  Should generate Warning.\n");
+    stat->options|= PS_STAT_SAMPLE_MEDIAN;
+    myReadout = pmSubtractBias(myReadout, NULL, list, PM_OVERSCAN_ALL, stat,
+                               0, PM_FIT_NONE, NULL);
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            psF32 expect = ((float) (i + j)) - 12.0;
+            psF32 actual = rc->image->data.F32[i][j];
+            if (FLT_EPSILON < fabs(expect - actual)) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                testStatus = 1;
+            }
+        }
+    }
+    stat->options = PS_STAT_SAMPLE_MEAN;
+ 
+    printf("------------------------------------------------------------------\n");
+    printf("Calling pmSubtractBias() undersize overscans (PM_OVERSCAN_ROWS).  Should generate Warning.\n");
+    rc = pmSubtractBias(myReadout, NULL, listShort, PM_OVERSCAN_ROWS, stat,
+                        0, PM_FIT_NONE, NULL);
+    if (0) {
+        for (i=0;i<numRows;i++) {
+            for (j=0;j<numCols;j++) {
+                psF32 expect = ((float) (i + j)) - 12.0;
+                psF32 actual = rc->image->data.F32[i][j];
+                if (FLT_EPSILON < fabs(expect - actual)) {
+                    printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                    testStatus = 1;
+                }
+            }
+        }
+    }
+ 
+    printf("------------------------------------------------------------------\n");
+    printf("Calling pmSubtractBias() undersize overscans (PM_OVERSCAN_COLUMNS).  Should generate Warning.\n");
+    rc = pmSubtractBias(myReadout, NULL, listShort, PM_OVERSCAN_COLUMNS, stat,
+                        0, PM_FIT_NONE, NULL);
+    if (0) {
+        for (i=0;i<numRows;i++) {
+            for (j=0;j<numCols;j++) {
+                psF32 expect = ((float) (i + j)) - 12.0;
+                psF32 actual = rc->image->data.F32[i][j];
+                if (FLT_EPSILON < fabs(expect - actual)) {
+                    printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                    testStatus = 1;
+                }
+            }
+        }
+    }
+ 
+    printf("------------------------------------------------------------------\n");
+    printf("Calling pmSubtractBias() undersize bias image (short rows).  Should generate Error.\n");
+    myReadout = pmSubtractBias(myReadout, NULL, NULL, PM_OVERSCAN_NONE, NULL,
+                               0, PM_FIT_NONE, myBiasShortRows);
+    if (rc != myReadout) {
+        printf("TEST ERROR: pmSubtractBias() did not return input pmReadout.\n");
+        testStatus = false;
+        psFree(rc);
+    }
+ 
+ 
+    printf("------------------------------------------------------------------\n");
+    printf("Calling pmSubtractBias() undersize bias image (short columns).  Should generate Error.\n");
+    myReadout = pmSubtractBias(myReadout, NULL, NULL, PM_OVERSCAN_NONE, NULL,
+                               0, PM_FIT_NONE, myBiasShortCols);
+    if (rc != myReadout) {
+        printf("TEST ERROR: pmSubtractBias() did not return input pmReadout.\n");
+        testStatus = false;
+        psFree(rc);
+    }
+ 
+    printf("------------------------------------------------------------------\n");
+    printf("Calling pmSubtractBias() with bogus PM_FIT.  Should generate Error.\n");
+    myReadout = pmSubtractBias(myReadout, NULL, list, PM_OVERSCAN_ROWS, stat,
+                               0, 54321, NULL);
+    if (rc != myReadout) {
+        printf("TEST ERROR: pmSubtractBias() did not return input pmReadout.\n");
+        testStatus = false;
+        psFree(rc);
+    }
+ 
+    printf("------------------------------------------------------------------\n");
+    printf("Calling pmSubtractBias() with bogus overScanAxis.  Should generate Error.\n");
+    myReadout = pmSubtractBias(myReadout, NULL, list, 54321, stat,
+                               0, PM_FIT_NONE, NULL);
+    if (rc != myReadout) {
+        printf("TEST ERROR: pmSubtractBias() did not return input pmReadout.\n");
+        testStatus = false;
+        psFree(rc);
+    }
+ 
+    if (0) {
+        for (i=0;i<numRows;i++) {
+            for (j=0;j<numCols;j++) {
+                psF32 expect = ((float) (i + j)) - 12.0;
+                psF32 actual = rc->image->data.F32[i][j];
+                if (FLT_EPSILON < fabs(expect - actual)) {
+                    printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                    testStatus = 1;
+                }
+            }
+        }
+    }
+ 
+    printf("------------------------------------------------------------------\n");
+    psFree(myReadout);
+    psFree(tmpImage2);
+    psFree(tmpImage3);
+    psFree(tmpImage4);
+    psFree(tmpImage2Short);
+    psFree(tmpImage3Short);
+    psFree(tmpImage4Short);
+    psFree(myBias);
+    psFree(myBiasShortRows);
+    psFree(myBiasShortCols);
+    psFree(stat);
+    psFree(list);
+    psFree(listShort);
+ 
+    printFooter(stdout, "pmSubtractBias", "Testing input parameter error conditions", true);
+    return(testStatus);
+}
+ 
+int test04( void )
+{
+    int testStatus = 0;
+ 
+    testStatus |= doSubtractOverscansTestInputCases(NUM_COLS, NUM_ROWS);
+    return(testStatus);
+}
+*/
+
+void PS_POLY1D_PRINT(
+    psPolynomial1D *poly)
+{
+    printf("-------------- PS_POLY1D_PRINT() --------------\n");
+    printf("poly->nX is %d\n", poly->nX);
+    for (psS32 i = 0 ; i < (1 + poly->nX) ; i++) {
+        printf("poly->coeff[%d] is %f\n", i, poly->coeff[i]);
+    }
+}
+
+void PS_PRINT_SPLINE2(psSpline1D *mySpline)
+{
+    printf("-------------- PS_PRINT_SPLINE2() --------------\n");
+    if (mySpline != NULL) {
+        printf("mySpline->n is %d\n", mySpline->n);
+        for (psS32 i = 0 ; i < mySpline->n ; i++) {
+            if (mySpline->spline[i] != NULL) {
+                PS_POLY1D_PRINT(mySpline->spline[i]);
+            }
+        }
+        if (mySpline->knots != NULL) {
+            PS_VECTOR_PRINT_F32(mySpline->knots);
+        }
+    } else {
+        printf("NULL\n");
+    }
+    printf("-------------- PS_PRINT_SPLINE2() DONE --------------\n");
+}
+
+
+
+
+
+
+/******************************************************************************
+doSubtractOverscansGeneric(): This is a general version of the
+bias subtraction tests which allows the various parameters to be specified
+as arguments.
+ *****************************************************************************/
+int doSubtractOverscansGeneric(
+    int imageNumCols,
+    int imageNumRows,
+    int overscanNumCols,
+    int overscanNumRows,
+    int numOverscans,
+    pmOverscanAxis overscanaxis,
+    pmFit fit,
+    psS32 nBin)
+{
+    int i;
+    int j;
+    float actual;
+    float expect;
+    int testStatus = 0;
+
+    printPositiveTestHeader(stdout, "pmSubtractBias", "PUT COMMENT HERE");
+    printf("---- doSubtractOverscansGeneric() ----\n");
+    printf("    Image size: %d by %d\n", imageNumRows, imageNumCols);
+    printf("    Overscan size: %d by %d\n", overscanNumRows, overscanNumCols);
+    printf("    Total Overscans: %d\n", numOverscans);
+    printf("    Binning factor: %d\n", nBin);
+    if (overscanaxis == PM_OVERSCAN_ROWS)
+        printf("    Overscan axis: PM_OVERSCAN_ROWS\n");
+    if (overscanaxis == PM_OVERSCAN_COLUMNS)
+        printf("    Overscan axis: PM_OVERSCAN_COLUMNS\n");
+    if (overscanaxis == PM_OVERSCAN_ALL)
+        printf("    Overscan axis: PM_OVERSCAN_ALL\n");
+    if (overscanaxis == PM_OVERSCAN_NONE)
+        printf("    Overscan axis: PM_OVERSCAN_NONE\n");
+    if (fit == PM_FIT_NONE)
+        printf("    Fit type: PM_FIT_NONE\n");
+    if (fit == PM_FIT_POLYNOMIAL)
+        printf("    Fit type: PM_FIT_POLYNOMIAL\n");
+    if (fit == PM_FIT_SPLINE)
+        printf("    Fit type: PM_FIT_SPLINE\n");
+
+    //
+    // Create and initialize input image, FPA hierarchy.
+    //
+    const psMetadata *camera = psMetadataAlloc();
+    pmFPA* fpa = pmFPAAlloc(camera);
+
+    if (fpa == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc returned a NULL.\n");
+        return 1;
+    }
+
+    pmChip *chip = pmChipAlloc(fpa, "ChipName");
+    if (chip == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmChipAlloc returned a NULL.\n");
+        return 2;
+    }
+
+    pmCell *cell = pmCellAlloc(chip, (psMetadata *) camera, "CellName");
+    if (cell == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmCellAlloc returned a NULL.\n");
+        return 3;
+    }
+
+    pmReadout *myReadout = pmReadoutAlloc(cell);
+    myReadout->image = psImageAlloc(imageNumCols, imageNumRows, PS_TYPE_F32);
+    for (i=0;i<myReadout->image->numRows;i++) {
+        for (j=0;j<myReadout->image->numCols;j++) {
+            myReadout->image->data.F32[i][j] = (float) (i + j);
+        }
+    }
+
+    //
+    // Set overscan axis in the metadata.
+    //
+    psBool rc = false;
+    if (overscanaxis == PM_OVERSCAN_ROWS) {
+        rc = psMetadataAddS32(myReadout->parent->concepts, PS_LIST_HEAD, "CELL.READDIR", 0, NULL, 1);
+    } else if (overscanaxis == PM_OVERSCAN_COLUMNS) {
+        rc = psMetadataAddS32(myReadout->parent->concepts, PS_LIST_HEAD, "CELL.READDIR", 0, NULL, 2);
+    } else if (overscanaxis == PM_OVERSCAN_ALL) {
+        rc = psMetadataAddS32(myReadout->parent->concepts, PS_LIST_HEAD, "CELL.READDIR", 0, NULL, 3);
+    } else if (overscanaxis == PM_OVERSCAN_NONE) {
+        rc = psMetadataAddS32(myReadout->parent->concepts, PS_LIST_HEAD, "CELL.READDIR", 0, NULL, 0);
+    }
+    if (rc == false) {
+        printf("TEST ERROR: Could not set CELL.READDIR metadata.\n");
+        testStatus = 1;
+    }
+
+    psStats *stat = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+    psPolynomial1D *myPoly = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, POLYNOMIAL_FIT_ORDER);
+    psSpline1D *mySpline = NULL;
+
+
+    if (0) {
+        if (overscanNumRows <= 0) {
+            overscanNumRows = 1;
+        }
+        if (overscanNumCols <= 0) {
+            overscanNumCols = 1;
+        }
+    }
+    psF32 oAverage = 0.0;
+    myReadout->bias = NULL;
+    for (psS32 i = 0 ; i < numOverscans ; i++) {
+        psImage *tmpImage = psImageAlloc(overscanNumCols, overscanNumRows, PS_TYPE_F32);
+        psF32 oValue = (float) (i + 3);
+        PS_IMAGE_SET_F32(tmpImage, oValue);
+        oAverage += oValue;
+        if (myReadout->bias == NULL) {
+            myReadout->bias = psListAlloc(tmpImage);
+        } else {
+            psListAdd(myReadout->bias, PS_LIST_HEAD, tmpImage);
+        }
+    }
+    oAverage/= (psF32) numOverscans;
+    if (0) {
+        if (fit == PM_FIT_NONE) {
+            myReadout = pmSubtractBias(myReadout, NULL, PM_FIT_NONE, overscanaxis,
+                                       stat, nBin, NULL, NULL);
+        } else if (fit == PM_FIT_POLYNOMIAL) {
+            myReadout = pmSubtractBias(myReadout, myPoly, PM_FIT_POLYNOMIAL, overscanaxis,
+                                       stat, nBin, NULL, NULL);
+        } else if (fit == PM_FIT_SPLINE) {
+            //        mySpline = psSpline1DAlloc();
+            myReadout = pmSubtractBias(myReadout, mySpline, PM_FIT_SPLINE, overscanaxis,
+                                       stat, nBin, NULL, NULL);
+        }
+        if (myReadout == NULL ) {
+            printf("TEST ERROR: pmSubtractBias() returned NULL.\n");
+            testStatus = 1;
+        } else {
+            for (i=0;i<imageNumRows;i++) {
+                for (j=0;j<imageNumCols;j++) {
+                    expect = ((float) (i + j)) - oAverage;
+                    actual = myReadout->image->data.F32[i][j];
+                    if (FLT_EPSILON < fabs(expect - actual)) {
+                        printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                        testStatus = 1;
+                    } else {
+                        //printf("GOOD: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                    }
+                }
+            }
+        }
+    }
+
+    // HEY
+    psFree(fpa);
+    psFree(stat);
+    psFree(myPoly);
+    psFree(mySpline);
+
+    printFooter(stdout, "pmSubtractBias", "Column Overscans", true);
+    return(testStatus);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/******************************************************************************
+test05a() The following combinations are tested here:
+ Overscan images are same size, no fit, bin factor is 1.
+ Overscan images are same size, no fit, bin factor is 2.
+ *****************************************************************************/
+int test05a(
+    psS32 imageNumCols,
+    psS32 imageNumRows,
+    psS32 overscanNumCols,
+    psS32 overscanNumRows)
+{
+    int testStatus = 0;
+
+    // imageNumCols, imageNumRows, overscanNumCols, overscanNumRows,
+    // overscanaxis, fit, nBin
+
+    //
+    // Overscan images are same size, no fit, bin factor is 1.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_NONE, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_NONE, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_NONE, 1);
+
+    //
+    // Overscan images are same size, no fit, bin factor is 2.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_NONE, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_NONE, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_NONE, 2);
+
+    return(testStatus);
+}
+
+
+/******************************************************************************
+test05b() The following combinations are tested here:
+ Overscan images are too small, spline fit, bin factor is 1.
+ Overscan images are too small, spline fit, bin factor is 2.
+ Overscan images are same size, spline fit, bin factor is 1.
+ Overscan images are same size, spline fit, bin factor is 2.
+ Overscan images are too big,   spline fit, bin factor is 1.
+ Overscan images are too big,   spline fit, bin factor is 2.
+ A single overscan image of the same size, spline fit, bin factor is 1.
+ 
+ Overscan images are too small, polynomial fit, bin factor is 1.
+ Overscan images are too small, polynomial fit, bin factor is 2.
+ Overscan images are same size, polynomial fit, bin factor is 1.
+ Overscan images are same size, polynomial fit, bin factor is 2.
+ Overscan images are too big,   polynomial fit, bin factor is 1.
+ Overscan images are too big,   polynomial fit, bin factor is 2.
+ A single overscan image of the same size, polynomial fit, bin factor is 1.
+ 
+XXX: Must add M-by-N image size tests.
+ *****************************************************************************/
+int test05b(
+    psS32 imageNumCols,
+    psS32 imageNumRows,
+    psS32 overscanNumCols,
+    psS32 overscanNumRows)
+{
+    int testStatus = 0;
+
+    //
+    // Overscan images are too small, spline fit, bin factor is 1.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols-2, overscanNumRows-2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_SPLINE, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols-2, overscanNumRows-2, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_SPLINE, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols-2, overscanNumRows-2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_SPLINE, 1);
+
+    //
+    // Overscan images are too small, spline fit, bin factor is 2.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols-2, overscanNumRows-2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_SPLINE, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols-2, overscanNumRows-2, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_SPLINE, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols-2, overscanNumRows-2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_SPLINE, 2);
+
+
+    //
+    // Overscan images are same size, spline fit, bin factor is 1.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_SPLINE, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_SPLINE, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_SPLINE, 1);
+
+    //
+    // Overscan images are same size, spline fit, bin factor is 2.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_SPLINE, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_SPLINE, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_SPLINE, 2);
+
+    //
+    // Overscan images are too big, spline fit, bin factor is 1.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols+2, overscanNumRows+2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_SPLINE, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols+2, overscanNumRows+2, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_SPLINE, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols+2, overscanNumRows+2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_SPLINE, 1);
+
+    //
+    // Overscan images are too big, spline fit, bin factor is 2.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols+2, overscanNumRows+2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_SPLINE, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols+2, overscanNumRows+2, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_SPLINE, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols+2, overscanNumRows+2 , NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_SPLINE, 2);
+
+
+    //
+    // A single overscan image of the same size, spline fit, bin factor is 1.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, 1,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_SPLINE, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, 1,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_SPLINE, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, 1,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_SPLINE, 1);
+
+
+    //
+    // Overscan images are too small, polynomial fit, bin factor is 1.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols-2, overscanNumRows-2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_POLYNOMIAL, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols-2, overscanNumRows-2, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_POLYNOMIAL, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols-2, overscanNumRows-2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_POLYNOMIAL, 1);
+
+    //
+    // Overscan images are too small, polynomial fit, bin factor is 2.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols-2, overscanNumRows-2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_POLYNOMIAL, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols-2, overscanNumRows-2, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_POLYNOMIAL, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols-2, overscanNumRows-2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_POLYNOMIAL, 2);
+
+
+    //
+    // Overscan images are same size, polynomial fit, bin factor is 1.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_POLYNOMIAL, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_POLYNOMIAL, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_POLYNOMIAL, 1);
+
+    //
+    // Overscan images are same size, polynomial fit, bin factor is 2.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_POLYNOMIAL, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_POLYNOMIAL, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_POLYNOMIAL, 2);
+
+    //
+    // Overscan images are too big, polynomial fit, bin factor is 1.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols+2, overscanNumRows+2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_POLYNOMIAL, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols+2, overscanNumRows+2, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_POLYNOMIAL, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols+2, overscanNumRows+2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_POLYNOMIAL, 1);
+
+    //
+    // Overscan images are too big, polynomial fit, bin factor is 2.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols+2, overscanNumRows+2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_POLYNOMIAL, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols+2, overscanNumRows+2, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_POLYNOMIAL, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols+2, overscanNumRows+2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_POLYNOMIAL, 2);
+
+    //
+    // A single overscan image of the same size, polynomial fit, bin factor is 1.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, 1,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_POLYNOMIAL, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, 1,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_POLYNOMIAL, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, 1,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_POLYNOMIAL, 1);
+
+    return(testStatus);
+}
+
+
+
+#define LOW_COLS 3
+#define LOW_ROWS 3
+
+/******************************************************************************
+test05(): See test05a() and test05b().
+ 
+We run the tests in test05b() starting with all possible combinations of
+sizes.
+ 
+XXX: Must add M-by-N image size tests.
+ *****************************************************************************/
+int test05()
+{
+    int testStatus = 0;
+    //    testStatus = test05a(NUM_COLS, NUM_ROWS, NUM_COLS, NUM_ROWS);
+
+    //    testStatus|= test05b(LOW_COLS, LOW_ROWS, LOW_COLS, LOW_ROWS);
+    //    testStatus|= test05b(LOW_COLS, LOW_ROWS, LOW_COLS, NUM_ROWS);
+    //    testStatus|= test05b(LOW_COLS, LOW_ROWS, NUM_COLS, LOW_ROWS);
+    //    testStatus|= test05b(LOW_COLS, LOW_ROWS, NUM_COLS, NUM_ROWS);
+
+    //    testStatus|= test05b(LOW_COLS, NUM_ROWS, LOW_COLS, LOW_ROWS);
+    //    testStatus|= test05b(LOW_COLS, NUM_ROWS, LOW_COLS, NUM_ROWS);
+    //    testStatus|= test05b(LOW_COLS, NUM_ROWS, NUM_COLS, LOW_ROWS);
+    //    testStatus|= test05b(LOW_COLS, NUM_ROWS, NUM_COLS, NUM_ROWS);
+
+    //    testStatus|= test05b(NUM_COLS, LOW_ROWS, LOW_COLS, LOW_ROWS);
+    //    testStatus|= test05b(NUM_COLS, LOW_ROWS, LOW_COLS, NUM_ROWS);
+    //    testStatus|= test05b(NUM_COLS, LOW_ROWS, NUM_COLS, LOW_ROWS);
+    //    testStatus|= test05b(NUM_COLS, LOW_ROWS, NUM_COLS, NUM_ROWS);
+
+    //    testStatus|= test05b(NUM_COLS, NUM_ROWS, LOW_COLS, LOW_ROWS);
+    //    testStatus|= test05b(NUM_COLS, NUM_ROWS, LOW_COLS, NUM_ROWS);
+    //    testStatus|= test05b(NUM_COLS, NUM_ROWS, NUM_COLS, LOW_ROWS);
+    testStatus|= test05b(NUM_COLS, NUM_ROWS, NUM_COLS, NUM_ROWS);
+
+
+    return(testStatus);
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tst_pmSubtractSky.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tst_pmSubtractSky.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/detrend/tst_pmSubtractSky.c	(revision 22157)
@@ -0,0 +1,375 @@
+/** @file tst_pmSubtractSky.c
+ *
+ *  @brief Contains the tests for pmSubtractSky.c:
+ *
+ * test00: This code will test the pmSubtractSky routine.
+ *
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-05-25 22:02:23 $
+ *
+ *  XXX: I added the CELL.TRIMSEC region code but there are not tests for it.
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+#include "psTest.h"
+#include "pslib.h"
+#include "pmSubtractSky.h"
+#define NUM_ROWS 512
+#define NUM_COLS 512
+#define POLY_X_ORDER 3
+#define POLY_Y_ORDER 3
+#define ERROR_TOLERANCE 1.0
+#define OBJECT_INTENSITY 2000.0
+static int test00(void);
+static int test01(void);
+testDescription tests[] = {
+                              {test00, 000, "pmSubtractSky", 0, false},
+                              {test01, 000, "pmSubtractSky: warning, error messages", 0, false},
+                              {NULL}
+                          };
+
+float func(int i, int j)
+{
+    return((float) (i + j));
+}
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    return !runTestSuite(stderr, "Test Point Driver", tests, argc, argv);
+    //    test00();
+}
+
+/******************************************************************************
+ *****************************************************************************/
+int doSubtractSkySimple(int numCols, int numRows, int binFactor)
+{
+    int i;
+    int j;
+    int testStatus = 0;
+    psImage *tmpImageF32 = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    //    pmReadout *myReadout = pmReadoutAlloc(numCols, numRows, tmpImageF32);
+    pmReadout *myReadout = pmReadoutAlloc(NULL);
+    myReadout->image = tmpImageF32;
+    psStats *myStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+    psPolynomial2D *myPoly = psPolynomial2DAlloc(PS_POLYNOMIAL_ORD, POLY_X_ORDER, POLY_Y_ORDER);
+
+    printPositiveTestHeader(stdout, "pmSubtractSky", "doSubtractSkySimple");
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            myReadout->image->data.F32[i][j] = func(i, j);
+        }
+    }
+
+    myReadout = pmSubtractSky(myReadout, (void *) myPoly, PM_FIT_POLYNOMIAL,
+                              binFactor, myStats, 10.0);
+    if (myReadout == NULL) {
+        printf("TEST ERROR: pmSubtractSky() returned NULL.\n");
+        testStatus = 1;
+    } else {
+        for (i=0;i<numRows;i++) {
+            for (j=0;j<numCols;j++) {
+                if (ERROR_TOLERANCE < fabs(myReadout->image->data.F32[i][j])) {
+                    printf("TEST ERROR: image[%d][%d] is %f, should be 0.0\n", i, j, myReadout->image->data.F32[i][j]);
+                    testStatus = 1;
+                }
+            }
+        }
+    }
+    psFree(myReadout);
+    psFree(myStats);
+    psFree(myPoly);
+    printFooter(stdout, "pmSubtractSky", "doSubtractSkySimple", true);
+    return(testStatus);
+}
+
+/******************************************************************************
+ *****************************************************************************/
+int doSubtractSkyWithObjects(int numCols, int numRows, int binFactor)
+{
+    int i;
+    int j;
+    int testStatus = 0;
+    psImage *tmpImageF32 = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    //    pmReadout *myReadout = pmReadoutAlloc(numCols, numRows, tmpImageF32);
+    pmReadout *myReadout = pmReadoutAlloc(NULL);
+    myReadout->image = tmpImageF32;
+    psStats *myStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+    psPolynomial2D *myPoly = psPolynomial2DAlloc(PS_POLYNOMIAL_ORD, POLY_X_ORDER, POLY_Y_ORDER);
+    psImage *trueImage = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    psF32 errorTolerance = ERROR_TOLERANCE * ((psF32) binFactor);
+
+    printPositiveTestHeader(stdout, "pmSubtractSky", "doSubtractSkyWithObjects");
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            myReadout->image->data.F32[i][j] = func(i, j);
+        }
+    }
+    // We insert a few bright spots in the image.
+    myReadout->image->data.F32[NUM_ROWS/4][NUM_COLS/4]+= OBJECT_INTENSITY;
+    myReadout->image->data.F32[NUM_ROWS/4][3*NUM_COLS/4]+= OBJECT_INTENSITY;
+    myReadout->image->data.F32[3*NUM_ROWS/4][NUM_COLS/4]+= OBJECT_INTENSITY;
+    myReadout->image->data.F32[3*NUM_ROWS/4][3*NUM_COLS/4]+= OBJECT_INTENSITY;
+    PS_IMAGE_SET_F32(trueImage, 0.0);
+    trueImage->data.F32[NUM_ROWS/4][NUM_COLS/4]+= OBJECT_INTENSITY;
+    trueImage->data.F32[NUM_ROWS/4][3*NUM_COLS/4]+= OBJECT_INTENSITY;
+    trueImage->data.F32[3*NUM_ROWS/4][NUM_COLS/4]+= OBJECT_INTENSITY;
+    trueImage->data.F32[3*NUM_ROWS/4][3*NUM_COLS/4]+= OBJECT_INTENSITY;
+
+    myReadout = pmSubtractSky(myReadout, (void *) myPoly, PM_FIT_POLYNOMIAL,
+                              binFactor, myStats, 2.0);
+    if (myReadout == NULL) {
+        printf("TEST ERROR: pmSubtractSky() returned NULL.\n");
+        testStatus = 1;
+    } else {
+        for (i=0;i<numRows;i++) {
+            for (j=0;j<numCols;j++) {
+                if (errorTolerance < fabs(myReadout->image->data.F32[i][j] - trueImage->data.F32[i][j])) {
+                    printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j,
+                           myReadout->image->data.F32[i][j], trueImage->data.F32[i][j]);
+                    testStatus = 1;
+                }
+            }
+        }
+    }
+    psFree(myReadout);
+    psFree(myStats);
+    psFree(myPoly);
+    psFree(trueImage);
+    printFooter(stdout, "pmSubtractSky", "doSubtractSkyWithObjects", true);
+    return(testStatus);
+}
+
+
+int test00( void )
+{
+    int testStatus = 0;
+
+    psTraceSetLevel(".", 0);
+
+    // Bin Factor == 1
+    printf("doSubtractSkySimple(1, 1, 1)\n");
+    testStatus |= doSubtractSkySimple(1, 1, 1);
+    printf("doSubtractSkySimple(NUM_COLS, 1, 1)\n");
+    testStatus |= doSubtractSkySimple(NUM_COLS, 1, 1);
+
+    printf("doSubtractSkySimple(1, NUM_ROWS, 1)\n");
+    testStatus |= doSubtractSkySimple(1, NUM_ROWS, 1);
+    printf("doSubtractSkySimple(NUM_COLS, NUM_ROWS, 1)\n");
+    testStatus |= doSubtractSkySimple(NUM_COLS, NUM_ROWS, 1);
+
+    // Bin Factor == 2
+    printf("doSubtractSkySimple(1, 1, 2)\n");
+    testStatus |= doSubtractSkySimple(1, 1, 2);
+    printf("doSubtractSkySimple(NUM_COLS, 1, 2)\n");
+    testStatus |= doSubtractSkySimple(NUM_COLS, 1, 2);
+    printf("doSubtractSkySimple(1, NUM_ROWS, 2)\n");
+    testStatus |= doSubtractSkySimple(1, NUM_ROWS, 2);
+    printf("doSubtractSkySimple(NUM_COLS, NUM_ROWS, 2)\n");
+    testStatus |= doSubtractSkySimple(NUM_COLS, NUM_ROWS, 2);
+
+    printf("doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 1)\n");
+    testStatus |= doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 1);
+    printf("doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 2)\n");
+    testStatus |= doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 2);
+    printf("doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 4)\n");
+    testStatus |= doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 8);
+
+    printf("doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 8)\n");
+    testStatus |= doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 8);
+    printf("doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 16)\n");
+    testStatus |= doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 16);
+    printf("doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 32)\n");
+    testStatus |= doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 32);
+    printf("doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 64)\n");
+    testStatus |= doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 64);
+    printf("doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 128)\n");
+    testStatus |= doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 128);
+    printf("doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 256)\n");
+    testStatus |= doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 256);
+
+    return(testStatus);
+}
+
+#define NUM_ROWS_SMALL 16
+#define NUM_COLS_SMALL 16
+int test01( void )
+{
+    int testStatus = 0;
+    psS32 i;
+    psS32 j;
+    psImage *tmpImageF32 = psImageAlloc(NUM_COLS_SMALL, NUM_ROWS_SMALL, PS_TYPE_F32);
+    psImage *tmpImageF64 = psImageAlloc(NUM_COLS_SMALL, NUM_ROWS_SMALL, PS_TYPE_F64);
+    //    pmReadout *myReadout = pmReadoutAlloc(NUM_COLS_SMALL, NUM_ROWS_SMALL, tmpImageF32);
+    pmReadout *myReadout = pmReadoutAlloc(NULL);
+    myReadout->image = tmpImageF32;
+    pmReadout *rc = NULL;
+    psStats *myStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+    psPolynomial2D *myPoly = psPolynomial2DAlloc(PS_POLYNOMIAL_ORD, POLY_X_ORDER, POLY_Y_ORDER);
+
+    printPositiveTestHeader(stdout, "pmSubtractSky", "Testing bad input parameter conditions.");
+    for (i=0;i<NUM_ROWS_SMALL;i++) {
+        for (j=0;j<NUM_COLS_SMALL;j++) {
+            myReadout->image->data.F32[i][j] = func(i, j);
+        }
+    }
+
+    printf("----------------------------------------------------------------\n");
+    printf("Calling pmSubtractSky() with NULL pmReadout.  Should error.\n\n");
+    rc = pmSubtractSky(NULL, (void *) myPoly, PM_FIT_POLYNOMIAL, 1, myStats, 2.0);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmSubtractSky() returned a non-NULL pmReadout\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------\n");
+    printf("Calling pmSubtractSky() with NULL pmReadout->image.  Should error.\n\n");
+    myReadout->image = NULL;
+    rc = pmSubtractSky(myReadout, (void *) myPoly, PM_FIT_POLYNOMIAL, 1, myStats, 2.0);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmSubtractSky() returned a non-NULL pmReadout\n");
+        testStatus = false;
+    }
+    myReadout->image = tmpImageF32;
+
+    printf("----------------------------------------------------------------\n");
+    printf("Calling pmSubtractSky() with PS_TYPE_F64 pmReadout->image.  Should error.\n\n");
+    myReadout->image = tmpImageF64;
+    rc = pmSubtractSky(myReadout, (void *) myPoly, PM_FIT_POLYNOMIAL, 1, myStats, 2.0);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmSubtractSky() returned a non-NULL pmReadout\n");
+        testStatus = false;
+    }
+    myReadout->image = tmpImageF32;
+
+    printf("----------------------------------------------------------------\n");
+    printf("Calling pmSubtractSky() with NULL fitSpec.  Should return image, no ERROR, no WARNING.\n\n");
+    rc = pmSubtractSky(myReadout, NULL, PM_FIT_POLYNOMIAL, 1, myStats, 2.0);
+    if (rc != myReadout) {
+        printf("TEST ERROR: pmSubtractSky() returned something other than pmReadout\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------\n");
+    printf("Calling pmSubtractSky() with PM_FIT_NONE fit.  Should return image, no ERROR, no WARNING.\n\n");
+    rc = pmSubtractSky(myReadout, (void *) myPoly, PM_FIT_NONE, 1, myStats, 2.0);
+    if (rc != myReadout) {
+        printf("TEST ERROR: pmSubtractSky() returned something other than pmReadout\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------\n");
+    printf("Calling pmSubtractSky() with PM_FIT_SPLINE fit.  Should return image, no ERROR, no WARNING.\n\n");
+    rc = pmSubtractSky(myReadout, (void *) myPoly, PM_FIT_SPLINE, 1, myStats, 2.0);
+    if (rc != myReadout) {
+        printf("TEST ERROR: pmSubtractSky() returned something other than pmReadout\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------\n");
+    printf("Calling pmSubtractSky() with NULL myStats.  Should fit entire image, generate WARNING.\n\n");
+    rc = pmSubtractSky(myReadout, (void *) myPoly, PM_FIT_POLYNOMIAL, 1, NULL, 2.0);
+    for (i=0;i<NUM_ROWS_SMALL;i++) {
+        for (j=0;j<NUM_COLS_SMALL;j++) {
+            if (ERROR_TOLERANCE < fabs(rc->image->data.F32[i][j])) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be 0.0\n", i, j,
+                       rc->image->data.F32[i][j]);
+                testStatus = false;
+            }
+        }
+    }
+
+    printf("----------------------------------------------------------------\n");
+    psU64 oldOptions = myStats->options;
+    myStats->options = 0;
+    printf("Calling pmSubtractSky() with no myStats->options specified.  Should fit entire image, generate WARNING.\n\n");
+    rc = pmSubtractSky(myReadout, (void *) myPoly, PM_FIT_POLYNOMIAL, 1, myStats, 2.0);
+    for (i=0;i<NUM_ROWS_SMALL;i++) {
+        for (j=0;j<NUM_COLS_SMALL;j++) {
+            if (ERROR_TOLERANCE < fabs(rc->image->data.F32[i][j])) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be 0.0\n", i, j,
+                       rc->image->data.F32[i][j]);
+                testStatus = false;
+            }
+        }
+    }
+    myStats->options = oldOptions;
+
+    printf("----------------------------------------------------------------\n");
+    oldOptions = myStats->options;
+    myStats->options = PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_MEDIAN;
+    printf("Calling pmSubtractSky() with multiple myStats->options specified.  Should fit entire image, generate WARNING.\n\n");
+    rc = pmSubtractSky(myReadout, (void *) myPoly, PM_FIT_POLYNOMIAL, 1, myStats, 2.0);
+    for (i=0;i<NUM_ROWS_SMALL;i++) {
+        for (j=0;j<NUM_COLS_SMALL;j++) {
+            if (ERROR_TOLERANCE < fabs(rc->image->data.F32[i][j])) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be 0.0\n", i, j,
+                       rc->image->data.F32[i][j]);
+                testStatus = false;
+            }
+        }
+    }
+    myStats->options = oldOptions;
+
+    printf("----------------------------------------------------------------\n");
+    printf("Calling pmSubtractSky() with 0 binFactor.  Should fit entire image, generate WARNING.\n\n");
+    rc = pmSubtractSky(myReadout, (void *) myPoly, PM_FIT_POLYNOMIAL, 0, myStats, 2.0);
+    for (i=0;i<NUM_ROWS_SMALL;i++) {
+        for (j=0;j<NUM_COLS_SMALL;j++) {
+            if (ERROR_TOLERANCE < fabs(rc->image->data.F32[i][j])) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be 0.0\n", i, j,
+                       rc->image->data.F32[i][j]);
+                testStatus = false;
+            }
+        }
+    }
+
+    printf("----------------------------------------------------------------\n");
+    printf("Calling pmSubtractSky() with -1 binFactor.  Should fit entire image, generate WARNING.\n\n");
+    rc = pmSubtractSky(myReadout, (void *) myPoly, PM_FIT_POLYNOMIAL, -1, myStats, 2.0);
+    for (i=0;i<NUM_ROWS_SMALL;i++) {
+        for (j=0;j<NUM_COLS_SMALL;j++) {
+            if (ERROR_TOLERANCE < fabs(rc->image->data.F32[i][j])) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be 0.0\n", i, j,
+                       rc->image->data.F32[i][j]);
+                testStatus = false;
+            }
+        }
+    }
+
+    printf("----------------------------------------------------------------\n");
+    printf("Calling pmSubtractSky() with -1.0 clipSD.  Should fit entire image, generate WARNING.\n\n");
+    rc = pmSubtractSky(myReadout, (void *) myPoly, PM_FIT_POLYNOMIAL, 1, myStats, -1.0);
+    for (i=0;i<NUM_ROWS_SMALL;i++) {
+        for (j=0;j<NUM_COLS_SMALL;j++) {
+            if (ERROR_TOLERANCE < fabs(rc->image->data.F32[i][j])) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be 0.0\n", i, j,
+                       rc->image->data.F32[i][j]);
+                testStatus = false;
+            }
+        }
+    }
+
+    printf("----------------------------------------------------------------\n");
+    printf("Calling pmSubtractSky() with bogus psFit.  Should generate Error.\n\n");
+    rc = pmSubtractSky(myReadout, (void *) myPoly, 54321, 1, myStats, -1.0);
+    if (rc != myReadout) {
+        printf("TEST ERROR: pmSubtractSky() returned something other than pmReadout\n");
+        testStatus = false;
+    }
+
+
+    //    myReadout = pmSubtractSky(myReadout, (void *) myPoly, PM_FIT_POLYNOMIAL,
+    //                              1, myStats, 2.0);
+
+
+    printf("----------------------------------------------------------------\n");
+    psFree(myReadout);
+    psFree(myStats);
+    psFree(myPoly);
+    psFree(tmpImageF64);
+    printFooter(stdout, "pmSubtractSky", "Testing bad input parameter conditions.", true);
+    return(testStatus);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/extras/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/extras/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/extras/.cvsignore	(revision 22157)
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/extras/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/extras/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/extras/Makefile.am	(revision 22157)
@@ -0,0 +1,25 @@
+AM_CPPFLAGS = \
+	$(SRCINC) \
+	-I$(top_srcdir)/test/tap/src \
+	-I$(top_srcdir)/test/pstap/src \
+	$(PSMODULES_CFLAGS)
+
+AM_LDFLAGS = \
+	$(top_builddir)/src/libpsmodules.la  \
+	$(top_builddir)/test/tap/src/libtap.la \
+	$(top_builddir)/test/pstap/src/libpstap.la \
+	$(PSMODULES_LIBS)
+
+TEST_PROGS =
+
+if BUILD_TESTS
+bin_PROGRAMS = $(TEST_PROGS)
+TESTS = $(TEST_PROGS)
+else
+check_PROGRAMS = $(TEST_PROGS)
+endif
+
+CLEANFILES = $(check_DATA) temp/* core core.* *~ *.bb *.bbg *.da gmon.out
+
+test: check
+	$(top_srcdir)/test/test.pl
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/imcombine/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/imcombine/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/imcombine/.cvsignore	(revision 22157)
@@ -0,0 +1,8 @@
+.deps
+.libs
+Makefile
+Makefile.in
+temp
+tst_pmImageCombine
+tst_pmReadoutCombine
+tap_pmImageCombine
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/imcombine/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/imcombine/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/imcombine/Makefile.am	(revision 22157)
@@ -0,0 +1,28 @@
+AM_CPPFLAGS = \
+	$(SRCINC) \
+	-I$(top_srcdir)/test/tap/src \
+	-I$(top_srcdir)/test/pstap/src \
+	$(PSMODULES_CFLAGS)
+
+AM_LDFLAGS = \
+	$(top_builddir)/src/libpsmodules.la  \
+	$(top_builddir)/test/tap/src/libtap.la \
+	$(top_builddir)/test/pstap/src/libpstap.la \
+	$(PSMODULES_LIBS)
+
+TEST_PROGS =
+
+if BUILD_TESTS
+bin_PROGRAMS = $(TEST_PROGS)
+TESTS = $(TEST_PROGS)
+else
+check_PROGRAMS = $(TEST_PROGS)
+endif
+
+CLEANFILES = $(check_DATA) temp/* core core.* *~ *.bb *.bbg *.da gmon.out
+
+test: check
+	$(top_srcdir)/test/test.pl
+
+# Removed
+#	tap_pmImageCombine
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/imcombine/tap_pmImageCombine.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/imcombine/tap_pmImageCombine.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/imcombine/tap_pmImageCombine.c	(revision 22157)
@@ -0,0 +1,67 @@
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+
+#define NUM 10
+#define SIZE 100
+
+psArray *generate_images(void)
+{
+    psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, 12345);   // Random Number Generator
+
+    // Generate images
+    psArray *images = psArrayAlloc(NUM);// Array of images
+    for (int i = 0; i < NUM; i++) {
+        psImage *image = psImageAlloc(SIZE, SIZE, PS_TYPE_F32); // Image i
+        for (int y = 0; y < SIZE; y++) {
+            for (int x = 0; x < SIZE; x++) {
+                image->data.F32[y][x] = psRandomGaussian(rng);
+            }
+        }
+        images->data[i] = image;
+    }
+    psFree(rng);
+
+    return images;
+}
+
+int main(int argc, char *argv[])
+{
+    plan_tests(6);
+
+    diag("Image combination tests");
+
+    // Basic combination
+    {
+        psArray *images = generate_images();
+        psImage *combined = pmCombineImages(NULL, NULL, images, NULL, NULL, 0, NULL, 1, 3.0);
+        ok(combined, "Combined image generated");
+        skip_start(!combined, 5, "Combination failed.");
+
+        ok(combined->type.type == PS_TYPE_F32, "Correct type");
+        ok(combined->numCols == SIZE && combined->numRows == SIZE, "Correct size");
+        int discrepant = 0;             // Number of discrepant pixels
+        for (int y = 0; y < SIZE; y++)
+        {
+            for (int x = 0; x < SIZE; x++) {
+                if (fabsf(combined->data.F32[y][x]) > 3.0 / sqrt(NUM)) {
+                    discrepant++;
+                }
+            }
+        }
+        ok(discrepant <= 30, "%d discrepant pixels", discrepant); // Should have 99.7% of 100x100 pixels OK
+
+        psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV);
+        psImageStats(stats, combined, NULL, 0);
+        ok(stats->sampleMean < 3.0 / sqrt(NUM * SIZE * SIZE), "Sample mean: %e", stats->sampleMean);
+        ok(fabs(stats->sampleStdev - 1.0 / sqrt(NUM)) < 1.0e-3, "Sample stdev: %e",
+           stats->sampleStdev);
+        psFree(stats);
+
+        skip_end();
+        psFree(combined);
+        psFree(images);
+    }
+
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/imcombine/tst_pmImageCombine.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/imcombine/tst_pmImageCombine.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/imcombine/tst_pmImageCombine.c	(revision 22157)
@@ -0,0 +1,365 @@
+/** @file tst_pmImageCombine.c
+ *
+ *  @brief Contains the tests for pmImageCombine.c:
+ *
+ *  test00: This code will test the various functions in the pmImageCombine.c file.
+ *
+ *  @author GLG, MHPCC
+ *
+ *  XXX: Must verify the results internally.  Don't use stdout file.
+ *  XXX: Must test masks with pmRejectPixels()
+ *
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-03-04 01:01:34 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+#include "psTest.h"
+#include "pslib.h"
+#include "pmImageCombine.h"
+static int test00(void);
+testDescription tests[] = {
+                              {test00, 000, "pmCombineImages()", true, false},
+                              {NULL}
+                          };
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    return !runTestSuite(stderr, "Test Point Driver", tests, argc, argv);
+}
+
+psF32 genRanFloat(psF32 low,
+                  psF32 high)
+{
+    psF32 ran1 = (((psF32) (random() % 10000)) / 10000.0);
+    return(low + (ran1 * (high - low)));
+}
+
+psF32 genRanInt(psS32 low,
+                psS32 high)
+{
+    psF32 ran1 = (((psF32) (random() % 10000)) / 10000.0);
+    return((psS32) (low + (ran1 * (high - low))));
+}
+
+#define TST00_EXPANSION_FACTOR_X 1.0
+#define TST00_EXPANSION_FACTOR_Y 1.0
+#define TST00_OFFSET_X 0.0
+#define TST00_OFFSET_Y 0.0
+#define TST00_NUM_PIXELS 10
+#define TST00_MASK_VALUE 1
+#define TST00_NUM_ITERATIONS 4
+#define TST00_SIGMA_CLIP 1.0
+#define TST00_REJECTION_THRESHOLD 0.01
+#define TST00_GRADIENT_LIMIT 10.0
+/*******************************************************************************
+NOTE: This function returns FALSE if there were no errors.
+ ******************************************************************************/
+psBool testCombineImages(psS32 numRows,
+                         psS32 numCols,
+                         psS32 numImages)
+{
+    printf("Testing pmCombineImages(%d, %d, %d)\n", numRows, numCols, numImages);
+    bool testStatus = false;
+
+    psArray *images = psArrayAlloc(numImages);
+    psArray *errors = psArrayAlloc(numImages);
+    psArray *masks = psArrayAlloc(numImages);
+    images->n = images->nalloc;
+    errors->n = errors->nalloc;
+    masks->n = masks->nalloc;
+    for (psS32 i = 0 ; i < numImages ; i++) {
+        images->data[i] = (psPtr *) psImageAlloc(numCols, numRows, PS_TYPE_F32);
+        errors->data[i] = (psPtr *) psImageAlloc(numCols, numRows, PS_TYPE_F32);
+        masks->data[i] = (psPtr *) psImageAlloc(numCols, numRows, PS_TYPE_U8);
+        psImage *image = (psImage *) images->data[i];
+        psImage *error = (psImage *) errors->data[i];
+        psImage *mask = (psImage *) masks->data[i];
+        PS_IMAGE_SET_F32(image, 0.0);
+        PS_IMAGE_SET_F32(error, 1.0);
+        PS_IMAGE_SET_U8(mask, 0);
+
+        for (psS32 row = 0 ; row < numRows ; row++) {
+            for (psS32 col = 0 ; col < numCols ; col++) {
+                // Scale row/col to [-1.0:1.0]
+                psF32 rowScaled = ((psF32) (row - (numRows/2))) / ((psF32) (numRows/2));
+                psF32 colScaled = ((psF32) (col - (numCols/2))) / ((psF32) (numCols/2));
+                image->data.F32[row][col] = PS_SQR((2.0 - rowScaled) + (2.0 - colScaled)) + genRanFloat(0.0, 0.5);
+            }
+        }
+    }
+
+    //
+    // Same as above except the numImages is wrong
+    //
+    psArray *imagesLong = psArrayAlloc(numImages+1);
+    psArray *errorsLong = psArrayAlloc(numImages+1);
+    psArray *masksLong = psArrayAlloc(numImages+1);
+    imagesLong->n = imagesLong->nalloc;
+    errorsLong->n = errorsLong->nalloc;
+    masksLong->n = masksLong->nalloc;
+    for (psS32 i = 0 ; i < numImages+1 ; i++) {
+        imagesLong->data[i] = (psPtr *) psImageAlloc(numCols, numRows, PS_TYPE_F32);
+        errorsLong->data[i] = (psPtr *) psImageAlloc(numCols, numRows, PS_TYPE_F32);
+        masksLong->data[i] = (psPtr *) psImageAlloc(numCols, numRows, PS_TYPE_U8);
+        psImage *image = (psImage *) imagesLong->data[i];
+        psImage *error = (psImage *) errorsLong->data[i];
+        psImage *mask = (psImage *) masksLong->data[i];
+        PS_IMAGE_SET_F32(image, 0.0);
+        PS_IMAGE_SET_F32(error, 1.0);
+        PS_IMAGE_SET_U8(mask, 0);
+
+        for (psS32 row = 0 ; row < numRows ; row++) {
+            for (psS32 col = 0 ; col < numCols ; col++) {
+                // Scale row/col to [-1.0:1.0]
+                psF32 rowScaled = ((psF32) (row - (numRows/2))) / ((psF32) (numRows/2));
+                psF32 colScaled = ((psF32) (col - (numCols/2))) / ((psF32) (numCols/2));
+                image->data.F32[row][col] = PS_SQR((2.0 - rowScaled) + (2.0 - colScaled)) + genRanFloat(0.0, 0.5);
+            }
+        }
+    }
+
+    //
+    // Same as above except the type is wrong
+    //
+    psArray *imagesBadType = psArrayAlloc(numImages);
+    psArray *errorsBadType = psArrayAlloc(numImages);
+    psArray *masksBadType = psArrayAlloc(numImages);
+    imagesBadType->n = imagesBadType->nalloc;
+    errorsBadType->n = errorsBadType->nalloc;
+    masksBadType->n = masksBadType->nalloc;
+    for (psS32 i = 0 ; i < numImages ; i++) {
+        imagesBadType->data[i] = (psPtr *) psImageAlloc(numCols, numRows, PS_TYPE_F64);
+        errorsBadType->data[i] = (psPtr *) psImageAlloc(numCols, numRows, PS_TYPE_F64);
+        masksBadType->data[i] = (psPtr *) psImageAlloc(numCols, numRows, PS_TYPE_S8);
+        psImage *image = (psImage *) imagesBadType->data[i];
+        psImage *error = (psImage *) errorsBadType->data[i];
+        psImage *mask = (psImage *) masksBadType    ->data[i];
+        PS_IMAGE_SET_F32(image, 0.0);
+        PS_IMAGE_SET_F32(error, 1.0);
+        PS_IMAGE_SET_U8(mask, 0);
+
+        for (psS32 row = 0 ; row < numRows ; row++) {
+            for (psS32 col = 0 ; col < numCols ; col++) {
+                // Scale row/col to [-1.0:1.0]
+                psF32 rowScaled = ((psF32) (row - (numRows/2))) / ((psF32) (numRows/2));
+                psF32 colScaled = ((psF32) (col - (numCols/2))) / ((psF32) (numCols/2));
+                image->data.F32[row][col] = PS_SQR((2.0 - rowScaled) + (2.0 - colScaled)) + genRanFloat(0.0, 0.5);
+            }
+        }
+    }
+
+    psPixels *pixels = psPixelsAlloc(TST00_NUM_PIXELS);
+    pixels->n = pixels->nalloc;
+    for (psS32 p = 0 ; p < TST00_NUM_PIXELS ; p++) {
+        psS32 col =  genRanInt(0, numCols);
+        psS32 row =  genRanInt(0, numRows);
+        pixels->data[p].x = (psF32) col;
+        pixels->data[p].y = (psF32) row;
+        psS32 im = genRanInt(0, numImages-1);
+        psImage *image = (psImage *) images->data[im];
+        image->data.F32[row][col] += 100.0;
+        printf("Generating a bad pixel in image (%d) at (%d, %d)\n", im, row, col);
+    }
+
+    psArray *questionablePixels = NULL;
+    psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+
+    //-------------------------------------------------------------------------
+    printf("Calling with a NULL images.  Should generate error, return NULL.\n");
+    psImage *outImg = pmCombineImages(NULL, &questionablePixels, NULL, errors,
+                                      masks, TST00_MASK_VALUE, pixels, TST00_NUM_ITERATIONS,
+                                      TST00_SIGMA_CLIP, stats);
+    if (outImg != NULL) {
+        printf("TEST ERROR: pmCombineImages() returned a non-NULL psImage.\n");
+        psFree(outImg);
+        testStatus = true;
+    }
+
+    //-------------------------------------------------------------------------
+    printf("Calling with a long images.  Should generate error, return NULL.\n");
+    outImg = pmCombineImages(NULL, &questionablePixels, imagesLong, errors,
+                             masks, TST00_MASK_VALUE, pixels, TST00_NUM_ITERATIONS,
+                             TST00_SIGMA_CLIP, stats);
+    if (outImg != NULL) {
+        printf("TEST ERROR: pmCombineImages() returned a non-NULL psImage.\n");
+        psFree(outImg);
+        testStatus = true;
+    }
+
+    //-------------------------------------------------------------------------
+    printf("Calling with a bad type images.  Should generate error, return NULL.\n");
+    outImg = pmCombineImages(NULL, &questionablePixels, imagesBadType, errors,
+                             masks, TST00_MASK_VALUE, pixels, TST00_NUM_ITERATIONS,
+                             TST00_SIGMA_CLIP, stats);
+    if (outImg != NULL) {
+        printf("TEST ERROR: pmCombineImages() returned a non-NULL psImage.\n");
+        psFree(outImg);
+        testStatus = true;
+    }
+
+    //-------------------------------------------------------------------------
+    printf("Calling with a long errors.  Should generate error, return NULL.\n");
+    outImg = pmCombineImages(NULL, &questionablePixels, images, errorsLong,
+                             masks, TST00_MASK_VALUE, pixels, TST00_NUM_ITERATIONS,
+                             TST00_SIGMA_CLIP, stats);
+    if (outImg != NULL) {
+        printf("TEST ERROR: pmCombineImages() returned a non-NULL psImage.\n");
+        psFree(outImg);
+        testStatus = true;
+    }
+
+    //-------------------------------------------------------------------------
+    printf("Calling with a bad type errors.  Should generate error, return NULL.\n");
+    outImg = pmCombineImages(NULL, &questionablePixels, images, errorsBadType,
+                             masks, TST00_MASK_VALUE, pixels, TST00_NUM_ITERATIONS,
+                             TST00_SIGMA_CLIP, stats);
+    if (outImg != NULL) {
+        printf("TEST ERROR: pmCombineImages() returned a non-NULL psImage.\n");
+        psFree(outImg);
+        testStatus = true;
+    }
+
+    //-------------------------------------------------------------------------
+    printf("Calling with a long masks.  Should generate error, return NULL.\n");
+    outImg = pmCombineImages(NULL, &questionablePixels, images, errors,
+                             masksLong, TST00_MASK_VALUE, pixels, TST00_NUM_ITERATIONS,
+                             TST00_SIGMA_CLIP, stats);
+    if (outImg != NULL) {
+        printf("TEST ERROR: pmCombineImages() returned a non-NULL psImage.\n");
+        psFree(outImg);
+        testStatus = true;
+    }
+
+    //-------------------------------------------------------------------------
+    printf("Calling with a bad type masks.  Should generate error, return NULL.\n");
+    outImg = pmCombineImages(NULL, &questionablePixels, images, errors,
+                             masksBadType, TST00_MASK_VALUE, pixels, TST00_NUM_ITERATIONS,
+                             TST00_SIGMA_CLIP, stats);
+    if (outImg != NULL) {
+        printf("TEST ERROR: pmCombineImages() returned a non-NULL psImage.\n");
+        psFree(outImg);
+        testStatus = true;
+    }
+
+    //-------------------------------------------------------------------------
+    printf("Calling with a NULL stats.  Should generate error, return NULL.\n");
+    outImg = pmCombineImages(NULL, &questionablePixels, images, errors,
+                             masks, TST00_MASK_VALUE, pixels, TST00_NUM_ITERATIONS,
+                             TST00_SIGMA_CLIP, NULL);
+    if (outImg != NULL) {
+        printf("TEST ERROR: pmCombineImages() returned a non-NULL psImage.\n");
+        psFree(outImg);
+        testStatus = true;
+    }
+
+    //-------------------------------------------------------------------------
+    printf("Calling with acceptable data.  Should generate a psImage.\n");
+    outImg = pmCombineImages(NULL, &questionablePixels, images, errors,
+                             masks, TST00_MASK_VALUE, pixels, TST00_NUM_ITERATIONS,
+                             TST00_SIGMA_CLIP, stats);
+    if (outImg == NULL) {
+        printf("TEST ERROR: pmCombineImages() returned a NULL psImage.\n");
+        testStatus = true;
+    }
+    if (0) {
+        for (psS32 p = 0 ; p < TST00_NUM_PIXELS ; p++) {
+            psS32 col = (psS32) (pixels->data[p]).x;
+            psS32 row = (psS32) (pixels->data[p]).y;
+            printf("------------------------------------------\n");
+            printf("Pixel (%d, %d) in combined image is %f\n", row, col, outImg->data.F32[row][col]);
+            for (psS32 i = 0 ; i < numImages ; i++) {
+                psImage *image = (psImage *) images->data[i];
+                printf("Pixel (after combine) (%d, %d) in image (%d) is %f\n", row, col, i, image->data.F32[row][col]);
+            }
+        }
+    }
+
+    if (questionablePixels->n != numImages) {
+        printf("TEST ERROR: pmCombineImages(): questionablePixels->n was %ld, should have been %d\n",
+               questionablePixels->n, numImages);
+        testStatus = true;
+    } else {
+        // XXX: We should internally verify this with the pixels list, not merely use the stdout.
+        for (psS32 i = 0 ; i < questionablePixels->n ; i++) {
+            psPixels *myPixels = (psPixels *) questionablePixels->data[i];
+            for (psS32 p = 0 ; p < myPixels->n ; p++) {
+                printf("Image %d, questionable pixel %d is (%f %f)\n",
+                       i, p, myPixels->data[p].y, myPixels->data[p].x);
+            }
+        }
+    }
+
+    psArray *expandTransforms = psArrayAlloc(numImages);
+    psArray *contractTransforms = psArrayAlloc(numImages);
+    expandTransforms->n = expandTransforms->nalloc;
+    contractTransforms->n = contractTransforms->nalloc;
+    for (psS32 im = 0 ; im < numImages ; im++) {
+        psPlaneTransform *ptExpand = psPlaneTransformAlloc(2, 2);
+        ptExpand->x->coeff[0][0] = TST00_OFFSET_X;
+        ptExpand->x->coeff[1][0] = TST00_EXPANSION_FACTOR_X;
+        ptExpand->y->coeff[0][0] = TST00_OFFSET_Y;
+        ptExpand->y->coeff[0][1] = TST00_EXPANSION_FACTOR_Y;
+        expandTransforms->data[im] = (psPtr *) ptExpand;
+
+        psPlaneTransform *ptContract = psPlaneTransformAlloc(2, 2);
+        ptContract->x->coeff[0][0] = -TST00_OFFSET_X;
+        ptContract->x->coeff[1][0] = 1.0 / TST00_EXPANSION_FACTOR_X;
+        ptContract->y->coeff[0][0] = -TST00_OFFSET_Y;
+        ptContract->y->coeff[0][1] = 1.0 / TST00_EXPANSION_FACTOR_Y;
+        contractTransforms->data[im] = (psPtr *) ptContract;
+    }
+
+    //-------------------------------------------------------------------------
+    //
+    // XXX: psRejectPixels() has known bugs.  Specifically, in the psImageTransform() call.
+    // We exclude this from our tests.
+    //
+    printf("\n\n\nCalling pmRejectPixels() with acceptable data.  Should generate a psArray.\n");
+    psArray *pixelRejects = pmRejectPixels(images, NULL, questionablePixels, expandTransforms,
+                                           contractTransforms, TST00_REJECTION_THRESHOLD,
+                                           TST00_GRADIENT_LIMIT);
+    if (pixelRejects == NULL) {
+        printf("TEST ERROR: pmRejectPixels() returned a NULL psArray.\n");
+        testStatus = true;
+    } else {
+        // XXX: We should internally verify this with the pixels list, not merely use the stdout.
+        for (psS32 i = 0 ; i < pixelRejects->n ; i++) {
+            psPixels *myPixels = (psPixels *) pixelRejects->data[i];
+            printf("tst_pmImageCombine.c: Image %d had %ld rejects.\n", i, myPixels->n);
+
+            for (psS32 p = 0 ; p < myPixels->n ; p++) {
+                printf("Image %d, rejected pixel %d is (%f %f)\n", i, p,
+                       myPixels->data[p].y, myPixels->data[p].x);
+            }
+        }
+        psFree(pixelRejects);
+    }
+
+    psFree(images);
+    psFree(errors);
+    psFree(masks);
+    psFree(imagesLong);
+    psFree(errorsLong);
+    psFree(masksLong);
+    psFree(imagesBadType);
+    psFree(errorsBadType);
+    psFree(masksBadType);
+    psFree(pixels);
+    psFree(stats);
+
+    return(testStatus);
+}
+
+/*******************************************************************************
+NOTE: This function returns TRUE if there were no errors.
+ ******************************************************************************/
+int test00( void )
+{
+    bool testStatus = false;
+
+    testStatus|= testCombineImages(10, 10, 5);
+
+    return(!testStatus);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/imcombine/tst_pmImageSubtract.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/imcombine/tst_pmImageSubtract.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/imcombine/tst_pmImageSubtract.c	(revision 22157)
@@ -0,0 +1,847 @@
+/** @file tst_pmImageSubtract.c
+ *
+ *  @brief Contains the tests for pmImageSubtract.c:
+ *
+ *  test00: This code will test the various functions in pmObjects.c
+ *
+ *  @author GLG, MHPCC
+ *
+ *  XXX: Most test simply ensure that the functions can be called with allowable
+ *  data.  More work need to be done to verify the results.
+ *
+ *  @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-05-25 22:02:46 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+#include "psTest.h"
+#include "pslib.h"
+#include "pmImageSubtract.h"
+#define ERROR_TOLERANCE 1.0
+static int test00(void);
+static int test01(void);
+static int test02(void);
+static int test03(void);
+testDescription tests[] = {
+                              {test00, 000, "pmSubtractionKernelsAllocPOIS()", true, false},
+                              {test01, 000, "pmSubtractionKernelsAllocISIS()", true, false},
+                              {test02, 000, "pmSubtractionFindStamps()", true, false},
+                              {test03, 000, "pmSubtractionCalculateEquation()", true, false},
+                              {NULL}
+                          };
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    return !runTestSuite(stderr, "Test Point Driver", tests, argc, argv);
+}
+
+
+/*******************************************************************************
+NOTE: This function returns FALSE if there were no errors.
+ 
+XXX: Call with various unallowable input parameters.
+ 
+XXX: Untested: we don't loop through the (u, v, xOrder, yOrder) psVectors and
+ensure that each value is set correctly.
+ ******************************************************************************/
+psBool testPOISAlloc(psS32 size,
+                     psS32 SpatialOrder)
+{
+    printf("Testing pmSubtractionKernelsAllocPOIS(%d, %d)\n", size, SpatialOrder);
+
+    bool testStatus = false;
+    psS32 nBasisFunctions = (2 * size + 1) * (2 * size + 1) * (SpatialOrder + 1) * (SpatialOrder + 2) / 2;
+
+    psSubtractionKernels *kernels = pmSubtractionKernelsAllocPOIS(size, SpatialOrder);
+    if (kernels == NULL) {
+        printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() returned a NULL psSubtractionKernels.\n");
+        testStatus = true;
+    } else {
+        if (kernels->type != PM_SUBTRACTION_KERNEL_POIS) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated the wrong kernels->type.\n");
+            testStatus = true;
+        }
+
+        if ((kernels->u == NULL) ||
+                (kernels->u->n != nBasisFunctions)) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated a NULL ->u member or\n");
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated a incorrect length ->u member.\n");
+            testStatus = true;
+        }
+
+        if ((kernels->v == NULL) ||
+                (kernels->v->n != nBasisFunctions)) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated a NULL ->v member or\n");
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated a incorrect length ->v member.\n");
+            testStatus = true;
+        }
+
+        if (kernels->sigma != NULL) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated a non-NULL ->sigma member.\n");
+            testStatus = true;
+        }
+
+        if ((kernels->xOrder == NULL) ||
+                (kernels->xOrder->n != nBasisFunctions)) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated a NULL ->xOrder member or\n");
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated a incorrect length ->xOrder member.\n");
+            testStatus = true;
+        }
+
+        if ((kernels->yOrder == NULL) ||
+                (kernels->yOrder->n != nBasisFunctions)) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated a NULL ->yOrder member or\n");
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated a incorrect length ->yOrder member.\n");
+            testStatus = true;
+        }
+
+        if (kernels->subIndex != 0) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated a non-zero ->subIndex member (%d).\n", kernels->subIndex);
+            testStatus = true;
+        }
+
+        psS32 i = kernels->subIndex;
+        if ((kernels->u->data.F32[i] != 0) ||
+                (kernels->v->data.F32[i] != 0)) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS(): the ->subIndex member points to a kernel with (%f, %f) (u, v) basis function.\n",
+                   kernels->u->data.F32[i], kernels->v->data.F32[i]);
+            testStatus = true;
+        }
+
+        if (kernels->preCalc != NULL) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated a non-NULL ->preCalc member.\n");
+            testStatus = true;
+        }
+
+        if (kernels->size != size) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated an incorrect ->size member (%d).\n", kernels->size);
+            testStatus = true;
+        }
+
+        if (kernels->spatialOrder != SpatialOrder) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated an incorrect ->spatialOrder member (%d).\n", kernels->spatialOrder);
+            testStatus = true;
+        }
+    }
+    psFree(kernels);
+    return(testStatus);
+}
+
+/*******************************************************************************
+NOTE: This function returns TRUE if there were no errors.
+ ******************************************************************************/
+int test00( void )
+{
+    bool testStatus = false;
+
+    testStatus|= testPOISAlloc(1, 1);
+    testStatus|= testPOISAlloc(2, 3);
+    testStatus|= testPOISAlloc(3, 4);
+
+    return(!testStatus);
+}
+
+/*******************************************************************************
+NOTE: This function returns FALSE if there were no errors.
+ 
+XXX: Call with various unallowable input parameters.
+ 
+XXX: Untested: we don't loop through the (u, v, xOrder, yOrder) psVectors and
+ensure that each value is set correctly.  We don't ensure that he preCalc
+psImages are set correctly.
+ ******************************************************************************/
+psBool testISISAlloc(psS32 sigmaLength,
+                     psS32 orderLength,
+                     psS32 size,
+                     psS32 SpatialOrder)
+{
+    printf("Testing pmSubtractionKernelsAllocISIS(%d, %d, %d, %d)\n",
+           sigmaLength, orderLength, size, SpatialOrder);
+
+    psVector *sigmas = psVectorAlloc(sigmaLength, PS_TYPE_F32);
+    sigmas->n = sigmas->nalloc;
+    for (psS32 i = 0 ; i < sigmas->n ; i++) {
+        sigmas->data.F32[i] = 1.0 + (psF32) i;
+    }
+    psVector *orders = psVectorAlloc(orderLength, PS_TYPE_S32);
+    orders->n = orders->nalloc;
+    for (psS32 i = 0 ; i < orders->n ; i++) {
+        orders->data.S32[i] = i + 2;
+    }
+
+    bool testStatus = false;
+    psS32 numSigmas = sigmas->n;
+    psS32 nBasisFunctions = 0;
+    for (psS32 s = 0 ; s < numSigmas ; s++) {
+        for (psS32 o = 0 ; o < orders->n ; o++) {
+            nBasisFunctions+= ((orders->data.S32[o] + 1) * (orders->data.S32[o] + 2) / 2);
+        }
+    }
+    nBasisFunctions*= ((SpatialOrder + 1) * (SpatialOrder + 2) / 2);
+
+    psSubtractionKernels *kernels = pmSubtractionKernelsAllocISIS(sigmas, orders, size, SpatialOrder);
+    if (kernels == NULL) {
+        printf("TEST ERROR: pmSubtractionKernelsAllocISIS() returned a NULL psSubtractionKernels.\n");
+        testStatus = true;
+    } else {
+        if (kernels->type != PM_SUBTRACTION_KERNEL_ISIS) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated the wrong kernels->type.\n");
+            testStatus = true;
+        }
+
+        if ((kernels->u == NULL) ||
+                (kernels->u->n != nBasisFunctions)) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated a NULL ->u member or\n");
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated a incorrect length ->u member.\n");
+            testStatus = true;
+        }
+
+        if ((kernels->v == NULL) ||
+                (kernels->v->n != nBasisFunctions)) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated a NULL ->v member or\n");
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated a incorrect length ->v member.\n");
+            testStatus = true;
+        }
+
+        if ((kernels->sigma == NULL) ||
+                (kernels->sigma->n != nBasisFunctions)) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated a NULL ->sigma member or\n");
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated a incorrect length ->sigma member.\n");
+            testStatus = true;
+        }
+
+        if ((kernels->xOrder == NULL) ||
+                (kernels->xOrder->n != nBasisFunctions)) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated a NULL ->xOrder member or\n");
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated a incorrect length ->xOrder member.\n");
+            testStatus = true;
+        }
+
+        if ((kernels->yOrder == NULL) ||
+                (kernels->yOrder->n != nBasisFunctions)) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated a NULL ->yOrder member or\n");
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated a incorrect length ->yOrder member.\n");
+            testStatus = true;
+        }
+
+        if (kernels->subIndex != 0) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated a non-zero ->subIndex member (%d).\n", kernels->subIndex);
+            testStatus = true;
+        }
+
+        //
+        // Ensure that kernels->subIndex points to the correct kernel.
+        //
+        psS32 i = kernels->subIndex;
+        if ((kernels->u->data.F32[i] != 0.0) ||
+                (kernels->v->data.F32[i] != 0.0) ||
+                (kernels->xOrder->data.F32[i] != 0.0) ||
+                (kernels->yOrder->data.F32[i] != 0.0) ||
+                (kernels->sigma->data.F32[i] != sigmas->data.F32[0])) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS(): the ->subIndex member points to the wrong kernel.\n");
+            printf("TEST ERROR: (u, v, xOrder, yOrder, sigma) is (%f, %f, %f, %f, %f).\n",
+                   kernels->u->data.F32[i], kernels->v->data.F32[i],
+                   kernels->xOrder->data.F32[i], kernels->yOrder->data.F32[i],
+                   kernels->sigma->data.F32[i]);
+            testStatus = true;
+        }
+
+        //
+        // Ensure that the preCalc images are allocated correctly.
+        //
+        if ((kernels->preCalc == NULL) ||
+                (kernels->preCalc->n != nBasisFunctions)) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated a NULL ->preCalc member or\n");
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated a incorrect length ->preCalc member.\n");
+            testStatus = true;
+        } else {
+            for (psS32 k = 0 ; k < kernels->u->n ; k++) {
+                psImage *kerImg = (psImage *) kernels->preCalc->data[k];
+                if (kerImg == NULL) {
+                    printf("TEST ERROR: the %d-th kernel preCalc image is NULL.\n", k);
+                    testStatus = true;
+                } else {
+                    if (kerImg->type.type != PS_TYPE_F32) {
+                        printf("TEST ERROR: preCalc image %d had ioncorrect type.\n", k);
+                        testStatus = true;
+                    }
+                    if ((kerImg->numRows != (1 + (2 * size))) ||
+                            (kerImg->numCols != (1 + (2 * size)))) {
+                        printf("TEST ERROR: preCalc image %d had incorrect size (%d, %d).\n", k,
+                               kerImg->numRows, kerImg->numCols);
+                        testStatus = true;
+                    }
+                }
+            }
+        }
+
+        if (kernels->size != size) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated an incorrect ->size member (%d).\n", kernels->size);
+            testStatus = true;
+        }
+
+        if (kernels->spatialOrder != SpatialOrder) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated an incorrect ->spatialOrder member (%d).\n", kernels->spatialOrder);
+            testStatus = true;
+        }
+    }
+    psFree(sigmas);
+
+    psFree(kernels->u);
+    psFree(kernels->v);
+    psFree(kernels->sigma);
+    psFree(kernels->xOrder);
+    psFree(kernels->yOrder);
+    psFree(kernels->preCalc);
+    psFree(kernels);
+    psFree(orders);
+    return(testStatus);
+}
+
+/*******************************************************************************
+NOTE: This function returns TRUE if there were no errors.
+ ******************************************************************************/
+int test01( void )
+{
+    bool testStatus = false;
+
+    /*
+        testStatus|= testISISAlloc(1, 1, 1, 1);
+        testStatus|= testISISAlloc(2, 2, 2, 2);
+        testStatus|= testISISAlloc(2, 3, 4, 5);
+        testStatus|= testISISAlloc(3, 4, 5, 6);
+    */
+
+    return(!testStatus);
+}
+
+
+/*******************************************************************************
+NOTE: This function returns FALSE if there were no errors.
+ 
+XXX: Can we test to ensure that no stamps overlap?
+ 
+XXX: Test stamp alloc/dealloc functions.
+ ******************************************************************************/
+#define TST02_THRESHOLD 3.0
+#define TST02_MASK_VAL 1
+psBool testFindStamps(psS32 numCols,
+                      psS32 numRows,
+                      psS32 xNum,
+                      psS32 yNum,
+                      psS32 border)
+{
+    printf("Testing pmSubtractionFindStamps(%d, %d, %d, %d, %d)\n",
+           numCols, numRows, xNum, yNum, border);
+    bool testStatus = false;
+
+    // Create a test image and set a single pixel in the center of each stamp.
+    psImage *tstImg = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    int numStamps = 0;
+    PS_IMAGE_SET_F32(tstImg, 0.0);
+    for (psS32 j = 0; j < yNum; j++) {
+        for (psS32 i = 0; i < xNum; i++) {
+            psS32 yMin = border + j * (numRows - 2.0 * border) / yNum;
+            psS32 yMax = PS_MIN(numRows-1, (border + (j + 1) * (numRows - 2.0 * border) / yNum) - 1);
+            psS32 xMin = border + i * (numCols - 2.0 * border) / xNum;
+            psS32 xMax = PS_MIN(numCols-1, (border + (i + 1) * (numCols - 2.0 * border) / xNum) - 1);
+
+            tstImg->data.F32[(yMax+yMin)/2][(xMax+xMin)/2] = TST02_THRESHOLD + (psF32) (i + j);
+            numStamps++;
+        }
+    }
+    psImage *tmpMask= psImageAlloc(numCols, numRows, PS_TYPE_U8);
+    PS_IMAGE_SET_U8(tstImg, 0);
+
+    //-------------------------------------------------------------------------
+    printf("Calling with a NULL psImage.  Should generate error, return NULL.\n");
+    psArray *stamps = pmSubtractionFindStamps(NULL, NULL, tmpMask, TST02_MASK_VAL,
+                      TST02_THRESHOLD, xNum, yNum,
+                      border);
+    if (stamps != NULL) {
+        printf("TEST ERROR: pmSubtractionFindStamps returned a non-NULL psArray.\n");
+        psFree(stamps);
+        testStatus = true;
+    }
+
+    //-------------------------------------------------------------------------
+    printf("Calling with a non-positive xNum.  Should generate error, return NULL.\n");
+    stamps = pmSubtractionFindStamps(NULL, tstImg, tmpMask, TST02_MASK_VAL,
+                                     TST02_THRESHOLD, 0, yNum,
+                                     border);
+    if (stamps != NULL) {
+        printf("TEST ERROR: pmSubtractionFindStamps returned a non-NULL psArray.\n");
+        psFree(stamps);
+        testStatus = true;
+    }
+
+    //-------------------------------------------------------------------------
+    printf("Calling with a non-positive yNum.  Should generate error, return NULL.\n");
+    stamps = pmSubtractionFindStamps(NULL, tstImg, tmpMask, TST02_MASK_VAL,
+                                     TST02_THRESHOLD, xNum, 0,
+                                     border);
+    if (stamps != NULL) {
+        printf("TEST ERROR: pmSubtractionFindStamps returned a non-NULL psArray.\n");
+        psFree(stamps);
+        testStatus = true;
+    }
+
+    //-------------------------------------------------------------------------
+    printf("Calling with a non-positive border.  Should generate error, return NULL.\n");
+    stamps = pmSubtractionFindStamps(NULL, tstImg, tmpMask, TST02_MASK_VAL,
+                                     TST02_THRESHOLD, xNum, yNum,
+                                     0);
+    if (stamps != NULL) {
+        printf("TEST ERROR: pmSubtractionFindStamps returned a non-NULL psArray.\n");
+        psFree(stamps);
+        testStatus = true;
+    }
+
+    //-------------------------------------------------------------------------
+    printf("Calling with acceptable input parameters, non-NULL mask.\n");
+    stamps = pmSubtractionFindStamps(NULL, tstImg, tmpMask, TST02_MASK_VAL,
+                                     TST02_THRESHOLD, xNum, yNum,
+                                     border);
+    if (stamps == NULL) {
+        printf("TEST ERROR: pmSubtractionFindStamps returned a non-NULL psArray.\n");
+        testStatus = true;
+    } else {
+        if (stamps->n != numStamps) {
+            printf("TEST ERROR: %ld stamps were found, %d were expected.\n",
+                   stamps->n, numStamps);
+            testStatus = true;
+        }
+        for (psS32 s = 0 ; s < stamps->n ; s++) {
+            pmStamp *stamp = (pmStamp *) stamps->data[s];
+            psS32 row = stamp->y;
+            psS32 col = stamp->x;
+            // printf("Stamp %d at (%d, %d) has a value of %f\n", s, row, col, tstImg->data.F32[row][col]);
+            if (tstImg->data.F32[row][col] < TST02_THRESHOLD) {
+                if (stamp->status != PM_STAMP_NONE) {
+                    printf("TEST ERROR: stamp %d had peak value %f (below theshold) and the status was not set to PM_STAMP_NONE.\n",
+                           s, tstImg->data.F32[row][col]);
+                    testStatus = true;
+                }
+            } else {
+                if (stamp->status != PM_STAMP_RECALC) {
+                    printf("TEST ERROR: stamp %d had peak value %f (above theshold) and the status was not set to PM_STAMP_RECALC.\n",
+                           s, tstImg->data.F32[row][col]);
+                    testStatus = true;
+                }
+            }
+        }
+    }
+    psFree(stamps);
+
+    //-------------------------------------------------------------------------
+    printf("Calling with acceptable input parameters, NULL mask.\n");
+    stamps = pmSubtractionFindStamps(NULL, tstImg, NULL, TST02_MASK_VAL,
+                                     TST02_THRESHOLD, xNum, yNum,
+                                     border);
+    if (stamps == NULL) {
+        printf("TEST ERROR: pmSubtractionFindStamps returned a non-NULL psArray.\n");
+        testStatus = true;
+    } else {
+        if (stamps->n != numStamps) {
+            printf("TEST ERROR: %ld stamps were found, %d were expected.\n",
+                   stamps->n, numStamps);
+            testStatus = true;
+        }
+        for (psS32 s = 0 ; s < stamps->n ; s++) {
+            pmStamp *stamp = (pmStamp *) stamps->data[s];
+            psS32 row = stamp->y;
+            psS32 col = stamp->x;
+            //printf("Stamp %d at (%d, %d) has a value of %f\n", s, row, col, tstImg->data.F32[row][col]);
+            if (tstImg->data.F32[row][col] < TST02_THRESHOLD) {
+                if (stamp->status != PM_STAMP_NONE) {
+                    printf("TEST ERROR: stamp %d had peak value %f (below theshold) and the status was not set to PM_STAMP_NONE.\n",
+                           s, tstImg->data.F32[row][col]);
+                    testStatus = true;
+                }
+            } else {
+                if (stamp->status != PM_STAMP_RECALC) {
+                    printf("TEST ERROR: stamp %d had peak value %f (above theshold) and the status was not set to PM_STAMP_RECALC.\n",
+                           s, tstImg->data.F32[row][col]);
+                    testStatus = true;
+                }
+            }
+        }
+    }
+
+    psFree(tstImg);
+    psFree(tmpMask);
+    psFree(stamps);
+
+    return(testStatus);
+}
+
+
+
+/*******************************************************************************
+NOTE: This function returns TRUE if there were no errors.
+ ******************************************************************************/
+int test02( void )
+{
+    bool testStatus = false;
+
+    testStatus|= testFindStamps(100, 100, 2, 2, 2);
+    testStatus|= testFindStamps(100, 100, 10, 10, 2);
+
+    return(!testStatus);
+}
+
+
+psF32 genRanFloat(psF32 low,
+                  psF32 high)
+{
+    psF32 ran1 = (((psF32) (random() % 10000)) / 10000.0);
+    return(low + (ran1 * (high - low)));
+}
+
+//
+// XXX: POIS kernels are producing NANs if the image size is 20 or less.
+//
+// XXX: ISIS kernels are producing NANS if the TST03_ORDER_LENGTH is 2 or larger.
+//
+#define TST03_THRESHOLD  3.0
+#define TST03_MASK_VAL  1
+#define TST03_KERNEL_SIZE 2
+#define TST03_SPATIAL_ORDER 2
+#define TST03_ORDER_LENGTH 1
+#define TST03_SIGMA_LENGTH 1
+#define TST03_PSF_MAX  10.0
+#define TST03_BG 0.0
+#define TST03_IMAGE_SIZE 25
+#define TST03_NUM_COLS  TST03_IMAGE_SIZE
+#define TST03_NUM_ROWS  TST03_IMAGE_SIZE
+#define TST03_NUM_STAMPS 2
+#define TST03_NUM_STAMPS_COLS TST03_NUM_STAMPS
+#define TST03_NUM_STAMPS_ROWS TST03_NUM_STAMPS
+#define TST03_BORDER  TST03_KERNEL_SIZE
+//#define TST03_FOOTPRINT (((TST03_IMAGE_SIZE - (2 * TST03_BORDER)) / TST03_NUM_STAMPS) - TST03_KERNEL_SIZE)
+#define TST03_FOOTPRINT  4
+#define TST03_PSF_WIDTH  (TST03_FOOTPRINT/2 - 1)
+
+/*******************************************************************************
+This routine generates an object in the center of the stamp defined by the
+(xMin, xMax) and (yMin, yMax) boundary.
+ ******************************************************************************/
+psBool genObject(psImage *tstImg,
+                 psImage *refImg,
+                 psS32 xMin,
+                 psS32 xMax,
+                 psS32 yMin,
+                 psS32 yMax)
+{
+    if (((1 + 2 * TST03_PSF_WIDTH) > (xMax - xMin)) ||
+            ((1 + 2 * TST03_PSF_WIDTH) > (yMax - yMin))) {
+        printf("INCORRECT TEST CONFIGURATION: TST03_PSF_WIDTH is too big.\n");
+        printf("TST03_PSF_WIDTH is %d: (xMin - xMax) is %d\n", TST03_PSF_WIDTH, (xMax - xMin));
+        return(FALSE);
+    }
+    //
+    // This code basically creates a peak at the center of the stamp with
+    // a height of TST03_PSF_MAX
+    //
+    psS32 xCenter = (xMax + xMin) / 2;
+    psS32 yCenter = (yMax + yMin) / 2;
+    psF32 subImageWidth = 1.0 + sqrtf(PS_SQR(((psF32) ((yMax-yMin)/2))) + PS_SQR(((psF32) ((xMax-xMin)/2))));
+    for (psS32 y = yMin ; y <= yMax ; y++) {
+        for (psS32 x = xMin ; x <= xMax ; x++) {
+            psF32 dist = sqrtf(PS_SQR((psF32) (y - yCenter)) + PS_SQR((psF32) (x - xCenter)));
+            psF32 pixel = TST03_PSF_MAX * PS_SQR(((psF32) (subImageWidth - dist)) / ((psF32) subImageWidth));
+            if (pixel < 0.0) {
+                pixel = 0.0;
+            }
+            refImg->data.F32[y][x] = pixel + TST03_BG;
+            tstImg->data.F32[y][x] = pixel + TST03_BG;
+            // Add some noise for the test image.
+            tstImg->data.F32[y][x]+= genRanFloat(0.0, 1.0);
+        }
+    }
+
+    return(TRUE);
+}
+
+/*******************************************************************************
+NOTE: This function returns FALSE if there were no errors.
+ 
+XXX: We should use a larger variety of input parameter configurations.
+ 
+I test the following functions here (since they linearly rely on a set of data
+structures that the previoues ones generate):
+    pmSubtractionCalculateEquation()
+    pmSubtractionSolveEquation()
+    pmSubtractionRejectStamps()
+    pmSubtractionKernelImage()
+ ******************************************************************************/
+psBool testSubCalcEqu(psS32 numCols,
+                      psS32 numRows,
+                      psS32 xNum,
+                      psS32 yNum,
+                      psS32 border,
+                      pmSubtractionKernelsType KernelType)
+{
+    printf("Testing pmSubtractionCalculateEquation(): \n");
+    printf("    image size is (%d, %d)\n", numRows, numCols);
+    printf("    num stamps is (%d, %d).  Border is %d\n", yNum, xNum, border);
+    if (KernelType == PM_SUBTRACTION_KERNEL_POIS) {
+        printf("   kernel type is PM_SUBTRACTION_KERNEL_POIS.\n");
+    } else if (KernelType == PM_SUBTRACTION_KERNEL_ISIS) {
+        printf("   kernel type is PM_SUBTRACTION_KERNEL_ISIS.\n");
+    }
+    bool testStatus = false;
+
+    // Create a test image and set a single pixel in the center of each stamp.
+    psImage *tstImg = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    psImage *refImg = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    psImage *maskImg = psImageAlloc(numCols, numRows, PS_TYPE_U8);
+    PS_IMAGE_SET_F32(tstImg, 0.0);
+    PS_IMAGE_SET_F32(maskImg, 0);
+    for (psS32 j = 0; j < yNum; j++) {
+        for (psS32 i = 0; i < xNum; i++) {
+            psS32 yMin = border + j * (numRows - 2.0 * border) / yNum;
+            psS32 yMax = PS_MIN(numRows-1, (border + (j + 1) * (numRows - 2.0 * border) / yNum) - 1);
+            psS32 xMin = border + i * (numCols - 2.0 * border) / xNum;
+            psS32 xMax = PS_MIN(numCols-1, (border + (i + 1) * (numCols - 2.0 * border) / xNum) - 1);
+
+            genObject(tstImg, refImg, xMin, xMax, yMin, yMax);
+        }
+    }
+    printf("Generating stamps...\n");
+    psArray *stamps = pmSubtractionFindStamps(NULL, tstImg, NULL, TST03_MASK_VAL,
+                      TST03_THRESHOLD, xNum, yNum,
+                      border);
+
+    //
+    // PsVectors sigmas and orders are for ISIS kernels only.
+    //
+    psVector *sigmas = psVectorAlloc(TST03_SIGMA_LENGTH, PS_TYPE_F32);
+    sigmas->n = sigmas->nalloc;
+    for (psS32 i = 0 ; i < sigmas->n ; i++) {
+        sigmas->data.F32[i] = 1.0 + (psF32) i;
+    }
+    psVector *orders = psVectorAlloc(TST03_ORDER_LENGTH, PS_TYPE_S32);
+    orders->n = orders->nalloc;
+    for (psS32 i = 0 ; i < orders->n ; i++) {
+        orders->data.S32[i] = i + 2;
+    }
+
+    //
+    // Create the subtraction kernels
+    //
+    printf("Generating kernel basis functions...\n");
+    psSubtractionKernels *myKernels = NULL;
+    if (KernelType == PM_SUBTRACTION_KERNEL_POIS) {
+        myKernels = pmSubtractionKernelsAllocPOIS(TST03_KERNEL_SIZE, TST03_SPATIAL_ORDER);
+    } else if (KernelType == PM_SUBTRACTION_KERNEL_ISIS) {
+        myKernels = pmSubtractionKernelsAllocISIS(sigmas, orders, TST03_KERNEL_SIZE, TST03_SPATIAL_ORDER);
+    }
+
+    if ((stamps == NULL) ||
+            (myKernels == NULL)) {
+        printf("TEST ERROR: stamps or myKernels is NULL.\n");
+        testStatus = true;
+    } else {
+        //-------------------------------------------------------------------------
+        printf("Calling with a NULL psArray stamps.  Should generate error, return FALSE.\n");
+        psBool rc = pmSubtractionCalculateEquation(NULL, refImg, tstImg, myKernels, TST03_FOOTPRINT);
+        if (rc == TRUE) {
+            printf("TEST ERROR: pmSubtractionCalculateEquation() returned TRUE.\n");
+            testStatus = true;
+        }
+
+        //-------------------------------------------------------------------------
+        printf("Calling with a NULL reference images.  Should generate error, return FALSE.\n");
+        rc = pmSubtractionCalculateEquation(stamps, NULL, tstImg, myKernels, TST03_FOOTPRINT);
+        if (rc == TRUE) {
+            printf("TEST ERROR: pmSubtractionCalculateEquation() returned TRUE.\n");
+            testStatus = true;
+        }
+
+        //-------------------------------------------------------------------------
+        printf("Calling with a NULL input images.  Should generate error, return FALSE.\n");
+        rc = pmSubtractionCalculateEquation(stamps, refImg, NULL, myKernels, TST03_FOOTPRINT);
+        if (rc == TRUE) {
+            printf("TEST ERROR: pmSubtractionCalculateEquation() returned TRUE.\n");
+            testStatus = true;
+        }
+
+        //-------------------------------------------------------------------------
+        printf("Calling with a NULL kernel basis functions.  Should generate error, return FALSE.\n");
+        rc = pmSubtractionCalculateEquation(stamps, refImg, tstImg, NULL, TST03_FOOTPRINT);
+        if (rc == TRUE) {
+            printf("TEST ERROR: pmSubtractionCalculateEquation() returned TRUE.\n");
+            testStatus = true;
+        }
+
+        //-------------------------------------------------------------------------
+        printf("Calling with acceptable input parameters.  Should return TRUE.\n");
+
+        rc = pmSubtractionCalculateEquation(stamps, refImg, tstImg, myKernels, TST03_FOOTPRINT);
+        if (rc != TRUE) {
+            printf("TEST ERROR: pmSubtractionCalculateEquation() returned FALSE.\n");
+            testStatus = true;
+        } else {
+
+            if (0) {
+                for (psS32 s = 0 ; s < stamps->n ; s++) {
+                    printf("********************************* Stamp %d *********************************\n", s);
+                    pmStamp *stamp = (pmStamp *) stamps->data[s];
+                    if (stamp->vector != NULL) {
+                        PS_VECTOR_PRINT_F64(stamp->vector);
+                    }
+                    if (stamp->matrix != NULL) {
+                        printf("Stamp matrix size is (%d, %d)\n", stamp->matrix->numRows, stamp->matrix->numCols);
+                        PS_IMAGE_PRINT_F64(stamp->matrix);
+                    }
+                }
+            }
+
+
+            //-------------------------------------------------------------------------
+            printf("Calling pmSubtractionSolveEquation() with a NULL stamp argument.  Should generate error, return FALSE.\n");
+            psVector *solution = pmSubtractionSolveEquation(NULL, NULL);
+            if (solution != NULL) {
+                printf("TEST ERROR: pmSubtractionSolveEquation() returned non-NULL.\n");
+                testStatus = true;
+            }
+
+            //-------------------------------------------------------------------------
+            printf("Calling pmSubtractionSolveEquation() with acceptable input parameters.  Should return non-NULL.\n");
+            solution = pmSubtractionSolveEquation(NULL, stamps);
+            if (solution == NULL) {
+                printf("TEST ERROR: pmSubtractionSolveEquation() returned NULL.\n");
+                testStatus = true;
+            } else {
+                printf("The solution vector is:\n");
+                for (psS32 i = 0 ; i < solution->n ; i++) {
+                    printf("(%.2f) ", solution->data.F64[i]);
+                }
+                printf("\n");
+
+                //-------------------------------------------------------------------------
+                printf("Calling pmSubtractionRejectStamps() with acceptable input parameters.  Should return TRUE.\n");
+                rc = pmSubtractionRejectStamps(stamps, maskImg, 0xff, TST03_FOOTPRINT, 1.0, refImg,
+                                               tstImg, solution, myKernels);
+                if (rc != TRUE) {
+                    printf("TEST ERROR: pmSubtractionRejectStamps() returned FALSE.\n");
+                    testStatus = true;
+                } else {}
+
+                //-------------------------------------------------------------------------
+                printf("Calling pmSubtractionKernelImage() with NULL solution.  Should generate error, return NULL.\n");
+                psImage *kernelImg = pmSubtractionKernelImage(NULL, NULL, myKernels, 0.0, 0.0);
+                if (kernelImg != NULL) {
+                    printf("TEST ERROR: pmSubtractionKernelImage() returned non-NULL.\n");
+                    testStatus = true;
+                }
+                free(kernelImg);
+
+                //-------------------------------------------------------------------------
+                printf("Calling pmSubtractionKernelImage() with NULL kernels.  Should generate error, return NULL.\n");
+                kernelImg = pmSubtractionKernelImage(NULL, solution, NULL, 0.0, 0.0);
+                if (kernelImg != NULL) {
+                    printf("TEST ERROR: pmSubtractionKernelImage() returned non-NULL.\n");
+                    testStatus = true;
+                }
+                free(kernelImg);
+
+                //-------------------------------------------------------------------------
+                printf("Calling pmSubtractionKernelImage() unallowable x value.  Should generate error, return NULL.\n");
+                kernelImg = pmSubtractionKernelImage(NULL, solution, myKernels, -2.0, 0.0);
+                if (kernelImg != NULL) {
+                    printf("TEST ERROR: pmSubtractionKernelImage() returned non-NULL.\n");
+                    testStatus = true;
+                }
+                free(kernelImg);
+
+                //-------------------------------------------------------------------------
+                printf("Calling pmSubtractionKernelImage() unallowable x value.  Should generate error, return NULL.\n");
+                kernelImg = pmSubtractionKernelImage(NULL, solution, myKernels, 2.0, 0.0);
+                if (kernelImg != NULL) {
+                    printf("TEST ERROR: pmSubtractionKernelImage() returned non-NULL.\n");
+                    testStatus = true;
+                }
+                free(kernelImg);
+
+                //-------------------------------------------------------------------------
+                printf("Calling pmSubtractionKernelImage() unallowable y value.  Should generate error, return NULL.\n");
+                kernelImg = pmSubtractionKernelImage(NULL, solution, myKernels, 0.0, -2.0);
+                if (kernelImg != NULL) {
+                    printf("TEST ERROR: pmSubtractionKernelImage() returned non-NULL.\n");
+                    testStatus = true;
+                }
+                free(kernelImg);
+
+                //-------------------------------------------------------------------------
+                printf("Calling pmSubtractionKernelImage() unallowable y value.  Should generate error, return NULL.\n");
+                kernelImg = pmSubtractionKernelImage(NULL, solution, myKernels, 0.0, 2.0);
+                if (kernelImg != NULL) {
+                    printf("TEST ERROR: pmSubtractionKernelImage() returned non-NULL.\n");
+                    testStatus = true;
+                }
+                free(kernelImg);
+
+                //-------------------------------------------------------------------------
+                printf("Calling pmSubtractionKernelImage() with acceptable input parameters.  Should return a psImage.\n");
+                kernelImg = pmSubtractionKernelImage(NULL, solution, myKernels, 0.5, 0.5);
+                if (kernelImg == NULL) {
+                    printf("TEST ERROR: pmSubtractionKernelImage() returned NULL.\n");
+                    testStatus = true;
+                } else {
+                    for (psS32 row = 0 ; row < kernelImg->numRows; row++) {
+                        for (psS32 col = 0 ; col < kernelImg->numCols; col++) {
+                            printf("%f ", kernelImg->data.F32[row][col]);
+                        }
+                        printf("\n");
+                    }
+                }
+                free(kernelImg);
+
+                psFree(solution);
+            }
+        }
+    }
+    psFree(tstImg);
+    psFree(refImg);
+    psFree(stamps);
+    psFree(myKernels);
+    psFree(orders);
+    psFree(sigmas);
+
+    return(testStatus);
+}
+
+/*******************************************************************************
+NOTE: This function returns TRUE if there were no errors.
+ ******************************************************************************/
+int test03( void )
+{
+    bool testStatus = false;
+
+
+    srand(1995);
+    if (1)
+        testStatus|= testSubCalcEqu(TST03_NUM_COLS,
+                                    TST03_NUM_ROWS,
+                                    TST03_NUM_STAMPS_COLS,
+                                    TST03_NUM_STAMPS_ROWS,
+                                    TST03_BORDER,
+                                    PM_SUBTRACTION_KERNEL_POIS);
+
+
+    srand(1995);
+    if (1)
+        testStatus|= testSubCalcEqu(TST03_NUM_COLS,
+                                    TST03_NUM_ROWS,
+                                    TST03_NUM_STAMPS_COLS,
+                                    TST03_NUM_STAMPS_ROWS,
+                                    TST03_BORDER,
+                                    PM_SUBTRACTION_KERNEL_ISIS);
+
+
+
+    return(!testStatus);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/imcombine/tst_pmReadoutCombine.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/imcombine/tst_pmReadoutCombine.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/imcombine/tst_pmReadoutCombine.c	(revision 22157)
@@ -0,0 +1,462 @@
+/** @file tst_pmReadoutCombine.c
+ *
+ *  test00() This routine will test the basic functionality.
+ *
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-03-04 01:01:34 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ *  XXX: Untested:
+ * S16, S32 types
+ * Multiple input readouts with varying sizes and offsets.
+ * params->fracLow and params->fracHigh
+ * params->nKeep
+ * (gain > 0.0) && (readnoise >= 0.0) (applyZeroScale == true)
+ * (gain > 0.0) && (readnoise >= 0.0) (applyZeroScale == false)
+ *
+ */
+
+#include "psTest.h"
+#include "pslib.h"
+#include "pmReadoutCombine.h"
+static int test00(void);
+static int test01(void);
+testDescription tests[] = {
+                              {test00, 000, "pmSubtractBias(): Basic readout combines with no image overlap", true, false},
+                              {test01, 000, "pmSubtractBias(): input parameter error conditions", true, false},
+                              {NULL}
+                          };
+
+#define NUM_READOUTS  10
+#define INPUT_NUM_ROWS 20
+#define INPUT_NUM_COLS 20
+#define VEC_ZERO 1.0
+#define VEC_SCALE 2.0
+
+psS32 VerifyTheOutput(psImage *output, psF32 expect)
+{
+    bool testStatus = true;
+
+    for (psS32 i = 0 ; i < output->numRows ; i++) {
+        for (psS32 j = 0 ; j < output->numCols ; j++) {
+            if (output->data.F32[i][j] != expect) {
+                printf("TEST ERROR: output[%d][%d] is %.2f, should be %f\n", i, j, output->data.F32[i][j], expect);
+                testStatus = false;
+            }
+        }
+    }
+    return(testStatus);
+}
+
+
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    return !runTestSuite(stderr, "Test Point Driver", tests, argc, argv);
+}
+
+/******************************************************************************
+simpleCombineNoOverlap(): this routine creates a list of NUM_READOUTS input
+readouts and calls pmReadoutCombine().
+ *****************************************************************************/
+int simpleCombineNoOverlap(psS32 numInputCols, psS32 numInputRows)
+{
+    int i;
+    int r;
+    psList *list = NULL;
+    int baseRowsReadout[NUM_READOUTS];
+    int baseColsReadout[NUM_READOUTS];
+    int baseRows[NUM_READOUTS];
+    int baseCols[NUM_READOUTS];
+    int numRows[NUM_READOUTS];
+    int numCols[NUM_READOUTS];
+    int minOutRow = 10000;
+    int minOutCol = 10000;
+    int maxOutRow = -1;
+    int maxOutCol = -1;
+    psImage *output = NULL;
+    psCombineParams *params = (psCombineParams *) psAlloc(sizeof(psCombineParams));
+    psVector *zero = psVectorAlloc(NUM_READOUTS, PS_TYPE_F32);
+    psVector *scale = psVectorAlloc(NUM_READOUTS, PS_TYPE_F32);
+    zero->n = zero->nalloc;
+    scale->n = scale->nalloc;
+    printPositiveTestHeader(stdout, "pmReadoutCombine", "simpleCombineNoOverlap");
+
+    for (i=0;i<NUM_READOUTS;i++) {
+        zero->data.F32[i] = VEC_ZERO;
+    }
+    for (i=0;i<NUM_READOUTS;i++) {
+        scale->data.F32[i] = VEC_SCALE;
+    }
+
+    params->stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+    params->maskVal = 1;
+    params->fracLow = 0.0;
+    params->fracHigh = 10000.0;
+    params->nKeep = 0;
+
+    //
+    // Create a psList of psReadouts.  The pixels in readout r will all have the
+    // value r.
+    //
+    for (r=0;r<NUM_READOUTS;r++) {
+        baseRowsReadout[r] = r + 40;
+        baseColsReadout[r] = r + 42;
+        baseRows[r] = r;
+        baseCols[r] = r+2;
+        numRows[r] = 4 + (2 * r);
+        numCols[r] = 8 + (2 * r);
+
+        baseRowsReadout[r] = 0;
+        baseColsReadout[r] = 0;
+        baseRows[r] = 0;
+        baseCols[r] = 0;
+        numRows[r] = numInputRows;
+        numCols[r] = numInputCols;
+
+        psImage *tmpImage = psImageAlloc(numCols[r], numRows[r], PS_TYPE_F32);
+        PS_IMAGE_SET_F32(tmpImage, ((float) r));
+        *(int *) (& (tmpImage->row0)) = baseRows[r];
+        *(int *) (& (tmpImage->col0)) = baseCols[r];
+        pmReadout *tmpReadout = pmReadoutAlloc(NULL);
+        tmpReadout->row0 = 0;
+        tmpReadout->col0 = 0;
+        tmpReadout->image = tmpImage;
+
+        minOutRow = PS_MIN(minOutRow, (baseRowsReadout[r] + baseRows[r]));
+        minOutCol = PS_MIN(minOutCol, (baseColsReadout[r] + baseCols[r]));
+        maxOutRow = PS_MAX(maxOutRow, (baseRowsReadout[r] + baseRows[r] + numRows[r]));
+        maxOutCol = PS_MAX(maxOutCol, (baseColsReadout[r] + baseCols[r] + numCols[r]));
+
+        if (r == 0) {
+            list = psListAlloc(tmpReadout);
+        } else {
+            psListAdd(list, PS_LIST_HEAD, tmpReadout);
+        }
+    }
+    printf("tst_pmReadoutCombine(): (minOutRow, minOutCol) to (maxOutRow, maxOutCol) is (%d, %d) (%d, %d)\n",
+           minOutRow, minOutCol, maxOutRow, maxOutCol);
+
+    output = pmReadoutCombine(output, list, params, zero, scale, true, 0.0, 0.0);
+    psF32 NR = (psF32) NUM_READOUTS;
+    psF32 expectedPixel = ((NR/2.0) * (VEC_ZERO + (VEC_ZERO + VEC_SCALE * (NR - 1)))) / NR;
+
+    int testStatus = VerifyTheOutput(output, expectedPixel);
+
+    psFree(params->stats);
+    psFree(params);
+    psFree(output);
+    psFree(zero);
+    psFree(scale);
+
+    psListElem *tmpInput = (psListElem *) list->head;
+    while (NULL != tmpInput) {
+        pmReadout *tmpReadout = (pmReadout *) tmpInput->data;
+        psFree(tmpReadout);
+        tmpInput = tmpInput->next;
+    }
+    psFree(list);
+
+    printFooter(stdout, "pmReadoutCombine", "simpleCombineNoOverlap", true);
+    return(testStatus);
+}
+
+int test00( void )
+{
+    int testStatus = 0;
+
+    testStatus |= simpleCombineNoOverlap(1, 1);
+    testStatus |= simpleCombineNoOverlap(INPUT_NUM_COLS, 1);
+    testStatus |= simpleCombineNoOverlap(1, INPUT_NUM_ROWS);
+    testStatus |= simpleCombineNoOverlap(INPUT_NUM_COLS, INPUT_NUM_ROWS);
+
+    return(testStatus);
+}
+
+/******************************************************************************
+test01(): we simply call pmReadoutCombine() with a variety of erroneous input
+parameter combinations and verify that it behaves properly.
+ *****************************************************************************/
+int test01()
+{
+    int testStatus = true;
+    int i;
+    int r;
+    psList *list = NULL;
+    int baseRowsReadout[NUM_READOUTS];
+    int baseColsReadout[NUM_READOUTS];
+    int baseRows[NUM_READOUTS];
+    int baseCols[NUM_READOUTS];
+    int numRows[NUM_READOUTS];
+    int numCols[NUM_READOUTS];
+    int minOutRow = 10000;
+    int minOutCol = 10000;
+    int maxOutRow = -1;
+    int maxOutCol = -1;
+    psImage *output = NULL;
+    psImage *rc = NULL;
+    psCombineParams *params = (psCombineParams *) psAlloc(sizeof(psCombineParams));
+    psVector *zero = psVectorAlloc(NUM_READOUTS, PS_TYPE_F32);
+    psVector *zeroHalf = psVectorAlloc(NUM_READOUTS/2, PS_TYPE_F32);
+    psVector *zeroBig = psVectorAlloc(NUM_READOUTS+1, PS_TYPE_F32);
+    psVector *zeroF64 = psVectorAlloc(NUM_READOUTS, PS_TYPE_F64);
+    psVector *scale = psVectorAlloc(NUM_READOUTS, PS_TYPE_F32);
+    psVector *scaleHalf = psVectorAlloc(NUM_READOUTS/2, PS_TYPE_F32);
+    psVector *scaleBig = psVectorAlloc(NUM_READOUTS*2, PS_TYPE_F32);
+    psVector *scaleF64 = psVectorAlloc(NUM_READOUTS, PS_TYPE_F64);
+    zero->n = zero->nalloc;
+    zeroHalf->n = zeroHalf->nalloc;
+    zeroBig->n = zeroBig->nalloc;
+    zeroF64->n = zeroF64->nalloc;
+    scale->n = scale->nalloc;
+    scaleHalf->n = scaleHalf->nalloc;
+    scaleBig->n = scaleBig->nalloc;
+    scaleF64->n = scaleF64->nalloc;
+    for (i=0;i<NUM_READOUTS;i++) {
+        zero->data.F32[i] = 3.0;
+        zeroBig->data.F32[i] = 3.0;
+        zero->data.F32[i] = VEC_ZERO;
+        zeroBig->data.F32[i] = VEC_ZERO;
+    }
+    for (i=0;i<NUM_READOUTS;i++) {
+        scale->data.F32[i] = 6.0;
+        scaleBig->data.F32[i] = 6.0;
+        scale->data.F32[i] = VEC_SCALE;
+        scaleBig->data.F32[i] = VEC_SCALE;
+    }
+    printPositiveTestHeader(stdout, "pmReadoutCombine", "Testing bad input parameter conditions");
+
+    params->stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+    params->maskVal = 1;
+    params->fracLow = 0.0;
+    params->fracHigh = 10000.0;
+    params->nKeep = 0;
+
+    for (r=0;r<NUM_READOUTS;r++) {
+        baseRowsReadout[r] = 0;
+        baseColsReadout[r] = 0;
+        baseRows[r] = 0;
+        baseCols[r] = 0;
+        numRows[r] = INPUT_NUM_ROWS;
+        numCols[r] = INPUT_NUM_COLS;
+
+        psImage *tmpImage = psImageAlloc(numCols[r], numRows[r], PS_TYPE_F32);
+        PS_IMAGE_SET_F32(tmpImage, ((float) r));
+        *(int *) (& (tmpImage->row0)) = baseRows[r];
+        *(int *) (& (tmpImage->col0)) = baseCols[r];
+        pmReadout *tmpReadout = pmReadoutAlloc(NULL);
+        tmpReadout->row0 = 0;
+        tmpReadout->col0 = 0;
+        tmpReadout->image = tmpImage;
+        minOutRow = PS_MIN(minOutRow, (baseRowsReadout[r] + baseRows[r]));
+        minOutCol = PS_MIN(minOutCol, (baseColsReadout[r] + baseCols[r]));
+        maxOutRow = PS_MAX(maxOutRow, (baseRowsReadout[r] + baseRows[r] + numRows[r]));
+        maxOutCol = PS_MAX(maxOutCol, (baseColsReadout[r] + baseCols[r] + numCols[r]));
+
+        if (r == 0) {
+            list = psListAlloc(tmpReadout);
+        } else {
+            psListAdd(list, PS_LIST_HEAD, tmpReadout);
+        }
+    }
+
+    printf("----------------------------------------------------------------------------\n");
+    printf("Calling pmReadoutCombine() with NULL zero vector.\n");
+    rc = pmReadoutCombine(NULL, list, params, NULL, scale, true, 0.0, 0.0);
+    if (rc != NULL) {
+        psF32 NR = (psF32) NUM_READOUTS;
+        psF32 expectedPixel = ((NR/2.0) * (0.0 + (0.0 + VEC_SCALE * (NR - 1)))) / NR;
+        if (false == VerifyTheOutput(rc, expectedPixel)) {
+            testStatus = false;
+        }
+
+        if (rc->type.type != scale->type.type) {
+            printf("TEST ERROR: output readout->image has incorrect type.\n");
+            testStatus = false;
+        }
+        psFree(rc);
+    } else {
+        printf("TEST ERROR: pmReadoutCombine() returned NULL\n");
+        testStatus = false;
+
+    }
+
+    printf("----------------------------------------------------------------------------\n");
+    printf("Calling pmReadoutCombine() with incorrect length zero vector (too small).  Should generate error.\n");
+    rc = pmReadoutCombine(NULL, list, params, zeroHalf, scale, true, 0.0, 0.0);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmReadoutCombine() did not return NULL\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------------------\n");
+    printf("Calling pmReadoutCombine() with incorrect type zero vector.  Should generate error.\n");
+    rc = pmReadoutCombine(NULL, list, params, zeroF64, scale, true, 0.0, 0.0);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmReadoutCombine() did not return NULL\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------------------\n");
+    printf("Calling pmReadoutCombine() with incorrect length zero vector (too big).  Should generate warning.\n");
+    rc = pmReadoutCombine(output, list, params, zeroBig, scale, true, 0.0, 0.0);
+    if (rc != NULL) {
+        psF32 NR = (psF32) NUM_READOUTS;
+        psF32 expectedPixel = ((NR/2.0) * (VEC_ZERO + (VEC_ZERO + VEC_SCALE * (NR - 1)))) / NR;
+
+        if (false == VerifyTheOutput(rc, expectedPixel)) {
+            testStatus = false;
+        }
+        psFree(rc);
+        rc = NULL;
+    } else {
+        printf("TEST ERROR: pmReadoutCombine() returned NULL\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------------------\n");
+    printf("Calling pmReadoutCombine() with NULL scale vector.\n");
+    rc = pmReadoutCombine(output, list, params, zero, NULL, true, 0.0, 0.0);
+
+    if (rc != NULL) {
+        psF32 NR = (psF32) NUM_READOUTS;
+        psF32 expectedPixel = ((NR/2.0) * (VEC_ZERO + (VEC_ZERO + 1.0 * (NR - 1)))) / NR;
+        if (false == VerifyTheOutput(rc, expectedPixel)) {
+            testStatus = false;
+        }
+
+        if (rc->type.type != scale->type.type) {
+            printf("TEST ERROR: output readout->image has incorrect type.\n");
+            testStatus = false;
+        }
+        psFree(rc);
+    } else {
+        printf("TEST ERROR: pmReadoutCombine() returned NULL\n");
+        testStatus = false;
+
+    }
+
+    printf("----------------------------------------------------------------------------\n");
+    printf("Calling pmReadoutCombine() with incorrect length scale vector (too small).  Should generate error.\n");
+    rc = pmReadoutCombine(output, list, params, zero, scaleHalf, true, 0.0, 0.0);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmReadoutCombine() did not return NULL\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------------------\n");
+    printf("Calling pmReadoutCombine() with incorrect type scale vector.  Should generate error.\n");
+    rc = pmReadoutCombine(output, list, params, zero, scaleF64, true, 0.0, 0.0);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmReadoutCombine() did not return NULL\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------------------\n");
+    printf("Calling pmReadoutCombine() with incorrect length scale vector (too big).  Should generate warning.\n");
+    rc = pmReadoutCombine(output, list, params, zero, scaleBig, true, 0.0, 0.0);
+    if (rc != NULL) {
+        psF32 NR = (psF32) NUM_READOUTS;
+        psF32 expectedPixel = ((NR/2.0) * (VEC_ZERO + (VEC_ZERO + VEC_SCALE * (NR - 1)))) / NR;
+
+        if (false == VerifyTheOutput(rc, expectedPixel)) {
+            testStatus = false;
+        }
+        psFree(rc);
+        rc = NULL;
+    } else {
+        printf("TEST ERROR: pmReadoutCombine() returned NULL\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------------------\n");
+    printf("Calling pmReadoutCombine() insufficient size output image.  Should generate error, return NULL.\n");
+    output = psImageAlloc(1, 1, PS_TYPE_F32);
+    rc = pmReadoutCombine(output, list, params, zero, scale, true, 0.0, 0.0);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmReadoutCombine() did not return NULL\n");
+        testStatus = false;
+    }
+    psFree(output);
+    output = NULL;
+
+    printf("----------------------------------------------------------------------------\n");
+    printf("Calling pmReadoutCombine() row0/col0 too large.  Should generate error, return NULL.\n");
+    output = psImageAlloc(1, 1, PS_TYPE_F32);
+    *(psS32*)&output->row0 = 10000;
+    *(psS32*)&output->col0 = 10000;
+    rc = pmReadoutCombine(output, list, params, zero, scale, true, 0.0, 0.0);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmReadoutCombine() did not return NULL\n");
+        testStatus = false;
+    }
+    psFree(output);
+    output = NULL;
+
+    printf("----------------------------------------------------------------------------\n");
+    printf("Calling pmReadoutCombine() with NULL input list.  Should generate error, return NULL.\n");
+    rc = pmReadoutCombine(output, NULL, params, zero, scale, true, 0.0, 0.0);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmReadoutCombine() did not return NULL\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------------------\n");
+    printf("Calling pmReadoutCombine() with NULL params.  Should generate error, return NULL.\n");
+    rc = pmReadoutCombine(output, list, NULL, zero, scale, true, 0.0, 0.0);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmReadoutCombine() did not return NULL\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------------------\n");
+    psStatsOptions oldStatsOpts = params->stats->options |= PS_STAT_MIN;
+    printf("Calling pmReadoutCombine() with multiple stats->options.  Should generate error, return NULL.\n");
+    rc = pmReadoutCombine(output, list, params, zero, scale, true, 0.0, 0.0);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmReadoutCombine() did not return NULL\n");
+        testStatus = false;
+    }
+    params->stats->options = oldStatsOpts;
+
+    printf("----------------------------------------------------------------------------\n");
+    psStats *oldStats = params->stats;
+    printf("Calling pmReadoutCombine() with NULL param->stats.  Should generate error, return NULL.\n");
+    params->stats = NULL;
+    rc = pmReadoutCombine(output, list, params, zero, scale, true, 0.0, 0.0);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmReadoutCombine() did not return NULL\n");
+        testStatus = false;
+    }
+    params->stats = oldStats;
+
+    printf("----------------------------------------------------------------------------\n");
+    printf("============================================================================\n");
+
+    psFree(params->stats);
+    psFree(params);
+    //    psFree(output);
+    //    psFree(rc);
+    psFree(zero);
+    psFree(zeroHalf);
+    psFree(zeroBig);
+    psFree(zeroF64);
+    psFree(scale);
+    psFree(scaleHalf);
+    psFree(scaleBig);
+    psFree(scaleF64);
+    psListElem *tmpInput = (psListElem *) list->head;
+    while (NULL != tmpInput) {
+        pmReadout *tmpReadout = (pmReadout *) tmpInput->data;
+        psFree(tmpReadout);
+        tmpInput = tmpInput->next;
+    }
+    psFree(list);
+
+    printFooter(stdout, "pmReadoutCombine", "Testing bad input parameter conditions", true);
+    return(testStatus);
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/.cvsignore	(revision 22157)
@@ -0,0 +1,9 @@
+.deps
+.libs
+Makefile
+Makefile.in
+temp
+tap_pmGrowthCurve
+tap_pmSourceFitModel
+tap_pmSourceFitModel_Delta
+tap_pmSourcePhotometry
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/Makefile.am	(revision 22157)
@@ -0,0 +1,51 @@
+AM_CPPFLAGS = \
+	$(SRCINC) \
+	-I$(top_srcdir)/test/tap/src \
+	-I$(top_srcdir)/test/pstap/src \
+	$(PSMODULES_CFLAGS)
+
+AM_LDFLAGS = \
+	$(top_builddir)/src/libpsmodules.la  \
+	$(top_builddir)/test/tap/src/libtap.la \
+	$(top_builddir)/test/pstap/src/libpstap.la \
+	$(PSMODULES_LIBS)
+
+TEST_PROGS = \
+	tap_pmPeaks \
+	tap_pmGrowthCurve \
+	tap_pmMoments \
+	tap_pmSource \
+	tap_pmModel \
+	tap_pmModelUtils \
+	tap_pmModelClass \
+	tap_pmPSF \
+	tap_pmTrend2D \
+	tap_pmResiduals \
+	tap_pmSourceExtendedPars \
+	tap_pmSourceSky \
+	tap_pmSourceIO_PS1_DEV_0 \
+	tap_pmSourceIO_PS1_DEV_1 \
+	tap_pmSourceIO_SMPDATA \
+	tap_pmSourceContour \
+	tap_pmSourceUtils \
+	tap_pmSourceFitSet \
+	tap_pmPSF_IO \
+	tap_pmSourceIO_SMPDATA
+
+if BUILD_TESTS
+bin_PROGRAMS = $(TEST_PROGS)
+TESTS = $(TEST_PROGS)
+else
+check_PROGRAMS = $(TEST_PROGS)
+endif
+
+CLEANFILES = $(check_DATA) temp/* core core.* *~ *.bb *.bbg *.da gmon.out
+
+test: check
+	$(top_srcdir)/test/test.pl
+
+# Removed
+#	tap_pmSourcePhotometry
+#	tap_pmGrowthCurve
+#	tap_pmSourceFitModel
+#	tap_pmSourceFitModel_Delta
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmFringe.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmFringe.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmFringe.c	(revision 22157)
@@ -0,0 +1,130 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+
+#define NUM_ROWS 8
+#define NUM_COLS 16
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    plan_tests(35);
+
+
+    // Test pmFringeRegionsAlloc()
+    {
+        psMemId id = psMemGetId();
+        pmFringeRegions *fringe = pmFringeRegionsAlloc(1, 2, 3, 4, 5);
+        ok(fringe != NULL, "pmFringeRegionsAlloc() returned non-NULL");
+        ok(fringe->x == NULL, "pmFringeRegionsAlloc() set fringe->x correctly");
+        ok(fringe->y == NULL, "pmFringeRegionsAlloc() set fringe->y correctly");
+        ok(fringe->mask == NULL, "pmFringeRegionsAlloc() set fringe->mask correctly");
+        ok(fringe->nRequested == 1, "pmFringeRegionsAlloc() set fringe->nRequested correctly");
+        ok(fringe->nAccepted == 0, "pmFringeRegionsAlloc() set fringe->nAccepted correctly");
+        ok(fringe->dX == 2, "pmFringeRegionsAlloc() set fringe->dX correctly");
+        ok(fringe->dY == 3, "pmFringeRegionsAlloc() set fringe->dY correctly");
+        ok(fringe->nX == 4, "pmFringeRegionsAlloc() set fringe->nX correctly");
+        ok(fringe->nY == 5, "pmFringeRegionsAlloc() set fringe->nY correctly");
+        psFree(fringe);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    #define NUM_FRINGE_PNTS 10
+    #define DX 2
+    #define DY 2
+    #define NX 1
+    #define NY 1
+    // test pmFringeRegionsCreatePoints(): NULL random number generator,
+    // NULL fringe X, y, and mask vectors.
+    {
+        psMemId id = psMemGetId();
+        pmFringeRegions *fringe = pmFringeRegionsAlloc(NUM_FRINGE_PNTS, DX, DY, NX, NY);
+        ok(fringe != NULL, "pmFringeRegionsAlloc() returned non-NULL");
+        psImage *img = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+        bool rc = pmFringeRegionsCreatePoints(fringe, img, NULL);
+        ok(rc, "pmFringeRegionsCreatePoints() returned TRUE");
+        ok(fringe->x != NULL &&
+           fringe->x->type.type == PS_TYPE_F32 &&
+           fringe->x->n ==NUM_FRINGE_PNTS, "pmFringeRegionsCreatePoints() returned correct fringe->x psVector");
+        ok(fringe->y != NULL &&
+           fringe->y->type.type == PS_TYPE_F32 &&
+           fringe->y->n ==NUM_FRINGE_PNTS, "pmFringeRegionsCreatePoints() returned correct fringe->y psVector");
+        ok(fringe->mask != NULL &&
+           fringe->mask->type.type == PS_TYPE_MASK &&
+           fringe->mask->n ==NUM_FRINGE_PNTS, "pmFringeRegionsCreatePoints() returned correct fringe->mask psVector");
+        bool errorFlag = false;
+        for (int i = 0 ; i < NUM_FRINGE_PNTS ; i++) {
+            if (fringe->mask->data.U8[i] != 0) {
+                diag("ERROR: pmFringeRegionsCreatePoints() did not set mask[%d] to 0", i);
+                errorFlag = true;
+            }
+            if (!((fringe->x->data.F32[i] >= DX) &&
+                 (fringe->x->data.F32[i] <= NUM_COLS-DX))) {
+                diag("ERROR: pmFringeRegionsCreatePoints() did not set x[%d] correctly.  It was %.2f, should be within (%d %d)", i,
+                      fringe->x->data.F32[i], DX, NUM_COLS-DX);
+                errorFlag = true;
+            }
+            if (!((fringe->y->data.F32[i] >= DY) &&
+                 (fringe->y->data.F32[i] <= NUM_ROWS-DY))) {
+                diag("ERROR: pmFringeRegionsCreatePoints() did not set x[%d] correctly.  It was %.2f, should be within (%d %d)", i,
+                      fringe->y->data.F32[i], DY, NUM_ROWS-DY);
+                errorFlag = true;
+            }
+        }
+
+        psFree(img);
+        psFree(fringe);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // test pmFringeRegionsCreatePoints(): non-NULL random number generator,
+    // non-NULL fringe X, y, and mask vectors.
+    {
+        psMemId id = psMemGetId();
+        pmFringeRegions *fringe = pmFringeRegionsAlloc(NUM_FRINGE_PNTS, DX, DY, NX, NY);
+        ok(fringe != NULL, "pmFringeRegionsAlloc() returned non-NULL");
+        psImage *img = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+        psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, 10);
+        fringe->x = psVectorAlloc(NUM_FRINGE_PNTS/2, PS_TYPE_F32);
+        fringe->y = psVectorAlloc(NUM_FRINGE_PNTS/2, PS_TYPE_F32);
+        fringe->mask = psVectorAlloc(NUM_FRINGE_PNTS/2, PS_TYPE_MASK);
+        bool rc = pmFringeRegionsCreatePoints(fringe, img, NULL);
+        ok(rc, "pmFringeRegionsCreatePoints() returned TRUE");
+        ok(fringe->x != NULL &&
+           fringe->x->type.type == PS_TYPE_F32 &&
+           fringe->x->n ==NUM_FRINGE_PNTS, "pmFringeRegionsCreatePoints() returned correct fringe->x psVector");
+        ok(fringe->y != NULL &&
+           fringe->y->type.type == PS_TYPE_F32 &&
+           fringe->y->n ==NUM_FRINGE_PNTS, "pmFringeRegionsCreatePoints() returned correct fringe->y psVector");
+        ok(fringe->mask != NULL &&
+           fringe->mask->type.type == PS_TYPE_MASK &&
+           fringe->mask->n ==NUM_FRINGE_PNTS, "pmFringeRegionsCreatePoints() returned correct fringe->mask psVector");
+        bool errorFlag = false;
+        for (int i = 0 ; i < NUM_FRINGE_PNTS ; i++) {
+            if (fringe->mask->data.U8[i] != 0) {
+                diag("ERROR: pmFringeRegionsCreatePoints() did not set mask[%d] to 0", i);
+                errorFlag = true;
+            }
+            if (!((fringe->x->data.F32[i] >= DX) &&
+                 (fringe->x->data.F32[i] <= NUM_COLS-DX))) {
+                diag("ERROR: pmFringeRegionsCreatePoints() did not set x[%d] correctly.  It was %.2f, should be within (%d %d)", i,
+                      fringe->x->data.F32[i], DX, NUM_COLS-DX);
+                errorFlag = true;
+            }
+            if (!((fringe->y->data.F32[i] >= DY) &&
+                 (fringe->y->data.F32[i] <= NUM_ROWS-DY))) {
+                diag("ERROR: pmFringeRegionsCreatePoints() did not set x[%d] correctly.  It was %.2f, should be within (%d %d)", i,
+                      fringe->y->data.F32[i], DY, NUM_ROWS-DY);
+                errorFlag = true;
+            }
+        }
+        psFree(rng);
+        psFree(img);
+        psFree(fringe);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmGrowthCurve.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmGrowthCurve.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmGrowthCurve.c	(revision 22157)
@@ -0,0 +1,378 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/*
+    STATUS:
+	All functions are tested.  However, some of the pmGrowthCurveCorrect()
+	tests that were supplied by IfA ae failing.
+*/
+
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(70);
+
+    // ----------------------------------------------------------------------
+    // pmGrowthCurveAlloc() tests
+    // call pmGrowthCurveAlloc() with acceptable input parameters.
+    {
+        psMemId id = psMemGetId();
+        pmGrowthCurve *growthCurve = pmGrowthCurveAlloc(1.0, 2.0, 3.0);
+        ok(growthCurve && psMemCheckGrowthCurve(growthCurve), "pmGrowthCurveAlloc() allocated a pmGrowthCurve correctly");
+        ok(growthCurve->radius && psMemCheckVector(growthCurve->radius), "pmGrowthCurveAlloc() allocated the radius psVector correctly");
+        ok(growthCurve->apMag  && psMemCheckVector(growthCurve->apMag) &&
+           growthCurve->apMag->n == growthCurve->radius->n, "pmGrowthCurveAlloc() allocated the apMag psVector correctly");
+        ok(growthCurve->refRadius == 3.0, "pmGrowthCurveAlloc() set growthCurve->refRadius correctly");
+        ok(growthCurve->maxRadius == 2.0, "pmGrowthCurveAlloc() set growthCurve->maxRadius correctly");
+        ok(growthCurve->apLoss == 0.0, "pmGrowthCurveAlloc() set growthCurve->apLoss correctly");
+        ok(growthCurve->fitMag == 0.0, "pmGrowthCurveAlloc() set growthCurve->fitMag correctly");
+        psFree(growthCurve);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmGrowthCurveCorrect() tests
+    // Call pmGrowthCurveCorrect() with NULL input pmGrowthCurve
+    {
+        psMemId id = psMemGetId();
+        pmGrowthCurve *growthCurve = pmGrowthCurveAlloc(1.0, 2.0, 3.0);
+        ok(isnan(pmGrowthCurveCorrect(NULL, 0.0)), "pmGrowthCurveCorrect() returned NAN with NULL input pmGrowthCurve");
+        psFree(growthCurve);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmGrowthCurveCorrect() with acceptable input parameters.
+    {
+        #define RADIUS 0.5
+        psMemId id = psMemGetId();
+        pmGrowthCurve *growthCurve = pmGrowthCurveAlloc(1.0, 2.0, 3.0);
+        float testCor = pmGrowthCurveCorrect(growthCurve, RADIUS);
+        float actRad = psVectorInterpolate (growthCurve->radius, growthCurve->apMag, RADIUS);
+        float actCor = growthCurve->apRef - actRad;
+
+        ok(!isnan(testCor), "pmGrowthCurveCorrect() call was successful");
+        ok(actCor == testCor, "pmGrowthCurveCorrect() calculated the correction correctly");
+
+        psFree(growthCurve);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // EM test 01: test allocation
+    // offset of 0.0,0.0 wrt growth ref source
+    {
+        psMemId id = psMemGetId();
+
+        pmGrowthCurve *growth = pmGrowthCurveAlloc (2.0, 100.0, 15.0);
+
+        ok(growth != NULL, "growth curve allocated");
+        skip_start(growth == NULL, 0, "Skipping tests because pmShutterCorrParsAlloc() failed");
+
+        ok(growth->radius->n == (100.0 - 2.0), "correct number of growth radii");
+        ok(growth->apMag->n == (100.0 - 2.0), "correct number of growth apMags");
+
+        ok_float(growth->refRadius, 15.0, "correct refRadius");
+        ok_float(growth->maxRadius, 100.0, "correct maxRadius");
+
+        // does the growth curve correctly fix aperture mags?
+
+        // generate a simple readout
+        pmReadout *readout = pmReadoutAlloc (NULL);
+        readout->image = psImageAlloc (64, 64, PS_TYPE_F32);
+        readout->mask  = psImageAlloc (64, 64, PS_TYPE_U8);
+
+        // create an empty reference image
+        psImageInit (readout->image, 0.0);
+        psImageInit (readout->mask, 0);
+
+        // generate a simple psf
+        pmModelClassInit();
+        pmPSF *psf = pmPSFBuildSimple("PS_MODEL_GAUSS", 1.5, 1.5, 0.0);
+        psf->growth = growth;
+
+        pmGrowthCurveGenerate(readout, psf, false, 0, 0);
+
+        // check ap mags for a few radii set by hand
+        ok_float_tol(growth->apMag->data.F32[0],   -9.7805, 0.0001, "apMag at radius 0: %f", growth->apMag->data.F32[0]);
+        ok_float_tol(growth->apMag->data.F32[3],  -10.3722, 0.0001, "apMag at radius 3: %f", growth->apMag->data.F32[3]);
+        ok_float_tol(growth->apMag->data.F32[10], -10.3759, 0.0001, "apMag at radius 10: %f", growth->apMag->data.F32[10]);
+        ok_float_tol(growth->apMag->data.F32[30], -10.3759, 0.0001, "apMag at radius 30: %f", growth->apMag->data.F32[30]);
+
+        ok_float_tol(growth->apRef,  -10.3759, 0.0001, "apMag at ref radius : %f", growth->apRef);
+        ok_float_tol(growth->fitMag, -10.3759, 0.0001, "fitMag : %f", growth->fitMag);
+        ok_float(growth->apLoss, 0.0, "apLoss : %f", growth->apLoss);
+
+        // create template model and measure apMag at fractional offsets
+        // XXX note model is at 0.5,0.5 subpix center
+        pmModel *modelRef = pmModelAlloc(psf->type);
+        modelRef->params->data.F32[PM_PAR_SKY] = 0;
+        modelRef->params->data.F32[PM_PAR_I0] = 1000;
+        modelRef->params->data.F32[PM_PAR_XPOS] = 32.5;
+        modelRef->params->data.F32[PM_PAR_YPOS] = 32.5;
+
+        // measure growth-corrected photometry:
+        pmSource *source = pmSourceAlloc ();
+        source->modelPSF = pmModelFromPSF (modelRef, psf);
+        source->type = PM_SOURCE_TYPE_STAR;
+        source->pixels = psMemIncrRefCounter (readout->image);
+        source->maskObj = psMemIncrRefCounter (readout->mask);
+
+        source->modelPSF->dparams->data.F32[PM_PAR_I0] = 1;
+        source->mode = PM_SOURCE_MODE_SUBTRACTED;
+
+        source->modelPSF->radiusFit = 15.0;
+
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0, 0);
+        double refMag = source->apMag;
+
+        source->modelPSF->radiusFit = 10.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0, 0);
+        ok_float_tol(refMag - source->apMag, +0.0000, 0.0001, "growth offset is is %f", refMag - source->apMag);
+
+        source->modelPSF->radiusFit = 8.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0, 0);
+        ok_float_tol(refMag - source->apMag, +0.0000, 0.0001, "growth offset is is %f", refMag - source->apMag);
+
+        source->modelPSF->radiusFit = 6.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0, 0);
+        ok_float_tol(refMag - source->apMag, +0.0003, 0.0001, "growth offset is is %f", refMag - source->apMag);
+
+        source->modelPSF->radiusFit = 4.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0, 0);
+        ok_float_tol(refMag - source->apMag, +0.0020, 0.0001, "growth offset is is %f", refMag - source->apMag);
+
+        source->modelPSF->radiusFit = 3.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0, 0);
+        ok_float_tol(refMag - source->apMag, -0.0001, 0.0001, "growth offset is is %f", refMag - source->apMag);
+
+        source->modelPSF->radiusFit = 2.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0, 0);
+        ok_float_tol(refMag - source->apMag, -0.0075, 0.0001, "growth offset is is %f", refMag - source->apMag);
+
+        // XXX include some apertures outside of growth correction range
+
+        psFree(modelRef);
+        psFree(source);
+        psFree(readout);
+        psFree(psf);
+
+        skip_end();
+
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // EM test 02: test allocation
+    // offset of 0.2,0.2 wrt growth ref source
+    {
+        psMemId id = psMemGetId();
+
+        pmGrowthCurve *growth = pmGrowthCurveAlloc (2.0, 100.0, 15.0);
+
+        ok(growth != NULL, "growth curve allocated");
+        skip_start(growth == NULL, 0, "Skipping tests because pmShutterCorrParsAlloc() failed");
+
+        ok(growth->radius->n == (100.0 - 2.0), "correct number of growth radii");
+        ok(growth->apMag->n == (100.0 - 2.0), "correct number of growth apMags");
+
+        ok_float(growth->refRadius, 15.0, "correct refRadius");
+        ok_float(growth->maxRadius, 100.0, "correct maxRadius");
+
+        // does the growth curve correctly fix aperture mags?
+
+        // generate a simple readout
+        pmReadout *readout = pmReadoutAlloc (NULL);
+        readout->image = psImageAlloc (64, 64, PS_TYPE_F32);
+        readout->mask  = psImageAlloc (64, 64, PS_TYPE_U8);
+
+        // create an empty reference image
+        psImageInit (readout->image, 0.0);
+        psImageInit (readout->mask, 0);
+
+        // generate a simple psf
+        pmPSF *psf = pmPSFBuildSimple ("PS_MODEL_GAUSS", 1.5, 1.5, 0.0);
+        psf->growth = growth;
+
+        pmGrowthCurveGenerate (readout, psf, false, 0, 0);
+
+        // check ap mags for a few radii set by hand
+        ok_float_tol(growth->apMag->data.F32[0],   -9.7805, 0.0001, "apMag at radius 0: %f", growth->apMag->data.F32[0]);
+        ok_float_tol(growth->apMag->data.F32[3],  -10.3722, 0.0001, "apMag at radius 3: %f", growth->apMag->data.F32[3]);
+        ok_float_tol(growth->apMag->data.F32[10], -10.3759, 0.0001, "apMag at radius 10: %f", growth->apMag->data.F32[10]);
+        ok_float_tol(growth->apMag->data.F32[30], -10.3759, 0.0001, "apMag at radius 30: %f", growth->apMag->data.F32[30]);
+
+        ok_float_tol(growth->apRef,  -10.3759, 0.0001, "apMag at ref radius : %f", growth->apRef);
+        ok_float_tol(growth->fitMag, -10.3759, 0.0001, "fitMag : %f", growth->fitMag);
+        ok_float(growth->apLoss, 0.0, "apLoss : %f", growth->apLoss);
+
+        // create template model and measure apMag at fractional offsets
+        // XXX note model is at 0.5,0.5 subpix center
+        pmModel *modelRef = pmModelAlloc(psf->type);
+        modelRef->params->data.F32[PM_PAR_SKY] = 0;
+        modelRef->params->data.F32[PM_PAR_I0] = 1000;
+        modelRef->params->data.F32[PM_PAR_XPOS] = 32.3;
+        modelRef->params->data.F32[PM_PAR_YPOS] = 32.3;
+
+        // measure growth-corrected photometry:
+        pmSource *source = pmSourceAlloc ();
+        source->modelPSF = pmModelFromPSF (modelRef, psf);
+        source->type = PM_SOURCE_TYPE_STAR;
+        source->pixels = psMemIncrRefCounter (readout->image);
+        source->maskObj = psMemIncrRefCounter (readout->mask);
+
+        source->modelPSF->dparams->data.F32[PM_PAR_I0] = 1;
+        source->mode = PM_SOURCE_MODE_SUBTRACTED;
+
+        source->modelPSF->radiusFit = 15.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0, 0);
+        double refMag = source->apMag;
+
+        source->modelPSF->radiusFit = 10.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0, 0);
+        ok_float_tol(refMag - source->apMag, +0.0000, 0.0001, "growth offset is is %f", refMag - source->apMag);
+
+        source->modelPSF->radiusFit = 8.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0, 0);
+        ok_float_tol(refMag - source->apMag, +0.0000, 0.0001, "growth offset is is %f", refMag - source->apMag);
+
+        source->modelPSF->radiusFit = 6.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0, 0);
+        ok_float_tol(refMag - source->apMag, +0.0004, 0.0001, "growth offset is is %f", refMag - source->apMag);
+
+        source->modelPSF->radiusFit = 4.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0, 0);
+        ok_float_tol(refMag - source->apMag, +0.0026, 0.0001, "growth offset is is %f", refMag - source->apMag);
+
+        source->modelPSF->radiusFit = 3.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0, 0);
+        ok_float_tol(refMag - source->apMag, -0.0001, 0.0001, "growth offset is is %f", refMag - source->apMag);
+
+        source->modelPSF->radiusFit = 2.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0, 0);
+        ok_float_tol(refMag - source->apMag, -0.0103, 0.0001, "growth offset is is %f", refMag - source->apMag);
+
+        // XXX include some apertures outside of growth correction range
+
+        psFree(modelRef);
+        psFree(source);
+        psFree(readout);
+        psFree(psf);
+
+        skip_end();
+
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // EM test 03: test allocation
+    // offset of 0.4,0.4 wrt growth ref source
+    {
+        psMemId id = psMemGetId();
+
+        pmGrowthCurve *growth = pmGrowthCurveAlloc (2.0, 100.0, 15.0);
+
+        ok(growth != NULL, "growth curve allocated");
+        skip_start(growth == NULL, 0, "Skipping tests because pmShutterCorrParsAlloc() failed");
+
+        ok(growth->radius->n == (100.0 - 2.0), "correct number of growth radii");
+        ok(growth->apMag->n == (100.0 - 2.0), "correct number of growth apMags");
+
+        ok_float(growth->refRadius, 15.0, "correct refRadius");
+        ok_float(growth->maxRadius, 100.0, "correct maxRadius");
+
+        // does the growth curve correctly fix aperture mags?
+
+        // generate a simple readout
+        pmReadout *readout = pmReadoutAlloc (NULL);
+        readout->image = psImageAlloc (64, 64, PS_TYPE_F32);
+        readout->mask  = psImageAlloc (64, 64, PS_TYPE_U8);
+
+        // create an empty reference image
+        psImageInit (readout->image, 0.0);
+        psImageInit (readout->mask, 0);
+
+        // generate a simple psf
+        pmPSF *psf = pmPSFBuildSimple ("PS_MODEL_GAUSS", 1.5, 1.5, 0.0);
+        psf->growth = growth;
+
+        pmGrowthCurveGenerate(readout, psf, false, 0, 0);
+
+        // check ap mags for a few radii set by hand
+        ok_float_tol(growth->apMag->data.F32[0],   -9.7805, 0.0001, "apMag at radius 0: %f", growth->apMag->data.F32[0]);
+        ok_float_tol(growth->apMag->data.F32[3],  -10.3722, 0.0001, "apMag at radius 3: %f", growth->apMag->data.F32[3]);
+        ok_float_tol(growth->apMag->data.F32[10], -10.3759, 0.0001, "apMag at radius 10: %f", growth->apMag->data.F32[10]);
+        ok_float_tol(growth->apMag->data.F32[30], -10.3759, 0.0001, "apMag at radius 30: %f", growth->apMag->data.F32[30]);
+
+        ok_float_tol(growth->apRef,  -10.3759, 0.0001, "apMag at ref radius : %f", growth->apRef);
+        ok_float_tol(growth->fitMag, -10.3759, 0.0001, "fitMag : %f", growth->fitMag);
+        ok_float(growth->apLoss, 0.0, "apLoss : %f", growth->apLoss);
+
+        // create template model and measure apMag at fractional offsets
+        // XXX note model is at 0.5,0.5 subpix center
+        pmModel *modelRef = pmModelAlloc(psf->type);
+        modelRef->params->data.F32[PM_PAR_SKY] = 0;
+        modelRef->params->data.F32[PM_PAR_I0] = 1000;
+        modelRef->params->data.F32[PM_PAR_XPOS] = 32.1;
+        modelRef->params->data.F32[PM_PAR_YPOS] = 32.1;
+
+        // measure growth-corrected photometry:
+        pmSource *source = pmSourceAlloc ();
+        source->modelPSF = pmModelFromPSF (modelRef, psf);
+        source->type = PM_SOURCE_TYPE_STAR;
+        source->pixels = psMemIncrRefCounter (readout->image);
+        source->maskObj = psMemIncrRefCounter (readout->mask);
+
+        source->modelPSF->dparams->data.F32[PM_PAR_I0] = 1;
+        source->mode = PM_SOURCE_MODE_SUBTRACTED;
+
+        source->modelPSF->radiusFit = 15.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0, 0);
+        double refMag = source->apMag;
+
+        source->modelPSF->radiusFit = 10.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0, 0);
+        ok_float_tol(refMag - source->apMag, +0.0000, 0.0001, "growth offset is is %f", refMag - source->apMag);
+
+        source->modelPSF->radiusFit = 8.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0, 0);
+        ok_float_tol(refMag - source->apMag, +0.0000, 0.0001, "growth offset is is %f", refMag - source->apMag);
+
+        source->modelPSF->radiusFit = 6.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0, 0);
+        ok_float_tol(refMag - source->apMag, +0.0006, 0.0001, "growth offset is is %f", refMag - source->apMag);
+
+        source->modelPSF->radiusFit = 4.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0, 0);
+        ok_float_tol(refMag - source->apMag, +0.0038, 0.0001, "growth offset is is %f", refMag - source->apMag);
+
+        source->modelPSF->radiusFit = 3.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0, 0);
+        ok_float_tol(refMag - source->apMag, +0.0000, 0.0001, "growth offset is is %f", refMag - source->apMag);
+
+        source->modelPSF->radiusFit = 2.0;
+        pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP, 0, 0);
+        ok_float_tol(refMag - source->apMag, -0.0164, 0.0001, "growth offset is is %f", refMag - source->apMag);
+
+        // XXX include some apertures outside of growth correction range
+
+        psFree(modelRef);
+        psFree(source);
+        psFree(readout);
+        psFree(psf);
+
+        skip_end();
+
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmModel.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmModel.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmModel.c	(revision 22157)
@@ -0,0 +1,460 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS:
+    All functions are tested.  However...
+
+    The source code for pmModelAddWithOffset() and pmModelSubWithOffset() is
+    almost exactly the same as the source code for pmModelAdd() and
+    pmModelSub().  We do not test them here.
+*/
+
+#define MISC_NUM                32
+#define MISC_NAME              "META00"
+#define NUM_BIAS_DATA           10
+#define TEST_NUM_ROWS           (8+1)
+#define TEST_NUM_COLS           (8+1)
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+#define NUM_ROWS		8
+#define NUM_COLS		16
+#define TEST_FLOATS_EQUAL(X, Y) (abs(X - Y) < 0.01)
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(readout);
+}
+
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(53);
+
+    // ----------------------------------------------------------------------
+    // pmModelAlloc() tests
+    // call pmModelAlloc() with unallowable model class type
+    {
+        psMemId id = psMemGetId();
+        pmModelClassInit();
+        pmModel *model = pmModelAlloc(-1);
+        ok(model == NULL, "pmModelAlloc() returned a NULL with unallowable model class type");
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // call pmModelAlloc() with acceptable input params
+    {
+        #define TEST_MODEL_CLASS_TYPE 1
+        psMemId id = psMemGetId();
+        pmModelClassInit();
+        pmModel *model = pmModelAlloc(TEST_MODEL_CLASS_TYPE);
+        ok(model != NULL && psMemCheckModel(model), "pmModelAlloc() returned a non-NULL pmModel");
+        skip_start(!model, 1, "Skipping tests because pmModelAlloc() returned NULL");
+        ok(model->type == TEST_MODEL_CLASS_TYPE, "pmModelAlloc() set pmModel->type correctly");
+        ok(model->chisq == 0.0, "pmModelAlloc() set pmModel->chisq correctly");
+        ok(model->nDOF == 0, "pmModelAlloc() set pmModel->nDOF correctly");
+        ok(model->nIter == 0, "pmModelAlloc() set pmModel->nIter correctly");
+        ok(model->radiusFit == 0, "pmModelAlloc() set pmModel->radiusFit correctly");
+        ok(model->flags == PM_MODEL_STATUS_NONE, "pmModelAlloc() set pmModel->flags correctly");
+        ok(model->residuals == NULL, "pmModelAlloc() set pmModel->residuals correctly");
+        int nParams = pmModelClassParameterCount(TEST_MODEL_CLASS_TYPE);
+        ok(model->params != NULL && model->params->n == nParams, "pmModelAlloc() set the pmModel->params psVector correctly");
+        ok(model->dparams != NULL && model->dparams->n == nParams, "pmModelAlloc() set the pmModel->dparams psVector correctly");
+        bool errorFlag = false;
+        for (psS32 i = 0; i < nParams; i++) {
+            if (model->params->data.F32[i] != 0.0 || model->dparams->data.F32[i] != 0.0) {
+                if (VERBOSE) {
+                    diag("ERROR: params/dparams[%d] is (%.2f %.2f) should be (%.2f %.2f)\n", i, model->params->data.F32[i], model->dparams->data.F32[i], 0.0, 0.0);
+		}
+                errorFlag = true;
+	    }
+        }
+        ok(!errorFlag, "pmModelAlloc() set the members of the model->params and model->dparams psVectors correctly");
+        pmModelClass *class = pmModelClassSelect(TEST_MODEL_CLASS_TYPE);
+        ok(model->modelFunc == class->modelFunc, "pmModelAlloc() set pmModel->modelFunc correctly");
+        ok(model->modelFlux == class->modelFlux, "pmModelAlloc() set pmModel->modelFlux correctly");
+        ok(model->modelRadius == class->modelRadius, "pmModelAlloc() set pmModel->modelRadius correctly");
+        ok(model->modelLimits == class->modelLimits, "pmModelAlloc() set pmModel->modelLimits correctly");
+        ok(model->modelGuess == class->modelGuess, "pmModelAlloc() set pmModel->modelGuess correctly");
+        ok(model->modelFromPSF == class->modelFromPSF, "pmModelAlloc() set pmModel->modelFromPSF correctly");
+        ok(model->modelParamsFromPSF == class->modelParamsFromPSF, "pmModelAlloc() set pmModel->modelParamsFromPSF correctly");
+        ok(model->modelFitStatus == class->modelFitStatus, "pmModelAlloc() set pmModel->modelFitStatus correctly");
+
+        psFree(model);
+        skip_end();
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmModelCopy() tests
+    // call pmModelCopy() with NULL input pmModel parameter
+    {
+        psMemId id = psMemGetId();
+        pmModel *model = pmModelCopy(NULL);
+        ok(model == NULL, "pmModelCopy() returned NULL with NULL input pmModel parameter");
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // call pmModelCopy() with acceptable input params
+    {
+        #define TEST_MODEL_CLASS_TYPE 1
+        psMemId id = psMemGetId();
+        pmModelClassInit();
+        pmModel *modelSrc = pmModelAlloc(TEST_MODEL_CLASS_TYPE);
+        ok(modelSrc != NULL && psMemCheckModel(modelSrc), "pmModelAlloc() returned a non-NULL pmModel");
+        modelSrc->chisq = 1.0;
+        modelSrc->nDOF = 2;
+        modelSrc->nIter = 3;
+        modelSrc->flags = PM_MODEL_STATUS_NONE;
+        modelSrc->radiusFit = 4;
+        pmModel *modelDst = pmModelCopy(modelSrc);
+        ok(modelDst != NULL && psMemCheckModel(modelDst), "pmModelCopy() returned a non-NULL pmModel");
+        ok(modelDst->chisq == 1.0, "pmModelCopy() set the pmModel->chisq member correctly");
+        ok(modelDst->nDOF == 2, "pmModelCopy() set the pmModel->nDOF member correctly");
+        ok(modelDst->nIter == 3, "pmModelCopy() set the pmModel->nIter member correctly");
+        ok(modelDst->flags == PM_MODEL_STATUS_NONE, "pmModelCopy() set the pmModel->flags member correctly");
+        ok(modelDst->radiusFit == 4, "pmModelCopy() set the pmModel->radiusFit member correctly");
+
+        psFree(modelSrc);
+        psFree(modelDst);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmModelEval() tests
+    // call pmModelEval() with NULL input pmModel parameter
+    {
+        psMemId id = psMemGetId();
+        pmModel *model = pmModelAlloc(TEST_MODEL_CLASS_TYPE);
+        psImage *img = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+        psF32 tmpF;
+        tmpF = pmModelEval(NULL, img, 0, 0);
+        ok(isnan(tmpF), "pmModelEval() returned NAN with NULL input pmModel parameter");
+        psFree(model);
+        psFree(img);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // psF32 pmModelEval(pmModel *model, psImage *image, psS32 col, psS32 row)
+    // call pmModelEval() with NULL input psImage parameter
+    {
+        psMemId id = psMemGetId();
+        pmModel *model = pmModelAlloc(TEST_MODEL_CLASS_TYPE);
+        psImage *img = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+        psF32 tmpF;
+        tmpF = pmModelEval(model, NULL, 0, 0);
+        ok(isnan(tmpF), "pmModelEval() returned NAN with NULL input psImage parameter");
+        psFree(model);
+        psFree(img);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // psF32 pmModelEval(pmModel *model, psImage *image, psS32 col, psS32 row)
+    // call pmModelEval() with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        pmModel *model = pmModelAlloc(TEST_MODEL_CLASS_TYPE);
+        psImage *img = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+        for (int i = 0 ; i < NUM_ROWS ; i++) {
+            for (int j = 0 ; j < NUM_COLS ; j++) {
+                img->data.F32[i][j] = (float) (i + j);
+	    }
+	}
+        psVector *x = psVectorAlloc(2, PS_TYPE_F32);
+        x->data.F32[0] = (float) (NUM_COLS / 2);
+        x->data.F32[1] = (float) (NUM_ROWS / 2);
+        psF32 testF = model->modelFunc (NULL, model->params, x);
+        psF32 tmpF;
+        tmpF = pmModelEval(model, img, (int) x->data.F32[0], (int) x->data.F32[1]);
+        ok(!isnan(tmpF), "pmModelEval() returned successfully");
+        ok(testF == tmpF, "pmModelEval() evaluated the model correctly");
+        psFree(model);
+        psFree(img);
+        psFree(x);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+/* XXX: This seems to have disappeared from pmModel.h
+    // ----------------------------------------------------------------------
+    // pmModelEvalWithOffset() tests
+    // psF32 pmModelEvalWithOffset(pmModel *model, psImage *image, 
+    //                             psS32 col, psS32 row, int dx, int dy)
+    // call pmModelEvalWithOffset() with NULL input pmModel parameter
+    {
+        psMemId id = psMemGetId();
+        pmModel *model = pmModelAlloc(TEST_MODEL_CLASS_TYPE);
+        psImage *img = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+        psF32 tmpF;
+        tmpF = pmModelEvalWithOffset(NULL, img, 0, 0, 0, 0);
+        ok(isnan(tmpF), "pmModelEvalWithOffset() returned NAN with NULL input pmModel parameter");
+        psFree(model);
+        psFree(img);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // psF32 pmModelEvalWithOffset(pmModel *model, psImage *image, 
+    //                             psS32 col, psS32 row, int dx, int dy)
+    // call pmModelEvalWithOffset() with NULL input psImage parameter
+    {
+        psMemId id = psMemGetId();
+        pmModel *model = pmModelAlloc(TEST_MODEL_CLASS_TYPE);
+        psImage *img = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+        psF32 tmpF;
+        tmpF = pmModelEvalWithOffset(model, NULL, 0, 0, 0, 0);
+        ok(isnan(tmpF), "pmModelEvalWithOffset() returned NAN with NULL input psImage parameter");
+        psFree(model);
+        psFree(img);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // psF32 pmModelEvalWithOffset(pmModel *model, psImage *image, psS32 col, psS32 row)
+    // call pmModelEvalWithOffset() with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        pmModel *model = pmModelAlloc(TEST_MODEL_CLASS_TYPE);
+        psImage *img = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+        for (int i = 0 ; i < NUM_ROWS ; i++) {
+            for (int j = 0 ; j < NUM_COLS ; j++) {
+                img->data.F32[i][j] = (float) (i + j);
+	    }
+	}
+        psVector *x = psVectorAlloc(2, PS_TYPE_F32);
+        x->data.F32[0] = (float) ((NUM_COLS / 2) + (NUM_COLS / 4));
+        x->data.F32[1] = (float) ((NUM_ROWS / 2) + (NUM_ROWS / 4));
+        psF32 testF = model->modelFunc (NULL, model->params, x);
+        psF32 tmpF;
+        tmpF = pmModelEvalWithOffset(model, img, (int) x->data.F32[0], (int) x->data.F32[1], NUM_COLS/4, NUM_ROWS/4);
+        ok(!isnan(tmpF), "pmModelEvalWithOffset() returned successfully");
+        ok(testF == tmpF, "pmModelEvalWithOffset() evaluated the model correctly");
+        psFree(model);
+        psFree(img);
+        psFree(x);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+*/
+
+
+    // ----------------------------------------------------------------------
+    // pmModelAdd() tests
+    // call pmModelAdd() with bad input parameters
+    {
+        psMemId id = psMemGetId();
+        psImage *img = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+        psImage *imgS32 = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_S32);
+        psImage *mask = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_U8);
+        for (int i = 0 ; i < NUM_ROWS ; i++) {
+            for (int j = 0 ; j < NUM_COLS ; j++) {
+                img->data.F32[i][j] = 0.0;
+                mask->data.U8[i][j] = 0;
+            }
+        }
+        pmModel *model = pmModelAlloc(pmModelClassGetType("PS_MODEL_GAUSS"));
+        psF32 *PAR = model->params->data.F32;
+        PAR[PM_PAR_XPOS] = (float) (NUM_COLS/2);
+        PAR[PM_PAR_YPOS] = (float) (NUM_ROWS/2);
+        PAR[PM_PAR_SXX] = 1.0;
+        PAR[PM_PAR_SYY] = 1.0;
+
+        // NULL psImage input parameter
+        bool rc = pmModelAdd(NULL, mask, model, PM_MODEL_OP_FUNC, 1);
+        ok(rc == false, "pmModelAdd() returned FALSE with NULL psImage input parameter");
+
+        // NULL pmModel input parameter
+        rc = pmModelAdd(img, mask, NULL, PM_MODEL_OP_FUNC, 1);
+        ok(rc == false, "pmModelAdd() returned FALSE with NULL pmModel input parameter");
+
+        // Incorrect type psImage input parameter
+        rc = pmModelAdd(imgS32, mask, model, PM_MODEL_OP_FUNC, 1);
+        ok(rc == false, "pmModelAdd() returned FALSE with Incorrect type psImage input parameter");
+
+        psFree(img);
+        psFree(imgS32);
+        psFree(mask);
+        psFree(model);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // call pmModelAdd() with acceptable parameters
+    // We only test with a single Gaussian model, with no residuals or masks.
+    // For completeness, additional tests should be added.
+    {
+        psMemId id = psMemGetId();
+        psImage *img = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+        psImage *mask = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_U8);
+        for (int i = 0 ; i < NUM_ROWS ; i++) {
+            for (int j = 0 ; j < NUM_COLS ; j++) {
+                img->data.F32[i][j] = 0.0;
+                mask->data.U8[i][j] = 0;
+            }
+        }
+        pmModel *model = pmModelAlloc(pmModelClassGetType("PS_MODEL_GAUSS"));
+        psF32 *PAR = model->params->data.F32;
+        PAR[PM_PAR_I0] = 5.0;
+        PAR[PM_PAR_XPOS] = 0.0;
+        PAR[PM_PAR_YPOS] = 0.0;
+        PAR[PM_PAR_XPOS] = (float) (NUM_COLS/2);
+        PAR[PM_PAR_YPOS] = (float) (NUM_ROWS/2);
+        PAR[PM_PAR_SXX] = 10.0;
+        PAR[PM_PAR_SYY] = 10.0;
+
+        bool rc = pmModelAdd(img, mask, model, PM_MODEL_OP_FUNC, 1);
+        ok(rc == true, "pmModelAdd() returned TRUE with acceptable input parameters");
+        psVector *x = psVectorAlloc(2, PS_TYPE_F32);
+        bool errorFlag = false;
+        for (int i = 0 ; i < NUM_ROWS ; i++) {
+            for (int j = 0 ; j < NUM_COLS ; j++) {
+                x->data.F32[0] = (float) j;
+                x->data.F32[1] = (float) i;
+                psF32 modF = model->modelFunc (NULL, model->params, x);
+                psF32 imgF = img->data.F32[i][j];
+                if (!TEST_FLOATS_EQUAL(modF, imgF)) {
+                    diag("ERROR: img[%d][%d] is %.2f, should be %.2f\n", i, j, img->data.F32[i][j], modF);
+                    errorFlag = true;
+                }
+            }
+        }
+        ok(!errorFlag, "pmModelAdd() set the image pixels correctly");
+        psFree(x);
+        psFree(img);
+        psFree(mask);
+        psFree(model);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmModelSub() tests
+    // call pmModelSub() with bad input parameters
+    {
+        psMemId id = psMemGetId();
+        psImage *img = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+        psImage *imgS32 = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_S32);
+        psImage *mask = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_U8);
+        for (int i = 0 ; i < NUM_ROWS ; i++) {
+            for (int j = 0 ; j < NUM_COLS ; j++) {
+                img->data.F32[i][j] = 0.0;
+                mask->data.U8[i][j] = 0;
+            }
+        }
+        pmModel *model = pmModelAlloc(pmModelClassGetType("PS_MODEL_GAUSS"));
+        psF32 *PAR = model->params->data.F32;
+        PAR[PM_PAR_XPOS] = (float) (NUM_COLS/2);
+        PAR[PM_PAR_YPOS] = (float) (NUM_ROWS/2);
+        PAR[PM_PAR_SXX] = 1.0;
+        PAR[PM_PAR_SYY] = 1.0;
+
+        // NULL psImage input parameter
+        bool rc = pmModelSub(NULL, mask, model, PM_MODEL_OP_FUNC, 1);
+        ok(rc == false, "pmModelSub() returned FALSE with NULL psImage input parameter");
+
+        // NULL pmModel input parameter
+        rc = pmModelSub(img, mask, NULL, PM_MODEL_OP_FUNC, 1);
+        ok(rc == false, "pmModelSub() returned FALSE with NULL pmModel input parameter");
+
+        // Incorrect type psImage input parameter
+        rc = pmModelSub(imgS32, mask, model, PM_MODEL_OP_FUNC, 1);
+        ok(rc == false, "pmModelSub() returned FALSE with Incorrect type psImage input parameter");
+
+        psFree(img);
+        psFree(imgS32);
+        psFree(mask);
+        psFree(model);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // call pmModelSub() with acceptable parameters
+    // We only test with a single Gaussian model, with no residuals or masks.
+    // For completeness, additional tests should be added.
+    {
+        psMemId id = psMemGetId();
+        psImage *img = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+        psImage *mask = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_U8);
+        for (int i = 0 ; i < NUM_ROWS ; i++) {
+            for (int j = 0 ; j < NUM_COLS ; j++) {
+                img->data.F32[i][j] = 0.0;
+                mask->data.U8[i][j] = 0;
+            }
+        }
+        pmModel *model = pmModelAlloc(pmModelClassGetType("PS_MODEL_GAUSS"));
+        psF32 *PAR = model->params->data.F32;
+        PAR[PM_PAR_I0] = 5.0;
+        PAR[PM_PAR_XPOS] = 0.0;
+        PAR[PM_PAR_YPOS] = 0.0;
+        PAR[PM_PAR_XPOS] = (float) (NUM_COLS/2);
+        PAR[PM_PAR_YPOS] = (float) (NUM_ROWS/2);
+        PAR[PM_PAR_SXX] = 10.0;
+        PAR[PM_PAR_SYY] = 10.0;
+
+        bool rc = pmModelSub(img, mask, model, PM_MODEL_OP_FUNC, 1);
+        ok(rc == true, "pmModelSub() returned TRUE with acceptable input parameters");
+        psVector *x = psVectorAlloc(2, PS_TYPE_F32);
+        bool errorFlag = false;
+        for (int i = 0 ; i < NUM_ROWS ; i++) {
+            for (int j = 0 ; j < NUM_COLS ; j++) {
+                x->data.F32[0] = (float) j;
+                x->data.F32[1] = (float) i;
+                psF32 modF = model->modelFunc (NULL, model->params, x);
+                psF32 imgF = img->data.F32[i][j];
+                if (!TEST_FLOATS_EQUAL(modF, -imgF)) {
+                    diag("ERROR: img[%d][%d] is %.2f, should be %.2f\n", i, j, img->data.F32[i][j], modF);
+                    errorFlag = true;
+                }
+            }
+        }
+        ok(!errorFlag, "pmModelSub() set the image pixels correctly");
+        psFree(x);
+        psFree(img);
+        psFree(mask);
+        psFree(model);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // XXX: The source code for pmModelAddWithOffset() and pmModelSubWithOffset() is
+    // almost exactly the same as the source code for pmModelAdd() and pmModelSub().
+    // We do not test them here.
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmModelClass.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmModelClass.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmModelClass.c	(revision 22157)
@@ -0,0 +1,181 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS:
+    All functions are tested except pmModelClassCleanup() which is deferred
+    because there's no way to test that it frees a static variable, except
+    throug hthe memory leak tests
+*/
+
+#define MISC_NUM                32
+#define MISC_NAME              "META00"
+#define NUM_BIAS_DATA           10
+#define TEST_NUM_ROWS           (8+1)
+#define TEST_NUM_COLS           (8+1)
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+#include "models/pmModel_GAUSS.c"
+#include "models/pmModel_PGAUSS.c"
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(readout);
+}
+
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(37);
+
+    // ----------------------------------------------------------------------
+    // Test pmModelClassAlloc()
+    // pmModelClass *pmModelClassAlloc (int nModels)
+    {
+        psMemId id = psMemGetId();
+        pmModelClass *modelClass = pmModelClassAlloc(4);
+        ok(modelClass != NULL && psMemCheckModelClass(modelClass), "pmModelClassAlloc() returned a non-NULL pmModelClass");
+        psFree(modelClass);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // Test pmModelClassCleanup(), pmModelClassSelect()
+    // Basically, call pmModelClassInit(), then pmModelClassCleanup(), and ensure that
+    // various default models are not there.
+    // XXX: We don't run this test because the spec changed and pmModelClassSelect() now calls
+    // pmModelClassInit().
+    if (0) {
+        psMemId id = psMemGetId();
+        pmModelClassInit();
+        pmModelClassCleanup();
+        ok(NULL == pmModelClassSelect(0), "pmModelClassCleanup(): removed PS_MODEL_GAUSS");
+        ok(NULL == pmModelClassSelect(1), "pmModelClassCleanup(): removed PS_MODEL_PGAUSS");
+        ok(NULL == pmModelClassSelect(2), "pmModelClassCleanup(): removed PS_MODEL_QGAUSS");
+        ok(NULL == pmModelClassSelect(3), "pmModelClassCleanup(): removed PS_MODEL_RGAUSS");
+        ok(NULL == pmModelClassSelect(4), "pmModelClassCleanup(): removed PS_MODEL_SERSIC");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // Test pmModelClassInit(), pmModelClassGetName()
+    // Basically, call pmModelClassCleanup(), then pmModelClassInit() and ensure that
+    // various default models are there.
+    {
+        psMemId id = psMemGetId();
+
+        pmModelClassCleanup();
+        ok(pmModelClassInit(), "pmModelClassInit() returned TRUE");
+        ok(!strcmp(pmModelClassGetName(0), "PS_MODEL_GAUSS"), "pmModelClassInit() added pmModel PS_MODEL_GAUSS");
+        ok(!strcmp(pmModelClassGetName(1), "PS_MODEL_PGAUSS"), "pmModelClassInit() added pmModel PS_MODEL_PGAUSS");
+        ok(!strcmp(pmModelClassGetName(2), "PS_MODEL_QGAUSS"), "pmModelClassInit() added pmModel PS_MODEL_QGAUSS");
+        ok(!strcmp(pmModelClassGetName(3), "PS_MODEL_RGAUSS"), "pmModelClassInit() added pmModel PS_MODEL_RGAUSS");
+        ok(!strcmp(pmModelClassGetName(4), "PS_MODEL_SERSIC"), "pmModelClassInit() added pmModel PS_MODEL_SERSIC");
+        ok(!pmModelClassInit(), "pmModelClassInit() returned FALSE (2nd time)");
+        ok(NULL == pmModelClassGetName(-1), "pmModelClassGetName(-1) returned NULL");
+        ok(NULL == pmModelClassGetName(1000), "pmModelClassGetName(1000) returned NULL");
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // Test pmModelClassGetType()
+    {
+        psMemId id = psMemGetId();
+        pmModelClassInit();
+        ok(-1 == pmModelClassGetType("BOGUS"), "pmModelClassGetType(BOGUS) returned -1");
+        ok(0 == pmModelClassGetType("PS_MODEL_GAUSS"), "pmModelClassGetType(PS_MODEL_GAUSS) successful");
+        ok(1 == pmModelClassGetType("PS_MODEL_PGAUSS"), "pmModelClassGetType(PS_MODEL_PGAUSS) successful");
+        ok(2 == pmModelClassGetType("PS_MODEL_QGAUSS"), "pmModelClassGetType(PS_MODEL_QGAUSS) successful");
+        ok(3 == pmModelClassGetType("PS_MODEL_RGAUSS"), "pmModelClassGetType(PS_MODEL_RGAUSS) successful");
+        ok(4 == pmModelClassGetType("PS_MODEL_SERSIC"), "pmModelClassGetType(PS_MODEL_SERSIC) successful");
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // Test pmModelClassParameterCount()
+    {
+        psMemId id = psMemGetId();
+        pmModelClassInit();
+        ok(0 == pmModelClassParameterCount(-1), "pmModelClassParameterCount(-1) returned 0");
+        ok(0 == pmModelClassParameterCount(1000), "pmModelClassParameterCount(1000) returned 0");
+        ok(7 == pmModelClassParameterCount(0), "pmModelClassParameterCount(0) returned 7");
+        ok(7 == pmModelClassParameterCount(1), "pmModelClassParameterCount(1) returned 7");
+        ok(8 == pmModelClassParameterCount(2), "pmModelClassParameterCount(2) returned 8");
+        ok(8 == pmModelClassParameterCount(3), "pmModelClassParameterCount(3) returned 8");
+        ok(8 == pmModelClassParameterCount(4), "pmModelClassParameterCount(4) returned 8");
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // Test pmModelClassSelect()
+    // pmModelClass *pmModelClassSelect (pmModelType type)
+    {
+        psMemId id = psMemGetId();
+        pmModelClassInit();
+        pmModelClass *model = NULL;
+        model = pmModelClassSelect(-1);
+        ok(model == NULL, "pmModelClassSelect(-1) successful");
+        model = pmModelClassSelect(1000);
+        ok(model == NULL, "pmModelClassSelect(1000) successful");
+        model = pmModelClassSelect(0);
+        ok(model != NULL && !strcmp(model->name, "PS_MODEL_GAUSS"), "pmModelClassSelect(0) successful");
+        model = pmModelClassSelect(1);
+        ok(model != NULL && !strcmp(model->name, "PS_MODEL_PGAUSS"), "pmModelClassSelect(1) successful");
+        model = pmModelClassSelect(2);
+        ok(model != NULL && !strcmp(model->name, "PS_MODEL_QGAUSS"), "pmModelClassSelect(2) successful");
+        model = pmModelClassSelect(3);
+        ok(model != NULL && !strcmp(model->name, "PS_MODEL_RGAUSS"), "pmModelClassSelect(3) successful");
+        model = pmModelClassSelect(4);
+        ok(model != NULL && !strcmp(model->name, "PS_MODEL_SERSIC"), "pmModelClassSelect(4) successful");
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // Test pmModelClassAdd()
+    // We create a new modelClass, then add it, then ensure that it exists.
+    {
+        psMemId id = psMemGetId();
+        pmModelClassInit();
+        pmModelClass *newModel = pmModelClassAlloc(1);
+        newModel->name = psStringCopy("PS_MODEL_NEW00");
+        newModel->nParams = 22;
+        pmModelClassAdd(newModel);
+        int ID = pmModelClassGetType("PS_MODEL_NEW00");
+        ok(ID != -1 && !strcmp("PS_MODEL_NEW00", pmModelClassGetName(ID)), "pmModelClassAdd() added the new model successfully");
+        psFree(newModel->name);
+        psFree(newModel);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmModelUtils.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmModelUtils.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmModelUtils.c	(revision 22157)
@@ -0,0 +1,203 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS:
+	All functions are tested.
+*/
+
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+#define NUM_ROWS		8
+#define NUM_COLS		16
+#define TEST_MODEL_CLASS_TYPE 1
+#define TEST_FLOATS_EQUAL(X, Y) (abs(X - Y) < 0.0001)
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(46);
+
+    // ----------------------------------------------------------------------
+    // pmModelFromPSF() tests
+    // pmModel *pmModelFromPSF (pmModel *modelEXT, pmPSF *psf)
+    // call pmModelFromPSF() with NULL pmModel input parameter
+    {
+        psMemId id = psMemGetId();
+        pmModelClassInit();
+        pmModel *model = pmModelAlloc(TEST_MODEL_CLASS_TYPE);
+        pmPSFOptions *psfOptions = pmPSFOptionsAlloc();
+        psfOptions->type = pmModelClassGetType("PS_MODEL_GAUSS");
+        pmPSF *psf = pmPSFAlloc(psfOptions);
+        pmModel *tmpModel = pmModelFromPSF(NULL, psf);
+        ok(tmpModel == NULL, "pmModelFromPSF() returned NULL with NULL pmModel input parameter");
+        psFree(model);
+        psFree(psfOptions);
+        psFree(psf);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // call pmModelFromPSF() with NULL pmPSF input parameter
+    {
+        psMemId id = psMemGetId();
+        pmModelClassInit();
+        pmModel *model = pmModelAlloc(TEST_MODEL_CLASS_TYPE);
+        pmPSFOptions *psfOptions = pmPSFOptionsAlloc();
+        psfOptions->type = pmModelClassGetType("PS_MODEL_GAUSS");
+        pmPSF *psf = pmPSFAlloc(psfOptions);
+        pmModel *tmpModel = pmModelFromPSF(model, NULL);
+        ok(tmpModel == NULL, "pmModelFromPSF() returned NULL with NULL pmPSF input parameter");
+        psFree(model);
+        psFree(psfOptions);
+        psFree(psf);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // call pmModelFromPSF() with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        pmModelClassInit();
+        pmModel *model = pmModelAlloc(TEST_MODEL_CLASS_TYPE);
+        pmPSFOptions *psfOptions = pmPSFOptionsAlloc();
+        psfOptions->type = pmModelClassGetType("PS_MODEL_GAUSS");
+        pmPSF *psf = pmPSFAlloc(psfOptions);
+        pmModel *tmpModel = pmModelFromPSF(model, psf);
+        ok(tmpModel != NULL, "pmModelFromPSF() returned non-NULL with acceptable input parameters");
+
+        pmModel *testModelPSF = pmModelAlloc(psf->type);
+        model->modelFromPSF(testModelPSF, model, psf);
+        ok(tmpModel->type == testModelPSF->type, "pmModelFromPSF() set the model->type correctly");
+        ok(tmpModel->chisq == testModelPSF->chisq, "pmModelFromPSF() set the model->chisq correctly");
+        ok(tmpModel->chisqNorm == testModelPSF->chisqNorm, "pmModelFromPSF() set the model->chisqNorm correctly");
+        ok(tmpModel->nDOF == testModelPSF->nDOF, "pmModelFromPSF() set the model->nDOF correctly");
+        ok(tmpModel->nIter == testModelPSF->nIter, "pmModelFromPSF() set the model->nIter correctly");
+        ok(tmpModel->flags == testModelPSF->flags, "pmModelFromPSF() set the model->flags correctly");
+        ok(tmpModel->radiusFit == testModelPSF->radiusFit, "pmModelFromPSF() set the model->radiusFit correctly");
+        ok(tmpModel->modelFunc == testModelPSF->modelFunc, "pmModelFromPSF() set the model->modelFunc correctly");
+        ok(tmpModel->modelFlux == testModelPSF->modelFlux, "pmModelFromPSF() set the model->modelFlux correctly");
+        ok(tmpModel->modelRadius == testModelPSF->modelRadius, "pmModelFromPSF() set the model->modelRadius correctly");
+        ok(tmpModel->modelLimits == testModelPSF->modelLimits, "pmModelFromPSF() set the model->modelLimits correctly");
+        ok(tmpModel->modelGuess == testModelPSF->modelGuess, "pmModelFromPSF() set the model->modelGuess correctly");
+        ok(tmpModel->modelFromPSF == testModelPSF->modelFromPSF, "pmModelFromPSF() set the model->modelFromPSF correctly");
+        ok(tmpModel->modelParamsFromPSF == testModelPSF->modelParamsFromPSF, "pmModelFromPSF() set the model->modelParamsFromPSF correctly");
+        ok(tmpModel->modelFitStatus == testModelPSF->modelFitStatus, "pmModelFromPSF() set the model->modelFitStatus correctly");
+
+        psFree(testModelPSF);
+        psFree(tmpModel);
+        psFree(model);
+        psFree(psfOptions);
+        psFree(psf);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmModelFromPSFforXY() tests
+    // pmModel *pmModelFromPSFforXY (pmPSF *psf, float Xo, float Yo, float Io)
+    // call pmModelFromPSFforXY() with NULL pmPSF input parameter
+    {
+        psMemId id = psMemGetId();
+        pmModelClassInit();
+        pmPSFOptions *psfOptions = pmPSFOptionsAlloc();
+        psfOptions->type = pmModelClassGetType("PS_MODEL_GAUSS");
+        pmPSF *psf = pmPSFAlloc(psfOptions);
+        pmModel *tmpModel = pmModelFromPSFforXY(NULL, 1.0, 2.0, 3.0);
+        ok(tmpModel == NULL, "pmModelFromPSFforXY() returned NULL with NULL pmPSF input parameter");
+        psFree(psfOptions);
+        psFree(psf);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // call pmModelFromPSFforXY() with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        pmModelClassInit();
+        pmPSFOptions *psfOptions = pmPSFOptionsAlloc();
+        psfOptions->type = pmModelClassGetType("PS_MODEL_GAUSS");
+        pmPSF *psf = pmPSFAlloc(psfOptions);
+
+        pmModel *testModelPSF = pmModelAlloc (psf->type);
+        testModelPSF->modelParamsFromPSF(testModelPSF, psf, 1.0, 2.0, 3.0);
+
+        pmModel *tmpModel = pmModelFromPSFforXY(psf, 1.0, 2.0, 3.0);
+        ok(tmpModel != NULL, "pmModelFromPSFforXY() returned non-NULL with acceptable input parameters");
+
+        ok(tmpModel->type == testModelPSF->type, "pmModelFromPSF() set the model->type correctly");
+        ok(tmpModel->chisq == testModelPSF->chisq, "pmModelFromPSF() set the model->chisq correctly");
+        ok(TEST_FLOATS_EQUAL(tmpModel->chisqNorm, testModelPSF->chisqNorm), "pmModelFromPSF() set the model->chisqNorm correctly");
+        ok(tmpModel->nDOF == testModelPSF->nDOF, "pmModelFromPSF() set the model->nDOF correctly");
+        ok(tmpModel->nIter == testModelPSF->nIter, "pmModelFromPSF() set the model->nIter correctly");
+        ok(tmpModel->flags == testModelPSF->flags, "pmModelFromPSF() set the model->flags correctly");
+        ok(tmpModel->radiusFit == testModelPSF->radiusFit, "pmModelFromPSF() set the model->radiusFit correctly");
+        ok(tmpModel->modelFunc == testModelPSF->modelFunc, "pmModelFromPSF() set the model->modelFunc correctly");
+        ok(tmpModel->modelFlux == testModelPSF->modelFlux, "pmModelFromPSF() set the model->modelFlux correctly");
+        ok(tmpModel->modelRadius == testModelPSF->modelRadius, "pmModelFromPSF() set the model->modelRadius correctly");
+        ok(tmpModel->modelLimits == testModelPSF->modelLimits, "pmModelFromPSF() set the model->modelLimits correctly");
+        ok(tmpModel->modelGuess == testModelPSF->modelGuess, "pmModelFromPSF() set the model->modelGuess correctly");
+        ok(tmpModel->modelFromPSF == testModelPSF->modelFromPSF, "pmModelFromPSF() set the model->modelFromPSF correctly");
+        ok(tmpModel->modelParamsFromPSF == testModelPSF->modelParamsFromPSF, "pmModelFromPSF() set the model->modelParamsFromPSF correctly");
+        ok(tmpModel->modelFitStatus == testModelPSF->modelFitStatus, "pmModelFromPSF() set the model->modelFitStatus correctly");
+
+        psFree(tmpModel);
+        psFree(testModelPSF);
+        psFree(psfOptions);
+        psFree(psf);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmModelSetFlux() tests
+    // bool pmModelSetFlux(pmModel *model, float flux) {
+    // call pmModelSetFlux() with NULL pmPSF input parameter
+    {
+        psMemId id = psMemGetId();
+        pmModelClassInit();
+        pmModel *model = pmModelAlloc(TEST_MODEL_CLASS_TYPE);
+        bool rc = pmModelSetFlux(NULL, 1.0);
+        ok(!rc, "pmModelSetFlux(() returned FALSE with NULL pmModel input parameter");
+        psFree(model);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // call pmModelSetFlux() with acceptable input parameters
+    // XXX: We should probably test with more input values
+    {
+        psMemId id = psMemGetId();
+        pmModelClassInit();
+        pmModel *model = pmModelAlloc(TEST_MODEL_CLASS_TYPE);
+        model->params->data.F32[PM_PAR_SXX] = 1.0;
+        model->params->data.F32[PM_PAR_SYY] = 1.0;
+        model->params->data.F32[PM_PAR_SXY] = 1.0;
+
+        // Compute the test flux value
+        float tmpF = model->params->data.F32[PM_PAR_I0];
+        model->params->data.F32[PM_PAR_I0] = 1.0;
+        float testFlux = model->modelFlux (model->params);
+        testFlux = 1.0 / testFlux;    
+        model->params->data.F32[PM_PAR_I0] = tmpF;
+
+        bool rc = pmModelSetFlux(model, 1.0);
+        ok(rc, "pmModelSetFlux(() returned TRUE with acceptable input parameters");
+        ok(TEST_FLOATS_EQUAL(testFlux, model->params->data.F32[PM_PAR_I0]), "pmModelSetFlux() set the flux correctly");
+        psFree(model);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmMoments.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmMoments.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmMoments.c	(revision 22157)
@@ -0,0 +1,33 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    plan_tests(11);
+
+    // Test pmMomentsAlloc()
+    {
+        psMemId id = psMemGetId();
+        pmMoments *tmpMoments = pmMomentsAlloc();
+        ok(tmpMoments != NULL, "pmMomentsAlloc() returned a non-NULL pmMoments");
+        skip_start(tmpMoments == NULL, 9, "Skipping tests because pmMomentsAlloc() returned NULL");
+        ok(tmpMoments->x == 0.0, "pmMomentsAlloc set->x correctly");
+        ok(tmpMoments->y == 0.0, "pmMomentsAlloc set->y correctly");
+        ok(tmpMoments->Sx == 0.0, "pmMomentsAlloc set->Sx correctly");
+        ok(tmpMoments->Sy == 0.0, "pmMomentsAlloc set->Sy correctly");
+        ok(tmpMoments->Sxy == 0.0, "pmMomentsAlloc set->Sxy correctly");
+        ok(tmpMoments->Sum == 0.0, "pmMomentsAlloc set->Sum correctly");
+        ok(tmpMoments->Peak == 0.0, "pmMomentsAlloc set->Peak correctly");
+        ok(tmpMoments->Sky == 0.0, "pmMomentsAlloc set->Sky correctly");
+        ok(tmpMoments->nPixels == 0, "pmMomentsAlloc set->nPixels correctly");
+        psFree(tmpMoments);
+        skip_end();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmPSF.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmPSF.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmPSF.c	(revision 22157)
@@ -0,0 +1,326 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/*
+Tested:
+    pmPSFOptions *pmPSFOptionsAlloc()
+    pmPSF *pmPSFAlloc (pmPSFOptions *options)
+    bool psMemCheckPSF(psPtr ptr)
+Must test:
+    double pmPSF_SXYfromModel (psF32 *modelPar)
+    double pmPSF_SXYtoModel (psF32 *fittedPar)
+    bool pmGrowthCurveGenerate (pmReadout *readout, pmPSF *psf, bool ignore, psMas!
+    pmPSF *pmPSFBuildSimple (char *typeName, float sxx, float syy, float sxy, ...)
+    bool pmPSF_AxesToModel (psF32 *modelPar, psEllipseAxes axes)
+    bool pmPSF_FitToModel (psF32 *fittedPar, float minMinorAxis)
+    psEllipsePol pmPSF_ModelToFit (psF32 *modelPar)
+    psEllipseAxes pmPSF_ModelToAxes (psF32 *modelPar, double maxAR)
+*/
+
+
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         10
+#define TEST_FLOATS_EQUAL(X, Y) (abs((X) - (Y)) < 0.0001)
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(83);
+
+    // ----------------------------------------------------------------------
+    // pmPSFOptionsAlloc() tests
+    // pmPSFOptions *pmPSFOptionsAlloc()
+    {
+        psMemId id = psMemGetId();
+        pmPSFOptions *psfOptions = pmPSFOptionsAlloc();
+        ok(psfOptions != NULL && psMemCheckPSFOptions(psfOptions), "pmPSFOptionsAlloc() returned non-NULL");
+        ok(psfOptions->type == 0, "pmPSFOptionsAlloc set pmPSFOptions->type ");
+        ok(psfOptions->stats == NULL, "pmPSFOptionsAlloc set pmPSFOptions->stats ");
+        ok(psfOptions->psfTrendMode == PM_TREND_NONE, "pmPSFOptionsAlloc set pmPSFOptions->psfTrendMode ");
+        ok(psfOptions->psfTrendNx == 0, "pmPSFOptionsAlloc set pmPSFOptions->psfTrendNx ");
+        ok(psfOptions->psfTrendNy == 0, "pmPSFOptionsAlloc set pmPSFOptions->psfTrendNy ");
+        ok(psfOptions->psfFieldNx == 0, "pmPSFOptionsAlloc set pmPSFOptions->psfFieldNx ");
+        ok(psfOptions->psfFieldNy == 0, "pmPSFOptionsAlloc set pmPSFOptions->psfFieldNy ");
+        ok(psfOptions->psfFieldXo == 0, "pmPSFOptionsAlloc set pmPSFOptions->psfFieldXo ");
+        ok(psfOptions->psfFieldYo == 0, "pmPSFOptionsAlloc set pmPSFOptions->psfFieldYo ");
+        ok(psfOptions->poissonErrorsPhotLMM == true, "pmPSFOptionsAlloc set pmPSFOptions->poissonErrorsPhotLMM ");
+        ok(psfOptions->poissonErrorsPhotLin == false, "pmPSFOptionsAlloc set pmPSFOptions->poissonErrorsPhotLin ");
+        ok(psfOptions->poissonErrorsParams  == true, "pmPSFOptionsAlloc set pmPSFOptions->poissonErrorsParams ");
+        psFree(psfOptions);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmPSFAlloc() tests
+    // call pmPSFAlloc() with NULL input parameters
+    #define TEST_POISSON_ERRORS true
+    {
+        psMemId id = psMemGetId();
+        pmPSF *psf = pmPSFAlloc(NULL);
+        ok(psf == NULL, "pmPSFAlloc() returned NULL with NULL input parameters");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // call pmPSFAlloc() with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        pmPSFOptions *psfOptions = pmPSFOptionsAlloc();
+        psfOptions->psfTrendNx = 1;
+        psfOptions->psfTrendNy = 2;
+        psfOptions->psfFieldNx = 3;
+        psfOptions->psfFieldNy = 4;
+        psfOptions->psfFieldXo = 5;
+        psfOptions->psfFieldYo = 6;
+        pmModelClassInit();
+        psfOptions->type = pmModelClassGetType("PS_MODEL_GAUSS");
+        pmPSF *psf = pmPSFAlloc(psfOptions);
+        ok(psf != NULL && psMemCheckPSF(psf), "pmPSFAlloc() returned non-NULL");
+        ok(psf->type == psfOptions->type, "pmPSFAlloc() set the pmPSF->type correctly");
+        ok(psf->chisq == 0.0, "pmPSFAlloc() set the pmPSF->chisq correctly");
+        ok(psf->ApResid == 0.0, "pmPSFAlloc() set the pmPSF->ApResid correctly");
+        ok(psf->dApResid == 0.0, "pmPSFAlloc() set the pmPSF->dApResid correctly");
+        ok(psf->skyBias == 0.0, "pmPSFAlloc() set the pmPSF->skyBias correctly");
+        ok(psf->skySat == 0.0, "pmPSFAlloc() set the pmPSF->skySat correctly");
+        ok(psf->nPSFstars == 0, "pmPSFAlloc() set the pmPSF->nPSFstars correctly");
+        ok(psf->nApResid == 0, "pmPSFAlloc() set the pmPSF->nApResid correctly");
+        int Nparams = pmModelClassParameterCount(psfOptions->type);
+        ok(psf->params != NULL &&
+           psMemCheckArray(psf->params) &&
+           psf->params->n == Nparams, "pmPSFAlloc() set the pmPSF->params correctly");
+        ok(psf->poissonErrorsPhotLMM == psfOptions->poissonErrorsPhotLMM, "pmPSFAlloc() set the pmPSF->poissonErrorsPhotLMM");
+        ok(psf->poissonErrorsPhotLin == psfOptions->poissonErrorsPhotLin, "pmPSFAlloc() set the pmPSF->poissonErrorsPhotLin");
+        ok(psf->poissonErrorsParams == psfOptions->poissonErrorsParams, "pmPSFAlloc() set the pmPSF->poissonErrorsParams");
+        ok(psf->ApTrend == NULL, "pmPSFAlloc() set the pmPSF->ApTrend");
+        ok(psf->FluxScale == NULL, "pmPSFAlloc() set the pmPSF->FluxScale");
+        ok(psf->growth == NULL, "pmPSFAlloc() set the pmPSF->growth");
+        ok(psf->residuals == NULL, "pmPSFAlloc() set the pmPSF->residuals");
+        ok(psf->psfTrendMode == psfOptions->psfTrendMode, "pmPSFAlloc() set the pmPSF->psfTrendMode");
+        ok(psf->trendNx == psfOptions->psfTrendNx, "pmPSFAlloc() set the pmPSF->trendNx (%d %d)", psf->trendNx, psfOptions->psfTrendNx);
+        ok(psf->trendNy == psfOptions->psfTrendNy, "pmPSFAlloc() set the pmPSF->trendNy (%d %d)", psf->trendNy, psfOptions->psfTrendNy);
+        ok(psf->fieldNx == psfOptions->psfFieldNx, "pmPSFAlloc() set the pmPSF->fieldNx");
+        ok(psf->fieldNy == psfOptions->psfFieldNy, "pmPSFAlloc() set the pmPSF->fieldNy");
+        ok(psf->fieldXo == psfOptions->psfFieldXo, "pmPSFAlloc() set the pmPSF->fieldXo");
+        ok(psf->fieldYo == psfOptions->psfFieldYo, "pmPSFAlloc() set the pmPSF->fieldYo");
+        ok(psf->ApTrend == NULL, "pmPSFAlloc() set the pmPSF->ApTrend correctly");
+        ok(psf->FluxScale == NULL, "pmPSFAlloc() set the pmPSF->FluxScale correctly");
+
+        if (psf->poissonErrorsPhotLMM) {
+            ok(psf->ChiTrend->nX == 1, "pmPSFAlloc() set the pmPSF->ChiTrend correctly");
+	} else {
+            ok(psf->ChiTrend->nX == 2, "pmPSFAlloc() set the pmPSF->ChiTrend correctly");
+	}
+        ok(psf->growth == NULL, "pmPSFAlloc() set the pmPSF->growth correctly");
+        ok(psf->residuals == NULL, "pmPSFAlloc() set the pmPSF->residuals correctly");
+        ok(psf->params != NULL && psMemCheckArray(psf->params) && psf->params->n == Nparams, 
+           "pmPSFAlloc() set the pmPSF->params psVector correctly");
+
+        pmModelClassCleanup();
+        psFree(psf);
+        psFree(psfOptions);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmPSF_SXYfromModel() tests
+    // Call pmPSF_SXYfromModel() with NULL input parameters
+    {
+        psMemId id = psMemGetId();
+        double tmpD = pmPSF_SXYfromModel(NULL);
+        ok(isnan(tmpD), "pmPSF_SXYfromModel() returned NULL with NULL input parameters");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmPSF_SXYfromModel() with NULL input parameters
+    {
+        psMemId id = psMemGetId();
+        psF32 modelPar[20];
+        modelPar[PM_PAR_SXX] = 2.0;
+        modelPar[PM_PAR_SYY] = 3.0;
+        modelPar[PM_PAR_SXY] = 5.0;
+        double SXX = modelPar[PM_PAR_SXX];
+        double SYY = modelPar[PM_PAR_SYY];
+        double SXY = modelPar[PM_PAR_SXY];
+        psF32 verF = SXY / PS_SQR(1.0 / PS_SQR(SXX) + 1.0 / PS_SQR(SYY));
+        psF32 testF = pmPSF_SXYfromModel(modelPar);
+        ok(verF == testF, "pmPSF_SXYfromModel() calculated correctly");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmPSF_SXYtoModel() tests
+    // Call pmPSF_SXYtoModel() with NULL input parameters
+    {
+        psMemId id = psMemGetId();
+        double tmpD = pmPSF_SXYtoModel(NULL);
+        ok(isnan(tmpD), "pmPSF_SXYtoModel() returned NULL with NULL input parameters");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmPSF_SXYtoModel() with NULL input parameters
+    {
+        psMemId id = psMemGetId();
+        psF32 fittedPar[20];
+        fittedPar[PM_PAR_SXX] = 2.0;
+        fittedPar[PM_PAR_SYY] = 3.0;
+        fittedPar[PM_PAR_SXY] = 5.0;
+        double SXX = fittedPar[PM_PAR_SXX];
+        double SYY = fittedPar[PM_PAR_SYY];
+        double fit = fittedPar[PM_PAR_SXY];
+        double verF = fit * PS_SQR(1.0 / PS_SQR(SXX) + 1.0 / PS_SQR(SYY));
+        psF32 testF = pmPSF_SXYtoModel(fittedPar);
+        ok(TEST_FLOATS_EQUAL(verF, testF), "pmPSF_SXYtoModel() calculated correctly (%f %f)", verF, testF);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmPSF_FitToModel() tests
+    // Call pmPSF_FitToModel() with NULL input parameters
+    {
+        psMemId id = psMemGetId();
+        bool rc = pmPSF_FitToModel(NULL, 0.0);
+        ok(rc == false, "pmPSF_FitToModel() returned NULL with NULL input parameters");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmPSF_FitToModel() with NULL input parameters
+    {
+        #define MIN_MINOR_AXIS 1.0
+        psMemId id = psMemGetId();
+        psF32 origFittedPar[3], testFittedPar[3];
+        psEllipsePol pol;
+        pol.e0 = origFittedPar[PM_PAR_E0] = testFittedPar[PM_PAR_E0] = 2.0;
+        pol.e1 = origFittedPar[PM_PAR_E1] = testFittedPar[PM_PAR_E1] = 3.0;
+        pol.e2 = origFittedPar[PM_PAR_E2] = testFittedPar[PM_PAR_E2] = 5.0;
+        ok(pmPSF_FitToModel(testFittedPar, MIN_MINOR_AXIS), "pmPSF_FitToModel() returned TRUE with acceptable input parameters");
+
+        psEllipseAxes axes;
+        psEllipsePolToAxes(pol, MIN_MINOR_AXIS, &axes);
+        psEllipseShape shape = psEllipseAxesToShape(axes);
+
+        ok(TEST_FLOATS_EQUAL(testFittedPar[PM_PAR_SXX], shape.sx * M_SQRT2),
+          "pmPSF_FitToModel() set fittedPar[PM_PAR_SXX] correctly");
+        ok(TEST_FLOATS_EQUAL(testFittedPar[PM_PAR_SYY], shape.sy * M_SQRT2),
+          "pmPSF_FitToModel() set fittedPar[PM_PAR_SYY] correctly");
+        ok(TEST_FLOATS_EQUAL(testFittedPar[PM_PAR_SXY], shape.sxy),
+          "pmPSF_FitToModel() set fittedPar[PM_PAR_SXY] correctly");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmPSF_ModelToFit() tests
+    // psEllipsePol pmPSF_ModelToFit (psF32 *modelPar)
+    // Call pmPSF_ModelToFit() with NULL input parameters
+    {
+        psMemId id = psMemGetId();
+        psEllipsePol pol = pmPSF_ModelToFit(NULL);
+        ok(isnan(pol.e0), "pmPSF_ModelToFit() returned NULL (psEllipsePol) with NULL input parameters");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmPSF_ModelToFit() with NULL input parameters
+    {
+        #define MIN_MINOR_AXIS 1.0
+        psMemId id = psMemGetId();
+        psF32 modelPar[3];
+        modelPar[PM_PAR_SXX] = 2.0;
+        modelPar[PM_PAR_SYY] = 3.0;
+        modelPar[PM_PAR_SXY] = 5.0;
+
+        psEllipsePol pol = pmPSF_ModelToFit(modelPar);
+        ok(!isnan(pol.e0), "pmPSF_ModelToFit() returned TRUE with acceptable input parameters");
+
+        psEllipseShape shape;
+        shape.sx  = modelPar[PM_PAR_SXX] / M_SQRT2;
+        shape.sy  = modelPar[PM_PAR_SYY] / M_SQRT2;
+        shape.sxy = modelPar[PM_PAR_SXY];
+        psEllipsePol actPol = psEllipseShapeToPol(shape);
+        ok(TEST_FLOATS_EQUAL(pol.e0, actPol.e0), "pmPSF_ModelToFit() set psEllipsePol.e0 correctly");
+        ok(TEST_FLOATS_EQUAL(pol.e1, actPol.e1), "pmPSF_ModelToFit() set psEllipsePol.e1 correctly");
+        ok(TEST_FLOATS_EQUAL(pol.e2, actPol.e2), "pmPSF_ModelToFit() set psEllipsePol.e2 correctly");
+
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmPSF_ModelToAxes() tests
+    // psEllipseAxes pmPSF_ModelToAxes (psF32 *modelPar, double maxAR)
+    // Call pmPSF_ModelToAxes() with NULL input parameters
+    {
+        psMemId id = psMemGetId();
+        psEllipseAxes axes = pmPSF_ModelToAxes(NULL, 1.0);
+        ok(isnan(axes.major), "pmPSF_ModelToAxes() returned NULL (psEllipseAxes) with NULL input parameters");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmPSF_ModelToAxes() with NULL input parameters
+    {
+        #define MAX_AX 1.0
+        psMemId id = psMemGetId();
+        psF32 modelPar[3];
+        modelPar[PM_PAR_SXX] = 2.0;
+        modelPar[PM_PAR_SYY] = 3.0;
+        modelPar[PM_PAR_SXY] = 5.0;
+
+        psEllipseShape shape;
+        shape.sx  = modelPar[PM_PAR_SXX] / M_SQRT2;
+        shape.sy  = modelPar[PM_PAR_SYY] / M_SQRT2;
+        shape.sxy = modelPar[PM_PAR_SXY];
+        psEllipseAxes axes = psEllipseShapeToAxes (shape, MAX_AX);
+
+        psEllipseAxes actAxes = pmPSF_ModelToAxes(modelPar, MAX_AX);
+        ok(!isnan(actAxes.major), "pmPSF_ModelToAxes() returned TRUE with acceptable input parameters");
+        ok(TEST_FLOATS_EQUAL(actAxes.major, axes.major), "pmPSF_ModelToAxes() set psEllipseAxes.major correctly");
+        ok(TEST_FLOATS_EQUAL(actAxes.minor, axes.minor), "pmPSF_ModelToAxes() set psEllipseAxes.minor correctly");
+        ok(TEST_FLOATS_EQUAL(actAxes.theta, axes.theta), "pmPSF_ModelToAxes() set psEllipseAxes.theta correctly");
+
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmPSF_AxesToModel() tests
+    // bool pmPSF_AxesToModel (psF32 *modelPar, psEllipseAxes axes)
+    // Call pmPSF_AxesToModel() with NULL input parameters
+    {
+        psMemId id = psMemGetId();
+        psEllipseAxes axes;
+        bool rc = pmPSF_AxesToModel(NULL, axes);
+        ok(rc == false, "pmPSF_AxesToModel() returned NULL with NULL input parameters");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmPSF_AxesToModel() with NULL input parameters
+    {
+        #define MIN_MINOR_AXIS 1.0
+        psMemId id = psMemGetId();
+        psF32 modelPar[3];
+        psEllipseAxes axes;
+        axes.major = 2.0;
+        axes.minor = 3.0;
+        axes.theta = 5.0;
+        ok(pmPSF_AxesToModel(modelPar, axes), "pmPSF_AxesToModel() returned TRUE with acceptable input parameters");
+        psEllipseShape shape = psEllipseAxesToShape(axes);
+        ok(TEST_FLOATS_EQUAL(modelPar[PM_PAR_SXX], shape.sx * M_SQRT2), "pmPSF_AxesToModel() set modelPar[PM_PAR_SXX] correctly");
+        ok(TEST_FLOATS_EQUAL(modelPar[PM_PAR_SYY], shape.sy * M_SQRT2), "pmPSF_AxesToModel() set modelPar[PM_PAR_SYY] correctly");
+        ok(TEST_FLOATS_EQUAL(modelPar[PM_PAR_SXY], shape.sxy), "pmPSF_AxesToModel() set modelPar[PM_PAR_SXY] correctly");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmPSF_IO.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmPSF_IO.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmPSF_IO.c	(revision 22157)
@@ -0,0 +1,467 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS
+    Uder construction.  Only lightly tested so far.
+*/
+
+#define TEST_FLOATS_EQUAL(X, Y) (abs(X - Y) < 0.0001)
+#define NUM_MODELS		5
+
+#define CHIP_ALLOC_NAME        "ChipName"
+#define CELL_ALLOC_NAME        "CellName"
+#define MISC_NUM                32
+#define MISC_NAME              "META00"
+#define MISC_NAME2             "META01"
+#define NUM_BIAS_DATA           10
+#define TEST_NUM_ROWS           4
+#define TEST_NUM_COLS           4
+#define NUM_READOUTS            3
+#define NUM_CELLS               10
+#define NUM_CHIPS               8
+#define NUM_HDUS                5
+#define BASE_IMAGE              10
+#define BASE_MASK               40
+#define BASE_WEIGHT             70
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+
+psPlaneTransform *PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM()
+{
+    psPlaneTransform *pt = psPlaneTransformAlloc(1, 1);
+    pt->x->coeff[1][0] = 1.0;
+    pt->y->coeff[0][1] = 1.0;
+    return(pt);
+}
+
+psPlaneDistort *PS_CREATE_4D_IDENTITY_PLANE_DISTORT()
+{
+    psPlaneDistort *pd = psPlaneDistortAlloc(1, 1, 1, 1);
+    pd->x->coeff[1][0][0][0] = 1.0;
+    pd->y->coeff[0][1][0][0] = 1.0;
+    return(pd);
+}
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(readout);
+}
+
+/******************************************************************************
+generateSimpleCell(): This function generates a pmCell data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmCell *generateSimpleCell(pmChip *chip)
+{
+    pmCell *cell = pmCellAlloc(chip, CELL_ALLOC_NAME);
+
+    psMetadataAddS32(cell->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(cell->readouts, NUM_READOUTS);
+    cell->hdu = pmHDUAlloc("cellExtName");
+    for (int i = 0 ; i < NUM_READOUTS ; i++) {
+        cell->readouts->data[i] = psMemDecrRefCounter((psPtr) generateSimpleReadout(cell));
+    }
+
+    // First try to read data from ../dataFiles, then try dataFiles.
+    bool rc = pmConfigFileRead(&cell->hdu->format, "../dataFiles/camera0/format0.config", "Camera format 0");
+    if (!rc) {
+        rc = pmConfigFileRead(&cell->hdu->format, "dataFiles/camera0/format0.config", "Camera format 0");
+        if (!rc) {
+            diag("pmConfigFileRead() was unsuccessful (from generateSimpleCell())");
+	}
+    }
+
+    cell->hdu->images = psArrayAlloc(NUM_HDUS);
+    cell->hdu->masks = psArrayAlloc(NUM_HDUS);
+    cell->hdu->weights = psArrayAlloc(NUM_HDUS);
+    for (int k = 0 ; k < NUM_HDUS ; k++) {
+        cell->hdu->images->data[k]  = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        cell->hdu->masks->data[k]   = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        cell->hdu->weights->data[k] = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(cell->hdu->images->data[k], (float) (BASE_IMAGE+k));
+        psImageInit(cell->hdu->masks->data[k], (psU8) (BASE_MASK+k));
+        psImageInit(cell->hdu->weights->data[k], (float) (BASE_WEIGHT+k));
+    }
+
+    //XXX: Should the region be set some other way?  Like through the various config files?
+//    psRegion *region = psRegionAlloc(0.0, TEST_NUM_COLS-1, 0.0, TEST_NUM_ROWS-1);
+    psRegion *region = psRegionAlloc(0.0, 0.0, 0.0, 0.0);
+    // You shouldn't have to remove the key from the metadata.  Find out how to simply change the key value.
+    psMetadataRemoveKey(cell->concepts, "CELL.TRIMSEC");
+    psMetadataAddPtr(cell->concepts, PS_LIST_TAIL|PS_META_REPLACE, "CELL.TRIMSEC", PS_DATA_REGION, "I am a region", region);
+    psFree(region);
+    return(cell);
+}
+
+/******************************************************************************
+generateSimpleChip(): This function generates a pmChip data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmChip *generateSimpleChip(pmFPA *fpa)
+{
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    chip->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    chip->fromFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    psMetadataAddS32(chip->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(chip->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(chip->cells, NUM_CELLS);
+    for (int i = 0 ; i < NUM_CELLS ; i++) {
+        chip->cells->data[i] = psMemDecrRefCounter((psPtr) generateSimpleCell(chip));
+    }
+
+    // XXX: Add code to initialize chip pmConcepts
+
+
+    return(chip);
+}
+
+/******************************************************************************
+generateSimpleFPA(): This function generates a pmFPA data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmFPA* generateSimpleFPA(psMetadata *camera)
+{
+    pmFPA* fpa = pmFPAAlloc(camera);
+    fpa->hdu = pmHDUAlloc("cellExtName");
+    fpa->fromTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toTPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    fpa->toSky = psProjectionAlloc(0.0,0.0,10.0,10.0,PS_PROJ_TAN);
+    psMetadataAddS32(fpa->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    if (camera != NULL) {
+        psMetadataAddS32((psMetadata *) fpa->camera, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    }
+    psMetadataAddS32(fpa->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(fpa->chips, NUM_CHIPS);
+    for (int i = 0 ; i < NUM_CHIPS ; i++) {
+        fpa->chips->data[i] = psMemDecrRefCounter((psPtr) generateSimpleChip(fpa));
+    }
+    pmConceptsBlankFPA(fpa);
+    return(fpa);
+}
+
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(28);
+
+
+    // ----------------------------------------------------------------------
+    // pmPSFmodelCheckDataStatusForView() tests
+    // bool pmPSFmodelCheckDataStatusForView (const pmFPAview *view, const pmFPAfile *file)
+    // Call pmPSFmodelCheckDataStatusForView() with NULL pmFPAview input parameter
+    if (1) {
+        psMemId id = psMemGetId();
+
+        pmFPAview *view = pmFPAviewAlloc(32);
+        pmFPAfile *file = pmFPAfileAlloc();
+        bool rc = pmPSFmodelCheckDataStatusForView(NULL, file);
+        ok(rc == false, "pmPSFmodelCheckDataStatusForView() returned FALSE with NULL pmFPAview input parameter");
+        psFree(view);
+        psFree(file);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmPSFmodelCheckDataStatusForView() with NULL pmFPAfile input parameter
+    if (1) {
+        psMemId id = psMemGetId();
+
+        pmFPAview *view = pmFPAviewAlloc(32);
+        pmFPAfile *file = pmFPAfileAlloc();
+        bool rc = pmPSFmodelCheckDataStatusForView(view, NULL);
+        ok(rc == false, "pmPSFmodelCheckDataStatusForView() returned FALSE with NULL pmFPAfile input parameter");
+        psFree(view);
+        psFree(file);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmPSFmodelCheckDataStatusForView() with acceptable input parameters
+    if (1) {
+        psMemId id = psMemGetId();
+
+        pmFPAview *view = pmFPAviewAlloc(32);
+        view->chip = -1;
+        view->cell = -1;
+        pmFPAfile *file = pmFPAfileAlloc();
+        psMetadata *camera = psMetadataAlloc();
+        file->fpa = generateSimpleFPA(camera);
+        bool rc = pmPSFmodelCheckDataStatusForView(view, file);
+        ok(rc == false, "pmPSFmodelCheckDataStatusForView() returned FALSE acceptable input parameters, but no PSPHOT.PSF");
+
+        // Add PSPHOT.PSF to chip->analysis and call pmPSFmodelCheckDataStatusForChip()
+        pmChip *chip = file->fpa->chips->data[0];
+        psVector *junk = psVectorAlloc(10, PS_TYPE_F32);
+        bool rc2 = psMetadataAddPtr(chip->analysis, PS_LIST_HEAD, "PSPHOT.PSF", PS_DATA_VECTOR, NULL, junk);
+        ok(rc2 == true, "added PSPHOT.PSF metadata to chip->analysis");
+        rc = pmPSFmodelCheckDataStatusForView(view, file);
+        ok(rc == true, "pmPSFmodelCheckDataStatusForView() returned TRUE acceptable input parameters");
+
+        psFree(view);
+        psFree(file->fpa);
+        file->fpa = NULL;
+        psFree(file);
+        psFree(camera);
+        psFree(junk);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmPSFmodelCheckDataStatusForFPA() tests
+    // bool pmPSFmodelCheckDataStatusForFPA (const pmFPA *fpa)
+    // Call pmPSFmodelCheckDataStatusForFPA() with NULL pmFPA input parameter
+    if (1) {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA *fpa = generateSimpleFPA(camera);
+        bool rc = pmPSFmodelCheckDataStatusForFPA(NULL);
+        ok(rc == false, "pmPSFmodelCheckDataStatusForFPA() returned FALSE with NULL pmFPA input parameter");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmPSFmodelCheckDataStatusForFPA() with NULL pmFPA->chips input parameter
+    if (1) {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA *fpa = generateSimpleFPA(camera);
+        for (int i = 0 ; i < fpa->chips->n ; i++) {
+            psFree(fpa->chips->data[i]);
+	}
+        bool rc = pmPSFmodelCheckDataStatusForFPA(NULL);
+        ok(rc == false, "pmPSFmodelCheckDataStatusForFPA() returned FALSE with NULL pmFPA->chips input parameter");
+        psFree(fpa);
+        psFree(camera);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmPSFmodelCheckDataStatusForFPA() with acceptable input parameters
+    if (1) {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA *fpa = generateSimpleFPA(camera);
+        bool rc = pmPSFmodelCheckDataStatusForFPA(fpa);
+        ok(rc == false, "pmPSFmodelCheckDataStatusForFPA() returned FALSE with acceptable input parameters, but no PSPHOT.PSF");
+
+        // Add PSPHOT.PSF to chip->analysis and call pmPSFmodelCheckDataStatusForChip()
+        psVector *junk = psVectorAlloc(10, PS_TYPE_F32);
+        pmChip *chip = fpa->chips->data[0];
+        bool rc2 = psMetadataAddPtr(chip->analysis, PS_LIST_HEAD, "PSPHOT.PSF", PS_DATA_VECTOR, NULL, junk);
+        ok(rc2 == true, "added PSPHOT.PSF metadata to chip->analysis");
+        rc = pmPSFmodelCheckDataStatusForFPA(fpa);
+        ok(rc == true, "pmPSFmodelCheckDataStatusForFPA() returned TRUE with acceptable input parameters");
+
+        psFree(fpa);
+        psFree(camera);
+        psFree(junk);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmPSFmodelCheckDataStatusForChip() tests
+    // bool pmPSFmodelCheckDataStatusForChip (const pmChip *chip)
+    // Call pmPSFmodelCheckDataStatusForChip() with NULL pmChip input parameter
+    if (1) {
+        psMemId id = psMemGetId();
+        bool rc = pmPSFmodelCheckDataStatusForChip(NULL);
+        ok(rc == false, "pmPSFmodelCheckDataStatusForChip() returned FALSE with NULL pmChip input parameter");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmPSFmodelCheckDataStatusForChip() with acceptable input parameters
+    if (1) {
+        psMemId id = psMemGetId();
+        psMetadata *camera = psMetadataAlloc();
+        pmFPA *fpa = generateSimpleFPA(camera);
+        pmChip *chip = fpa->chips->data[0];
+        bool rc = pmPSFmodelCheckDataStatusForChip(chip);
+        ok(rc == false, "pmPSFmodelCheckDataStatusForChip() returned false with acceptable pmChip, but no PSPHOT.PSF");
+
+        // Add PSPHOT.PSF to chip->analysis and call pmPSFmodelCheckDataStatusForChip()
+        psVector *junk = psVectorAlloc(10, PS_TYPE_F32);
+        bool rc2 = psMetadataAddPtr(chip->analysis, PS_LIST_HEAD, "PSPHOT.PSF", PS_DATA_VECTOR, NULL, junk);
+        ok(rc2 == true, "added PSPHOT.PSF metadata to chip->analysis");
+        rc = pmPSFmodelCheckDataStatusForChip(chip);
+        ok(rc == true, "pmPSFmodelCheckDataStatusForChip() returned TRUE with acceptable input parameters");
+    
+        psFree(fpa);
+        psFree(camera);
+        psFree(junk);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmPSFmodelWrite() tests
+    // bool pmPSFmodelWrite (psMetadata *analysis, const pmFPAview *view,
+    //                       pmFPAfile *file, const pmConfig *config)
+    // Call pmPSFmodelWrite() with NULL pmFPAview input parameter
+    if (1) {
+        psMemId id = psMemGetId();
+        pmFPAview *view = pmFPAviewAlloc(32);
+        pmFPAfile *file = pmFPAfileAlloc();
+        psMetadata *camera = psMetadataAlloc();
+        file->fpa = generateSimpleFPA(camera);
+        psMetadata *analysis = psMetadataAlloc();
+        pmConfig *config = pmConfigAlloc();
+
+        bool rc = pmPSFmodelWrite(analysis, NULL, file, config);
+        ok(rc == false, "pmPSFmodelWrite() returned FALSE with NULL pmFPAview input parameter");
+
+        psFree(view);
+        psFree(file->fpa);
+        file->fpa = NULL;
+        psFree(file);
+        psFree(camera);
+        psFree(analysis);
+        psFree(config);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmPSFmodelWrite() with NULL pmFPAfile input parameter
+    if (1) {
+        psMemId id = psMemGetId();
+        pmFPAview *view = pmFPAviewAlloc(32);
+        pmFPAfile *file = pmFPAfileAlloc();
+        psMetadata *camera = psMetadataAlloc();
+        file->fpa = generateSimpleFPA(camera);
+        psMetadata *analysis = psMetadataAlloc();
+        pmConfig *config = pmConfigAlloc();
+
+        bool rc = pmPSFmodelWrite(analysis, view, NULL, config);
+        ok(rc == false, "pmPSFmodelWrite() returned FALSE with NULL pmFPAfile input parameter");
+
+        psFree(view);
+        psFree(file->fpa);
+        file->fpa = NULL;
+        psFree(file);
+        psFree(camera);
+        psFree(analysis);
+        psFree(config);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmPSFmodelWrite() with NULL pmFPAfile->file input parameter
+    if (1) {
+        psMemId id = psMemGetId();
+        pmFPAview *view = pmFPAviewAlloc(32);
+        pmFPAfile *file = pmFPAfileAlloc();
+        psMetadata *analysis = psMetadataAlloc();
+        pmConfig *config = pmConfigAlloc();
+
+        bool rc = pmPSFmodelWrite(analysis, view, NULL, config);
+        ok(rc == false, "pmPSFmodelWrite() returned FALSE with NULL pmFPAfile->fpa input parameter");
+
+        psFree(view);
+        psFree(file);
+        psFree(analysis);
+        psFree(config);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmPSFmodelWrite() with acceptable input parameters
+    // XXX: This is currently being coded.  It does not work.
+    if (0) {
+        psMemId id = psMemGetId();
+        pmFPAview *view = pmFPAviewAlloc(32);
+        pmFPAfile *file = pmFPAfileAlloc();
+        psMetadata *camera = psMetadataAlloc();
+        file->fpa = generateSimpleFPA(camera);
+        pmConfigFileRead(&file->camera, "../dataFiles/camera0/camera.config", "CAMERA");
+        psMetadataPrint(stdout, file->camera, 0);
+        psMetadata *menu = psMetadataLookupMetadata(NULL, file->camera, "EXTNAME.RULES");
+        if (!menu) {
+            printf("NOTE: missing EXTNAME.RULES in camera.config\n");
+            exit(1);
+        }
+
+
+        psMetadata *analysis = psMetadataAlloc();
+        pmConfig *config = pmConfigAlloc();
+
+        // XXX: I failed here and then moved on.
+        /*
+        The function reads the PSPHOT recipes and requires that it contains a
+	PSPHOT key in the metadata.  I'm not sure how to do that, but I could
+	have hacked around it.  Then, however, it required several keys in
+	that metadata like PSF.CLUMP.X, PSF.CLUMP.Y, PSF.CLUMP.DX,
+	PSF.CLUMP.DY.  I could also put them in there, but I just gave up.
+	What should I do here?  I could easily create a metadata file with all
+	those metadata keys, but I'd rather use an existing one.
+        */
+
+        if (1) {
+            psMetadata *junk = psMetadataAlloc();
+            bool rc0 = pmConfigFileRead(&junk, "../dataFiles/recipes/psphot.config", "SAVE.PSF");
+            if (!rc0) {
+                rc0 = pmConfigFileRead(&junk, "dataFiles/recipes/psphot.config", "SAVE.PSF");
+	    }
+            ok(rc0, "Successfully read the PSPHOT recipe file");
+//          psMetadata *recipe = psMetadataLookupPtr(&rc0, junk, "SAVE.OUTPUT");
+            bool rc2 = psMetadataLookupBool(&rc0, junk, "SAVE.OUTPUT");
+            printf("rc0 is %d\n", (int) rc0);
+            printf("rc2 is %d\n", (int) rc2);
+//          if (recipe == NULL) printf("ERROR: recipe is NULL\n");
+            psMetadataPrint(stdout, junk, 0);
+            psFree(junk);
+	}
+        if (config->recipes == NULL) printf("COOL: config->recipes is NULL");
+        bool rc0 = pmConfigFileRead(&config->recipes, "../dataFiles/camera0/recipes.config", "PSPHOT");
+        if (!rc0) {
+            rc0 = pmConfigFileRead(&config->recipes, "dataFiles/camera0/recipes.config", "PSPHOT");
+	}
+        ok(rc0, "Successfully read the PSPHOT recipe file");
+psMetadataPrint(stdout, config->recipes, 0);
+
+        if (config->recipes == NULL) printf("FUCK: config->recipes is NULL");
+        psMetadata *recipe = psMetadataLookupPtr(NULL, config->recipes, "PSPHOT");
+        if (!recipe) {
+            printf("FUCK: missing recipe %s\n", "PSPHOT");
+            exit(1);
+        }
+
+
+        bool rc = pmPSFmodelWrite(analysis, view, file, config);
+        ok(rc == true, "pmPSFmodelWrite() returned TRUE with acceptable input parameters");
+
+        psFree(view);
+        psFree(file->fpa);
+        file->fpa = NULL;
+        psFree(file);
+        psFree(camera);
+        psFree(analysis);
+        psFree(config);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmPSFtoMetadata.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmPSFtoMetadata.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmPSFtoMetadata.c	(revision 22157)
@@ -0,0 +1,52 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS:
+    Under construction: 10% complete.
+*/
+
+#define MISC_NUM                32
+#define MISC_NAME              "META00"
+#define NUM_BIAS_DATA           10
+#define TEST_NUM_ROWS           (8)
+#define TEST_NUM_COLS           (16)
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+#define TEST_FLOATS_EQUAL(X, Y) (abs(X - Y) < 0.0001)
+#define NUM_SOURCES		100
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(1);
+
+
+    // ----------------------------------------------------------------------
+    // pmPSFtoMetadata() tests
+    // psMetadata *pmPSFtoMetadata (psMetadata *metadata, pmPSF *psf)
+    // Call pmPSFtoMetadata() with NULL psPSF input parameter
+    if (1) {
+        psMemId id = psMemGetId();
+        psMetadata *metadata = psMetadataAlloc();
+        pmPSFOptions *psfOptions = pmPSFOptionsAlloc();
+        psfOptions->psfTrendNx = 1;
+        psfOptions->psfTrendNy = 2;
+        psfOptions->psfFieldNx = 3;
+        psfOptions->psfFieldNy = 4;
+        psfOptions->psfFieldXo = 5;
+        psfOptions->psfFieldYo = 6;
+        pmModelClassInit();
+        psfOptions->type = pmModelClassGetType("PS_MODEL_GAUSS");
+        pmPSF *psf = pmPSFAlloc(psfOptions);
+        psMetadata *meta2 = pmPSFtoMetadata(metadata, NULL);
+        ok(meta2 == NULL, "pmPSFtoMetadata() returned NULL with NULL psPSF input parameter");
+        psFree(metadata);
+        psFree(psfOptions);
+        psFree(psf);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmPeaks.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmPeaks.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmPeaks.c	(revision 22157)
@@ -0,0 +1,744 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS:
+    All functions are tested.
+        pmPeaksInImage(): Must debug tests for small images (1-by-1, N-by-1, 1-by-N)
+*/
+
+#define TST01_VECTOR_LENGTH 10
+#define NUM_ROWS 10
+#define NUM_COLS 10
+#define TST02_NUM_ROWS 5
+#define TST02_NUM_COLS 5
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+
+/******************************************************************************
+test01(): we first test pmPeaksInVector() with a variety of bad input
+parameters.  Then we test it with a simple vector both 1- and multi-elements.
+ *****************************************************************************/
+bool test_pmPeaksInVector(int n)
+{
+    psMemId id = psMemGetId();
+    bool testStatus = true;
+    psVector *inData = psVectorAlloc(n, PS_TYPE_F32);
+    inData->n = inData->nalloc;
+    psVector *outData = NULL;
+
+    // Test first pixel peak.
+    for (psS32 i = 0 ; i < n ; i++) {
+        inData->data.F32[i] = (float) (n-i);
+    }
+    inData->data.F32[0] = (float) n;
+    outData= pmPeaksInVector(inData, 0.0);
+    if (outData == NULL) {
+        diag("TEST ERROR: pmPeaksInVector returned a NULL psVector.\n");
+        testStatus = false;
+    } else {
+        if (outData->n != 1) {
+            diag("TEST ERROR: outData->n is %ld\n", outData->n);
+            testStatus = false;
+        }
+        if (outData->data.U32[0] != 0) {
+            diag("TEST ERROR: Did not find peak at element 0.\n");
+            testStatus = false;
+        }
+        psFree(outData);
+    }
+
+
+    //
+    // Test first pixel peak, large threshold
+    //
+    for (psS32 i = 0 ; i < n ; i++) {
+        inData->data.F32[i] = (float) (n-i);
+    }
+    inData->data.F32[0] = (float) n;
+    outData= pmPeaksInVector(inData, (float) (n*n));
+    if (outData == NULL) {
+        diag("TEST ERROR: pmPeaksInVector returned a NULL psVector.\n");
+        testStatus = false;
+    } else {
+        if (outData->n != 0) {
+            diag("TEST ERROR: outData->n is %ld\n", outData->n);
+            testStatus = false;
+        }
+        psFree(outData);
+
+        // Skip remaining tests if the input vector has length 1.
+        if (n == 1) {
+            psFree(inData);
+            ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+            return(testStatus);
+        }
+    }
+
+    // Test last pixel peak.
+    for (psS32 i = 0 ; i < n ; i++) {
+        inData->data.F32[i] = (float) (i);
+    }
+    inData->data.F32[n-1] = (float) n;
+
+    outData= pmPeaksInVector(inData, 0.0);
+    if (outData == NULL) {
+        diag("TEST ERROR: pmPeaksInVector returned a NULL psVector.\n");
+        testStatus = false;
+    } else {
+        if (outData->n != 1) {
+            diag("TEST ERROR: outData->n is %ld\n", outData->n);
+            testStatus = false;
+        }
+        if (outData->data.U32[0] != n-1) {
+            diag("TEST ERROR: Did not find peak at element %d.\n", n-1);
+            testStatus = false;
+        }
+        psFree(outData);
+    }
+
+
+    // Test last pixel peak, large threshold.
+    for (psS32 i = 0 ; i < n ; i++) {
+        inData->data.F32[i] = (float) (i);
+    }
+    inData->data.F32[n-1] = (float) n;
+    outData= pmPeaksInVector(inData, (float) (n*n));
+    if (outData == NULL) {
+        diag("TEST ERROR: pmPeaksInVector returned a NULL psVector.\n");
+        testStatus = false;
+    } else {
+        if (outData->n != 0) {
+            diag("TEST ERROR: outData->n is %ld\n", outData->n);
+            testStatus = false;
+        }
+        psFree(outData);
+    }
+
+
+    // Test interior peaks.
+    // Set all even number elements to be peaks.
+    for (psS32 i = 0 ; i < n ; i++) {
+        if (0 == i%2) {
+            inData->data.F32[i] = (float) (2 * i);
+        } else {
+            inData->data.F32[i] = (float) (i);
+        }
+    }
+    inData->data.F32[0] = (float) n;
+
+
+    outData= pmPeaksInVector(inData, 0.0);
+    if (outData == NULL) {
+        diag("TEST ERROR: pmPeaksInVector returned a NULL psVector.\n");
+        testStatus = false;
+    } else {
+        if (outData->n != n/2) {
+            diag("TEST ERROR: outData->n is %ld\n", outData->n);
+            testStatus = false;
+        }
+        for (psS32 i = 0 ; i < outData->n ; i++) {
+            if (outData->data.U32[i] != (2 * i)) {
+                diag("TEST ERROR: the %d-th peak is element number %d\n", i, outData->data.U32[i]);
+                testStatus = false;
+            }
+        }
+        psFree(outData);
+    }
+
+
+    // Test interior peaks, with threshold = n*n.
+    // Should generate an empty output psVector.
+    outData= pmPeaksInVector(inData, (float) (n*n));
+    if (outData == NULL) {
+        diag("TEST ERROR: pmPeaksInVector returned a NULL psVector.\n");
+        testStatus = false;
+    } else {
+        if (outData->n != 0) {
+            diag("TEST ERROR: outData->n is %ld\n", outData->n);
+            testStatus = false;
+        }
+        psFree(outData);
+    }
+    psFree(inData);
+    ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    return(testStatus);
+}
+
+
+bool test_pmPeaksInImage(int numRows, int numCols)
+{
+    psMemId id = psMemGetId();
+    bool testStatus = true;
+    psImage *inData = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    psArray *outData = NULL;
+    // Initialize test image.
+    for (psS32 i = 0 ; i < numRows ; i++) {
+        for (psS32 j = 0 ; j < numCols ; j++) {
+            inData->data.F32[i][j] = PS_SQR(i - numRows/2) + PS_SQR(j-numCols/2);
+        }
+    }
+
+    // Set corner and center pixels as peaks.
+    inData->data.F32[0][0] = PS_SQR(numRows) + PS_SQR(numCols);
+    inData->data.F32[0][numCols-1] = PS_SQR(numRows) + PS_SQR(numCols);
+    inData->data.F32[numRows-1][0] = PS_SQR(numRows) + PS_SQR(numCols);
+    inData->data.F32[numRows-1][numCols-1] = PS_SQR(numRows) + PS_SQR(numCols);
+    inData->data.F32[numRows/2][numCols/2] = PS_SQR(numRows) + PS_SQR(numCols);
+
+    // Call pmPeaksInImage() with a threshold of 0.0.
+    outData = pmPeaksInImage(inData, 0.0);
+
+    if (outData == NULL) {
+        diag("TEST ERROR: pmPeaksInImage returned a NULL psList.\n");
+        testStatus = false;
+    } else {
+        psS32 expectedNumPeaks;
+        if ((numRows == 1) && (numCols == 1)) {
+            expectedNumPeaks = 1;
+        } else if ((numRows == 1) || (numCols == 1)) {
+            expectedNumPeaks = 3;
+        } else {
+            expectedNumPeaks = 5;
+        }
+        if (outData->n != expectedNumPeaks) {
+            diag("TEST ERROR: pmPeaksInImage found %ld peaks (should be %d)\n", outData->n, expectedNumPeaks);
+            testStatus = false;
+        }
+
+        // HEY: verify
+        for (psS32 i = 0 ; i < outData->n ; i++) {
+            pmPeak *tmpPeak = (pmPeak *) outData->data[i];
+            if (((tmpPeak->x == 0) && (tmpPeak->y == 0)) ||
+                    ((tmpPeak->x == 0) && (tmpPeak->y == numRows-1)) ||
+                    ((tmpPeak->x == numCols-1) && (tmpPeak->y == 0)) ||
+                    ((tmpPeak->x == numCols-1) && (tmpPeak->y == numRows-1))) {
+                if (!((tmpPeak->type & PM_PEAK_LONE) || (tmpPeak->type & PM_PEAK_EDGE))) {
+                    diag("TEST ERROR: (0) peak at (%d, %d) (%f) ->type set improperly (0x%x).",
+                          tmpPeak->y, tmpPeak->x, tmpPeak->value, tmpPeak->type);
+                    diag(" should be (0x%x or 0x%x).\n", PM_PEAK_LONE, PM_PEAK_EDGE);
+                    testStatus = false;
+                }
+            } else if ((tmpPeak->x == numCols/2) && (tmpPeak->y == numRows/2)) {
+                if (tmpPeak->type != PM_PEAK_LONE) {
+                    diag("TEST ERROR: (1) peak at (%d, %d) (%f) ->type set improperly (0x%x).\n",
+                           tmpPeak->y, tmpPeak->x, tmpPeak->value, tmpPeak->type);
+                    diag(" should be (0x%x).\n", PM_PEAK_LONE);
+                    testStatus = false;
+                }
+            } else {
+                diag("TEST ERROR: Peak at (%d, %d) (%f)\n", tmpPeak->y, tmpPeak->x, tmpPeak->value);
+                testStatus = false;
+            }
+        }
+    }
+    psFree(inData);
+    psFree(outData);
+    ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    return(testStatus);
+}
+
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(69);
+
+
+    // ------------------------------------------------------------------------
+    // Test pmPeakAlloc()
+    {
+        psMemId id = psMemGetId();
+        pmPeak *tmpPeak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+        ok(tmpPeak != NULL, "pmPeakAlloc() returned a non-NULL pmPeak");
+        skip_start(tmpPeak == NULL, 9, "Skipping tests because pmPeakAlloc() returned NULL");
+        ok(tmpPeak->id == 1, "pmPeakAlloc() set pmPeak->id");
+        ok(tmpPeak->x == 1, "pmPeakAlloc() set pmPeak->x");
+        ok(tmpPeak->y == 2, "pmPeakAlloc() set pmPeak->y");
+        ok(tmpPeak->value == 3.0, "pmPeakAlloc() set pmPeak->value");
+        ok(tmpPeak->flux == 0, "pmPeakAlloc() pmPeak->flux");
+        ok(tmpPeak->SN == 0, "pmPeakAlloc() pmPeak->SN");
+        ok(tmpPeak->xf == 1, "pmPeakAlloc() pmPeak->xf");
+        ok(tmpPeak->yf == 2, "pmPeakAlloc() pmPeak->yF");
+        ok(tmpPeak->type == PM_PEAK_LONE, "pmPeakAlloc() pmPeak->type");
+        psFree(tmpPeak);
+        skip_end();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmPeakAlloc(): ensure pmPeak->id is properly incremented.
+    {
+        psMemId id = psMemGetId();
+        pmPeak *tmpPeak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+        skip_start(tmpPeak == NULL, 1, "Skipping tests because pmPeakAlloc() returned NULL");
+        ok(tmpPeak->id == 2, "pmPeakAlloc() incremented and set pmPeak->id");
+        psFree(tmpPeak);
+        skip_end();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // Calling pmPeaksCompareAscend with NULL peak1
+    // XXX: This currently seg-faults because NULL args are not pretested in pmPeaksCompareAscend()
+    if (0) {
+        psMemId id = psMemGetId();
+        pmPeak **peak2 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        *peak2 = pmPeakAlloc(3, 4, 3.0, PM_PEAK_LONE);
+        int rc = pmPeaksCompareAscend(NULL, (const void **) peak2);
+        ok(rc == -1, "pmPeaksCompareAscend() returned correct result (peak1 < peak2)");
+        psFree(*peak2);
+        psFree(peak2);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Calling pmPeaksCompareAscend with NULL peak2
+    // XXX: This currently seg-faults because NULL args are not pretested in pmPeaksCompareAscend()
+    if (0) {
+        psMemId id = psMemGetId();
+        pmPeak **peak1 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        *peak1 = pmPeakAlloc(3, 4, 2.0, PM_PEAK_LONE);
+        int rc = pmPeaksCompareAscend((const void **)peak1, NULL);
+        ok(rc == -1, "pmPeaksCompareAscend() returned correct result (peak1 < peak2)");
+        psFree(*peak1);
+        psFree(peak1);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Calling pmPeaksCompareAscend with NULL *peak1
+    // XXX: This currently seg-faults because NULL args are not pretested in pmPeaksCompareAscend()
+    if (0) {
+        psMemId id = psMemGetId();
+        pmPeak **peak1 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        pmPeak **peak2 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        *peak2 = pmPeakAlloc(3, 4, 3.0, PM_PEAK_LONE);
+        int rc = pmPeaksCompareAscend((const void **)peak1, (const void **) peak2);
+        ok(rc == -1, "pmPeaksCompareAscend() returned correct result (peak1 < peak2)");
+        psFree(peak1);
+        psFree(*peak2);
+        psFree(peak2);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Calling pmPeaksCompareAscend with NULL *peak2
+    // XXX: This currently seg-faults because NULL args are not pretested in pmPeaksCompareAscend()
+    if (0) {
+        psMemId id = psMemGetId();
+        pmPeak **peak1 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        *peak1 = pmPeakAlloc(3, 4, 2.0, PM_PEAK_LONE);
+        pmPeak **peak2 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        int rc = pmPeaksCompareAscend((const void **)peak1, (const void **) peak2);
+        ok(rc == -1, "pmPeaksCompareAscend() returned correct result (peak1 < peak2)");
+        psFree(*peak1);
+        psFree(peak1);
+        psFree(peak2);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // Calling pmPeaksCompareAscend with peak1 < peak2
+    {
+        psMemId id = psMemGetId();
+        pmPeak **peak1 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        *peak1 = pmPeakAlloc(3, 4, 2.0, PM_PEAK_LONE);
+        pmPeak **peak2 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        *peak2 = pmPeakAlloc(3, 4, 3.0, PM_PEAK_LONE);
+        int rc = pmPeaksCompareAscend((const void **)peak1, (const void **) peak2);
+        ok(rc == -1, "pmPeaksCompareAscend() returned correct result (peak1 < peak2)");
+        psFree(*peak1);
+        psFree(peak1);
+        psFree(*peak2);
+        psFree(peak2);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Calling pmPeaksCompareAscend with peak1 > peak2
+    {
+        psMemId id = psMemGetId();
+        pmPeak **peak1 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        *peak1 = pmPeakAlloc(3, 4, 3.0, PM_PEAK_LONE);
+        pmPeak **peak2 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        *peak2 = pmPeakAlloc(3, 4, 2.0, PM_PEAK_LONE);
+        int rc = pmPeaksCompareAscend((const void **)peak1, (const void **) peak2);
+        ok(rc == 1, "pmPeaksCompareAscend() returned correct result (peak1 > peak2)");
+        psFree(*peak1);
+        psFree(peak1);
+        psFree(*peak2);
+        psFree(peak2);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Calling pmPeaksCompareAscend with peak1 == peak2
+    {
+        psMemId id = psMemGetId();
+        pmPeak **peak1 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        *peak1 = pmPeakAlloc(3, 4, 2.0, PM_PEAK_LONE);
+        pmPeak **peak2 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        *peak2 = pmPeakAlloc(3, 4, 2.0, PM_PEAK_LONE);
+        int rc = pmPeaksCompareAscend((const void **)peak1, (const void **) peak2);
+        ok(rc == 0, "pmPeaksCompareAscend() returned correct result (peak1 == peak2)", rc);
+        psFree(*peak1);
+        psFree(peak1);
+        psFree(*peak2);
+        psFree(peak2);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // Calling pmPeaksCompareDescend with NULL peak1
+    // XXX: This currently seg-faults because NULL args are not pretested in pmPeaksCompareDescend()
+    if (0) {
+        psMemId id = psMemGetId();
+        pmPeak **peak2 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        *peak2 = pmPeakAlloc(3, 4, 3.0, PM_PEAK_LONE);
+        int rc = pmPeaksCompareDescend(NULL, (const void **) peak2);
+        ok(rc == -1, "pmPeaksCompareDescend() returned correct result (peak1 < peak2)");
+        psFree(*peak2);
+        psFree(peak2);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Calling pmPeaksCompareDescend with NULL peak2
+    // XXX: This currently seg-faults because NULL args are not pretested in pmPeaksCompareDescend()
+    if (0) {
+        psMemId id = psMemGetId();
+        pmPeak **peak1 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        *peak1 = pmPeakAlloc(3, 4, 2.0, PM_PEAK_LONE);
+        int rc = pmPeaksCompareDescend((const void **)peak1, NULL);
+        ok(rc == -1, "pmPeaksCompareDescend() returned correct result (peak1 < peak2)");
+        psFree(*peak1);
+        psFree(peak1);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Calling pmPeaksCompareDescend with NULL *peak1
+    // XXX: This currently seg-faults because NULL args are not pretested in pmPeaksCompareDescend()
+    if (0) {
+        psMemId id = psMemGetId();
+        pmPeak **peak1 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        pmPeak **peak2 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        *peak2 = pmPeakAlloc(3, 4, 3.0, PM_PEAK_LONE);
+        int rc = pmPeaksCompareDescend((const void **)peak1, (const void **) peak2);
+        ok(rc == -1, "pmPeaksCompareDescend() returned correct result (peak1 < peak2)");
+        psFree(peak1);
+        psFree(*peak2);
+        psFree(peak2);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Calling pmPeaksCompareDescend with NULL *peak2
+    // XXX: This currently seg-faults because NULL args are not pretested in pmPeaksCompareDescend()
+    if (0) {
+        psMemId id = psMemGetId();
+        pmPeak **peak1 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        *peak1 = pmPeakAlloc(3, 4, 2.0, PM_PEAK_LONE);
+        pmPeak **peak2 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        int rc = pmPeaksCompareDescend((const void **)peak1, (const void **) peak2);
+        ok(rc == -1, "pmPeaksCompareDescend() returned correct result (peak1 < peak2)");
+        psFree(*peak1);
+        psFree(peak1);
+        psFree(peak2);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // Calling pmPeaksCompareDescend with peak1 < peak2
+    {
+        psMemId id = psMemGetId();
+        pmPeak **peak1 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        *peak1 = pmPeakAlloc(3, 4, 2.0, PM_PEAK_LONE);
+        pmPeak **peak2 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        *peak2 = pmPeakAlloc(3, 4, 3.0, PM_PEAK_LONE);
+        int rc = pmPeaksCompareDescend((const void **)peak1, (const void **) peak2);
+        ok(rc == 1, "pmPeaksCompareDescend() returned correct result (peak1 < peak2)");
+        psFree(*peak1);
+        psFree(peak1);
+        psFree(*peak2);
+        psFree(peak2);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Calling pmPeaksCompareDescend with peak1 > peak2
+    {
+        psMemId id = psMemGetId();
+        pmPeak **peak1 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        *peak1 = pmPeakAlloc(3, 4, 3.0, PM_PEAK_LONE);
+        pmPeak **peak2 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        *peak2 = pmPeakAlloc(3, 4, 2.0, PM_PEAK_LONE);
+        int rc = pmPeaksCompareDescend((const void **)peak1, (const void **) peak2);
+        ok(rc == -1, "pmPeaksCompareDescend() returned correct result (peak1 > peak2)");
+        psFree(*peak1);
+        psFree(peak1);
+        psFree(*peak2);
+        psFree(peak2);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Calling pmPeaksCompareDescend with peak1 == peak2
+    {
+        psMemId id = psMemGetId();
+        pmPeak **peak1 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        *peak1 = pmPeakAlloc(3, 4, 2.0, PM_PEAK_LONE);
+        pmPeak **peak2 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        *peak2 = pmPeakAlloc(3, 4, 2.0, PM_PEAK_LONE);
+        int rc = pmPeaksCompareDescend((const void **)peak1, (const void **) peak2);
+        ok(rc == 0, "pmPeaksCompareDescend() returned correct result (peak1 == peak2)", rc);
+        psFree(*peak1);
+        psFree(peak1);
+        psFree(*peak2);
+        psFree(peak2);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // pmPeakSortBySN() tests
+    // int pmPeakSortBySN (const void **a, const void **b)
+    // Call pmPeakSortBySN() with acceptable input parameters.
+    // XXX: We don't test with NULL input parameters since this functions has no PS_ASSERTS to protect
+    // against that.
+    {
+        psMemId id = psMemGetId();
+        pmPeak **peak1 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        *peak1 = pmPeakAlloc(3, 4, 2.0, PM_PEAK_LONE);
+        (*peak1)->SN = 10.0;
+        pmPeak **peak2 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        *peak2 = pmPeakAlloc(3, 4, 3.0, PM_PEAK_LONE);
+        (*peak2)->SN = 20.0;
+        int rc = pmPeakSortBySN((const void **)peak1, (const void **) peak2);
+        ok(rc == 1, "pmPeakSortBySN() returned correct result (peak1 < peak2) (%d)", rc);
+        rc = pmPeakSortBySN((const void **)peak2, (const void **) peak1);
+        ok(rc == -1, "pmPeakSortBySN() returned correct result (peak2 < peak1) (%d)", rc);
+        rc = pmPeakSortBySN((const void **)peak1, (const void **) peak1);
+        ok(rc == 0, "pmPeakSortBySN() returned correct result (peak1 == peak2) (%d)", rc);
+        psFree(*peak1);
+        psFree(peak1);
+        psFree(*peak2);
+        psFree(peak2);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // pmPeakSortByY() tests
+    // int pmPeakSortByY (const void **a, const void **b)
+    // Call pmPeakSortByY() with acceptable input parameters.
+    // XXX: We don't test with NULL input parameters since this functions has no PS_ASSERTS to protect
+    // against that.
+    {
+        psMemId id = psMemGetId();
+        pmPeak **peak1 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        *peak1 = pmPeakAlloc(3, 4, 2.0, PM_PEAK_LONE);
+        (*peak1)->y = 10.0;
+        pmPeak **peak2 = (pmPeak **) psAlloc(sizeof(pmPeak *));
+        *peak2 = pmPeakAlloc(3, 4, 3.0, PM_PEAK_LONE);
+        (*peak2)->y = 20.0;
+        int rc = pmPeakSortByY((const void **)peak1, (const void **) peak2);
+        ok(rc == -1, "pmPeakSortByY() returned correct result (peak1 < peak2) (%d)", rc);
+        rc = pmPeakSortByY((const void **)peak2, (const void **) peak1);
+        ok(rc == 1, "pmPeakSortByY() returned correct result (peak2 < peak1) (%d)", rc);
+        rc = pmPeakSortByY((const void **)peak1, (const void **) peak1);
+        ok(rc == 0, "pmPeakSortByY() returned correct result (peak1 == peak2) (%d)", rc);
+        psFree(*peak1);
+        psFree(peak1);
+        psFree(*peak2);
+        psFree(peak2);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // pmPeaksInVector() tests
+    // Test pmPeaksInVector() with bad input parameters.
+    // Calling pmPeaksInVector with NULL psVector.  Should generate error.
+    {
+        psMemId id = psMemGetId();
+        psVector *tmpVec = pmPeaksInVector(NULL, 0.0);
+        ok(tmpVec == NULL, "pmPeaksInVector() returned a NULL with NULL psVector input");
+        psFree(tmpVec);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Calling pmPeaksInVector with empty psVector.  Should generate error.
+    {
+        psMemId id = psMemGetId();
+        psVector *tmpVecEmpty = psVectorAlloc(0, PS_TYPE_F32);
+        psVector *tmpVec = pmPeaksInVector(tmpVecEmpty, 0.0);
+        ok(tmpVec == NULL, "pmPeaksInVector() returned a NULL with NULL psVector input");
+        psFree(tmpVec);
+        psFree(tmpVecEmpty);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Calling pmPeaksInVector with PS_TYPE_F64 psVector.  Should generate error.
+    {
+        psMemId id = psMemGetId();
+        psVector *tmpVecF64 = psVectorAlloc(TST01_VECTOR_LENGTH, PS_TYPE_F64);
+        psVector *tmpVec = pmPeaksInVector(tmpVecF64, 0.0);
+        ok(tmpVec == NULL, "pmPeaksInVector() returned a NULL with F64 psVector input");
+        psFree(tmpVecF64);
+        psFree(tmpVec);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+    ok(test_pmPeaksInVector(1), "Tested pmPeaksInVector() on length 1 input vector");
+    ok(test_pmPeaksInVector(10), "Tested pmPeaksInVector() on length 10 input vector");
+
+
+    // ------------------------------------------------------------------------
+    // pmPeaksInImage() tests
+    // Calling pmPeaksInImage with NULL psImage.  Should generate error.
+    {
+        psMemId id = psMemGetId();
+        psArray *tmpArray = pmPeaksInImage(NULL, 0.0);
+        ok(tmpArray == NULL, "pmPeaksInImage() returned NULL with NULL input image");
+        psFree(tmpArray);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Calling pmPeaksInImage with empty psImage.  Should generate error.
+    {
+        psMemId id = psMemGetId();
+        psImage *tmpImageEmpty = psImageAlloc(0, 0, PS_TYPE_F32);
+        psArray *tmpArray = pmPeaksInImage(tmpImageEmpty, 0.0);
+        ok(tmpArray == NULL, "pmPeaksInImage() returned NULL with empty input image");
+        psFree(tmpArray);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+    
+
+    // Calling pmPeaksInImage with PS_TYPE_F64 psImage.  Should generate error
+    {
+        psMemId id = psMemGetId();
+        psImage *tmpImageF64 = psImageAlloc(TST02_NUM_ROWS, TST02_NUM_COLS, PS_TYPE_F64);
+        psArray *tmpArray = pmPeaksInImage(tmpImageF64, 0.0);
+        ok(tmpArray == NULL, "pmPeaksInImage() returned NULL with F64 input image");
+        psFree(tmpImageF64);
+        psFree(tmpArray);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // XXX: Uncomment these and debug
+    //    testStatus&= test_pmPeaksInImage(1, 1);
+    //    testStatus&= test_pmPeaksInImage(2, 5);
+    //    testStatus&= test_pmPeaksInImage(5, 2);
+    // HEY: add code for small images
+    //    testStatus&= test_pmPeaksInImage(1, 1);
+    //    testStatus&= test_pmPeaksInImage(1, 8);
+    //    testStatus&= test_pmPeaksInImage(8, 1);
+    ok(test_pmPeaksInImage(TST02_NUM_ROWS, TST02_NUM_COLS),
+      "Tested pmPeaksInImage() on (%d, %d) image", TST02_NUM_ROWS, TST02_NUM_COLS);
+    ok(test_pmPeaksInImage(2*TST02_NUM_ROWS, TST02_NUM_COLS),
+      "Tested pmPeaksInImage() on (%d, %d) image", 2*TST02_NUM_ROWS, TST02_NUM_COLS);
+    ok(test_pmPeaksInImage(TST02_NUM_ROWS, 2*TST02_NUM_COLS),
+      "Tested pmPeaksInImage() on (%d, %d) image", TST02_NUM_ROWS, 2*TST02_NUM_COLS);
+
+
+    // ------------------------------------------------------------------------
+    // Test pmPeaksSubset()
+    // Calling pmPeaksSubset with NULL psList.  Should generate error.
+    {
+        psMemId id = psMemGetId();
+        psArray *outData = pmPeaksSubset(NULL, 0.0, psRegionSet(0, 0, 0, 0));
+        ok(outData == NULL, "pmPeaksSubset() returned a NULL with a NULL psArray input");
+        psFree(outData);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Set peaks in input image.  All even-column and even-row pixels are
+    // set non-zero, all other pixels are set to zero.
+    psImage *imgData = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+    psS32 numPeaksOrig = 0;
+    for (psS32 i = 0 ; i < NUM_ROWS ; i++) {
+        for (psS32 j = 0 ; j < NUM_COLS ; j++) {
+            if ((0 == i%2) && (0 == j%2)) {
+                imgData->data.F32[i][j] = (float) (i + 10);
+                numPeaksOrig++;
+            } else {
+                imgData->data.F32[i][j] = 0.0;
+            }
+        }
+    }
+    // Call pmPeaksSubset() with large maxValue and disjoint psRegion.
+    // Should not remove any peaks.
+    {
+        psMemId id = psMemGetId();
+        psArray *outData = pmPeaksInImage(imgData, 0.0);
+        psArray *outData2 = pmPeaksSubset(outData, PS_MAX_F32, psRegionSet(20, 20, 20, 20));
+        ok(outData2 != NULL && psMemCheckArray(outData),
+           "pmPeaksSubset() returned a non-NULL psArray (large maxValue)");
+        ok(outData2->n == numPeaksOrig,
+          "pmPeaksSubset() returned correct number of peaks (was %d, should be %d)", outData2->n+1, numPeaksOrig);
+        psFree(outData);
+        psFree(outData2);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmPeaksSubset() with small maxValue and disjoint psRegion.
+    // Should not remove any peaks.
+    {
+        psMemId id = psMemGetId();
+        psArray *outData = pmPeaksInImage(imgData, 0.0);
+        psArray *outData2 = pmPeaksSubset(outData, 0.0, psRegionSet(20, 20, 20, 20));
+        ok(outData2 != NULL && psMemCheckArray(outData),
+           "pmPeaksSubset() returned a non-NULL psArray (small maxValue)");
+        ok(outData2->n == 0,
+          "pmPeaksSubset() returned correct number of peaks (was %d, should be %d)", outData2->n, 0);
+        psFree(outData);
+        psFree(outData2);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmPeaksSubset() with large maxValue and enclosing psRegion.
+    // Should remove aall peaks.
+    {
+        psMemId id = psMemGetId();
+        psArray *outData = pmPeaksInImage(imgData, 0.0);
+        psRegion tmpRegion = psRegionSet(-PS_MAX_F32, PS_MAX_F32, -PS_MAX_F32, PS_MAX_F32);
+        psArray *outData2 = pmPeaksSubset(outData, PS_MAX_F32, tmpRegion);
+        ok(outData2 != NULL && psMemCheckArray(outData),
+           "pmPeaksSubset() returned a non-NULL psArray (small maxValue, enclosing psRegion)");
+        ok(outData2->n == 0,
+          "pmPeaksSubset() returned correct number of peaks (was %d, should be %d)", outData2->n, 0);
+        psFree(outData);
+        psFree(outData2);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmPeaksSubset() with large maxValue and enclosing psRegion.
+    // Should remove aall peaks.
+    {
+        psMemId id = psMemGetId();
+        psArray *outData = pmPeaksInImage(imgData, 0.0);
+        psRegion tmpRegion = psRegionSet(-PS_MAX_F32, PS_MAX_F32, -PS_MAX_F32, PS_MAX_F32);
+        psArray *outData2 = pmPeaksSubset(outData, 0.0, tmpRegion);
+        ok(outData2 != NULL && psMemCheckArray(outData),
+           "pmPeaksSubset() returned a non-NULL psArray (small maxValue, inclusive psRegion)");
+        ok(outData2->n == 0,
+          "pmPeaksSubset() returned correct number of peaks (was %d, should be %d)", outData2->n, 0);
+        psFree(outData);
+        psFree(outData2);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmResiduals.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmResiduals.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmResiduals.c	(revision 22157)
@@ -0,0 +1,46 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS:
+	All functions are tested.
+*/
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    plan_tests(11);
+
+
+    // Test pmFringeRegionsAlloc()
+    // pmResiduals *pmResidualsAlloc (int xSize, int ySize, int xBin, int yBin);
+    {
+        psMemId id = psMemGetId();
+        int xSize = 1;
+        int ySize = 2;
+        int xBin = 3;
+        int yBin = 4;
+        pmResiduals *resid = pmResidualsAlloc(xSize, ySize, xBin, yBin);
+        ok(resid != NULL && psMemCheckResiduals(resid), "pmResidualsAlloc() allocated a pmResiduals struct correctly");
+        ok(resid->Ro && psMemCheckImage(resid->Ro), "pmResidualsAlloc() allocated the resid->Ro image");
+        ok(resid->Rx && psMemCheckImage(resid->Rx), "pmResidualsAlloc() allocated the resid->Rx image");
+        ok(resid->Ry && psMemCheckImage(resid->Ry), "pmResidualsAlloc() allocated the resid->Ry image");
+        ok(resid->weight && psMemCheckImage(resid->weight), "pmResidualsAlloc() allocated the resid->weight image");
+        ok(resid->mask && psMemCheckImage(resid->mask), "pmResidualsAlloc() allocated the resid->mask image");
+
+        int nX = xSize * xBin;
+        int nY = ySize * yBin;
+        nX = (nX % 2) ? nX : nX + 1;
+        nY = (nY % 2) ? nY : nY + 1;
+        ok(resid->xBin == xBin, "pmResidualsAlloc() set resid->xBin correctly");
+        ok(resid->yBin == yBin, "pmResidualsAlloc() set resid->yBin correctly");
+        ok(resid->xCenter == 0.5*(nX - 1), "pmResidualsAlloc() set resid->xCenter correctly");
+        ok(resid->yCenter == 0.5*(nY - 1), "pmResidualsAlloc() set resid->xCenter correctly");
+
+        psFree(resid);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSource.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSource.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSource.c	(revision 22157)
@@ -0,0 +1,838 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/*
+Tested:
+    Tested
+        pmSourceAlloc()
+        pmSourceCopy()
+        pmSourceDefinePixels()
+        pmSourceRedefinePixels()
+        pmSourcePSFClump()
+        pmSourceGetModel()
+        pmSourceAdd()
+        pmSourceSub()
+        pmSourceAddWithOffset()
+        pmSourceSubWithOffset()
+        pmSourceOp()
+        pmSourceCacheModel()
+        pmSourceCachePSF()
+        pmSourceSortBySN()	(COMPILER ERRORS)
+        pmSourceSortByY()	(COMPILER ERRORS)
+    Must test
+        pmSourceMoments()
+        pmSourceRoughClass()
+
+*/
+
+
+#define MISC_NUM                32
+#define MISC_NAME              "META00"
+#define NUM_BIAS_DATA           10
+#define TEST_NUM_ROWS           (4+1)
+#define TEST_NUM_COLS           (4+1)
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+#define TEST_FLOATS_EQUAL(X, Y) (abs(X - Y) < 0.01)
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(readout);
+}
+
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(128);
+
+    // ----------------------------------------------------------------------
+    // Test pmSourceAlloc()
+    // pmSource *pmSourceAlloc();
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        ok(src != NULL && psMemCheckSource(src), "pmSourceAlloc() returned a non-NULL pmSource");
+        skip_start(src == NULL, 24, "Skipping tests because pmSourceAlloc() returned NULL");
+        ok(src->peak == NULL, "pmSourceAlloc() pmSource->peak correctly");
+        ok(src->pixels == NULL, "pmSourceAlloc() pmSource->pixels correctly");
+        ok(src->weight == NULL, "pmSourceAlloc() pmSource->weight correctly");
+        ok(src->maskObj == NULL, "pmSourceAlloc() pmSource->maskObj correctly");
+        ok(src->maskView == NULL, "pmSourceAlloc() pmSource->maskView correctly");
+        ok(src->modelFlux == NULL, "pmSourceAlloc() pmSource->modelFlux correctly");
+        ok(src->psfFlux == NULL, "pmSourceAlloc() pmSource->psfFlux correctly");
+        ok(src->moments == NULL, "pmSourceAlloc() pmSource->moments correctly");
+        ok(src->blends == NULL, "pmSourceAlloc() pmSource->blends correctly");
+        ok(src->modelPSF == NULL, "pmSourceAlloc() pmSource->modelPSF correctly");
+        ok(src->modelEXT == NULL, "pmSourceAlloc() pmSource->modelEXT correctly");
+        ok(src->modelConv == NULL, "pmSourceAlloc() pmSource->modelConv correctly");
+        ok(src->type == PM_SOURCE_TYPE_UNKNOWN, "pmSourceAlloc() pmSource->type correctly");
+        ok(src->mode == PM_SOURCE_MODE_DEFAULT, "pmSourceAlloc() pmSource->mode correctly");
+
+        ok(isnan(src->psfMag), "pmSourceAlloc() pmSource->psfMag correctly");
+        ok(isnan(src->extMag), "pmSourceAlloc() pmSource->extMag correctly");
+        ok(isnan(src->errMag), "pmSourceAlloc() pmSource->errMag correctly");
+        ok(isnan(src->apMag), "pmSourceAlloc() pmSource->apMag correctly");
+        ok(isnan(src->sky), "pmSourceAlloc() pmSource->sky correctly");
+        ok(isnan(src->skyErr), "pmSourceAlloc() pmSource->skyErr correctly");
+        ok(isnan(src->pixWeight), "pmSourceAlloc() pmSource->pixWeight correctly");
+        int srcID = src->id;
+        psFree(src);
+
+        // Allocate another pmSource to ensure that pmSource->id is incremented
+        pmSource *src = pmSourceAlloc();
+        ok(src != NULL, "pmSourceAlloc() returned a non-NULL pmSource");
+        ok(src->id == srcID+1, "pmSourceAlloc() incremented the pmSource->id correctly");
+        psFree(src);
+
+        skip_end();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmSourceCopy() tests
+    // Call pmSourceCopy() with NULL input
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceCopy(NULL);
+        ok(src == NULL, "pmSourceCopy(NULL) returned NULL");
+        psFree(src);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceCopy() with non-NULL input, but NULL members
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        ok(src != NULL, "pmSourceAlloc() returned a non-NULL pmSource");
+        pmSource *dst = pmSourceCopy(src);
+        ok(dst != NULL, "pmSourceCopy() returned a non-NULL pmSource");
+        ok(dst != src, "pmSourceCopy() allocated a new pmSource");
+        ok(dst->type == src->type, "pmSourceCopy() set the pmSource->type");
+        ok(dst->mode == src->mode, "pmSourceCopy() set the pmSource->mode");
+        psFree(src);
+        psFree(dst);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceCopy() with non-NULL input, non-NULL members
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        ok(src != NULL, "pmSourceAlloc() returned a non-NULL pmSource");
+        // Set pmPeak values
+        src->peak = pmPeakAlloc (1, 2, 3.0, PM_PEAK_LONE);
+        src->peak->xf = 4.0;
+        src->peak->yf = 5.0;
+        src->peak->flux = 6.0;
+        src->peak->SN = 10.0;
+        src->moments = pmMomentsAlloc();
+        src->moments->x = 11.0;
+        src->moments->y = 12.0;
+        src->moments->Sx = 15.0;
+        src->moments->Sy = 16.0;
+        src->moments->Sxy = 19.0;
+
+        src->pixels = psImageAlloc(2, 4, PS_TYPE_F32);
+        src->weight = psImageAlloc(6, 8, PS_TYPE_F32);
+        src->maskView  = psImageAlloc(10, 12, PS_TYPE_U8);
+        pmSource *dst = pmSourceCopy(src);
+        ok(dst != NULL, "pmSourceCopy() returned a non-NULL pmSource");
+        ok(dst != src, "pmSourceCopy() allocated a new pmSource");
+        ok(dst->type == src->type, "pmSourceCopy() set the pmSource->type");
+        ok(dst->mode == src->mode, "pmSourceCopy() set the pmSource->mode");
+        ok(dst->peak != NULL, "pmSourceCopy() allocated a new pmSource->peak");
+        ok(dst->peak->xf == src->peak->xf, "pmSourceCopy() pmSource->peak->xf");
+        ok(dst->peak->yf == src->peak->yf, "pmSourceCopy() pmSource->peak->yf");
+        ok(dst->peak->flux == src->peak->flux, "pmSourceCopy() pmSource->peak->flux");
+        ok(dst->peak->SN == src->peak->SN, "pmSourceCopy() pmSource->peak->SN");
+        ok(dst->moments != NULL, "pmSourceCopy() allocated a new pmSource->moments");
+        ok(dst->moments->x == src->moments->x, "pmSourceCopy() pmSource->moments->x");
+        ok(dst->moments->y == src->moments->y, "pmSourceCopy() pmSource->moments->y");
+        ok(dst->moments->Sx == src->moments->Sx, "pmSourceCopy() pmSource->moments->Sx");
+        ok(dst->moments->Sy == src->moments->Sy, "pmSourceCopy() pmSource->moments->Sy");
+        ok(dst->moments->Sxy == src->moments->Sxy, "pmSourceCopy() pmSource->moments->Sxy");
+
+        // XXX: We should possibly do a better job testing that these images are copied correctly.
+        ok(dst->pixels != NULL, "pmSourceCopy() allocated a new pmSource->pixels");
+        ok(dst->pixels->numCols == src->pixels->numCols && dst->pixels->numRows == src->pixels->numRows, 
+           "pmSourceCopy() generated correct size pmSource->pixels");
+        ok(dst->weight != NULL, "pmSourceCopy() allocated a new pmSource->weight");
+        ok(dst->weight->numCols == src->weight->numCols && dst->weight->numRows == src->weight->numRows, 
+           "pmSourceCopy() generated correct size pmSource->weight");
+        ok(dst->maskView != NULL, "pmSourceCopy() allocated a new pmSource->maskView");
+        ok(dst->maskView->numCols == src->maskView->numCols && dst->maskView->numRows == src->maskView->numRows, 
+           "pmSourceCopy() generated correct size pmSource->maskView");
+
+        psFree(src);
+        psFree(dst);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmSourceDefinePixels() tests
+    // Call pmSourceDefinePixels() with NULL pmSource input parameter
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        pmReadout *readout = generateSimpleReadout(NULL);
+        ok(!pmSourceDefinePixels(NULL, readout, 1.0, 2.0, 3.0),
+           "pmSourceDefinePixels() returned false with NULL pmSource input parameter");
+        psFree(src);
+        psFree(readout);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceDefinePixels() with NULL pmReadout input parameter
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        pmReadout *readout = generateSimpleReadout(NULL);
+        ok(!pmSourceDefinePixels(src, NULL, 1.0, 2.0, 3.0),
+           "pmSourceDefinePixels() returned false with NULL pmReadout input parameter");
+        psFree(src);
+        psFree(readout);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceDefinePixels() with NULL pmReadout->image input parameter
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        pmReadout *readout = generateSimpleReadout(NULL);
+        psFree(readout->image);
+        readout->image = NULL;
+        ok(!pmSourceDefinePixels(src, NULL, 1.0, 2.0, 3.0),
+           "pmSourceDefinePixels() returned false with NULL pmReadout input parameter");
+        psFree(src);
+        psFree(readout);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceDefinePixels() with negative radius input parameter
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        pmReadout *readout = generateSimpleReadout(NULL);
+        ok(!pmSourceDefinePixels(src, readout, 1.0, 2.0, -3.0),
+           "pmSourceDefinePixels() returned false with negative radius input parameter");
+        psFree(src);
+        psFree(readout);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceDefinePixels() with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        pmReadout *readout = generateSimpleReadout(NULL);
+        for (int i = 0 ; i < readout->image->numRows ; i++) {
+            for (int j = 0 ; j < readout->image->numCols ; j++) {
+                readout->image->data.F32[i][j] = (float) (i + j);
+	    }
+	}
+        bool rc = pmSourceDefinePixels(src, readout, (float) TEST_NUM_COLS/2, (float) TEST_NUM_ROWS/2, 2.0);
+        ok(rc, "pmSourceDefinePixels() returned TRUE with acceptable input parameters");
+        int expectedNumCols = 1 + (TEST_NUM_COLS/2);
+        int expectedNumRows = 1 + (TEST_NUM_COLS/2);
+        // XX: We only verify the size of the pixels, weight, maskView, and maskObj
+        // images.  A better test would verify that the actual data is set correctly.
+        // We don't do that here since it basically duplicated psLib function tests.
+        ok(src->pixels->numCols == expectedNumCols && src->pixels->numRows == expectedNumRows, 
+               "pmSourceDefinePixels() set the size of pmSource->pixels correctly");
+        ok(src->weight->numCols == expectedNumCols && src->weight->numRows == expectedNumRows, 
+               "pmSourceDefinePixels() set the size of pmSource->weight correctly");
+        ok(src->maskView->numCols == expectedNumCols && src->maskView->numRows == expectedNumRows, 
+               "pmSourceDefinePixels() set the size of pmSource->maskView correctly");
+        ok(src->maskObj->numCols == expectedNumCols && src->maskObj->numRows == expectedNumRows, 
+               "pmSourceDefinePixels() set the size of pmSource->maskObj correctly");
+
+        psRegion region = psRegionForSquare((float) TEST_NUM_COLS/2, (float) TEST_NUM_ROWS/2, 2.0);
+        region = psRegionForImage(readout->image, region);
+        ok(src->region.x0 == region.x0 && src->region.x1 == region.x1 &&
+           src->region.y0 == region.y0 && src->region.y1 == region.y1,
+          "pmSourceDefinePixels() set pmSource->region correctly");
+
+        psFree(src);
+        psFree(readout);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+
+
+    // ----------------------------------------------------------------------
+    // pmSourceRedefinePixels() tests
+    // Call pmSourceRedefinePixels() with NULL pmSource input parameter
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        pmReadout *readout = generateSimpleReadout(NULL);
+        ok(!pmSourceRedefinePixels(NULL, readout, 1.0, 2.0, 3.0),
+           "pmSourceRedefinePixels() returned false with NULL pmSource input parameter");
+        psFree(src);
+        psFree(readout);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceRedefinePixels() with NULL pmReadout input parameter
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        pmReadout *readout = generateSimpleReadout(NULL);
+        ok(!pmSourceRedefinePixels(src, NULL, 1.0, 2.0, 3.0),
+           "pmSourceRedefinePixels() returned false with NULL pmReadout input parameter");
+        psFree(src);
+        psFree(readout);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceRedefinePixels() with NULL pmReadout->image input parameter
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        pmReadout *readout = generateSimpleReadout(NULL);
+        psFree(readout->image);
+        readout->image = NULL;
+        ok(!pmSourceRedefinePixels(src, NULL, 1.0, 2.0, 3.0),
+           "pmSourceRedefinePixels() returned false with NULL pmReadout input parameter");
+        psFree(src);
+        psFree(readout);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceRedefinePixels() with negative radius input parameter
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        pmReadout *readout = generateSimpleReadout(NULL);
+        ok(!pmSourceRedefinePixels(src, readout, 1.0, 2.0, -3.0),
+           "pmSourceRedefinePixels() returned false with negative radius input parameter");
+        psFree(src);
+        psFree(readout);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceRedefinePixels() with acceptable input parameters
+    if (1) {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        pmReadout *readout = generateSimpleReadout(NULL);
+        for (int i = 0 ; i < readout->image->numRows ; i++) {
+            for (int j = 0 ; j < readout->image->numCols ; j++) {
+                readout->image->data.F32[i][j] = (float) (i + j);
+	    }
+	}
+        // First set the pixels region to a square of radius 1.
+        bool rc = pmSourceDefinePixels(src, readout, (float) TEST_NUM_COLS/2, (float) TEST_NUM_ROWS/2, 1.0);
+        ok(rc, "pmSourceDefinePixels() returned TRUE with radius 1");
+
+        // Set these flux images so we can verify they are later psFree'ed and set to NULL
+        src->modelFlux = psImageAlloc(2, 2, PS_TYPE_F32);
+        src->psfFlux = psImageAlloc(2, 2, PS_TYPE_F32);
+
+        // Now set the radius to 2, and call pmSourceRedefinePixels()
+        rc = pmSourceRedefinePixels(src, readout, (float) TEST_NUM_COLS/2, (float) TEST_NUM_ROWS/2, 2.0);
+        ok(rc, "pmSourceRedefinePixels() returned TRUE with acceptable input parameters");
+        int expectedNumCols = 1 + (TEST_NUM_COLS/2);
+        int expectedNumRows = 1 + (TEST_NUM_COLS/2);
+        // XX: We only verify the size of the pixels, weight, maskView, and maskObj
+        // images.  A better test would verify that the actual data is set correctly.
+        // We don't do that here since it basically duplicated psLib function tests.
+        ok(src->pixels->numCols == expectedNumCols && src->pixels->numRows == expectedNumRows, 
+               "pmSourceRedefinePixels() set the size of pmSource->pixels correctly");
+        ok(src->weight->numCols == expectedNumCols && src->weight->numRows == expectedNumRows, 
+               "pmSourceRedefinePixels() set the size of pmSource->weight correctly");
+        ok(src->maskView->numCols == expectedNumCols && src->maskView->numRows == expectedNumRows, 
+               "pmSourceRedefinePixels() set the size of pmSource->maskView correctly");
+        ok(src->maskObj->numCols == expectedNumCols && src->maskObj->numRows == expectedNumRows, 
+               "pmSourceRedefinePixels() set the size of pmSource->maskObj correctly");
+
+        psRegion region = psRegionForSquare((float) TEST_NUM_COLS/2, (float) TEST_NUM_ROWS/2, 2.0);
+        region = psRegionForImage(readout->image, region);
+        ok(src->region.x0 == region.x0 && src->region.x1 == region.x1 &&
+           src->region.y0 == region.y0 && src->region.y1 == region.y1,
+          "pmSourceRedefinePixels() set pmSource->region correctly");
+
+        ok(src->modelFlux == NULL, "pmSourceRedefinePixels() set the pmSource->modelFlux to NULL");
+        ok(src->psfFlux == NULL, "pmSourceRedefinePixels() set the pmSource->psfFlux to NULL");
+
+        psFree(src);
+        psFree(readout);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmSourcePSFClump() tests
+    // pmPSFClump pmSourcePSFClump(psArray *sources, psMetadata *recipe)
+    // Call pmSourcePSFClump() with NULL pmSource input parameter
+    #define NUM_SOURCES 10
+    {
+        psMemId id = psMemGetId();
+        psArray *sources = psArrayAlloc(NUM_SOURCES);
+        psMetadata *recipe = psMetadataAlloc();
+        bool rc = psMetadataAddF32(recipe, PS_LIST_HEAD, "PSF_CLUMP_SN_LIM", 0, NULL, 0.0);
+        rc = psMetadataAddF32(recipe, PS_LIST_HEAD, "MOMENTS_SX_MAX", 0, NULL, 10.0);
+        rc = psMetadataAddF32(recipe, PS_LIST_HEAD, "MOMENTS_SY_MAX", 0, NULL, 10.0);
+        rc = psMetadataAddF32(recipe, PS_LIST_HEAD, "MOMENTS_AR_MAX", 0, NULL, 3.0);
+
+        pmPSFClump clump = pmSourcePSFClump(NULL, recipe);
+        ok(clump.X == -1.0 && clump.dX == -1.0 && 
+           clump.Y == 0.0 && clump.dY == 0.0, "pmSourcePSFClump(NULL, recipe) returned the error clump");
+
+        psFree(sources);
+        psFree(recipe);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourcePSFClump() with NULL recipe input parameter
+    {
+        psMemId id = psMemGetId();
+        psArray *sources = psArrayAlloc(NUM_SOURCES);
+        psMetadata *recipe = psMetadataAlloc();
+        bool rc = psMetadataAddF32(recipe, PS_LIST_HEAD, "PSF_CLUMP_SN_LIM", 0, NULL, 0.0);
+        rc = psMetadataAddF32(recipe, PS_LIST_HEAD, "MOMENTS_SX_MAX", 0, NULL, 10.0);
+        rc = psMetadataAddF32(recipe, PS_LIST_HEAD, "MOMENTS_SY_MAX", 0, NULL, 10.0);
+        rc = psMetadataAddF32(recipe, PS_LIST_HEAD, "MOMENTS_AR_MAX", 0, NULL, 3.0);
+
+        pmPSFClump clump = pmSourcePSFClump(sources, NULL);
+        ok(clump.X == -1.0 && clump.dX == -1.0 && 
+           clump.Y == 0.0 && clump.dY == 0.0, "pmSourcePSFClump(sources, NULL) returned the error clump");
+
+        psFree(sources);
+        psFree(recipe);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourcePSFClump() with acceptable input parameters
+    // XX: This is a fairly simplistic test.  We set the moments of all test pmSources
+    // to (5.0, 5.0), so the clump should be fairly easy to detect.  For further testing
+    // add:
+    //	  Add outliers to the Sx and Sy data, make sure they aren't used in the calculation
+    //    Force it to get the various metadata from the recipes arg.
+    //    Add a few non pmSource types to the sources input pmArray
+    //    Add a few NULL pmSources, or NULL pmSource->moments to the sources input pmArray
+    //    Ensure the data is saved with the KEEP_PSF_CLUMP metadata item
+    //    Add cases where you fail to find a peak.
+    //    Modify data so that the inputs have different moments
+    //
+    {
+        psMemId id = psMemGetId();
+        psArray *sources = psArrayAlloc(NUM_SOURCES);
+        for (int i = 0 ; i < sources->n ; i++) {
+            pmSource *src = pmSourceAlloc();
+            src->moments = pmMomentsAlloc();
+            src->moments->Sx = 5.0;
+            src->moments->Sy = 5.0;
+            sources->data[i] = src;
+	}
+        psMetadata *recipe = psMetadataAlloc();
+        bool rc = psMetadataAddF32(recipe, PS_LIST_HEAD, "PSF_CLUMP_SN_LIM", 0, NULL, 0.0);
+        rc = psMetadataAddF32(recipe, PS_LIST_HEAD, "MOMENTS_SX_MAX", 0, NULL, 10.0);
+        rc = psMetadataAddF32(recipe, PS_LIST_HEAD, "MOMENTS_SY_MAX", 0, NULL, 10.0);
+        rc = psMetadataAddF32(recipe, PS_LIST_HEAD, "MOMENTS_AR_MAX", 0, NULL, 3.0);
+
+        pmPSFClump clump = pmSourcePSFClump(sources, recipe);
+        ok(clump.X == 5.0 && clump.dX == 0.0 && 
+           clump.Y == 5.0 && clump.dY == 0.0, "pmSourcePSFClump(sources, NULL) returned the correct clump");
+
+        if (VERBOSE) {
+            printf("clump: (%.2f %.2f %.2f %.2f)\n", clump.X, clump.dX, clump.Y, clump.dY);
+	}
+        psFree(sources);
+        psFree(recipe);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmSourceGetModel() tests
+    // pmModel *pmSourceGetModel (bool *isPSF, const pmSource *source)
+    // Call pmSourceGetModel() with NULL pmSource input parameter
+    #define NUM_SOURCES 10
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        bool isPDF;
+        pmModel *model = pmSourceGetModel(&isPDF, NULL);
+        ok(model == NULL, "pmSourceGetModel() returned NULL with NULL pmSource input parameter");
+
+        psFree(src);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // Call pmSourceGetModel() with acceptable input parameters
+    #define NUM_SOURCES 10
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        // For testing only:
+        src->modelPSF = (pmModel *) 1;
+        src->modelConv = (pmModel *) 2;
+        src->modelEXT = (pmModel *) 3;
+        bool isPDF;
+
+        src->modelConv = (pmModel *) 0;
+        src->type = PM_SOURCE_TYPE_UNKNOWN;
+        pmModel *model = pmSourceGetModel(&isPDF, src);
+        ok(model == NULL, "pmSourceGetModel() returned a NULL pmModel with acceptable input parameters and src->type = PM_SOURCE_TYPE_UNKNOWN");
+        ok(false == isPDF, "pmSourceGetModel() set isPDF to FALSE");
+
+        src->type = PM_SOURCE_TYPE_STAR;
+        model = pmSourceGetModel(&isPDF, src);
+        ok(model != NULL, "pmSourceGetModel() returned a non-NULL pmModel with acceptable input parameters");
+        ok(1 == (int) model, "pmSourceGetModel() returned the correct model with pmSource->type == PM_SOURCE_TYPE_STAR");
+        ok(true == isPDF, "pmSourceGetModel() set isPDF to TRUE");
+
+        src->type = PM_SOURCE_TYPE_EXTENDED;
+        src->modelConv = (pmModel *) 2;
+        model = pmSourceGetModel(&isPDF, src);
+        ok(model != NULL, "pmSourceGetModel() returned a non-NULL pmModel with acceptable input parameters");
+        ok(2 == (int) model, "pmSourceGetModel() returned the correct model with pmSource->type == PM_SOURCE_TYPE_EXTENDED (%d)", (int) model);
+        ok(false == isPDF, "pmSourceGetModel() set isPDF to FALSE");
+
+        src->modelConv = (pmModel *) 0;
+        src->type = PM_SOURCE_TYPE_EXTENDED;
+        model = pmSourceGetModel(&isPDF, src);
+        ok(model != NULL, "pmSourceGetModel() returned a non-NULL pmModel with acceptable input parameters");
+        ok(3 == (int) model, "pmSourceGetModel() returned the correct model with pmSource->type == PM_SOURCE_TYPE_EXTENDED");
+        ok(false == isPDF, "pmSourceGetModel() set isPDF to FALSE");
+
+        src->modelPSF = NULL;
+        src->modelConv = NULL;
+        src->modelEXT = NULL;
+        psFree(src);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmSourceOp() tests
+    // bool pmSourceOp (pmSource *source, pmModelOpMode mode, bool add,
+    //                  psMaskType maskVal, int dx, int dy)
+    // Call pmSourceOp() with NULL pmSource input parameter
+    #define NUM_SOURCES 10
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        bool rc = pmSourceOp(NULL, PM_MODEL_OP_NONE, true, 1, 0, 0);
+        ok(!rc, "pmSourceOpl() returned FALSE with NULL pmSource input parameter");
+        psFree(src);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // call pmSourceOp() with acceptable parameters
+    // We only test with a single Gaussian model, with no residuals or masks.
+    // For completeness, additional tests should be added.
+        // We should also set mode &= PM_MODEL_OP_NOISE to test that the src->weights are added.
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        src->pixels = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        for (int i = 0 ; i < TEST_NUM_ROWS ; i++) {
+            for (int j = 0 ; j < TEST_NUM_COLS ; j++) {
+                src->pixels->data.F32[i][j] = 0.0;
+            }
+        }
+        src->peak = pmPeakAlloc(TEST_NUM_COLS/2, TEST_NUM_ROWS/2, 5.0, PM_PEAK_LONE);
+        src->type = PM_SOURCE_TYPE_STAR;
+        src->modelPSF = pmModelAlloc(pmModelClassGetType("PS_MODEL_GAUSS"));
+        psF32 *PAR = src->modelPSF->params->data.F32;
+        PAR[PM_PAR_I0] = 5.0;
+        PAR[PM_PAR_XPOS] = 0.0;
+        PAR[PM_PAR_YPOS] = 0.0;
+        PAR[PM_PAR_XPOS] = (float) (TEST_NUM_COLS/2);
+        PAR[PM_PAR_YPOS] = (float) (TEST_NUM_ROWS/2);
+        PAR[PM_PAR_SXX] = 10.0;
+        PAR[PM_PAR_SYY] = 10.0;
+
+        bool rc = pmSourceOp(src, PM_SOURCE_MODE_PSFMODEL, true, 0, 0, 0);
+        ok(rc == true, "pmSourceOp() returned TRUE with acceptable input parameters");
+        psVector *x = psVectorAlloc(2, PS_TYPE_F32);
+        bool errorFlag = false;
+        for (int i = 0 ; i < TEST_NUM_ROWS ; i++) {
+            for (int j = 0 ; j < TEST_NUM_COLS ; j++) {
+                x->data.F32[0] = (float) j;
+                x->data.F32[1] = (float) i;
+                psF32 modF = src->modelPSF->modelFunc (NULL, src->modelPSF->params, x);
+                psF32 imgF = src->pixels->data.F32[i][j];
+                if (!TEST_FLOATS_EQUAL(modF, imgF)) {
+                    diag("ERROR: src->pixels[%d][%d] is %.2f, should be %.2f", i, j, src->pixels->data.F32[i][j], modF);
+                    errorFlag = true;
+                }
+            }
+        }
+        ok(!errorFlag, "pmSourceOp() set the image pixels correctly (PSF function)");
+        psFree(x);
+        psFree(src);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // call pmSourceOp() with acceptable parameters
+    // Test source->modelFlux
+        // We should also set mode &= PM_MODEL_OP_NOISE to test that the src->weights are added.
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        src->pixels = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        src->modelFlux = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        for (int i = 0 ; i < TEST_NUM_ROWS ; i++) {
+            for (int j = 0 ; j < TEST_NUM_COLS ; j++) {
+                src->pixels->data.F32[i][j] = 0.0;
+                src->modelFlux->data.F32[i][j] = (float) (i + j);
+            }
+        }
+        src->peak = pmPeakAlloc(TEST_NUM_COLS/2, TEST_NUM_ROWS/2, 5.0, PM_PEAK_LONE);
+        src->type = PM_SOURCE_TYPE_STAR;
+        src->modelPSF = pmModelAlloc(pmModelClassGetType("PS_MODEL_GAUSS"));
+        psF32 *PAR = src->modelPSF->params->data.F32;
+        PAR[PM_PAR_I0] = 1.0;
+        PAR[PM_PAR_XPOS] = 0.0;
+        PAR[PM_PAR_YPOS] = 0.0;
+        PAR[PM_PAR_XPOS] = (float) (TEST_NUM_COLS/2);
+        PAR[PM_PAR_YPOS] = (float) (TEST_NUM_ROWS/2);
+        PAR[PM_PAR_SXX] = 10.0;
+        PAR[PM_PAR_SYY] = 10.0;
+
+        bool rc = pmSourceOp(src, PM_SOURCE_MODE_PSFMODEL, true, 0, 0, 0);
+        ok(rc == true, "pmSourceOp() returned TRUE with acceptable input parameters");
+        psVector *x = psVectorAlloc(2, PS_TYPE_F32);
+        bool errorFlag = false;
+        for (int i = 0 ; i < TEST_NUM_ROWS ; i++) {
+            for (int j = 0 ; j < TEST_NUM_COLS ; j++) {
+                x->data.F32[0] = (float) j;
+                x->data.F32[1] = (float) i;
+                psF32 modF = src->modelFlux->data.F32[i][j];
+                psF32 imgF = src->pixels->data.F32[i][j];
+                if (!TEST_FLOATS_EQUAL(modF, imgF)) {
+                    diag("ERROR: src->pixels[%d][%d] is %.2f, should be %.2f", i, j, src->pixels->data.F32[i][j], modF);
+                    errorFlag = true;
+                }
+            }
+        }
+        ok(!errorFlag, "pmSourceOp() set the image pixels correctly (src->modelFlux cache image)");
+        psFree(x);
+        psFree(src);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmSourceCacheModel() tests
+    // call pmSourceCacheModel() with NULL pmSource input parameter
+    {
+        psMemId id = psMemGetId();
+        bool rc = pmSourceCacheModel(NULL, 0);
+        ok(rc == false, "pmSourceCacheModel() returned FALSE with NULL pmSource input parameter");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmSourceCacheModel() tests
+    // bool pmSourceCacheModel (pmSource *source, psMaskType maskVal) {
+    // call pmSourceCacheModel() with acceptable parameters
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        src->pixels = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        for (int i = 0 ; i < TEST_NUM_ROWS ; i++) {
+            for (int j = 0 ; j < TEST_NUM_COLS ; j++) {
+                src->pixels->data.F32[i][j] = 0.0;
+            }
+        }
+        src->peak = pmPeakAlloc(TEST_NUM_COLS/2, TEST_NUM_ROWS/2, 5.0, PM_PEAK_LONE);
+        src->type = PM_SOURCE_TYPE_STAR;
+        src->modelPSF = pmModelAlloc(pmModelClassGetType("PS_MODEL_GAUSS"));
+        psF32 *PAR = src->modelPSF->params->data.F32;
+        PAR[PM_PAR_I0] = 1.0;
+        PAR[PM_PAR_XPOS] = 0.0;
+        PAR[PM_PAR_YPOS] = 0.0;
+        PAR[PM_PAR_XPOS] = (float) (TEST_NUM_COLS/2);
+        PAR[PM_PAR_YPOS] = (float) (TEST_NUM_ROWS/2);
+        PAR[PM_PAR_SXX] = 10.0;
+        PAR[PM_PAR_SYY] = 10.0;
+
+        bool rc = pmSourceCacheModel(src, 0);
+        ok(rc == true, "pmSourceCacheModel() returned TRUE with acceptable input parameters");
+
+        psVector *x = psVectorAlloc(2, PS_TYPE_F32);
+        bool errorFlag = false;
+        for (int i = 0 ; i < TEST_NUM_ROWS ; i++) {
+            for (int j = 0 ; j < TEST_NUM_COLS ; j++) {
+                x->data.F32[0] = (float) j;
+                x->data.F32[1] = (float) i;
+                psF32 modF = src->modelPSF->modelFunc (NULL, src->modelPSF->params, x);
+                psF32 imgF = src->modelFlux->data.F32[i][j];
+                if (!TEST_FLOATS_EQUAL(modF, imgF)) {
+                    diag("ERROR: src->modelFlux[%d][%d] is %.2f, should be %.2f", i, j, src->modelFlux->data.F32[i][j], modF);
+                    errorFlag = true;
+                }
+            }
+        }
+        ok(!errorFlag, "pmSourceCacheModel() set the src->modelFlux correctly (PSF function)");
+        psFree(x);
+        psFree(src);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmSourceCachePSF() tests
+    // bool pmSourceCachePSF (pmSource *source, psMaskType maskVal) {
+    // call pmSourceCachePSF() with NULL pmSource input parameter
+    {
+        psMemId id = psMemGetId();
+        bool rc = pmSourceCachePSF(NULL, 0);
+        ok(rc == false, "pmSourceCachePSF() returned FALSE with NULL pmSource input parameter");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // call pmSourceCachePSF() with acceptable parameters
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        src->pixels = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        for (int i = 0 ; i < TEST_NUM_ROWS ; i++) {
+            for (int j = 0 ; j < TEST_NUM_COLS ; j++) {
+                src->pixels->data.F32[i][j] = 0.0;
+            }
+        }
+        src->peak = pmPeakAlloc(TEST_NUM_COLS/2, TEST_NUM_ROWS/2, 5.0, PM_PEAK_LONE);
+        src->type = PM_SOURCE_TYPE_STAR;
+        src->modelPSF = pmModelAlloc(pmModelClassGetType("PS_MODEL_GAUSS"));
+        psF32 *PAR = src->modelPSF->params->data.F32;
+        PAR[PM_PAR_I0] = 1.0;
+        PAR[PM_PAR_XPOS] = 0.0;
+        PAR[PM_PAR_YPOS] = 0.0;
+        PAR[PM_PAR_XPOS] = (float) (TEST_NUM_COLS/2);
+        PAR[PM_PAR_YPOS] = (float) (TEST_NUM_ROWS/2);
+        PAR[PM_PAR_SXX] = 10.0;
+        PAR[PM_PAR_SYY] = 10.0;
+
+        bool rc = pmSourceCachePSF(src, 0);
+        ok(rc == true, "pmSourceCachePSF() returned TRUE with acceptable input parameters");
+
+        psVector *x = psVectorAlloc(2, PS_TYPE_F32);
+        bool errorFlag = false;
+        for (int i = 0 ; i < TEST_NUM_ROWS ; i++) {
+            for (int j = 0 ; j < TEST_NUM_COLS ; j++) {
+                x->data.F32[0] = (float) j;
+                x->data.F32[1] = (float) i;
+                psF32 modF = src->modelPSF->modelFunc (NULL, src->modelPSF->params, x);
+                psF32 imgF = src->psfFlux->data.F32[i][j];
+                if (!TEST_FLOATS_EQUAL(modF, imgF)) {
+                    diag("ERROR: src->psfFlux[%d][%d] is %.2f, should be %.2f", i, j, src->psfFlux->data.F32[i][j], modF);
+                    errorFlag = true;
+                }
+            }
+        }
+        ok(!errorFlag, "pmSourceCachePSF() set the src->psfFlux correctly (PSF function)");
+        psFree(x);
+        psFree(src);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------
+    // pmSourceSortBySN() tests
+    // int pmSourceSortBySN (const void **a, const void **b)
+    // Call pmSourceSortBySN() with acceptable input parameters.
+    // XXX: We don't test with NULL input parameters since this function has no PS_ASSERTS to protect
+    // against that.
+/* XXXX: Compiler errors: fix this
+    {
+        psMemId id = psMemGetId();
+        pmSource *src1 = pmSourceAlloc();
+        src1->peak = pmPeakAlloc(3, 4, 2.0, PM_PEAK_LONE);
+        src1->peak->SN = 10.0;
+        pmSource *src2 = pmSourceAlloc();
+        src2->peak = pmPeakAlloc(3, 4, 2.0, PM_PEAK_LONE);
+        src2->peak->SN = 20.0;
+
+        int rc = pmSourceSortBySN((const void **) &src1, (const void **) &src2);
+        ok(rc == 1, "pmSourceSortBySN() returned correct result (source1 < source2) (%d)", rc);
+        rc = pmSourceSortBySN((const void **) &src2, (const void **) &src1);
+        ok(rc == -1, "pmSourceSortBySN() returned correct result (source2 < source1) (%d)", rc);
+        rc = pmSourceSortBySN((const void **) &src1, (const void **) &src1);
+        ok(rc == 0, "pmSourceSortBySN() returned correct result (source1 == source2) (%d)", rc);
+
+        psFree(src1);
+        psFree(src2);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------
+    // pmSourceSortByY() tests
+    // int pmSourceSortByY (const void **a, const void **b)
+    // Call pmSourceSortByY() with acceptable input parameters.
+    // XXX: We don't test with NULL input parameters since this function has no PS_ASSERTS to protect
+    // against that.
+    {
+        psMemId id = psMemGetId();
+        pmSource *src1 = pmSourceAlloc();
+        src1->peak = pmPeakAlloc(3, 4, 2.0, PM_PEAK_LONE);
+        src1->peak->y = 10.0;
+        pmSource *src2 = pmSourceAlloc();
+        src2->peak = pmPeakAlloc(3, 4, 2.0, PM_PEAK_LONE);
+        src2->peak->y = 20.0;
+
+        int rc = pmSourceSortByY((const void **) &src1, (const void **) &src2);
+        ok(rc == -1, "pmSourceSortByY() returned correct result (source1 < source2) (%d)", rc);
+        rc = pmSourceSortByY((const void **) &src2, (const void **) &src1);
+        ok(rc == 1, "pmSourceSortByY() returned correct result (source2 < source1) (%d)", rc);
+        rc = pmSourceSortByY((const void **) &src1, (const void **) &src1);
+        ok(rc == 0, "pmSourceSortByY() returned correct result (source1 == source2) (%d)", rc);
+
+        psFree(src1);
+        psFree(src2);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+*/
+
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceContour.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceContour.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceContour.c	(revision 22157)
@@ -0,0 +1,179 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS:
+    XXX: All functions only tested with unallowable input parameters.
+    I tried to use acceptable data, but could not get the source code to work the
+    way I thought it should have.
+*/
+#define MISC_NUM                32
+#define MISC_NAME              "META00"
+#define NUM_BIAS_DATA           10
+#define TEST_NUM_ROWS           (10)
+#define TEST_NUM_COLS           (16)
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         10
+#define TEST_FLOATS_EQUAL(X, Y) (abs(X - Y) < 0.0001)
+#define NUM_SOURCES		100
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(11);
+
+
+    // ----------------------------------------------------------------------
+    // pmSourceContour() tests
+    // psArray *pmSourceContour (psImage *image, int xc, int yc, float threshold)
+    // Call pmSourceContour() with NULL psImage input parameter
+    {
+        psMemId id = psMemGetId();
+        psImage *img = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psArray *array = pmSourceContour(NULL, 1, 2, 3.0);
+        ok(array == NULL, "pmSourceContour() returned NULL with NULL psImage input parameter");
+        psFree(img);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceContour() with unallowed row/column numbers
+    {
+        psMemId id = psMemGetId();
+        psImage *img = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psArray *array = pmSourceContour(img, -1, 2, 3.0);
+        ok(array == NULL, "pmSourceContour() returned NULL with column = -1");
+        array = pmSourceContour(img, TEST_NUM_COLS, 2, 3.0);
+        ok(array == NULL, "pmSourceContour() returned NULL with column >= numCols");
+        array = pmSourceContour(img, 1, -1, 3.0);
+        ok(array == NULL, "pmSourceContour() returned NULL with row = -1");
+        array = pmSourceContour(img, 1, TEST_NUM_ROWS, 3.0);
+        ok(array == NULL, "pmSourceContour() returned NULL with row >= numRows");
+
+        psFree(img);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceContour() with acceptable input parameters
+    // XXX: These tests currently fail
+    if (0) {
+        psMemId id = psMemGetId();
+        psImage *img = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        for (int i = 0 ; i < img->numRows ; i++) {
+            for (int j = 0 ; j < img->numCols ; j++) {
+                img->data.F32[i][j] = (float) ((TEST_NUM_ROWS + TEST_NUM_COLS) - (abs(i - (TEST_NUM_ROWS/2)) + abs(j - (TEST_NUM_COLS/2))));
+	    }
+	}
+        if (1) {
+            for (int i = 0 ; i < img->numRows ; i++) {
+                for (int j = 0 ; j < img->numCols ; j++) {
+                    printf("(%.0f)", img->data.F32[i][j]);
+    	    }
+                printf("\n");
+	    }
+	}
+
+        psArray *array = pmSourceContour(img, TEST_NUM_COLS/2, TEST_NUM_ROWS/2, 22.0);
+        ok(array != NULL, "pmSourceContour() returned non-NULL with acceptable input parameters");
+        for (int i = 0 ; i < array->n ; i++) {
+            psVector *vec = (psVector *) array->data[i];
+            printf("Point %d: (%.2f %.2f)\n", i, vec->data.F32[0], vec->data.F32[1]);
+	}
+
+        psFree(array);
+        psFree(img);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmSourceContour_Crude() tests
+    // psArray *pmSourceContour_Crude_Crude(pmSource *source, psImage *image, psF32 level)
+    // Call pmSourceContour_Crude() with NULL psSource input parameter
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+        src->pixels = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        src->moments = pmMomentsAlloc();
+        src->modelEXT = pmModelAlloc(1);
+        psImage *img = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        for (int i = 0 ; i < img->numRows ; i++) {
+            for (int j = 0 ; j < img->numCols ; j++) {
+                if ((i >= TEST_NUM_ROWS/4) && (i < 3*TEST_NUM_ROWS/4) &&
+                    (j >= TEST_NUM_COLS/4) && (j < 3*TEST_NUM_COLS/4)) {
+                    img->data.F32[i][j] = 5.0;
+		} else {
+                    img->data.F32[i][j] = 0.0;
+		}
+	    }
+	}
+        psArray *array = pmSourceContour_Crude(NULL, img, 3.0);
+        ok(array == NULL, "pmSourceContour_Crude() returned NULL with NULL pmSource input parameter");
+        psFree(img);
+        psFree(src);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceContour_Crude() with NULL psImage input parameter
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+        src->pixels = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        src->moments = pmMomentsAlloc();
+        src->modelEXT = pmModelAlloc(1);
+        psImage *img = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        for (int i = 0 ; i < img->numRows ; i++) {
+            for (int j = 0 ; j < img->numCols ; j++) {
+                if ((i >= TEST_NUM_ROWS/4) && (i < 3*TEST_NUM_ROWS/4) &&
+                    (j >= TEST_NUM_COLS/4) && (j < 3*TEST_NUM_COLS/4)) {
+                    img->data.F32[i][j] = 5.0;
+		} else {
+                    img->data.F32[i][j] = 0.0;
+		}
+	    }
+	}
+        psArray *array = pmSourceContour_Crude(src, NULL, 3.0);
+        ok(array == NULL, "pmSourceContour_Crude() returned NULL with NULL pmImage input parameter");
+        psFree(img);
+        psFree(src);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceContour_Crude() with acceptable input parameters
+    // XXX: Must correct this
+    if (0) {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+        src->pixels = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        src->moments = pmMomentsAlloc();
+        src->modelEXT = pmModelAlloc(1);
+        psImage *img = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+
+        if (0) {
+            for (int i = 0 ; i < img->numRows ; i++) {
+                for (int j = 0 ; j < img->numCols ; j++) {
+                    img->data.F32[i][j] = (float) ((TEST_NUM_ROWS + TEST_NUM_COLS) - (abs(i - (TEST_NUM_ROWS/2)) + abs(j - (TEST_NUM_COLS/2))));
+		}
+	    }
+	}
+        printf("Calling pmSourceContour_Crude()\n");
+        psArray *array = pmSourceContour_Crude(src, img, 22.0);
+        printf("Called pmSourceContour_Crude()\n");
+        ok(array != NULL, "pmSourceContour_Crude() returned non-NULL with NULL pmImage input parameter");
+        psFree(img);
+        psFree(src);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceExtendedPars.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceExtendedPars.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceExtendedPars.c	(revision 22157)
@@ -0,0 +1,121 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS:
+        All functions are tested.
+*/
+
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(35);
+
+    // ----------------------------------------------------------------------
+    // pmSourceExtendedParsAlloc() tests
+    {
+        psMemId id = psMemGetId();
+        pmSourceExtendedPars *tmp = pmSourceExtendedParsAlloc();
+        ok(tmp && psMemCheckSourceExtendedPars(tmp), "pmSourceExtendedParsAlloc() allocated a pmSourceExtendedPars struct");
+
+        ok(tmp->profile == NULL, "pmSourceExtendedParsAlloc() set the ->profile member to NULL");
+        ok(tmp->annuli == NULL, "pmSourceExtendedParsAlloc() set the ->annuli member to NULL");
+        ok(tmp->isophot == NULL, "pmSourceExtendedParsAlloc() set the ->isophot member to NULL");
+        ok(tmp->petrosian == NULL, "pmSourceExtendedParsAlloc() set the ->petrosian member to NULL");
+        ok(tmp->kron == NULL, "pmSourceExtendedParsAlloc() set the ->kron member to NULL");
+
+        psFree(tmp);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmSourceRadialProfileAlloc() tests
+    {
+        psMemId id = psMemGetId();
+        pmSourceRadialProfile *tmp = pmSourceRadialProfileAlloc();
+        ok(tmp && psMemCheckSourceRadialProfile(tmp), "pmSourceRadialProfile() allocated a pmSourceRadialProfilestruct");
+
+        ok(tmp->radius == NULL, "pmSourceRadialProfileAlloc() set the ->radius member to NULL");
+        ok(tmp->flux == NULL, "pmSourceRadialProfileAlloc() set the ->flux member to NULL");
+        ok(tmp->weight == NULL, "pmSourceRadialProfileAlloc() set the ->weight member to NULL");
+
+        psFree(tmp);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmSourceIsophotalValuesAlloc() tests
+    {
+        psMemId id = psMemGetId();
+        pmSourceIsophotalValues *tmp = pmSourceIsophotalValuesAlloc();
+        ok(tmp && psMemCheckSourceIsophotalValues(tmp), "pmSourceIsophotalValues() allocated a pmSourceIsophotalValuesstruct");
+
+        ok(tmp->mag == 0.0, "pmSourceIsophotalValuesAlloc() set the ->mag member to 0.0");
+        ok(tmp->magErr == 0.0, "pmSourceIsophotalValuesAlloc() set the ->magErr member to 0.0");
+        ok(tmp->rad == 0.0, "pmSourceIsophotalValuesAlloc() set the ->rad member to 0.0");
+        ok(tmp->radErr == 0.0, "pmSourceIsophotalValuesAlloc() set the ->radErr member to 0.0");
+
+        psFree(tmp);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmSourcePetrosianValuesAlloc() tests
+    {
+        psMemId id = psMemGetId();
+        pmSourcePetrosianValues *tmp = pmSourcePetrosianValuesAlloc();
+        ok(tmp && psMemCheckSourcePetrosianValues(tmp), "pmSourcePetrosianValues() allocated a pmSourcePetrosianValuesstruct");
+
+        ok(tmp->mag == 0.0, "pmSourcePetrosianValuesAlloc() set the ->mag member to 0.0");
+        ok(tmp->magErr == 0.0, "pmSourcePetrosianValuesAlloc() set the ->magErr member to 0.0");
+        ok(tmp->rad == 0.0, "pmSourcePetrosianValuesAlloc() set the ->rad member to 0.0");
+        ok(tmp->radErr == 0.0, "pmSourcePetrosianValuesAlloc() set the ->radErr member to 0.0");
+
+        psFree(tmp);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmSourceKronValuesAlloc() tests
+    {
+        psMemId id = psMemGetId();
+        pmSourceKronValues *tmp = pmSourceKronValuesAlloc();
+        ok(tmp && psMemCheckSourceKronValues(tmp), "pmSourceKronValues() allocated a pmSourceKronValuesstruct");
+
+        ok(tmp->mag == 0.0, "pmSourceKronValuesAlloc() set the ->mag member to 0.0");
+        ok(tmp->magErr == 0.0, "pmSourceKronValuesAlloc() set the ->magErr member to 0.0");
+        ok(tmp->rad == 0.0, "pmSourceKronValuesAlloc() set the ->rad member to 0.0");
+        ok(tmp->radErr == 0.0, "pmSourceKronValuesAlloc() set the ->radErr member to 0.0");
+
+        psFree(tmp);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmSourceAnnuliAlloc() tests
+    {
+        psMemId id = psMemGetId();
+        pmSourceAnnuli *tmp = pmSourceAnnuliAlloc();
+        ok(tmp && psMemCheckSourceAnnuli(tmp), "pmSourceAnnuli() allocated a pmSourceAnnulistruct");
+
+        ok(tmp->flux == NULL, "pmSourceAnnuliAlloc() set the ->flux member to NULL");
+        ok(tmp->fluxErr == NULL, "pmSourceAnnuliAlloc() set the ->fluxErr member to NULL");
+        ok(tmp->fluxVar == NULL, "pmSourceAnnuliAlloc() set the ->fluxVar member to NULL");
+
+        psFree(tmp);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceFitModel.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceFitModel.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceFitModel.c	(revision 22157)
@@ -0,0 +1,191 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "tap.h"
+#include "pstap.h"
+
+bool fitModels (psRandom *seed, float flux, float radius, float sigma);
+bool fitModelFlux (psRandom *seed, float flux, float radius, float sigma);
+
+// tests to check accuracy of fitted models for a range of fit radii, sigma, and flux.
+// we generate a fake source, then fit the model to it.  the difference or fractional
+// difference between the true and fitted parameters is saved.
+// we run these tests 200 times each an examine the resulting distribution of deviations.
+// the tests fail if the stdevs are more than 2x the expected stdev based on poisson noise
+// ** is 2x too generous?
+int main (void)
+{
+    pmModelGroupInit ();
+    pmSourceFitModelInit (15, 0.01, 1.0, true);
+
+    // psTraceSetLevel ("psModules.objects.pmSourceFitModel", 10);
+    // psTraceSetLevel ("psLib.math.psMinimizeLMChi2", 10);
+
+    plan_tests(240);
+
+    // build a gauss-deviate vector (mean = 0.0, sigma = 1.0)
+    psRandom *seed = psRandomAlloc (PS_RANDOM_TAUS, 0);
+
+    static float radius[] = {3.0, 5.0, 7.0, 10.0, 15.0, 25.0};
+    static float sigma[] = {1.0, 1.5, 2.0};
+    static float flux[] = {10000.0, 3000.0, 1000.0, 300.0, 100.0, 30.0, 10.0};
+
+    // drop-dead simple: should always work
+    bool status = fitModels (seed, 10000.0, 10.0, 2.0);
+    skip_start (!status, 240, "*** BASIC MODEL FITTING FAILS! *** : skipping related tests");
+
+    for (int i = 0; i < sizeof(sigma)/sizeof(float); i++) {
+        for (int j = 0; j < sizeof(radius)/sizeof(float); j++) {
+            for (int k = 0; k < sizeof(flux)/sizeof(float); k++) {
+                fitModels (seed, flux[k], radius[j], sigma[i]);
+            }
+        }
+    }
+
+    skip_end();
+    return exit_status();
+}
+
+static psVector *par1 = NULL;
+static psVector *par2 = NULL;
+static psVector *par3 = NULL;
+static psVector *par4 = NULL;
+static psVector *par5 = NULL;
+
+# define NMODELS 200
+bool fitModels (psRandom *seed, float flux, float radius, float sigma)
+{
+
+    psMemId id = psMemGetId();
+
+    diag("test model fit - flux: %f, radius: %f, sigma: %f", flux, radius, sigma);
+
+    par1 = psVectorAllocEmpty (NMODELS, PS_TYPE_F32);
+    par2 = psVectorAllocEmpty (NMODELS, PS_TYPE_F32);
+    par3 = psVectorAllocEmpty (NMODELS, PS_TYPE_F32);
+    par4 = psVectorAllocEmpty (NMODELS, PS_TYPE_F32);
+    par5 = psVectorAllocEmpty (NMODELS, PS_TYPE_F32);
+
+    for (int i = 0; i < NMODELS; i++) {
+        fitModelFlux (seed, flux, radius, sigma);
+    }
+
+    float signal = 2*M_PI*sigma*sigma*flux;
+    float noise = sqrt(signal + 4*M_PI*sigma*sigma*(100 + PS_SQR(5)));
+    float dMag = noise / signal;
+    float dPos = sigma * dMag;
+    diag ("signal: %f, noise: %f, dMag: %f, dPos: %f", signal, noise, dMag, dPos);
+
+    bool status = (par1->n == NMODELS);
+    ok (status, "all %d tests passed", NMODELS);
+
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV);
+    psVectorStats (stats, par1, NULL, NULL, 0);
+    ok ((stats->sampleStdev/dMag < 2.0), "Io ref/fit stdev: %e : %e sigma", stats->sampleStdev, stats->sampleStdev/dMag);
+    psVectorStats (stats, par2, NULL, NULL, 0);
+    ok ((stats->sampleStdev/dPos < 2.0), "Xo ref/fit stdev: %e : %e sigma", stats->sampleStdev, stats->sampleStdev/dPos);
+    psVectorStats (stats, par3, NULL, NULL, 0);
+    ok ((stats->sampleStdev/dPos < 2.0), "Yo ref/fit stdev: %e : %e sigma", stats->sampleStdev, stats->sampleStdev/dPos);
+    psVectorStats (stats, par4, NULL, NULL, 0);
+    ok ((stats->sampleStdev/dMag < 2.0), "Sx ref/fit stdev: %e : %e sigma", stats->sampleStdev, stats->sampleStdev/dMag);
+    psVectorStats (stats, par5, NULL, NULL, 0);
+    ok ((stats->sampleStdev/dMag < 2.0), "Sy ref/fit stdev: %e : %e sigma", stats->sampleStdev, stats->sampleStdev/dMag);
+
+    psFree (par1);
+    psFree (par2);
+    psFree (par3);
+    psFree (par4);
+    psFree (par5);
+    psFree (stats);
+
+    ok(!psMemCheckLeaks (id, NULL, stderr, false), "no memory leaks");
+    return status;
+}
+
+bool fitModelFlux (psRandom *seed, float flux, float radius, float sigma)
+{
+
+    psVector *rnd = psVectorAlloc (1000, PS_TYPE_F32);
+    for (int i = 0; i < rnd->n; i++) {
+        rnd->data.F32[i] = psRandomGaussian (seed);
+    }
+
+    // construct a model
+    pmSource *source = pmSourceAlloc ();
+    source->moments = pmMomentsAlloc ();
+
+    pmModelType type = pmModelClassGetType ("PS_MODEL_GAUSS");
+    source->modelEXT = pmModelAlloc (type);
+
+    source->modelEXT->params->data.F32[0] = 0;
+    source->modelEXT->params->data.F32[1] = flux;
+    source->modelEXT->params->data.F32[2] = 50;
+    source->modelEXT->params->data.F32[3] = 50;
+    source->modelEXT->params->data.F32[4] = 2.0*sqrt(sigma);
+    source->modelEXT->params->data.F32[5] = 2.0*sqrt(sigma);
+    source->modelEXT->params->data.F32[6] = 0;
+
+    source->pixels = psImageAlloc (100, 100, PS_TYPE_F32);
+    source->weight = psImageAlloc (100, 100, PS_TYPE_F32);
+    source->mask   = psImageAlloc (100, 100, PS_TYPE_U8);
+    psImageInit (source->pixels, 0.0);
+    psImageInit (source->weight, 0.0);
+    psImageInit (source->mask, 0);
+
+    // create an image with the model, and add noise: gain is 1, subtracted sky is 100, readnoise is 5
+    pmModelAdd (source->pixels, source->mask, source->modelEXT, PM_MODEL_OP_FULL);
+    int npix = 0;
+    for (int j = 0; j < source->pixels->numRows; j++) {
+        for (int i = 0; i < source->pixels->numCols; i++) {
+            float flux = source->pixels->data.F32[j][i];
+            float var = flux + 100 + PS_SQR(5);
+            source->pixels->data.F32[j][i] += rnd->data.F32[npix]*sqrt(var);
+            source->weight->data.F32[j][i] = var;
+            npix ++;
+            if (npix == rnd->n)
+                npix = 0;
+        }
+    }
+
+    // psFits *fits = psFitsOpen ("test.fits", "w");
+    // psFitsWriteImage (fits, NULL, source->pixels, 0, NULL);
+    // psFitsClose (fits);
+
+    // save the original model, modify params
+    pmModel *guess = pmModelCopy (source->modelEXT);
+    guess->params->data.F32[1] *= 0.9;
+    guess->params->data.F32[2] += 1.0;
+    guess->params->data.F32[3] -= 1.0;
+    guess->params->data.F32[4] *= 0.9;
+    guess->params->data.F32[5] *= 0.9;
+
+    psImageKeepCircle (source->mask, 50, 50, radius, "OR", PM_MASK_MARK);
+    bool status = pmSourceFitModel (source, guess, PM_SOURCE_FIT_EXT);
+    if (!status) {
+        psFree (rnd);
+        psFree (source);
+        psFree (guess);
+        return false;
+    }
+    psImageKeepCircle (source->mask, 50, 50, radius, "AND", PS_NOT_U8(PM_MASK_MARK));
+
+    par1->data.F32[par1->n] = (source->modelEXT->params->data.F32[1] / guess->params->data.F32[1]);
+    par2->data.F32[par2->n] = (source->modelEXT->params->data.F32[2] - guess->params->data.F32[2]);
+    par3->data.F32[par3->n] = (source->modelEXT->params->data.F32[3] - guess->params->data.F32[3]);
+    par4->data.F32[par4->n] = (source->modelEXT->params->data.F32[4] / guess->params->data.F32[4]);
+    par5->data.F32[par5->n] = (source->modelEXT->params->data.F32[5] / guess->params->data.F32[5]);
+
+    psVectorExtend (par1, 100, 1);
+    psVectorExtend (par2, 100, 1);
+    psVectorExtend (par3, 100, 1);
+    psVectorExtend (par4, 100, 1);
+    psVectorExtend (par5, 100, 1);
+
+    psFree (rnd);
+    psFree (source);
+    psFree (guess);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceFitModel_Delta.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceFitModel_Delta.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceFitModel_Delta.c	(revision 22157)
@@ -0,0 +1,238 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "tap.h"
+#include "pstap.h"
+
+bool fitModels (psRandom *seed, float flux, float radius, float sigma);
+bool fitModelFlux (psRandom *seed, float flux, float radius, float sigma);
+bool printDev (float *src, float *par, int Npar, bool absolute);
+
+int main (void)
+{
+    pmModelGroupInit ();
+    pmSourceFitModelInit (15, 0.01, 1.0, true);
+
+    float flux = 10000;
+    float sigma = 2.0;
+    float radius = 10.0;
+
+    plan_tests(240);
+
+    // build a gauss-deviate vector (mean = 0.0, sigma = 1.0)
+    psRandom *seed = psRandomAlloc (PS_RANDOM_TAUS, 0);
+
+    // noise vector to noise up the image
+    psVector *rnd = psVectorAlloc (1000, PS_TYPE_F32);
+    for (int i = 0; i < rnd->n; i++) {
+        rnd->data.F32[i] = psRandomGaussian (seed);
+    }
+
+    // construct a GAUSS model
+    pmSource *source = pmSourceAlloc ();
+    source->moments = pmMomentsAlloc ();
+
+    pmModelType type = pmModelClassGetType ("PS_MODEL_GAUSS");
+    source->modelEXT = pmModelAlloc (type);
+
+    source->modelEXT->params->data.F32[0] = 0;
+    source->modelEXT->params->data.F32[1] = flux;
+    source->modelEXT->params->data.F32[2] = 50;
+    source->modelEXT->params->data.F32[3] = 50;
+    source->modelEXT->params->data.F32[4] = 2.0*sqrt(sigma);
+    source->modelEXT->params->data.F32[5] = 2.0*sqrt(sigma);
+    source->modelEXT->params->data.F32[6] = 0;
+
+    source->pixels = psImageAlloc (100, 100, PS_TYPE_F32);
+    source->weight = psImageAlloc (100, 100, PS_TYPE_F32);
+    source->mask   = psImageAlloc (100, 100, PS_TYPE_U8);
+    psImageInit (source->pixels, 0.0);
+    psImageInit (source->weight, 0.0);
+    psImageInit (source->mask, 0);
+
+    // create an image with the model, and add noise: gain is 1, subtracted sky is 100, readnoise is 5
+    pmModelAdd (source->pixels, source->mask, source->modelEXT, PM_MODEL_OP_FULL);
+    int npix = 0;
+    for (int j = 0; j < source->pixels->numRows; j++) {
+        for (int i = 0; i < source->pixels->numCols; i++) {
+            float flux = source->pixels->data.F32[j][i];
+            float var = flux + 100 + PS_SQR(5);
+            source->pixels->data.F32[j][i] += rnd->data.F32[npix]*sqrt(var);
+            source->weight->data.F32[j][i] = var;
+            npix ++;
+            if (npix == rnd->n)
+                npix = 0;
+        }
+    }
+
+    // fit with a PGAUSS model
+    pmModel *guess;
+
+    type = pmModelClassGetType ("PS_MODEL_PGAUSS");
+    guess = pmModelAlloc (type);
+
+    guess->params->data.F32[0] = 0;
+    guess->params->data.F32[1] = flux;
+    guess->params->data.F32[2] = 50;
+    guess->params->data.F32[3] = 50;
+    guess->params->data.F32[4] = 2.0*sqrt(sigma);
+    guess->params->data.F32[5] = 2.0*sqrt(sigma);
+    guess->params->data.F32[6] = 0;
+    // modify guess
+    guess->params->data.F32[1] *= 0.9;
+    guess->params->data.F32[2] += 1.0;
+    guess->params->data.F32[3] -= 1.0;
+    guess->params->data.F32[4] *= 0.9;
+    guess->params->data.F32[5] *= 0.9;
+
+    psImageKeepCircle (source->mask, 50, 50, radius, "OR", PM_MASK_MARK);
+    pmSourceFitModel (source, guess, PM_SOURCE_FIT_EXT);
+    psImageKeepCircle (source->mask, 50, 50, radius, "AND", PS_NOT_U8(PM_MASK_MARK));
+
+    printDev (source->modelEXT->params->data.F32, guess->params->data.F32, 1, false);
+    printDev (source->modelEXT->params->data.F32, guess->params->data.F32, 2, true);
+    printDev (source->modelEXT->params->data.F32, guess->params->data.F32, 3, true);
+    printDev (source->modelEXT->params->data.F32, guess->params->data.F32, 4, false);
+    printDev (source->modelEXT->params->data.F32, guess->params->data.F32, 5, false);
+
+    psImageKeepCircle (source->mask, 50, 50, radius, "OR", PM_MASK_MARK);
+    pmSourceFitModel (source, guess, PM_SOURCE_FIT_PSF);
+    psImageKeepCircle (source->mask, 50, 50, radius, "AND", PS_NOT_U8(PM_MASK_MARK));
+
+    printDev (source->modelEXT->params->data.F32, guess->params->data.F32, 1, false);
+    printDev (source->modelEXT->params->data.F32, guess->params->data.F32, 2, true);
+    printDev (source->modelEXT->params->data.F32, guess->params->data.F32, 3, true);
+    printDev (source->modelEXT->params->data.F32, guess->params->data.F32, 4, false);
+    printDev (source->modelEXT->params->data.F32, guess->params->data.F32, 5, false);
+
+    // muck up the Sx, Sy terms a little : how does this affect the dparams?
+    float Sx = guess->params->data.F32[4];
+    float Sy = guess->params->data.F32[5];
+    guess->params->data.F32[4] = 0.95*Sx;
+    guess->params->data.F32[5] = 0.95*Sy;
+
+    psImageKeepCircle (source->mask, 50, 50, radius, "OR", PM_MASK_MARK);
+    pmSourceFitModel (source, guess, PM_SOURCE_FIT_PSF);
+    psImageKeepCircle (source->mask, 50, 50, radius, "AND", PS_NOT_U8(PM_MASK_MARK));
+
+    printDev (source->modelEXT->params->data.F32, guess->params->data.F32, 1, false);
+    printDev (source->modelEXT->params->data.F32, guess->params->data.F32, 2, true);
+    printDev (source->modelEXT->params->data.F32, guess->params->data.F32, 3, true);
+    printDev (source->modelEXT->params->data.F32, guess->params->data.F32, 4, false);
+    printDev (source->modelEXT->params->data.F32, guess->params->data.F32, 5, false);
+
+    // muck up the Sx, Sy terms a little : how does this affect the dparams?
+    guess->params->data.F32[4] = 0.99*Sx;
+    guess->params->data.F32[5] = 0.99*Sy;
+
+    psImageKeepCircle (source->mask, 50, 50, radius, "OR", PM_MASK_MARK);
+    pmSourceFitModel (source, guess, PM_SOURCE_FIT_PSF);
+    psImageKeepCircle (source->mask, 50, 50, radius, "AND", PS_NOT_U8(PM_MASK_MARK));
+
+    printDev (source->modelEXT->params->data.F32, guess->params->data.F32, 1, false);
+    printDev (source->modelEXT->params->data.F32, guess->params->data.F32, 2, true);
+    printDev (source->modelEXT->params->data.F32, guess->params->data.F32, 3, true);
+    printDev (source->modelEXT->params->data.F32, guess->params->data.F32, 4, false);
+    printDev (source->modelEXT->params->data.F32, guess->params->data.F32, 5, false);
+
+    psFree (rnd);
+    psFree (source);
+    psFree (guess);
+
+    return true;
+}
+
+bool printDev (float *src, float *fit, int Npar, bool absolute)
+{
+    float dev;
+    if (absolute) {
+        dev = (src[Npar]-fit[Npar]);
+        fprintf (stderr, "par %d : %f vs %f : abso dev %f\n", Npar, src[Npar], fit[Npar], dev);
+    } else {
+        dev = (src[Npar]/fit[Npar]);
+        fprintf (stderr, "par %d : %f vs %f : frac dev %f\n", Npar, src[Npar], fit[Npar], dev);
+    }
+    return true;
+}
+
+# if (0)
+    int main (void)
+{
+    pmModelGroupInit ();
+    pmSourceFitModelInit (15, 0.01, 1.0, true);
+
+    plan_tests(240);
+
+    // build a gauss-deviate vector (mean = 0.0, sigma = 1.0)
+    psRandom *seed = psRandomAlloc (PS_RANDOM_TAUS, 0);
+
+    static float radius[] = {3.0, 5.0, 7.0, 10.0, 15.0, 25.0};
+    static float sigma[] = {1.0, 1.5, 2.0};
+    static float flux[] = {10000.0, 3000.0, 1000.0, 300.0, 100.0, 30.0, 10.0};
+
+    for (int i = 0; i < sizeof(sigma)/sizeof(float); i++) {
+        for (int j = 0; j < sizeof(radius)/sizeof(float); j++) {
+            for (int k = 0; k < sizeof(flux)/sizeof(float); k++) {
+                fitModels (seed, flux[k], radius[j], sigma[i]);
+            }
+        }
+    }
+
+    return exit_status();
+}
+
+static psVector *par1 = NULL;
+static psVector *par2 = NULL;
+static psVector *par3 = NULL;
+static psVector *par4 = NULL;
+static psVector *par5 = NULL;
+
+bool fitModels (psRandom *seed, float flux, float radius, float sigma)
+{
+
+    psMemId id = psMemGetId();
+
+    diag("test model fit - flux: %f, radius: %f, sigma: %f", flux, radius, sigma);
+
+    par1 = psVectorAllocEmpty (200, PS_TYPE_F32);
+    par2 = psVectorAllocEmpty (200, PS_TYPE_F32);
+    par3 = psVectorAllocEmpty (200, PS_TYPE_F32);
+    par4 = psVectorAllocEmpty (200, PS_TYPE_F32);
+    par5 = psVectorAllocEmpty (200, PS_TYPE_F32);
+
+    for (int i = 0; i < 200; i++) {
+        fitModelFlux (seed, flux, radius, sigma);
+    }
+
+    float signal = 2*M_PI*sigma*sigma*flux;
+    float noise = sqrt(signal + 4*M_PI*sigma*sigma*(100 + PS_SQR(5)));
+    float dMag = noise / signal;
+    float dPos = sigma * dMag;
+    diag ("signal: %f, noise: %f, dMag: %f, dPos: %f", signal, noise, dMag, dPos);
+
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV);
+    psVectorStats (stats, par1, NULL, NULL, 0);
+    ok ((stats->sampleStdev/dMag < 2.0), "Io ref/fit stdev: %f : %f sigma", stats->sampleStdev, stats->sampleStdev/dMag);
+    psVectorStats (stats, par2, NULL, NULL, 0);
+    ok ((stats->sampleStdev/dPos < 2.0), "Xo ref/fit stdev: %f : %f sigma", stats->sampleStdev, stats->sampleStdev/dPos);
+    psVectorStats (stats, par3, NULL, NULL, 0);
+    ok ((stats->sampleStdev/dPos < 2.0), "Yo ref/fit stdev: %f : %f sigma", stats->sampleStdev, stats->sampleStdev/dPos);
+    psVectorStats (stats, par4, NULL, NULL, 0);
+    ok ((stats->sampleStdev/dMag < 2.0), "Sx ref/fit stdev: %f : %f sigma", stats->sampleStdev, stats->sampleStdev/dMag);
+    psVectorStats (stats, par5, NULL, NULL, 0);
+    ok ((stats->sampleStdev/dMag < 2.0), "Sy ref/fit stdev: %f : %f sigma", stats->sampleStdev, stats->sampleStdev/dMag);
+
+    psFree (par1);
+    psFree (par2);
+    psFree (par3);
+    psFree (par4);
+    psFree (par5);
+    psFree (stats);
+
+    ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    return true;
+}
+
+# endif
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceFitSet.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceFitSet.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceFitSet.c	(revision 22157)
@@ -0,0 +1,726 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS:
+    All functions are tested except:
+        pmSourceFitSetCheckLimits()
+        pmSourceFitSetFunction()
+    Those functions set static variables which aer invisible to test code.
+
+    These functions are very lightly tested and must be augmented:
+	pmSourceFitSet()
+	pmSourceFitSetMasks()
+*/
+
+#define MISC_NUM                32
+#define MISC_NAME              "META00"
+#define NUM_BIAS_DATA           10
+#define TEST_NUM_ROWS           (16)
+#define TEST_NUM_COLS           (30)
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+#define TEST_FLOATS_EQUAL(X, Y) (abs(X - Y) < 0.0001)
+#define NUM_MODELS		5
+
+pmSource *create_pmSource() {
+    pmSource *src = pmSourceAlloc();
+    if (1) {
+        src->pixels = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        src->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        src->maskObj = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+        if (1) {
+            for (int i = 0 ; i < TEST_NUM_ROWS ; i++) {
+                for (int j = 0 ; j < TEST_NUM_COLS ; j++) {
+                    src->pixels->data.F32[i][j] = 0.0;
+                    src->weight->data.F32[i][j] = 1.0;
+                    src->maskObj->data.U8[i][j] = 0;
+                }
+            }
+        }
+        if (1) {
+            int halfRows = TEST_NUM_ROWS/2;
+            int halfCols = TEST_NUM_COLS/2;
+            for (int i = halfRows-1 ; i < halfRows+1 ; i++) {
+                for (int j = halfCols-1 ; j < halfCols+1 ; j++) {
+                    src->pixels->data.F32[i][j] = 1.0;
+                }
+            }
+            src->pixels->data.F32[halfRows][halfCols] = 5.0;
+        }
+    }
+    return(src);
+}
+
+bool call_pmSourceFitSet() {
+    psArray *modelSet = psArrayAlloc(NUM_MODELS);
+    for (int i = 0 ; i < NUM_MODELS ; i++) {
+        modelSet->data[i] = (psPtr *) pmModelAlloc(i);
+    }
+    pmSource *src = create_pmSource();
+    bool rc = pmSourceFitSet(src, modelSet, PM_SOURCE_FIT_PSF, 1);
+    if (!rc) {
+        diag("ERROR: pmSourceFitSet() returned FALSE");
+        return(false);
+    }
+    psFree(src);
+    for (int i = 0 ; i < NUM_MODELS ; i++) {
+        psFree(modelSet->data[i]);
+        modelSet->data[i] = NULL;
+    }
+    psFree(modelSet);
+    pmModelClassCleanup();
+  
+    return(true);
+}
+
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(70);
+
+
+    // ----------------------------------------------------------------------
+    // pmSourceFitSetDataAlloc() tests
+    // Call pmSourceFitSetDataAlloc() with NULL psArray input parameter
+    {
+        psMemId id = psMemGetId();
+        psArray *modelSet = psArrayAlloc(NUM_MODELS);
+        for (int i = 0 ; i < modelSet->n ; i++) {
+            modelSet->data[i] = (psPtr *) pmModelAlloc(1);
+	}
+        pmSourceFitSetData *fitSetData = pmSourceFitSetDataAlloc(NULL);
+        ok(fitSetData == NULL, "pmSourceFitSetDataAlloc() returned NULL with NULL psArray input parameter");
+        psFree(fitSetData);
+        pmModelClassCleanup();
+        psFree(modelSet); 
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceFitSetDataAlloc() with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        psArray *modelSet = psArrayAlloc(NUM_MODELS);
+        for (int i = 0 ; i < modelSet->n ; i++) {
+            modelSet->data[i] = (psPtr *) pmModelAlloc(i);
+	}
+        pmSourceFitSetData *set = pmSourceFitSetDataAlloc(modelSet);
+        ok(set != NULL && psMemCheckSourceFitSetData(set),
+          "pmSourceFitSetDataAlloc() returned non-NULL with acceptable input parameters");
+        ok(set->paramSet != NULL && set->paramSet->n == modelSet->n, 
+          "pmSourceFitSetDataAlloc() set the set->paramSet psArray correctly");
+        ok(set->derivSet != NULL && set->derivSet->n == modelSet->n, 
+          "pmSourceFitSetDataAlloc() set the set->derivSet psArray correctly");
+        for (int i = 0 ; i < modelSet->n ; i++) {
+            int nParams = pmModelClassParameterCount(i);
+            psVector *tmpV = (psVector *) (set->paramSet->data[i]);
+            ok(tmpV != NULL && psMemCheckVector(tmpV) && tmpV->n == nParams,
+               "pmSourceFitSetDataAlloc() set the set->paramSet->data psVector correctly");
+
+            tmpV = (psVector *) (set->derivSet->data[i]);
+            ok(tmpV != NULL && psMemCheckVector(tmpV) && tmpV->n == nParams,
+               "pmSourceFitSetDataAlloc() set the set->derivSet->data psVector correctly");
+
+            bool errorFlag = false;
+            for (int i = 0 ; i < tmpV->n ; i++) {
+                if (tmpV->data.F32[i] != 0.0) {
+                    errorFlag = true;
+		}
+	    }
+            ok(!errorFlag, "pmSourceFitSetDataAlloc() set the set->derivSet->data correctly");
+	}
+        
+        psFree(set);
+        pmModelClassCleanup();
+        psFree(modelSet); 
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmSourceFitSetCheckLimits() tests
+    // Call pmSourceFitSetCheckLimits() with thisSet == NULL
+    // XXX: Can not test full functionality of pmSourceFitSetCheckLimits() because it
+    // requires that the static variable thisSet be allocated.
+    {
+        psMemId id = psMemGetId();
+        #define NUM_PARAMS 10
+        psF32 *params = (psF32 *) psAlloc(NUM_PARAMS * sizeof(psF32));
+        psF32 *betas = (psF32 *) psAlloc(NUM_PARAMS * sizeof(psF32));
+        bool rc = pmSourceFitSetCheckLimits(PS_MINIMIZE_PARAM_MIN, 10, params, betas);
+        ok(rc == false, "pmSourceFitSetCheckLimits() returned NULL with thisSet == FALSE");
+        psFree(params);
+        psFree(betas);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmSourceFitSetFunction() tests
+    // Call pmSourceFitSetFunction() with thisSet == NULL
+    // XXX: Can not test full functionality of pmSourceFitSetCheckLimits() because it
+    // requires that the static variable thisSet be allocated.
+    {
+        psMemId id = psMemGetId();
+        #define NUM_PARAMS 10
+        psVector *deriv = psVectorAlloc(NUM_PARAMS, PS_TYPE_F32);
+        psVector *param = psVectorAlloc(NUM_PARAMS, PS_TYPE_F32);
+        psVector *x = psVectorAlloc(NUM_PARAMS, PS_TYPE_F32);
+        psF32 tmpF = pmSourceFitSetFunction(deriv, param, x);
+        ok(isnan(tmpF), "pmSourceFitSetFunction() returned NULL with thisSet == FALSE");
+        psFree(deriv);
+        psFree(param);
+        psFree(x);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmSourceFitSetJoin() tests
+    // Call pmSourceFitSetJoin() with NULL pmSourceFitSetData input parameter
+    {
+        psMemId id = psMemGetId();
+        #define NUM_PARAMS 10
+        psVector *deriv = psVectorAlloc(NUM_PARAMS, PS_TYPE_F32);
+        psVector *param = psVectorAlloc(NUM_PARAMS, PS_TYPE_F32);
+        psArray *modelSet = psArrayAlloc(NUM_MODELS);
+        for (int i = 0 ; i < modelSet->n ; i++) {
+            modelSet->data[i] = (psPtr *) pmModelAlloc(i);
+	}
+        pmSourceFitSetData *set = pmSourceFitSetDataAlloc(modelSet);
+        bool rc = pmSourceFitSetJoin(deriv, param, NULL);
+        ok(rc == false, "pmSourceFitSetJoin() returned FALSE with NULL pmSourceFitSetData input parameter");
+        psFree(deriv);
+        psFree(param);
+        psFree(modelSet); 
+        psFree(set);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceFitSetJoin() with unequal size set->paramSet and set->derivSet input parameters
+    {
+        psMemId id = psMemGetId();
+        psVector *deriv = psVectorAlloc(1000, PS_TYPE_F32);
+        psVector *param = psVectorAlloc(1000, PS_TYPE_F32);
+        psArray *modelSet = psArrayAlloc(NUM_MODELS);
+        for (int i = 0 ; i < modelSet->n ; i++) {
+            modelSet->data[i] = (psPtr *) pmModelAlloc(i);
+	}
+        pmSourceFitSetData *set = pmSourceFitSetDataAlloc(modelSet);
+        psFree(set->paramSet);
+        set->paramSet = psArrayAlloc(set->derivSet->n + 1);
+        bool rc = pmSourceFitSetJoin(deriv, param, set);
+        ok(rc == false, "pmSourceFitSetJoin() returned FALSE with unequal size set->paramSet and set->derivSet input parameters");
+        psFree(deriv);
+        psFree(param);
+        psFree(modelSet); 
+        psFree(set);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceFitSetJoin() with deriv and param input psVector too small
+    // XXX: Must add a PS_ASSERT to the source code to detect this
+    if (0) {
+        psMemId id = psMemGetId();
+        psVector *small = psVectorAlloc(1, PS_TYPE_F32);
+        psVector *big = psVectorAlloc(1000, PS_TYPE_F32);
+        psArray *modelSet = psArrayAlloc(NUM_MODELS);
+        for (int i = 0 ; i < modelSet->n ; i++) {
+            modelSet->data[i] = (psPtr *) pmModelAlloc(i);
+	}
+        pmSourceFitSetData *set = pmSourceFitSetDataAlloc(modelSet);
+        bool rc = pmSourceFitSetJoin(small, big, set);
+        ok(rc == false, "pmSourceFitSetJoin() returned FALSE with deriv input psVector too small");
+        rc = pmSourceFitSetJoin(big, small, set);
+        ok(rc == false, "pmSourceFitSetJoin() returned FALSE with param input psVector too small");
+        psFree(small);
+        psFree(big);
+        psFree(modelSet); 
+        psFree(set);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceFitSetJoin() with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        psVector *deriv = psVectorAlloc(1000, PS_TYPE_F32);
+        psVector *param = psVectorAlloc(1000, PS_TYPE_F32);
+        psArray *modelSet = psArrayAlloc(NUM_MODELS);
+        for (int i = 0 ; i < modelSet->n ; i++) {
+            modelSet->data[i] = (psPtr *) pmModelAlloc(i);
+	}
+        pmSourceFitSetData *set = pmSourceFitSetDataAlloc(modelSet);
+
+        psF32 cnt = 0.0;
+        for (int i = 0; i < set->paramSet->n; i++) {
+            psVector *paramOne = set->paramSet->data[i];
+            psVector *derivOne = set->derivSet->data[i];
+            for (int j = 0; j < paramOne->n; j++) {
+                paramOne->data.F32[j] = cnt;
+                derivOne->data.F32[j] = cnt;
+                cnt = cnt + 1.0;
+            }
+        }
+
+        bool rc = pmSourceFitSetJoin(deriv, param, set);
+        ok(rc == true, "pmSourceFitSetJoin() returned TRUE acceptable input parameters");
+
+        bool errorFlag = false;
+        for (int i = 0; i < (int) cnt ; i++) {
+            if (deriv->data.F32[i] != (float) i) {
+                diag("ERROR: deriv->data.F32[%d] is %.2ff, should be %.2f\n", i, deriv->data.F32[i], (float) i);
+                errorFlag = true;
+            }
+            if (param->data.F32[i] != (float) i) {
+                diag("ERROR: param->data.F32[%d] is %.2ff, should be %.2f\n", i, param->data.F32[i], (float) i);
+                errorFlag = true;
+            }
+        }
+        ok(!errorFlag, "pmSourceFitSetJoin() set the deriv and param psVectors correctly");
+
+        psFree(deriv);
+        psFree(param);
+        psFree(modelSet); 
+        psFree(set);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmSourceFitSetSplit() tests
+    // Call pmSourceFitSetSplit() with NULL pmSourceFitSetData input parameter
+    {
+        psMemId id = psMemGetId();
+        #define NUM_PARAMS 10
+        psVector *deriv = psVectorAlloc(NUM_PARAMS, PS_TYPE_F32);
+        psVector *param = psVectorAlloc(NUM_PARAMS, PS_TYPE_F32);
+        psArray *modelSet = psArrayAlloc(NUM_MODELS);
+        for (int i = 0 ; i < modelSet->n ; i++) {
+            modelSet->data[i] = (psPtr *) pmModelAlloc(i);
+	}
+        pmSourceFitSetData *set = pmSourceFitSetDataAlloc(modelSet);
+        bool rc = pmSourceFitSetSplit(NULL, deriv, param);
+        ok(rc == false, "pmSourceFitSetSplit() returned FALSE with NULL pmSourceFitSetData input parameter");
+        psFree(deriv);
+        psFree(param);
+        psFree(modelSet); 
+        psFree(set);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceFitSetSplit() with NULL src->paramSet and src->derivSet input parameters and
+    // src->paramSet and src->derivSet of unequal size
+    {
+        psMemId id = psMemGetId();
+        #define NUM_PARAMS 10
+        psVector *deriv = psVectorAlloc(NUM_PARAMS, PS_TYPE_F32);
+        psVector *param = psVectorAlloc(NUM_PARAMS, PS_TYPE_F32);
+        psArray *modelSet = psArrayAlloc(NUM_MODELS);
+        for (int i = 0 ; i < modelSet->n ; i++) {
+            modelSet->data[i] = (psPtr *) pmModelAlloc(i);
+	}
+        pmSourceFitSetData *set = pmSourceFitSetDataAlloc(modelSet);
+        psArray *tmpArray = set->paramSet;
+        set->paramSet = NULL;
+        bool rc = pmSourceFitSetSplit(set, deriv, param);
+        ok(rc == false, "pmSourceFitSetSplit() returned FALSE with NULL src->paramSet input parameter");
+        set->paramSet = tmpArray;
+
+        tmpArray = set->derivSet;
+        set->derivSet = NULL;
+        rc = pmSourceFitSetSplit(set, deriv, param);
+        ok(rc == false, "pmSourceFitSetSplit() returned FALSE with NULL src->derivSet input parameter");
+        set->derivSet = tmpArray;
+
+        psFree(set->paramSet);
+        set->paramSet = psArrayAlloc(set->derivSet->n + 1);
+        ok(rc == false, "pmSourceFitSetSplit() returned FALSE with src->paramSet and src->derivSet of unequal size");
+
+        psFree(deriv);
+        psFree(param);
+        psFree(modelSet); 
+        psFree(set);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceFitSetSplit() with NULL param input parameter
+    {
+        psMemId id = psMemGetId();
+        psVector *deriv = psVectorAlloc(1000, PS_TYPE_F32);
+        psVector *param = psVectorAlloc(1000, PS_TYPE_F32);
+        for (int i = 0 ; i < 1000 ; i++) {
+            param->data.F32[i] = (float) i;
+            deriv->data.F32[i] = (float) i;
+        }
+        psArray *modelSet = psArrayAlloc(NUM_MODELS);
+        for (int i = 0 ; i < modelSet->n ; i++) {
+            modelSet->data[i] = (psPtr *) pmModelAlloc(i);
+	}
+        pmSourceFitSetData *set = pmSourceFitSetDataAlloc(modelSet);
+        bool rc = pmSourceFitSetSplit(set, deriv, NULL);
+        ok(rc == false, "pmSourceFitSetSplit() returned FALSE with NULL param input parameter");
+        psFree(deriv);
+        psFree(param);
+        psFree(modelSet); 
+        psFree(set);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceFitSetSplit() with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        psVector *deriv = psVectorAlloc(1000, PS_TYPE_F32);
+        psVector *param = psVectorAlloc(1000, PS_TYPE_F32);
+        for (int i = 0 ; i < 1000 ; i++) {
+            deriv->data.F32[i] = (float) i;
+            param->data.F32[i] = (float) i;
+        }
+        psArray *modelSet = psArrayAlloc(NUM_MODELS);
+        for (int i = 0 ; i < modelSet->n ; i++) {
+            modelSet->data[i] = (psPtr *) pmModelAlloc(i);
+	}
+        pmSourceFitSetData *set = pmSourceFitSetDataAlloc(modelSet);
+        bool rc = pmSourceFitSetSplit(set, deriv, param);
+        ok(rc == true, "pmSourceFitSetSplit() returned FALSE with acceptable input parameters");
+
+        bool errorFlag = false;
+        psF32 cnt = 0.0;
+        for (int i = 0; i < set->paramSet->n; i++) {
+            psVector *paramOne = set->paramSet->data[i];
+            psVector *derivOne = set->derivSet->data[i];
+            for (int j = 0; j < paramOne->n; j++) {
+                if (paramOne->data.F32[j] != cnt) {
+                    diag("ERROR: paramOne->data.F32[%d] is %.2ff, should be %.2f\n", i, paramOne->data.F32[i], (float) i);
+                    errorFlag = true;
+                }
+                if (derivOne->data.F32[j] != cnt) {
+                    diag("ERROR: derivOne->data.F32[%d] is %.2ff, should be %.2f\n", i, derivOne->data.F32[i], (float) i);
+                    errorFlag = true;
+                }
+                cnt = cnt + 1.0;
+            }
+        }
+        ok(!errorFlag, "pmSourceFitSetSplit() set the deriv and param psVectors correctly");
+
+        psFree(deriv);
+        psFree(param);
+        psFree(modelSet); 
+        psFree(set);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmSourceFitSetValues() tests
+    // Call pmSourceFitSetValues() with bad input parameters
+    {
+        psMemId id = psMemGetId();
+        #define VEC_SIZE 1000
+        #define NUM_ITER 32
+        #define TOL 0.1
+        psVector *param = psVectorAlloc(VEC_SIZE, PS_TYPE_F32);
+        psVector *dparam = psVectorAlloc(VEC_SIZE, PS_TYPE_F32);
+        psArray *modelSet = psArrayAlloc(NUM_MODELS);
+        for (int i = 0 ; i < modelSet->n ; i++) {
+            modelSet->data[i] = (psPtr *) pmModelAlloc(i);
+	}
+        pmSourceFitSetData *set = pmSourceFitSetDataAlloc(modelSet);
+        pmSource *src = create_pmSource();
+        psMinimization *myMin = psMinimizationAlloc(NUM_ITER, TOL);
+        int nPix = 10;
+        bool fitStatus = true;
+        // NULL set input parameter
+        bool rc = pmSourceFitSetValues(NULL, dparam, param, src, myMin, nPix, fitStatus);
+        ok(rc == false, "pmSourceFitSetValues() returned FALSE with NULL set input parameter");
+
+        // NULL set->paramSet
+        psArray *tmpArray = set->paramSet;
+        set->paramSet = NULL;
+        rc = pmSourceFitSetValues(set, dparam, param, src, myMin, nPix, fitStatus);
+        ok(rc == false, "pmSourceFitSetValues() returned FALSE with NULL set->paramSet");
+        set->paramSet = tmpArray;
+
+        // NULL dparam input parameter
+        rc = pmSourceFitSetValues(set, NULL, param, src, myMin, nPix, fitStatus);
+        ok(rc == false, "pmSourceFitSetValues() returned FALSE with NULL dparam input parameter");
+
+        // NULL param input parameter
+        rc = pmSourceFitSetValues(set, dparam, NULL, src, myMin, nPix, fitStatus);
+        ok(rc == false, "pmSourceFitSetValues() returned FALSE with NULL param input parameter");
+
+        // NULL pmSource input parameter
+        rc = pmSourceFitSetValues(set, dparam, param, NULL, myMin, nPix, fitStatus);
+        ok(rc == false, "pmSourceFitSetValues() returned FALSE with NULL pmSource input parameter");
+
+        // NULL pmSource->pixels input parameter
+        psImage *tmpImg = src->pixels;
+        src->pixels = NULL;
+        rc = pmSourceFitSetValues(set, dparam, param, src, myMin, nPix, fitStatus);
+        ok(rc == false, "pmSourceFitSetValues() returned FALSE with NULL pmSource->pixels input parameter");
+        src->pixels = tmpImg;
+
+        // NULL psMinimization input parameter
+        rc = pmSourceFitSetValues(set, dparam, param, src, NULL, nPix, fitStatus);
+        ok(rc == false, "pmSourceFitSetValues() returned FALSE with NULL psMinimization input parameter");
+
+        psFree(param);
+        psFree(dparam);
+        psFree(modelSet); 
+        psFree(set);
+        psFree(src);
+        psFree(myMin);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmSourceFitSetValues() tests
+    // Call pmSourceFitSetValues() with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        #define VEC_SIZE 1000
+        #define NUM_ITER 32
+        #define TOL 0.1
+        #define MIN_VALUE	22.0
+        #define NUM_PIX 100
+        psVector *param = psVectorAlloc(VEC_SIZE, PS_TYPE_F32);
+        psVector *dparam = psVectorAlloc(VEC_SIZE, PS_TYPE_F32);
+        for (int i = 0 ; i < VEC_SIZE ; i++) {
+            param->data.F32[i] = (float) i;
+            dparam->data.F32[i] = (float) i;
+        }
+        psArray *modelSet = psArrayAlloc(NUM_MODELS);
+        for (int i = 0 ; i < modelSet->n ; i++) {
+            modelSet->data[i] = (psPtr *) pmModelAlloc(i);
+	}
+        pmSourceFitSetData *set = pmSourceFitSetDataAlloc(modelSet);
+        pmSource *src = create_pmSource();
+        psMinimization *myMin = psMinimizationAlloc(NUM_ITER, TOL);
+        int nPix = NUM_PIX;
+        bool fitStatus = true;
+
+        bool rc = pmSourceFitSetValues(set, dparam, param, src, myMin, nPix, fitStatus);
+        ok(rc == true, "pmSourceFitSetValues() returned TRUE with acceptable input paramaters");
+
+        bool errorFlag = false;
+        psF32 cnt = 0.0;
+        for (int i = 0; i < set->paramSet->n; i++) {
+            pmModel *model = set->modelSet->data[i];
+            for (int j = 0; j < model->params->n; j++) {
+                if (model->params->data.F32[j] != cnt) {
+                    diag("ERROR: model->params->data.F32[%d] is %.2ff, should be %.2f\n", i, model->params->data.F32[i], (float) i);
+                    errorFlag = true;
+                }
+                if (model->dparams->data.F32[j] != cnt) {
+                    diag("ERROR: model->dparams->data.F32[%d] is %.2ff, should be %.2f\n", i, model->dparams->data.F32[i], (float) i);
+                    errorFlag = true;
+                }
+                if (model->chisq != myMin->value) {
+                    diag("ERROR: model->chisq is %.2f, should be %.2f", model->chisq, MIN_VALUE);
+                    errorFlag = true;
+                }
+                if (model->nIter != myMin->iter) {
+                    diag("ERROR: model->nIter is %.2f, should be %.2f", model->nIter, NUM_ITER);
+                    errorFlag = true;
+                }
+                if (model->nDOF != NUM_PIX - model->params->n) {
+                    diag("ERROR: model->nDOF is %d, should be %d", model->nDOF, NUM_PIX-model->params->n);
+                    errorFlag = true;
+                }
+
+                cnt = cnt + 1.0;
+            }
+        }
+        ok(!errorFlag, "pmSourceFitSetValues() set the deriv and param psVectors correctly");
+
+        psFree(param);
+        psFree(dparam);
+        psFree(modelSet); 
+        psFree(set);
+        psFree(src);
+        psFree(myMin);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+
+    // ----------------------------------------------------------------------
+    // pmSourceFitSetMasks() tests
+    // Call pmSourceFitSetMasks() with bad input parameters
+    {
+        psMemId id = psMemGetId();
+        #define VEC_SIZE 1000
+        #define NUM_ITER 32
+        #define TOL 0.1
+        psArray *modelSet = psArrayAlloc(NUM_MODELS);
+        for (int i = 0 ; i < modelSet->n ; i++) {
+            modelSet->data[i] = (psPtr *) pmModelAlloc(i);
+	}
+        pmSourceFitSetData *set = pmSourceFitSetDataAlloc(modelSet);
+        psMinConstraint *constraint = psMinConstraintAlloc();
+
+        // NULL psMinConstraint input parameter
+        bool rc = pmSourceFitSetMasks(NULL, set, PM_SOURCE_FIT_NORM);
+        ok(rc == false, "pmSourceFitSetMasks() returned TRUE with NULL psMinConstraint input parameter");
+
+        // NULL pmSourceFitSetData input parameter
+        rc = pmSourceFitSetMasks(constraint, NULL, PM_SOURCE_FIT_NORM);
+        ok(rc == false, "pmSourceFitSetMasks() returned TRUE with NULL pmSourceFitSetData input parameter");
+
+        psFree(modelSet); 
+        psFree(set);
+        psFree(constraint);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceFitSetMasks() with acceptable input parameters
+    // For thoroughness, we should test the PM_SOURCE_FIT_PSF and PM_SOURCE_FIT_EXT mode
+    {
+        psMemId id = psMemGetId();
+        #define VEC_SIZE 1000
+        #define NUM_ITER 32
+        #define TOL 0.1
+        psArray *modelSet = psArrayAlloc(NUM_MODELS);
+        for (int i = 0 ; i < modelSet->n ; i++) {
+            modelSet->data[i] = (psPtr *) pmModelAlloc(i);
+	}
+        pmSourceFitSetData *set = pmSourceFitSetDataAlloc(modelSet);
+        psMinConstraint *constraint = psMinConstraintAlloc();
+        constraint->paramMask = psVectorAlloc(1000, PS_TYPE_F32);
+
+        // Acceptable input parameters
+        bool rc = pmSourceFitSetMasks(constraint, set, PM_SOURCE_FIT_NORM);
+        ok(rc == true, "pmSourceFitSetMasks() returned TRUE with acceptable input parameters");
+
+        bool errorFlag = false;
+        int n = 0;
+        for (int i = 0; i < set->paramSet->n; i++) {
+            psVector *paramOne = set->paramSet->data[i];
+            for (int j = 0; j < paramOne->n; j++) {
+                if (j == PM_PAR_I0) continue;
+                if (constraint->paramMask->data.U8[n + j] != 1) {
+                    diag("ERROR: constraint->paramMask->data.U8[%d] is %d, should be a",
+                          n + j, constraint->paramMask->data.U8[n + j]);
+                    errorFlag = true;
+                }
+            }
+            n += paramOne->n;
+        }
+        ok(!errorFlag, "pmSourceFitSetMasks() constraint->paramMask psVector correctly");
+
+        psFree(modelSet); 
+        psFree(set);
+        psFree(constraint);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmSourceFitSet() tests
+    // Call pmSourceFitSet() with NULL psSource input parameter
+    {
+        psMemId id = psMemGetId();
+        psArray *modelSet = psArrayAlloc(NUM_MODELS);
+        for (int i = 0 ; i < NUM_MODELS ; i++) {
+            modelSet->data[i] = (psPtr *) pmModelAlloc(i);
+	}
+        pmSource *src = create_pmSource();
+        bool rc = pmSourceFitSet(NULL, modelSet, PM_SOURCE_FIT_PSF, 1);
+        ok(rc == false, "pmSourceFitSet() returned FALSE with NULL psSource input parameter");
+        psFree(src);
+        for (int i = 0 ; i < NUM_MODELS ; i++) {
+            psFree(modelSet->data[i]);
+            modelSet->data[i] = NULL;
+        }
+        psFree(modelSet);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceFitSet() with NULL psSource pixels, weight, maskObj input parameters
+    {
+        psMemId id = psMemGetId();
+        psArray *modelSet = psArrayAlloc(NUM_MODELS);
+        for (int i = 0 ; i < NUM_MODELS ; i++) {
+            modelSet->data[i] = (psPtr *) pmModelAlloc(i);
+	}
+        pmSource *src = create_pmSource();
+        psImage *tmpImg = src->pixels;
+        src->pixels = NULL;
+        bool rc = pmSourceFitSet(src, modelSet, PM_SOURCE_FIT_PSF, 1);
+        ok(rc == false, "pmSourceFitSet() returned FALSE with NULL src->pixels input parameter");
+        src->pixels = tmpImg;
+
+        tmpImg = src->weight;
+        src->weight = NULL;
+        rc = pmSourceFitSet(src, modelSet, PM_SOURCE_FIT_PSF, 1);
+        ok(rc == false, "pmSourceFitSet() returned FALSE with NULL src->weight input parameter");
+        src->weight = tmpImg;
+
+        tmpImg = src->maskObj;
+        src->maskObj = NULL;
+        rc = pmSourceFitSet(src, modelSet, PM_SOURCE_FIT_PSF, 1);
+        ok(rc == false, "pmSourceFitSet() returned FALSE with NULL src->maskObj input parameter");
+        src->maskObj = tmpImg;
+        psFree(src);
+        for (int i = 0 ; i < NUM_MODELS ; i++) {
+            psFree(modelSet->data[i]);
+            modelSet->data[i] = NULL;
+        }
+        psFree(modelSet);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceFitSet() with acceptable input parameters
+    // This is a verly limited test.  It only uses a simple object in the pmSource
+    // parameter and simply tests that pmSourceFitSet() returns teh correct type and mode.
+    // Must add more extensive input types.
+    if (1) {
+        psMemId id = psMemGetId();
+        psArray *modelSet = psArrayAlloc(NUM_MODELS);
+        for (int i = 0 ; i < NUM_MODELS ; i++) {
+            modelSet->data[i] = (psPtr *) pmModelAlloc(i);
+	}
+        pmSource *src = create_pmSource();
+        bool rc = pmSourceFitSet(src, modelSet, PM_SOURCE_FIT_PSF, 1);
+        ok(rc == true, "pmSourceFitSet() returned TRUE with acceptable parameters");
+        ok(src->mode & PM_SOURCE_MODE_FITTED, "pmSourceFitSet() set source->mode |= PM_SOURCE_MODE_FITTED (%d)", src->mode);
+        ok(src->type == PM_SOURCE_TYPE_UNKNOWN, "pmSourceFitSet() set source->type correctly (%d)", src->type);
+
+        psFree(src);
+        for (int i = 0 ; i < NUM_MODELS ; i++) {
+            psFree(modelSet->data[i]);
+            modelSet->data[i] = NULL;
+        }
+        psFree(modelSet);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceIO_PS1_DEV_0.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceIO_PS1_DEV_0.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceIO_PS1_DEV_0.c	(revision 22157)
@@ -0,0 +1,243 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS:
+    All functions are tested.
+    XX: These tests read/write a file.  Must choose a more unique name.
+*/
+
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+#define TEST_FLOATS_EQUAL(X, Y) (abs(X - Y) < 0.0001)
+#define NUM_SOURCES		5
+#define FITS_FILENAME  ".tmp00"
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(79);
+
+
+    // ----------------------------------------------------------------------
+    // pmSourcesWrite_PS1_DEV_0() tests
+    // Call pmSourcesWrite_PS1_DEV_0() with NULL psFits input parameter
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen(FITS_FILENAME, "w");
+        psArray *sources = psArrayAlloc(NUM_SOURCES);
+        for (int i = 0 ; i < sources->n ; i++) {
+            pmSource *src = pmSourceAlloc();
+            src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+            src->type = PM_SOURCE_TYPE_STAR;
+            sources->data[i] = (psPtr *) src;
+	}
+        psMetadata *imageHeader = psMetadataAlloc();
+        psMetadata *tableHeader = psMetadataAlloc();
+        psString extname = psStringCopy("ext");
+        bool rc = pmSourcesWrite_PS1_DEV_0(NULL, sources, imageHeader, tableHeader, extname);
+        ok(rc == false, "pmSourcesWrite_PS1_DEV_0() returned FALSE with NULL psFits input parameter");
+        psFree(fitsFile);
+        psFree(sources);
+        psFree(imageHeader);
+        psFree(tableHeader);
+        psFree(extname);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourcesWrite_PS1_DEV_0() with NULL pmSource input parameter
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen(FITS_FILENAME, "w");
+        psArray *sources = psArrayAlloc(NUM_SOURCES);
+        for (int i = 0 ; i < sources->n ; i++) {
+            pmSource *src = pmSourceAlloc();
+            src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+            sources->data[i] = (psPtr *) src;
+	}
+        psMetadata *imageHeader = psMetadataAlloc();
+        psMetadata *tableHeader = psMetadataAlloc();
+        psString extname = psStringCopy("ext");
+        bool rc = pmSourcesWrite_PS1_DEV_0(fitsFile, NULL, imageHeader, tableHeader, extname);
+        ok(rc == false, "pmSourcesWrite_PS1_DEV_0() returned FALSE with NULL pmSource input parameter");
+        psFree(fitsFile);
+        psFree(sources);
+        psFree(imageHeader);
+        psFree(tableHeader);
+        psFree(extname);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourcesWrite_PS1_DEV_0() with NULL extname input parameter
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen(FITS_FILENAME, "w");
+        psArray *sources = psArrayAlloc(NUM_SOURCES);
+        for (int i = 0 ; i < sources->n ; i++) {
+            pmSource *src = pmSourceAlloc();
+            src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+            sources->data[i] = (psPtr *) src;
+	}
+        psMetadata *imageHeader = psMetadataAlloc();
+        psMetadata *tableHeader = psMetadataAlloc();
+        psString extname = psStringCopy("ext");
+        bool rc = pmSourcesWrite_PS1_DEV_0(fitsFile, sources, imageHeader, tableHeader, NULL);
+        ok(rc == false, "pmSourcesWrite_PS1_DEV_0() returned FALSE with NULL extname input parameter");
+        psFree(fitsFile);
+        psFree(sources);
+        psFree(imageHeader);
+        psFree(tableHeader);
+        psFree(extname);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmSourcesRead_PS1_DEV_0() tests
+    // Call pmSourcesRead_PS1_DEV_0() with NULL psFits input parameter
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen(FITS_FILENAME, "r");
+        psMetadata *header = psMetadataAlloc();
+        psArray *array = pmSourcesRead_PS1_DEV_0(NULL, header);
+        ok(array == NULL, "pmSourcesRead_PS1_DEV_0() returned NULL with NULL psFits input parameter");
+        psFree(fitsFile);
+        psFree(header);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourcesRead_PS1_DEV_0() with NULL header input parameter
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen(FITS_FILENAME, "r");
+        psMetadata *header = psMetadataAlloc();
+        psArray *array = pmSourcesRead_PS1_DEV_0(fitsFile, NULL);
+        ok(array == NULL, "pmSourcesRead_PS1_DEV_0() returned NULL with NULL header input parameter");
+        psFree(fitsFile);
+        psFree(header);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // Call pmSourcesWrite_PS1_DEV_0() with acceptable input parameters
+    #define TEST_BASE_X_POS		10.0
+    #define TEST_BASE_Y_POS		20.0
+    #define TEST_BASE_X_ERR		30.0
+    #define TEST_BASE_Y_ERR		40.0
+    #define TEST_BASE_PSF_MAG	50.0
+    #define TEST_BASE_ERR_MAG	60.0
+    #define TEST_BASE_SKY		70.0
+    #define TEST_BASE_SKY_ERR	80.0
+    #define TEST_BASE_PIX_WEIGHT	90.0
+    #define TEST_BASE_PEAK_FLUX	120.0
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen(FITS_FILENAME, "w");
+        psArray *sources = psArrayAlloc(NUM_SOURCES);
+        for (int i = 0 ; i < sources->n ; i++) {
+            pmSource *src = pmSourceAlloc();
+            src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+            // Set even numbered sources to PM_SOURCE_TYPE_STAR
+            pmModel *model = NULL;
+            if (i%2) {
+                src->type = PM_SOURCE_TYPE_STAR;
+                src->modelPSF = pmModelAlloc(1);
+                model = src->modelPSF;
+	    } else {
+                src->type = PM_SOURCE_TYPE_EXTENDED;
+                src->modelConv = pmModelAlloc(1);
+                model = src->modelConv;
+ 	    }
+            for (int p = 0 ; p < model->params->n ; p++) {
+                model->params->data.F32[p] = (float) (i + p);
+                model->dparams->data.F32[p] = (float) (i + p);
+	    }
+            model->params->data.F32[PM_PAR_XPOS] = TEST_BASE_X_POS + (float) i;
+            model->params->data.F32[PM_PAR_YPOS] = TEST_BASE_Y_POS + (float) i;
+            model->dparams->data.F32[PM_PAR_XPOS] = TEST_BASE_X_ERR + (float) i;
+            model->dparams->data.F32[PM_PAR_YPOS] = TEST_BASE_Y_ERR + (float) i;
+            src->psfMag = TEST_BASE_PSF_MAG + (float) i;
+            src->errMag = TEST_BASE_ERR_MAG + (float) i;
+            src->peak->flux = TEST_BASE_PEAK_FLUX + (float) i;
+            src->sky = TEST_BASE_SKY + (float) i;
+            src->skyErr = TEST_BASE_SKY_ERR + (float) i;
+            src->pixWeight = TEST_BASE_PIX_WEIGHT + (float) i;
+            sources->data[i] = (psPtr *) src;
+	}
+        psMetadata *imageHeader = psMetadataAlloc();
+        psMetadata *tableHeader = psMetadataAlloc();
+        psString extname = psStringCopy("ext");
+        bool rc = pmSourcesWrite_PS1_DEV_0(fitsFile, sources, imageHeader, tableHeader, extname);
+        ok(rc == true, "pmSourcesWrite_PS1_DEV_0() returned TRUE with acceptable input parameters");
+        psFree(fitsFile);
+        psFree(sources);
+        psFree(imageHeader);
+        psFree(tableHeader);
+        psFree(extname);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourcesRead_PS1_DEV_0() with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen(FITS_FILENAME, "r");
+        psMetadata *header = psMetadataAlloc();
+        psArray *array = pmSourcesRead_PS1_DEV_0(fitsFile, header);
+        ok(array != NULL, "pmSourcesRead_PS1_DEV_0() returned non-NULL with acceptable input parameters");
+        skip_start(array == NULL, 1, "Skipping tests because pmSourcesRead_PS1_DEV_0() returned NULL");
+        for (int i = 0 ; i < array->n ; i++) {
+             pmSource *src = (pmSource *) array->data[i];
+             ok(src != NULL && psMemCheckSource(src), "pmSourcesRead_PS1_DEV_0() read source %d correctly", i);
+
+             // XXX: Source code always sets the type to PM_SOURCE_TYPE_STAR.  Is that right?
+             ok(src->type == PM_SOURCE_TYPE_STAR, "pmSourcesRead_PS1_DEV_0() set the source type correctly (is %d, should be %d)",
+                src->type, PM_SOURCE_TYPE_STAR);
+
+             ok(src->sky == (TEST_BASE_SKY + (float) i), "pmSourcesRead_PS1_DEV_0() set src->sky correctly (is %.2f, should be %.2f)",
+                src->sky, (TEST_BASE_SKY + (float) i));
+             ok(src->skyErr == (TEST_BASE_SKY_ERR + (float) i), "pmSourcesRead_PS1_DEV_0() set src->skyErr correctly (is %.2f, should be %.2f)",
+                src->skyErr, (TEST_BASE_SKY_ERR + (float) i));
+             ok(src->pixWeight == (TEST_BASE_PIX_WEIGHT + (float) i), "pmSourcesRead_PS1_DEV_0() set src->pixWeight correctly (is %.2f, should be %.2f)",
+                src->pixWeight, (TEST_BASE_PIX_WEIGHT + (float) i));
+             ok(TEST_FLOATS_EQUAL(src->peak->flux, (TEST_BASE_PEAK_FLUX + (float) i)), "pmSourcesRead_PS1_DEV_0() set src->peak->flux correctly (is %.2f, should be %.2f)",
+                src->peak->flux, (TEST_BASE_PEAK_FLUX + (float) i));
+             ok(src->psfMag == (TEST_BASE_PSF_MAG + (float) i), "pmSourcesRead_PS1_DEV_0() set src->psfMag correctly (is %.2f, should be %.2f)",
+                src->psfMag, (TEST_BASE_PSF_MAG + (float) i));
+             ok(src->errMag == (TEST_BASE_ERR_MAG + (float) i), "pmSourcesRead_PS1_DEV_0() set src->errMag correctly (is %.2f, should be %.2f)",
+                src->errMag, (TEST_BASE_ERR_MAG + (float) i));
+
+             // XXX: Source code always sets src->modelPSF.  Is that right?
+             pmModel *model = src->modelPSF;
+             ok(model != NULL  && psMemCheckModel(model), "pmSourcesRead_PS1_DEV_0() set src->modelPSF correctly");
+             skip_start(model == NULL, 2, "Skipping tests because pmSourcesRead_PS1_DEV_0() did not set src->modelPSF");
+             ok(model->params->data.F32[PM_PAR_XPOS] == (TEST_BASE_X_POS + (float) i),
+               "pmSourcesRead_PS1_DEV_0() set src->model->params->data.F32[PM_PAR_XPOS] correctly (is %.2f, should be %.2f)",
+                model->params->data.F32[PM_PAR_XPOS], (TEST_BASE_X_POS + (float) i));
+             ok(model->params->data.F32[PM_PAR_YPOS] == (TEST_BASE_Y_POS + (float) i),
+               "pmSourcesRead_PS1_DEV_0() set src->model->params->data.F32[PM_PAR_YPOS] correctly (is %.2f, should be %.2f)",
+                model->params->data.F32[PM_PAR_YPOS], (TEST_BASE_Y_POS + (float) i));
+             ok(model->dparams->data.F32[PM_PAR_XPOS] == (TEST_BASE_X_ERR + (float) i),
+               "pmSourcesRead_PS1_DEV_0() set src->model->dparams->data.F32[PM_PAR_XPOS] correctly (is %.2f, should be %.2f)",
+                model->dparams->data.F32[PM_PAR_XPOS], (TEST_BASE_X_ERR + (float) i));
+             ok(model->dparams->data.F32[PM_PAR_YPOS] == (TEST_BASE_Y_ERR + (float) i),
+               "pmSourcesRead_PS1_DEV_0() set src->model->dparams->data.F32[PM_PAR_YPOS] correctly (is %.2f, should be %.2f)",
+                model->dparams->data.F32[PM_PAR_YPOS], (TEST_BASE_Y_ERR + (float) i));
+             skip_end();
+	}
+        skip_end();
+        psFree(fitsFile);
+        psFree(header);
+        psFree(array);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceIO_PS1_DEV_1.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceIO_PS1_DEV_1.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceIO_PS1_DEV_1.c	(revision 22157)
@@ -0,0 +1,309 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS:
+    No test for pmSourcesWrite_PS1_DEV_1_XSRC() since there is no associated
+        read function.
+    All other functions are tested.
+    XX: These tests read/write a file.  Must choose a more unique name.
+*/
+
+#define MISC_NUM                32
+#define MISC_NAME              "META00"
+#define NUM_BIAS_DATA           10
+#define TEST_NUM_ROWS           (8)
+#define TEST_NUM_COLS           (16)
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+#define TEST_FLOATS_EQUAL(X, Y) (abs(X - Y) < 0.0001)
+#define NUM_SOURCES		5
+#define TABLE_FILENAME	"table.fits"
+const char* tableFilename = "table.fits";
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(79);
+
+
+    // ----------------------------------------------------------------------
+    // pmSourcesWrite_PS1_DEV_1() tests
+    // Call pmSourcesWrite_PS1_DEV_1() with NULL psFits input parameter
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen(TABLE_FILENAME, "w");
+        psArray *sources = psArrayAlloc(NUM_SOURCES);
+        for (int i = 0 ; i < sources->n ; i++) {
+            pmSource *src = pmSourceAlloc();
+            src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+            src->type = PM_SOURCE_TYPE_STAR;
+            sources->data[i] = (psPtr *) src;
+	}
+        psMetadata *imageHeader = psMetadataAlloc();
+        psMetadata *tableHeader = psMetadataAlloc();
+        psString extname = psStringCopy("ext");
+        bool rc = pmSourcesWrite_PS1_DEV_1(NULL, sources, imageHeader, tableHeader, extname, NULL);
+        ok(rc == false, "pmSourcesWrite_PS1_DEV_1() returned FALSE with NULL psFits input parameter");
+        psFree(fitsFile);
+        psFree(sources);
+        psFree(imageHeader);
+        psFree(tableHeader);
+        psFree(extname);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourcesWrite_PS1_DEV_1() with NULL pmSource input parameter
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen(TABLE_FILENAME, "w");
+        psArray *sources = psArrayAlloc(NUM_SOURCES);
+        for (int i = 0 ; i < sources->n ; i++) {
+            pmSource *src = pmSourceAlloc();
+            src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+            sources->data[i] = (psPtr *) src;
+	}
+        psMetadata *imageHeader = psMetadataAlloc();
+        psMetadata *tableHeader = psMetadataAlloc();
+        psString extname = psStringCopy("ext");
+        bool rc = pmSourcesWrite_PS1_DEV_1(fitsFile, NULL, imageHeader, tableHeader, extname, NULL);
+        ok(rc == false, "pmSourcesWrite_PS1_DEV_1() returned FALSE with NULL pmSource input parameter");
+        psFree(fitsFile);
+        psFree(sources);
+        psFree(imageHeader);
+        psFree(tableHeader);
+        psFree(extname);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourcesWrite_PS1_DEV_1() with NULL extname input parameter
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen(TABLE_FILENAME, "w");
+        psArray *sources = psArrayAlloc(NUM_SOURCES);
+        for (int i = 0 ; i < sources->n ; i++) {
+            pmSource *src = pmSourceAlloc();
+            src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+            sources->data[i] = (psPtr *) src;
+	}
+        psMetadata *imageHeader = psMetadataAlloc();
+        psMetadata *tableHeader = psMetadataAlloc();
+        psString extname = psStringCopy("ext");
+        bool rc = pmSourcesWrite_PS1_DEV_1(fitsFile, sources, imageHeader, tableHeader, NULL, NULL);
+        ok(rc == false, "pmSourcesWrite_PS1_DEV_1() returned FALSE with NULL extname input parameter");
+        psFree(fitsFile);
+        psFree(sources);
+        psFree(imageHeader);
+        psFree(tableHeader);
+        psFree(extname);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmSourcesRead_PS1_DEV_1() tests
+    // Call pmSourcesRead_PS1_DEV_1() with NULL psFits input parameter
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen(TABLE_FILENAME, "r");
+        psMetadata *header = psMetadataAlloc();
+        psArray *array = pmSourcesRead_PS1_DEV_1(NULL, header);
+        ok(array == NULL, "pmSourcesRead_PS1_DEV_1() returned NULL with NULL psFits input parameter");
+        psFree(fitsFile);
+        psFree(header);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourcesRead_PS1_DEV_1() with NULL header input parameter
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen(TABLE_FILENAME, "r");
+        psMetadata *header = psMetadataAlloc();
+        psArray *array = pmSourcesRead_PS1_DEV_1(fitsFile, NULL);
+        ok(array == NULL, "pmSourcesRead_PS1_DEV_1() returned NULL with NULL header input parameter");
+        psFree(fitsFile);
+        psFree(header);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // Call pmSourcesWrite_PS1_DEV_1() with acceptable input parameters
+    #define TEST_BASE_X_POS             10.0
+    #define TEST_BASE_Y_POS             20.0
+    #define TEST_BASE_X_ERR             30.0
+    #define TEST_BASE_Y_ERR             40.0
+    #define TEST_BASE_PSF_MAG		50.0
+    #define TEST_BASE_ERR_MAG		60.0
+    #define TEST_BASE_SKY               500.0
+    #define TEST_BASE_SKY_ERR		80.0
+    #define TEST_BASE_PIX_WEIGHT        90.0
+    #define TEST_BASE_PEAK_FLUX		120.0
+    #define TEST_BASE_PSF_PROB		150.0
+    #define TEST_BASE_CR_N_SIGMA	160.0
+    #define TEST_BASE_EXT_N_SIGMA       170.0   
+    #define TEST_BASE_MODE		1
+
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen(TABLE_FILENAME, "w");
+        if (fitsFile == NULL) {
+            diag("ERROR: Could not create 'table' FITS file");
+            return false;
+        }
+
+        if (1) {
+            // make the PHU an image (per FITS standard, it must be)
+            psImage* image = psImageAlloc(16, 16, PS_TYPE_F32);
+            if (!psFitsWriteImage(fitsFile, NULL, image, 1, NULL)) {
+                diag("ERROR: Could not write PHU image");
+                return false;
+            }
+            psFree(image);
+        }
+
+        psArray *sources = psArrayAlloc(NUM_SOURCES);
+        for (int i = 0 ; i < sources->n ; i++) {
+            pmSource *src = pmSourceAlloc();
+            src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+            // Set even numbered sources to PM_SOURCE_TYPE_STAR
+            pmModel *model = NULL;
+            if (i%2) {
+                src->type = PM_SOURCE_TYPE_STAR;
+                src->modelPSF = pmModelAlloc(1);
+                model = src->modelPSF;
+	    } else {
+                src->type = PM_SOURCE_TYPE_EXTENDED;
+                src->modelConv = pmModelAlloc(1);
+                model = src->modelConv;
+	    }
+            for (int p = 0 ; p < model->params->n ; p++) {
+                model->params->data.F32[p] = (float) (i + p);
+                model->dparams->data.F32[p] = (float) (i + p);
+	    }
+
+            model->params->data.F32[PM_PAR_XPOS] = TEST_BASE_X_POS + (float) i;
+            model->params->data.F32[PM_PAR_YPOS] = TEST_BASE_Y_POS + (float) i;
+            model->dparams->data.F32[PM_PAR_XPOS] = TEST_BASE_X_ERR + (float) i;
+            model->dparams->data.F32[PM_PAR_YPOS] = TEST_BASE_Y_ERR + (float) i;
+            src->psfMag = TEST_BASE_PSF_MAG + (float) i;
+            src->errMag = TEST_BASE_ERR_MAG + (float) i;
+            src->peak->flux = TEST_BASE_PEAK_FLUX + (float) i;
+            src->sky = TEST_BASE_SKY + (float) i;
+            src->skyErr = TEST_BASE_SKY_ERR + (float) i;
+            src->pixWeight = TEST_BASE_PIX_WEIGHT + (float) i;
+            sources->data[i] = (psPtr *) src;
+            src->psfMag = TEST_BASE_PSF_MAG + (float) i;
+            src->errMag = TEST_BASE_ERR_MAG + (float) i;
+            src->sky = TEST_BASE_SKY + (float) i;
+            src->skyErr = TEST_BASE_SKY_ERR + (float) i;
+//            src->psfProb = TEST_BASE_PSF_PROB + (float) i;
+            src->crNsigma = TEST_BASE_CR_N_SIGMA + (float) i;
+            src->extNsigma = TEST_BASE_EXT_N_SIGMA + (float) i;
+            src->mode = TEST_BASE_MODE + i;
+            src->peak->SN = (float) (10 - i);
+            sources->data[i] = (psPtr *) src;
+	}
+        psMetadata *imageHeader = psMetadataAlloc();
+        psMetadata *tableHeader = psMetadataAlloc();
+        psString extname = psStringCopy("ext");
+        bool rc = pmSourcesWrite_PS1_DEV_1(fitsFile, sources, imageHeader, tableHeader, extname, NULL);
+        ok(rc == true, "pmSourcesWrite_PS1_DEV_1() returned TRUE with acceptable input parameters");
+        psFree(fitsFile);
+        psFree(sources);
+        psFree(imageHeader);
+        psFree(tableHeader);
+        psFree(extname);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourcesRead_PS1_DEV_1() with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen(tableFilename, "rw");
+        if (fitsFile == NULL) {
+            diag("ERROR: Could not create 'table' FITS file");
+            return false;
+        }
+
+        // XXX: I'm not exactly sure why, but without this, the psFitsReadTableSize() call
+        // in pmSourcesRead_PS1_DEV_1() fails.  However, Robert also did this in psFits.
+        if (1) {
+            psFitsMoveExtNum(fitsFile, 1, false);
+	}
+
+        // XX: Debugging purposes only.  Trying to duplicate the call to
+        // psFitsTableRead() from DEV_0
+        if (0) {
+            psArray *table = psFitsReadTable(fitsFile);
+            if (table == NULL) {
+                printf("ERROR: table is NULL\n");
+                exit(0);
+	    }
+            for (int i = 0; i < table->n; i++) {
+                psMetadata *row = table->data[i];
+                float sky = psMetadataLookupF32(NULL, row, "SKY");
+                printf("For row %d, the psFitsReadTable() produces a sky of %.2f\n", i, sky);
+	    }
+	}
+
+        psMetadata *header = psMetadataAlloc();
+        psArray *array = pmSourcesRead_PS1_DEV_1(fitsFile, header);
+        ok(array != NULL, "pmSourcesRead_PS1_DEV_1() returned non-NULL with acceptable input parameters");
+        skip_start(array == NULL, 1, "Skipping tests because pmSourcesRead_PS1_DEV_1() returned NULL");
+        for (int i = 0 ; i < array->n ; i++) {
+             pmSource *src = (pmSource *) array->data[i];
+             ok(src != NULL && psMemCheckSource(src), "pmSourcesRead_PS1_DEV_1() read source %d correctly", i);
+
+             // XXX: Source code always sets the type to PM_SOURCE_TYPE_STAR.  Is that right?
+             ok(src->type == PM_SOURCE_TYPE_STAR, "pmSourcesRead_PS1_DEV_1() set the source type correctly (is %d, should be %d)",
+                src->type, PM_SOURCE_TYPE_STAR);
+
+             ok(src->sky == (TEST_BASE_SKY + (float) i), "pmSourcesRead_PS1_DEV_1() set src->sky correctly (is %.2f, should be %.2f)",
+                src->sky, (TEST_BASE_SKY + (float) i));
+             ok(src->skyErr == (TEST_BASE_SKY_ERR + (float) i), "pmSourcesRead_PS1_DEV_1() set src->skyErr correctly (is %.2f, should be %.2f)",
+                src->skyErr, (TEST_BASE_SKY_ERR + (float) i));
+             ok(src->pixWeight == (TEST_BASE_PIX_WEIGHT + (float) i), "pmSourcesRead_PS1_DEV_1() set src->pixWeight correctly (is %.2f, should be %.2f)",
+                src->pixWeight, (TEST_BASE_PIX_WEIGHT + (float) i));
+             ok(TEST_FLOATS_EQUAL(src->peak->flux, (TEST_BASE_PEAK_FLUX + (float) i)), "pmSourcesRead_PS1_DEV_1() set src->peak->flux correctly (is %.2f, should be %.2f)",
+                src->peak->flux, (TEST_BASE_PEAK_FLUX + (float) i));
+             ok(src->psfMag == (TEST_BASE_PSF_MAG + (float) i), "pmSourcesRead_PS1_DEV_1() set src->psfMag correctly (is %.2f, should be %.2f)",
+                src->psfMag, (TEST_BASE_PSF_MAG + (float) i));
+             ok(src->errMag == (TEST_BASE_ERR_MAG + (float) i), "pmSourcesRead_PS1_DEV_1() set src->errMag correctly (is %.2f, should be %.2f)",
+                src->errMag, (TEST_BASE_ERR_MAG + (float) i));
+
+             // XXX: Source code always sets src->modelPSF.  Is that right?
+             pmModel *model = src->modelPSF;
+             ok(model != NULL  && psMemCheckModel(model), "pmSourcesRead_PS1_DEV_1() set src->modelPSF correctly");
+             skip_start(model == NULL, 2, "Skipping tests because pmSourcesRead_PS1_DEV_1() did not set src->modelPSF");
+             ok(model->params->data.F32[PM_PAR_XPOS] == (TEST_BASE_X_POS + (float) i),
+               "pmSourcesRead_PS1_DEV_1() set src->model->params->data.F32[PM_PAR_XPOS] correctly (is %.2f, should be %.2f)",
+                model->params->data.F32[PM_PAR_XPOS], (TEST_BASE_X_POS + (float) i));
+             ok(model->params->data.F32[PM_PAR_YPOS] == (TEST_BASE_Y_POS + (float) i),
+               "pmSourcesRead_PS1_DEV_1() set src->model->params->data.F32[PM_PAR_YPOS] correctly (is %.2f, should be %.2f)",
+                model->params->data.F32[PM_PAR_YPOS], (TEST_BASE_Y_POS + (float) i));
+             ok(model->dparams->data.F32[PM_PAR_XPOS] == (TEST_BASE_X_ERR + (float) i),
+               "pmSourcesRead_PS1_DEV_1() set src->model->dparams->data.F32[PM_PAR_XPOS] correctly (is %.2f, should be %.2f)",
+                model->dparams->data.F32[PM_PAR_XPOS], (TEST_BASE_X_ERR + (float) i));
+             ok(model->dparams->data.F32[PM_PAR_YPOS] == (TEST_BASE_Y_ERR + (float) i),
+               "pmSourcesRead_PS1_DEV_1() set src->model->dparams->data.F32[PM_PAR_YPOS] correctly (is %.2f, should be %.2f)",
+                model->dparams->data.F32[PM_PAR_YPOS], (TEST_BASE_Y_ERR + (float) i));
+             skip_end();
+	}
+        skip_end();
+        psFree(fitsFile);
+        psFree(header);
+        psFree(array);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceIO_SMPDATA.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceIO_SMPDATA.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceIO_SMPDATA.c	(revision 22157)
@@ -0,0 +1,276 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS:
+    All functions are tested.
+*/
+
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+#define TEST_FLOATS_EQUAL(X, Y) (abs(X - Y) < 0.0001)
+#define NUM_SOURCES		5
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(69);
+
+
+    // ----------------------------------------------------------------------
+    // pmSourcesWrite_SMPDATA() tests
+    // Call pmSourcesWrite_SMPDATA() with NULL psFits input parameter
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen(".tmp00", "w");
+        psArray *sources = psArrayAlloc(NUM_SOURCES);
+        for (int i = 0 ; i < sources->n ; i++) {
+            pmSource *src = pmSourceAlloc();
+            src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+            src->type = PM_SOURCE_TYPE_STAR;
+            sources->data[i] = (psPtr *) src;
+	}
+        psMetadata *imageHeader = psMetadataAlloc();
+        psMetadata *tableHeader = psMetadataAlloc();
+        psString extname = psStringCopy("ext");
+        bool rc = pmSourcesWrite_SMPDATA(NULL, sources, imageHeader, tableHeader, extname);
+        ok(rc == false, "pmSourcesWrite_SMPDATA() returned FALSE with NULL psFits input parameter");
+        psFree(fitsFile);
+        psFree(sources);
+        psFree(imageHeader);
+        psFree(tableHeader);
+        psFree(extname);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourcesWrite_SMPDATA() with NULL pmSource input parameter
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen(".tmp00", "w");
+        psArray *sources = psArrayAlloc(NUM_SOURCES);
+        for (int i = 0 ; i < sources->n ; i++) {
+            pmSource *src = pmSourceAlloc();
+            src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+            sources->data[i] = (psPtr *) src;
+	}
+        psMetadata *imageHeader = psMetadataAlloc();
+        psMetadata *tableHeader = psMetadataAlloc();
+        psString extname = psStringCopy("ext");
+        bool rc = pmSourcesWrite_SMPDATA(fitsFile, NULL, imageHeader, tableHeader, extname);
+        ok(rc == false, "pmSourcesWrite_SMPDATA() returned FALSE with NULL pmSource input parameter");
+        psFree(fitsFile);
+        psFree(sources);
+        psFree(imageHeader);
+        psFree(tableHeader);
+        psFree(extname);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourcesWrite_SMPDATA() with NULL extname input parameter
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen(".tmp00", "w");
+        psArray *sources = psArrayAlloc(NUM_SOURCES);
+        for (int i = 0 ; i < sources->n ; i++) {
+            pmSource *src = pmSourceAlloc();
+            src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+            sources->data[i] = (psPtr *) src;
+	}
+        psMetadata *imageHeader = psMetadataAlloc();
+        psMetadata *tableHeader = psMetadataAlloc();
+        psString extname = psStringCopy("ext");
+        bool rc = pmSourcesWrite_SMPDATA(fitsFile, sources, imageHeader, tableHeader, NULL);
+        ok(rc == false, "pmSourcesWrite_SMPDATA() returned FALSE with NULL extname input parameter");
+        psFree(fitsFile);
+        psFree(sources);
+        psFree(imageHeader);
+        psFree(tableHeader);
+        psFree(extname);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmSourcesRead_SMPDATA() tests
+    // Call pmSourcesRead_SMPDATA() with NULL psFits input parameter
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen(".tmp00", "r");
+        psMetadata *header = psMetadataAlloc();
+        psArray *array = pmSourcesRead_SMPDATA(NULL, header);
+        ok(array == NULL, "pmSourcesRead_SMPDATA() returned NULL with NULL psFits input parameter");
+        psFree(fitsFile);
+        psFree(header);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourcesRead_SMPDATA() with NULL header input parameter
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen(".tmp00", "r");
+        psMetadata *header = psMetadataAlloc();
+        psArray *array = pmSourcesRead_SMPDATA(fitsFile, NULL);
+        ok(array == NULL, "pmSourcesRead_SMPDATA() returned NULL with NULL header input parameter");
+        psFree(fitsFile);
+        psFree(header);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // Call pmSourcesWrite_SMPDATA() with acceptable input parameters
+    #define TEST_BASE_X_POS		10.0
+    #define TEST_BASE_Y_POS		20.0
+    #define TEST_BASE_X_ERR		30.0
+    #define TEST_BASE_Y_ERR		40.0
+    #define TEST_BASE_PSF_MAG		50.0
+    #define TEST_BASE_ERR_MAG		60.0
+    #define TEST_BASE_SKY		70.0
+    #define TEST_BASE_SKY_ERR		80.0
+    #define TEST_BASE_PIX_WEIGHT	90.0
+    #define TEST_BASE_PEAK_FLUX		120.0
+    #define TEST_BASE_EXT_MAG		150.0
+    #define TEST_BASE_AP_MAG		160.0
+    // XXX: The following metadata items are not tested: FWHM_X, FWHM_Y, THETA
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen(".tmp00", "w");
+        psArray *sources = psArrayAlloc(NUM_SOURCES);
+        for (int i = 0 ; i < sources->n ; i++) {
+            pmSource *src = pmSourceAlloc();
+            src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+            // Set even numbered sources to PM_SOURCE_TYPE_STAR
+            pmModel *model = NULL;
+            if (i%2) {
+                src->type = PM_SOURCE_TYPE_STAR;
+                src->modelPSF = pmModelAlloc(1);
+                model = src->modelPSF;
+	    } else {
+                src->type = PM_SOURCE_TYPE_EXTENDED;
+                src->modelConv = pmModelAlloc(1);
+                model = src->modelConv;
+ 	    }
+            for (int p = 0 ; p < model->params->n ; p++) {
+                model->params->data.F32[p] = (float) (i + p);
+                model->dparams->data.F32[p] = (float) (i + p);
+	    }
+            model->params->data.F32[PM_PAR_XPOS] = TEST_BASE_X_POS + (float) i;
+            model->params->data.F32[PM_PAR_YPOS] = TEST_BASE_Y_POS + (float) i;
+            model->dparams->data.F32[PM_PAR_XPOS] = TEST_BASE_X_ERR + (float) i;
+            model->dparams->data.F32[PM_PAR_YPOS] = TEST_BASE_Y_ERR + (float) i;
+            src->psfMag = TEST_BASE_PSF_MAG + (float) i;
+            src->errMag = TEST_BASE_ERR_MAG + (float) i;
+            src->peak->flux = TEST_BASE_PEAK_FLUX + (float) i;
+            src->sky = TEST_BASE_SKY + (float) i;
+            src->skyErr = TEST_BASE_SKY_ERR + (float) i;
+            src->pixWeight = TEST_BASE_PIX_WEIGHT + (float) i;
+            src->extMag = TEST_BASE_EXT_MAG + (float) i;
+            src->apMag = TEST_BASE_AP_MAG + (float) i;
+            sources->data[i] = (psPtr *) src;
+	}
+        psMetadata *imageHeader = psMetadataAlloc();
+        psMetadata *tableHeader = psMetadataAlloc();
+        psString extname = psStringCopy("ext");
+        bool rc = pmSourcesWrite_SMPDATA(fitsFile, sources, imageHeader, tableHeader, extname);
+        ok(rc == true, "pmSourcesWrite_SMPDATA() returned TRUE with acceptable input parameters");
+        psFree(fitsFile);
+        psFree(sources);
+        psFree(imageHeader);
+        psFree(tableHeader);
+        psFree(extname);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // Call pmSourcesRead_SMPDATA() with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        psFits* fitsFile = psFitsOpen(".tmp00", "r");
+        float ZERO_POINT = 25.0;
+        psMetadata *header = psMetadataAlloc();
+        psArray *array = pmSourcesRead_SMPDATA(fitsFile, header);
+        ok(array != NULL, "pmSourcesRead_SMPDATA() returned non-NULL with acceptable input parameters");
+        skip_start(array == NULL, 1, "Skipping tests because pmSourcesRead_SMPDATA() returned NULL");
+        for (int i = 0 ; i < array->n ; i++) {
+             pmSource *src = (pmSource *) array->data[i];
+             ok(src != NULL && psMemCheckSource(src), "pmSourcesRead_SMPDATA() read source %d correctly", i);
+
+             // src->model data
+             pmModel *model = src->modelPSF;
+             ok(model != NULL  && psMemCheckModel(model), "pmSourcesRead_SMPDATA() set src->modelPSF correctly");
+             skip_start(model == NULL, 2, "Skipping tests because pmSourcesRead_SMPDATA() did not set src->modelPSF");
+             {
+                 ok(model->params->data.F32[PM_PAR_XPOS] == (TEST_BASE_X_POS + (float) i),
+                   "pmSourcesRead_SMPDATA() set src->model->params->data.F32[PM_PAR_XPOS] correctly (is %.2f, should be %.2f)",
+                    model->params->data.F32[PM_PAR_XPOS], (TEST_BASE_X_POS + (float) i));
+
+                 ok(model->params->data.F32[PM_PAR_YPOS] == (TEST_BASE_Y_POS + (float) i),
+                   "pmSourcesRead_SMPDATA() set src->model->params->data.F32[PM_PAR_YPOS] correctly (is %.2f, should be %.2f)",
+                    model->params->data.F32[PM_PAR_YPOS], (TEST_BASE_Y_POS + (float) i));
+
+                 float tmpSrcSky = TEST_BASE_SKY + (float) i;
+                 float lsky = (tmpSrcSky < 1.0) ? 0.0 : log10(tmpSrcSky);
+                 float tmpF = pow(10.0, lsky);
+                 ok(model->params->data.F32[PM_PAR_SKY] == tmpF,
+                   "pmSourcesRead_SMPDATA() set src->model->params->data.F32[PM_PAR_SKY] correctly (is %.2f, should be %.2f)",
+                    model->params->data.F32[PM_PAR_SKY], tmpF);
+	     }
+             ok(src->psfMag == (TEST_BASE_PSF_MAG + (float) i), "pmSourcesRead_SMPDATA() set src->psfMag correctly (is %.2f, should be %.2f)",
+                src->psfMag, (TEST_BASE_PSF_MAG + (float) i));
+             float tmpF =  0.001 * PS_MIN(999, (1000 * (TEST_BASE_ERR_MAG + (float) i)));
+             ok(src->errMag == tmpF, "pmSourcesRead_SMPDATA() set src->errMag correctly (is %.2f, should be %.2f)",
+                src->errMag, tmpF);
+             tmpF = PS_MIN(99.0, (TEST_BASE_EXT_MAG + ZERO_POINT)) - ZERO_POINT;
+             ok(src->extMag == tmpF, "pmSourcesRead_SMPDATA() set src->extMag correctly (is %.2f, should be %.2f)",
+                src->extMag, tmpF);
+             tmpF = PS_MIN(99.0, (TEST_BASE_AP_MAG + ZERO_POINT)) - ZERO_POINT;
+             ok(src->apMag == tmpF, "pmSourcesRead_SMPDATA() set src->apMag correctly (is %.2f, should be %.2f)",
+                src->apMag, tmpF);
+             if (i%2) {
+                 ok(src->type == PM_SOURCE_TYPE_STAR, "pmSourcesRead_SMPDATA() set the source type correctly (is %d, should be %d)",
+                    src->type, PM_SOURCE_TYPE_STAR);
+	     } else {
+                 ok(src->type == PM_SOURCE_TYPE_EXTENDED, "pmSourcesRead_SMPDATA() set the source type correctly (is %d, should be %d)",
+                    src->type, PM_SOURCE_TYPE_EXTENDED);
+	     }
+             psU8 tmpU8 = (psU8) PS_MIN(255, PS_MAX(0, (255*(TEST_BASE_PIX_WEIGHT + (float) i))));
+             tmpF = (psF32) (tmpU8 / 255.0);
+             ok(src->pixWeight == tmpF, "pmSourcesRead_SMPDATA() set src->pixWeight correctly (is %.2f, should be %.2f)",
+                src->pixWeight, tmpF);
+
+             skip_end();
+
+             if (0) { // OLD
+                 // XXX: Source code always sets the type to PM_SOURCE_TYPE_STAR.  Is that right?
+                 ok(src->sky == (TEST_BASE_SKY + (float) i), "pmSourcesRead_SMPDATA() set src->sky correctly (is %.2f, should be %.2f)",
+                    src->sky, (TEST_BASE_SKY + (float) i));
+                 if (0) {
+                     ok(src->pixWeight == (TEST_BASE_PIX_WEIGHT + (float) i), "pmSourcesRead_SMPDATA() set src->pixWeight correctly (is %.2f, should be %.2f)",
+                        src->pixWeight, (TEST_BASE_PIX_WEIGHT + (float) i));
+		 }
+                 ok(TEST_FLOATS_EQUAL(src->peak->flux, 0.0), "pmSourcesRead_SMPDATA() set src->peak->flux correctly (is %.2f, should be %.2f)",
+                    src->peak->flux, 0.0);
+                 // XXX: Source code always sets src->modelPSF.  Is that right?
+                 ok(model->dparams->data.F32[PM_PAR_XPOS] == (TEST_BASE_X_ERR + (float) i),
+                   "pmSourcesRead_SMPDATA() set src->model->dparams->data.F32[PM_PAR_XPOS] correctly (is %.2f, should be %.2f)",
+                    model->dparams->data.F32[PM_PAR_XPOS], (TEST_BASE_X_ERR + (float) i));
+                 ok(model->dparams->data.F32[PM_PAR_YPOS] == (TEST_BASE_Y_ERR + (float) i),
+                   "pmSourcesRead_SMPDATA() set src->model->dparams->data.F32[PM_PAR_YPOS] correctly (is %.2f, should be %.2f)",
+                    model->dparams->data.F32[PM_PAR_YPOS], (TEST_BASE_Y_ERR + (float) i));
+	     }
+	}
+        skip_end();
+        psFree(fitsFile);
+        psFree(header);
+        psFree(array);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourcePhotometry.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourcePhotometry.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourcePhotometry.c	(revision 22157)
@@ -0,0 +1,167 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "tap.h"
+#include "pstap.h"
+
+bool pmSourcePhotometry_TestOffsets (double radius, double sigma, double fitMag, double apMag, double err1, double err2);
+
+int main (void)
+{
+
+    pmModelGroupInit ();
+
+    plan_tests(240);
+
+    diag("pmSourcePhotometry tests");
+
+    // test consistency of interpolated photometry for a range of apertures (for a fixed PSF sigma)
+    pmSourcePhotometry_TestOffsets (15.0, 2.0, -10.3759, -10.3759, +0.0000, +0.0000);
+    pmSourcePhotometry_TestOffsets (10.0, 2.0, -10.3759, -10.3759, +0.0000, +0.0000);
+    pmSourcePhotometry_TestOffsets ( 8.0, 2.0, -10.3759, -10.3759, +0.0000, +0.0000);
+    pmSourcePhotometry_TestOffsets ( 7.0, 2.0, -10.3759, -10.3759, +0.0000, +0.0001);
+    pmSourcePhotometry_TestOffsets ( 6.0, 2.0, -10.3759, -10.3758, +0.0001, +0.0004);
+    pmSourcePhotometry_TestOffsets ( 5.0, 2.0, -10.3759, -10.3733, +0.0003, +0.0011);
+    pmSourcePhotometry_TestOffsets ( 4.0, 2.0, -10.3759, -10.3520, +0.0006, +0.0018);
+    pmSourcePhotometry_TestOffsets ( 3.0, 2.0, -10.3759, -10.2626, +0.0001, +0.0002);
+    pmSourcePhotometry_TestOffsets ( 2.0, 2.0, -10.3759,  -9.7729, -0.0027, -0.0089);
+    pmSourcePhotometry_TestOffsets ( 1.0, 2.0, -10.3759,  -8.8689, -0.0051, -0.0161);
+
+    // test consistency of interpolated photometry for a range of apertures (for a fixed PSF sigma)
+    pmSourcePhotometry_TestOffsets (15.0, 1.5, -10.3759, -10.3759, +0.0000, +0.0000);
+    pmSourcePhotometry_TestOffsets (10.0, 1.5, -10.3759, -10.3759, +0.0000, +0.0000);
+    pmSourcePhotometry_TestOffsets ( 8.0, 1.5, -10.3759, -10.3759, +0.0000, +0.0000);
+    pmSourcePhotometry_TestOffsets ( 7.0, 1.5, -10.3759, -10.3759, +0.0000, +0.0001);
+    pmSourcePhotometry_TestOffsets ( 6.0, 1.5, -10.3759, -10.3758, +0.0001, +0.0004);
+    pmSourcePhotometry_TestOffsets ( 5.0, 1.5, -10.3759, -10.3733, +0.0003, +0.0011);
+    pmSourcePhotometry_TestOffsets ( 4.0, 1.5, -10.3759, -10.3520, +0.0006, +0.0018);
+    pmSourcePhotometry_TestOffsets ( 3.0, 1.5, -10.3759, -10.2626, +0.0001, +0.0002);
+    pmSourcePhotometry_TestOffsets ( 2.0, 1.5, -10.3759,  -9.7729, -0.0027, -0.0089);
+    pmSourcePhotometry_TestOffsets ( 1.0, 1.5, -10.3759,  -8.8689, -0.0051, -0.0161);
+
+    // test consistency of interpolated photometry for a range of apertures (for a fixed PSF sigma)
+    pmSourcePhotometry_TestOffsets (15.0, 1.0,  -9.4955,  -9.4955, +0.0000, +0.0000);
+    pmSourcePhotometry_TestOffsets (10.0, 1.0,  -9.4955,  -9.4955, +0.0000, +0.0000);
+    pmSourcePhotometry_TestOffsets ( 8.0, 1.0,  -9.4955,  -9.4955, +0.0000, +0.0000);
+    pmSourcePhotometry_TestOffsets ( 7.0, 1.0,  -9.4955,  -9.4955, +0.0000, +0.0000);
+    pmSourcePhotometry_TestOffsets ( 6.0, 1.0,  -9.4955,  -9.4955, +0.0000, +0.0001);
+    pmSourcePhotometry_TestOffsets ( 5.0, 1.0,  -9.4955,  -9.4955, +0.0001, +0.0002);
+    pmSourcePhotometry_TestOffsets ( 4.0, 1.0,  -9.4955,  -9.4968, +0.0006, +0.0022);
+    pmSourcePhotometry_TestOffsets ( 3.0, 1.0,  -9.4955,  -9.4945, +0.0021, +0.0068);
+    pmSourcePhotometry_TestOffsets ( 2.0, 1.0,  -9.4955,  -9.3323, -0.0034, -0.0118);
+    pmSourcePhotometry_TestOffsets ( 1.0, 1.0,  -9.4955,  -8.6844, -0.0141, -0.0440);
+
+    return exit_status();
+}
+
+bool pmSourcePhotometry_TestOffsets (double radius, double sigma, double fitMag, double apMag, double err1, double err2)
+{
+    psMemId id = psMemGetId();
+
+    diag("pmSourcePhotometry test offsets for radius %f", radius);
+
+    // generate a simple readout
+    pmReadout *readout = pmReadoutAlloc (NULL);
+    skip_start(readout == NULL, 0, "Skipping tests because pmReadoutAlloc failed");
+
+    readout->image = psImageAlloc (64, 64, PS_TYPE_F32);
+    readout->mask  = psImageAlloc (64, 64, PS_TYPE_U8);
+
+    // create an empty reference image
+    psImageInit (readout->image, 0.0);
+    psImageInit (readout->mask, 0);
+
+    // generate a simple psf
+    pmPSF *psf = pmPSFBuildSimple ("PS_MODEL_GAUSS", sigma, sigma, 0.0);
+    // psf->growth = pmGrowthCurveAlloc (2.0, 100.0, 15.0);
+    // pmGrowthCurveGenerate (readout, psf, false);
+
+    // create a source
+    pmSource *source = pmSourceAlloc ();
+    source->pixels = psMemIncrRefCounter (readout->image);
+    source->mask   = psMemIncrRefCounter (readout->mask);
+    source->type   = PM_SOURCE_TYPE_STAR;
+    source->mode   = PM_SOURCE_MODE_SUBTRACTED;
+
+    // create template model and measure apMag at fractional offsets
+    pmModel *modelRef = pmModelAlloc(psf->type);
+    modelRef->params->data.F32[PM_PAR_SKY] = 0;
+    modelRef->params->data.F32[PM_PAR_I0] = 1000;
+    modelRef->params->data.F32[PM_PAR_XPOS] = 32.5;
+    modelRef->params->data.F32[PM_PAR_YPOS] = 32.5;
+
+    // create modelPSF from this model
+    source->modelPSF = pmModelFromPSF (modelRef, psf);
+    source->modelPSF->dparams->data.F32[PM_PAR_I0] = 1;
+    source->modelPSF->radiusFit = radius;
+
+    // measure photometry for centered source (fractional pix : 0.5,0.5)
+    // pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_INTERP);
+    pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_INTERP);
+    ok_float_tol(source->psfMag, fitMag, 0.0002, "source fitMag is %f", source->psfMag);
+    ok_float_tol(source->apMag,  apMag, 0.0002, "source apMag is %f", source->apMag);
+    ok_float(source->errMag,   0.001, "source errMag is %f", source->errMag);
+    float refMag = source->apMag;
+
+    // these use an offset of 0.2,0.2
+    // measure photometry for a sub-pixel offset position
+    source->modelPSF->params->data.F32[PM_PAR_XPOS] = 32.3;
+    source->modelPSF->params->data.F32[PM_PAR_YPOS] = 32.3;
+    pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_INTERP);
+    ok_float_tol(refMag - source->apMag,  err1, 0.0002, "offset error is %f", refMag - source->apMag);
+
+    // measure photometry for a sub-pixel offset position
+    source->modelPSF->params->data.F32[PM_PAR_XPOS] = 32.7;
+    source->modelPSF->params->data.F32[PM_PAR_YPOS] = 32.3;
+    pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_INTERP);
+    ok_float_tol(refMag - source->apMag,  err1, 0.0002, "offset error is %f", refMag - source->apMag);
+
+    // measure photometry for a sub-pixel offset position
+    source->modelPSF->params->data.F32[PM_PAR_XPOS] = 32.3;
+    source->modelPSF->params->data.F32[PM_PAR_YPOS] = 32.7;
+    pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_INTERP);
+    ok_float_tol(refMag - source->apMag,  err1, 0.0002, "offset error is %f", refMag - source->apMag);
+
+    // measure photometry for a sub-pixel offset position
+    source->modelPSF->params->data.F32[PM_PAR_XPOS] = 32.7;
+    source->modelPSF->params->data.F32[PM_PAR_YPOS] = 32.7;
+    pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_INTERP);
+    ok_float_tol(refMag - source->apMag,  err1, 0.0002, "offset error is %f", refMag - source->apMag);
+
+    // these use an offset of 0.4,0.4
+    // measure photometry for a sub-pixel offset position
+    source->modelPSF->params->data.F32[PM_PAR_XPOS] = 32.1;
+    source->modelPSF->params->data.F32[PM_PAR_YPOS] = 32.1;
+    pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_INTERP);
+    ok_float_tol(refMag - source->apMag,  err2, 0.0002, "offset error is %f", refMag - source->apMag);
+
+    // measure photometry for a sub-pixel offset position
+    source->modelPSF->params->data.F32[PM_PAR_XPOS] = 32.9;
+    source->modelPSF->params->data.F32[PM_PAR_YPOS] = 32.1;
+    pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_INTERP);
+    ok_float_tol(refMag - source->apMag,  err2, 0.0002, "offset error is %f", refMag - source->apMag);
+
+    // measure photometry for a sub-pixel offset position
+    source->modelPSF->params->data.F32[PM_PAR_XPOS] = 32.1;
+    source->modelPSF->params->data.F32[PM_PAR_YPOS] = 32.9;
+    pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_INTERP);
+    ok_float_tol(refMag - source->apMag,  err2, 0.0002, "offset error is %f", refMag - source->apMag);
+
+    // measure photometry for a sub-pixel offset position
+    source->modelPSF->params->data.F32[PM_PAR_XPOS] = 32.9;
+    source->modelPSF->params->data.F32[PM_PAR_YPOS] = 32.9;
+    pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_INTERP);
+    ok_float_tol(refMag - source->apMag,  err2, 0.0002, "offset error is %f", refMag - source->apMag);
+
+    psFree (source);
+    psFree (modelRef);
+    psFree (psf);
+    psFree (readout);
+
+    skip_end();
+
+    ok(!psMemCheckLeaks (id, NULL, stdout, false), "no memory leaks");
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceSky.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceSky.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceSky.c	(revision 22157)
@@ -0,0 +1,267 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS:
+    All functions are tested.
+        pmSourceLocalSky(): needs more thorough testing with acceptable input params.
+        pmSourceLocalSkyVariance(): needs more thorough testing with acceptable input params.
+*/
+
+#define MISC_NUM                32
+#define MISC_NAME              "META00"
+#define NUM_BIAS_DATA           10
+#define TEST_NUM_ROWS           (8)
+#define TEST_NUM_COLS           (16)
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+#define TEST_FLOATS_EQUAL(X, Y) (abs(X - Y) < 0.0001)
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(26);
+
+
+    // ----------------------------------------------------------------------
+    // pmSourceLocalSky() tests
+    // Call pmSourceLocalSky() with NULL pmSource input parameter
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        src->pixels = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        for (int i =0 ; i < src->pixels->numRows ; i++) {
+            for (int j = 0 ; j < src->pixels->numCols ; j++) {
+                src->pixels->data.F32[i][j] = (float) (i + j);
+	    }
+	}
+        src->maskObj = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+        bool rc = pmSourceLocalSky(NULL, PS_STAT_SAMPLE_MEAN, 10.0, 1, 2);
+        ok(rc == false, "pmSourceLocalSky() returned FALSE with NULL pmSource input parameter");
+        psFree(src);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceLocalSky() with NULL pmSource->pixels input parameter
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        src->maskObj = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+        bool rc = pmSourceLocalSky(src, PS_STAT_SAMPLE_MEAN, 10.0, 1, 2);
+        ok(rc == false, "pmSourceLocalSky() returned FALSE with NULL pmSource->pixels input parameter");
+        psFree(src);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceLocalSky() with NULL pmSource->peak input parameter
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        src->pixels = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        for (int i =0 ; i < src->pixels->numRows ; i++) {
+            for (int j = 0 ; j < src->pixels->numCols ; j++) {
+                src->pixels->data.F32[i][j] = (float) (i + j);
+	    }
+	}
+        src->maskObj = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        bool rc = pmSourceLocalSky(src, PS_STAT_SAMPLE_MEAN, 10.0, 1, 2);
+        ok(rc == false, "pmSourceLocalSky() returned FALSE with NULL pmSource->peak input parameter");
+        psFree(src);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceLocalSky() with NULL pmSource->maskObj input parameter
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        src->pixels = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        for (int i =0 ; i < src->pixels->numRows ; i++) {
+            for (int j = 0 ; j < src->pixels->numCols ; j++) {
+                src->pixels->data.F32[i][j] = (float) (i + j);
+	    }
+	}
+        src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+        bool rc = pmSourceLocalSky(src, PS_STAT_SAMPLE_MEAN, -10.0, 1, 2);
+        ok(rc == false, "pmSourceLocalSky() returned FALSE with NULL pmSource->maskObj input parameter");
+        psFree(src);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceLocalSky() with negative input radius
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        src->pixels = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        for (int i =0 ; i < src->pixels->numRows ; i++) {
+            for (int j = 0 ; j < src->pixels->numCols ; j++) {
+                src->pixels->data.F32[i][j] = (float) (i + j);
+	    }
+	}
+        src->maskObj = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+        bool rc = pmSourceLocalSky(src, PS_STAT_SAMPLE_MEAN, -10.0, 1, 2);
+        ok(rc == false, "pmSourceLocalSky() returned FALSE with negative input radius");
+        psFree(src);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    
+    // Call pmSourceLocalSky() with acceptable input parameters
+    // XX: Future Improvements:
+    //     Test more PS_STATS types
+    //     Test more psRegion values (region bigger than image, 0 region, etc.)
+    //     Test mask values
+    //
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        src->pixels = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        src->maskObj = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        psF32 mean = 0.0;
+        for (int i = 0 ; i < src->pixels->numRows ; i++) {
+            for (int j = 0 ; j < src->pixels->numCols ; j++) {
+                src->pixels->data.F32[i][j] = (float) (i + j);
+                mean+= (float) (i + j);
+                src->maskObj->data.U8[i][j] = 0;
+	    }
+	}
+        src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+        bool rc = pmSourceLocalSky(src, PS_STAT_SAMPLE_MEAN, 10.0, 0, 0);
+        ok(rc == true, "pmSourceLocalSky() returned TRUE with acceptable input parameters");
+        psF32 actualMean =  mean / (int) (TEST_NUM_ROWS * TEST_NUM_COLS);
+        psF32 testMean = src->moments->Sky;
+        ok(TEST_FLOATS_EQUAL(actualMean, testMean), "pmSourceLocalSky() calculated the mean correctly");
+        psFree(src);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmSourceLocalSkyVariance() tests
+    // Call pmSourceLocalSkyVariance() with NULL pmSource input parameter
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        src->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        for (int i =0 ; i < src->weight->numRows ; i++) {
+            for (int j = 0 ; j < src->weight->numCols ; j++) {
+                src->weight->data.F32[i][j] = (float) (i + j);
+	    }
+	}
+        src->maskObj = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+        bool rc = pmSourceLocalSkyVariance(NULL, PS_STAT_SAMPLE_MEAN, 10.0, 1, 2);
+        ok(rc == false, "pmSourceLocalSkyVariance() returned FALSE with NULL pmSource input parameter");
+        psFree(src);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceLocalSkyVariance() with NULL pmSource->weight input parameter
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        src->maskObj = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+        bool rc = pmSourceLocalSkyVariance(src, PS_STAT_SAMPLE_MEAN, 10.0, 1, 2);
+        ok(rc == false, "pmSourceLocalSkyVariance() returned FALSE with NULL pmSource->weight input parameter");
+        psFree(src);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceLocalSkyVariance() with NULL pmSource->maskObj input parameter
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        src->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        for (int i =0 ; i < src->weight->numRows ; i++) {
+            for (int j = 0 ; j < src->weight->numCols ; j++) {
+                src->weight->data.F32[i][j] = (float) (i + j);
+	    }
+	}
+        src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+        bool rc = pmSourceLocalSkyVariance(src, PS_STAT_SAMPLE_MEAN, -10.0, 1, 2);
+        ok(rc == false, "pmSourceLocalSkyVariance() returned FALSE with NULL pmSource->maskObj input parameter");
+        psFree(src);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceLocalSkyVariance() with NULL pmSource->peak input parameter
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        src->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        for (int i =0 ; i < src->weight->numRows ; i++) {
+            for (int j = 0 ; j < src->weight->numCols ; j++) {
+                src->weight->data.F32[i][j] = (float) (i + j);
+	    }
+	}
+        src->maskObj = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        bool rc = pmSourceLocalSkyVariance(src, PS_STAT_SAMPLE_MEAN, 10.0, 1, 2);
+        ok(rc == false, "pmSourceLocalSkyVariance() returned FALSE with NULL pmSource->peak input parameter");
+        psFree(src);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceLocalSkyVariance() with negative input radius
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        src->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        for (int i =0 ; i < src->weight->numRows ; i++) {
+            for (int j = 0 ; j < src->weight->numCols ; j++) {
+                src->weight->data.F32[i][j] = (float) (i + j);
+	    }
+	}
+        src->maskObj = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+        bool rc = pmSourceLocalSkyVariance(src, PS_STAT_SAMPLE_MEAN, -10.0, 1, 2);
+        ok(rc == false, "pmSourceLocalSkyVariance() returned FALSE with negative input radius");
+        psFree(src);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceLocalSkyVariance() with acceptable input parameters
+    // XX: Future Improvements:
+    //     Test more PS_STATS types
+    //     Test more psRegion values (region bigger than image, 0 region, etc.)
+    //     Test mask values
+    //
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        src->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        src->maskObj = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        psF32 mean = 0.0;
+        for (int i = 0 ; i < src->weight->numRows ; i++) {
+            for (int j = 0 ; j < src->weight->numCols ; j++) {
+                src->weight->data.F32[i][j] = (float) (i + j);
+                mean+= (float) (i + j);
+                src->maskObj->data.U8[i][j] = 0;
+	    }
+	}
+        src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+        bool rc = pmSourceLocalSkyVariance(src, PS_STAT_SAMPLE_MEAN, 10.0, 0, 0);
+        ok(rc == true, "pmSourceLocalSkyVariance() returned TRUE with acceptable input parameters");
+        psF32 actualMean =  mean / (int) (TEST_NUM_ROWS * TEST_NUM_COLS);
+        psF32 testMean = src->moments->dSky;
+        ok(TEST_FLOATS_EQUAL(actualMean, testMean), "pmSourceLocalSky() calculated the mean correctly");
+        psFree(src);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceUtils.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceUtils.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmSourceUtils.c	(revision 22157)
@@ -0,0 +1,272 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS
+    All functions are tested.
+       pmSourceFromModel(): Must verify the pmSourceDefinePixels() set the
+           values correctly.
+*/
+
+#define MISC_NUM                32
+#define MISC_NAME              "META00"
+#define NUM_BIAS_DATA           10
+#define TEST_NUM_ROWS           (8)
+#define TEST_NUM_COLS           (16)
+#define VERBOSE                 0
+#define ERR_TRACE_LEVEL         0
+#define TEST_FLOATS_EQUAL(X, Y) (abs((X) - (Y)) < 0.0001)
+#define NUM_SOURCES		100
+
+#define CELL_ALLOC_NAME        "CellName"
+#define NUM_READOUTS            3
+#define NUM_CELLS               10
+#define NUM_HDUS                5
+#define BASE_IMAGE              10
+#define BASE_MASK               40
+#define BASE_WEIGHT             70
+
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmCell *cell)
+{
+    pmReadout *readout = pmReadoutAlloc(cell);
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(tmpImage, (double) i);
+        psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        psFree(tmpImage);
+    }
+    psMetadataAddS32(readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    return(readout);
+}
+
+/******************************************************************************
+generateSimpleCell(): This function generates a pmCell data structure and then
+populates its members with real data.
+ *****************************************************************************/
+pmCell *generateSimpleCell(pmChip *chip)
+{
+    pmCell *cell = pmCellAlloc(chip, CELL_ALLOC_NAME);
+
+    psMetadataAddS32(cell->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psMetadataAddS32(cell->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+    psArrayRealloc(cell->readouts, NUM_READOUTS);
+    cell->hdu = pmHDUAlloc("cellExtName");
+    for (int i = 0 ; i < NUM_READOUTS ; i++) {
+        cell->readouts->data[i] = generateSimpleReadout(cell);
+    }
+
+    // First try to read data from ../dataFiles, then try dataFiles.
+    bool rc = pmConfigFileRead(&cell->hdu->format, "../dataFiles/camera0/format0.config", "Camera format 0");
+    if (!rc) {
+        rc = pmConfigFileRead(&cell->hdu->format, "dataFiles/camera0/format0.config", "Camera format 0");
+        if (!rc) {
+            diag("pmConfigFileRead() was unsuccessful (from generateSimpleCell())");
+	}
+    }
+
+    cell->hdu->images = psArrayAlloc(NUM_HDUS);
+    cell->hdu->masks = psArrayAlloc(NUM_HDUS);
+    cell->hdu->weights = psArrayAlloc(NUM_HDUS);
+    for (int k = 0 ; k < NUM_HDUS ; k++) {
+        cell->hdu->images->data[k]  = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        cell->hdu->masks->data[k]   = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_MASK);
+        cell->hdu->weights->data[k] = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        psImageInit(cell->hdu->images->data[k], (float) (BASE_IMAGE+k));
+        psImageInit(cell->hdu->masks->data[k], (psU8) (BASE_MASK+k));
+        psImageInit(cell->hdu->weights->data[k], (float) (BASE_WEIGHT+k));
+    }
+
+    psRegion *region = psRegionAlloc(0.0, 0.0, 0.0, 0.0);
+    // You shouldn't have to remove the key from the metadata.  Find out how to simply change the key value.
+    psMetadataRemoveKey(cell->concepts, "CELL.TRIMSEC");
+    psMetadataAddPtr(cell->concepts, PS_LIST_TAIL|PS_META_REPLACE, "CELL.TRIMSEC", PS_DATA_REGION, "I am a region", region);
+    psFree(region);
+    return(cell);
+}
+
+void myFreeCell(pmCell *cell)
+{
+    for (int k = 0 ; k < cell->readouts->n ; k++) {
+        psFree(cell->readouts->data[k]);
+    }
+    psFree(cell);
+}
+
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    psTraceSetLevel("psModules.objects", 0);
+    plan_tests(23);
+
+
+    // ----------------------------------------------------------------------
+    // pmSourceModelGuess() tests
+    // Call pmSourceModelGuess() with NULL pmSource input parameter
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+        src->moments = pmMomentsAlloc();
+        pmModelType type = pmModelClassGetType ("PS_MODEL_GAUSS");
+        pmModel *model = pmSourceModelGuess(NULL, type);
+        ok(model == NULL, "pmSourceModelGuess() returned NULL with NULL pmSource input parameter");
+        psFree(src);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceModelGuess() with NULL pmSource->peak input parameter
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        src->moments = pmMomentsAlloc();
+        pmModelType type = pmModelClassGetType ("PS_MODEL_GAUSS");
+        pmModel *model = pmSourceModelGuess(NULL, type);
+        ok(model == NULL, "pmSourceModelGuess() returned NULL with NULL pmSource->peak input parameter");
+        psFree(src);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceModelGuess() with NULL pmSource->moments input parameter
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+        pmModelType type = pmModelClassGetType ("PS_MODEL_GAUSS");
+        pmModel *model = pmSourceModelGuess(NULL, type);
+        ok(model == NULL, "pmSourceModelGuess() returned NULL with NULL pmSource->moments input parameter");
+        psFree(src);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // pmModel *pmSourceModelGuess(pmSource *source, pmModelType modelType)
+    // Call pmSourceModelGuess() with acceptable input parameters
+    // We only test a single model (PS_MODEL_GAUSS), but since this function is mostly
+    // a wrapper to the model functions, that will suffice.
+    {
+        psMemId id = psMemGetId();
+        pmSource *src = pmSourceAlloc();
+        src->peak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+        src->moments = pmMomentsAlloc();
+
+        src->moments->Sx = 1.0;
+        src->moments->y = 2.0;
+        src->moments->Sxy = 3.0;
+        src->moments->Sky = 4.0;
+        src->moments->Peak = 5.0;
+        src->moments->x = 6.0;
+        src->moments->y = 7.0;
+
+        pmModelType type = pmModelClassGetType("PS_MODEL_GAUSS");
+        pmModel *testModel = pmModelAlloc(type);
+        testModel->modelGuess(testModel, src);
+        pmModel *model = pmSourceModelGuess(src, type);
+        ok(model != NULL, "pmSourceModelGuess() returned non-NULL with acceptable input parameters");
+        psF32 *PAR  = model->params->data.F32;
+        psEllipseMoments emoments;
+        emoments.x2 = src->moments->Sx;
+        emoments.y2 = src->moments->Sy;
+        emoments.xy = src->moments->Sxy;
+        // force the axis ratio to be < 20.0
+        psEllipseAxes axes = psEllipseMomentsToAxes (emoments, 20.0);
+        psEllipseShape shape = psEllipseAxesToShape (axes);
+        ok(TEST_FLOATS_EQUAL(PAR[PM_PAR_SKY], src->moments->Sky), "pmSourceModelGuess() returned set model->params[PM_PAR_SKY] correctly");
+        ok(TEST_FLOATS_EQUAL(PAR[PM_PAR_I0], src->moments->Peak - src->moments->Sky), "pmSourceModelGuess() returned set model->params[PM_PAR_IO] correctly");
+        ok(TEST_FLOATS_EQUAL(PAR[PM_PAR_XPOS], src->moments->x), "pmSourceModelGuess() returned set model->params[PM_PAR_XPOS] correctly");
+        ok(TEST_FLOATS_EQUAL(PAR[PM_PAR_YPOS], src->moments->y), "pmSourceModelGuess() returned set model->params[PM_PAR_YPOS] correctly");
+        ok(TEST_FLOATS_EQUAL(PAR[PM_PAR_SXX], PS_MAX(0.5, M_SQRT2*shape.sx)), "pmSourceModelGuess() returned set model->params[PM_PAR_SXX] correctly");
+        ok(TEST_FLOATS_EQUAL(PAR[PM_PAR_SYY], PS_MAX(0.5, M_SQRT2*shape.sy)), "pmSourceModelGuess() returned set model->params[PM_PAR_SYY] correctly");
+        ok(TEST_FLOATS_EQUAL(PAR[PM_PAR_SXY], shape.sxy), "pmSourceModelGuess() returned set model->params[PM_PAR_SXY] correctly");
+        psFree(src);
+        psFree(testModel);
+        psFree(model);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ----------------------------------------------------------------------
+    // pmSourceModelGuess() tests
+    // Call pmSourceFromModel() with NULL pmModel input parameter
+    {
+        psMemId id = psMemGetId();
+        pmModelType type = pmModelClassGetType ("PS_MODEL_GAUSS");
+        pmModel *model = pmModelAlloc(type);
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *readout = cell->readouts->data[0];
+        pmSource *src = pmSourceFromModel(NULL, readout, 10.0, PM_SOURCE_TYPE_STAR);
+        ok(src == NULL, "pmSourceFromModel() returned NULL with NULL pmModel input parameter");
+        psFree(model);
+        psFree(src);
+        myFreeCell(cell);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceFromModel() with NULL pmReadout input parameter
+    {
+        psMemId id = psMemGetId();
+
+        pmModelType type = pmModelClassGetType ("PS_MODEL_GAUSS");
+        pmModel *model = pmModelAlloc(type);
+        pmCell *cell = generateSimpleCell(NULL);
+        pmSource *src = pmSourceFromModel(model, NULL, 10.0, PM_SOURCE_TYPE_STAR);
+        ok(src == NULL, "pmSourceFromModel() returned NULL with NULL pmReadout input parameter");
+        psFree(model);
+        psFree(src);
+        myFreeCell(cell);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmSourceFromModel() with acceptable input parameters
+    // XXX: Must verify the pmSourceDefinePixels() set the values correctly.
+    {
+        psMemId id = psMemGetId();
+
+        pmModel *model = pmModelAlloc(pmModelClassGetType("PS_MODEL_GAUSS"));
+        float Io    = model->params->data.F32[PM_PAR_I0] = 2.0;
+        float xChip = model->params->data.F32[PM_PAR_XPOS] = 3.0;
+        float yChip = model->params->data.F32[PM_PAR_YPOS] = 5.0;
+        pmCell *cell = generateSimpleCell(NULL);
+        pmReadout *readout = cell->readouts->data[0];
+        pmSource *src = pmSourceFromModel(model, readout, 10.0, PM_SOURCE_TYPE_STAR);
+        ok(src != NULL, "pmSourceFromModel() returned non-NULL with acceptable input parameters");
+        ok(src->modelPSF == model, "pmSourceFromModel() set pmSource->modelPSF correctly");
+
+        pmPeak *tmpPeak = pmPeakAlloc (xChip, yChip, Io, PM_PEAK_LONE);
+        ok(src->peak->x == xChip, "pmSourceFromModel() set pmSource->peak->x correctly (%.2f %.2f)", src->peak->x, xChip);
+
+        psFree(model);
+        // XXX: We get psMemory aborts if the following is not done.
+        // There is probably an issue with psMemIncrRefCounter() in pmSourceUtils.c
+
+        src->modelPSF = NULL;
+        src->modelEXT = NULL;
+        psFree(src);
+        psFree(tmpPeak);
+        myFreeCell(cell);
+        pmModelClassCleanup();
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmTrend2D.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmTrend2D.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tap_pmTrend2D.c	(revision 22157)
@@ -0,0 +1,504 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "tap.h"
+#include "pstap.h"
+/* STATUS:
+    All functions are tested.
+        pmTrend2DFit(): Must test the PM_TREND_MAP case.
+*/
+
+#define NUM_ROWS 8
+#define NUM_COLS 16
+#define ERR_TRACE_LEVEL 0
+#define TEST_FLOATS_EQUAL(X, Y) (abs(X - Y) < 0.01)
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    psLogSetLevel(PS_LOG_INFO);
+    psTraceSetLevel("err", ERR_TRACE_LEVEL);
+    plan_tests(91);
+
+    // ------------------------------------------------------------------------
+    // Test pmTrend2DAlloc()
+    // Call pmTrend2DAlloc() with NULL psImage input parameter (PM_TREND_MAP)
+    {
+        psMemId id = psMemGetId();
+        psImage *img = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+        psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+        pmTrend2D *trend = pmTrend2DAlloc(PM_TREND_MAP, NULL, 2, 2, stats);
+        ok(trend == NULL, "pmTrend2DAlloc() returned NULL with NULL psImage input parameter");
+        psFree(img);
+        psFree(stats);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmTrend2DAlloc() with NULL psImage input parameter (PM_TREND_POLY_ORD)
+    {
+        psMemId id = psMemGetId();
+        psImage *img = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+        psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+        pmTrend2D *trend = pmTrend2DAlloc(PM_TREND_POLY_ORD, NULL, 2, 2, stats);
+        ok(trend != NULL, "pmTrend2DAlloc() returned NULL with NULL psImage input parameter");
+        psFree(img);
+        psFree(stats);
+        psFree(trend);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmTrend2DAlloc() with NULL psStats input parameter
+    {
+        psMemId id = psMemGetId();
+        psImage *img = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+        psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+        pmTrend2D *trend = pmTrend2DAlloc(PM_TREND_POLY_ORD, img, 2, 2, NULL);
+        ok(trend == NULL, "pmTrend2DAlloc() returned NULL with NULL psStats input parameter");
+        psFree(img);
+        psFree(stats);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmTrend2DAlloc() with unallowed pmTrend2DMode
+    // XXX: We skip this test because pmTrend2DAlloc() aborts.
+    if (0) {
+        psMemId id = psMemGetId();
+        psImage *img = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+        psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+        pmTrend2D *trend = pmTrend2DAlloc(999, img, 2, 2, stats);
+        ok(trend == NULL, "pmTrend2DAlloc() returned NULL with unallowed pmTrend2DMode");
+        psFree(img);
+        psFree(stats);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmTrend2DAlloc() with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        psImage *img = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+        psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+
+        // Call pmTrend2DAlloc() with PM_TREND_POLY_ORD
+        pmTrend2D *trend = pmTrend2DAlloc(PM_TREND_POLY_ORD, img, 2, 4, stats);
+        ok(trend != NULL && psMemCheckTrend2D(trend), 
+          "pmTrend2DAlloc() returned non-NULL with acceptable input parameters");
+        ok(trend->map == NULL, "pmTrend2DAlloc() set trend->map to NULL");
+        ok(trend->mode == PM_TREND_POLY_ORD, "pmTrend2DAlloc() set trend->mode correctly");
+        ok(trend->stats == stats, "pmTrend2DAlloc() set trend->stats correctly");
+        ok(trend->poly != NULL && trend->poly->type == PS_POLYNOMIAL_ORD, "pmTrend2DAlloc() set trend->poly->type correctly");
+        ok(trend->poly->type == PS_POLYNOMIAL_ORD, "pmTrend2DAlloc() set trend->poly->type correctly");
+        ok(trend->poly->nX == 2, "pmTrend2DAlloc() set trend->poly->nX correctly");
+        ok(trend->poly->nY == 4, "pmTrend2DAlloc() set trend->poly->nY correctly");
+        psFree(trend);
+
+        // Call pmTrend2DAlloc() with PM_TREND_POLY_CHEB
+        trend = pmTrend2DAlloc(PM_TREND_POLY_CHEB, img, 2, 4, stats);
+        ok(trend != NULL && psMemCheckTrend2D(trend), 
+          "pmTrend2DAlloc() returned non-NULL with acceptable input parameters");
+        ok(trend->map == NULL, "pmTrend2DAlloc() set trend->map to NULL");
+        ok(trend->mode == PM_TREND_POLY_CHEB, "pmTrend2DAlloc() set trend->mode correctly");
+        ok(trend->stats == stats, "pmTrend2DAlloc() set trend->stats correctly");
+        ok(trend->poly != NULL && trend->poly->type == PS_POLYNOMIAL_CHEB, "pmTrend2DAlloc() set trend->poly->type correctly");
+        ok(trend->poly->type == PS_POLYNOMIAL_CHEB, "pmTrend2DAlloc() set trend->poly->type correctly");
+        ok(trend->poly->nX == 2, "pmTrend2DAlloc() set trend->poly->nX correctly");
+        ok(trend->poly->nY == 4, "pmTrend2DAlloc() set trend->poly->nY correctly");
+        psFree(trend);
+
+        // Create a new pmTrend with PM_TREND_MAP
+        trend = pmTrend2DAlloc(PM_TREND_MAP, img, 2, 4, stats);
+        ok(trend->map != NULL && psMemCheckImageMap(trend->map), 
+           "pmTrend2DAlloc() set trend->map correctly");
+        psFree(trend);
+
+        psFree(img);
+        psFree(stats);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // Test pmTrend2DNoImageAlloc()
+    // Call pmTrend2DNoImageAlloc() with NULL psImageBinning input parameter
+    {
+        psMemId id = psMemGetId();
+        psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+        psImageBinning *binning = psImageBinningAlloc();
+        pmTrend2D *trend = pmTrend2DNoImageAlloc(PM_TREND_MAP, NULL, stats);
+        ok(trend == NULL, "pmTrend2DNoImageAlloc() returned NULL with NULL psImageBinning input parameter");
+        psFree(stats);
+        psFree(binning);
+        psFree(trend);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmTrend2DNoImageAlloc() with NULL psStats input parameter
+    {
+        psMemId id = psMemGetId();
+        psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+        psImageBinning *binning = psImageBinningAlloc();
+        pmTrend2D *trend = pmTrend2DNoImageAlloc(PM_TREND_MAP, binning, NULL);
+        ok(trend == NULL, "pmTrend2DNoImageAlloc() returned NULL with NULL psStats input parameter");
+        psFree(stats);
+        psFree(binning);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmTrend2DNoImageAlloc() with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+        psImageBinning *binning = psImageBinningAlloc();
+        binning->nXruff = 2;
+        binning->nYruff = 4;
+
+        // Call pmTrend2DNoImageAlloc() with PM_TREND_POLY_ORD
+        pmTrend2D *trend = pmTrend2DNoImageAlloc(PM_TREND_POLY_ORD, binning, stats);
+        ok(trend != NULL && psMemCheckTrend2D(trend), "pmTrend2DNoImageAlloc() returned non-NULL with acceptable input parameters");
+        ok(trend->map == NULL, "pmTrend2DAlloc() set trend->map to NULL");
+        ok(trend->mode == PM_TREND_POLY_ORD, "pmTrend2DAlloc() set trend->mode correctly");
+        ok(trend->stats == stats, "pmTrend2DAlloc() set trend->stats correctly");
+        ok(trend->poly != NULL && trend->poly->type == PS_POLYNOMIAL_ORD, "pmTrend2DAlloc() set trend->poly->type correctly");
+        ok(trend->poly->type == PS_POLYNOMIAL_ORD, "pmTrend2DAlloc() set trend->poly->type correctly");
+        ok(trend->poly->nX == 2, "pmTrend2DAlloc() set trend->poly->nX correctly");
+        ok(trend->poly->nY == 4, "pmTrend2DAlloc() set trend->poly->nY correctly");
+        psFree(trend);
+
+        // Call pmTrend2DNoImageAlloc() with PM_TREND_POLY_CHEB
+        trend = pmTrend2DNoImageAlloc(PM_TREND_POLY_CHEB, binning, stats);
+        ok(trend != NULL && psMemCheckTrend2D(trend), "pmTrend2DNoImageAlloc() returned non-NULL with acceptable input parameters");
+        ok(trend->map == NULL, "pmTrend2DAlloc() set trend->map to NULL");
+        ok(trend->mode == PM_TREND_POLY_CHEB, "pmTrend2DAlloc() set trend->mode correctly");
+        ok(trend->stats == stats, "pmTrend2DAlloc() set trend->stats correctly");
+        ok(trend->poly != NULL && trend->poly->type == PS_POLYNOMIAL_CHEB, "pmTrend2DAlloc() set trend->poly->type correctly");
+        ok(trend->poly->type == PS_POLYNOMIAL_CHEB, "pmTrend2DAlloc() set trend->poly->type correctly");
+        ok(trend->poly->nX == 2, "pmTrend2DAlloc() set trend->poly->nX correctly");
+        ok(trend->poly->nY == 4, "pmTrend2DAlloc() set trend->poly->nY correctly");
+        psFree(trend);
+
+        // Call pmTrend2DNoImageAlloc() with PM_TREND_MAP
+        trend = pmTrend2DNoImageAlloc(PM_TREND_MAP, binning, stats);
+        ok(trend != NULL && psMemCheckTrend2D(trend), "pmTrend2DNoImageAlloc() returned non-NULL with acceptable input parameters");
+        ok(trend->map && psMemCheckImageMap(trend->map), "pmTrend2DNoImageAlloc() set the trend->map correctly");
+        psFree(trend);
+
+        psFree(stats);
+        psFree(binning);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // Test pmTrend2DFieldAlloc()
+    // Call pmTrend2DFieldAlloc() with NULL psStats input parameter
+    {
+        psMemId id = psMemGetId();
+        psImage *img = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+        psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+        pmTrend2D *trend = pmTrend2DFieldAlloc(PM_TREND_MAP, 1, 2, 3, 4, NULL);
+        ok(trend == NULL, "pmTrend2DFieldAlloc() returned NULL with NULL psStats input parameter");
+        psFree(img);
+        psFree(stats);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmTrend2DFieldAlloc() with acceptable input parameters
+    {
+        psMemId id = psMemGetId();
+        psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+        pmTrend2D *trend = pmTrend2DFieldAlloc(PM_TREND_POLY_ORD, 1, 2, 3, 4, stats);
+        ok(trend != NULL && psMemCheckTrend2D(trend), "pmTrend2DFieldAlloc() returned non-NULL with acceptable input parameters");
+        ok(trend->poly != NULL && trend->poly->type == PS_POLYNOMIAL_ORD, "pmTrend2DFieldAlloc() set trend->poly->type correctly");
+        ok(trend->poly->type == PS_POLYNOMIAL_ORD, "pmTrend2DFieldAlloc() set trend->poly->type correctly");
+        ok(trend->poly->nX == 3, "pmTrend2DFieldAlloc() set trend->poly->nX correctly");
+        ok(trend->poly->nY == 4, "pmTrend2DFieldAlloc() set trend->poly->nY correctly");
+        psFree(trend);
+        trend = NULL;
+
+        // Create a new pmTrend with PM_TREND_MAP
+        // XXX: This currently fails due to a big in pmTrend2DFieldAlloc():
+        if (0) {
+            trend = pmTrend2DFieldAlloc(PM_TREND_MAP, 1, 2, 3, 4, stats);
+            ok(trend->map != NULL && psMemCheckImageMap(trend->map), 
+               "pmTrend2DAlloc() set trend->map correctly");
+	}
+
+        psFree(stats);
+        psFree(trend);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // Test pmTrend2DModeToString()
+    // psString pmTrend2DModeToString (pmTrend2DMode mode)
+    // Call pmTrend2DModeToString() with unallowed pmTrend2DMode.
+    // XX: We comment this out because pmTrend2DModeToString() aborts.
+    if (0) {
+        psMemId id = psMemGetId();
+        psString str = pmTrend2DModeToString(99);
+        ok(str == NULL, "pmTrend2DModeToString() returned NULL with unallowed pmTrend2DMode");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmTrend2DModeToString() with unallowed pmTrend2DMode.
+    {
+        psMemId id = psMemGetId();
+        psString str = pmTrend2DModeToString(PM_TREND_NONE);
+        ok(!strcmp(str, "NONE"), "pmTrend2DModeToString(PM_TREND_NONE)");
+        psFree(str);
+
+        str = pmTrend2DModeToString(PM_TREND_POLY_ORD);
+        ok(!strcmp(str, "POLY_ORD"), "pmTrend2DModeToString(PM_TREND_POLY_ORD)");
+        psFree(str);
+
+        str = pmTrend2DModeToString(PM_TREND_POLY_CHEB);
+        ok(!strcmp(str, "POLY_CHEB"), "pmTrend2DModeToString(PM_TREND_POLY_CHEB)");
+        psFree(str);
+
+        str = pmTrend2DModeToString(PM_TREND_MAP);
+        ok(!strcmp(str, "MAP"), "pmTrend2DModeToString(PM_TREND_MAP)");
+        psFree(str);
+
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // Test pmTrend2DModeFromString()
+    // Call pmTrend2DModeFromString() with NULL input parameter
+    {
+        psMemId id = psMemGetId();
+        pmTrend2DMode mode = pmTrend2DModeFromString(NULL);
+        ok(PM_TREND_NONE == mode, "pmTrend2DModeFromString(NULL) returned PM_TREND_NONE");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmTrend2DModeFromString() with unallowed input string
+    {
+        psMemId id = psMemGetId();
+        pmTrend2DMode mode = pmTrend2DModeFromString("BOGUS");
+        ok(PM_TREND_NONE == mode, "pmTrend2DModeFromString(BOGUS) returned PM_TREND_NONE");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmTrend2DModeFromString() with acceptable input string
+    {
+        psMemId id = psMemGetId();
+        pmTrend2DMode mode = pmTrend2DModeFromString("NONE");
+        ok(PM_TREND_NONE == mode, "pmTrend2DModeFromString(NONE) returned PM_TREND_NONE");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmTrend2DModeFromString() with acceptable input string
+    {
+        psMemId id = psMemGetId();
+        pmTrend2DMode mode = pmTrend2DModeFromString("POLY_ORD");
+        ok(PM_TREND_POLY_ORD == mode, "pmTrend2DModeFromString(POLY_ORD) returned PM_TREND_NONE");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmTrend2DModeFromString() with acceptable input string
+    {
+        psMemId id = psMemGetId();
+        pmTrend2DMode mode = pmTrend2DModeFromString("POLY_CHEB");
+        ok(PM_TREND_POLY_CHEB == mode, "pmTrend2DModeFromString(POLY_CHEB) returned PM_TREND_NONE");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Call pmTrend2DModeFromString() with acceptable input string
+    {
+        psMemId id = psMemGetId();
+        pmTrend2DMode mode = pmTrend2DModeFromString("MAP");
+        ok(PM_TREND_MAP == mode, "pmTrend2DModeFromString(MAP) returned PM_TREND_NONE");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // Test pmTrend2DFit()
+    // Call pmTrend2DFit() with bad input parameters
+    {
+        #define VEC_SIZE 9
+        psMemId id = psMemGetId();
+        psImage *img = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+        psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV);
+        pmTrend2D *trend = pmTrend2DAlloc(PM_TREND_POLY_ORD, img, 4, 4, stats);
+        ok(trend != NULL && psMemCheckTrend2D(trend), 
+          "pmTrend2DAlloc() returned non-NULL with acceptable input parameters");
+        psVector *x = psVectorAlloc(VEC_SIZE, PS_TYPE_F32);
+        psVector *y = psVectorAlloc(VEC_SIZE, PS_TYPE_F32);
+        psVector *f = psVectorAlloc(VEC_SIZE, PS_TYPE_F32);
+        psVector *mask = psVectorAlloc(VEC_SIZE, PS_TYPE_U8);
+        psVector *df = psVectorAlloc(VEC_SIZE, PS_TYPE_F32);
+        for (int i = 0 ; i < VEC_SIZE ; i++) {
+            x->data.F32[i] = (float) (i);
+            y->data.F32[i] = (float) (2 * i);
+            f->data.F32[i] = x->data.F32[i] * y->data.F32[i];
+            mask->data.U8[i] = 0;
+            df->data.F32[i] = 0.0;
+        }
+
+        // NULL pmTrend2D input parameter
+        bool rc = pmTrend2DFit(NULL, mask, 0, x, y, f, df);
+        ok(rc == false, "pmTrend2DFit() returned FALSE with NULL pmTrend2D input parameter");
+
+        // NULL mask input parameter
+        rc = pmTrend2DFit(trend, NULL, 0, x, y, f, df);
+        ok(rc == false, "pmTrend2DFit() returned FALSE with NULL mask input parameter");
+
+        // NULL x psVector input parameter
+        rc = pmTrend2DFit(trend, mask, 0, NULL, y, f, df);
+        ok(rc == false, "pmTrend2DFit() returned FALSE with NULL x psVector input parameter");
+
+        // NULL y psVector input parameter
+        rc = pmTrend2DFit(trend, mask, 0, x, NULL, f, df);
+        ok(rc == false, "pmTrend2DFit() returned FALSE with NULL y psVector input parameter");
+
+        // NULL f psVector input parameter
+        rc = pmTrend2DFit(trend, mask, 0, x, y, NULL, df);
+        ok(rc == false, "pmTrend2DFit() returned FALSE with NULL f psVector input parameter");
+
+        // NULL df psVector input parameter
+        rc = pmTrend2DFit(trend, mask, 0, x, y, f, NULL);
+        ok(rc == true, "pmTrend2DFit() returned TRUE with NULL df psVector input parameter");
+
+        psFree(img);
+        psFree(stats);
+        psFree(trend);
+        psFree(x);
+        psFree(y);
+        psFree(f);
+        psFree(mask);
+        psFree(df);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // Test pmTrend2DEval()
+    // Call pmTrend2DEval() with bad input parameters
+    {
+        psMemId id = psMemGetId();
+        psF64 tmpD = pmTrend2DEval(NULL, 0.0, 0.0);
+        ok(TEST_FLOATS_EQUAL(tmpD, 0.0), "pmTrend2DEval() returned 0.0 with NULL pmTrend2D input parameter");
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // ------------------------------------------------------------------------
+    // Test pmTrend2DEvalVector()
+    // psVector *pmTrend2DEvalVector (pmTrend2D *trend, psVector *x, psVector *y)
+    // Call pmTrend2DEvalVector() with bad input parameters
+    {
+        psMemId id = psMemGetId();
+        psImage *img = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+        psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV);
+        pmTrend2D *trend = pmTrend2DAlloc(PM_TREND_POLY_ORD, img, 4, 4, stats);
+        psVector *x = psVectorAlloc(VEC_SIZE, PS_TYPE_F32);
+        psVector *y = psVectorAlloc(VEC_SIZE, PS_TYPE_F32);
+
+        // NULL pmTrend2D input parameter
+        psVector *f = pmTrend2DEvalVector(NULL, x, y);
+        ok(f == NULL, "pmTrend2DEvalVector() returned NULL with NULL pmTrend2D input parameter");
+
+        // NULL x psVector input parameter
+        f = pmTrend2DEvalVector(trend, NULL, y);
+        ok(f == NULL, "pmTrend2DEvalVector() returned NULL with NULL x psVector input parameter");
+
+        // NULL y psVector input parameter
+        f = pmTrend2DEvalVector(trend, x, NULL);
+        ok(f == NULL, "pmTrend2DEvalVector() returned NULL with NULL y psVector input parameter");
+
+        psFree(img);
+        psFree(stats);
+        psFree(trend);
+        psFree(x);
+        psFree(y);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // Test pmTrend2DFit(), pmTrend2DEval(), pmTrend2DEvalVector() with acceptable input parameters
+    // NOTE: We only test with a very simple 2D polynomial fit.  This is appropriate since the
+    // polynomial testing routines are tested extensively elsewhere.
+    // XXX: Must test the PM_TREND_MAP case.
+    {
+        #define VEC_SIZE 9
+        psMemId id = psMemGetId();
+        psImage *img = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+        psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV);
+        pmTrend2D *trend = pmTrend2DAlloc(PM_TREND_POLY_ORD, img, 4, 4, stats);
+        ok(trend != NULL && psMemCheckTrend2D(trend), 
+          "pmTrend2DAlloc() returned non-NULL with acceptable input parameters");
+        psVector *x = psVectorAlloc(VEC_SIZE, PS_TYPE_F32);
+        psVector *y = psVectorAlloc(VEC_SIZE, PS_TYPE_F32);
+        psVector *f = psVectorAlloc(VEC_SIZE, PS_TYPE_F32);
+        psVector *mask = psVectorAlloc(VEC_SIZE, PS_TYPE_U8);
+        psVector *df = psVectorAlloc(VEC_SIZE, PS_TYPE_F32);
+
+        int cnt = 0;
+        for (int i = 0 ; i < 3 ; i++) {
+            for (int j = 0 ; j < 3 ; j++) {
+                x->data.F32[cnt] = (float) i;
+                y->data.F32[cnt] = (float) j;
+                f->data.F32[cnt] = x->data.F32[cnt] * y->data.F32[cnt];
+                mask->data.U8[cnt] = 0;
+                df->data.F32[cnt] = 0.0;
+                cnt++;
+            }
+        }
+
+        bool rc = pmTrend2DFit(trend, mask, 0, x, y, f, NULL);
+        ok(rc == true, "pmTrend2DFit() returned TRUE with acceptable input parameters");
+
+        // Test pmTrend2DFit, pmTrend2DEval()
+        bool errorFlag = false;
+        for (int i = 0 ; i < VEC_SIZE ; i++) {
+            if (!TEST_FLOATS_EQUAL(pmTrend2DEval(trend, x->data.F32[i], y->data.F32[i]), f->data.F32[i])) {
+                diag("ERROR: at (%.2f %.2f), eval is %.2f, should be %.2f\n", x->data.F32[i], y->data.F32[i],
+                      pmTrend2DEval(trend, x->data.F32[i], y->data.F32[i]), f->data.F32[i]);
+                errorFlag = true;
+            }
+        }
+        ok(!errorFlag, "pmTrend2DFit() and pmTrend2DEval() set and evaluated the 2DTrend polynomial correctly");
+
+        // Test pmTrend2DEvalVector()
+        psVector *fTest = pmTrend2DEvalVector(trend, x, y);
+        errorFlag = false;
+        for (int i = 0 ; i < VEC_SIZE ; i++) {
+            if (!TEST_FLOATS_EQUAL(fTest->data.F32[i], f->data.F32[i])) {
+                diag("ERROR: at (%.2f %.2f), eval is %.2f, should be %.2f\n", 
+                      x->data.F32[i], y->data.F32[i], fTest->data.F32[i], f->data.F32[i]);
+                errorFlag = true;
+            }
+        }
+        ok(!errorFlag, "pmTrend2DFit() and pmTrend2DEval() set and evaluated the 2DTrend polynomial correctly");
+
+
+        psFree(img);
+        psFree(stats);
+        psFree(trend);
+        psFree(x);
+        psFree(y);
+        psFree(f);
+        psFree(fTest);
+        psFree(mask);
+        psFree(df);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tst_pmObjects01.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tst_pmObjects01.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/objects/tst_pmObjects01.c	(revision 22157)
@@ -0,0 +1,1300 @@
+/** @file tst_pmObjects.c
+ *
+ *  @brief Contains the tests for pmObjects.c:
+ *
+ * test00: This code will test the pmObjects routines.
+ *
+ *  @author GLG, MHPCC
+ *
+ * XXX: Must test
+ *       pmSourceRoughClass
+ *  many others...
+ *
+ *
+ * XXX: Must test output results for many other functions.
+ *
+ * XXX: There are many cases where row/col can be switched in the code.
+ * We must test that here by using non-square images.  All tests
+ * in this file should be run with non-square images.
+ *
+ * XXX: Memory leaks are not being caught.  If I allocated a psVector in these functions
+ * and never deallocate, no error is generated.
+ *
+ * XXX: Much of this file is commented out due to the API changes in rel 7.
+ *
+ *
+Fully Tested:
+    pmPeakAlloc()
+    pmMomentsAlloc()
+Weakly Tested:
+    pmSourceMoments()
+    most of psObjects.c is not tested
+ *
+ *  @version $Revision: 1.9 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2007-08-24 00:11:59 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+#include "psTest.h"
+#include "pslib.h"
+#include "pmObjects.h"
+#include "pmModelGroup.h"
+
+#define NUM_ROWS 10
+#define NUM_COLS 10
+#define ERROR_TOLERANCE 1.0
+#define ERROR_TOL 0.001
+static int test00(void);
+static int test01(void);
+static int test02(void);
+static int test03(void);
+static int test04(void);
+static int test05(void);
+//static int test06(void);
+//static int test07(void);
+//static int test08(void);
+//static int test09(void);
+//static int test15(void);
+//static int test16(void);
+//static int test20(void);
+testDescription tests[] = {
+                              {test00, 000, "pmObjects: structure allocators and deallocators", true, false},
+                              {test01, 001, "pmObjects: psFindVectorPeaks()", true, false},
+                              {test02, 001, "pmObjects: psFindImagePeaks()", true, false},
+                              {test03, 001, "pmObjects: pmCullPeaks()", true, false},
+                              {test04, 001, "pmObjects: pmSourceLocalSky()", true, false},
+                              {test05, 001, "pmObjects: pmSourceMoments()", true, false},
+                              //                              {test06, 001, "pmObjects: pmSourceSetPixelsCircle()", true, false},
+                              //                              {test07, 001, "pmObjects: pmMin()", true, false},
+                              //                              {test08, 001, "pmObjects: pmSourceModelGuess()", true, false},
+                              //{test09, 001, "pmObjects: pmSourceContour()", true, false},
+                              //{test15, 001, "pmObjects: pmSourceAddModel()", true, false},
+                              //{test16, 001, "pmObjects: pmSourceSubModel()", true, false},
+                              //{test20, 001, "pmObjects: pmSourceSubModel()", true, false},
+                              {NULL}
+                          };
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    //
+    // We include the function names here in psTraceSetLevel() commands for
+    // debugging convenience.  There is no guarantee that this list of functions
+    // is complete.
+    //
+    psTraceSetLevel(".", 0);
+    psTraceSetLevel("pmPeakAlloc", 0);
+    psTraceSetLevel("pmMomentsAlloc", 0);
+    psTraceSetLevel("modelFree", 0);
+    psTraceSetLevel("pmModelAlloc", 0);
+    psTraceSetLevel("sourceFree", 0);
+    psTraceSetLevel("pmSourceAlloc", 0);
+    psTraceSetLevel("pmFindVectorPeaks", 0);
+    psTraceSetLevel("getRowVectorFromImage", 0);
+    psTraceSetLevel("myListAddPeak", 0);
+    psTraceSetLevel("pmFindImagePeaks", 0);
+    psTraceSetLevel("isItInThisRegion", 0);
+    psTraceSetLevel("pmCullPeaks", 0);
+    psTraceSetLevel("pmPeaksSubset", 0);
+    psTraceSetLevel("pmSourceLocalSky", 0);
+    psTraceSetLevel("checkRadius2", 0);
+    psTraceSetLevel("pmSourceMoments", 0);
+    psTraceSetLevel("pmComparePeakAscend", 0);
+    psTraceSetLevel("pmComparePeakDescend", 0);
+    psTraceSetLevel("pmSourcePSFClump", 0);
+    psTraceSetLevel("pmSourceRoughClass", 0);
+    psTraceSetLevel("pmSourceDefinePixels", 0);
+    psTraceSetLevel("pmSourceModelGuess", 0);
+    psTraceSetLevel("pmModelEval", 0);
+    psTraceSetLevel("findValue", 0);
+    psTraceSetLevel("pmSourceContour", 0);
+    psTraceSetLevel("pmSourceFitModel_v5", 0);
+    psTraceSetLevel("pmSourceFitModel", 0);
+    psTraceSetLevel("p_pmSourceAddOrSubModel", 0);
+    psTraceSetLevel("pmSourceAddModel", 0);
+    psTraceSetLevel("pmSourceSubModel", 0);
+
+    return !runTestSuite(stderr, "Test Point Driver", tests, argc, argv);
+}
+
+/******************************************************************************
+test00(): Test the various allocators and deallocators.
+ *****************************************************************************/
+int test00( void )
+{
+    bool testStatus = true;
+    psTraceSetLevel(".", 0);
+
+    printf("Testing pmPeakAlloc()...\n");
+    pmPeak *tmpPeak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+    if (tmpPeak == NULL) {
+        printf("TEST ERROR: pmPeakAlloc() returned a NULL pmPeak\n");
+        testStatus = false;
+    } else {
+        if (tmpPeak->x != 1) {
+            printf("TEST ERROR: pmPeakAlloc() improperly set pmPeak->x\n");
+            testStatus = false;
+        }
+        if (tmpPeak->y != 2) {
+            printf("TEST ERROR: pmPeakAlloc() improperly set pmPeak->y\n");
+            testStatus = false;
+        }
+        if (tmpPeak->counts != 3.0) {
+            printf("TEST ERROR: pmPeakAlloc() improperly set pmPeak->counts\n");
+            testStatus = false;
+        }
+        if (tmpPeak->class != PM_PEAK_LONE) {
+            printf("TEST ERROR: pmPeakAlloc() improperly set pmPeak->class\n");
+            testStatus = false;
+        }
+    }
+    psFree(tmpPeak);
+
+    printf("Testing pmMomentsAlloc()...\n");
+    pmMoments *tmpMoments = pmMomentsAlloc();
+    if (tmpMoments == NULL) {
+        printf("TEST ERROR: pmMomentsAlloc() returned a NULL pmMoments\n");
+        testStatus = false;
+    } else {
+        if ((fabs(tmpMoments->x-0.0) > ERROR_TOL) ||
+                (fabs(tmpMoments->y-0.0) > ERROR_TOL) ||
+                (fabs(tmpMoments->Sx-0.0) > ERROR_TOL) ||
+                (fabs(tmpMoments->Sy-0.0) > ERROR_TOL) ||
+                (fabs(tmpMoments->Sxy-0.0) > ERROR_TOL) ||
+                (fabs(tmpMoments->Sum-0.0) > ERROR_TOL) ||
+                (fabs(tmpMoments->Peak-0.0) > ERROR_TOL) ||
+                (fabs(tmpMoments->Sky-0.0) > ERROR_TOL) ||
+                (tmpMoments->nPixels != 0)) {
+            printf("TEST ERROR: pmMomentsAlloc() did not properly initialize the pmMoments structure.\n");
+            printf("    tmpMoments->x is %f\n", tmpMoments->x);
+            printf("    tmpMoments->y is %f\n", tmpMoments->y);
+            printf("    tmpMoments->Sx is %f\n", tmpMoments->Sx);
+            printf("    tmpMoments->Sy is %f\n", tmpMoments->Sy);
+            printf("    tmpMoments->Sxy is %f\n", tmpMoments->Sxy);
+            printf("    tmpMoments->Sum is %f\n", tmpMoments->Sum);
+            printf("    tmpMoments->Peak is %f\n", tmpMoments->Peak);
+            printf("    tmpMoments->Sky is %f\n", tmpMoments->Sky);
+            printf("    tmp    Moments->nPixels is %d\n", tmpMoments->nPixels);
+            testStatus = false;
+        }
+    }
+    psFree(tmpMoments);
+
+
+    //
+    // Loop through each type of model
+    //
+    psS32 i = 0;
+    while (0 != pmModelClassParameterCount(i)) {
+        printf("Testing pmModelAlloc(%s)...\n", pmModelClassGetName(0));
+        pmModel *tmpModel = pmModelAlloc(i);
+        if (tmpModel == NULL) {
+            printf("TEST ERROR: pmModelAlloc(%s) returned a NULL pmModel\n", pmModelClassGetName(0));
+            testStatus = false;
+        } else {
+
+            /* XXX: Should we test that the members were set correctly?
+                        if ((tmpModel->params->n != 7) || (tmpModel->dparams->n != 7)) {
+                            printf("TEST ERROR: pmModelAlloc(PS_MODEL_GAUSS) allocated an incorrect number of params (%ld, %ld)\n",
+                                   tmpModel->params->n, tmpModel->dparams->n);
+                            testStatus = false;
+                        } else {
+                            for (psS32 i = 0 ; i < 7 ; i++) {
+                                if ((tmpModel->params->data.F32[i] != 0.0) ||
+                                        (tmpModel->dparams->data.F32[i] != 0.0)) {
+                                    printf("TEST ERROR: pmModelAlloc(PS_MODEL_GAUSS) did not ininitialize the params/dparams array to 0.0.\n");
+                                    testStatus = false;
+                                }
+                            }
+                        }
+            */
+        }
+        psFree(tmpModel);
+        i++;
+    }
+
+
+    pmSource *tmpSource = pmSourceAlloc();
+    if (tmpSource == NULL) {
+        printf("TEST ERROR: pmSourceAlloc() returned a NULL pmSource\n");
+        testStatus = false;
+    }
+    psFree(tmpSource);
+
+    return(testStatus);
+}
+
+/******************************************************************************
+test01(): we first test pmFindVectorPeaks() with a variety of bad input
+parameters.  Then we test it with a simple vector both 1- and multi-elements.
+ *****************************************************************************/
+#define TST01_VECTOR_LENGTH 10
+bool test_pmFindVectorPeaks(int n)
+{
+    bool testStatus = true;
+    psVector *inData = psVectorAlloc(n, PS_TYPE_F32);
+    inData->n = inData->nalloc;
+    psVector *outData = NULL;
+
+    printf("-------------- Calling test_pmFindVectorPeaks on an %d size vector. --------------\n", n);
+    //
+    // Test first pixel peak.
+    //
+    printf("Test pmFindVectorPeaks() with a first-element peak.\n");
+    for (psS32 i = 0 ; i < n ; i++) {
+        inData->data.F32[i] = (float) (n-i);
+    }
+    inData->data.F32[0] = (float) n;
+
+    outData= pmFindVectorPeaks(inData, 0.0);
+    if (outData == NULL) {
+        printf("TEST ERROR: pmFindVectorPeaks returned a NULL psVector.\n");
+        testStatus = false;
+    } else {
+        if (outData->n != 1) {
+            printf("TEST ERROR: outData->n is %ld\n", outData->n);
+            testStatus = false;
+        }
+        if (outData->data.U32[0] != 0) {
+            printf("TEST ERROR: Did not find peak at element 0.\n");
+            testStatus = false;
+        }
+        psFree(outData);
+    }
+
+    //
+    // Test first pixel peak, large threshold
+    //
+    printf("Test pmFindVectorPeaks() with a first-element peak, large threshold.\n");
+    for (psS32 i = 0 ; i < n ; i++) {
+        inData->data.F32[i] = (float) (n-i);
+    }
+    inData->data.F32[0] = (float) n;
+
+    outData= pmFindVectorPeaks(inData, (float) (n*n));
+    if (outData == NULL) {
+        printf("TEST ERROR: pmFindVectorPeaks returned a NULL psVector.\n");
+        testStatus = false;
+    } else {
+
+        if (outData->n != 0) {
+            printf("TEST ERROR: outData->n is %ld\n", outData->n);
+            testStatus = false;
+        }
+        psFree(outData);
+
+        // Skip remaining tests if the input vector has length 1.
+        if (n == 1) {
+            psFree(inData);
+            return(testStatus);
+        }
+    }
+
+    //
+    // Test last pixel peak.
+    //
+    printf("Test pmFindVectorPeaks() with a last-element peak.\n");
+    for (psS32 i = 0 ; i < n ; i++) {
+        inData->data.F32[i] = (float) (i);
+    }
+    inData->data.F32[n-1] = (float) n;
+
+    outData= pmFindVectorPeaks(inData, 0.0);
+    if (outData == NULL) {
+        printf("TEST ERROR: pmFindVectorPeaks returned a NULL psVector.\n");
+        testStatus = false;
+    } else {
+
+        if (outData->n != 1) {
+            printf("TEST ERROR: outData->n is %ld\n", outData->n);
+            testStatus = false;
+        }
+        if (outData->data.U32[0] != n-1) {
+            printf("TEST ERROR: Did not find peak at element %d.\n", n-1);
+            testStatus = false;
+        }
+        psFree(outData);
+    }
+
+    //
+    // Test last pixel peak, large threshold.
+    //
+    printf("Test pmFindVectorPeaks() with a last-element peak, large threshold.\n");
+    for (psS32 i = 0 ; i < n ; i++) {
+        inData->data.F32[i] = (float) (i);
+    }
+    inData->data.F32[n-1] = (float) n;
+
+    outData= pmFindVectorPeaks(inData, (float) (n*n));
+    if (outData == NULL) {
+        printf("TEST ERROR: pmFindVectorPeaks returned a NULL psVector.\n");
+        testStatus = false;
+    } else {
+
+        if (outData->n != 0) {
+            printf("TEST ERROR: outData->n is %ld\n", outData->n);
+            testStatus = false;
+        }
+        psFree(outData);
+    }
+
+    //
+    // Test interior peaks.
+    // Set all even number elements to be peaks.
+    //
+    printf("Test pmFindVectorPeaks() with all even-numbered elements peak.\n");
+    for (psS32 i = 0 ; i < n ; i++) {
+        if (0 == i%2) {
+            inData->data.F32[i] = (float) (2 * i);
+        } else {
+            inData->data.F32[i] = (float) (i);
+        }
+    }
+    inData->data.F32[0] = (float) n;
+
+
+    outData= pmFindVectorPeaks(inData, 0.0);
+    if (outData == NULL) {
+        printf("TEST ERROR: pmFindVectorPeaks returned a NULL psVector.\n");
+        testStatus = false;
+    } else {
+
+        if (outData->n != n/2) {
+            printf("TEST ERROR: outData->n is %ld\n", outData->n);
+            testStatus = false;
+        }
+
+        for (psS32 i = 0 ; i < outData->n ; i++) {
+            if (outData->data.U32[i] != (2 * i)) {
+                printf("TEST ERROR: the %d-th peak is element number %d\n", i, outData->data.U32[i]);
+                testStatus = false;
+            }
+        }
+        psFree(outData);
+    }
+
+    //
+    // Test interior peaks, with threshold = n*n.
+    // Should generate an empty output psVector.
+    //
+    printf("Test pmFindVectorPeaks() with all even-numbered elements peak, large threshold.\n");
+    outData= pmFindVectorPeaks(inData, (float) (n*n));
+    if (outData == NULL) {
+        printf("TEST ERROR: pmFindVectorPeaks returned a NULL psVector.\n");
+        testStatus = false;
+    } else {
+
+        if (outData->n != 0) {
+            printf("TEST ERROR: outData->n is %ld\n", outData->n);
+            testStatus = false;
+        }
+        psFree(outData);
+    }
+
+    psFree(inData);
+    return(testStatus);
+}
+
+int test01( void )
+{
+    bool testStatus = true;
+    psVector *tmpVec = NULL;
+    psVector *tmpVecF64 = psVectorAlloc(TST01_VECTOR_LENGTH, PS_TYPE_F64);
+    psVector *tmpVecEmpty = psVectorAlloc(0, PS_TYPE_F32);
+    tmpVecF64->n = tmpVecF64->nalloc;
+    tmpVecEmpty->n = tmpVecEmpty->nalloc;
+
+    psTraceSetLevel(".", 0);
+
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmFindVectorPeaks with NULL psVector.  Should generate error and return NULL.\n");
+    tmpVec = pmFindVectorPeaks(NULL, 0.0);
+    if (tmpVec != NULL) {
+        printf("TEST ERROR: pmFindVectorPeaks() returned a non-NULL psVector.\n");
+        testStatus = false;
+        psFree(tmpVec);
+    }
+
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmFindVectorPeaks with empty psVector.  Should generate error and return NULL.\n");
+    tmpVec = pmFindVectorPeaks(tmpVecEmpty, 0.0);
+    if (tmpVec != NULL) {
+        printf("TEST ERROR: pmFindVectorPeaks() returned a non-NULL psVector.\n");
+        testStatus = false;
+        psFree(tmpVec);
+    }
+
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmFindVectorPeaks with PS_TYPE_F64 psVector.  Should generate error and return NULL.\n");
+    tmpVec = pmFindVectorPeaks(tmpVecF64, 0.0);
+    if (tmpVec != NULL) {
+        printf("TEST ERROR: pmFindVectorPeaks() returned a non-NULL psVector.\n");
+        testStatus = false;
+        psFree(tmpVec);
+    }
+    testStatus&= test_pmFindVectorPeaks(1);
+    testStatus&= test_pmFindVectorPeaks(TST01_VECTOR_LENGTH);
+
+    psFree(tmpVecF64);
+    psFree(tmpVecEmpty);
+    return(testStatus);
+}
+
+/******************************************************************************
+test02():
+// XXX: Must test flat peaks.
+// XXX: test 1-by-n and n-by-1 images.
+ *****************************************************************************/
+#define TST02_NUM_ROWS 5
+#define TST02_NUM_COLS 5
+bool test_pmFindImagePeaks(int numRows, int numCols)
+{
+    printf("-------------- Calling test_pmFindImagePeaks on an %d-by-%d image. --------------\n", numRows, numCols);
+    //    if ((numRows < 4) || (numCols < 4)) {
+    //        printf("WARNING: Don't call this test with a smaller than 4-by-4 image.\n");
+    //        return(true);
+    //    }
+    bool testStatus = true;
+    psImage *inData = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    psArray *outData = NULL;
+
+    //
+    // Initialize test image.
+    //
+    for (psS32 i = 0 ; i < numRows ; i++) {
+        for (psS32 j = 0 ; j < numCols ; j++) {
+            inData->data.F32[i][j] = PS_SQR(i - numRows/2) + PS_SQR(j-numCols/2);
+        }
+    }
+    //
+    // Set corner and center pixels as peaks.
+    //
+    inData->data.F32[0][0] = PS_SQR(numRows) + PS_SQR(numCols);
+    inData->data.F32[0][numCols-1] = PS_SQR(numRows) + PS_SQR(numCols);
+    inData->data.F32[numRows-1][0] = PS_SQR(numRows) + PS_SQR(numCols);
+    inData->data.F32[numRows-1][numCols-1] = PS_SQR(numRows) + PS_SQR(numCols);
+    inData->data.F32[numRows/2][numCols/2] = PS_SQR(numRows) + PS_SQR(numCols);
+
+    //
+    // Print image.
+    //
+    for (psS32 i = 0 ; i < numRows ; i++) {
+        for (psS32 j = 0 ; j < numCols ; j++) {
+            printf("(%.1f) ", inData->data.F32[i][j]);
+        }
+        printf("\n");
+    }
+
+    //
+    // Call pmFindImagePeaks() with a threshold of 0.0.
+    //
+    outData = pmFindImagePeaks(inData, 0.0);
+
+    if (outData == NULL) {
+        printf("TEST ERROR: pmFindImagePeaks returned a NULL psList.\n");
+        testStatus = false;
+    } else {
+        psS32 expectedNumPeaks;
+        if ((numRows == 1) && (numCols == 1)) {
+            expectedNumPeaks = 1;
+        } else if ((numRows == 1) || (numCols == 1)) {
+            expectedNumPeaks = 3;
+        } else {
+            expectedNumPeaks = 5;
+        }
+        if (outData->n != expectedNumPeaks) {
+            printf("TEST ERROR: pmFindImagePeaks found %ld peaks (should be %d)\n", outData->n, expectedNumPeaks);
+            testStatus = false;
+        }
+
+        // HEY: verify
+        for (psS32 i = 0 ; i < outData->n ; i++) {
+            pmPeak *tmpPeak = (pmPeak *) outData->data[i];
+            if (((tmpPeak->x == 0) && (tmpPeak->y == 0)) ||
+                    ((tmpPeak->x == 0) && (tmpPeak->y == numRows-1)) ||
+                    ((tmpPeak->x == numCols-1) && (tmpPeak->y == 0)) ||
+                    ((tmpPeak->x == numCols-1) && (tmpPeak->y == numRows-1))) {
+                if (!((tmpPeak->class & PM_PEAK_LONE) || (tmpPeak->class & PM_PEAK_EDGE))) {
+                    printf("TEST ERROR: (0) peak at (%d, %d) (%f) ->class set improperly (0x%x).",
+                           tmpPeak->y, tmpPeak->x, tmpPeak->counts, tmpPeak->class);
+                    printf(" should be (0x%x or 0x%x).\n", PM_PEAK_LONE, PM_PEAK_EDGE);
+                    testStatus = false;
+                }
+            } else if ((tmpPeak->x == numCols/2) && (tmpPeak->y == numRows/2)) {
+                if (tmpPeak->class != PM_PEAK_LONE) {
+                    printf("TEST ERROR: (1) peak at (%d, %d) (%f) ->class set improperly (0x%x).\n",
+                           tmpPeak->y, tmpPeak->x, tmpPeak->counts, tmpPeak->class);
+                    printf(" should be (0x%x).\n", PM_PEAK_LONE);
+                    testStatus = false;
+                }
+            } else {
+                printf("TEST ERROR: Peak at (%d, %d) (%f)\n", tmpPeak->y, tmpPeak->x, tmpPeak->counts);
+                testStatus = false;
+            }
+        }
+    }
+
+    psFree(inData);
+    psFree(outData);
+    return(testStatus);
+}
+
+int test02( void )
+{
+    bool testStatus = true;
+    psArray *tmpArray = NULL;
+    psImage *tmpImageF64 = psImageAlloc(TST02_NUM_ROWS, TST02_NUM_COLS, PS_TYPE_F64);
+    psImage *tmpImageEmpty = psImageAlloc(0, 0, PS_TYPE_F32);
+
+    psTraceSetLevel(".", 0);
+
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmFindImagePeaks with NULL psImage.  Should generate error and return NULL.\n");
+    tmpArray = pmFindImagePeaks(NULL, 0.0);
+    if (tmpArray != NULL) {
+        printf("TEST ERROR: pmFindImagePeaks() returned a non-NULL psImage.\n");
+        testStatus = false;
+        psFree(tmpArray);
+    }
+
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmFindImagePeaks with empty psImage.  Should generate error and return NULL.\n");
+    tmpArray = pmFindImagePeaks(tmpImageEmpty, 0.0);
+    if (tmpArray != NULL) {
+        printf("TEST ERROR: pmFindImagePeaks() returned a non-NULL psImage.\n");
+        testStatus = false;
+        psFree(tmpArray);
+    }
+
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmFindImagePeaks with PS_TYPE_F64 psImage.  Should generate error and return NULL.\n");
+    tmpArray = pmFindImagePeaks(tmpImageF64, 0.0);
+    if (tmpArray != NULL) {
+        printf("TEST ERROR: pmFindImagePeaks() returned a non-NULL psImage.\n");
+        testStatus = false;
+        psFree(tmpArray);
+    }
+    printf("----------------------------------------------------------------------------------\n");
+    //    testStatus&= test_pmFindImagePeaks(1, 1);
+    //    testStatus&= test_pmFindImagePeaks(2, 5);
+    //    testStatus&= test_pmFindImagePeaks(5, 2);
+    // HEY: add code for small images
+    //    testStatus&= test_pmFindImagePeaks(1, 1);
+    //    testStatus&= test_pmFindImagePeaks(1, 8);
+    //    testStatus&= test_pmFindImagePeaks(8, 1);
+    testStatus&= test_pmFindImagePeaks(TST02_NUM_ROWS,   TST02_NUM_COLS);
+    testStatus&= test_pmFindImagePeaks(2*TST02_NUM_ROWS, TST02_NUM_COLS);
+    testStatus&= test_pmFindImagePeaks(TST02_NUM_ROWS,   2*TST02_NUM_COLS);
+
+
+    psFree(tmpImageF64);
+    psFree(tmpImageEmpty);
+    return(testStatus);
+}
+
+/******************************************************************************
+test03(): We first test pmCullPeaks() with various NULL and unallowable input
+parameters.  Then we generate a list of peaks and test that pmCullPeaks()
+removes them correctly.
+ *****************************************************************************/
+int test03( void )
+{
+    bool testStatus = true;
+    psImage *imgData = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+    psArray *outData = NULL;
+
+    /* XXX: Modify for new pmCullPeaks()
+            printf("----------------------------------------------------------------------------------\n");
+            printf("Calling pmCullPeaks with NULL psList.  Should generate error and return NULL.\n");
+            outData = pmCullPeaks(NULL, 0.0, NULL);
+            if (outData != NULL) {
+                printf("TEST ERROR: pmCulPeaks() returned a non-NULL psList.\n");
+                testStatus = false;
+        }
+    */
+
+    //
+    // Set peaks in input image.  All even-column and even-row pixels are
+    // set non-zero, all other pixels are set to zero.
+    //
+    psS32 numPeaksOrig = 0;
+    for (psS32 i = 0 ; i < NUM_ROWS ; i++) {
+        for (psS32 j = 0 ; j < NUM_COLS ; j++) {
+            if ((0 == i%2) && (0 == j%2)) {
+                imgData->data.F32[i][j] = (float) (i + 10);
+                numPeaksOrig++;
+            } else {
+                imgData->data.F32[i][j] = 0.0;
+            }
+        }
+    }
+    for (psS32 i = 0 ; i < NUM_ROWS ; i++) {
+        for (psS32 j = 0 ; j < NUM_COLS ; j++) {
+            printf("(%.1f) ", imgData->data.F32[i][j]);
+        }
+        printf("\n");
+    }
+    printf("Set %d peaks\n", numPeaksOrig);
+
+    //
+    // Call pmCullPeaks() with HUGE maxValue and NULL psRegion.  Should not
+    // remove any peaks.
+    //
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmCullPeaks with large maxValue and NULL psRegion.\n");
+    outData = pmFindImagePeaks(imgData, 0.0);
+    /* XXX: Modify for new pmCullPeaks
+        outData = pmCullPeaks(outData, PS_MAX_F32, NULL);
+
+        if (outData == NULL) {
+            printf("TEST ERROR: pmCullPeaks() returned a non-NULL psList.\n");
+            testStatus = false;
+            return(testStatus);
+        }
+        if (outData->n != numPeaksOrig) {
+            printf("TEST ERROR (0): pmCullPeaks incorrectly removed peaks\n");
+            printf("The pmCullPeaks() output had %d peaks, should have had %d peaks.\n", outData->n, numPeaksOrig);
+            testStatus = false;
+        }
+    */
+    psFree(outData);
+
+    //
+    // Call pmCullPeaks() with TINY maxValue and NULL psRegion.  Should
+    // remove all peaks.
+    //
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmCullPeaks with tiny maxValue and NULL psRegion.\n");
+    outData = pmFindImagePeaks(imgData, 0.0);
+    printf("pmFindImagePeaks found %ld peaks\n", outData->n);
+    /* XXX: Modify for new pmCullPeaks
+        outData = pmCullPeaks(outData, 0.0, NULL);
+
+        if (outData == NULL) {
+            printf("TEST ERROR: pmCullPeaks() returned a non-NULL psList.\n");
+            testStatus = false;
+            return(testStatus);
+        }
+        if (outData->n != 0) {
+            printf("TEST ERROR (1): pmCullPeaks incorrectly removed peaks\n");
+            printf("The pmCullPeaks() output had %d peaks, should have had %d peaks.\n", outData->n, 0);
+            testStatus = false;
+        }
+        psFree(outData);
+    */
+
+    //
+    // Call pmCullPeaks() with HUGE maxValue and disjoint psRegion.  Should
+    // not remove any peaks.
+    //
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmCullPeaks with large maxValue and disjoint psRegion.\n");
+    outData = pmFindImagePeaks(imgData, 0.0);
+    printf("pmFindImagePeaks found %ld peaks\n", outData->n);
+    psRegion tmpRegion = psRegionSet(10000.0, 20000.0, 10000.0, 20000.0);
+
+    /* XXX: Modify for new pmCullPeaks
+        outData = pmCullPeaks(outData, PS_MAX_F32, tmpRegion);
+
+        if (outData == NULL) {
+            printf("TEST ERROR: pmCullPeaks() returned a non-NULL psList.\n");
+            testStatus = false;
+            return(testStatus);
+        }
+        if (outData->n != numPeaksOrig) {
+            printf("TEST ERROR (2): pmCullPeaks incorrectly removed peaks\n");
+            printf("The pmCullPeaks() output had %d peaks, should have had %d peaks.\n", outData->n, numPeaksOrig);
+            testStatus = false;
+        }
+    */
+    psFree(outData);
+
+    //
+    // Call pmCullPeaks() with HUGE maxValue and non-disjoint psRegion.  Should
+    // remove all peaks.
+    //
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmCullPeaks with large maxValue and non-disjoint psRegion.\n");
+    outData = pmFindImagePeaks(imgData, 0.0);
+    printf("pmFindImagePeaks found %ld peaks\n", outData->n);
+    tmpRegion = psRegionSet(-PS_MAX_F32, PS_MAX_F32, -PS_MAX_F32, PS_MAX_F32);
+    /* XXX: Modify for new pmCullPeaks
+        outData = pmCullPeaks(outData, PS_MAX_F32, tmpRegion);
+
+        if (outData == NULL) {
+            printf("TEST ERROR: pmCullPeaks() returned a non-NULL psList.\n");
+            testStatus = false;
+            return(testStatus);
+        }
+        if (outData->n != 0) {
+            printf("TEST ERROR (3): pmCullPeaks incorrectly removed peaks\n");
+            printf("The pmCullPeaks() output had %d peaks, should have had %d peaks.\n", outData->n, 0);
+            testStatus = false;
+        }
+    */
+    psFree(outData);
+
+    printf("----------------------------------------------------------------------------------\n");
+    psFree(imgData);
+    return(testStatus);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#define TST04_NUM_ROWS 100
+#define TST04_NUM_COLS 100
+#define TST04_SKY 20.0
+#define TST04_INNER_RADIUS 3
+#define TST04_OUTER_RADIUS 5
+/******************************************************************************
+test04(): We first test pmSourceLocalSky() with various NULL and unallowable
+input parameters.
+ 
+XXX: Should we produce tests with boundary numbers for the inner/outer radius?
+ 
+XXX: Call this with varying sizes for the image.
+ *****************************************************************************/
+int test04( void )
+{
+    bool testStatus = true;
+    psImage *imgData = psImageAlloc(TST04_NUM_COLS, TST04_NUM_ROWS, PS_TYPE_F32);
+    psImageInit(imgData, TST04_SKY);
+    psImage *imgMask = psImageAlloc(TST04_NUM_COLS, TST04_NUM_ROWS, PS_TYPE_U8);
+    psImageInit(imgMask, 0);
+    //    psImage *imgMaskS8 = psImageAlloc(TST04_NUM_COLS, TST04_NUM_ROWS, PS_TYPE_S8);
+    //    psImageInit(imgMaskS8, 0);
+    psImage *imgDataF64 = psImageAlloc(TST04_NUM_COLS, TST04_NUM_ROWS, PS_TYPE_F64);
+    psImageInit(imgDataF64, 0.0);
+    pmPeak *tmpPeak = pmPeakAlloc((psF32) (TST04_NUM_ROWS / 2),
+                                  (psF32) (TST04_NUM_COLS / 2),
+                                  200.0,
+                                  PM_PEAK_LONE);
+    pmSource *tmpSource = pmSourceAlloc();
+    tmpSource->pixels = imgData;
+    tmpSource->mask = imgMask;
+    tmpSource->peak = tmpPeak;
+
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceLocalSky with NULL tmpSource.  Should generate error and return FALSE.\n");
+    bool rc = pmSourceLocalSky(NULL, PS_STAT_SAMPLE_MEAN, 10.0);
+    if (rc != false) {
+        printf("TEST ERROR: pmSourceLocalSky() returned a non-FALSE pmSource.\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceLocalSky with Radius<0.0.  Should generate error and return FALSE.\n");
+    rc = pmSourceLocalSky(tmpSource, PS_STAT_SAMPLE_MEAN, -10.0);
+    if (rc != false) {
+        printf("TEST ERROR: pmSourceLocalSky() returned a non-FALSE pmSource.\n");
+        testStatus = false;
+    }
+
+    //
+    // XXX: The following code should be a separate function, and we should call it
+    // with a variety of image sizes, peaks.
+    //
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceLocalSky with valid data.\n");
+    tmpPeak->x = (psF32) (TST04_NUM_ROWS / 2);
+    tmpPeak->y = (psF32) (TST04_NUM_COLS / 2);
+    rc = pmSourceLocalSky(tmpSource, PS_STAT_SAMPLE_MEAN, 10.0);
+
+    if (rc == false) {
+        printf("TEST ERROR: pmSourceLocalSky() returned a FALSE pmSource.\n");
+        testStatus = false;
+    } else {
+        if (tmpSource->peak == NULL) {
+            printf("TEST ERROR: pmSourceLocalSky() returned a NULL pmSource->peak.\n");
+            testStatus = false;
+        } else {
+            if (tmpSource->peak->x != tmpPeak->x) {
+                printf("TEST ERROR: pmSourceLocalSky() pmSource->peak->x was %d, should have been %d.\n",
+                       tmpSource->peak->x, tmpPeak->x);
+                testStatus = false;
+            }
+
+            if (tmpSource->peak->y != tmpPeak->y) {
+                printf("TEST ERROR: pmSourceLocalSky() pmSource->peak->y was %d, should have been %d.\n",
+                       tmpSource->peak->y, tmpPeak->y);
+                testStatus = false;
+            }
+
+            if (tmpSource->peak->counts != tmpPeak->counts) {
+                printf("TEST ERROR: pmSourceLocalSky() pmSource->peak->counts was %f, should have been %f.\n",
+                       tmpSource->peak->counts, tmpPeak->counts);
+                testStatus = false;
+            }
+
+            if (tmpSource->peak->class != tmpPeak->class) {
+                printf("TEST ERROR: pmSourceLocalSky() pmSource->peak->class was %d, should have been %d.\n",
+                       tmpSource->peak->class, tmpPeak->class);
+                testStatus = false;
+            }
+        }
+
+        if (tmpSource->moments == NULL) {
+            printf("TEST ERROR: pmSourceLocalSky() returned a NULL pmSource->moments.\n");
+            testStatus = false;
+        } else {
+            if (tmpSource->moments->Sky != TST04_SKY) {
+                printf("TEST ERROR: pmSourceLocalSky() pmSource->moments->Sky was %f, should have been %f.\n", tmpSource->moments->Sky, TST04_SKY);
+                testStatus = false;
+            }
+        }
+    }
+
+    printf("----------------------------------------------------------------------------------\n");
+    psFree(tmpSource);
+    //    psFree(imgData);
+    //    psFree(imgDataF64);
+    //    psFree(imgMask);
+    //    psFree(imgMaskS8);
+    return(testStatus);
+}
+
+#define TST05_NUM_ROWS 100
+#define TST05_NUM_COLS 100
+#define TST05_SKY 20.0
+#define TST05_INNER_RADIUS 3
+#define TST05_OUTER_RADIUS 5
+/******************************************************************************
+test05(): We first test pmSourceMoments() with various NULL and unallowable
+input parameters.
+ 
+XXX: Should we produce tests with boundary numbers for the inner/outer radius?
+ 
+XXX: Call this with varying sizes for the image.
+ 
+XXX: The actual values of the moments are not tested.
+ *****************************************************************************/
+int test05( void )
+{
+    bool testStatus = true;
+    psImage *imgData = psImageAlloc(TST04_NUM_COLS, TST04_NUM_ROWS, PS_TYPE_F32);
+    psImageInit(imgData, TST04_SKY);
+    psImage *imgMask = psImageAlloc(TST04_NUM_COLS, TST04_NUM_ROWS, PS_TYPE_U8);
+    psImageInit(imgMask, 0);
+    pmPeak *tmpPeak = pmPeakAlloc((psF32) (TST04_NUM_ROWS / 2),
+                                  (psF32) (TST04_NUM_COLS / 2),
+                                  200.0,
+                                  PM_PEAK_LONE);
+    pmSource *tmpSource = pmSourceAlloc();
+    tmpSource->pixels = imgData;
+    tmpSource->mask = imgMask;
+    tmpSource->peak = tmpPeak;
+    psBool rc = pmSourceLocalSky(tmpSource, PS_STAT_SAMPLE_MEAN, 10.0);
+
+    if (rc == false) {
+        printf("TEST ERROR: pmSourceLocalSky() returned a FALSE pmSource.\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceMoments with NULL pmSource.  Should generate error and return FALSE.\n");
+    rc = pmSourceMoments(NULL, 10.0);
+    if (rc != false) {
+        printf("TEST ERROR: pmSourceMoments() returned TRUE.\n");
+        testStatus = false;
+    }
+    // XXX: test with pmSource->peaks NULL
+    // XXX: test with pmSource->pixels NULL
+    // XXX: test with pmSource->mask NULL
+
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceMoments with radius < 0.0.  Should generate error and return FALSE.\n");
+    rc = pmSourceMoments(tmpSource, -10.0);
+    if (rc != false) {
+        printf("TEST ERROR: pmSourceMoments() returned TRUE.\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------------------------\n");
+    psFree(tmpSource);
+    return(testStatus);
+
+}
+
+#define TST09_NUM_ROWS 70
+#define TST09_NUM_COLS 70
+#define TST09_SKY 5.0
+#define TST09_INNER_RADIUS 3
+#define TST09_OUTER_RADIUS 10
+#define LEVEL (TST09_SKY + 10.0)
+/******************************************************************************
+test09(): We first test pmSourceContour() with various NULL and unallowable
+input parameters.
+ 
+XXX: We don't verify the numbers.
+ *****************************************************************************/
+int test09( void )
+{
+    bool testStatus = true;
+    psImage *imgData = psImageAlloc(TST09_NUM_COLS, TST09_NUM_ROWS, PS_TYPE_F32);
+    psImageInit(imgData, TST09_SKY);
+    psImage *imgMask = psImageAlloc(TST09_NUM_COLS, TST09_NUM_ROWS, PS_TYPE_U8);
+    psImageInit(imgMask, 0);
+    pmPeak *tmpPeak = pmPeakAlloc((psF32) (TST09_NUM_ROWS / 2),
+                                  (psF32) (TST09_NUM_COLS / 2),
+                                  200.0,
+                                  PM_PEAK_LONE);
+    pmSource *tmpSource = pmSourceAlloc();
+    tmpSource->pixels = imgData;
+    tmpSource->mask = imgMask;
+    tmpSource->peak = tmpPeak;
+    psBool rc = pmSourceLocalSky(tmpSource, PS_STAT_SAMPLE_MEAN, 10.0);
+    if (rc == false) {
+        printf("TEST ERROR: pmSourceLocalSky() returned a FALSE pmSource.\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceContour with NULL pmSource .  Should generate error, return FALSE.\n");
+    rc = pmSourceContour(NULL, imgData, LEVEL, PS_CONTOUR_CRUDE);
+    if (rc != false) {
+        printf("TEST ERROR: pmSourceContour() returned TRUE.\n");
+        testStatus = false;
+        psFree(rc);
+    }
+
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceContour with NULL psImage .  Should generate error, return FALSE.\n");
+    rc = pmSourceContour(tmpSource, NULL, LEVEL, PS_CONTOUR_CRUDE);
+    if (rc != FALSE) {
+        printf("TEST ERROR: pmSourceContour() returned TRUE.\n");
+        testStatus = false;
+        psFree(rc);
+    }
+
+    //
+    // XXX: pmSourceContour() has a problem with contour tops/bottoms.
+    // Must correct this.
+    //
+    if (1) {
+        printf("----------------------------------------------------------------------------------\n");
+        printf("Calling pmSourceContour with acceptable data.\n");
+        printf("NOTE: must figure out the parameters for this test to be meaningful.\n");
+        tmpSource->modelPSF->params->data.F32[0] = TST09_SKY;
+        tmpSource->modelPSF->params->data.F32[1] = 15.0;
+        tmpSource->modelPSF->params->data.F32[2] = (psF32) (TST09_NUM_ROWS / 2);
+        tmpSource->modelPSF->params->data.F32[3] = (psF32) (TST09_NUM_COLS / 2);
+        tmpSource->modelPSF->params->data.F32[4] = 2.0;
+        tmpSource->modelPSF->params->data.F32[5] = 2.0;
+        tmpSource->modelPSF->params->data.F32[6] = 2.0;
+        rc = pmSourceContour(tmpSource, imgData, LEVEL, PS_CONTOUR_CRUDE);
+        if (rc == false) {
+            printf("TEST ERROR: pmSourceContour() returned FALSE.\n");
+            testStatus = false;
+        } else {
+            psFree(rc);
+        }
+    }
+
+    psFree(tmpSource);
+    return(testStatus);
+}
+
+#define TST15_NUM_ROWS 100
+#define TST15_NUM_COLS 100
+#define TST15_SKY 10.0
+#define TST15_INNER_RADIUS 3
+#define TST15_OUTER_RADIUS 5
+/******************************************************************************
+test15(): We first test pmSourceAddModel() with various NULL and unallowable
+input parameters.
+ 
+XXX: We don't verify the numbers.
+ *****************************************************************************/
+/*
+int test15( void )
+{
+    bool testStatus = true;
+    psImage *imgData = psImageAlloc(TST15_NUM_COLS, TST15_NUM_ROWS, PS_TYPE_F32);
+    psImageInit(imgData, TST15_SKY);
+    psImage *imgMask = psImageAlloc(TST15_NUM_COLS, TST15_NUM_ROWS, PS_TYPE_U8);
+    psImageInit(imgMask, 0);
+    pmPeak *tmpPeak = pmPeakAlloc((psF32) (TST15_NUM_ROWS / 2),
+                                  (psF32) (TST15_NUM_COLS / 2),
+                                  200.0,
+                                  PM_PEAK_LONE);
+    pmSource *tmpSource = pmSourceAlloc();
+    tmpSource->pixels = imgData;
+    tmpSource->mask = imgMask;
+    tmpSource->peak = tmpPeak;
+    psBool rc = pmSourceLocalSky(tmpSource, PS_STAT_SAMPLE_MEAN, 10.0);
+    if (rc == false) {
+        printf("TEST ERROR: pmSourceLocalSky() returned a FALSE pmSource.\n");
+        testStatus = false;
+    }
+ 
+ 
+    tmpSource->modelPSF = pmModelAlloc(PS_MODEL_GAUSS);
+    tmpSource->modelPSF->params->data.F32[0] = 5.0;
+    tmpSource->modelPSF->params->data.F32[1] = 70.0;
+    tmpSource->modelPSF->params->data.F32[2] = (psF32) (TST15_NUM_ROWS / 2);
+    tmpSource->modelPSF->params->data.F32[3] = (psF32) (TST15_NUM_COLS / 2);
+    tmpSource->modelPSF->params->data.F32[4] = 1.0;
+    tmpSource->modelPSF->params->data.F32[5] = 1.0;
+    tmpSource->modelPSF->params->data.F32[6] = 2.0;
+ 
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceAddModel with NULL psImage.  Should generate error, return FALSE.\n");
+    rc = pmSourceAddModel(NULL, tmpSource, true);
+    if (rc == true) {
+        printf("TEST ERROR: pmSourceAddModel() returned TRUE.\n");
+        testStatus = false;
+    }
+ 
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceAddModel with NULL psSrc.  Should generate error, return FALSE.\n");
+    rc = pmSourceAddModel(imgData, NULL, true);
+    if (rc == true) {
+        printf("TEST ERROR: pmSourceAddModel() returned TRUE.\n");
+        testStatus = false;
+    }
+ 
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceAddModel with acceptable data.\n");
+    rc = pmSourceAddModel(imgData, tmpSource, true);
+    if (rc != true) {
+        printf("TEST ERROR: pmSourceAddModel() returned FALSE.\n");
+        testStatus = false;
+    }
+ 
+    psFree(tmpSource);
+    psFree(imgData);
+    return(testStatus);
+}
+*/
+
+#define TST16_NUM_ROWS 100
+#define TST16_NUM_COLS 100
+#define TST16_SKY 10.0
+#define TST16_INNER_RADIUS 3
+#define TST16_OUTER_RADIUS 5
+/******************************************************************************
+test16(): We first test pmSourceSubModel() with various NULL and unallowable
+input parameters.
+ 
+XXX: We don't verify the numbers.
+ *****************************************************************************/
+/*
+int test16( void )
+{
+    bool testStatus = true;
+    psImage *imgData = psImageAlloc(TST16_NUM_COLS, TST16_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < imgData->numRows; i++) {
+        for (psS32 j = 0 ; j < imgData->numCols; j++) {
+            imgData->data.F32[i][j] = TST16_SKY;
+        }
+    }
+    pmSource *tmpSource = NULL;
+    psBool rc = false;
+ 
+    pmPeak *tmpPeak = pmPeakAlloc((psF32) (TST16_NUM_ROWS / 2),
+                                  (psF32) (TST16_NUM_COLS / 2),
+                                  200.0,
+                                  PM_PEAK_LONE);
+ 
+    printf("Calling pmSourceLocalSky with valid data.\n");
+    tmpPeak->x = (psF32) (TST16_NUM_ROWS / 2);
+    tmpPeak->y = (psF32) (TST16_NUM_COLS / 2);
+    tmpSource = pmSourceLocalSky(imgData,
+                                 tmpPeak,
+                                 PS_STAT_SAMPLE_MEAN,
+                                 (psF32) TST16_INNER_RADIUS,
+                                 (psF32) TST16_OUTER_RADIUS);
+ 
+    if (tmpSource == NULL) {
+        printf("TEST ERROR: pmSourceLocalSky() returned a NULL pmSource.\n");
+        testStatus = false;
+    }
+ 
+    tmpSource->modelPSF = pmModelAlloc(PS_MODEL_GAUSS);
+    tmpSource->modelPSF->params->data.F32[0] = 5.0;
+    tmpSource->modelPSF->params->data.F32[1] = 70.0;
+    tmpSource->modelPSF->params->data.F32[2] = (psF32) (TST16_NUM_ROWS / 2);
+    tmpSource->modelPSF->params->data.F32[3] = (psF32) (TST16_NUM_COLS / 2);
+    tmpSource->modelPSF->params->data.F32[4] = 1.0;
+    tmpSource->modelPSF->params->data.F32[5] = 1.0;
+    tmpSource->modelPSF->params->data.F32[6] = 2.0;
+ 
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceSubModel with NULL psImage.  Should generate error, return FALSE.\n");
+    rc = pmSourceSubModel(NULL, tmpSource, true);
+    if (rc == true) {
+        printf("TEST ERROR: pmSourceSubModel() returned TRUE.\n");
+        testStatus = false;
+    }
+ 
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceSubModel with NULL psSrc.  Should generate error, return FALSE.\n");
+    rc = pmSourceSubModel(imgData, NULL, true);
+    if (rc == true) {
+        printf("TEST ERROR: pmSourceSubModel() returned TRUE.\n");
+        testStatus = false;
+    }
+ 
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceSubModel with acceptable data.\n");
+    rc = pmSourceSubModel(imgData, tmpSource, true);
+    if (rc != true) {
+        printf("TEST ERROR: pmSourceSubModel() returned FALSE.\n");
+        testStatus = false;
+    }
+ 
+    psFree(tmpSource);
+    psFree(imgData);
+    return(testStatus);
+}
+*/
+
+#define TST20_NUM_ROWS 100
+#define TST20_NUM_COLS 100
+#define TST20_SKY 10.0
+#define TST20_INNER_RADIUS 3
+#define TST20_OUTER_RADIUS 5
+/******************************************************************************
+test20(): We first test pmSourceSubModel() with various NULL and unallowable
+input parameters.
+ 
+XXX: We don't verify the numbers.
+ *****************************************************************************/
+/*
+int test20( void )
+{
+    bool testStatus = true;
+    psImage *imgData = psImageAlloc(TST20_NUM_COLS, TST20_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < imgData->numRows; i++) {
+        for (psS32 j = 0 ; j < imgData->numCols; j++) {
+            imgData->data.F32[i][j] = TST20_SKY;
+        }
+    }
+    pmSource *tmpSource = NULL;
+    psBool rc = false;
+ 
+    pmPeak *tmpPeak = pmPeakAlloc((psF32) (TST20_NUM_ROWS / 2),
+                                  (psF32) (TST20_NUM_COLS / 2),
+                                  200.0,
+                                  PM_PEAK_LONE);
+ 
+    printf("Calling pmSourceLocalSky with valid data.\n");
+    tmpPeak->x = (psF32) (TST20_NUM_ROWS / 2);
+    tmpPeak->y = (psF32) (TST20_NUM_COLS / 2);
+    tmpSource = pmSourceLocalSky(imgData,
+                                 tmpPeak,
+                                 PS_STAT_SAMPLE_MEAN,
+                                 (psF32) TST20_INNER_RADIUS,
+                                 (psF32) TST20_OUTER_RADIUS);
+ 
+    if (tmpSource == NULL) {
+        printf("TEST ERROR: pmSourceLocalSky() returned a NULL pmSource.\n");
+        testStatus = false;
+    }
+ 
+    tmpSource->modelPSF = pmModelAlloc(PS_MODEL_GAUSS);
+ 
+ 
+    tmpSource->modelPSF->params->data.F32[0] = 5.0;
+    tmpSource->modelPSF->params->data.F32[1] = 70.0;
+    tmpSource->modelPSF->params->data.F32[2] = (psF32) (TST20_NUM_ROWS / 2);
+    tmpSource->modelPSF->params->data.F32[3] = (psF32) (TST20_NUM_COLS / 2);
+    tmpSource->modelPSF->params->data.F32[4] = 1.0;
+    tmpSource->modelPSF->params->data.F32[5] = 1.0;
+    tmpSource->modelPSF->params->data.F32[6] = 2.0;
+ 
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceFitModel with NULL psImage.  Should generate error, return FALSE.\n");
+    rc = pmSourceFitModel(tmpSource, NULL);
+    if (rc == true) {
+        printf("TEST ERROR: pmSourceFitModel() returned TRUE.\n");
+        testStatus = false;
+    }
+ 
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceFitModel with NULL pmSource.  Should generate error, return FALSE.\n");
+    rc = pmSourceFitModel(NULL, imgData);
+    if (rc == true) {
+        printf("TEST ERROR: pmSourceFitModel() returned TRUE.\n");
+        testStatus = false;
+    }
+ 
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceFitModel with acceptable data.\n");
+    rc = pmSourceFitModel(tmpSource, imgData);
+    printf("pmSourceFitModel returned %d\n", rc);
+ 
+    // XXX: Memory leaks are not being tested
+    psVector *junk = psVectorAlloc(10, PS_TYPE_F32);
+    junk->data.F32[0] = 0.0;
+ 
+    psFree(tmpSource);
+    psFree(imgData);
+    return(testStatus);
+}
+*/
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/pstap/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/pstap/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/pstap/.cvsignore	(revision 22157)
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/pstap/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/pstap/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/pstap/Makefile.am	(revision 22157)
@@ -0,0 +1,1 @@
+SUBDIRS = src
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/pstap/src/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/pstap/src/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/pstap/src/.cvsignore	(revision 22157)
@@ -0,0 +1,6 @@
+Makefile
+Makefile.in
+libpstap.la
+pstap.lo
+.deps
+.libs
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/pstap/src/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/pstap/src/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/pstap/src/Makefile.am	(revision 22157)
@@ -0,0 +1,18 @@
+
+AM_CPPFLAGS = \
+	$(SRCINC) \
+	-I$(top_srcdir)/test/tap/src \
+	-I$(top_srcdir)/test/pstap/src \
+	$(PSMODULES_CFLAGS)
+
+AM_LDFLAGS = \
+	$(top_builddir)/src/libpsmodules.la  \
+	$(top_builddir)/test/tap/src/libtap.la \
+	$(PSMODULES_LIBS)
+
+TEST_LTLIBS = libpstap.la
+libpstap_la_SOURCES = pstap.c
+noinst_HEADERS = pstap.h
+
+noinst_LTLIBRARIES = $(TEST_LTLIBS)
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/pstap/src/pstap.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/pstap/src/pstap.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/pstap/src/pstap.c	(revision 22157)
@@ -0,0 +1,6 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <tap.h>
+#include <pslib.h>
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/pstap/src/pstap.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/pstap/src/pstap.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/pstap/src/pstap.h	(revision 22157)
@@ -0,0 +1,63 @@
+#include <pslib.h>
+
+#include "tap.h"
+
+#define done() ok(psMemCheckLeaks(0, NULL, stdout, false) == 0, "Memory Leaks"); return exit_status()
+
+        # define mem() ok(psMemCheckLeaks(psMemGetLastId(), NULL, stdout, false) == 0, "Memory Leaks")
+
+        # define checkLeaks false
+
+        # define checkMem() if(checkLeaks) mem()
+
+            # ifdef __GNUC__
+
+            // write a comment which is counted as a test (and swallowed by prove)
+            # define note(A, ...) _gen_result(1, __func__, __FILE__, __LINE__, A, ## __VA_ARGS__);
+
+// use to test the value of a float
+# define ok_float(VALUE,EXPECT,COMMENT, ...)\
+ok((fabsf((VALUE)-(EXPECT)) < FLT_EPSILON), COMMENT, ## __VA_ARGS__);
+
+// use to test the value of a double
+# define ok_double(VALUE,EXPECT,COMMENT, ...)\
+ok((fabs((VALUE)-(EXPECT)) < DBL_EPSILON), COMMENT, ## __VA_ARGS__);
+
+// use to test the value of a float within a defined tolerance
+# define ok_float_tol(VALUE,EXPECT,TOL,COMMENT, ...)\
+ok((fabsf((VALUE)-(EXPECT)) < (TOL)), COMMENT, ## __VA_ARGS__);
+
+// use to test the value of a double within a defined tolerance
+# define ok_double_tol(VALUE,EXPECT,TOL,COMMENT, ...)\
+ok((fabs((VALUE)-(EXPECT)) < (TOL)), COMMENT, ## __VA_ARGS__);
+
+# define ok_str(VALUE,EXPECT,COMMENT, ...)\
+ok(strcmp(VALUE, EXPECT) == 0, COMMENT, ## __VA_ARGS__);
+
+#elif __STDC_VERSION__ >= 199901L /* __GNUC__ */
+
+// write a comment which is counted as a test (and swallowed by prove)
+# define note(A, ...) _gen_result(1, __func__, __FILE__, __LINE__, A, ...);
+
+// use to test the value of a float
+# define ok_float(VALUE,EXPECT, ...)\
+ok((fabsf((VALUE)-(EXPECT)) < FLT_EPSILON), __VA_ARGS__);
+
+// use to test the value of a double
+# define ok_double(VALUE,EXPECT, ...)\
+ok((fabs((VALUE)-(EXPECT)) < DBL_EPSILON), __VA_ARGS__);
+
+// use to test the value of a float
+# define ok_float_tol(VALUE,EXPECT,TOL, ...)\
+ok((fabsf((VALUE)-(EXPECT)) < (TOL)), __VA_ARGS__);
+
+// use to test the value of a double
+# define ok_double_tol(VALUE,EXPECT,TOL, ...)\
+ok((fabs((VALUE)-(EXPECT)) < )(TOL)), __VA_ARGS__);
+
+# define ok_str(VALUE,EXPECT, ...)\
+ok(strcmp(VALUE, EXPECT) == 0, __VA_ARGS__);
+
+#else /* __STDC_VERSION__ */
+# error "Needs gcc or C99 compiler for variadic macros."
+#endif /* __STDC_VERSION__ */
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/.cvsignore	(revision 22157)
@@ -0,0 +1,13 @@
+.in
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+config.log
+config.status
+configure
+libtool
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/INSTALL
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/INSTALL	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/INSTALL	(revision 22157)
@@ -0,0 +1,8 @@
+Quick Installation
+
+    ./configure
+    make
+    make check
+    make install
+
+Run "configure --help" for additional options.
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/LICENSE
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/LICENSE	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/LICENSE	(revision 22157)
@@ -0,0 +1,23 @@
+Copyright (c) 2004 Nik Clayton
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/Makefile.am	(revision 22157)
@@ -0,0 +1,5 @@
+SUBDIRS  = src
+#SUBDIRS += tests
+
+prove:
+	prove -v -r
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/README
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/README	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/README	(revision 22157)
@@ -0,0 +1,11 @@
+NAME
+     tap -- write tests that implement the Test Anything Protocol
+
+SYNOPSIS
+     #include <tap.h>
+
+DESCRIPTION
+     The tap library provides functions for writing test scripts that produce
+     output consistent with the Test Anything Protocol.  A test harness that
+     parses this protocol can run these tests and produce useful reports indi-
+     cating their success or failure.
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/bootstrap.sh
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/bootstrap.sh	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/bootstrap.sh	(revision 22157)
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+set -x
+aclocal19 -I /usr/local/share/aclocal || aclocal || exit 1
+autoheader259 || autoheader || exit 1
+libtoolize15 -c -f || libtoolize -c -f || glibtoolize -c -f || exit 1
+automake19 -a -c || automake -a -c || exit 1
+autoconf259 || autoconf || exit 1
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/compile
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/compile	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/compile	(revision 22157)
@@ -0,0 +1,142 @@
+#! /bin/sh
+# Wrapper for compilers which do not understand `-c -o'.
+
+scriptversion=2004-10-12.08
+
+# Copyright (C) 1999, 2000, 2003, 2004 Free Software Foundation, Inc.
+# Written by Tom Tromey <tromey@cygnus.com>.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# This file is maintained in Automake, please report
+# bugs to <bug-automake@gnu.org> or send patches to
+# <automake-patches@gnu.org>.
+
+case $1 in
+  '')
+     echo "$0: No command.  Try \`$0 --help' for more information." 1>&2
+     exit 1;
+     ;;
+  -h | --h*)
+    cat <<\EOF
+Usage: compile [--help] [--version] PROGRAM [ARGS]
+
+Wrapper for compilers which do not understand `-c -o'.
+Remove `-o dest.o' from ARGS, run PROGRAM with the remaining
+arguments, and rename the output as expected.
+
+If you are trying to build a whole package this is not the
+right script to run: please start by reading the file `INSTALL'.
+
+Report bugs to <bug-automake@gnu.org>.
+EOF
+    exit 0
+    ;;
+  -v | --v*)
+    echo "compile $scriptversion"
+    exit 0
+    ;;
+esac
+
+ofile=
+cfile=
+eat=
+
+for arg
+do
+  if test -n "$eat"; then
+    eat=
+  else
+    case $1 in
+      -o)
+	# configure might choose to run compile as `compile cc -o foo foo.c'.
+	# So we strip `-o arg' only if arg is an object.
+	eat=1
+	case $2 in
+	  *.o | *.obj)
+	    ofile=$2
+	    ;;
+	  *)
+	    set x "$@" -o "$2"
+	    shift
+	    ;;
+	esac
+	;;
+      *.c)
+	cfile=$1
+	set x "$@" "$1"
+	shift
+	;;
+      *)
+	set x "$@" "$1"
+	shift
+	;;
+    esac
+  fi
+  shift
+done
+
+if test -z "$ofile" || test -z "$cfile"; then
+  # If no `-o' option was seen then we might have been invoked from a
+  # pattern rule where we don't need one.  That is ok -- this is a
+  # normal compilation that the losing compiler can handle.  If no
+  # `.c' file was seen then we are probably linking.  That is also
+  # ok.
+  exec "$@"
+fi
+
+# Name of file we expect compiler to create.
+cofile=`echo "$cfile" | sed -e 's|^.*/||' -e 's/\.c$/.o/'`
+
+# Create the lock directory.
+# Note: use `[/.-]' here to ensure that we don't use the same name
+# that we are using for the .o file.  Also, base the name on the expected
+# object file name, since that is what matters with a parallel build.
+lockdir=`echo "$cofile" | sed -e 's|[/.-]|_|g'`.d
+while true; do
+  if mkdir "$lockdir" >/dev/null 2>&1; then
+    break
+  fi
+  sleep 1
+done
+# FIXME: race condition here if user kills between mkdir and trap.
+trap "rmdir '$lockdir'; exit 1" 1 2 15
+
+# Run the compile.
+"$@"
+ret=$?
+
+if test -f "$cofile"; then
+  mv "$cofile" "$ofile"
+elif test -f "${cofile}bj"; then
+  mv "${cofile}bj" "$ofile"
+fi
+
+rmdir "$lockdir"
+exit $ret
+
+# Local Variables:
+# mode: shell-script
+# sh-indentation: 2
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-end: "$"
+# End:
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/configure.in
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/configure.in	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/configure.in	(revision 22157)
@@ -0,0 +1,44 @@
+AC_INIT(tap, 1.01)
+AC_CONFIG_SRCDIR(src/tap.c)
+AM_INIT_AUTOMAKE([foreign])
+AC_CONFIG_HEADERS([src/config.h])
+AC_GNU_SOURCE
+AC_PROG_CC
+AC_PROG_LIBTOOL
+AC_PROG_INSTALL
+
+# Checks for libraries
+case "$host" in
+	*-*-*freebsd4*)
+		LDFLAGS="$LDFLAGS -pthread"
+		HAVE_LIBPTHREAD=1
+		;;
+	*)
+		AC_CHECK_LIB(pthread, main)
+		;;
+esac
+
+dnl build tests at the same time as the source code
+AC_ARG_ENABLE(tests,
+  [AS_HELP_STRING(--enable-tests,build tests at same time as source)],
+  [AC_MSG_RESULT(test building enabled)
+   tests=true],
+   [tests=false])
+AM_CONDITIONAL(BUILD_TESTS, test x$tests = xtrue)
+
+# Checks for header files
+AC_HEADER_STDC
+AC_CHECK_HEADERS([stdlib.h])
+AC_CHECK_HEADERS([pthread.h])
+
+# Checks for  typedefs, structures, and compiler characteristics.
+AC_C_CONST
+
+# Checks for library functions.
+AC_FUNC_VPRINTF
+AC_CHECK_FUNCS([atexit])
+
+AC_CONFIG_FILES([Makefile
+		 src/Makefile
+		])
+AC_OUTPUT
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/src/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/src/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/src/.cvsignore	(revision 22157)
@@ -0,0 +1,13 @@
+.deps
+.libs
+Makefile
+Makefile.in
+libtap.la
+tap.lo
+config.h
+config.h.in
+stamp-h1
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/src/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/src/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/src/Makefile.am	(revision 22157)
@@ -0,0 +1,8 @@
+TEST_LTLIBS = libtap.la
+libtap_la_SOURCES = tap.c tap.h
+noinst_HEADERS = tap.h
+noinst_LTLIBRARIES = $(TEST_LTLIBS)
+
+#man_MANS = tap.3
+EXTRA_DIST = $(man_MANS)
+
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/src/tap.3
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/src/tap.3	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/src/tap.3	(revision 22157)
@@ -0,0 +1,380 @@
+.Dd December 20, 2004
+.Os
+.Dt TAP 3
+.Sh NAME
+.Nm tap
+.Nd write tests that implement the Test Anything Protocol
+.Sh SYNOPSIS
+.In tap.h
+.Sh DESCRIPTION
+The
+.Nm
+library provides functions for writing test scripts that produce output
+consistent with the Test Anything Protocol.  A test harness that parses
+this protocol can run these tests and produce useful reports indicating
+their success or failure.
+.Ss PRINTF STRINGS
+In the descriptions that follow, for any function that takes as the
+last two parameters
+.Dq Fa char * , Fa ...
+it can be assumed that the
+.Fa char *
+is a
+.Fn printf
+-like format string, and the optional arguments are values to be placed
+in that string.
+.Ss TEST PLANS
+.Bl -tag -width indent
+.It Xo
+.Ft int
+.Fn plan_tests "unsigned int"
+.Xc
+.It Xo
+.Ft int
+.Fn plan_no_plan "void"
+.Xc
+.It Xo
+.Ft int
+.Fn plan_skip_all "char *" "..."
+.Xc
+.El
+.Pp
+You must first specify a test plan.  This indicates how many tests you
+intend to run, and allows the test harness to notice if any tests were
+missed, or if the test program exited prematurely.
+.Pp
+To do this, use
+.Fn plan_tests ,
+which returns the number of planned tests.  The function will cause
+your program to exit prematurely if you specify 0 tests.
+.Pp
+In some situations you may not know how many tests you will be running, or
+you are developing your test program, and do not want to update the
+.Fn plan_tests
+parameter every time you make a change.  For those situations use
+.Fn plan_no_plan .
+It returns 1, and indicates to the test harness that an indeterminate number
+of tests will be run.
+.Pp
+Both
+.Fn plan_tests
+and
+.Fn plan_no_plan
+will cause your test program to exit prematurely with a diagnostic
+message if they are called more than once.
+.Pp
+If your test program detects at run time that some required functionality
+is missing (for example, it relies on a database connection which is not
+present, or a particular configuration option that has not been included
+in the running kernel) use
+.Fn plan_skip_all ,
+passing as parameters a string to display indicating the reason for skipping
+the tests.
+.Ss SIMPLE TESTS
+.Bl -tag -width indent
+.It Xo
+.Ft unsigned int
+.Fn ok "expression" "char *" "..."
+.Xc
+.It Xo
+.Ft unsigned int
+.Fn ok1 "expression"
+.Xc
+.It Xo
+.Ft unsigned int
+.Fn pass "char *" "..."
+.Xc
+.It Xo
+.Ft unsigned int
+.Fn fail "char *" "..."
+.Xc
+.El
+.Pp
+Tests are implemented as expressions checked by calls to the
+.Fn ok
+and
+.Fn ok1
+macros.  In both cases
+.Fa expression
+should evaluate to true if the test succeeded.
+.Pp
+.Fn ok
+allows you to specify a name, or comment, describing the test which will
+be included in the output.
+.Fn ok1
+is for those times when the expression to be tested is self
+explanatory and does not need an associated comment.  In those cases
+the test expression becomes the comment.
+.Pp
+These four calls are equivalent:
+.Bd -literal -offset indent
+int i = 5;
+
+ok(i == 5, "i equals 5");      /* Overly verbose */
+ok(i == 5, "i equals %d", i);  /* Just to demonstrate printf-like
+                                  behaviour of the test name */
+ok(i == 5, "i == 5");          /* Needless repetition */
+ok1(i == 5);                   /* Just right */
+.Ed
+.Pp
+It is good practice to ensure that the test name describes the meaning
+behind the test rather than what you are testing.  Viz
+.Bd -literal -offset indent
+ok(db != NULL, "db is not NULL");            /* Not bad, but */
+ok(db != NULL, "Database conn. succeeded");  /* this is better */
+.Ed
+.Pp
+.Fn ok
+and
+.Fn ok1
+return 1 if the expression evaluated to true, and 0 if it evaluated to
+false.  This lets you chain calls from
+.Fn ok
+to
+.Fn diag
+to only produce diagnostic output if the test failed.  For example, this
+code will include diagnostic information about why the database connection
+failed, but only if the test failed.
+.Bd -literal -offset indent
+ok(db != NULL, "Database conn. succeeded") ||
+    diag("Database error code: %d", dberrno);
+.Ed
+.Pp
+You also have
+.Fn pass
+and
+.Fn fail .
+From the Test::More documentation:
+.Bd -literal -offset indent
+Sometimes you just want to say that the tests have passed.
+Usually the case is you've got some complicated condition
+that is difficult to wedge into an ok().  In this case,
+you can simply use pass() (to declare the test ok) or fail
+(for not ok).
+
+Use these very, very, very sparingly.
+.Ed
+.Pp
+These are synonyms for ok(1, ...) and ok(0, ...).
+.Ss SKIPPING TESTS
+.Bl -tag -width indent
+.It Xo
+.Ft int
+.Fn skip "unsigned int" "char *" "..."
+.Xc
+.It Xo
+.Fn skip_start "expression" "unsigned int" "char *" "..."
+.Xc
+.It Xo
+.Fn skip_end
+.Xc
+.El
+.Pp
+Sets of tests can be skipped.  Ordinarily you would do this because
+the test can't be run in this particular testing environment.
+.Pp
+For example, suppose some tests should be run as root.  If the test is
+not being run as root then the tests should be skipped.  In this 
+implementation, skipped tests are flagged as being ok, with a special
+message indicating that they were skipped.  It is your responsibility
+to ensure that the number of tests skipped (the first parameter to
+.Fn skip )
+is correct for the number of tests to skip.
+.Pp
+One way of implementing this is with a
+.Dq do { } while(0);
+loop, or an
+.Dq if( ) { } else { }
+construct, to ensure that there are no additional side effects from the
+skipped tests.
+.Bd -literal -offset indent
+if(getuid() != 0) {
+        skip(1, "because test only works as root");
+} else {
+        ok(do_something_as_root() == 0, "Did something as root");
+}
+.Ed
+.Pp
+Two macros are provided to assist with this.  The previous example could
+be re-written as follows.
+.Bd -literal -offset indent
+skip_start(getuid() != 0, 1, "because test only works as root");
+
+ok(do_something_as_root() == 0, "Did something as root");
+
+skip_end();
+.Ed
+.Ss MARKING TESTS AS Dq TODO
+.Bl -tag -width indent
+.It Xo
+.Ft void
+.Fn todo_start "char *" "..."
+.Xc
+.It Xo
+.Ft void
+.Fn todo_end "void"
+.Xc
+.El
+.Pp
+Sets of tests can be flagged as being
+.Dq TODO .
+These are tests that you expect to fail, probably because you haven't
+fixed a bug, or finished a new feature yet.  These tests will still be
+run, but with additional output that indicates that they are expected
+to fail.  Should a test start to succeed unexpectedly, tools like
+.Xr prove 1
+will indicate this, and you can move the test out of the todo
+block.  This is much more useful than simply commenting out (or
+.Dq #ifdef 0 ... #endif )
+the tests.
+.Bd -literal -offset indent
+todo_start("dwim() not returning true yet");
+
+ok(dwim(), "Did what the user wanted");
+
+todo_end();
+.Ed
+.Pp
+Should
+.Fn dwim
+ever start succeeding you will know about it as soon as you run the
+tests.  Note that
+.Em unlike
+the
+.Fn skip_*
+family, additional code between
+.Fn todo_start
+and
+.Fn todo_end
+.Em is
+executed.
+.Ss SKIP vs. TODO
+From the Test::More documentation;
+.Bd -literal -offset indent
+If it's something the user might not be able to do, use SKIP.
+This includes optional modules that aren't installed, running
+under an OS that doesn't have some feature (like fork() or
+symlinks), or maybe you need an Internet connection and one
+isn't available.
+
+If it's something the programmer hasn't done yet, use TODO.
+This is for any code you haven't written yet, or bugs you have
+yet to fix, but want to put tests in your testing script 
+(always a good idea).
+.Ed
+.Ss DIAGNOSTIC OUTPUT
+.Bl -tag -width indent
+.It Xo
+.Fr unsigned int
+.Fn diag "char *" "..."
+.Xc
+.El
+.Pp
+If your tests need to produce diagnostic output, use
+.Fn diag .
+It ensures that the output will not be considered by the TAP test harness.
+.Fn diag
+adds the necessary trailing
+.Dq \en
+for you.
+.Bd -literal -offset indent
+diag("Expected return code 0, got return code %d", rcode);
+.Ed
+.Pp
+.Fn diag
+always returns 0.
+.Ss EXIT STATUS
+.Bl -tag -width indent
+.It Xo
+.Fr int
+.Fn exit_status void
+.Xc
+.El
+.Pp
+For maximum compatability your test program should return a particular
+exit code.  This is calculated by
+.Fn exit_status
+so it is sufficient to always return from
+.Fn main
+with either
+.Dq return exit_status();
+or
+.Dq exit(exit_status());
+as appropriate.
+.Sh ENVIRONMENT
+The following environment variables affect
+.Nm .
+.Bl -tag -width indent
+.It Ev HARNESS_ACTIVE
+Causes an extra
+.Dq \en
+to be printed before any diagnostic failure output generated by
+.Nm .
+This variable is normally set if tests are being run under Perl's
+Test::Harness.
+.El
+.Sh EXAMPLES
+The
+.Pa tests
+directory in the source distribution contains numerous tests of
+.Nm
+functionality, written using
+.Nm .
+Examine them for examples of how to construct test suites.
+.Sh COMPATABILITY
+.Nm
+strives to be compatible with the Perl Test::More and Test::Harness 
+modules.  The test suite verifies that
+.Nm
+is bug-for-bug compatible with their behaviour.  This is why some
+functions which would more naturally return nothing return constant
+values.
+.Pp
+If the
+.Lb libpthread
+is found at compile time,
+.Nm
+.Em should
+be thread safe.  Indications to the contrary (and test cases that expose
+incorrect behaviour) are very welcome.
+.Sh SEE ALSO
+.Xr Test::More 1 ,
+.Xr Test::Harness 1 ,
+.Xr prove 1
+.Sh STANDARDS
+.Nm
+requires a
+.St -isoC-99
+compiler.  Some of the
+.Nm
+functionality is implemented as variadic macros, and that functionality
+was not formally codified until C99.  Patches to use
+.Nm
+with earlier compilers that have their own implementation of variadic
+macros will be gratefully received.
+.Sh HISTORY
+.Nm
+was written to help improve the quality and coverage of the FreeBSD
+regression test suite, and released in the hope that others find it
+a useful tool to help improve the quality of their code.
+.Sh AUTHORS
+.An "Nik Clayton" Aq nik@ngo.org.uk ,
+.Aq nik@FreeBSD.org
+.Pp
+.Nm
+would not exist without the efforts of
+.An "Michael G Schwern" Aq schqern@pobox.com ,
+.An "Andy Lester" Aq andy@petdance.com ,
+and the countless others who have worked on the Perl QA programme.
+.Sh BUGS
+Ideally, running the tests would have no side effects on the behaviour
+of the application you are testing.  However, it is not always possible
+to avoid them.  The following side effects of using
+.Nm
+are known.
+.Bl -bullet -offset indent
+.It
+stdout is set to unbuffered mode after calling any of the
+.Fn plan_*
+functions.
+.El
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/src/tap.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/src/tap.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/src/tap.c	(revision 22157)
@@ -0,0 +1,430 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "tap.h"
+
+static int no_plan = 0;
+static int skip_all = 0;
+static int have_plan = 0;
+static unsigned int test_count = 0; /* Number of tests that have been run */
+static unsigned int e_tests = 0; /* Expected number of tests to run */
+static unsigned int failures = 0; /* Number of tests that failed */
+static char *todo_msg = NULL;
+static char *todo_msg_fixed = "libtap malloc issue";
+static int todo = 0;
+static int test_died = 0;
+
+/* Encapsulate the pthread code in a conditional.  In the absence of
+   libpthread the code does nothing */
+#ifdef HAVE_LIBPTHREAD
+#include <pthread.h>
+static pthread_mutex_t M = PTHREAD_MUTEX_INITIALIZER;
+# define LOCK pthread_mutex_lock(&M);
+# define UNLOCK pthread_mutex_unlock(&M);
+#else
+# define LOCK
+# define UNLOCK
+#endif
+
+static void _expected_tests(unsigned int);
+static void _tap_init(void);
+static void _cleanup(void);
+
+/*
+ * Generate a test result.
+ *
+ * ok -- boolean, indicates whether or not the test passed.
+ * test_name -- the name of the test, may be NULL
+ * test_comment -- a comment to print afterwards, may be NULL
+ */
+unsigned int
+_gen_result(int ok, const char *func, char *file, unsigned int line,
+            char *test_name, ...)
+{
+    va_list ap;
+    char *local_test_name = NULL;
+    char *c;
+    int name_is_digits;
+
+    LOCK;
+
+    test_count++;
+
+    /* Start by taking the test name and performing any printf()
+       expansions on it */
+    if(test_name != NULL) {
+        va_start(ap, test_name);
+        vasprintf(&local_test_name, test_name, ap);
+        va_end(ap);
+
+        /* Make sure the test name contains more than digits
+           and spaces.  Emit an error message and exit if it
+           does */
+        if(local_test_name) {
+            name_is_digits = 1;
+            for(c = local_test_name; *c != '\0'; c++) {
+                if(!isdigit(*c) && !isspace(*c)) {
+                    name_is_digits = 0;
+                    break;
+                }
+            }
+
+            if(name_is_digits) {
+                diag("    You named your test '%s'.  You shouldn't use numbers for your test names.", local_test_name);
+                diag("    Very confusing.");
+            }
+        }
+    }
+
+    if(!ok) {
+        printf("not ");
+        failures++;
+    }
+
+    printf("ok %d", test_count);
+
+    if(test_name != NULL) {
+        printf(" - ");
+
+        /* Print the test name, escaping any '#' characters it
+           might contain */
+        if(local_test_name != NULL) {
+            flockfile(stdout);
+            for(c = local_test_name; *c != '\0'; c++) {
+                if(*c == '#')
+                    fputc('\\', stdout);
+                fputc((int)*c, stdout);
+            }
+            funlockfile(stdout);
+        } else { /* vasprintf() failed, use a fixed message */
+            printf("%s", todo_msg_fixed);
+        }
+    }
+
+    /* If we're in a todo_start() block then flag the test as being
+       TODO.  todo_msg should contain the message to print at this
+       point.  If it's NULL then asprintf() failed, and we should
+       use the fixed message.
+
+       This is not counted as a failure, so decrement the counter if
+       the test failed. */
+    if(todo) {
+        printf(" # TODO %s", todo_msg ? todo_msg : todo_msg_fixed);
+        if(!ok)
+            failures--;
+    }
+
+    printf("\n");
+
+    if(!ok) {
+        if(getenv("HARNESS_ACTIVE") != NULL)
+            fputs("\n", stderr);
+
+        diag("    Failed %stest (%s:%s() at line %d)",
+             todo ? "(TODO) " : "", file, func, line);
+    }
+    free(local_test_name);
+
+    UNLOCK;
+
+    /* We only care (when testing) that ok is positive, but here we
+       specifically only want to return 1 or 0 */
+    return ok ? 1 : 0;
+}
+
+/*
+ * Initialise the TAP library.  Will only do so once, however many times it's
+ * called.
+ */
+void
+_tap_init(void)
+{
+    static int run_once = 0;
+
+    if(!run_once) {
+        atexit(_cleanup);
+
+        /* stdout needs to be unbuffered so that the output appears
+           in the same place relative to stderr output as it does 
+           with Test::Harness */
+        setbuf(stdout, 0);
+        run_once = 1;
+    }
+}
+
+/*
+ * Note that there's no plan.
+ */
+int
+plan_no_plan(void)
+{
+
+    LOCK;
+
+    _tap_init();
+
+    if(have_plan != 0) {
+        fprintf(stderr, "You tried to plan twice!\n");
+        test_died = 1;
+        UNLOCK;
+        exit(255);
+    }
+
+    have_plan = 1;
+    no_plan = 1;
+
+    UNLOCK;
+
+    return 1;
+}
+
+/*
+ * Note that the plan is to skip all tests
+ */
+int
+plan_skip_all(char *reason)
+{
+
+    LOCK;
+
+    _tap_init();
+
+    skip_all = 1;
+
+    printf("1..0");
+
+    if(reason != NULL)
+        printf(" # Skip %s", reason);
+
+    printf("\n");
+
+    UNLOCK;
+
+    exit(0);
+}
+
+/*
+ * Note the number of tests that will be run.
+ */
+int
+plan_tests(unsigned int tests)
+{
+
+    LOCK;
+
+    _tap_init();
+
+    if(have_plan != 0) {
+        fprintf(stderr, "You tried to plan twice!\n");
+        test_died = 1;
+        UNLOCK;
+        exit(255);
+    }
+
+    if(tests == 0) {
+        fprintf(stderr, "You said to run 0 tests!  You've got to run something.\n");
+        test_died = 1;
+        UNLOCK;
+        exit(255);
+    }
+
+    have_plan = 1;
+
+    _expected_tests(tests);
+
+    UNLOCK;
+
+    return e_tests;
+}
+
+unsigned int
+diag(char *fmt, ...)
+{
+    va_list ap;
+
+    fputs("# ", stderr);
+
+    va_start(ap, fmt);
+    vfprintf(stderr, fmt, ap);
+    va_end(ap);
+
+    fputs("\n", stderr);
+
+    return 0;
+}
+
+void
+_expected_tests(unsigned int tests)
+{
+
+    printf("1..%d\n", tests);
+    e_tests = tests;
+}
+
+int
+skip(unsigned int n, char *fmt, ...)
+{
+    va_list ap;
+    char *skip_msg;
+
+    LOCK;
+
+    va_start(ap, fmt);
+    asprintf(&skip_msg, fmt, ap);
+    va_end(ap);
+
+    while(n-- > 0) {
+        test_count++;
+        printf("ok %d # skip %s\n", test_count,
+               skip_msg != NULL ?
+               skip_msg : "libtap():malloc() failed");
+    }
+
+    free(skip_msg);
+
+    UNLOCK;
+
+    return 1;
+}
+
+void
+todo_start(char *fmt, ...)
+{
+    va_list ap;
+
+    LOCK;
+
+    va_start(ap, fmt);
+    vasprintf(&todo_msg, fmt, ap);
+    va_end(ap);
+
+    todo = 1;
+
+    UNLOCK;
+}
+
+void
+todo_end(void)
+{
+
+    LOCK;
+
+    todo = 0;
+    free(todo_msg);
+
+    UNLOCK;
+}
+
+int
+exit_status(void)
+{
+    int r;
+
+    LOCK;
+
+    /* If there's no plan, just return the number of failures */
+    if(no_plan || !have_plan) {
+        UNLOCK;
+        return failures;
+    }
+
+    /* Ran too many tests?  Return the number of tests that were run
+       that shouldn't have been */
+    if(e_tests < test_count) {
+        r = test_count - e_tests;
+        UNLOCK;
+        return r;
+    }
+
+    /* Return the number of tests that failed + the number of tests
+       that weren't run */
+    r = failures + e_tests - test_count;
+    UNLOCK;
+
+    return r;
+}
+
+/*
+ * Cleanup at the end of the run, produce any final output that might be
+ * required.
+ */
+void
+_cleanup(void)
+{
+
+    LOCK;
+
+    /* If plan_no_plan() wasn't called, and we don't have a plan,
+       and we're not skipping everything, then something happened
+       before we could produce any output */
+    if(!no_plan && !have_plan && !skip_all) {
+        diag("Looks like your test died before it could output anything.");
+        UNLOCK;
+        return;
+    }
+
+    if(test_died) {
+        diag("Looks like your test died just after %d.", test_count);
+        UNLOCK;
+        return;
+    }
+
+
+    /* No plan provided, but now we know how many tests were run, and can
+       print the header at the end */
+    if(!skip_all && (no_plan || !have_plan)) {
+        printf("1..%d\n", test_count);
+    }
+
+    if((have_plan && !no_plan) && e_tests < test_count) {
+        diag("Looks like you planned %d %s but ran %d extra.",
+             e_tests, e_tests == 1 ? "test" : "tests", test_count - e_tests);
+        UNLOCK;
+        return;
+    }
+
+    if((have_plan || !no_plan) && e_tests > test_count) {
+        diag("Looks like you planned %d %s but only ran %d.",
+             e_tests, e_tests == 1 ? "test" : "tests", test_count);
+        UNLOCK;
+        return;
+    }
+
+    if(failures)
+        diag("Looks like you failed %d %s of %d.",
+             failures, failures == 1 ? "test" : "tests", test_count);
+
+    UNLOCK;
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/src/tap.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/src/tap.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/src/tap.h	(revision 22157)
@@ -0,0 +1,89 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* '## __VA_ARGS__' is a gcc'ism. C99 doesn't allow the token pasting
+   and requires the caller to add the final comma if they've ommitted
+   the optional arguments */
+#ifdef __GNUC__
+# define ok(e, test, ...) ((e) ?     \
+                           _gen_result(1, __func__, __FILE__, __LINE__, \
+                                       test, ## __VA_ARGS__) :  \
+                           _gen_result(0, __func__, __FILE__, __LINE__, \
+                                       test, ## __VA_ARGS__))
+
+# define ok1(e) ((e) ?       \
+                 _gen_result(1, __func__, __FILE__, __LINE__, "%s", #e) : \
+                 _gen_result(0, __func__, __FILE__, __LINE__, "%s", #e))
+
+# define pass(test, ...) ok(1, test, ## __VA_ARGS__);
+# define fail(test, ...) ok(0, test, ## __VA_ARGS__);
+
+# define skip_start(test, n, fmt, ...)   \
+do {      \
+    if((test)) {    \
+        skip(n, fmt, ## __VA_ARGS__); \
+        continue;   \
+    }
+    #elif __STDC_VERSION__ >= 199901L /* __GNUC__ */
+    # define ok(e, ...) ((e) ?      \
+                         _gen_result(1, __func__, __FILE__, __LINE__, \
+                                     __VA_ARGS__) :    \
+                         _gen_result(0, __func__, __FILE__, __LINE__, \
+                                     __VA_ARGS__))
+
+    # define ok1(e) ((e) ?       \
+                     _gen_result(1, __func__, __FILE__, __LINE__, "%s", #e) : \
+                     _gen_result(0, __func__, __FILE__, __LINE__, "%s", #e))
+
+    # define pass(...) ok(1, __VA_ARGS__);
+    # define fail(...) ok(0, __VA_ARGS__);
+
+    # define skip_start(test, n, ...)   \
+    do {      \
+        if((test)) {    \
+            skip(n,  __VA_ARGS__);  \
+            continue;   \
+        }
+        #else /* __STDC_VERSION__ */
+        # error "Needs gcc or C99 compiler for variadic macros."
+        #endif /* __STDC_VERSION__ */
+
+        #define skip_end() } while(0);
+
+    unsigned int _gen_result(int, const char *, char *, unsigned int, char *, ...);
+
+    int plan_no_plan(void);
+    int plan_skip_all(char *);
+    int plan_tests(unsigned int);
+
+    unsigned int diag(char *, ...);
+
+    int skip(unsigned int, char *, ...);
+
+    void todo_start(char *, ...);
+    void todo_end(void);
+
+    int exit_status(void);
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/.cvsignore	(revision 22157)
@@ -0,0 +1,6 @@
+Makefile
+Makefile.in
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/Makefile.am	(revision 22157)
@@ -0,0 +1,7 @@
+SUBDIRS=	diag
+SUBDIRS+=	fail
+SUBDIRS+=	ok
+SUBDIRS+=	pass
+SUBDIRS+=	plan
+SUBDIRS+=	skip
+SUBDIRS+=	todo
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/README
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/README	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/README	(revision 22157)
@@ -0,0 +1,12 @@
+Most of the tests follow the same pattern.
+
+ * test.pl that uses Test::More, and demonstrates whatever functionality 
+   that we're trying to test.  This is the reference code.
+
+ * test.c, which tests the libtap reimplementation of the same functionality.
+
+ * test.t, which compiles the .c program, runs both test scripts, and then 
+   diffs their output to make sure it's identical.
+
+   Right now, test.t is identical in every directory.  This sucks somewhat.
+   It should either be a symlink to a common script
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/diag/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/diag/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/diag/.cvsignore	(revision 22157)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/diag/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/diag/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/diag/Makefile.am	(revision 22157)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/diag/test.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/diag/test.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/diag/test.c	(revision 22157)
@@ -0,0 +1,46 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+
+    plan_tests(2);
+
+    rc = diag("A diagnostic message");
+    diag("Returned: %d", rc);
+
+    /* Make sure the failure is passed through */
+    ok(1, "test 1") || diag("ok() failed, and shouldn't");
+    ok(0, "test 2") || diag("ok() passed, and shouldn't");
+
+    return exit_status();
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/diag/test.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/diag/test.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/diag/test.pl	(revision 22157)
@@ -0,0 +1,16 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+plan tests => 2;
+
+$rc = diag("A diagnostic message");
+diag("Returned: $rc");
+
+ok(1, 'test 1') or diag "ok() failed, and shouldn't";
+ok(0, 'test 2') or diag "ok() passed, and shouldn't";
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/diag/test.t
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/diag/test.t	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/diag/test.t	(revision 22157)
@@ -0,0 +1,67 @@
+#!/bin/sh
+
+echo '1..7'
+
+perl $srcdir/test.pl 2>/dev/null >test.pl.out
+perlstatus=$?
+
+./test > test.c.out 2>&1
+cstatus=$?
+
+if grep "^# A diagnostic message$" test.c.out > /dev/null ; then
+    echo "ok 1 - found a diagnostic message"
+else
+    echo "not ok 1 - found a diagnostic message"
+    retval=1
+fi
+
+if grep "^# Returned: 0$" test.c.out > /dev/null ; then
+    echo "ok 2 - diag() expected return value" 
+else
+    echo "not ok 2 - diag() expected return value" 
+    retval=1
+fi
+
+if grep "^#     Failed test (.*test.c:main() at line 43)$" test.c.out > /dev/null ; then
+    echo "ok 3 - 'failed test at line' diag" 
+else
+    echo "not ok 3 - 'failed test at line' diag" 
+    retval=1
+fi
+
+if grep "^# ok() passed, and shouldn't$" test.c.out > /dev/null ; then
+    echo "ok 4 - expected diag"
+else
+    echo "ok 4 - expected diag"
+    retval=1
+fi
+
+if grep "^# Looks like you failed 1 test of 2.$" test.c.out > /dev/null ; then
+    echo "ok 5 - failed 1 test"
+ else
+    echo "ok 5 - failed 1 test"
+    retval=1
+fi
+
+sed -e '/^#/D' test.c.out > tmp
+mv tmp test.c.out
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 6 - TAP output is identical'
+else
+	retval=1
+	echo 'not ok 6 - TAP output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 7 - status code'
+else
+	retval=1
+	echo 'not ok 7 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/fail/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/fail/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/fail/.cvsignore	(revision 22157)
@@ -0,0 +1,15 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/fail/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/fail/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/fail/Makefile.am	(revision 22157)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/fail/test.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/fail/test.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/fail/test.c	(revision 22157)
@@ -0,0 +1,46 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+
+    rc = plan_tests(2);
+    diag("Returned: %d", rc);
+
+    rc = fail("test to fail");
+    diag("Returned: %d", rc);
+
+    rc = fail("test to fail %s", "with extra string");
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/fail/test.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/fail/test.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/fail/test.pl	(revision 22157)
@@ -0,0 +1,17 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 2;
+diag("Returned: " . sprintf('%d', $rc));
+
+$rc = fail('test to fail');
+diag("Returned: $rc");
+
+$rc = fail('test to fail with extra string');
+diag("Returned: $rc");
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/fail/test.t
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/fail/test.t	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/fail/test.t	(revision 22157)
@@ -0,0 +1,74 @@
+#!/bin/sh
+
+echo '1..8'
+
+perl $srcdir/test.pl 2>/dev/null > test.pl.out
+perlstatus=$?
+
+./test > test.c.out 2>&1 
+cstatus=$?
+
+if grep "^# Returned: 2$" test.c.out >/dev/null ; then
+  echo "ok 1 - expected return value";
+else
+  echo "not ok 1 - expected return value";
+  retval=1
+fi
+
+if grep "^#     Failed test (.*test.c:main() at line 39)$" test.c.out >/dev/null ; then
+  echo "ok 2 - failed expected test";
+else
+  echo "not ok 2 - failed expected test";
+  retval=1
+fi
+
+if grep "^# Returned: 0$" test.c.out >/dev/null ; then
+  echo "ok 3 - expected return value";
+else
+  echo "not ok 3 - expected return value";
+  retval=1
+fi
+
+if grep "^#     Failed test (.*test.c:main() at line 42)$" test.c.out >/dev/null ; then
+  echo "ok 4 - failed expected test";
+else
+  echo "not ok 4 - failed expected test";
+  retval=1
+fi
+  
+if grep "^# Returned: 0$" test.c.out >/dev/null ; then
+  echo "ok 5 - expected return value";
+else
+  echo "not ok 5 - expected return value";
+  retval=1
+fi
+
+if grep "^# Looks like you failed 2 tests of 2.$" test.c.out >/dev/null ; then
+  echo "ok 6 - expected return value";
+else
+  echo "not ok 6 - expected return value";
+  retval=1
+fi
+
+sed -e '/^#/D' test.c.out > tmp
+mv tmp test.c.out
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 7 - output is identical'
+else
+	retval=1
+	echo 'not ok 7 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 8 - status code'
+else
+	retval=1
+	echo 'not ok 8 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/.cvsignore	(revision 22157)
@@ -0,0 +1,6 @@
+Makefile
+Makefile.in
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/Makefile.am	(revision 22157)
@@ -0,0 +1,3 @@
+SUBDIRS  =	ok
+SUBDIRS +=	ok-hash
+SUBDIRS +=	ok-numeric
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-hash/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-hash/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-hash/.cvsignore	(revision 22157)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-hash/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-hash/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-hash/Makefile.am	(revision 22157)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-hash/test.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-hash/test.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-hash/test.c	(revision 22157)
@@ -0,0 +1,52 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+
+    rc = plan_tests(4);
+    diag("Returned: %d", rc);
+
+    rc = ok(1, "Test with no hash");
+    diag("Returned: %d", rc);
+
+    rc = ok(1, "Test with one # hash");
+    diag("Returned: %d", rc);
+
+    rc = ok(1, "Test with # two # hashes");
+    diag("Returned: %d", rc);
+
+    rc = ok(1, "Test with ## back to back hashes");
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-hash/test.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-hash/test.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-hash/test.pl	(revision 22157)
@@ -0,0 +1,24 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 4;
+diag("Returned: " . sprintf("%d", $rc));
+
+
+$rc = ok(1, 'Test with no hash');
+diag("Returned: $rc");
+
+$rc = ok(1, 'Test with one # hash');
+diag("Returned: $rc");
+
+$rc = ok(1, 'Test with # two # hashes');
+diag("Returned: $rc");
+
+$rc = ok(1, 'Test with ## back to back hashes');
+diag("Returned: $rc");
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-hash/test.t
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-hash/test.t	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-hash/test.t	(revision 22157)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-numeric/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-numeric/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-numeric/.cvsignore	(revision 22157)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-numeric/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-numeric/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-numeric/Makefile.am	(revision 22157)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-numeric/test.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-numeric/test.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-numeric/test.c	(revision 22157)
@@ -0,0 +1,49 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+
+    rc = plan_tests(3);
+    diag("Returned: %d", rc);
+
+    rc = ok(1, "First test");
+    diag("Returned: %d", rc);
+
+    rc = ok(1, "1");
+    diag("Returned: %d", rc);
+
+    rc = ok(1, "Third test");
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-numeric/test.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-numeric/test.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-numeric/test.pl	(revision 22157)
@@ -0,0 +1,21 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 3;
+diag("Returned: " . sprintf("%d", $rc));
+
+
+$rc = ok(1, 'First test');
+diag("Returned: $rc");
+
+$rc = ok(1, '1');
+diag("Returned: $rc");
+
+$rc = ok(1, 'Third test');
+diag("Returned: $rc");
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-numeric/test.t
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-numeric/test.t	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok-numeric/test.t	(revision 22157)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok/.cvsignore	(revision 22157)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok/Makefile.am	(revision 22157)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok/test.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok/test.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok/test.c	(revision 22157)
@@ -0,0 +1,55 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+
+    rc = plan_tests(5);
+    diag("Returned: %d", rc);
+
+    rc = ok(1 == 1, "1 equals 1");
+    diag("Returned: %d", rc);
+
+    rc = ok(1 == 1, "1 equals %d", 1);
+    diag("Returned: %d", rc);
+
+    rc = ok1(1 == 1);
+    diag("Returned: %d", rc);
+
+    rc = ok(1 == 2, "1 equals 2");
+    diag("Returned: %d", rc);
+
+    rc = ok1(1 == 2);
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok/test.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok/test.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok/test.pl	(revision 22157)
@@ -0,0 +1,27 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 5;
+diag("Returned: " . sprintf("%d", $rc));
+
+
+$rc = ok(1 == 1, '1 equals 1');	# Test ok() passes when it should
+diag("Returned: $rc");
+
+$rc = ok(1 == 1, '1 equals 1'); # Used for %d testing in test.c
+diag("Returned: $rc");
+
+$rc = ok(1 == 1, '1 == 1');	# Test ok1() passes when it should
+diag("Returned: $rc");
+
+$rc = ok(1 == 2, '1 equals 2');	# Test ok() fails when it should
+diag("Returned: $rc");
+
+$rc = ok(1 == 2, '1 == 2');	# Test ok1() fails when it should
+diag("Returned: $rc");
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok/test.t
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok/test.t	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/ok/ok/test.t	(revision 22157)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/pass/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/pass/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/pass/.cvsignore	(revision 22157)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/pass/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/pass/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/pass/Makefile.am	(revision 22157)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/pass/test.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/pass/test.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/pass/test.c	(revision 22157)
@@ -0,0 +1,46 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+
+    rc = plan_tests(2);
+    diag("Returned: %d", rc);
+
+    rc = pass("test to pass");
+    diag("Returned: %d", rc);
+
+    rc = pass("test to pass %s", "with extra string");
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/pass/test.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/pass/test.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/pass/test.pl	(revision 22157)
@@ -0,0 +1,17 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 2;
+diag("Returned: " . sprintf('%d', $rc));
+
+$rc = pass('test to pass');
+diag("Returned: $rc");
+
+$rc = pass('test to pass with extra string');
+diag("Returned: $rc");
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/pass/test.t
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/pass/test.t	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/pass/test.t	(revision 22157)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/.cvsignore	(revision 22157)
@@ -0,0 +1,6 @@
+Makefile
+Makefile.in
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/Makefile.am	(revision 22157)
@@ -0,0 +1,7 @@
+SUBDIRS  =	no-tests
+SUBDIRS +=	no_plan
+SUBDIRS +=	not-enough-tests
+SUBDIRS +=	too-many-plans
+SUBDIRS +=	too-many-tests
+SUBDIRS +=	sane
+SUBDIRS +=	skip_all
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no-tests/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no-tests/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no-tests/.cvsignore	(revision 22157)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no-tests/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no-tests/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no-tests/Makefile.am	(revision 22157)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no-tests/test.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no-tests/test.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no-tests/test.c	(revision 22157)
@@ -0,0 +1,43 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+
+    rc = plan_tests(0);
+    diag("Returned: %d", rc);
+
+    rc = ok(1, NULL);
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no-tests/test.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no-tests/test.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no-tests/test.pl	(revision 22157)
@@ -0,0 +1,14 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 0;
+diag("Returned: " . sprintf("%d", $rc));
+
+$rc = ok(1);
+diag("Returned: $rc");
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no-tests/test.t
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no-tests/test.t	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no-tests/test.t	(revision 22157)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no_plan/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no_plan/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no_plan/.cvsignore	(revision 22157)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no_plan/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no_plan/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no_plan/Makefile.am	(revision 22157)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no_plan/test.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no_plan/test.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no_plan/test.c	(revision 22157)
@@ -0,0 +1,43 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+
+    rc = plan_no_plan();
+    diag("Returned: %d", rc);
+
+    rc = ok(1, NULL);
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no_plan/test.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no_plan/test.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no_plan/test.pl	(revision 22157)
@@ -0,0 +1,14 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+my $rc = 0;
+
+use Test::More;
+
+$rc = plan qw(no_plan);
+diag("Returned: " . sprintf("%d", $rc));
+
+$rc = ok(1);
+diag("Returned: $rc");
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no_plan/test.t
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no_plan/test.t	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/no_plan/test.t	(revision 22157)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/not-enough-tests/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/not-enough-tests/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/not-enough-tests/.cvsignore	(revision 22157)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/not-enough-tests/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/not-enough-tests/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/not-enough-tests/Makefile.am	(revision 22157)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/not-enough-tests/test.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/not-enough-tests/test.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/not-enough-tests/test.c	(revision 22157)
@@ -0,0 +1,49 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+
+    rc = plan_tests(1);
+    diag("Returned: %d", rc);
+
+    rc = ok(1, NULL);
+    diag("Returned: %d", rc);
+
+    rc = ok(1, NULL);
+    diag("Returned: %d", rc);
+
+    rc = ok(1, NULL);
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/not-enough-tests/test.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/not-enough-tests/test.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/not-enough-tests/test.pl	(revision 22157)
@@ -0,0 +1,20 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 1;
+diag("Returned: " . sprintf("%d", $rc));
+
+$rc = ok(1);
+diag("Returned: $rc");
+
+$rc = ok(1);
+diag("Returned: $rc");
+
+$rc = ok(1);
+diag("Returned: $rc");
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/not-enough-tests/test.t
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/not-enough-tests/test.t	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/not-enough-tests/test.t	(revision 22157)
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	# comment this out until we're exit-code compatible with Test::More
+	#retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/sane/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/sane/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/sane/.cvsignore	(revision 22157)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/sane/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/sane/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/sane/Makefile.am	(revision 22157)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/sane/test.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/sane/test.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/sane/test.c	(revision 22157)
@@ -0,0 +1,43 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+
+    rc = plan_tests(1);
+    diag("Returned: %d", rc);
+
+    rc = ok(1, NULL);
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/sane/test.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/sane/test.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/sane/test.pl	(revision 22157)
@@ -0,0 +1,14 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 1;
+diag("Returned: " . sprintf("%d", $rc));
+
+$rc = ok(1);
+diag("Returned: $rc");
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/sane/test.t
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/sane/test.t	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/sane/test.t	(revision 22157)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/skip_all/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/skip_all/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/skip_all/.cvsignore	(revision 22157)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/skip_all/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/skip_all/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/skip_all/Makefile.am	(revision 22157)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/skip_all/plan.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/skip_all/plan.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/skip_all/plan.c	(revision 22157)
@@ -0,0 +1,39 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "tap.h"
+
+/* Run pre-defined tests on the test library to make sure that the basic
+   functionality works, and it can be used to test itself afterwards */
+
+int
+main(int argc, char *argv[])
+{
+    plan_skip_all("No good reason");
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/skip_all/test.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/skip_all/test.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/skip_all/test.c	(revision 22157)
@@ -0,0 +1,38 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+
+    rc = plan_skip_all("No good reason");
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/skip_all/test.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/skip_all/test.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/skip_all/test.pl	(revision 22157)
@@ -0,0 +1,11 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan skip_all => "No good reason";
+diag("Returned: " . sprintf("%d", $rc));
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/skip_all/test.t
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/skip_all/test.t	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/skip_all/test.t	(revision 22157)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-plans/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-plans/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-plans/.cvsignore	(revision 22157)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-plans/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-plans/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-plans/Makefile.am	(revision 22157)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-plans/test.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-plans/test.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-plans/test.c	(revision 22157)
@@ -0,0 +1,49 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+
+    rc = plan_tests(1);
+    diag("Returned: %d", rc);
+
+    rc = ok(1, NULL);
+    diag("Returned: %d", rc);
+
+    rc = plan_tests(1);
+    diag("Returned: %d", rc);
+
+    rc = ok(0, NULL);
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-plans/test.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-plans/test.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-plans/test.pl	(revision 22157)
@@ -0,0 +1,20 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 1;
+diag("Returned: " . sprintf("%d", $rc));
+
+$rc = ok(1);
+diag("Returned: $rc");
+
+$rc = plan tests => 1;
+diag("Returned: $rc");
+
+$rc = ok(0);
+diag("Returned: $rc");
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-plans/test.t
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-plans/test.t	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-plans/test.t	(revision 22157)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-tests/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-tests/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-tests/.cvsignore	(revision 22157)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-tests/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-tests/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-tests/Makefile.am	(revision 22157)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-tests/test.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-tests/test.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-tests/test.c	(revision 22157)
@@ -0,0 +1,46 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+
+    rc = plan_tests(5);
+    diag("Returned: %d", rc);
+
+    rc = ok(1, NULL);
+    diag("Returned: %d", rc);
+
+    rc = ok(0, NULL);
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-tests/test.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-tests/test.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-tests/test.pl	(revision 22157)
@@ -0,0 +1,17 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 5;
+diag("Returned: " . sprintf("%d", $rc));
+
+$rc = ok(1);
+diag("Returned: $rc");
+
+$rc = ok(0);
+diag("Returned: $rc");
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-tests/test.t
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-tests/test.t	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/plan/too-many-tests/test.t	(revision 22157)
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+    # we're not exit-status compatible with Test::More yet
+	#retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/skip/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/skip/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/skip/.cvsignore	(revision 22157)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/skip/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/skip/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/skip/Makefile.am	(revision 22157)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/skip/test.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/skip/test.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/skip/test.c	(revision 22157)
@@ -0,0 +1,69 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+    unsigned int side_effect = 0;
+
+    rc = plan_tests(4);
+    diag("Returned: %d", rc);
+
+    rc = ok(1 == 1, "1 equals 1"); /* Should always work */
+    diag("Returned: %d", rc);
+
+    do {
+        if(1) {
+            rc = skip(1, "Testing skipping");
+            continue;
+        }
+
+        side_effect++;
+
+        ok(side_effect == 1, "side_effect checked out");
+
+    } while(0);
+
+    diag("Returned: %d", rc);
+
+    skip_start(1 == 1, 1, "Testing skipping #2");
+
+    side_effect++;
+    rc = ok(side_effect == 1, "side_effect checked out");
+    diag("Returned: %d", rc);
+
+    skip_end();
+
+    rc = ok(side_effect == 0, "side_effect is %d", side_effect);
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/skip/test.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/skip/test.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/skip/test.pl	(revision 22157)
@@ -0,0 +1,40 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 4;
+diag("Returned: " . sprintf("%d", $rc));
+
+my $side_effect = 0;		# Check whether skipping has side effects
+
+$rc = ok(1 == 1, '1 equals 1');	# Test ok() passes when it should
+diag("Returned: $rc");
+
+# Start skipping
+SKIP: {
+	$rc = skip "Testing skipping", 1;
+
+	$side_effect++;
+
+	$rc = ok($side_effect == 1, '$side_effect checked out');
+}
+
+diag("Returned: $rc");
+
+SKIP: {
+	$rc = skip "Testing skipping #2", 1;
+	diag("Returned: $rc");
+
+	$side_effect++;
+
+	$rc = ok($side_effect == 1, '$side_effect checked out');
+	diag("Returned: $rc");
+}
+
+$rc = ok($side_effect == 0, "side_effect is $side_effect");
+diag("Returned: $rc");
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/skip/test.t
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/skip/test.t	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/skip/test.t	(revision 22157)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/todo/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/todo/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/todo/.cvsignore	(revision 22157)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/todo/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/todo/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/todo/Makefile.am	(revision 22157)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/todo/test.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/todo/test.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/todo/test.c	(revision 22157)
@@ -0,0 +1,68 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+    unsigned int side_effect = 0;
+
+    rc = plan_tests(5);
+    diag("Returned: %d", rc);
+
+    rc = ok(1 == 1, "1 equals 1"); /* Should always work */
+    diag("Returned: %d", rc);
+
+    todo_start("For testing purposes");
+
+    side_effect++;
+
+    /* This test should fail */
+    rc = ok(side_effect == 0, "side_effect checked out");
+    diag("Returned: %d", rc);
+
+    /* This test should unexpectedly succeed */
+    rc = ok(side_effect == 1, "side_effect checked out");
+    diag("Returned: %d", rc);
+
+    todo_end();
+
+    todo_start("Testing printf() %s in todo_start()", "expansion");
+
+    rc = ok(0, "dummy test");
+    diag("Returned: %d", rc);
+
+    todo_end();
+
+    rc = ok(side_effect == 1, "side_effect is %d", side_effect);
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/todo/test.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/todo/test.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/todo/test.pl	(revision 22157)
@@ -0,0 +1,41 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 5;
+diag("Returned: " . sprintf("%d", $rc));
+
+my $side_effect = 0;		# Check whether TODO has side effects
+
+$rc = ok(1 == 1, '1 equals 1');	# Test ok() passes when it should
+diag("Returned: $rc");
+
+# Start TODO tests
+TODO: {
+	local $TODO = 'For testing purposes';
+
+	$side_effect++;
+
+	# This test should fail
+	$rc = ok($side_effect == 0, 'side_effect checked out');
+	diag("Returned: $rc");
+
+	# This test should unexpectedly succeed
+	$rc = ok($side_effect == 1, 'side_effect checked out');
+	diag("Returned: $rc");
+}
+
+TODO: {
+	local $TODO = 'Testing printf() expansion in todo_start()';
+
+	$rc = ok(0, 'dummy test');
+	diag("Returned: $rc");
+}
+
+$rc = ok($side_effect == 1, "side_effect is $side_effect");
+diag("Returned: $rc");
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/todo/test.t
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/todo/test.t	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/tap/tests/todo/test.t	(revision 22157)
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+# Test:;More prints diagnostic from TODO tests on stdout
+# http://rt.cpan.org/Ticket/Display.html?id=14982
+sed '/^#/D' test.pl.out > tmp
+mv tmp test.pl.out
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /branches/eam_branches/eam_branch_20080706/psModules/test/test.pl
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psModules/test/test.pl	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psModules/test/test.pl	(revision 22157)
@@ -0,0 +1,31 @@
+#!/usr/bin/env perl
+
+# Copyright (C) 2006  Joshua Hoblitt
+#
+# $Id: test.pl,v 1.1 2006-09-23 02:47:58 magnier Exp $
+
+use strict;
+use warnings FATAL => qw( all);
+
+use vars qw($VERSION);
+$VERSION = '0.01';
+
+use File::Find::Rule;
+use Cwd;
+
+my $rule = File::Find::Rule->new;
+# ignore .lib directories
+$rule->or($rule->new
+        ->directory
+        ->name('.libs')
+        ->prune
+        ->discard,
+        $rule->new
+    );
+$rule->name(qr/^tap_[^.]*$/)
+        ->maxdepth(2)
+        ->relative;
+
+my @test_files = $rule->in(getcwd());
+
+system("prove @test_files");
Index: /branches/eam_branches/eam_branch_20080706/psphot/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/.cvsignore	(revision 22157)
@@ -0,0 +1,19 @@
+bin
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+compile
+config.log
+config.status
+configure
+depcomp
+install-sh
+missing
+config.guess
+libtool
+ltmain.sh
+stamp-h1
+config.sub
+psphot.pc
+psphot-config
Index: /branches/eam_branches/eam_branch_20080706/psphot/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/Makefile.am	(revision 22157)
@@ -0,0 +1,12 @@
+SUBDIRS = src
+
+CLEANFILES = *.pyc *~ core core.*
+
+bin_SCRIPTS = psphot-config 
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA= psphot.pc
+
+EXTRA_DIST = \
+	psphot-config.in \
+	psphot.pc.in \
+	autogen.sh
Index: /branches/eam_branches/eam_branch_20080706/psphot/autogen.sh
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/autogen.sh	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/autogen.sh	(revision 22157)
@@ -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=psphot
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL="aclocal $ACLOCAL_FLAGS"
+AUTOHEADER=autoheader
+AUTOMAKE=automake
+AUTOCONF=autoconf
+
+($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $LIBTOOlIZE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/libtool/"
+        DIE=1
+}
+
+($ACLOCAL --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $ACLOCAL installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOHEADER --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOHEADER installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOMAKE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOCONF installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+if test "$DIE" -eq 1; then
+        exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+        echo "You must run this script in the top-level $PROJECT directory"
+        exit 1
+}
+
+if test -z "$*"; then
+        echo "I am going to run ./configure with no arguments - if you wish "
+        echo "to pass any to it, please specify them on the $0 command line."
+fi
+
+$LIBTOOLIZE --copy --force || echo "$LIBTOOlIZE failed"
+$ACLOCAL || echo "$ACLOCAL failed"
+$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE  failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /branches/eam_branches/eam_branch_20080706/psphot/configure.ac
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/configure.ac	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/configure.ac	(revision 22157)
@@ -0,0 +1,216 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_PREREQ(2.61)
+
+AC_INIT([psphot], [0.9.0], [ipp-support@ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([src])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_CONFIG_HEADER([src/config.h])
+AM_MAINTAINER_MODE
+
+IPP_STDCFLAGS
+
+AC_LANG(C)
+AC_GNU_SOURCE
+AC_PROG_CC_C99
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+AC_SYS_LARGEFILE
+
+dnl ------------------------------------------------------------
+
+AC_PATH_PROG([ERRORCODES], [psParseErrorCodes], [missing])
+if test "$ERRORCODES" = "missing" ; then
+  AC_MSG_ERROR([psParseErrorCodes is required])
+fi
+
+dnl ------------------ kapa,libkapa options -------------------------
+dnl -- libkapa implies the requirement for libpng, libjpeg as well --
+
+dnl save LIBS/CFLAGS/LDFLAGS
+TMP_LIBS=${LIBS}
+TMP_CFLAGS=${CFLAGS}
+TMP_LDFLAGS=${LDFLAGS}
+TMP_CPPFLAGS=${CPPFLAGS}
+
+dnl test for command-line options: use ohana-config if not supplied
+KAPA_CFLAGS_CONFIG="true"
+KAPA_LIBS_CONFIG="true"
+AC_ARG_WITH(kapa,
+[AS_HELP_STRING(--with-kapa=DIR,Specify location of libkapa)],
+[KAPA_CFLAGS="-I$withval/include" KAPA_LIBS="-L$withval/lib" 
+ KAPA_CFLAGS_CONFIG="false"       KAPA_LIBS_CONFIG="false"])
+AC_ARG_WITH(kapa-include,
+[AS_HELP_STRING(--with-kapa-include=DIR,Specify libkapa include directory.)],
+[KAPA_CFLAGS="-I$withval" KAPA_CFLAGS_CONFIG="false"])
+AC_ARG_WITH(kapa-lib,
+[AS_HELP_STRING(--with-kapa-lib=DIR,Specify libkapa library directory.)],
+[KAPA_LIBS="-L$withval" KAPA_LIBS_CONFIG="false"])
+
+echo "KAPA_CFLAGS_CONFIG: $KAPA_CFLAGS_CONFIG"
+echo "KAPA_LIBS_CONFIG: $KAPA_LIBS_CONFIG"
+echo "KAPA_CFLAGS: $KAPA_CFLAGS"
+echo "KAPA_LIBS: $KAPA_LIBS"
+
+dnl HAVE_KAPA is set to false if any of the tests fail
+HAVE_KAPA="true"
+AC_MSG_NOTICE([checking for libkapa])
+if test "$KAPA_CFLAGS_CONFIG" = "true" -o "$KAPA_LIBS_CONFIG" = "true"; then
+  AC_MSG_NOTICE([kapa info supplied by ohana-config])
+  KAPA_CONFIG=`which ohana-config`
+  AC_CHECK_FILE($KAPA_CONFIG,[],
+    [HAVE_KAPA="false"; AC_MSG_WARN([libkapa is not found: output plots disabled.  Obtain libkapa at http://kiawe.ifa.hawaii.edu/Elixir/Ohana or use --with-kapa to specify location])])
+  
+  echo "HAVE_KAPA: $HAVE_KAPA"
+  echo "KAPA_CFLAGS_CONFIG: $KAPA_CFLAGS_CONFIG"
+
+  if test "$HAVE_KAPA" = "true" -a "$KAPA_CFLAGS_CONFIG" = "true" ; then
+   AC_MSG_NOTICE([libkapa cflags info supplied by ohana-config])
+   AC_MSG_CHECKING([libkapa cflags])
+   KAPA_CFLAGS="`${KAPA_CONFIG} --cflags`"
+   AC_MSG_RESULT([${KAPA_CFLAGS}])
+  fi
+
+  if test "$HAVE_KAPA" = "true" -a "$KAPA_LIBS_CONFIG" = "true" ; then
+   AC_MSG_NOTICE([libkapa ldflags info supplied by ohana-config])
+   AC_MSG_CHECKING([libkapa ldflags])
+   KAPA_LIBS="`${KAPA_CONFIG} --libs` -lX11"
+   AC_MSG_RESULT([${KAPA_LIBS}])
+  fi
+fi
+
+if test "$HAVE_KAPA" = "true" ; then
+ AC_MSG_NOTICE([libkapa supplied])
+ PSPHOT_CFLAGS="${PSPHOT_CFLAGS} ${KAPA_CFLAGS}"
+ PSPHOT_LIBS="${PSPHOT_LIBS} ${KAPA_LIBS}"
+else
+ AC_MSG_NOTICE([libkapa ignored])
+fi
+
+dnl HAVE_KAPA is set to false if any of the tests fail
+dnl HAVE_KAPA=true
+dnl AC_CHECK_HEADERS([kapa.h],
+dnl  [PSPHOT_CFLAGS="$PSPHOT_CFLAGS $KAPA_CFLAGS" AC_SUBST(KAPA_CFLAGS)],
+dnl  [HAVE_KAPA=false; AC_MSG_WARN([libkapa headers not found: output plots disabled.  Obtain libkapa at http://kiawe.ifa.hawaii.edu/Elixir/Ohana or use --with-kapa to specify location.])]
+dnl )
+dnl AC_CHECK_LIB(kapa,KapaInitGraph,
+dnl  [PSPHOT_LIBS="$PSPHOT_LIBS $JPEG_LDFLAGS -ljpeg"],  
+dnl  [HAVE_KAPA=false; AC_MSG_WARN([libkapa headers not found: output plots disabled.  Obtain libkapa at http://kiawe.ifa.hawaii.edu/Elixir/Ohana or use --with-kapa to specify location.])],[-lm]
+dnl )
+
+dnl restore the CFLAGS/LDFLAGS
+LIBS=${TMP_LIBS}
+CFLAGS=${TMP_CFLAGS}
+LDFLAGS=${TMP_LDFLAGS}
+CPPFLAGS=${TMP_CPPFLAGS}
+
+dnl ------------------ libjpeg options ---------------------
+
+dnl save LIBS/CFLAGS/LDFLAGS
+TMP_LIBS=${LIBS}
+TMP_CFLAGS=${CFLAGS}
+TMP_LDFLAGS=${LDFLAGS}
+TMP_CPPFLAGS=${CPPFLAGS}
+
+AC_ARG_WITH(jpeg,
+[AS_HELP_STRING(--with-jpeg=DIR,Specify location of libjpeg.)],
+[JPEG_CFLAGS="-I$withval/include"
+ JPEG_LDFLAGS="-L$withval/lib"])
+AC_ARG_WITH(jpeg-include,
+[AS_HELP_STRING(--with-jpeg-include=DIR,Specify libjpeg include directory.)],
+[JPEG_CFLAGS="-I$withval"])
+AC_ARG_WITH(jpeg-lib,
+[AS_HELP_STRING(--with-jpeg-lib=DIR,Specify libjpeg library directory.)],
+[JPEG_LDFLAGS="-L$withval"])
+
+CFLAGS="${CFLAGS} ${JPEG_CFLAGS}"
+CPPFLAGS=${CFLAGS}
+LDFLAGS="${LDFLAGS} ${JPEG_LDFLAGS}"
+
+AC_CHECK_HEADERS([jpeglib.h],
+  [PSPHOT_CFLAGS="$PSPHOT_CFLAGS $JPEG_CFLAGS" AC_SUBST(JPEG_CFLAGS)],
+  [HAVE_KAPA=false; AC_MSG_WARN([libjpeg headers not found: output plots disabled.  Obtain libjpeg from http://www.ijg.org/ or use --with-jpeg to specify location.])]
+)
+
+AC_CHECK_LIB(jpeg,jpeg_CreateCompress,
+  [PSPHOT_LIBS="$PSPHOT_LIBS $JPEG_LDFLAGS -ljpeg"],
+  [HAVE_KAPA=false; AC_MSG_WARN([libjpeg library not found: output plots disabled.  Obtain libjpeg from http://www.ijg.org/ or use --with-jpeg to specify location.])]
+)
+
+dnl restore the CFLAGS/LDFLAGS
+LIBS=${TMP_LIBS}
+CFLAGS=${TMP_CFLAGS}
+LDFLAGS=${TMP_LDFLAGS}
+CPPFLAGS=${TMP_CPPFLAGS}
+
+dnl ------------------ libpng options ---------------------
+
+dnl save LIBS/CFLAGS/LDFLAGS
+TMP_LIBS=${LIBS}
+TMP_CFLAGS=${CFLAGS}
+TMP_LDFLAGS=${LDFLAGS}
+TMP_CPPFLAGS=${CPPFLAGS}
+
+AC_ARG_WITH(png,
+[AS_HELP_STRING(--with-png=DIR,Specify location of libpng.)],
+[PNG_CFLAGS="-I$withval/include"
+ PNG_LDFLAGS="-L$withval/lib"])
+AC_ARG_WITH(png-include,
+[AS_HELP_STRING(--with-png-include=DIR,Specify libpng include directory.)],
+[PNG_CFLAGS="-I$withval"])
+AC_ARG_WITH(png-lib,
+[AS_HELP_STRING(--with-png-lib=DIR,Specify libpng library directory.)],
+[PNG_LDFLAGS="-L$withval"])
+
+CFLAGS="${CFLAGS} ${PNG_CFLAGS}"
+CPPFLAGS=${CFLAGS}
+LDFLAGS="${LDFLAGS} ${PNG_LDFLAGS}"
+
+AC_CHECK_HEADERS([png.h],
+  [PSPHOT_CFLAGS="$PSPHOT_CFLAGS $PNG_CFLAGS" AC_SUBST(PNG_CFLAGS)],
+  [HAVE_KAPA=false; AC_MSG_WARN([libpng headers not found: output plots disabled.  Obtain libpng from http://www.ijg.org/ or use --with-png to specify location.])]
+)
+
+AC_CHECK_LIB(png,png_init_io,
+  [PSPHOT_LIBS="$PSPHOT_LIBS $PNG_LDFLAGS -lpng"],
+  [HAVE_KAPA=false; AC_MSG_WARN([libpng library not found: output plots disabled.  Obtain libpng from http://www.ijg.org/ or use --with-png to specify location.])]
+)
+
+dnl restore the CFLAGS/LDFLAGS
+LIBS=${TMP_LIBS}
+CFLAGS=${TMP_CFLAGS}
+LDFLAGS=${TMP_LDFLAGS}
+CPPFLAGS=${TMP_CPPFLAGS}
+
+dnl ------------------ use kapa or not? ---------------------
+
+if test "$HAVE_KAPA" == "true" ; then
+  AC_MSG_RESULT([including plotting functions])
+  AC_DEFINE([HAVE_KAPA],[1],[enable use of libkapa])
+else
+  AC_MSG_RESULT([skipping plotting functions])
+  AC_DEFINE([HAVE_KAPA],[0],[disable use of libkapa])
+fi
+
+dnl ------------- psLib, psModules ---------------
+PKG_CHECK_MODULES([PSLIB], [pslib >= 1.0.0])
+PKG_CHECK_MODULES([PSMODULE], [psmodules >= 1.0.0])
+
+dnl Set CFLAGS for build
+IPP_STDOPTS
+
+CFLAGS="${CFLAGS=} -Wall -Werror"
+echo "PSPHOT_CFLAGS: $PSPHOT_CFLAGS"
+echo "PSPHOT_LIBS: $PSPHOT_LIBS"
+
+AC_SUBST([PSPHOT_CFLAGS])
+AC_SUBST([PSPHOT_LIBS])
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+  psphot-config
+  psphot.pc
+])
+
+AC_OUTPUT
Index: /branches/eam_branches/eam_branch_20080706/psphot/doc/2Dmodels.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/doc/2Dmodels.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/doc/2Dmodels.txt	(revision 22157)
@@ -0,0 +1,29 @@
+
+I am testing a new concept for the 2D representation of various values
+measured by psphot.  I have been using 2D polynomials to fit the
+variations of, eg, the psf parameters as a function of position in the
+images.  this has been somewhat successful, but it has also been
+fraught with a number of problems.  the worst issue has been the fact
+that the polynomial form is not physically motivated.  in some cases,
+eg the variation of the psf shape parameters, the true variations for
+real distributions are not well represented by a polynomial.  I have
+done some ad hoc tricks to get the functional form to look roughly
+like a polynomial, but this is really quite a hack.  the other problem
+is that it is difficult to decide if the polynomial does an acceptable
+job representing the variations.
+
+My new concept for modeling 2D variations in some parameter is to
+define a low-resolution 2D image to represent the variation.  The
+advantage is that the image may be define with whatever resolution may
+be sampled by the input data, and the functional form need not be
+constrained by the polynomial basis function.  
+
+** is this a poor substitute for using a chebychev polynomial as a
+   better basis function?
+
+psphot parameters which vary by position:
+
+pmPSF.params 
+pmPSF.ApTrend
+pmPSF.FluxScale (flux given normalization of 1.0)
+
Index: /branches/eam_branches/eam_branch_20080706/psphot/doc/config.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/doc/config.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/doc/config.txt	(revision 22157)
@@ -0,0 +1,56 @@
+
+# input data options
+# IMAGE             STR    file.fits
+# MASK_IMAGE	    STR    mask.fits
+# WEIGHT_IMAGE	    STR	   weight.fits
+
+# output data options
+OUTPUT_FILE	    STR	   output.smp    # output object file
+OUTPUT_MODE	    STR	   TEXT          # output mode: TEXT, OBJ, SX, CMP, CMF 
+# RESID_IMAGE	    STR	   resid.fits    # output residual image
+
+# image noise parameters
+RDNOISE             STR  HD:RDNOISE      # read-noise in electrons
+GAIN                STR  HD:GAIN         # electrons / DN
+
+# masking parameters
+XMIN                F32  32        	 # minimum valid x-coord
+XMAX                F32   0        	 # maximum valid x-coord
+YMIN                F32   1        	 # minimum valid y-coord
+YMAX                F32   0        	 # maximum valid y-coord
+SATURATION          F32  50000     	 # saturation level on this chip
+
+# image statistics parameters
+IMSTATS_NPIX        S32  100000    	 # number of pixels to use for sky estimate:
+
+# peak finding 
+PEAKS_SMOOTH_SIGMA  F32  1.0       	 # smoothing kernel sigma in pixels
+PEAKS_SMOOTH_NSIGMA F32  3.0   	   	 # smoothing kernel width in sigmas
+PEAKS_NSIGMA_LIMIT  F32  10.0  	   	 # peak significance threshold
+
+# basic object statistics
+SKY_INNER_RADIUS    F32  15		 # square annulus for local sky measurement
+SKY_OUTER_RADIUS    F32  25		 # square annulus for local sky measurement
+PSF_MOMENTS_RADIUS  F32  5
+PSF_SN_LIM          F32  100
+
+# PSF model parameters : choose the PSF model
+# list as many PSF_MODEL options as desired
+PSF_MODEL           STR  PS_MODEL_QGAUSS
+#PSF_MODEL           STR  PS_MODEL_PGAUSS
+#PSF_MODEL           STR  PS_MODEL_GAUSS
+PSF_FIT_RADIUS      F32  25		 # fitting radius for test PSF model
+
+# PSF model parameters : apply the PSF model
+PSF_FIT_NSIGMA       F32  1		 # significance for pixel included in fit
+PSF_FIT_PADDING      F32  5              # extra annulus to use for fit 
+PSF_SHAPE_NSIGMA     F32  3.0		 # max significance for shape variation
+PSF_MIN_SN           F32  2.0		 # reject objects below this significance
+PSF_MAX_CHI          F32  10.0		 # reject objects worse that this
+
+# Galaxy model parameters
+GAL_MODEL            STR  PS_MODEL_SGAUSS
+GAL_MIN_SN           F32  3
+GAL_FIT_NSIGMA       F32  1		 # significance for pixel included in fit
+GAL_FIT_PADDING      F32  5              # extra annulus to use for fit 
+GAL_MOMENTS_RADIUS   F32  9
Index: /branches/eam_branches/eam_branch_20080706/psphot/doc/footprints.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/doc/footprints.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/doc/footprints.txt	(revision 22157)
@@ -0,0 +1,43 @@
+
+pmFootprintCullPeaks is very expensive (15 - 30 msec per object) and
+must be performed on every peak.  We need to improve the speed (even
+at the cost of memory) and we need to thread it.
+
+*** pmFootprints makes use of the mask image, but it does not use the
+    master mask values.  this must be fixed!
+
+-- here is the algorithm
+
+** note: this is run once for each footprint...
+
+* the incoming footprint peak list is already sorted by brightness
+
+* loop over all peaks
+
+  * choose a threshold which is the peak flux - nsigma_delta*stdev
+    ? actually, the threshold is using the edge of the containing
+    subimage?  I'm a little confused by this.
+
+  * if the threshold is below a minimum threshold, we drop it
+    - the code is calling psArrayRemoveIndex for each of these, then decrementing i.
+      this is inefficient since psArray is not a psList.  two possible options here:
+      
+      * do not remove the dropped peaks from the array until the full
+        processing is done, then re-build the array in a single pass.
+        flag peaks to be dropped (either in the structure or with a
+        parallel vector)
+
+      * have the code build a separate 'bright peaks' array on the fly
+        then swap the new peaks out under the pmFootprint.
+
+  * psArrayRemoveIndex is used 3x in pmFootprints.c : review each case
+
+  * the subImage is divided into footprints above threshold about the
+    current peak.
+
+    - EAM : Need to disect pmFindFootprintsAtPoint.  Is it expensive
+        in this case?  Should we be calling the normal pmFootprints
+        function?
+
+
+  
Index: /branches/eam_branches/eam_branch_20080706/psphot/doc/notes.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/doc/notes.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/doc/notes.txt	(revision 22157)
@@ -0,0 +1,671 @@
+
+2008.06.25
+
+ timing : footprints
+
+ on GPC1, with FFT: 
+    pass 1 :     convolved with grow disc: 17.558344 sec
+    pass 2 :     convolved with grow disc: 8.915036 sec
+
+2008.01.31
+
+ Most of the fixes listed below have been finished except for:
+
+ * careful handling of the r ~ 0 fluxes / effective positions (for eg sersic)
+ * the EXT output parameters have not been tested
+
+ In addition, some further work to be done:
+
+ * multiple image detection
+ * multiple image fitting (including projection)
+ * optimization
+ * multi-threading
+
+ * possibly try the 'active deblending' concept that Ken from WISE is using.
+   * instead of fitting a double star with the positions floating at a
+     slightly random location, search for significant additions within
+     a grid about the central peak (say ~ 1sigma / 0.2 pix?).  
+     (note that this is a linear fit)
+
+ * for the blend groups, freeze the positions for the fainter sources
+   (consistent with the non-linear S/N threshold)
+
+ 
+
+2007.08.17
+
+ I am working on a number of cleanup / fixes.  I have made an overhaul
+ of the pmModel APIs, adding function pointers in the pmModel
+ structure to the class-specific utility functions (eg,
+ pmModel->modelFunc is the actual function which is evaluated). 
+
+ TO DO:
+
+ * update pmSourceFitSet to be able to include more than one model
+   type (currently it assumes the sources are all, eg, PSFs).  This is
+   now needed because the old implementation used the function lookups
+   which have been dropped.
+
+ * define a generic API set to handle 2D modelling of a scalar using
+   either polynomials (as the pmPSF code currently does) or an
+   image-based representation (as the psphot sky model currently
+   does).  
+
+   The image-based representation can automatically step down from NxM
+   super pixels to a smaller number based on the density of
+   measurements.  
+
+   Include ways to smooth and regularize the output result.
+
+   This mechanism can be applied to: psf parameters, aperture
+   residual, psf peak-to-flux variations, the psphot background
+   representation.
+
+ * generate and store the output radial profile for objects
+
+ * finish testing and incorportate the CR / EXT measurements
+
+ * adjustments to pixel center based on second derivatives : needed
+   for the sersic models.
+
+ * adjustments to pixel flux for extreme values (r ~ 0) : needed for
+   the sersic models.
+
+ * on psf stars : fall back on a Gaussian.
+
+ * OPTIMIZATIONS !!
+
+ * drop the model sky element : should only be in source->sky,dsky
+
+2006.11.16
+
+ensemble:
+
+  * first block: 
+    * select sources of interest / skip
+    * generate a guess model
+    * re-measure moments for saturated stars (MOVE)
+    * update peak position (MOVE to peaks)
+    * set the object radius 
+    * build temporary source copies (including pixels, mask, weight)
+
+  * second block:
+    * measure the cross-products and weights
+    * add to the sparse matrix
+
+  * build full-image mask
+  * measure coord weights
+  * build order elements
+
+  * solve for source amplitudes
+  * update models
+
+  * create psSparseBorder to solve matrix equations which have a large
+    sparse region, with a completely filled border of a smaller number
+    of rows.
+
+  * trying to modify psphotEnsemble to (1) be more general (not just
+    PSFs) and (2) fit for the sky offset.
+
+    - how is the radius for each source set?
+    
+
+2006.11.11
+
+  I am trying to define a complete set of tests to validate the
+  measurement of the photometry compared with the simulated images.
+  Part of my confusion is controlling exactly which corrections are
+  applied to the output magnitudes.  Here is the complete list:
+
+  psfMag_out = psfMag_meas + ApTrend(x,y,0.0,0.0)
+  apMag_out = apMag_meas + growth(radius,refRadius) + f(skyBias,skySat)
+
+  these corrections are modified by the following config settings:
+
+  IGNORE_GROWTH (sets all growth magnitudes to the fitMag value,
+  effectively turning off the growth correction)
+
+  APTREND : set the level of correction
+    NONE     : no correction is fitted
+    CONSTANT : just a single constant (applied to get psfMag_out)
+    SKYBIAS  : the skybias term is fitted (constant applied to psfMag,
+               slope applied apMag)
+  
+2006.11.08
+
+  I have been testing the pmSourceMagnitudes interpolation of the
+  source position when measuing the aperture magnitudes.  I built a
+  test suite for pmSourceMagnitudes and pmGrowthCurve.  even with
+  bicubic interpolation, there are noticable errors for small
+  apertures (ie, small compared to the PSF sigma).  I used a gaussian
+  PSF to make the tests.  it seems that, if the radius is less than 2
+  sigma or so, the interpolated aperture mag is in error by as much as
+  12mmag.  The size of this effect must depend on the smoothing
+  algorithm, the shape of the PSF, the size of the PSF relative to the
+  pixels, and the size of the subpixel interpolation.  I believe the
+  effect is a result of the smoothing introduced by the interpolation:
+  the smoothing pushes the flux in the inner portions of the PSF down
+  and enhances the flux in the outer portions.  thus, there is
+  actually a turn over in the error: at very large values, the error
+  is very small because the aperture contains all of the flux, and the
+  interpolation has little effect.  as the radius shrinks, the error
+  grows (in the sense of M_raw - M_interpolated; ie, the flux in the
+  interpolated source is too large).  around 2-3sigma, the error turns
+  over and becomes negative (too little flux in the interpolated
+  source). 
+
+  The sigmas I used for these tests (1.0, 1.5, 2.0 pix) are
+  appropriate for the range we expect to see from PS1, or any
+  well-sampled detector.  Thus, even though the effect is probably
+  less important for poorer seeing, we have to be careful to avoid it
+  in our Pan-STARRS analysis.
+
+  Currently, the minimum aperture is defined only by the config
+  variable PSF_FIT_PAD, which really implies a padding.  My proposal
+  is to require the minimum aperture to be a number of sigma (an
+  additional parameter).  Then, the formula for the radius would be
+  something like:
+
+  MAX (modelRadiusPSF (PSF_FIT_NSIGMA), PSF_FIT_RADIUS_NSIG) +
+  PSF_FIT_PAD
+
+  with PSF_FIT_RADIUS_NSIG (choose a better name!) set to something
+  like 2 or 3.
+
+2006.11.06
+
+  I have added the aperture interpolation as a component of the
+  pmSourceMagnitudes function.  
+
+  I am trying to make things a bit more organized, with more
+  consistent APIs. 
+
+  * changed pmPSFtry to use the pmSourceMagnitudes API
+  * changed pmPSFtry to use new pmSource entries for each try (saving
+  * the pmModels generated)
+
+  other notes:
+  * moved psphotGrowthCurve -> pmGrowthCurveGenerate 
+
+  things I know need to be added / fixed:
+  * measure bicube fit to peak from smoothed image
+  * use psphotEnsemble fit to re-measure normalization?
+  * extended object statistical measures 
+
+2006.11.02
+
+  Looking into the growth curve problem.  RHL sent his recipe for
+  correcting to true circular apertures.  I've done a test fix of
+  interpolating the stellar image to the desired center.  it looks
+  like bilinear interpolation is insufficient (about 8% error for 2
+  pixel radius aperture).  Unless I've got the formula wrong, the
+  effective smoothing of the gaussian is moving flux from the center
+  out and making small apertures in error that depends on the offset.
+  Bicubic could be enough to clean this up?  
+
+  I think the current code may need to be careful modified to include
+  the interpolation for every aperture measurement.  there are paired
+  'pmModelAdd' and pmModelSub steps to add and remove the model flux.
+  I need to be careful about how the mask affects these steps.
+
+  I also need to be careful in interpreting the test results: since
+  the simulated images create objects by filling in a pixel and then
+  smoothing, all objects have centroids which are exactly 0.5 pixel
+  offsets.  These all then have a fixed growth correction error, which
+  fortunately happens to be substantial (because of a 0.5 pixel offset
+  in the growth curve reference object!)
+
+  I need to double check the 0.5 pixel error issues.  There are
+  problems with the pslib version of psImageShift and
+  psImageInterpolate (or at least inconsistencies).  the pmModelAdd
+  and Sub functions and the centroid functions are probably also
+  inconsistent.
+
+2006.11.01
+
+  Trying to understand low-level errors in psphot using simulated
+  data.  the main issue I am concerned with is the bias RHL is seeing
+  in SDSS-psphot comparisons.  But there are other issues that are
+  creeping in and probably point at logical errors in the code.
+
+  * in pmPSFtry, I measure the first pass on the aperture residual (to
+    choose between PSF model options, if that is being tested).  This
+    analysis currently uses a polynomial fit (1st order) for the offset
+    and the sky bias (using r^2/flux as an independent variable).
+
+    Notes:
+
+    - is the psf-fit error distribution reasonable?  perhaps a little
+      on the high side: at 10000 DN, noise is 0.0138, should be
+      0.0107.
+
+    - scatter of ap-fit is surprisingly high.  M_input - M_fit has a
+      median chisq of 2 (mean of 5), while M_input - M_ap has a median
+      chisq of 125 (mean of 380).  This is using a radius of 15.  It
+      cleans up hugely when I go to a radius of 7 and even more so
+      with radius = 4. (In this case, the PSF sigma is 1.5, so 4 is
+      well matched and 7 is only slightly large).
+
+      * make the initial PSF fitting radius and aperture radius
+	  different?  
+      * make the initial PSF fitting radius a function of the measured
+	  PSF sigma?
+
+    - I have been fitting ap-fit mag without weights; the resulting
+      ap-fit errors are highly over-estimated since the ap-fit scatter
+      is too high.  I have added code to carry the weights (error from
+      the fit only at the moment, ie, not the ap error as well).  This
+      is good, but not as important if the radius is chosen well above.
+
+  * a bias in the psphotEnsemble results depending on the sky level
+    but not in the PSF model fits?  turning on/off CONST_PHOT_WT has
+    an equivalent effect, but is quite small.  why is one sensitive to
+    the sky and the other is not?  
+
+  * the error measured by psphotEnsemble was wrong (and inconsistent
+    for a given weighting situation): fixed the def of the error.
+
+  * still trying to understand the corrections being made.  running a
+    few tests:
+
+    keeping PSF_FIT_RADIUS at 4.0
+
+    t1 : no growth correction,    constant weights
+    t2 :    growth correction,    constant weights
+    t3 : no growth correction, no constant weights
+    t4 :    growth correction, no constant weights
+    t5 :    growth correction,    constant weights, constant ap (4)
+    t6 :    growth correction,    constant weights, integer radius
+    t7 :    growth correction,    constant weights, integer radius
+    (for t7, I've fixed the bug that the growth correction include the
+    min radius).
+
+  * psphotGrowthCurve is messed up : the measurement does not correct
+    for the actual object center, and at small radii this is an
+    important source of noise.
+
+2006.06.28
+
+  I am adding a few minor features.  First up is the ability to break
+  the processing at interesting stages.  These will be:
+
+  - PEAKS: after sources are generated, but before moments are
+    measured
+  - MOMENTS: after moments are measured, before the PSF is measured
+  - PSFMODEL: after the psf model is measured, before sources are
+    fitted in bulk
+  - ENSEMBLE: after the linear ensemble fitting
+  - DEFAULT: complete processing
+
+2006.04.22
+
+  I have updated psphot to work with the cycle 11 release of psLib and
+  psModules.  At this point, the IfA group is now working off of the
+  CVS head rather than from a separate IfA branch of psLib and
+  psModules.  The cycle 11 release of psLib needed a few updates to
+  fix minor problems.  The psModules code needed significant work to
+  fix discrepancies.  The new version of psphot has also been leak
+  checked, at least for basic configurations.  This new version of
+  psphot should be used with the new camera configuration system,
+  which can be found in the ipp/config directory.  Here are the
+  necessary tags:
+
+  psphot: psphot_dev_11_0
+  psModules: rel11_ifa_pre0
+  psLib: rel11_ifa_0
+
+2006.04.16
+
+  Examining some of the throughput issues.  the flux measurement speed
+  is strongly dependent on the interation of the function step size:
+
+  dz = 0.01 : 3.24s for 4400 objects
+  dz = 0.1  : 0.44s for 4400 objects
+
+  there is an increased error in the result with the larger step size,
+  but it is smaller than the phot error.  Still, we could do better
+  with a smarter integration function.
+
+  Including the aperture photometry on all sources, but not the
+  pixWeight, the source magnitudes takes 4.8 sec for 4400 objects.
+
+  Adding the pixWeight increases the time to 7.5 sec for 4400 objects
+
+2006.02.12
+
+  Last week, I made a new tag (psphot_dev_08) after finishing the work
+  to clean memory leaks and to modify psphot to work with Paul's
+  ppImage infrastructure.  
+
+  I have added a new background model function which generates a
+  median map based on Robert's suggestion of a) half-step windows and
+  b) linear interpolation.
+
+  Now, I am working to remove the constant sky level concepts and make
+  every relevant test use the signal/noise ratio as the trigger.  This
+  has impacts on the peak detections, the threshold for blends, and
+  the threshold for choosing pixels in the fitting routines.
+
+2005.12.23
+
+  Improvements in psphot as of psphot_dev_07
+
+  - updated to work with psModule release 9.0
+  - moved model test functions into psphot as optional mode
+  - added pmSourceFitSet : simultaneous model group fitting
+  - added bicubic interpolation to tweak up the peak coordinates
+  - added aperture residual analysis after complete object model & subtraction
+  - added 3D (x,y,rflux) aperture residual fitting
+  - added blend vs blob fitting (BlendFit)
+  - added ensemble PSF fitting, with sparse matrix inversion
+  - improved the flagging / marking of model results
+  - better abstraction for setting fit parameters
+  - various low-level fixes
+  - added PSF output option
+  - added break points
+  - added more optional output formats
+  - fixed FITS table output
+
+  Things still to be added to psphot:
+
+  - better initial background model (NxM median grid?)
+  - include local background model in EnsemblePSF Fit (use sparse matrix)
+  - 2nd pass (after object subtraction) to smooth and detect faint objects
+  - re-add background to object-subtracted image
+  - cleanup function names
+  - push more functions into psLib / psModule
+  - fix extended / galaxy models to improve stability
+  - add mode with input PSF
+  - add mode with input fixed sources (PS_SOURCE_FIXED)
+  - cleanup memory leaks
+
+2005.11.25
+
+  I've updated psphot to work with the current psLib v8, though a
+  number of psLib fixes were needed.  These are pushed under
+  psLib:eam_rel8_b2.  The psphot code which works with that version of
+  psLib is tagged psphot_dev_04.
+
+  I'm working on converting psphot to work with the current release of
+  psModules, which basically includes all of the object functions. 
+
+  - change comments are relative to psModules (vs psphot versions)
+
+  psEllipse.c : OK
+  psEllipse.h : OK
+  pmModelGroup.c : OK (missing TGAUSS entry)
+  pmModelGroup.h : OK (adds many comments)
+
+  pmPSFtry.c : a bunch of formatting weirdness (try-> got line breaks
+	       added pmPSFtryMetric_Alt
+	       fixed up usage of psVectorClipFitPolynomial1D  
+	       fixed up usage of psPolynomial..Alloc  
+
+  pmPSFtry.h : OK (Added extensive comments)
+
+  pmPSF.c : fixed up usage of psPolynomial..Alloc
+
+  pmPSF.h : OK (Added extensive comments)
+
+  pmObjects.c : cleaned up some formatting,
+		fixed usage of psImageCountPixelMask
+
+  pmObjects.h : added apMag and fitMag to pmSource
+		dropped PS_MODEL_name defines
+
+  psLibUtils.[ch] should be dropped from psModules
+  psModulesUtils.[ch] should be dropped from psModules
+
+  Makefile.am : dropped psModulesUtils.[ch], psLibUtils.[ch]
+
+  - I have successfully tested psphot with the psModules.v8 code
+    (eam_rel8_b1 tag).  I only needed to make a few modes in the
+    psModules code.  
+
+2005.11.16
+
+  I have made some fixes to make psphot work with pslib rel8_0.  I
+  need to merge psphot with the rel8_0 version of the psMinimize and
+  pmObjects code.  
+
+2005.09.06
+
+  I have built a working version of PSPhot using my own copy of the
+  pmObjects.[ch] files.  I need to minimize the difference between the
+  v0.7.0, v0.5.0, and my own version of pmObjects.c
+
+  additions to psLib based on the new version:
+  - extended the minimization to include parameter limits
+
+  changes v0.5.0 / v0.7.0 / EAM
+  - changed all ps* to pm*
+  - fixed naming for local static functions (eg modelFree not p_psModelFree)
+  - dropped all hard-coded model names (vs v0.7.0)
+  - using current psMemSetDellocator name
+  - added noise image to pmSource
+  - converted old asserts to new asserts (PS_.._CHECK..)
+  - add return from error in pmFindImagePeaks
+  - allow isItInThisRegion to have NULL region (true)
+  - fixed subImages to correspond to region definition (end is
+    exclusive) in LocalSky and SetPixelCircle
+  - dropped old, redundant code in pmSourceLocalSky
+  - fixed pmSourceMoments use of mask (0 is valid, not 1)
+  - added pmSourceMoments validity tests (numPixels, Sum, centroid shift)
+  - added correction for Sxy (XY/Sum - x*y)
+  - added pmSourcePSFClump function (and pmPSFClump structure)
+  - allow missing sources in pmSourcePSFClump (not all peaks yield
+    sources)
+  - moved pmSourceRoughClass metadata lookups out of inner loop
+  - added saturated pixel test using pmImageCountPixelMask
+  - various redefinitions of initial classes
+  - dropped old, redundant code in pmSourceSetPixelsCircle
+  - mask image set to type psU8 (was psF32)
+  - abstract object model functions for pmSourceModelGuess
+  - converted evalModel to a public function psModelEval, modified params
+  - pmSourceFitModel operates on a specified model
+  - pmSourceFitModel had option to apply/ignore PSF information
+  - pmSourceFitModel calculates yErr
+  - pmSourceFitModel calculates the covariance matrix
+  - added paramMask to pmSourceFitModel 
+  - pmSourceFitModel_EAM uses covar to carry in beta and param
+    limits...
+  - pmSourceFitModel requires solution to stay within image
+  - pmSourceFitModel calculates and saves chisq, nIter, nDOF
+  - pmSourceFitModel uses GaussNewtonDelta for frozen params
+  - added mask to pmSourceAdd, pmSourceSub
+  - model abstractions in pmSourceAdd, pmSourceSub
+  - fixed 'center' option in pmSourceAdd/Sub
+
+  - pmPSF params should be F64
+
+  lingering concerns:
+  - Polynomials and related functions are STILL messed up.  this is a
+    major priority!!!
+  - pmSourceFitModel calculates sqrt(var), psMinimize calculates sq(sqrt(var))
+  - does psImageAlloc zero the image?  if so, drop init routine
+    from pmSourcePSFClump
+  - pmSourceRoughClass uses RDNOISE, GAIN to calculate SN: should use
+    the source->noise image? part of pmSourceMoments?
+  - pmSourceMoments_EAM uses macro for checkRegion
+  - pmObjects_EAM.c uses some hardcoded mask values (LocalSky)
+  - why does LocalSky_EAM not need subImageMask->col0 = subImage->col0??
+  - psGetRowVectorFromImage should be in psLib (in psLibUtils)
+  - pmSourceSetPixelsCircle and pmSourceLocalSky probably could use
+    the psImageMaskRegion, etc functions.
+  - pmSourceSetPixelsCircle uses a hard-coded mask value
+  - define a mask registration process??
+  - pmSourceContour is using the model flux, not the image flux
+
+2005.09.05
+
+  I have a working version of PSPhot which handles PSF and galaxy
+  models.  I am tagging the module with an alpha version number. this
+  version compiles with psLib-0.5.0, at least my version of it.  My
+  current goal is to make it work with the current psLib head (nearly
+  0.7.0) with a mimimum of _EAM versions of functions.
+
+  the tag for the working version is dev_01
+
+2005.06.04
+
+  progress on psphot has moved along well.  At this point, the process
+  loads the image, finds the peaks, determines a best PSF model,
+  identifies objects which are consistent with that model, and
+  attempts to fit a galaxy model to the objects which are not
+  consistent with the model. 
+
+  a variety of minor issues remain, as well as some major issues. 
+
+  - use a proper noise image to keep the fits honest after other
+    objects have been subtracted.  
+
+  - define the masks in terms of a single mask image.  this image
+    could be provided initially by the user.   
+
+  - add noise enhancement (couple with mask marking subtraction)
+
+  - 
+
+2005.04.12
+
+- psPeak, psSource, etc: should be pmPeak, pmSource, etc
+- pmCullPeaks: should be pmCullImagePeaks
+
+- does pmSourceMoments need an image argument?
+
+- psLibInit / p_psTimeInit
+
+  we should not have to know about astronomy time concepts (ie, have a
+  timeConfig file) in order to use the time functions for basic work.
+  This means having a PS_TIME_UNIX which just works with the UNIX
+  clock and avoids all initialization issues.
+
+- psImageStats is much too slow for basic median
+  - this function does a complete sort on all pixel values.  we need
+  to use the histogram method (spec a different type of median)
+
+- psImageStats, ROBUST_MEDIAN fails
+- there are errors in psVectorStats for ROBUST MEAN, STDEV (& median?)
+  * this is ill-defined.  revise the ADD: too sensitive to the
+  binning, smoothing scales 
+
+- pmFindImagePeaks is not finding any peaks:
+  typo in interior row section: see bug 359
+  after this fix: PASS
+
+* p_psGetRowVectorFromImage : PASS
+* psGetRowVectorFromImage : PASS 
+- both have about the same time (~15 us +/- 5us) for 2048 pix on alala
+
+* pmFindVectorPeaks : PASS (returned row data in data.U32)
+
+- # include <string.h> : needed in psMetadata.c
+
+* MyListAddPeak / pmPeakAlloc are inconsistent wrt col,row vs x,y
+
+- why does psMetadataItemAllocV allocate a string of length
+  MAX_STRING_LENGTH for the comment, then use strncpy?
+
+  should be:
+
+    // set metadata item comment
+    if (comment == NULL) {
+        // Per SDRS, null isn't allowed, must use "" instead
+        metadataItem->comment = psStringCopy ("");
+    } else {
+        metadataItem->comment = psStringCopy (comment);
+    }
+
+psMetadataAdd -> psMetadataAddItem is all confused:
+ - we end up with two psMetadataItemAlloc calls, when only one should
+   be used
+ - psMetadataItemAlloc was doing the wrong thing if the incoming data
+   was NULL
+ - I think the logic in this block is wrong as well.  
+
+ * I fixed this in psMetadata.c
+
+2005.04.14 : 
+
+ - psImageStats is still much too slow
+
+ - pmObjects : changed to using psArray to carry peaks / sources,
+   rather than psList
+
+ - added function pmPeaksSubset to replace pmCullPeaks
+
+ - pmSourceLocalSky: added min/max fncs to force subimage to stay in
+   image
+
+ - Sx, Sy can go negative: have forced limit to 0,0
+
+ - pmSourceRoughClass: implemented sigmaX, sigmaY search for stellar
+   clump (uses pmComparePeakDescend)
+
+   - the search ignores the 0,0 pixel
+
+   - pmSourceRoughClass uses all detected peaks.  allow an exclusion
+     for peak > max, peak < min?
+
+   - need to push clump stats on the metadata
+
+   - the source classes are assigned so they are mutually exclusive 
+
+   - change PS_SOURCE_OTHER to STAR? 
+
+ - pmSourceSetPixelCircle:
+
+   - name should be pmSourceSetPixelsCircle (geometry could be an
+     option...)
+
+   - I cleaned the defined region to match the convensions of
+     pmSourceLocalSky
+
+   - truncate and force subimage to lie on image
+
+   - CheckRadius2 seems like an inefficient coding
+
+   - valid pixels have mask value of 1 (inverse of other funcs)
+
+ - pmSourceFitModel:
+
+   - num iterations is much too large
+
+   - 
+
+mrq:
+
+ - allocate alpha, beta arrays
+ - mrq2dcof (pars, alpha, beta)
+ - make guess pars', alpha', beta'
+ - mrq2dcof (pars', alpha', beta')
+ - if (chisq < old chisq), keep new guess
+
+
+2005.04.19:
+
+- psMinimizeLMChi2 has some serious problems.  I re-wrote it starting
+  from the mrqmin example in ohana.   after a variety little bugs, it
+  seems to work quite well.  the program 'modeltest.c' runs two
+  different gaussians through psMinimizeLMChi2, and gets the right
+  answer quite quickly.  The first pass at the implementation had a
+  variety of problems.  to avoid any worry about errors in the
+  psMatrix code, I implemented psGaussJordan, basing the code on the
+  elixir gaussj.c code.  this worked fine, and let me find the errors
+  elsewhere in the code. the LUD version now works just the same as
+  the GaussJ version.
+
+
+2005.04.21
+
+- psMinimizeLMChi2 does an excellent job now in most cases.  I am able
+  to run pmSourceFitModel on the PSF stars quite well.  I added in the
+  (nearly) correct errors (actually, just using sqrt(N), limiting to
+  N>=1).  I see one problem object, which is quite faint, and does not
+  converge well: I get very large values for x,y, Sx, Sy, etc.  I need
+  to think about constraining these fits from running way off scale.
+
+  fit:   0.123879 sec for 277 stars (gauss)
+  fit:   0.155940 sec for 277 stars (pgauss) (!)
+  fit:   0.153292 sec for 277 stars
+  (0.5 msec per star)
Index: /branches/eam_branches/eam_branch_20080706/psphot/doc/outline.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/doc/outline.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/doc/outline.txt	(revision 22157)
@@ -0,0 +1,21 @@
+
+Here is a summary outline of the steps taken by psphotReadout:
+
+* setup (choose recipe, choose readout, define break-point)
+* create mask and weight images if needed, set mask for analysis region
+* psphotImageMedian : construct a background model image and subtract it
+* psphotFindPeaks   : smooth and find the peak pixels from the smoothed image
+  - no pmSource defined yet, only pmPeak
+* psphotSourceStats : create sources for each peak and measure their moments
+  - pmSource defined here, with pixels covering SKY_OUTER_RADIUS
+  - no pmModel is defined yet.
+* psphotBasicDeblend : identify blended sources by proximity and valley depth
+* psphotRoughClass : source classification guess based on moments
+  - set the source.type,mode.
+  - re-calculate moments for saturated stars
+* psphotChoosePSF : define the psf model
+  - this generates model fits to the identified psf stars
+    (one for each tested model)
+  - these models are not kept (seems like a waste, but later fits must be consistent)
+* psphotGuessModels : set the initial PSF model for each object (even EXTENDED)
+  - creates modelPSF entries
Index: /branches/eam_branches/eam_branch_20080706/psphot/doc/output.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/doc/output.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/doc/output.txt	(revision 22157)
@@ -0,0 +1,15 @@
+
+we have several output formats:
+  - TEXT : separate ASCII tables for PSF, EXT, MNT, and NULL
+  - SX : sextractor-style ASCII file
+  - OBJ : dophot-style ASCII file
+  - CMP : old-style elixir format (header + text)
+  - CMF : fits-table
+
+we have several options for organization:
+  - single chip/readout : outroot.ext
+  - split chips : outroot.NN.ext
+  - mef chips : outroot.ext (extensions with names)
+
+  * only CMF will work with MEF model
+
Index: /branches/eam_branches/eam_branch_20080706/psphot/doc/psfmodel.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/doc/psfmodel.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/doc/psfmodel.txt	(revision 22157)
@@ -0,0 +1,62 @@
+
+2007.09.25
+
+  The PSF model has parameters which vary across the image.  This 2D
+  is can be represented as either a polynomial (ordinary or chebychev)
+  or as an image map.  The user requests the maximum X & Y scale
+  (PSF.TREND.NX & PSF.TREND.NY) for the variations, and
+  psphotChoosePSF / pmPSFtry attempt to find the best choice for the
+  scale.  For the polynomial representations, NX and NY are the order
+  of the variation.  For the image map, NX and NY are the size of the
+  image map.
+
+2007.09.21
+
+  there are three places where we can choose to use errors in the fits or not:
+
+  * non-linear fitting of the models to the pixel flux distribution (poissonErrorsPhotLMM)
+  * linear fitting of the models to the pixel flux distribution (poissonErrorsPhotLin)
+  * fitting of the 2D variations in the psf parameters (poissonErrorsParams)
+  * fitting of the 2D variations in the aperture residuals
+
+2007.09.20
+
+  I am upgrading the PSF model to allow the parameter variation to be
+  modeled with pmTrend2D instead of just polynomials.  I am making a
+  list of places to modify the code:
+
+pmPSFAlloc : need a method beyong psfTrendMask to carry in the psf
+options
+
+pmPSF_ModelToFit : no need to change these
+
+update pmPSFBuildSimple to set the parameters of the pmTrend, which
+ever is used.
+
+pmPSFtry.c: some significant re-work!
+
+
+
+pmPSF_IO : need new functions to save / load the trend (psImages)
+
+2006.10.27
+
+  I have been working to fix the PSF modeling in psphot.  The PSF
+  model consists of a flux model for an individual object using an
+  analytical model with a number of parameters.  For a collection of
+  PSF objects, the variation of the parameters as a function of
+  position are then themselves fitted with a model.  The PSF model for
+  a single object consists of a radial profile with a functional form
+  f(z) where a given value of z defines an elliptical coutour of the
+  form z = \frac{x^2}{2\sigma_x^2} + \frac{y^2}{2\sigma_y^2} +
+  \sigma_{xy}xy.
+
+
+
+  The term \sigma_{xy} is difficult to model as a simple function of x
+  and y (eg, a low-order polynomial).  
+
+  A better model can be constructed for
+  \frac{\sigma_{xy}}{(\sigma_x^{-2} + \sigma_y^{-2})^2}, which varies
+  like a^2 \sin 2\theta 
+
Index: /branches/eam_branches/eam_branch_20080706/psphot/doc/psphot.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/doc/psphot.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/doc/psphot.txt	(revision 22157)
@@ -0,0 +1,160 @@
+
+TBD priorities:
+
+- test psImageSmooth / remove psImageSmooth_EAM
+- unify pmSourceLocalSky / pmSourceLocalSky_EAM
+- unify pmSourceMoments_EAM / pmSourceMoments_EAM
+- merge with image data hierarchy stuff
+- better name for psPSF_Test
+
+Defined APIs:
+
+top-level psphot functions : these do not need to be in psModules
+  psMetadata  *psphotArguments ();
+  psImageData *psphotSetup ();
+  psStats     *psphotImageStats ();
+  psArray     *pmPeaksSigmaLimit ();
+  psArray     *psphotSourceStats ();
+  pmPSF       *psphotChoosePSF ();
+  bool         psphotApplyPSF ();
+  bool         psphotFitGalaxies ();
+  void         psphotOutput ();
+
+psphot-specific functions
+  bool         psphotMarkPSF ();
+  bool         psphotSubtractPSF ();
+  int          psphotSortBySN ();
+  int          psphotSaveImage ();
+
+** add the following entries to the psLibSDRS
+
+psLib extra utilities
+  bool         psTimerStart ();		- already in SDRS
+  void         psTimerFree (); 		- already in SDRS
+  bool         psTimerClear ();		- add to psLibSDRS
+  psF64        psTimerMark (); 		- already in SDRS
+  psS32        psLogArguments ();	- now: psArgumentVerbosity in SDRS
+  psS32        psTraceArguments ();	- now: psArgumentVerbosity in SDRS
+  int          psArgumentGet ();	- already in SDRS
+  int          psArgumentRemove ();	- already in SDRS
+  psVector    *psVectorCreate ();	- add to psLibSDRS
+  int          psImageCountPixelMask ();- add to psLibSDRS
+  psVector    *psGetRowVectorFromImage(); - add to psLibSDRS?
+  bool         p_psVectorPrintRow ();     - add to psLibSDRS?
+
+// basic image functions
+  bool         psImageInit ();		- already in SDRS
+  void         psImageSmooth_EAM ();    - test psLib version and drop
+
+// psLine functions
+  psLine      *psLineAlloc ();		- add to psLibSDRS
+  bool         psLineInit (); 		- add to psLibSDRS
+  bool         psLineAdd ();  		- add to psLibSDRS
+
+** compare with current implementation / update psLib
+   we need to define a mechanism to carry the beta values and the
+   parameter limits
+
+// minimize 
+  psBool       p_psMinLM_GuessABP_EAM ();
+  psBool       psMinimizeLMChi2_EAM();
+  psF64        p_psMinLM_dLinear ();  -- not included in pslib.h files
+
+** already in psLib : some need to be cleaned up by MHPCC
+
+// polynomial functions
+  psF64           Polynomial2DEval();
+  psImage        *psBuildSums2D();
+  psPolynomial2D *VectorFitPolynomial2DOrd_EAM();
+  psPolynomial2D *Polynomial2DAlloc();
+  void            psPolynomial2DDump ();
+  psPolynomial2D *RobustFit2D_nomask();
+  psPolynomial2D *RobustFit2D();
+  psVector       *Polynomial2DEvalVector();
+
+  psVector       *psBuildSums1D();
+  void            psPolynomial1DDump ();
+  psF64           Polynomial1DEval_EAM();
+  psVector       *Polynomial1DEvalVector_EAM();
+  psPolynomial1D *Polynomial1DAlloc();
+  psPolynomial1D *VectorFitPolynomial1DOrd_EAM();
+
+** make sure these are all in the psModules SDRS:
+
+pmObjects.h:
+  pmMoments           *pmMomentsAlloc();
+  pmModel             *pmModelAlloc();
+  pmSource            *pmSourceAlloc();
+  psVector            *pmFindVectorPeaks()
+  psArray             *pmFindImagePeaks()
+  psList              *pmCullPeaks()
+  pmSource            *pmSourceLocalSky()
+  bool                 pmSourceMoments()
+  pmPSFClump           pmSourcePSFClump()
+  bool                 pmSourceRoughClass()
+  bool                 pmSourceSetPixelsCircle()
+  pmModel             *pmSourceModelGuess()
+  psArray             *pmSourceContour()
+  bool                 pmSourceFitModel()
+  bool                 pmSourceAddModel()
+  bool                 pmSourceSubModel()
+  int                  pmModelParameterCount ();
+  char                *pmModelGetType ();
+  pmModelType          pmModelSetType ();
+  pmModelFunc          pmModelFunc_GetFunction ();
+  pmModelFlux          pmModelFlux_GetFunction ();
+  pmModelRadius        pmModelRadius_GetFunction ();
+  pmModelLimits        pmModelLimits_GetFunction ();
+  pmModelGuessFunc     pmModelGuessFunc_GetFunction ();
+  pmModelFromPSFFunc   pmModelFromPSFFunc_GetFunction ();
+  pmModelFitStatusFunc pmModelFitStatusFunc_GetFunction ();
+
+model function abstractions:
+  typedef psMinimizeLMChi2Func pmModelFunc;
+  typedef psF64 (*pmModelFlux)(const psVector *params);
+  typedef psF64 (*pmModelRadius)(const psVector *params, double flux);
+  typedef bool (*pmModelLimits)(psVector **beta_lim, psVector **params_min, psVector **params_max);
+  typedef bool (*pmModelGuessFunc)(pmModel *model, pmSource *source);
+  typedef bool (*pmModelFromPSFFunc)(pmModel *modelPSF, pmModel *modelFLT, pmPSF *psf);
+  typedef bool (*pmModelFitStatusFunc)(pmModel *model);
+
+conversions between elliptical shape representations
+  EllipseAxes  EllipseMomentsToAxes ();
+  EllipseShape EllipseAxesToShape ();
+  EllipseAxes  EllipseShapeToAxes ();
+
+output functions
+  bool         pmSourcesWriteText ();  	-- be careful about psImageData...
+  bool         pmSourcesWriteOBJ ();   	-- be careful about psImageData...
+  bool         pmSourcesWriteCMP ();   	-- be careful about psImageData...
+  bool         pmSourcesWriteCMF ();   	-- be careful about psImageData...
+  bool         pmSourcesWriteSX ();    	-- be careful about psImageData...
+  int          pmSourcesDophotType ();
+  bool         pmPeaksWriteText ();
+  bool         pmMomentsWriteText ();
+  bool         pmModelWritePSFs ();
+  bool         pmModelWriteFLTs ();
+  bool         pmModelWriteNULLs ();
+
+// psf utilities
+  pmPSF       *pmPSFAlloc ();
+  pmPSF_Test  *pmPSF_TestAlloc ();
+  pmPSF_Test  *pmPSF_TestModel ();
+  bool         pmPSFFromModels ();
+  pmModel     *pmModelFromPSF ();
+  bool         pmSourcePhotometry ();
+  bool         pmPSFMetricModel ();
+
+// psModule extra utilities
+  bool         pmSourceFitModel_EAM();  -- requires updated version of psMinimize...
+  bool         pmSourceLocalSky_EAM (); -- compare with pmSourceLocalSky & choose
+  bool         pmSourceMoments_EAM();   -- compare with pmSourceMoments & choose
+  bool         pmSourceDefinePixels();  -- be careful about psImageData...
+  bool         pmModelFitStatus ();
+  int          pmSourceDophotType ();
+  psF32        pmConfigLookupF32 ();	-- merge with Paul's image container hierarchy
+
+** this needs to be confronted with the phase2 / image container hierarchy
+
+// psImageData functions
+  psImageData *psImageDataAlloc ();
Index: /branches/eam_branches/eam_branch_20080706/psphot/doc/regions.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/doc/regions.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/doc/regions.txt	(revision 22157)
@@ -0,0 +1,159 @@
+
+I am having some trouble with functions that refer to both subimages
+and regions.  there are inconsistencies between the coordinate
+conventions.  I am going to go through the list of image functions are
+decide if we can choose either 1) external coordinates always refer to
+parent coordinates or 2) something else!
+
+bool psImageSet(const psImage *image, int x, int y, double complex value);
+double complex psImageGet(const psImage *image, int x, int y);
+ - sdrs does not specify
+ - code uses subimage
+
+psRegion psRegionForImage(psImage *image, psRegion in);
+ - sdrs says subimage
+ - code uses subimage
+
+psImage *psFitsReadImage(psImage *out, const psFits *fits, psRegion region, int z);
+ - region here specifies a subimage of the disk image
+ 
+bool psFitsUpdateImage(psFits *fits, const psImage *input, int x0, int y0, int z);
+ - x0,y0 here refer to the disk image coordinates
+
+bool psFitsWriteImage(psFits *fits, psMetadata *header, const psImage
+*input, int depth, const char *extname);
+ - note that the output disk image is truncated to the subimage
+
+bool psFitsInsertImage(psFits *fits, psMetadata *header, const psImage *input, int depth,
+                       const char *extname, bool after);
+ - same as above
+
+psImage *psImageSubset(psImage *image, psRegion region);
+ - code uses region as coordinates in parent image
+ - sdrs does not specify
+ - XXX I think the range tests for region fail if the input is a
+   subimage
+
+psImage *psImageCopy(psImage *output, const psImage *input, psElemType type);
+ - maintains the col0,row0 values
+
+psVector *psImageRow(psVector *out, const psImage *input, int row);
+psVector *psImageCol(psVector *out, const psImage *input, int column);
+ - code uses subimage coordinates for row and column
+
+psVector *psImageSlice()
+psVector* psImageCut()
+ - code uses subimage coordinates (no correction)
+ - sdrs does not specify
+
+psImage *psImageRebin()
+ - mask and input image must currently agree
+
+psImage *psImageTransform()
+ - sdrs is unclear: I think the region defines the output image such
+ that the pixel in the input image *parent* coordinates which
+ corresponds to region.x0,y0 in turn corresponds to the output image
+ 0,0 pixel; the output image always has col0,row0 = 0,0
+
+long psImageCountPixelMask (psImage *mask, psRegion region, psMaskType value);
+ - sdrs says the region corresponds to subimage coordinates
+
+psPolynomial2D *psImageFitPolynomial(psPolynomial2D *coeffs, const psImage *input);
+psImage *psImageEvalPolynomial(psImage *input, const psPolynomial2D *coeffs);
+ - sdrs does not specify
+ - polynomial coordinates should refer to the *parent* image
+
+double complex psImagePixelInterpolate(input, x, y, ...)
+ - sdrs does not specify
+
+int psImageOverlaySection(psImage *image, const psImage *overlay, )
+ - sdrs does not specify
+
+psImage *psPixelsToMask()
+psPixels *psPixelsFromMask()
+ - sdrs does not specify
+
+void psImageMaskRegion()
+void psImageKeepRegion()
+ - sdrs does not specify
+
+void psImageMaskCircle()
+void psImageKeepCircle()
+ - sdrs does not specify
+
+pmReadout *pmSubtractSky(pmReadout *in, psPolynomial2D *poly, psImage *mask, psU8 maskVal, 
+                         int binFactor, psStats *stats, float clipSD);
+ - choice of polynomial coordinates
+
+stats *pmFringeStats (psArray *fringePoints, psImage *image, psMetadata *config);
+ - unspecified
+
+psF32 pmModelEval(pmModel *model, psImage *image, psS32 col, psS32 row);
+ - sdrs says col,row are in *subimage* coords
+
+psArray *pmFindImagePeaks(const psImage *image, float threshold);
+ - does not specify coordinate system
+ - code uses subimage?
+
+bool pmSourceDefinePixels()
+bool pmSourceRedefinePixels()
+ - sdrs does not specify very clearly
+
+
+
+---------
+
+functions which are not affected
+psFitsReadImage()
+psFitsUpdateImage()
+psFitsWriteImage()
+psFitsInsertImage()
+psImageCopy()  -- maintains col0,row0
+psImageRebin() -- mask and image must match
+
+functions which currently use parent coordinates (CORRECT)
+psImageSubset()  -- check on range tests
+psImageMaskRegion()
+psImageKeepRegion()
+psImageMaskCircle()
+psImageKeepCircle()
+pmSourceMoments -- (OK if input peaks are in parent coords)
+
+functions which currently use subimage coordinates (FIX)
+psImageSet()
+psImageGet()
+psRegionForImage() *fixed*
+psImageRow()
+psImageCol()
+psImageSlice()
+psImageCut()
+psImageCountPixelMask ();
+pmModelEval()
+pmFindImagePeaks() *fixed*
+pmSourceDefinePixels() *fixed by psRegionForImage fix*
+pmSourceRedefinePixels() *fixed by psRegionForImage fix*
+
+functions which are unclear (CHECK)
+psImageTransform()
+psImageFitPolynomial()
+psImageEvalPolynomial()
+psImagePixelInterpolate()
+psPixelsToMask()
+pmSubtractSky()
+pmFringeStats()
+
+
+***** one ambiguity: if we have a subimage, but we do not have the
+      parent image data (subimage does not carry dimensions of parent
+      array), then a 0 or negative upper-edge of a region cannot be
+      defined relative to the parent. we can specify it relative to
+      the subimage.  so, for example, given a subimage [32,100:32,100]
+      of a much larger image, then psRegionForImage with 0,0,0,0 as
+      input would return a region with values 32,100:32,100...
+
+
+
+
+
+*** things I've modified
+    psRegionForImage expects parent (not subimage) coordinates
Index: /branches/eam_branches/eam_branch_20080706/psphot/doc/speed.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/doc/speed.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/doc/speed.txt	(revision 22157)
@@ -0,0 +1,129 @@
+
+psphot -g
+psModule -g
+psLib -O2
+on alala:
+
+alala: psphot -vv starfield.fits starfield starfield.cnf -resid starfield.resid.3.fits
+    load data: 0.983568 sec
+    System random seed value used  seed = 25904F7C7454AE25 hex
+    background: 1229.280029 +/- 27.700180
+    image stats: 0.063085 sec
+    System random seed value used  seed = 411E5A0600A49AF3 hex
+    fit background model: 0.580152 sec
+    smooth: 1.842415 sec
+    threshold: 83.100540 DN
+    4356 peaks: 0.571587 sec
+    4230 moments: 3.965099 sec
+    SN range: 3.355829 - 1043.401001
+    identified 107 blended objects (0.121737 sec)
+    selected candidate 300 PSF objects
+    fit flt:   3.682850 sec for 300 sources
+    fit psf:   1.704141 sec for 300 sources
+    fit stats: 0.000884 +/- 0.004499
+    try model PS_MODEL_QGAUSS, ap-fit: -0.007532 +/- 0.004499 : sky bias: -0.717999
+    select psf model: 5.402742 sec
+    selected psf model PS_MODEL_QGAUSS, ApResid: -0.007532 +/- 0.004499
+    fitting pixels with at least 27.700180 object counts
+    built models: 0.991345 (4230 objects)
+    built matrix: 1.472869 (27998 elements)
+    measure ensemble of PSFs: 4.334556
+    fit PSF models: 12.205738 sec for 4230 objects
+    replace unfitted models: 0.021473 sec (4230 objects)
+    measure aperture residuals : 0.451886 sec
+    measure full-frame aperture residual: 0.454032 sec
+    aperture residual: -0.002842 +/- 0.002729 : 0.166072 bias
+    wrote output: 4.648821 sec
+    complete psphot run: 35.334080 sec
+
+
+psphot -g
+psModule -O2
+psLib -O2
+on alala:
+
+alala: psphot -vv starfield.fits starfield starfield.cnf -resid starfield.resid.3.fits
+    load data: 1.001502 sec
+    System random seed value used  seed = 5F43A63F532D4B3F hex
+    background: 1229.280029 +/- 27.700180
+    image stats: 0.063169 sec
+    System random seed value used  seed = F5F5FF99A6578F61 hex
+    fit background model: 0.530384 sec
+    smooth: 1.856414 sec
+    threshold: 83.100540 DN
+    4365 peaks: 0.237292 sec
+    4239 moments: 4.087611 sec
+    SN range: 3.356469 - 1043.400879
+    identified 107 blended objects (0.123372 sec)
+    selected candidate 300 PSF objects
+    fit flt:   3.741444 sec for 300 sources
+    fit psf:   1.676478 sec for 300 sources
+    fit stats: 0.000883 +/- 0.004499
+    try model PS_MODEL_QGAUSS, ap-fit: -0.007532 +/- 0.004499 : sky bias: -0.717985
+    select psf model: 5.458156 sec
+    selected psf model PS_MODEL_QGAUSS, ApResid: -0.007532 +/- 0.004499
+    fitting pixels with at least 27.700180 object counts
+    built models: 0.791613 (4239 objects)
+    built matrix: 1.267909 (28101 elements)
+    measure ensemble of PSFs: 3.551364
+    fit PSF models: 11.240197 sec for 4239 objects
+    replace unfitted models: 0.018051 sec (4239 objects)
+    measure aperture residuals : 0.411856 sec
+    measure full-frame aperture residual: 0.413843 sec
+    aperture residual: -0.002831 +/- 0.002717 : 0.164772 bias
+    wrote output: 4.355429 sec
+    complete psphot run: 33.126283 sec
+
+after reducing indirection in pmSourceMoments:
+    4268 moments: 3.762452 sec
+
+psphot -O2
+psModule -O2
+psLib -O2
+
+alala: psphot -vv starfield.fits starfield starfield.cnf -resid starfield.resid.3.fits
+    load data: 0.484324 sec
+    System random seed value used  seed = 980050732A392EC7 hex
+    background: 1229.280029 +/- 27.700180
+    image stats: 0.058759 sec
+    System random seed value used  seed = 44E1A6EE77B0E9CE hex
+    fit background model: 0.395125 sec
+    smooth: 1.861776 sec
+    threshold: 83.100540 DN
+    4351 peaks: 0.234874 sec
+    4264 moments: 3.719435 sec
+    SN range: 2.023345 - 1043.401123
+    identified 124 blended objects (0.115230 sec)
+    selected candidate 300 PSF objects
+    fit flt:   3.525501 sec for 300 sources
+    fit psf:   1.595031 sec for 300 sources
+    fit stats: 0.000884 +/- 0.004499
+    try model PS_MODEL_QGAUSS, ap-fit: -0.007532 +/- 0.004499 : sky bias: -0.717976
+    select psf model: 5.160442 sec
+    selected psf model PS_MODEL_QGAUSS, ApResid: -0.007532 +/- 0.004499
+    fitting pixels with at least 27.700180 object counts
+    built models: 0.760805 (4264 objects)
+    built matrix: 0.908674 (23703 elements)
+    measure ensemble of PSFs: 3.115425
+    fit PSF models: 10.162397 sec for 4264 objects
+    replace unfitted models: 0.017491 sec (4264 objects)
+    measure aperture residuals : 0.408242 sec
+    measure full-frame aperture residual: 0.410207 sec
+    aperture residual: -0.002908 +/- 0.002734 : 0.170692 bias
+    wrote output: 4.225560 sec
+    complete psphot run: 30.149281 sec
+
+
+    fit PSF models: 10.162397 sec for 4264 objects
+    select psf model: 5.160442 sec
+    wrote output: 4.225560 sec
+    4264 moments: 3.719435 sec
+    measure ensemble of PSFs: 3.115425 sec
+
+    other : 3.5 sec
+
+    fixed per objects : 4 msec / object (17 of 30 sec)
+
+
+** still can re-compile with -DPS_NO_TRACE
+** ASSERTS cannot be compiled out at the moment!
Index: /branches/eam_branches/eam_branch_20080706/psphot/doc/timing.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/doc/timing.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/doc/timing.txt	(revision 22157)
@@ -0,0 +1,84 @@
+
+                                                         -- threadable?
+                                                         |
+  0.000673 sec NPEAK      0.00 msec / item (505)       - ? Find SatStars
+  0.231828 sec NPIX       0.00 msec / item (PIX)       - ? Replace Background
+  0.349591 sec NPIX       0.00 msec / item (2048*4096) - ? Background Model
+  0.609246 sec NPIX       0.00 msec / item (2048*4096) - ? Find Peaks
+  0.733840 sec NPIX       0.00 msec / item (2048*4096) - ? Find Peaks
+  6.076478 sec NPIX       0.00 msec / item (2048*4096) - ? Smooth Images
+  7.683749 sec NPIX       0.00 msec / item (2048*4096) - ? Smooth Image
+  0.003003 sec NPEAK      0.01 msec / item (505)       - ? Source Size
+  0.003934 sec NPEAK      0.01 msec / item (??)        - ? Source Sizes
+  0.019364 sec NPEAK      0.04 msec / item (505)       - ? Find Blends
+  0.045397 sec NPEAK      0.09 msec / item (??)        - ? Replaced Flux
+  0.046370 sec NPEAK      0.09 msec / item (505)       - ? Add Noise
+  0.046680 sec NPEAK      0.09 msec / item (505)       - ? Sub Noise
+  0.050980 sec NPEAK      0.10 msec / item (505)       - ? Replaced Flux
+  2.785612 sec NFOOT      0.27 msec / item (10455)     - ? Find Footprints
+  0.322163 sec NPEAK      0.64 msec / item (505)       - ? Rough Class
+  0.163793 sec NPEAK      0.66 msec / item (??)        - ? Rough Class
+  0.820221 sec NFOOT      1.40 msec / item (587)       - ? Find Footprints
+  2.311492 sec NPEAK      4.58 msec / item (??)        - ? Built Models
+  2.413531 sec NPEAK      4.78 msec / item (505)       - ? Build Models
+  1.635674 sec NPEAK      6.54 msec / item (??)        - ? Measure Moments
+  3.547435 sec NPEAK      7.02 msec / item (505)       - ? Measure Moments
+  8.118224 sec NSRCS      7.81 msec / item (1040)      - ? ApTrend
+  4.165990 sec NPEAK      8.25 msec / item (505)       - ? Fit PSF (linear)
+  4.294968 sec NPEAK      8.50 msec / item (505)       - ? Fit PSF (linear)
+  4.638916 sec NPEAK      9.19 msec / item (??)        - ? Fit PSF (linear)
+154.599482 sec NFOOT     14.79 msec / item (10455)     - ? Cull Footprints
+  1.259951 sec NPSF      17.75 msec / item (71)        - ? Fit PSF (non-linear)
+ 22.009468 sec NFOOT     37.49 msec / item (587)       - ? Cull Footprints: 
+ 15.069461 sec NPEAK     38.34 msec / item (393)       - ? Fit PSF (non-linear) + EXT (27) 
+  2.938324 sec NPSF      41.38 msec / item (71)        - ? Fit EXT (non-linear)
+  2.723019 sec NPSF      53.39 msec / item (51)        - ? PSF Residuals
+
+244.837692 sec    complete psphot run: 
+
+Note:
+
+* PSF Residuals is the most expensive per item, but will always be
+  performed on a limited number of items (typically < 300 PSF stars).
+  The maximum impact is thus limited.
+
+* The non-linear fitting is (not surprisingly) very expensive per
+  item, but again will only be performed on a limited subset (S/N >
+  20?).
+
+* The Cull Footprints is quite expensive per item, and has the serious
+  problem that it must be performed on all peaks.  This will dominate
+  all processing time unless we can address it.
+
+* The linear fitting in this example seems to surprisingly expensive
+  per item, but I think it is not quite accurate.  
+
+* ApTrend is only measured on a subset of the sources (S/N > XX, max
+  total number)
+
+* Measure Moments and Build Models are potentially a substantial drain
+  since these (probably) should be applied to all objects.  Moments is
+  currently being limited to brighter objects.  
+
+Thread strategies:
+
+
+* tests with wbb4.031117_0154.052.fits test -chip 7 : 
+
+* baseline: 
+    grow   : 18.313876 sec
+    merged : 18.772070 sec
+    total  : 26.958080 sec (dt = 8.18)
+
+* upgrade to remove psArrayRemove:
+    grow   : 18.327212 sec
+    merged : 18.778043 sec
+    total  : 25.553486 sec (dt = 6.78; ddt = 1.4)
+
+* recycle idImage:
+    grow   : 18.383393 sec
+    merged : 18.848875 sec 
+    total  : 24.721451 sec (dt = 5.88; ddt = 2.3)
+
+(no real difference??)
+
Index: /branches/eam_branches/eam_branch_20080706/psphot/doc/timing.v2.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/doc/timing.v2.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/doc/timing.v2.txt	(revision 22157)
@@ -0,0 +1,94 @@
+#             msec                   - multithread?
+#             item                   |
+15.558560 sec 37.31    NBRT   417  : y  non-linear fits
+ 0.000736 sec  0.00    NPEAK  550  : ?  satstar blends
+ 0.005947 sec  0.00    NPEAK 1315  : ?  source sizes
+ 0.076172 sec  0.01    NPEAK 7636  : ?  set footprint ids (NPIX?)
+ 0.003383 sec  0.01    NPEAK  550  : ?  source sizes
+ 0.019354 sec  0.04    NPEAK  550  : ?  other blends
+ 0.050572 sec  0.08    NPEAK  627  : ?  set footprint ids
+ 0.049778 sec  0.09    NPEAK  550  : ?  replaced models
+ 0.051736 sec  0.09    NPEAK  550  : ?  add noise
+ 0.051929 sec  0.09    NPEAK  550  : ?  sub noise
+ 0.055094 sec  0.10    NPEAK  550  : ?  replaced models
+ 0.152204 sec  0.20    NPEAK  765  : ?  rough class
+ 0.8      sec  0.26    NPEAK 3100  : ?  merge footprints
+ 0.268983 sec  0.49    NPEAK  550  : ?  rough class
+ 0.7      sec  1.25    NPEAK  560  : ?  cull peaks
+ 2.092846 sec  2.74    NPEAK  765  : ?  moments
+ 8.9      sec  3.05    NPEAK 2914  : ?  cull peaks
+ 3.339909 sec  4.37    NPEAK  765  : ?  built models
+ 2.572153 sec  4.68    NPEAK  550  : ?  build models
+ 3.816792 sec  6.94    NPEAK  550  : ?  moments
+ 9.337548 sec  7.10    NPEAK 1315  : ?  measure magnitudes :  for 1315 objects (643 with apertures)
+ 4.301656 sec  7.82    NPEAK  550  : ?  linear fit
+ 4.429642 sec  8.05    NPEAK  550  : ?  linear fit
+ 4.947403 sec  9.00    NPEAK  550  : ?  linear fit 
+ 0.2      sec  0.02    NPIX 8300 k : ?  found grown footprints: 
+ 0.205680 sec  0.02    NPIX 8300 k : ?  find footprints
+ 0.211664 sec  0.03    NPIX 8300 k : ?  replace background
+ 0.348357 sec  0.04    NPIX 8300 k : ?  subtracted background
+ 0.598805 sec  0.07    NPIX 8300 k : ?  find peaks
+ 0.599952 sec  0.07    NPIX 8300 k : ?  find peaks 
+ 0.723508 sec  0.09    NPIX 8300 k : ?  found footprints: 
+ 1.4      sec  0.17    NPIX 8300 k : ?  found grown footprints: 
+ 0.809106 sec 12.64    NPSF    64  : ?  psf fit
+ 2.155031 sec 34.21    NPSF    63  : ?  full fit
+ 2.592620 sec 74.07    NPSF    35  : ?  psf residuals
+ 5.931090 sec  0.71   sNPIX 8300 k : ?  smoothed signficance
+ 7.780419 sec  0.94   sNPIX 8300 k : ?  smoothed signficance
+12.947970 sec  1.56   sNPIX 8300 k : ?  convolved with grow disc: 
+14.525486 sec  1.75   sNPIX 8300 k : ?  convolved with grow disc: 
+
+Notes:
+
+Note:
+
+* PSF Residuals is the most expensive per item, but will always be
+  performed on a limited number of items (typically < 300 PSF stars).
+  The maximum impact is thus limited.
+
+* The non-linear fitting is (not surprisingly) very expensive per
+  item, but again will only be performed on a limited subset (S/N >
+  20?).
+
+* the linear fits are the next most costly, and will dominate the
+  timing since all sources must be so fit, and multiple times.
+
+* measure magnitudes is expensive, but only runs once
+
+* measure moments is expensive, but only runs once (and/or on a bright subset?)
+
+Adding it up:
+
+GPC1 : 10^11 det for 60*5000* 60 chips --> 5500 det / chip
+     : 23 megapix
+
+* cost per object:
+  
+  56.5 msec -> 310 sec for detection analysis (on kawelu, not optimized)
+
+* cost for bright detections
+
+  ~ 35 sec (for 1000 bright sources)
+
+* cost per megapixel:
+
+  5.5 sec
+
+  -> 126 sec for GPC1
+
+* other overhead (psf construction, background, smoothing)
+
+  -> 20 sec
+
+** total : 490 sec (kawelu, not optimized, 1.0GHz!)
+
+** ~ 220 sec optimized 
+** ~ 100 sec on 2.3GHz machines
+(probably there are other gains on the real hardware: memory bandwidth, ?)
+
+assuming 100sec is all we get, we need ~1.5 cores per chip to keep up at 60sec / exposure (average)
+
+120 cores for ppImage (chip)
+
Index: /branches/eam_branches/eam_branch_20080706/psphot/doc/timing.v3.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/doc/timing.v3.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/doc/timing.v3.txt	(revision 22157)
@@ -0,0 +1,39 @@
+0.000408 sec    found 0 satstar blend peaks, leaving 551 sources: 
+0.002058 sec    measure source sizes for 551 sources: 
+0.003553 sec    measure source sizes for 1302 sources: 
+0.009676 sec    identified 36 blended objects: 
+0.026266 sec    replaced models for 551 objects: 
+0.026892 sec    sub noise for 551 objects: 
+0.026924 sec    add noise for 551 objects: 
+0.029050 sec    replaced models for 551 objects: 
+0.032975 sec    set footprint array IDs: 
+0.047061 sec    set footprint array IDs: 
+0.080533 sec    rough classification: 
+0.107024 sec    replace background flux : 
+0.111200 sec    found 631 footprints: 
+0.132417 sec    rough classification: 
+0.182467 sec    subtracted background model: 
+0.2      sec    cull peaks
+0.297289 sec    2676 peaks: 
+0.3      sec    rest of foot
+0.300492 sec    849 peaks: 
+0.364908 sec    found 7613 footprints: 
+0.393265 sec    measure ensemble of PSFs: 
+0.405241 sec    build median image: 
+0.454711 sec    measure ensemble of PSFs: 
+0.680027 sec    measure ensemble of PSFs: 
+0.900696 sec    751 sources, 290 moments, 9 failed: 
+1.1 sec    rest of footpr
+1.147541 sec    built models for 551 objects: 
+1.289976 sec    generate residuals for 35 objects: 
+1.456877 sec    built models for 751 objects: 
+1.5      sec    select psf model (ex resid)
+1.661166 sec    551 sources, 551 moments, 8 failed: 
+2.988601 sec    built smoothed signficance image: 
+3.677652 sec    built smoothed signficance image: 
+4.354904 sec    measure magnitudes :  for 1302 objects (634 with apertures)
+5.8 sec    cull peaks
+6.594287 sec    convolved with grow disc: 
+7.413455 sec    convolved with grow disc: 
+7.616990 sec    fit models:  for 417 objects (354 psf, 31 ext, 32 failed, 134 skipped)
+55.393567 sec    complete psphot run: 
Index: /branches/eam_branches/eam_branch_20080706/psphot/doc/versions.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/doc/versions.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/doc/versions.txt	(revision 22157)
@@ -0,0 +1,49 @@
+
+2006.02.02
+  I'm tagging psphot after I updated it to work with the readouts
+  using the structures being used for ppImage.  In this release, I
+  have also moved to autoconf for psphot, and the psphotReadout
+  portion is being defined as a library against which the other
+  elements are linked.  It should be possible to use this library with
+  ppImage, but I have not yet tested that.  
+
+  link psphot_dev_08 against psLib:eam_rel9_b2 and
+  psModule:eam_rel9_b2.
+
+2006.01.18
+  the tag below (psphot_dev_07) was moved to a later release.  It
+  should be used with psLib:eam_rel9_b1 from 2006/1/18 and
+  psModule:eam_rel9_p0.  
+
+2005.12.23
+  psphot_dev_07 should be compiled against psLib:eam_rel9_b1 and
+  psModule:eam_rel9_b1.  This version now handles the ensemble PSF
+  fitting, detection of blended sources, fitting of 'blends' (groups
+  of PSFs with distinct be 'blended' peaks) and 'blobs' (unresolved
+  extended objects, with a test for double PSF vs extended source
+  model.
+
+2005.11.25
+
+  psphot_dev_05 should be compiled against psLib tag eam_rel8_b2 and
+  psModules tag eam_rel8_b1.  this version removes all _EAM versions
+  of psLib and psModules code.  It now uses the psModules version of
+  the object code.
+
+  psphot_dev_04 should be compiled against psLib tag eam_rel8_b2 this
+  version removes all _EAM versions of psLib code, so that it is now
+  consistent with psLib.v8 (fixes are pushed into the eam_rel8_b2
+  branch).
+
+2005.11.16
+
+  psphot_dev_03 should be compiled against psLib tag eam_rel8_b1, a
+  branch starting from psLib rel8_0.  I needed to make some minor
+  fixes in psLib to work successfully with psLib rel8_0.
+
+  psphot_dev_02 was getting segfaults from a bug added into psLib in
+  rel8_0 in p_psVectorClippedStats.  I also fixed some errors arising
+  from changing types (argument to psMetadataParseConfig).
+
+
+
Index: /branches/eam_branches/eam_branch_20080706/psphot/psphot-config.in
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/psphot-config.in	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/psphot-config.in	(revision 22157)
@@ -0,0 +1,87 @@
+#! /bin/sh
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+top_srcdir=@ABS_SRCDIR@
+
+usage()
+{
+    cat <<EOF
+Usage: psphot-config [OPTION]
+
+Known values for OPTION are:
+
+  --prefix		print psphot installation prefix
+  --libs		print library linking information
+  --cflags		print pre-processor and compiler flags
+  --build-libs		print library linking information to the build (non-installed) version
+  --build-cflags	print pre-processor and compiler flags to the build (non-installed) version
+  --help		display this help and exit
+  --version		output version information
+
+EOF
+
+    exit $1
+}
+
+if test $# -eq 0; then
+    usage 1
+fi
+
+cflags=false
+libs=false
+
+while test $# -gt 0; do
+    case "$1" in
+    -*=*) optarg=`echo "$1" | sed 's/[-_a-zA-Z0-9]*=//'` ;;
+    *) optarg= ;;
+    esac
+
+    case "$1" in
+    --prefix=*)
+	prefix=$optarg
+	;;
+
+    --prefix)
+	echo $prefix
+	;;
+
+    --version)
+	echo @VERSION@
+	exit 0
+	;;
+
+    --help)
+	usage 0
+	;;
+
+    --cflags)
+       	echo -I${includedir} @PSPHOT_CFLAGS@
+       	;;
+
+    --libs)
+       	echo -L${libdir} -lpsphot @PSPHOT_LIBS@
+       	;;
+
+    --build-cflags)
+       	echo @SRCINC@ @PSPHOT_CFLAGS@
+       	;;
+
+    --build-libs)
+       	echo -L@ABS_SRCDIR@/src/.libs -lpsphot @PSPHOT_LIBS@
+       	;;
+
+    --deps)
+       	echo @PSPHOT_LIBS@
+       	;;
+    *)
+	usage
+	exit 1
+	;;
+    esac
+    shift
+done
+
+exit 0
Index: /branches/eam_branches/eam_branch_20080706/psphot/psphot.pc.in
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/psphot.pc.in	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/psphot.pc.in	(revision 22157)
@@ -0,0 +1,12 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libpsphot
+Description: Pan-STARRS Photometry Library
+Version: @VERSION@
+Requires: pslib psmodules
+Libs: -L${libdir} -lpsphot
+Libs.private: @PSPHOT_LIBS@
+Cflags: -I${includedir}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/.cvsignore	(revision 22157)
@@ -0,0 +1,18 @@
+*.o
+*.lo
+.libs
+.deps
+Makefile
+Makefile.in
+libpsphot.la
+psphot
+psphotTest
+psphot.loT
+psphotErrorCodes.h
+psphotErrorCodes.c
+config.h
+config.h.in
+stamp-h1
+
+polyfitTest
+growthTest
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/Makefile.am	(revision 22157)
@@ -0,0 +1,117 @@
+
+lib_LTLIBRARIES = libpsphot.la
+libpsphot_la_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(PSPHOT_CFLAGS)
+
+bin_PROGRAMS = psphot
+
+psphot_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(PSPHOT_CFLAGS)
+psphot_LDFLAGS = $(PSLIB_LIBS) $(PSMODULE_LIBS) $(PSPHOT_LIBS)
+psphot_LDADD = libpsphot.la
+
+psphot_SOURCES = \
+        psphot.c                \
+	psphotArguments.c	\
+	psphotCleanup.c	   	\
+	psphotImageLoop.c	\
+	psphotMosaicChip.c	\
+	psphotSetMaskBits.c	\
+	psphotParseCamera.c
+
+libpsphot_la_SOURCES = \
+	psphotErrorCodes.c	       \
+	pmFootprint.c		       \
+	pmFootprintArrayGrow.c	       \
+	pmFootprintArraysMerge.c       \
+	pmFootprintIDs.c	       \
+	pmFootprintsFind.c	       \
+	pmFootprintsFindAtPoint.c      \
+	pmFootprintsAssignPeaks.c      \
+	pmDetections.c		       \
+	pmSpan.c		       \
+	psphotCullPeaks.c	       \
+	psphotVersion.c		       \
+	psphotModelGroupInit.c	       \
+	psphotMaskReadout.c	       \
+	psphotDefineFiles.c	       \
+	psphotReadout.c		       \
+	psphotModelBackground.c	       \
+	psphotSubtractBackground.c     \
+	psphotFindDetections.c	       \
+	psphotFindPeaks.c	       \
+	psphotFindFootprints.c	       \
+	psphotSignificanceImage.c      \
+	psphotSourceStats.c	       \
+	psphotRoughClass.c	       \
+	psphotBasicDeblend.c	       \
+	psphotChoosePSF.c	       \
+	psphotGuessModels.c            \
+	psphotFitSourcesLinear.c       \
+	psphotBlendFit.c	       \
+	psphotReplaceUnfit.c	       \
+	psphotApResid.c		       \
+	psphotMagnitudes.c	       \
+	psphotSkyReplace.c	       \
+	psphotEvalPSF.c		       \
+	psphotEvalFLT.c		       \
+	psphotSourceFits.c	       \
+	psphotRadiusChecks.c	       \
+	psphotOutput.c		       \
+	psphotFakeSources.c	       \
+	psphotModelWithPSF.c           \
+	psphotExtendedSourceAnalysis.c \
+	psphotExtendedSourceFits.c     \
+	psphotRadialProfile.c	       \
+	psphotPetrosian.c	       \
+	psphotIsophotal.c	       \
+	psphotAnnuli.c		       \
+	psphotKron.c		       \
+	psphotKernelFromPSF.c	       \
+	psphotPSFConvModel.c	       \
+	psphotModelTest.c	       \
+	psphotFitSet.c		       \
+	psphotSourceFreePixels.c       \
+	psphotSummaryPlots.c           \
+	psphotMergeSources.c	       \
+	psphotLoadPSF.c	               \
+	psphotReadoutCleanup.c	       \
+	psphotSourcePlots.c	       \
+	psphotRadialPlot.c	       \
+	psphotDeblendSatstars.c	       \
+	psphotMosaicSubimage.c	       \
+	psphotMakeResiduals.c	       \
+	psphotSourceSize.c	       \
+	psphotDiagnosticPlots.c	       \
+	psphotMakeFluxScale.c	       \
+	psphotCheckStarDistribution.c  \
+	psphotAddNoise.c
+
+# dropped? psphotGrowthCurve.c
+
+include_HEADERS = \
+	psphot.h \
+	pmFootprint.h \
+	psphotErrorCodes.h
+
+noinst_HEADERS = \
+	psphotInternal.h \
+	psphotStandAlone.h
+
+clean-local:
+	-rm -f TAGS
+
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
+
+# Error codes.
+BUILT_SOURCES = psphotErrorCodes.h psphotErrorCodes.c
+CLEANFILES = psphotErrorCodes.h psphotErrorCodes.c
+EXTRA_DIST = psphotErrorCodes.dat psphotErrorCodes.c.in psphotErrorCodes.h.in \
+	models/pmModel_STRAIL.c \
+	models/pmModel_TEST1.c
+
+psphotErrorCodes.h : psphotErrorCodes.dat psphotErrorCodes.h.in
+	$(ERRORCODES) --data=psphotErrorCodes.dat --outdir=. psphotErrorCodes.h
+
+psphotErrorCodes.c : psphotErrorCodes.dat psphotErrorCodes.c.in psphotErrorCodes.h
+	$(ERRORCODES) --data=psphotErrorCodes.dat --outdir=. psphotErrorCodes.c
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/models/pmModel_STRAIL.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/models/pmModel_STRAIL.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/models/pmModel_STRAIL.c	(revision 22157)
@@ -0,0 +1,618 @@
+
+/******************************************************************************
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = 1 / SigmaX;
+    params->data.F32[5] = 1 / SigmaY;
+    params->data.F32[6] = Sxy;
+    params->data.F32[7] = length;
+    params->data.F32[8] = theta;
+*****************************************************************************/
+
+# define PM_MODEL_FUNC            pmModelFunc_STRAIL
+# define PM_MODEL_FLUX            pmModelFlux_STRAIL
+# define PM_MODEL_GUESS           pmModelGuess_STRAIL
+# define PM_MODEL_LIMITS          pmModelLimits_STRAIL
+# define PM_MODEL_RADIUS          pmModelRadius_STRAIL
+# define PM_MODEL_FROM_PSF        pmModelFromPSF_STRAIL
+# define PM_MODEL_PARAMS_FROM_PSF pmModelParamsFromPSF_STRAIL
+# define PM_MODEL_FIT_STATUS      pmModelFitStatus_STRAIL
+
+psF32 PM_MODEL_FUNC(psVector *deriv,
+                         const psVector *params,
+                         const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 trailLength = PAR[7];
+    psF32 theta = PAR[8];
+
+    psF32 x0 = PAR[2];  //streak center
+    psF32 y0 = PAR[3];  //streak center
+
+    //S values (1/sigma for x and y case, sigma for xy case)
+    psF32 sx = PAR[4];
+    psF32 sy = PAR[5];
+    psF32 sxy = PAR[6];
+
+    psF32 sinT=sin(theta);
+    psF32 cosT=cos(theta);
+    psF32 sin2T=sin(2.0*theta);
+    psF32 cos2T=cos(2.0*theta);
+
+    //    printf("Trying object at %4.1f,%4.1f with length %3.1f and angle %1.3f\r", x0, y0, length, theta);
+
+    //current location relative to trail center
+    psF32 X  = x->data.F32[0] - x0;
+    psF32 Y  = x->data.F32[1] - y0;
+
+    //x' and y' location (trail-orienter coords)
+    psF32 xs = X*cosT + Y*sinT;
+    psF32 ys = -1.0*X*sinT + Y*cosT;
+
+    //initialize variables to be changed below
+    psF32 x1 = 0;
+    psF32 y1 = 0;
+    psF32 px = 0;
+    psF32 py = 0;
+    psF32 z  = 0;
+    psF32 zx = 0;
+    psF32 t  = 0;
+    psF32 tx = 0;
+    psF32 r  = 0;
+    psF32 rx = 0;
+    psF32 f  = 0;
+
+    psF32 sxrot = 0;
+    psF32 syrot = 0;
+    psF32 sxyrot = 0;
+    psF32 dsxrot = 0;
+    psF32 dsyrot = 0;
+    psF32 dsxyrot = 0;
+
+    //    psF32 Rx = 0;
+    //    psF32 Ry = 0;
+    //    psF32 Rxy = 0;
+
+
+    //calculate new S values (1/sigma) for rotated frame
+    psF32 sxrotsq = PS_SQR(cosT*sx) + PS_SQR(sinT*sy) + cosT*sinT*sxy;
+    psF32 syrotsq = PS_SQR(cosT*sy) + PS_SQR(sinT*sx) - cosT*sinT*sxy;
+
+    //    psF32 testtwo=10.1;
+    //    psF32 testone=fabsf(testtwo);
+    //    fprintf (stderr, "Test: %f is the absolute value of %f?\n",testone,testtwo);
+    if (sxrotsq<0) {
+      sxrot = sqrt(-(sxrotsq));
+      syrot = sqrt(syrotsq);
+      fprintf (stderr, "error in sxrotsq: Neg,  sxrotsq=%f sx=%f sy=%f sxy=%f theta=%f\n",sxrotsq,sx,sy,sxy,theta);
+    } else if (syrotsq<0) {
+      sxrot = sqrt(sxrotsq);
+      syrot = sqrt(-(syrotsq));
+      fprintf (stderr, "error in syrotsq: Neg,  syrotsq=%f sx=%f sy=%f sxy=%f theta=%f\n",syrotsq,sx,sy,sxy,theta);
+    } else if (sxrotsq==0){
+      sxrot = 0.01;
+      syrot = sqrt(syrotsq);
+      fprintf (stderr, "error in sxrotsq: Zero,  sxrotsq=%f \n",sxrotsq);
+    } else if (syrotsq==0) {
+      syrot = 0.01;
+      sxrot = sqrt(sxrotsq);
+      fprintf (stderr, "error in syrotsq: Zero,  syrotsq=%f \n",syrotsq);
+      //      return(0);
+    }else {
+      sxrot = sqrt(sxrotsq);
+      syrot = sqrt(syrotsq);
+    }
+
+    sxyrot = sxy*cos2T + (PS_SQR(sy) - PS_SQR(sx))*sin2T;
+
+      if (isnan(sxrot)) {
+        fprintf (stderr, "error in sxrot  syrot=%f sx=%f sy=%f sxy=%f cosT=%f sinT=%f\n",syrot,sx,sy,sxy,cosT,sinT);
+      } else if (isnan(syrot)) {
+        fprintf (stderr, "error in syrot  sxrot=%f sx=%f sy=%f sxy=%f cosT=%f sinT=%f\n",sxrot,sx,sy,sxy,cosT,sinT);
+      }
+
+    //calculate length of pipe (not of trail motion)
+    psF32 length = trailLength - 2.0*2.0/sxrot;
+
+
+    if (xs < length/(-2.0)) {
+      x1 = (xs+length/2.0)*cosT - ys*sinT; //Endpoint1
+      y1 = (xs+length/2.0)*sinT + ys*cosT; //Endpoint1
+      //1.6 factor comes from by-eye testing of fits, sqrt from the later squaring of it
+      //1.6 ~= phi (golden mean)...coincidence?
+      px = sxrot*x1/sqrt(1.6);
+      py = syrot*y1;
+
+      //first find out what the falloff in the x direction is...
+      zx  = 0.5*PS_SQR(px);
+      tx = 1 + zx + zx*zx/2.0 + zx*zx*zx/6.0;
+      rx = 1.0 / (tx + zx*zx*zx*zx/24.0);
+
+      //...and now in the y direction
+      z  = 0.5*PS_SQR(py)+sxyrot*x1*y1;
+      t  = 1 + z + z*z/2.0;
+      r  = 1.0 / (t + z*z*z/6.0); /* exp (-Z) */
+      f  = PAR[1]*rx*r + PAR[0];
+
+    } else if (xs > length/2.0){
+      x1 = (xs-length/2.0)*cosT - ys*sinT; //Endpoint2
+      y1 = (xs-length/2.0)*sinT + ys*cosT; //Endpoint2
+      px = sxrot*x1/sqrt(1.6);
+      py = syrot*y1;
+      zx  = 0.5*PS_SQR(px);
+      tx = 1 + zx + zx*zx/2.0 + zx*zx*zx/6.0;
+      rx = 1.0 / (tx + zx*zx*zx*zx/24.0);
+
+      z  = 0.5*PS_SQR(py)+sxyrot*x1*y1;
+      t  = 1 + z + z*z/2.0;
+      r  = 1.0 / (t + z*z*z/6.0); /* exp (-Z) */
+      f  = PAR[1]*rx*r + PAR[0];
+
+      if (isnan(r)) {
+        fprintf (stderr, "error in +r  t=%f z=%f\n",t,z);
+      }
+
+    } else {
+      x1 = -ys*sinT;
+      y1 = ys*cosT;
+      px = sx*x1;
+      py = sy*y1;
+      z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + sxy*x1*y1;
+      t  = 1 + z + z*z/2.0;
+      r  = 1.0 / (t + z*z*z/6.0); /* exp (-Z) */
+      rx = 1.0;  //so that dF/dF0 can be generalized
+      f  = PAR[1]*r + PAR[0];
+
+      if (isnan(r)) {
+        fprintf (stderr, "error in midr  t=%f z=%f\n",t,z);
+      }
+    }
+
+
+    //ok...so this is df/dPAR[X]
+    if (deriv != NULL) {
+        psF32 q = 1;
+        //stable
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r * rx;
+
+          if (isnan(deriv->data.F32[0])) {
+            fprintf (stderr, "error in deriv0\n");
+          } else if (isnan(deriv->data.F32[1])) {
+            fprintf (stderr, "error in deriv1 r=%f rx=%f\n",r,rx);
+          }
+
+        dsxrot = 2.0*PS_SQR(sy)*sinT*cosT - 2.0*PS_SQR(sx)*sinT*cosT + sxy*(PS_SQR(cosT)-PS_SQR(sinT));
+        dsyrot = 2.0*PS_SQR(sx)*sinT*cosT - 2.0*PS_SQR(sy)*sinT*cosT - sxy*(PS_SQR(cosT)-PS_SQR(sinT));
+        dsxyrot = 2.0*cos2T*(PS_SQR(sy)-PS_SQR(sx)) - 2.0*sxy*sin2T;
+
+          if (isnan(dsxrot)) {
+            fprintf (stderr, "error in dsxrot\n");
+          } else if (isnan(dsyrot)) {
+            fprintf (stderr, "error in dsyrot\n");
+          } else if (isnan(dsxyrot)) {
+            fprintf (stderr, "error in dsxyrot\n");
+          }
+
+        //initialize variables definied in the if statements
+        //      psF32 XXX=0;
+        //      psF32 YYY=0;
+
+
+        // variable over piecewise func
+        // change the endcaps to be 4th order gaussians with sxrot_fit=1.6*sxrot
+        // y' direction can ge adequately modelled by a 3rd order gaussian with syrot
+        if (xs < length/(-2.0)) {
+
+          q=PAR[1]*r*rx;
+          deriv->data.F32[2] = -q*(rx*tx/1.6*x1*PS_SQR(sxrot) + r*t*y1*sxyrot);
+          deriv->data.F32[3] = -q*r*t*(y1*PS_SQR(syrot) + x1*sxyrot);
+          deriv->data.F32[4] = -q*(rx*tx*x1*sx*PS_SQR(cosT)/1.6*(x1+2.0*cosT/sxrot) + r*t*y1*sx*sinT*(y1*sinT+2.0*PS_SQR(syrot)*PS_SQR(cosT)/(sxrot*sxrot*sxrot)) + 2.0*r*t*sx*(-1.0*x1*y1*sin2T+y1*sxyrot*(cosT*cosT*cosT)/(sxrot*sxrot*sxrot)+x1*sxyrot*(sinT*cosT*cosT)/(sxrot*sxrot*sxrot)));
+          deriv->data.F32[5] = -q*(rx*tx*x1*sy*PS_SQR(sinT)/1.6*(x1+2.0*cosT/sxrot) + r*t*y1*sy*(y1*PS_SQR(cosT)+2.0*PS_SQR(syrot)*(sinT*sinT*sinT)/(sxrot*sxrot*sxrot)) + 2.0*r*t*sy*(x1*y1*sin2T+y1*sxyrot*(sinT*sinT*cosT)/(sxrot*sxrot*sxrot)+x1*sxyrot*(sinT*sinT*sinT)/(sxrot*sxrot*sxrot)));
+          deriv->data.F32[6] = -q*(rx*tx*x1*cosT*sinT/3.2*(x1+2.0*cosT/sxrot) + r*t*y1*cosT*sinT/2.0*(-y1+2.0*PS_SQR(syrot)*sinT/(sxrot*sxrot*sxrot)) + r*t*(x1*y1*cos2T+y1*sxyrot*sinT*cosT*cosT/(sxrot*sxrot*sxrot)+x1*sxyrot*sinT*sinT*cosT/(sxrot*sxrot*sxrot)));
+          deriv->data.F32[7] = -q*(rx*tx*PS_SQR(sxrot)*x1*cosT/3.2 + r*t*PS_SQR(syrot)*y1*sinT/2.0 + r*t*sxyrot/2.0*(y1*cosT+x1*sinT));
+          deriv->data.F32[8] = -q*(rx*tx*x1/3.2*(x1*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))+PS_SQR(sxrot)*(2.0*cosT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot) - sinT*length)) + r*t*y1/2.0*(y1*(2.0*sinT*cosT*(PS_SQR(sx)-PS_SQR(sy))-sxy*(PS_SQR(cosT)-PS_SQR(sinT)))+PS_SQR(syrot)*(2.0*sinT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot)+cosT*length)) + r*t*(2.0*x1*y1*(cos2T*(PS_SQR(sy)-PS_SQR(sx))-sxy*sin2T)+y1*sxyrot/2.0*(2.0*cosT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot)-length*sinT)+x1*sxyrot/2.0*(2.0*sinT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot)+length*cosT)));
+
+          /*      if (isnan(deriv->data.F32[2])) {
+            fprintf (stderr, "error in deriv2\n");
+          } else if (isnan(deriv->data.F32[3])) {
+            fprintf (stderr, "error in deriv3\n");
+          } else if (isnan(deriv->data.F32[4])) {
+            fprintf (stderr, "error in deriv4\n");
+          } else if (isnan(deriv->data.F32[5])) {
+            fprintf (stderr, "error in deriv5\n");
+          } else if (isnan(deriv->data.F32[6])) {
+            fprintf (stderr, "error in deriv6\n");
+          } else if (isnan(deriv->data.F32[7])) {
+            fprintf (stderr, "error in deriv7\n");
+          } else if (isnan(deriv->data.F32[8])) {
+            fprintf (stderr, "error in deriv8\n");
+          }
+          */
+
+
+
+        } else if (xs > length/2.0){
+
+          q=PAR[1]*r*rx;
+          deriv->data.F32[2] = -q*(rx*tx/1.6*x1*PS_SQR(sxrot) + r*t*y1*sxyrot);
+          deriv->data.F32[3] = -q*r*t*(y1*PS_SQR(syrot) + x1*sxyrot);
+          deriv->data.F32[4] = -q*(rx*tx*x1*sx*PS_SQR(cosT)/1.6*(x1-2.0*cosT/sxrot) + r*t*y1*sx*sinT*(y1*sinT-2.0*PS_SQR(syrot)*PS_SQR(cosT)/(sxrot*sxrot*sxrot)) + 2.0*r*t*sx*(-1.0*x1*y1*sin2T-y1*sxyrot*(cosT*cosT*cosT)/(sxrot*sxrot*sxrot)-x1*sxyrot*(sinT*cosT*cosT)/(sxrot*sxrot*sxrot)));
+          deriv->data.F32[5] = -q*(rx*tx*x1*sy*PS_SQR(sinT)/1.6*(x1-2.0*cosT/sxrot) + r*t*y1*sy*(y1*PS_SQR(cosT)-2.0*PS_SQR(syrot)*(sinT*sinT*sinT)/(sxrot*sxrot*sxrot)) + 2.0*r*t*sy*(x1*y1*sin2T-y1*sxyrot*(sinT*sinT*cosT)/(sxrot*sxrot*sxrot)-x1*sxyrot*(sinT*sinT*sinT)/(sxrot*sxrot*sxrot)));
+          deriv->data.F32[6] = -q*(rx*tx*x1*cosT*sinT/3.2*(x1-2.0*cosT/sxrot) + r*t*y1*cosT*sinT/2.0*(-y1-2.0*PS_SQR(syrot)*sinT/(sxrot*sxrot*sxrot)) + r*t*(x1*y1*cos2T-y1*sxyrot*sinT*cosT*cosT/(sxrot*sxrot*sxrot)-x1*sxyrot*sinT*sinT*cosT/(sxrot*sxrot*sxrot)));
+          deriv->data.F32[7] = q*(rx*tx*PS_SQR(sxrot)*x1*cosT/3.2 + r*t*PS_SQR(syrot)*y1*sinT/2.0 + r*t*sxyrot/2.0*(y1*cosT+x1*sinT));
+          deriv->data.F32[8] = -q*(rx*tx*x1/3.2*(x1*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))-PS_SQR(sxrot)*(2.0*cosT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot) - sinT*length)) + r*t*y1/2.0*(y1*(2.0*sinT*cosT*(PS_SQR(sx)-PS_SQR(sy))-sxy*(PS_SQR(cosT)-PS_SQR(sinT)))-PS_SQR(syrot)*(2.0*sinT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot)+cosT*length)) + r*t*(2.0*x1*y1*(cos2T*(PS_SQR(sy)-PS_SQR(sx))-sxy*sin2T)-y1*sxyrot/2.0*(2.0*cosT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot)-length*sinT)-x1*sxyrot/2.0*(2.0*sinT*(2.0*sinT*cosT*(PS_SQR(sy)-PS_SQR(sx))+sxy*(PS_SQR(cosT)-PS_SQR(sinT)))/(sxrot*sxrot*sxrot)+length*cosT)));
+
+
+          /*      if (isnan(deriv->data.F32[2])) {
+            fprintf (stderr, "error in deriv2\n");
+          } else if (isnan(deriv->data.F32[3])) {
+            fprintf (stderr, "error in deriv3\n");
+          } else if (isnan(deriv->data.F32[4])) {
+            fprintf (stderr, "error in deriv4\n");
+          } else if (isnan(deriv->data.F32[5])) {
+            fprintf (stderr, "error in deriv5\n");
+          } else if (isnan(deriv->data.F32[6])) {
+            fprintf (stderr, "error in deriv6\n");
+          } else if (isnan(deriv->data.F32[7])) {
+            fprintf (stderr, "error in deriv7\n");
+          } else if (isnan(deriv->data.F32[8])) {
+            fprintf (stderr, "error in deriv8\n");
+          }
+          */
+
+        } else {
+          // this does not change from before, as the y' falloff can be modelled by the standard 3rd order gaussian
+          // note difference from a pure gaussian: q = PAR[1]*r
+          q = PAR[1]*r*r*t;
+          deriv->data.F32[2] = q*(PS_SQR(sx*sinT)*x1 - PS_SQR(sy)*y1*cosT*sinT - sxy*x1*sinT*cosT + sxy*y1*PS_SQR(sinT));
+          deriv->data.F32[3] = q*(-1*PS_SQR(sx)*x1*sinT*cosT + PS_SQR(sy*cosT)*y1 + sxy*x1*PS_SQR(cosT) - sxy*y1*sinT*cosT);
+          deriv->data.F32[4] = -q*sx*PS_SQR(x1);
+          deriv->data.F32[5] = -q*sy*PS_SQR(y1);
+          deriv->data.F32[6] = -q*x1*y1;
+          deriv->data.F32[7] = 0;
+          deriv->data.F32[8] = -q*( PS_SQR(sx)*x1*(xs*sinT - ys*cosT) + PS_SQR(sy)*y1*(xs*cosT - ys*sinT) + sxy*x1*(xs*cosT - ys*sinT) + sxy*y1*(xs*sinT - ys*cosT) );
+
+          if (isnan(deriv->data.F32[2])) {
+            fprintf (stderr, "error in deriv2\n");
+          } else if (isnan(deriv->data.F32[3])) {
+            fprintf (stderr, "error in deriv3\n");
+          } else if (isnan(deriv->data.F32[4])) {
+            fprintf (stderr, "error in deriv4\n");
+          } else if (isnan(deriv->data.F32[5])) {
+            fprintf (stderr, "error in deriv5\n");
+          } else if (isnan(deriv->data.F32[6])) {
+            fprintf (stderr, "error in deriv6\n");
+          } else if (isnan(deriv->data.F32[7])) {
+            fprintf (stderr, "error in deriv7\n");
+          } else if (isnan(deriv->data.F32[8])) {
+            fprintf (stderr, "error in deriv8\n");
+          }
+
+
+        }
+    }
+    return(f);
+}
+
+//fixed
+// XXX this needs to apply the axis ratio limits to prevent avoid solutions
+# define AR_MAX 20.0
+# define AR_RATIO 0.99
+bool PM_MODEL_LIMITS (psMinConstraintMode mode, int nParam, float *params, float *beta) {
+
+    float beta_lim = 0;
+    float params_min = 0;
+    float params_max = 0;
+    float f1, f2, q1;
+    float q2 = 0;
+
+    // we need to calculate the limits for SXY specially
+    if (nParam == PM_PAR_SXY) {
+        f1 = 1.0 / PS_SQR(params[PM_PAR_SYY]) + 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        f2 = 1.0 / PS_SQR(params[PM_PAR_SYY]) - 1.0 / PS_SQR(params[PM_PAR_SXX]);
+        q1 = PS_SQR(f1)*AR_RATIO - PS_SQR(f2);
+        assert (q1 > 0);
+        q2  = 0.5*sqrt (q1);
+    }
+
+    switch (mode) {
+      case PS_MINIMIZE_BETA_LIMIT:
+        switch (nParam) {
+          case PM_PAR_SKY:  beta_lim = 1000;   break;
+          case PM_PAR_I0:   beta_lim = 10000;    break; // too small?
+          case PM_PAR_XPOS: beta_lim = 50;     break;
+          case PM_PAR_YPOS: beta_lim = 50;     break;
+          case PM_PAR_SXX:  beta_lim = 0.5;    break;
+          case PM_PAR_SYY:  beta_lim = 0.5;    break;
+          case PM_PAR_SXY:  beta_lim = 1.0;    break;  // set this to q2?
+          case 7:           beta_lim = 10.0;     break;
+          case 8:           beta_lim = M_PI/6.0; break;
+
+          default:
+            psAbort("invalid parameter %d for beta test", nParam);
+        }
+        if (fabs(beta[nParam]) > fabs(beta_lim)) {
+            beta[nParam] = (beta[nParam] > 0) ? fabs(beta_lim) : -fabs(beta_lim);
+            return false;
+        }
+        return true;
+      case PS_MINIMIZE_PARAM_MIN:
+        switch (nParam) {
+          case PM_PAR_SKY:  params_min = -1000; break;
+          case PM_PAR_I0:   params_min =     0; break;
+          case PM_PAR_XPOS: params_min =  -100; break;
+          case PM_PAR_YPOS: params_min =  -100; break;
+          case PM_PAR_SXX:  params_min =   0.5; break;
+          case PM_PAR_SYY:  params_min =   0.5; break;
+          case PM_PAR_SXY:  params_min =  -5.0; break; // set this to -q2?
+          case 7:           params_min =     0;  break;
+          case 8:           params_min = -1*M_PI; break;
+
+          default:
+            psAbort("invalid parameter %d for param min test", nParam);
+        }
+        if (params[nParam] < params_min) {
+            params[nParam] = params_min;
+            return false;
+        }
+        return true;
+      case PS_MINIMIZE_PARAM_MAX:
+        switch (nParam) {
+          case PM_PAR_SKY:  params_max =   1e5; break;
+          case PM_PAR_I0:   params_max =   1e8; break;
+          case PM_PAR_XPOS: params_max =   1e4; break;
+          case PM_PAR_YPOS: params_max =   1e4; break;
+          case PM_PAR_SXX:  params_max =   100; break;
+          case PM_PAR_SYY:  params_max =   100; break;
+          case PM_PAR_SXY:  params_max =   +q2; break;
+          case 7:           params_max =   150; break;
+          case 8:           params_max =  M_PI; break;
+          default:
+            psAbort("invalid parameter %d for param max test", nParam);
+        }
+        if (params[nParam] > params_max) {
+            params[nParam] = params_max;
+            return false;
+        }
+        return true;
+      default:
+        psAbort("invalid choice for limits");
+    }
+    psAbort("should not reach here");
+    return false;
+}
+
+//fixed
+psF64 PM_MODEL_FLUX(const psVector *params)
+{
+    float f, norm, z;
+
+    psF32 *PAR = params->data.F32;
+
+    psF64 A1   = PS_SQR(PAR[4]);
+    psF64 A2   = PS_SQR(PAR[5]);
+    psF64 A3   = PS_SQR(PAR[6]);
+    psF32 Rx=2./PS_SQR(PAR[4]);
+    psF32 Ry=2./PS_SQR(PAR[5]);
+    psF32 Rxy=PAR[6];
+
+
+    psF32 theta = PAR[8];
+    psF32 sinT=sin(theta);
+    psF32 cosT=cos(theta);
+
+    psF32 Syrot = ( PS_SQR(sinT)/Rx + PS_SQR(cosT)/Ry - Rxy*sinT*cosT );  //rotated sigma y
+
+    psF64 A4   = Syrot*PAR[7];
+
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3) + A4;
+    // Area is equivalent to 2 pi sigma^2 + rectangle
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+    for (z = 0.005; z < 50; z += 0.01) {
+        f = 1.0 / (1 + z + z*z/2 + z*z*z/6);
+        norm += f;
+    }
+    norm *= 0.01;
+
+    psF64 Flux = params->data.F32[1] * Area * norm;
+
+    return(Flux);
+}
+
+// define this function so it never returns Inf or NaN
+// also prevent 0 returns, and just send a v small number
+// return the radius which yields the requested flux
+
+//fixed, but need to change how it is called to accomodate 2 radii
+psF64 PM_MODEL_RADIUS  (const psVector *params, psF64 flux)
+{
+    if (flux <= 0) return (1.0);
+    if (params->data.F32[1] <= 0) return (1.0);
+    if (flux >= params->data.F32[1]) return (1.0);
+
+    psF32 *PAR = params->data.F32;
+    psF32 sigma  = sqrt(2.0) * hypot (1.0 / params->data.F32[4], 1.0 / params->data.F32[5]);
+
+    psF32 theta = PAR[8];
+    psF32 sinT=sin(theta);
+    psF32 cosT=cos(theta);
+    psF32 Rx=2./PS_SQR(PAR[4]);
+    psF32 Ry=2./PS_SQR(PAR[5]);
+    psF32 Rxy=PAR[6];
+    psF32 length=PAR[7];
+
+    psF32 Syrot = ( PS_SQR(sinT)/Rx + PS_SQR(cosT)/Ry - Rxy*sinT*cosT );  //rotated sigma y
+
+    psF64 radius = 0;
+    if (flux > 0){
+      psF64 radius0 = sigma * sqrt (2.0 * log(params->data.F32[1] / flux));
+      psF64 radius1 = Syrot * sqrt (2.0 * log(params->data.F32[1] / flux));
+
+      if (radius0 > radius1) {
+        radius=radius0+length/2.0;
+      } else {
+        radius=radius1+length/2.0;
+      }
+    } else {
+      radius = 1000;
+    }
+
+    if (radius < 0.01){
+      radius = 0.01;
+    }
+
+    if (isnan(radius)) {
+      fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+//fixed I think...no good way of guessing as far as I can tell
+bool PM_MODEL_GUESS (pmModel *model, pmSource *source)
+{
+    pmMoments *Smoments = source->moments;
+    psF32     *params  = model->params->data.F32;
+
+    psEllipseAxes axes;
+    psEllipseShape shape;
+    psEllipseMoments moments;
+
+    moments.x2 = PS_SQR(Smoments->Sx);
+    moments.y2 = PS_SQR(Smoments->Sy);
+    moments.xy = Smoments->Sxy;
+    //sometimes these moment inputs are zero...why?
+
+    // solve the math to go from Moments To Shape
+    // limit the axis ratio to 20 (a guess)
+    axes = psEllipseMomentsToAxes(moments, 20.0);
+    shape = psEllipseAxesToShape(axes);
+
+    params[0] = Smoments->Sky;
+    params[1] = Smoments->Peak - Smoments->Sky;
+    params[2] = Smoments->x;
+    params[3] = Smoments->y;
+    params[7] = 2 * axes.major;
+    params[8] = axes.theta;
+
+    if (moments.x2 == 0 || moments.y2 == 0){
+      params[4] = 2.0;
+      params[5] = 2.0;
+      params[6] = 0.0;
+    } else {
+      params[4] = 1.0 / shape.sx;
+      params[5] = 1.0 / shape.sy;
+      params[6] = shape.sxy;
+    }
+
+    //    printf("Who is NaN? momx: %4.3f  momy: %4.3f  momxy: %4.3f\n", moments.x2,moments.y2, moments.xy);
+
+    return(true);
+}
+
+//fixed
+bool PM_MODEL_FROM_PSF (pmModel *modelPSF, pmModel *modelFLT, const pmPSF *psf)
+{
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+    out[7] = in[7];
+    out[8] = in[8];
+
+    for (int i = 4; i < 7; i++) {
+      pmTrend2D *trend = psf->params->data[i-4];
+        out[i] = pmTrend2DEval (trend, out[2], out[3]);
+    }
+    return(true);
+}
+
+// construct the PSF model from the FLT model and the psf
+// XXX is this sufficiently general do be a global function, not a pmModelClass function?
+bool PM_MODEL_PARAMS_FROM_PSF (pmModel *model, const pmPSF *psf, float Xo, float Yo, float Io)
+{
+    psF32 *PAR = model->params->data.F32;
+
+    // we require these two parameters to exist
+    assert (psf->params->n > PM_PAR_YPOS);
+    assert (psf->params->n > PM_PAR_XPOS);
+
+    PAR[PM_PAR_SKY]  = 0.0;
+    PAR[PM_PAR_I0]   = Io;
+    PAR[PM_PAR_XPOS] = Xo;
+    PAR[PM_PAR_YPOS] = Yo;
+
+    // supply the model-fitted parameters, or copy from the input
+    for (int i = 0; i < psf->params->n; i++) {
+        if (i == PM_PAR_SKY) continue;
+        pmTrend2D *trend = psf->params->data[i];
+        PAR[i] = pmTrend2DEval(trend, Xo, Yo);
+    }
+
+    // the 2D PSF model fits polarization terms (E0,E1,E2)
+    // convert to shape terms (SXX,SYY,SXY)
+    // XXX user-defined value for limit?
+    if (!pmPSF_FitToModel (PAR, 0.1)) {
+        psError(PM_ERR_PSF, false, "Failed to fit object at (r,c) = (%.1f,%.1f)", Xo, Yo);
+        return false;
+    }
+
+    // apply the model limits here: this truncates excessive extrapolation
+    // XXX do we need to do this still?  should we put in asserts to test?
+    for (int i = 0; i < psf->params->n; i++) {
+        // apply the limits to all components or just the psf-model parameters?
+        if (psf->params->data[i] == NULL)
+            continue;
+
+        bool status = true;
+        status &= PM_MODEL_LIMITS (PS_MINIMIZE_PARAM_MIN, i, PAR, NULL);
+        status &= PM_MODEL_LIMITS (PS_MINIMIZE_PARAM_MAX, i, PAR, NULL);
+        if (!status) {
+            psTrace ("psModules.objects", 5, "Hitting parameter limits at (r,c) = (%.1f, %.1f)", Xo, Yo);
+            model->flags |= PM_MODEL_STATUS_LIMITS;
+        }
+    }
+    return(true);
+}
+
+//done I think
+bool PM_MODEL_FIT_STATUS (pmModel *model)
+{
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[4] / PAR[4]);
+    dP += PS_SQR(dPAR[5] / PAR[5]);
+    dP = sqrt (dP);
+
+    status = true;
+    status &= (dP < 0.5);
+    status &= (PAR[1] > 0);
+    status &= ((dPAR[1]/PAR[1]) < 0.5);
+    //    status &= ((dPAR[7]/PAR[7]) < 0.5);
+    //    status &= ((dPAR[8]/PAR[8]) < 0.5);
+
+    if (status) return true;
+    return false;
+}
+
+# undef PM_MODEL_FUNC
+# undef PM_MODEL_FLUX
+# undef PM_MODEL_GUESS
+# undef PM_MODEL_LIMITS
+# undef PM_MODEL_RADIUS
+# undef PM_MODEL_FROM_PSF
+# undef PM_MODEL_PARAMS_FROM_PSF
+# undef PM_MODEL_FIT_STATUS
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/models/pmModel_TEST1.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/models/pmModel_TEST1.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/models/pmModel_TEST1.c	(revision 22157)
@@ -0,0 +1,311 @@
+/******************************************************************************
+ * this file defines the TEST1 source shape model.  Note that these model functions are loaded
+ * by pmModelGroup.c using 'include', and thus need no 'include' statements of their own.  The
+ * models use a psVector to represent the set of parameters, with the sequence used to specify
+ * the meaning of the parameter.  The meaning of the parameters may thus vary depending on the
+ * specifics of the model.  All models which are used a PSF representations share a few
+ * parameters, for which # define names are listed in pmModel.h:
+
+ * PM_PAR_SKY 0   - local sky : note that this is unused and may be dropped in the future
+ * PM_PAR_I0 1    - central intensity
+ * PM_PAR_XPOS 2  - X center of object
+ * PM_PAR_YPOS 3  - Y center of object
+ * PM_PAR_SXX 4   - X^2 term of elliptical contour (sqrt(2) * SigmaX)
+ * PM_PAR_SYY 5   - Y^2 term of elliptical contour (sqrt(2) * SigmaY)
+ * PM_PAR_SXY 6   - X*Y term of elliptical contour
+ *****************************************************************************/
+
+# define PM_MODEL_FUNC            pmModelFunc_TEST1
+# define PM_MODEL_FLUX            pmModelFlux_TEST1
+# define PM_MODEL_GUESS           pmModelGuess_TEST1
+# define PM_MODEL_LIMITS          pmModelLimits_TEST1
+# define PM_MODEL_RADIUS          pmModelRadius_TEST1
+# define PM_MODEL_FROM_PSF        pmModelFromPSF_TEST1
+# define PM_MODEL_PARAMS_FROM_PSF pmModelParamsFromPSF_TEST1
+# define PM_MODEL_FIT_STATUS      pmModelFitStatus_TEST1
+
+// the model is a function of the pixel coordinate (pixcoord[0,1] = x,y)
+psF32 PM_MODEL_FUNC(psVector *deriv,
+                    const psVector *params,
+                    const psVector *pixcoord)
+{
+    psF32 *PAR = params->data.F32;
+
+    // XXX this is fitting sigma_x/sqrt(2), sigma_y/sqrt(2)
+    psF32 X  = pixcoord->data.F32[0] - PAR[PM_PAR_XPOS];
+    psF32 Y  = pixcoord->data.F32[1] - PAR[PM_PAR_YPOS];
+    psF32 px = X / PAR[PM_PAR_SXX];
+    psF32 py = Y / PAR[PM_PAR_SYY];
+    psF32 z  = PS_SQR(px) + PS_SQR(py) + PAR[PM_PAR_SXY]*X*Y;
+    psF32 t  = 1 + z + z*z/2.0;
+    psF32 r  = 1.0 / (t + z*z*z/6.0); /* exp (-Z) */
+    psF32 f  = PAR[PM_PAR_I0]*r + PAR[PM_PAR_SKY];
+
+    if (deriv != NULL) {
+        psF32 *dPAR = deriv->data.F32;
+        psF32 q = PAR[PM_PAR_I0]*r*r*t;
+        dPAR[PM_PAR_SKY]  = +1.0;
+        dPAR[PM_PAR_I0]   = +r;
+        dPAR[PM_PAR_XPOS] = q*(2.0*px/PAR[PM_PAR_SXX] + Y*PAR[PM_PAR_SXY]);
+        dPAR[PM_PAR_YPOS] = q*(2.0*py/PAR[PM_PAR_SYY] + X*PAR[PM_PAR_SXY]);
+        dPAR[PM_PAR_SXX]  = +2.0*q*px*px/PAR[PM_PAR_SXX];
+        dPAR[PM_PAR_SYY]  = +2.0*q*py*py/PAR[PM_PAR_SYY];
+        dPAR[PM_PAR_SXY]  = -q*X*Y;
+    }
+    return(f);
+}
+
+// define the parameter limits
+bool PM_MODEL_LIMITS (psMinConstraintMode mode, int nParam, float *params, float *beta)
+{
+    float beta_lim = 0;
+    float params_min = 0;
+    float params_max = 0;
+
+    switch (mode) {
+      case PS_MINIMIZE_BETA_LIMIT:
+        switch (nParam) {
+          case PM_PAR_SKY:  beta_lim = 1000;  break;
+          case PM_PAR_I0:   beta_lim = 3e6;   break;
+          case PM_PAR_XPOS: beta_lim = 5;     break;
+          case PM_PAR_YPOS: beta_lim = 5;     break;
+          case PM_PAR_SXX:  beta_lim = 0.5;   break;
+          case PM_PAR_SYY:  beta_lim = 0.5;   break;
+          case PM_PAR_SXY:  beta_lim = 0.5;   break;
+          default:
+            psAbort("invalid parameter %d for beta test", nParam);
+        }
+        if (fabs(beta[nParam]) > fabs(beta_lim)) {
+            beta[nParam] = (beta[nParam] > 0) ? fabs(beta_lim) : -fabs(beta_lim);
+            return false;
+        }
+        return true;
+      case PS_MINIMIZE_PARAM_MIN:
+        switch (nParam) {
+          case PM_PAR_SKY:  params_min = -1000; break;
+          case PM_PAR_I0:   params_min =     0; break;
+          case PM_PAR_XPOS: params_min =  -100; break;
+          case PM_PAR_YPOS: params_min =  -100; break;
+          case PM_PAR_SXX:  params_min =   0.5; break;
+          case PM_PAR_SYY:  params_min =   0.5; break;
+          case PM_PAR_SXY:  params_min =  -5.0; break;
+          default:
+            psAbort("invalid parameter %d for param min test", nParam);
+        }
+        if (params[nParam] < params_min) {
+            params[nParam] = params_min;
+            return false;
+        }
+        return true;
+      case PS_MINIMIZE_PARAM_MAX:
+        switch (nParam) {
+          case PM_PAR_SKY:  params_max =   1e5; break;
+          case PM_PAR_I0:   params_max =   1e8; break;
+          case PM_PAR_XPOS: params_max =   1e4; break;
+          case PM_PAR_YPOS: params_max =   1e4; break;
+          case PM_PAR_SXX:  params_max =   100; break;
+          case PM_PAR_SYY:  params_max =   100; break;
+          case PM_PAR_SXY:  params_max =  +5.0; break;
+          default:
+            psAbort("invalid parameter %d for param max test", nParam);
+        }
+        if (params[nParam] > params_max) {
+            params[nParam] = params_max;
+            return false;
+        }
+        return true;
+      default:
+        psAbort("invalid choice for limits");
+    }
+    psAbort("should not reach here");
+    return false;
+}
+
+// make an initial guess for parameters
+bool PM_MODEL_GUESS (pmModel *model, pmSource *source)
+{
+    pmMoments *moments = source->moments;
+    psF32     *PAR  = model->params->data.F32;
+
+    PAR[PM_PAR_SKY] = moments->Sky;
+    PAR[PM_PAR_I0] = moments->Peak - moments->Sky;
+    PAR[PM_PAR_XPOS] = moments->x;
+    PAR[PM_PAR_YPOS] = moments->y;
+    PAR[PM_PAR_SXX] = PS_MAX(0.5, moments->Sx);
+    PAR[PM_PAR_SYY] = PS_MAX(0.5, moments->Sy);
+    PAR[PM_PAR_SXY] = 0.0;  // XXX we can get this right if we do the integral
+
+    return(true);
+}
+
+psF64 PM_MODEL_FLUX(const psVector *params)
+{
+    float norm, z;
+    psEllipseShape shape;
+
+    psF32 *PAR = params->data.F32;
+
+    shape.sx  = PAR[PM_PAR_SXX] / sqrt(2.0);
+    shape.sy  = PAR[PM_PAR_SYY] / sqrt(2.0);
+    shape.sxy = PAR[PM_PAR_SXY];
+
+    // Area is equivalent to 2 pi sigma^2
+    psEllipseAxes axes = psEllipseShapeToAxes (shape, 20.0);
+    psF64 Area = 2.0 * M_PI * axes.major * axes.minor;
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+
+# define DZ 0.25
+
+    float f0 = 1.0;
+    float f1, f2;
+    for (z = DZ; z < 50; z += DZ) {
+        f1 = 1.0 / (1 + z + z*z/2.0 + z*z*z/6.0);
+        z += DZ;
+        f2 = 1.0 / (1 + z + z*z/2.0 + z*z*z/6.0);
+        norm += f0 + 4*f1 + f2;
+        f0 = f2;
+    }
+    norm *= DZ / 3.0;
+
+    psF64 Flux = PAR[PM_PAR_I0] * Area * norm;
+
+    return(Flux);
+}
+
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 PM_MODEL_RADIUS (const psVector *params, psF64 flux)
+{
+    psEllipseShape shape;
+
+    if (flux <= 0)
+        return (1.0);
+    if (params->data.F32[PM_PAR_I0] <= 0)
+        return (1.0);
+    if (flux >= params->data.F32[PM_PAR_I0])
+        return (1.0);
+
+    psF32 *PAR = params->data.F32;
+
+    shape.sx  = PAR[PM_PAR_SXX] / sqrt(2.0);
+    shape.sy  = PAR[PM_PAR_SYY] / sqrt(2.0);
+    shape.sxy = PAR[PM_PAR_SXY];
+
+    // this estimates the radius assuming f(z) is roughly exp(-z)
+    psEllipseAxes axes = psEllipseShapeToAxes (shape, 20.0);
+    psF64 radius = axes.major * sqrt (2.0 * log(params->data.F32[PM_PAR_I0] / flux));
+
+    if (isnan(radius)) psAbort("error in code: never return invalid radius");
+    if (radius < 0) psAbort("error in code: never return invalid radius");
+
+    return (radius);
+}
+
+bool PM_MODEL_FROM_PSF (pmModel *modelPSF, pmModel *modelFLT, const pmPSF *psf)
+{
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+
+    // we require these two parameters to exist
+    assert (psf->params->n > PM_PAR_YPOS);
+    assert (psf->params->n > PM_PAR_XPOS);
+
+    for (int i = 0; i < psf->params->n; i++) {
+        if (psf->params->data[i] == NULL) {
+            out[i] = in[i];
+        } else {
+            pmTrend2D *trend = psf->params->data[i];
+            out[i] = pmTrend2DEval(trend, in[PM_PAR_XPOS], in[PM_PAR_YPOS]);
+        }
+    }
+
+    // the 2D model for SXY actually fits SXY / (SXX^-2 + SYY^-2); correct here
+    out[PM_PAR_SXY] = pmPSF_SXYtoModel (out);
+
+    return(true);
+}
+
+// construct the PSF model from the FLT model and the psf
+// XXX is this sufficiently general do be a global function, not a pmModelClass function?
+bool PM_MODEL_PARAMS_FROM_PSF (pmModel *model, const pmPSF *psf, float Xo, float Yo, float Io)
+{
+    psF32 *PAR = model->params->data.F32;
+
+    // we require these two parameters to exist
+    assert (psf->params->n > PM_PAR_YPOS);
+    assert (psf->params->n > PM_PAR_XPOS);
+
+    PAR[PM_PAR_SKY]  = 0.0;
+    PAR[PM_PAR_I0]   = Io;
+    PAR[PM_PAR_XPOS] = Xo;
+    PAR[PM_PAR_YPOS] = Yo;
+
+    // supply the model-fitted parameters, or copy from the input
+    for (int i = 0; i < psf->params->n; i++) {
+        if (i == PM_PAR_SKY) continue;
+        pmTrend2D *trend = psf->params->data[i];
+        PAR[i] = pmTrend2DEval(trend, Xo, Yo);
+    }
+
+    // the 2D PSF model fits polarization terms (E0,E1,E2)
+    // convert to shape terms (SXX,SYY,SXY)
+    // XXX user-defined value for limit?
+    if (!pmPSF_FitToModel (PAR, 0.1)) {
+        psError(PM_ERR_PSF, false, "Failed to fit object at (r,c) = (%.1f,%.1f)", Xo, Yo);
+        return false;
+    }
+
+    // apply the model limits here: this truncates excessive extrapolation
+    // XXX do we need to do this still?  should we put in asserts to test?
+    for (int i = 0; i < psf->params->n; i++) {
+        // apply the limits to all components or just the psf-model parameters?
+        if (psf->params->data[i] == NULL)
+            continue;
+
+        bool status = true;
+        status &= PM_MODEL_LIMITS (PS_MINIMIZE_PARAM_MIN, i, PAR, NULL);
+        status &= PM_MODEL_LIMITS (PS_MINIMIZE_PARAM_MAX, i, PAR, NULL);
+        if (!status) {
+            psTrace ("psModules.objects", 5, "Hitting parameter limits at (r,c) = (%.1f, %.1f)", Xo, Yo);
+            model->flags |= PM_MODEL_STATUS_LIMITS;
+        }
+    }
+    return(true);
+}
+
+// XXX double-check these definitions below
+// this test is invalid if the parameters are derived
+// from the PSF model
+bool PM_MODEL_FIT_STATUS (pmModel *model)
+{
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[PM_PAR_SXX] / PAR[PM_PAR_SXX]);
+    dP += PS_SQR(dPAR[PM_PAR_SYY] / PAR[PM_PAR_SYY]);
+    dP = sqrt (dP);
+
+    status = true;
+    status &= (dP < 0.5);
+    status &= (PAR[PM_PAR_I0] > 0);
+    status &= ((dPAR[PM_PAR_I0]/PAR[PM_PAR_I0]) < 0.5);
+
+    if (status)
+        return true;
+    return false;
+}
+
+# undef PM_MODEL_FUNC
+# undef PM_MODEL_FLUX
+# undef PM_MODEL_GUESS
+# undef PM_MODEL_LIMITS
+# undef PM_MODEL_RADIUS
+# undef PM_MODEL_FROM_PSF
+# undef PM_MODEL_PARAMS_FROM_PSF
+# undef PM_MODEL_FIT_STATUS
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/pmDetections.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/pmDetections.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/pmDetections.c	(revision 22157)
@@ -0,0 +1,26 @@
+# include "psphotInternal.h"
+
+void pmDetectionsFree (pmDetections *detections) {
+
+  if (!detections) return;
+
+  psFree (detections->footprints);
+  psFree (detections->peaks);
+  psFree (detections->oldPeaks);
+  return;
+}
+
+// generate a pmDetections container with empty (allocated) footprints and peaks containers
+pmDetections *pmDetectionsAlloc() {
+
+    pmDetections *detections = (pmDetections *)psAlloc(sizeof(pmDetections));
+    psMemSetDeallocator(detections, (psFreeFunc) pmDetectionsFree);
+
+    detections->footprints = NULL;
+    detections->peaks      = NULL;
+    detections->oldPeaks   = NULL;
+    detections->last       = 0;
+
+    return (detections);
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/pmFootprint.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/pmFootprint.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/pmFootprint.c	(revision 22157)
@@ -0,0 +1,167 @@
+# include "psphotInternal.h"
+
+/**** low-level pmFootprint functions ****/
+
+static void footprintFree(pmFootprint *tmp)
+{
+   if (!tmp) {
+        return;
+   }
+
+   psTrace("psModules.objects", 5, "---- begin ----\n");
+
+   psFree(tmp->spans);
+   psFree(tmp->peaks);
+
+   psTrace("psModules.objects", 5, "---- end ----\n");
+}
+
+/*
+ * pmFootprintAlloc(): Allocate the pmFootprint structure to NULL.
+ */
+pmFootprint *pmFootprintAlloc(int nspan, // number of spans expected in footprint
+			      const psImage *image) // region footprint lives in
+{
+    psTrace("psModules.objects", 5, "---- begin ----\n");
+
+    static int id = 1;
+    pmFootprint *footprint = (pmFootprint *)psAlloc(sizeof(pmFootprint));
+    *(int *)&footprint->id = id++;
+    psMemSetDeallocator(footprint, (psFreeFunc) footprintFree);
+
+    footprint->normalized = false;
+
+    assert(nspan >= 0);
+    footprint->npix = 0;
+    footprint->spans = psArrayAllocEmpty(nspan);
+    footprint->peaks = psArrayAlloc(0);
+
+    footprint->bbox.x0 = footprint->bbox.y0 = 0;
+    footprint->bbox.x1 = footprint->bbox.y1 = -1;
+
+    if (image == NULL) {
+	footprint->region.x0 = footprint->region.y0 = 0;
+	footprint->region.x1 = footprint->region.y1 = -1;
+    } else {
+	footprint->region.x0 = image->col0;
+	footprint->region.x1 = image->col0 + image->numCols - 1;
+	footprint->region.y0 = image->row0;
+	footprint->region.y1 = image->row0 + image->numRows - 1;
+    }
+
+    psTrace("psModules.objects", 5, "---- end ----\n");
+    return(footprint);
+}
+
+bool pmFootprintTest(const psPtr ptr) {
+    return (psMemGetDeallocator(ptr) == (psFreeFunc)footprintFree);
+}
+
+pmFootprint *pmFootprintNormalize(pmFootprint *fp) {
+    if (fp != NULL && !fp->normalized) {
+	fp->peaks = psArraySort(fp->peaks, pmPeakSortBySN);
+	fp->normalized = true;
+    }
+
+    return fp;
+}
+
+//
+// Add a span to a footprint, returning the new span
+//
+pmSpan *pmFootprintAddSpan(pmFootprint *fp,	// the footprint to add to
+			   const int y, // row to add
+			   int x0,      // range of
+			   int x1) {    //          columns
+
+    if (x1 < x0) {
+	int tmp = x0;
+	x0 = x1;
+	x1 = tmp;
+    }
+
+    pmSpan *sp = pmSpanAlloc(y, x0, x1);
+    psArrayAdd(fp->spans, 1, sp);
+    psFree(sp);
+    
+    fp->npix += x1 - x0 + 1;
+
+    if (fp->spans->n == 1) {
+	fp->bbox.x0 = x0;
+	fp->bbox.x1 = x1;
+	fp->bbox.y0 = y;
+	fp->bbox.y1 = y;
+    } else {
+	if (x0 < fp->bbox.x0) fp->bbox.x0 = x0;
+	if (x1 > fp->bbox.x1) fp->bbox.x1 = x1;
+	if (y < fp->bbox.y0) fp->bbox.y0 = y;
+	if (y > fp->bbox.y1) fp->bbox.y1 = y;
+    }
+
+    return sp;
+}
+
+void pmFootprintSetBBox(pmFootprint *fp) {
+    assert (fp != NULL);
+    if (fp->spans->n == 0) {
+	return;
+    }
+    pmSpan *sp = fp->spans->data[0];
+    int x0 = sp->x0;
+    int x1 = sp->x1;
+    int y0 = sp->y;
+    int y1 = sp->y;
+
+    for (int i = 1; i < fp->spans->n; i++) {
+	sp = fp->spans->data[i];
+	
+	if (sp->x0 < x0) x0 = sp->x0;
+	if (sp->x1 > x1) x1 = sp->x1;
+	if (sp->y < y0) y0 = sp->y;
+	if (sp->y > y1) y1 = sp->y;
+    }
+
+    fp->bbox.x0 = x0;
+    fp->bbox.x1 = x1;
+    fp->bbox.y0 = y0;
+    fp->bbox.y1 = y1;
+}
+
+int pmFootprintSetNpix(pmFootprint *fp) {
+   assert (fp != NULL);
+   int npix = 0;
+   for (int i = 0; i < fp->spans->n; i++) {
+       pmSpan *span = fp->spans->data[i];
+       npix += span->x1 - span->x0 + 1;
+   }
+   fp->npix = npix;
+
+   return npix;
+}
+
+/*
+ * Extract the peaks in a psArray of pmFootprints, returning a psArray of pmPeaks
+ */
+psArray *pmFootprintArrayToPeaks(const psArray *footprints) {
+   assert(footprints != NULL);
+   assert(footprints->n == 0 || pmFootprintTest(footprints->data[0]));
+
+   int npeak = 0;
+   for (int i = 0; i < footprints->n; i++) {
+      const pmFootprint *fp = footprints->data[i];
+      npeak += fp->peaks->n;
+   }
+
+   psArray *peaks = psArrayAllocEmpty(npeak);
+   
+   for (int i = 0; i < footprints->n; i++) {
+      const pmFootprint *fp = footprints->data[i];
+      for (int j = 0; j < fp->peaks->n; j++) {
+	 psArrayAdd(peaks, 1, fp->peaks->data[j]);
+      }
+   }
+
+   return peaks;
+}
+
+/************************************************************************************************************/
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/pmFootprint.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/pmFootprint.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/pmFootprint.h	(revision 22157)
@@ -0,0 +1,73 @@
+#if !defined(PM_FOOTPRINT_H)
+#define PM_FOOTPRINT_H
+//
+// Describe a segment of an image
+//
+typedef struct {
+    int y;                              //!< Row that span's in
+    int x0;                             //!< Starting column (inclusive)
+    int x1;                             //!< Ending column (inclusive)
+} pmSpan;
+
+pmSpan *pmSpanAlloc(int y, int x1, int x2);
+bool pmSpanTest(const psPtr ptr);
+int pmSpanSortByYX (const void **a, const void **b);
+
+typedef struct {
+    const int id;                       //!< unique ID
+    int npix;                           //!< number of pixels in this pmFootprint
+    psArray *spans;                     //!< the pmSpans
+    psRegion bbox;                      //!< the pmFootprint's bounding box
+    psArray *peaks;                     //!< the peaks lying in this footprint
+    psRegion region;   //!< A region describing the psImage the footprints live in
+    bool normalized;                    //!< Are the spans sorted? 
+} pmFootprint;
+
+pmFootprint *pmFootprintAlloc(int nspan, const psImage *img);
+bool pmFootprintTest(const psPtr ptr);
+
+pmFootprint *pmFootprintNormalize(pmFootprint *fp);
+int pmFootprintSetNpix(pmFootprint *fp);
+void pmFootprintSetBBox(pmFootprint *fp);
+
+pmSpan *pmFootprintAddSpan(pmFootprint *fp,	// the footprint to add to
+			   const int y, // row to add
+			   int x0,      // range of
+			   int x1);    //          columns
+
+psArray *pmFootprintsFind(const psImage *img, const float threshold, const int npixMin);
+pmFootprint *pmFootprintsFindAtPoint(const psImage *img,
+                                    const float threshold,
+                                    const psArray *peaks,
+                                    int row, int col);
+
+psArray *pmFootprintArrayGrow(const psArray *footprints, int r);
+psArray *pmFootprintArraysMerge(const psArray *footprints1, const psArray *footprints2,
+                                const int includePeaks);
+
+psImage *pmSetFootprintArrayIDs(const psArray *footprints, const bool relativeIDs);
+psImage *pmSetFootprintID(psImage *idImage, const pmFootprint *fp, const int id);
+void pmSetFootprintArrayIDsForImage(psImage *idImage,
+				    const psArray *footprints, // the footprints to insert
+				    const bool relativeIDs); // show IDs starting at 0, not pmFootprint->id
+
+psErrorCode pmFootprintsAssignPeaks(psArray *footprints, const psArray *peaks);
+
+psErrorCode pmFootprintArrayCullPeaks(const psImage *img, const psImage *weight, psArray *footprints,
+                                 const float nsigma, const float threshold_min);
+psErrorCode pmFootprintCullPeaks(const psImage *img, const psImage *weight, pmFootprint *fp,
+                                 const float nsigma, const float threshold_min);
+
+psArray *pmFootprintArrayToPeaks(const psArray *footprints);
+
+typedef struct {
+  psArray *footprints;	      // collection of footprints in the image
+  psArray *peaks;	      // collection of all peaks contained by the footprints
+  psArray *oldPeaks;	      // collection of all peaks previously found
+  int last;
+} pmDetections;
+
+pmDetections *pmDetectionsAlloc ();
+
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/pmFootprintArrayGrow.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/pmFootprintArrayGrow.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/pmFootprintArrayGrow.c	(revision 22157)
@@ -0,0 +1,69 @@
+# include "psphotInternal.h"
+
+/*
+ * Grow a psArray of pmFootprints isotropically by r pixels, returning a new psArray of new pmFootprints
+ */
+psArray *pmFootprintArrayGrow(const psArray *footprints, // footprints to grow
+			      int r) {	// how much to grow each footprint
+    assert (footprints->n == 0 || pmFootprintTest(footprints->data[0]));
+
+    psTimerStart ("grow");
+
+    if (footprints->n == 0) {		// we don't know the size of the footprint's region
+	return psArrayAlloc(0);
+    }
+    /*
+     * We'll insert the footprints into an image, then convolve with a disk,
+     * then extract a footprint from the result --- this is magically what we want.
+     */
+    psImage *idImage = pmSetFootprintArrayIDs(footprints, true);
+    psLogMsg ("psphot", PS_LOG_DETAIL, "set footprint array IDs: %f sec\n", psTimerMark ("grow"));
+
+    if (r <= 0) {
+	r = 1;				// r == 1 => no grow
+    }
+    psKernel *circle = psKernelAlloc(-r, r, -r, r);
+    assert (circle->image->numRows == 2*r + 1 && circle->image->numCols == circle->image->numRows);
+    for (int i = 0; i <= r; i++) {
+	for (int j = 0; j <= r; j++) {
+	    if (i*i + j*j <= r*r) {
+		circle->kernel[i][j] = 
+		    circle->kernel[i][-j] = 
+		    circle->kernel[-i][j] = 
+		    circle->kernel[-i][-j] = 1;
+	    }
+	}
+    }
+
+# if (1)    
+    psImage *f32ImageIn = psImageCopy (NULL, idImage, PS_TYPE_F32);
+    psImage *f32ImageOut = psImageConvolveFFT(NULL, f32ImageIn, NULL, 0, circle);
+    psImage *grownIdImage = psImageCopy (NULL, f32ImageOut, PS_TYPE_S32);
+    psFree (f32ImageIn);
+    psFree (f32ImageOut);
+# else
+    psImage *grownIdImage = psImageConvolveDirect(NULL, idImage, circle); // Here's the actual grow step
+# endif
+
+    psLogMsg ("psphot", PS_LOG_DETAIL, "convolved with grow disc: %f sec\n", psTimerMark ("grow"));
+    psFree(circle);	
+
+    psArray *grown = pmFootprintsFind(grownIdImage, 0.5, 1); // and here we rebuild the grown footprints
+    psLogMsg ("psphot", PS_LOG_DETAIL, "found grown footprints: %f sec\n", psTimerMark ("grow"));
+
+    assert (grown != NULL);
+    psFree(idImage); psFree(grownIdImage);
+    /*
+     * Now assign the peaks appropriately.  We could do this more efficiently
+     * using grownIdImage (which we just freed), but this is easy and probably fast enough
+     */
+    const psArray *peaks = pmFootprintArrayToPeaks(footprints);
+    pmFootprintsAssignPeaks(grown, peaks);
+    psFree((psArray *)peaks);
+
+    psLogMsg ("psphot", PS_LOG_DETAIL, "finished grow: %f sec\n", psTimerMark ("grow"));
+
+    return grown;
+    
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/pmFootprintArraysMerge.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/pmFootprintArraysMerge.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/pmFootprintArraysMerge.c	(revision 22157)
@@ -0,0 +1,68 @@
+# include "psphotInternal.h"
+
+/*
+ * Merge together two psArrays of pmFootprints neither of which is damaged.
+ *
+ * The returned psArray may contain elements of the inital psArrays (with
+ * their reference counters suitable incremented)
+ */
+psArray *pmFootprintArraysMerge(const psArray *footprints1, // one set of footprints
+				const psArray *footprints2, // the other set
+				const int includePeaks) { // which peaks to set? 0x1 => footprints1, 0x2 => 2
+    assert (footprints1->n == 0 || pmFootprintTest(footprints1->data[0]));
+    assert (footprints2->n == 0 || pmFootprintTest(footprints2->data[0]));
+
+    if (footprints1->n == 0 || footprints2->n == 0) {		// nothing to do but put copies on merged
+	const psArray *old = (footprints1->n == 0) ? footprints2 : footprints1;
+
+	psArray *merged = psArrayAllocEmpty(old->n);
+	for (int i = 0; i < old->n; i++) {
+	    psArrayAdd(merged, 1, old->data[i]);
+	}
+	
+	return merged;
+    }
+    /*
+     * We have real work to do as some pmFootprints in footprints2 may overlap
+     * with footprints1
+     */
+    {
+	pmFootprint *fp1 = footprints1->data[0];
+	pmFootprint *fp2 = footprints2->data[0];
+	if (fp1->region.x0 != fp2->region.x0 ||
+	    fp1->region.x1 != fp2->region.x1 ||
+	    fp1->region.y0 != fp2->region.y0 ||
+	    fp1->region.y1 != fp2->region.y1) {
+	    psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+		    "The two pmFootprint arrays correspnond to different-sized regions");
+	    return NULL;
+	}
+    }
+    /*
+     * We'll insert first one set of footprints then the other into an image, then
+     * extract a footprint from the result --- this is magically what we want.
+     */
+    psImage *idImage = pmSetFootprintArrayIDs(footprints1, true);
+    pmSetFootprintArrayIDsForImage(idImage, footprints2, true);
+
+    psArray *merged = pmFootprintsFind(idImage, 0.5, 1);
+    assert (merged != NULL);
+    psFree(idImage);
+    /*
+     * Now assign the peaks appropriately.  We could do this more efficiently
+     * using idImage (which we just freed), but this is easy and probably fast enough
+     */ 
+    if (includePeaks & 0x1) {
+	const psArray *peaks = pmFootprintArrayToPeaks(footprints1);
+	pmFootprintsAssignPeaks(merged, peaks);
+	psFree((psArray *)peaks);
+    }
+
+    if (includePeaks & 0x2) {
+	const psArray *peaks = pmFootprintArrayToPeaks(footprints2);
+	pmFootprintsAssignPeaks(merged, peaks);
+	psFree((psArray *)peaks);
+    }
+    
+    return merged;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/pmFootprintIDs.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/pmFootprintIDs.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/pmFootprintIDs.c	(revision 22157)
@@ -0,0 +1,104 @@
+# include "psphotInternal.h"
+
+/**** functions to manipulate footprint IDs ****/
+
+
+/*
+ * Worker routine for the pmSetFootprintArrayIDs/pmSetFootprintID (and pmMergeFootprintArrays)
+ */
+static void
+set_footprint_id(psImage *idImage,	// the image to set
+		 const pmFootprint *fp, // the footprint to insert
+		 const int id) {	// the desired ID
+   const int col0 = fp->region.x0;
+   const int row0 = fp->region.y0;
+
+   for (int j = 0; j < fp->spans->n; j++) {
+       const pmSpan *span = fp->spans->data[j];
+       psS32 *imgRow = idImage->data.S32[span->y - row0];
+       for(int k = span->x0 - col0; k <= span->x1 - col0; k++) {
+	   imgRow[k] += id;
+       }
+   }
+}
+
+void pmSetFootprintArrayIDsForImage(psImage *idImage,
+				    const psArray *footprints, // the footprints to insert
+				    const bool relativeIDs) { // show IDs starting at 0, not pmFootprint->id
+    int id = 0;				// first index will be 1
+    for (int i = 0; i < footprints->n; i++) {
+	const pmFootprint *fp = footprints->data[i];
+	if (relativeIDs) {
+	    id++;
+	} else {
+	    id = fp->id;
+	}
+       
+	set_footprint_id(idImage, fp, id);
+    }
+}
+
+/*
+ * Set an image to the value of footprint's ID whever they may fall
+ */
+psImage *pmSetFootprintArrayIDs(const psArray *footprints, // the footprints to insert
+				const bool relativeIDs) { // show IDs starting at 1, not pmFootprint->id
+   assert (footprints != NULL);
+
+   if (footprints->n == 0) {
+       psError(PS_ERR_BAD_PARAMETER_SIZE, true, "You didn't provide any footprints");
+       return NULL;
+   }
+   const pmFootprint *fp = footprints->data[0];
+   assert(pmFootprintTest((const psPtr)fp));
+   const int numCols = fp->region.x1 - fp->region.x0 + 1;
+   const int numRows = fp->region.y1 - fp->region.y0 + 1;
+   const int col0 = fp->region.x0;
+   const int row0 = fp->region.y0;
+   assert (numCols >= 0 && numRows >= 0);
+   
+   psImage *idImage = psImageAlloc(numCols, numRows, PS_TYPE_S32);
+   P_PSIMAGE_SET_ROW0(idImage, row0);
+   P_PSIMAGE_SET_COL0(idImage, col0);
+   psImageInit(idImage, 0);
+   /*
+    * do the work
+    */
+   pmSetFootprintArrayIDsForImage(idImage, footprints, relativeIDs);
+
+   return idImage;
+   
+}
+
+/*
+ * Set an image to the value of footprint's ID whever they may fall
+ */
+psImage *pmSetFootprintID(psImage *idImage,
+			  const pmFootprint *fp, // the footprint to insert
+			  const int id) {	// the desired ID
+   assert(fp != NULL && pmFootprintTest((const psPtr)fp));
+   const int numCols = fp->region.x1 - fp->region.x0 + 1;
+   const int numRows = fp->region.y1 - fp->region.y0 + 1;
+   const int col0 = fp->region.x0;
+   const int row0 = fp->region.y0;
+   assert (numCols >= 0 && numRows >= 0);
+   
+   if (idImage == NULL) {
+       idImage = psImageAlloc(numCols, numRows, PS_TYPE_S32);
+   } else {
+       assert (idImage->numCols == numCols);
+       assert (idImage->numRows == numRows);
+       // XXX assert on type (S32)
+   }
+   P_PSIMAGE_SET_ROW0(idImage, row0);
+   P_PSIMAGE_SET_COL0(idImage, col0);
+   psImageInit(idImage, 0);
+   /*
+    * do the work
+    */
+   set_footprint_id(idImage, fp, id);
+
+   return idImage;
+   
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/pmFootprintsAssignPeaks.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/pmFootprintsAssignPeaks.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/pmFootprintsAssignPeaks.c	(revision 22157)
@@ -0,0 +1,116 @@
+# include "psphotInternal.h"
+
+/*
+ * Given a psArray of pmFootprints and another of pmPeaks, assign the peaks to the
+ * footprints in which that fall; if they _don't_ fall in a footprint, add a suitable
+ * one to the list.
+ */
+psErrorCode
+pmFootprintsAssignPeaks(psArray *footprints,	// the pmFootprints
+			const psArray *peaks) { // the pmPeaks
+    assert (footprints != NULL);
+    assert (footprints->n == 0 || pmFootprintTest(footprints->data[0]));
+    assert (peaks != NULL);
+    assert (peaks->n == 0 || psMemCheckPeak(peaks->data[0]));
+    
+    if (footprints->n == 0) {
+	if (peaks->n > 0) {
+	    return psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Your list of footprints is empty");
+	}
+	return PS_ERR_NONE;
+    }
+    /*
+     * Create an image filled with the object IDs, and use it to assign pmPeaks to the
+     * objects
+     */
+    psImage *ids = pmSetFootprintArrayIDs(footprints, true);
+    assert (ids != NULL);
+    assert (ids->type.type == PS_TYPE_S32);
+    const int row0 = ids->row0;
+    const int col0 = ids->col0;
+    const int numRows = ids->numRows;
+    const int numCols = ids->numCols;
+
+    for (int i = 0; i < peaks->n; i++) {
+	pmPeak *peak = peaks->data[i];
+	const int x = peak->x - col0;
+	const int y = peak->y - row0;
+	
+	assert (x >= 0 && x < numCols && y >= 0 && y < numRows);
+	int id = ids->data.S32[y][x - col0];
+
+	if (id == 0) {			// peak isn't in a footprint, so make one for it
+	    pmFootprint *nfp = pmFootprintAlloc(1, ids);
+	    pmFootprintAddSpan(nfp, y, x, x);
+	    psArrayAdd(footprints, 1, nfp);
+	    psFree(nfp);
+	    id = footprints->n;
+	}
+
+	assert (id >= 1 && id <= footprints->n);
+	pmFootprint *fp = footprints->data[id - 1];
+	psArrayAdd(fp->peaks, 5, peak);
+    }
+    
+    psFree(ids);
+
+    // Make sure that peaks within each footprint are sorted and unique
+    for (int i = 0; i < footprints->n; i++) {
+	
+	pmFootprint *fp = footprints->data[i];
+
+	// XXX are we allowed to have peak-less footprints??
+	if (!fp->peaks->n) continue;
+
+        fp->peaks = psArraySort(fp->peaks, pmPeakSortBySN);
+
+	// XXX EAM : the algorithm below should be much faster than using psArrayRemove if
+	// the number of peaks in the footprint is large, or if there are no duplicates.
+	// if we have a lot of small-number peak arrays with duplicates, this may be
+	// slower.
+
+	// track the number of good peaks in the footprint
+	int nGood = 1;
+
+	// check for duplicates
+	// on first pass, we set the index to NULL if peak is a duplicate
+	// XXX EAM : this can leave behind duplicates of the same S/N
+	// (if sorted list has A, B, A, B ...)
+	for (int j = 1; j < fp->peaks->n; j++) { 
+	    if (fp->peaks->data[j] == fp->peaks->data[nGood]) {
+		// everything on the array has its own mem reference; free and drop this one
+		psFree (fp->peaks->data[j]);
+		fp->peaks->data[j] = NULL;
+	    } else {
+		nGood ++;
+	    }
+	}
+
+	// no deleted peaks, go to next footprint
+	if (nGood == fp->peaks->n) continue;
+
+	int nKeep = 0;
+
+	psArray *goodPeaks = psArrayAlloc (nGood);
+	// on second pass, save the good peaks
+	for (int j = 0; j < fp->peaks->n; j++) { // check for duplicates
+	    if (fp->peaks->data[j] == NULL) continue;
+	    // transfer the data (NULL to avoid double free)
+	    // this is only slightly sleazy
+	    goodPeaks->data[nKeep] = fp->peaks->data[j];
+	    fp->peaks->data[j] = NULL;
+	    nKeep ++;
+	}
+	psAssert (nGood == nKeep, "mis-counted nKeep or nGood");
+
+	// free the old (now NULL-filled) array
+	psFree (fp->peaks);
+	fp->peaks = goodPeaks;
+    }
+
+    // (void)psArrayRemoveIndex(fp->peaks, j);
+
+
+    return PS_ERR_NONE;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/pmFootprintsFind.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/pmFootprintsFind.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/pmFootprintsFind.c	(revision 22157)
@@ -0,0 +1,228 @@
+# include "psphotInternal.h"
+void *memset (void *s, int c, size_t n);
+
+/**** find footprints in an image (fast on large scale images) ****/
+
+// XXX EAM : why use WSPAN in here rather than pmSpan?
+typedef struct {			/* run-length code for part of object*/
+   int id;				/* ID for object */
+   int y;				/* Row wherein WSPAN dwells */
+   int x0, x1;				/* inclusive range of columns */
+} WSPAN;
+
+/*
+ * comparison function for qsort; sort by ID then row
+ */
+static int
+compar(const void *va, const void *vb)
+{
+   const WSPAN *a = va;
+   const WSPAN *b = vb;
+
+   if(a->id < b->id) {
+      return(-1);
+   } else if(a->id > b->id) {
+      return(1);
+   } else {
+      return(a->y - b->y);
+   }
+}
+
+/*
+ * Follow a chain of aliases, returning the final resolved value.
+ */
+static int
+resolve_alias(const int *aliases,	/* list of aliases */
+	      int id)			/* alias to look up */
+{
+   int resolved = id;			/* resolved alias */
+
+   while(id != aliases[id]) {
+      resolved = id = aliases[id];
+   }
+
+   return(resolved);
+}
+
+/*
+ * Go through an image, finding sets of connected pixels above threshold
+ * and assembling them into pmFootprints;  the resulting set of objects
+ * is returned as a psArray
+ */
+psArray *
+pmFootprintsFind(const psImage *img,	// image to search
+		 const float threshold,	// Threshold
+		 const int npixMin)	// minimum number of pixels in an acceptable pmFootprint
+{
+   int i0;				/* initial value of i */
+   int id;				/* object ID */
+   int in_span;				/* object ID of current WSPAN */
+   int nspan = 0;			/* number of spans */
+   int nobj = 0;			/* number of objects found */
+   int x0 = 0;			        /* unpacked from a WSPAN */
+   int *tmp;				/* used in swapping idc/idp */
+
+   assert(img != NULL);
+
+   bool F32 = false;			// is this an F32 image?
+   if (img->type.type == PS_TYPE_F32) {
+       F32 = true;
+   } else if (img->type.type == PS_TYPE_S32) {
+       F32 = false;
+   } else {				// N.b. You can't trivially add more cases here; F32 is just a bool
+       psError(PS_ERR_UNKNOWN, true, "Unsupported psImage type: %d", img->type.type);
+       return NULL;
+   }
+   psF32 *imgRowF32 = NULL;		// row pointer if F32
+   psS32 *imgRowS32 = NULL;		//  "   "   "  "  !F32
+   
+   const int row0 = img->row0;
+   const int col0 = img->col0;
+   const int numRows = img->numRows;
+   const int numCols = img->numCols;
+/*
+ * Storage for arrays that identify objects by ID. We want to be able to
+ * refer to idp[-1] and idp[numCols], hence the (numCols + 2)
+ */
+   int *id_s = psAlloc(2*(numCols + 2)*sizeof(int));
+   memset(id_s, '\0', 2*(numCols + 2)*sizeof(int)); assert(id_s[0] == 0);
+   int *idc = id_s + 1;			// object IDs in current/
+   int *idp = idc + (numCols + 2);	//                       previous row
+
+   int size_aliases = 1 + numRows/20;	// size of aliases[] array
+   int *aliases = psAlloc(size_aliases*sizeof(int)); // aliases for object IDs
+
+   int size_spans = 1 + numRows/20;	// size of spans[] array
+   WSPAN *spans = psAlloc(size_spans*sizeof(WSPAN)); // row:x0,x1 for objects
+/*
+ * Go through image identifying objects
+ */
+   for (int i = 0; i < numRows; i++) {
+      int j;
+      tmp = idc; idc = idp; idp = tmp;	/* swap ID pointers */
+      memset(idc, '\0', numCols*sizeof(int));
+      
+      imgRowF32 = img->data.F32[i];	// only one of
+      imgRowS32 = img->data.S32[i];	//      these is valid!
+
+      in_span = 0;			/* not in a span */
+      for (j = 0; j < numCols; j++) {
+	 double pixVal = F32 ? imgRowF32[j] : imgRowS32[j];
+	 if (pixVal < threshold) {
+	    if (in_span) {
+	       if(nspan >= size_spans) {
+		  size_spans *= 2;
+		  spans = psRealloc(spans, size_spans*sizeof(WSPAN));
+	       }
+	       spans[nspan].id = in_span;
+	       spans[nspan].y = i;
+	       spans[nspan].x0 = x0;
+	       spans[nspan].x1 = j - 1;
+	       
+	       nspan++;
+
+	       in_span = 0;
+	    }
+	 } else {			/* a pixel to fix */
+	    if(idc[j - 1] != 0) {
+	       id = idc[j - 1];
+	    } else if(idp[j - 1] != 0) {
+	       id = idp[j - 1];
+	    } else if(idp[j] != 0) {
+	       id = idp[j];
+	    } else if(idp[j + 1] != 0) {
+	       id = idp[j + 1];
+	    } else {
+	       id = ++nobj;
+
+	       if(id >= size_aliases) {
+		  size_aliases *= 2;
+		  aliases = psRealloc(aliases, size_aliases*sizeof(int));
+	       }
+	       aliases[id] = id;
+	    }
+
+	    idc[j] = id;
+	    if(!in_span) {
+	       x0 = j; in_span = id;
+	    }
+/*
+ * Do we need to merge ID numbers? If so, make suitable entries in aliases[]
+ */
+	    if(idp[j + 1] != 0 && idp[j + 1] != id) {
+	       aliases[resolve_alias(aliases, idp[j + 1])] =
+						    resolve_alias(aliases, id);
+	       
+	       idc[j] = id = idp[j + 1];
+	    }
+	 }
+      }
+
+      if(in_span) {
+	 if(nspan >= size_spans) {
+	    size_spans *= 2;
+	    spans = psRealloc(spans, size_spans*sizeof(WSPAN));
+	 }
+
+	 assert(nspan < size_spans);	/* we checked for space above */
+	 spans[nspan].id = in_span;
+	 spans[nspan].y = i;
+	 spans[nspan].x0 = x0;
+	 spans[nspan].x1 = j - 1;
+	 
+	 nspan++;
+      }
+   }
+
+   psFree(id_s);
+/*
+ * Resolve aliases; first alias chains, then the IDs in the spans
+ */
+   for (int i = 0; i < nspan; i++) {
+      spans[i].id = resolve_alias(aliases, spans[i].id);
+   }
+
+   psFree(aliases);
+/*
+ * Sort spans by ID, so we can sweep through them once
+ */
+   if(nspan > 0) {
+      qsort(spans, nspan, sizeof(WSPAN), compar);
+   }
+/*
+ * Build pmFootprints from the spans
+ */
+   psArray *footprints = psArrayAlloc(nobj);
+   int n = 0;			// number of pmFootprints
+
+   if(nspan > 0) {
+      id = spans[0].id;
+      i0 = 0;
+      for (int i = 0; i <= nspan; i++) {	/* nspan + 1 to catch last object */
+	 if(i == nspan || spans[i].id != id) {
+	    pmFootprint *fp = pmFootprintAlloc(i - i0, img);
+	    
+	    for(; i0 < i; i0++) {
+		pmFootprintAddSpan(fp, spans[i0].y + row0,
+				   spans[i0].x0 + col0, spans[i0].x1 + col0);
+	    }
+
+	    if (fp->npix < npixMin) {
+	       psFree(fp);
+	    } else {
+	       footprints->data[n++] = fp;
+	    }
+	 }
+	 
+	 id = spans[i].id;
+      }
+   }
+
+   footprints = psArrayRealloc(footprints, n);
+/*
+ * clean up
+ */
+   psFree(spans);
+
+   return footprints;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/pmFootprintsFindAtPoint.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/pmFootprintsFindAtPoint.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/pmFootprintsFindAtPoint.c	(revision 22157)
@@ -0,0 +1,390 @@
+# include "psphotInternal.h"
+
+/**** find footprints in a small image relative to a reference point ****/
+
+/*
+ * A data structure to hold the starting point for a search for pixels above threshold,
+ * used by pmFootprintsFindAtPoint
+ *
+ * We don't want to find this span again --- it's already part of the footprint ---
+ * so we set appropriate mask bits
+ *
+ * EAM : these function were confusingly using "startspan" and "spartspan"  
+ * I've rationalized them all to 'startspan'
+ */
+
+//
+// An enum for what we should do with a Startspan
+//
+typedef enum {PM_SSPAN_DOWN = 0,	// scan down from this span
+	      PM_SSPAN_UP,		// scan up from this span
+	      PM_SSPAN_RESTART,		// restart scanning from this span
+	      PM_SSPAN_DONE		// this span is processed
+} PM_SSPAN_DIR;				// How to continue searching
+//
+// An enum for mask's pixel values.  We're looking for pixels that are above threshold, and
+// we keep extra book-keeping information in the PM_SSPAN_STOP plane.  It's simpler to be
+// able to check for 
+//
+enum {
+    PM_SSPAN_INITIAL = 0x0,		// initial state of pixels.
+    PM_SSPAN_DETECTED = 0x1,		// we've seen this pixel
+    PM_SSPAN_STOP = 0x2			// you may stop searching when you see this pixel
+};
+//
+// The struct that remembers how to [re-]start scanning the image for pixels
+//
+typedef struct {
+    const pmSpan *span;			// save the pixel range
+    PM_SSPAN_DIR direction;		// How to continue searching
+    bool stop;				// should we stop searching?
+} Startspan;
+
+static void startspanFree(Startspan *sspan) {
+    psFree((void *)sspan->span);
+}
+
+static Startspan *
+StartspanAlloc(const pmSpan *span,	// The span in question
+	       psImage *mask,		// Pixels that we've already detected
+	       const PM_SSPAN_DIR dir	// Should we continue searching towards the top of the image?
+    ) {
+    Startspan *sspan = psAlloc(sizeof(Startspan));
+    psMemSetDeallocator(sspan, (psFreeFunc)startspanFree);
+
+    sspan->span = psMemIncrRefCounter((void *)span);
+    sspan->direction = dir;
+    sspan->stop = false;
+    
+    if (mask != NULL) {			// remember that we've detected these pixels
+	psMaskType *mpix = &mask->data.PS_TYPE_MASK_DATA[span->y - mask->row0][span->x0 - mask->col0];
+
+	for (int i = 0; i <= span->x1 - span->x0; i++) {
+	    mpix[i] |= PM_SSPAN_DETECTED;
+	    if (mpix[i] & PM_SSPAN_STOP) {
+		sspan->stop = true;
+	    }
+	}
+    }
+    
+    return sspan;
+}
+
+//
+// Add a new Startspan to an array of Startspans.  Iff we see a stop bit, return true
+//
+static bool add_startspan(psArray *startspans, // the saved Startspans
+			  const pmSpan *sp, // the span in question
+			  psImage *mask, // mask of detected/stop pixels
+			  const PM_SSPAN_DIR dir) { // the desired direction to search
+    if (dir == PM_SSPAN_RESTART) {
+	if (add_startspan(startspans, sp, mask,  PM_SSPAN_UP) ||
+	    add_startspan(startspans, sp, NULL, PM_SSPAN_DOWN)) {
+	    return true;
+	}
+    } else {
+	Startspan *sspan = StartspanAlloc(sp, mask, dir);
+	if (sspan->stop) {		// we detected a stop bit
+	    psFree(sspan);		// don't allocate new span
+
+	    return true;
+	} else {
+	    psArrayAdd(startspans, 1, sspan);
+	    psFree(sspan);		// as it's now owned by startspans
+	}
+    }
+
+    return false;
+}
+
+/************************************************************************************************************/
+/*
+ * Search the image for pixels above threshold, starting at a single Startspan.
+ * We search the array looking for one to process; it'd be better to move the
+ * ones that we're done with to the end, but it probably isn't worth it for
+ * the anticipated uses of this routine.
+ *
+ * This is the guts of pmFootprintsFindAtPoint
+ */
+static bool do_startspan(pmFootprint *fp, // the footprint that we're building
+			 const psImage *img, // the psImage we're working on
+			 psImage *mask, // the associated masks
+			 const float threshold,	// Threshold
+			 psArray *startspans) {	// specify which span to process next
+    bool F32 = false;			// is this an F32 image?
+    if (img->type.type == PS_TYPE_F32) {
+	F32 = true;
+    } else if (img->type.type == PS_TYPE_S32) {
+	F32 = false;
+    } else {				// N.b. You can't trivially add more cases here; F32 is just a bool
+	psError(PS_ERR_UNKNOWN, true, "Unsupported psImage type: %d", img->type.type);
+	return NULL;
+    }
+
+    psF32 *imgRowF32 = NULL;		// row pointer if F32
+    psS32 *imgRowS32 = NULL;		//  "   "   "  "  !F32
+    psMaskType *maskRow = NULL;		//  masks's row pointer
+    
+    const int row0 = img->row0;
+    const int col0 = img->col0;
+    const int numRows = img->numRows;
+    const int numCols = img->numCols;
+    
+    /********************************************************************************************************/
+    
+    Startspan *sspan = NULL;
+    for (int i = 0; i < startspans->n; i++) {
+	sspan = startspans->data[i];
+	if (sspan->direction != PM_SSPAN_DONE) {
+	    break;
+	}
+	if (sspan->stop) {
+	    break;
+	}
+    }
+    if (sspan == NULL || sspan->direction == PM_SSPAN_DONE) { // no more Startspans to process
+	return false;
+    }
+    if (sspan->stop) {			// they don't want any more spans processed
+	return false;
+    }
+    /*
+     * Work
+     */
+    const PM_SSPAN_DIR dir = sspan->direction;
+    /*
+     * Set initial span to the startspan
+     */
+    int x0 = sspan->span->x0 - col0, x1 = sspan->span->x1 - col0;
+    /*
+     * Go through image identifying objects
+     */
+    int nx0, nx1 = -1;			// new values of x0, x1
+    const int di = (dir == PM_SSPAN_UP) ? 1 : -1; // how much i changes to get to the next row
+    bool stop = false;			// should I stop searching for spans?
+
+    for (int i = sspan->span->y -row0 + di; i < numRows && i >= 0; i += di) {
+	imgRowF32 = img->data.F32[i];	// only one of
+	imgRowS32 = img->data.S32[i];	//      these is valid!
+	maskRow = mask->data.PS_TYPE_MASK_DATA[i];
+	//
+	// Search left from the pixel diagonally to the left of (i - di, x0). If there's
+	// a connected span there it may need to grow up and/or down, so push it onto
+	// the stack for later consideration
+	//
+	nx0 = -1;
+	for (int j = x0 - 1; j >= -1; j--) {
+	    double pixVal = (j < 0) ? threshold - 100 : (F32 ? imgRowF32[j] : imgRowS32[j]);
+	    if ((maskRow[j] & PM_SSPAN_DETECTED) || pixVal < threshold) {
+		if (j < x0 - 1) {	// we found some pixels above threshold
+		    nx0 = j + 1;
+		}
+		break;
+	    }
+	}
+
+	if (nx0 < 0) {			// no span to the left
+	    nx1 = x0 - 1;		// we're going to resume searching at nx1 + 1
+	} else {
+	    //
+	    // Search right in leftmost span
+	    //
+	    //nx1 = 0;			// make gcc happy
+	    for (int j = nx0 + 1; j <= numCols; j++) {
+		double pixVal = (j >= numCols) ? threshold - 100 : (F32 ? imgRowF32[j] : imgRowS32[j]);
+		if ((maskRow[j] & PM_SSPAN_DETECTED) || pixVal < threshold) {
+		    nx1 = j - 1;
+		    break;
+		}
+	    }
+	    
+	    const pmSpan *sp = pmFootprintAddSpan(fp, i + row0, nx0 + col0, nx1 + col0);
+	    
+	    if (add_startspan(startspans, sp, mask, PM_SSPAN_RESTART)) {
+		stop = true;
+		break;
+	    }
+	}
+	//
+	// Now look for spans connected to the old span.  The first of these we'll
+	// simply process, but others will have to be deferred for later consideration.
+	//
+	// In fact, if the span overhangs to the right we'll have to defer the overhang
+	// until later too, as it too can grow in both directions
+	//
+	// Note that column numCols exists virtually, and always ends the last span; this
+	// is why we claim below that sx1 is always set
+	//
+	bool first = false;		// is this the first new span detected?
+	for (int j = nx1 + 1; j <= x1 + 1; j++) {
+	    double pixVal = (j >= numCols) ? threshold - 100 : (F32 ? imgRowF32[j] : imgRowS32[j]);
+	    if (!(maskRow[j] & PM_SSPAN_DETECTED) && pixVal >= threshold) {
+		int sx0 = j++;		// span that we're working on is sx0:sx1
+		int sx1 = -1;		// We know that if we got here, we'll also set sx1
+		for (; j <= numCols; j++) {
+		    double pixVal = (j >= numCols) ? threshold - 100 : (F32 ? imgRowF32[j] : imgRowS32[j]);
+		    if ((maskRow[j] & PM_SSPAN_DETECTED) || pixVal < threshold) { // end of span
+			sx1 = j;
+			break;
+		    }
+		}
+		assert (sx1 >= 0);
+
+		const pmSpan *sp;
+		if (first) {
+		    if (sx1 <= x1) {
+			sp = pmFootprintAddSpan(fp, i + row0, sx0 + col0, sx1 + col0 - 1);
+			if (add_startspan(startspans, sp, mask, PM_SSPAN_DONE)) {
+			    stop = true;
+			    break;
+			}
+		    } else {		// overhangs to right
+			sp = pmFootprintAddSpan(fp, i + row0, sx0 + col0, x1 + col0);
+			if (add_startspan(startspans, sp, mask, PM_SSPAN_DONE)) {
+			    stop = true;
+			    break;
+			}
+			sp = pmFootprintAddSpan(fp, i + row0, x1 + 1 + col0, sx1 + col0 - 1);
+			if (add_startspan(startspans, sp, mask, PM_SSPAN_RESTART)) {
+			    stop = true;
+			    break;
+			}
+		    }
+		    first = false;
+		} else {
+		    sp = pmFootprintAddSpan(fp, i + row0, sx0 + col0, sx1 + col0 - 1);
+		    if (add_startspan(startspans, sp, mask, PM_SSPAN_RESTART)) {
+			stop = true;
+			break;
+		    }
+		}
+	    }
+	}
+
+	if (stop || first == false) {	// we're done
+	    break;
+	}
+
+	x0 = nx0; x1 = nx1;
+    }
+    /*
+     * Cleanup
+     */
+
+    sspan->direction = PM_SSPAN_DONE;
+    return stop ? false : true;
+}
+
+/*
+ * Go through an image, starting at (row, col) and assembling all the pixels
+ * that are connected to that point (in a chess kings-move sort of way) into
+ * a pmFootprint.
+ *
+ * This is much slower than pmFootprintsFind if you want to find lots of
+ * footprints, but if you only want a small region about a given point it
+ * can be much faster
+ *
+ * N.b. The returned pmFootprint is not in "normal form"; that is the pmSpans
+ * are not sorted by increasing y, x0, x1.  If this matters to you, call
+ * pmFootprintNormalize()
+ */
+pmFootprint *
+pmFootprintsFindAtPoint(const psImage *img,	// image to search
+		       const float threshold,	// Threshold
+		       const psArray *peaks, // array of peaks; finding one terminates search for footprint
+		       int row, int col) { // starting position (in img's parent's coordinate system)
+   assert(img != NULL);
+
+   bool F32 = false;			// is this an F32 image?
+   if (img->type.type == PS_TYPE_F32) {
+       F32 = true;
+   } else if (img->type.type == PS_TYPE_S32) {
+       F32 = false;
+   } else {				// N.b. You can't trivially add more cases here; F32 is just a bool
+       psError(PS_ERR_UNKNOWN, true, "Unsupported psImage type: %d", img->type.type);
+       return NULL;
+   }
+   psF32 *imgRowF32 = NULL;		// row pointer if F32
+   psS32 *imgRowS32 = NULL;		//  "   "   "  "  !F32
+   
+   const int row0 = img->row0;
+   const int col0 = img->col0;
+   const int numRows = img->numRows;
+   const int numCols = img->numCols;
+/*
+ * Is point in image, and above threshold?
+ */
+   row -= row0; col -= col0;
+   if (row < 0 || row >= numRows ||
+       col < 0 || col >= numCols) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "row/col == (%d, %d) are out of bounds [%d--%d, %d--%d]",
+		row + row0, col + col0, row0, row0 + numRows - 1, col0, col0 + numCols - 1);
+       return NULL;
+   }
+
+   double pixVal = F32 ? img->data.F32[row][col] : img->data.S32[row][col];
+   if (pixVal < threshold) {
+       return pmFootprintAlloc(0, img);
+   }
+   
+   pmFootprint *fp = pmFootprintAlloc(1 + img->numRows/10, img);
+/*
+ * We need a mask for two purposes; to indicate which pixels are already detected,
+ * and to store the "stop" pixels --- those that, once reached, should stop us
+ * looking for the rest of the pmFootprint.  These are generally set from peaks.
+ */
+   psImage *mask = psImageAlloc(numCols, numRows, PS_TYPE_MASK);
+   P_PSIMAGE_SET_ROW0(mask, row0);
+   P_PSIMAGE_SET_COL0(mask, col0);
+   psImageInit(mask, PM_SSPAN_INITIAL);
+   //
+   // Set stop bits from peaks list
+   //
+   assert (peaks == NULL || peaks->n == 0 || psMemCheckPeak(peaks->data[0]));
+   if (peaks != NULL) {
+       for (int i = 0; i < peaks->n; i++) {
+	   pmPeak *peak = peaks->data[i];
+	   mask->data.PS_TYPE_MASK_DATA[peak->y - mask->row0][peak->x - mask->col0] |= PM_SSPAN_STOP;
+       }
+   }
+/*
+ * Find starting span passing through (row, col)
+ */
+   psArray *startspans = psArrayAllocEmpty(1); // spans where we have to restart the search
+   
+   imgRowF32 = img->data.F32[row];	// only one of
+   imgRowS32 = img->data.S32[row];	//      these is valid!
+   psMaskType *maskRow = mask->data.PS_TYPE_MASK_DATA[row];
+   {
+       int i;
+       for (i = col; i >= 0; i--) {
+	   pixVal = F32 ? imgRowF32[i] : imgRowS32[i];
+	   if ((maskRow[i] & PM_SSPAN_DETECTED) || pixVal < threshold) {
+	       break;
+	   }
+       }
+       int i0 = i;
+       for (i = col; i < numCols; i++) {
+	   pixVal = F32 ? imgRowF32[i] : imgRowS32[i];
+	   if ((maskRow[i] & PM_SSPAN_DETECTED) || pixVal < threshold) {
+	       break;
+	   }
+       }
+       int i1 = i;
+       const pmSpan *sp = pmFootprintAddSpan(fp, row + row0, i0 + col0 + 1, i1 + col0 - 1);
+
+       (void)add_startspan(startspans, sp, mask, PM_SSPAN_RESTART);
+   }
+   /*
+    * Now workout from those Startspans, searching for pixels above threshold
+    */
+   while (do_startspan(fp, img, mask, threshold, startspans)) continue;
+   /*
+    * Cleanup
+    */
+   psFree(mask);
+   psFree(startspans);			// restores the image pixel
+
+   return fp;				// pmFootprint really
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/pmSpan.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/pmSpan.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/pmSpan.c	(revision 22157)
@@ -0,0 +1,55 @@
+# include "psphotInternal.h"
+
+/*** functions to manipulate image spans ***/
+
+static void spanFree(pmSpan *tmp) {
+    return;
+}
+
+/*
+ * pmSpanAlloc()
+ */
+pmSpan *pmSpanAlloc(int y, int x0, int x1)
+{
+    pmSpan *span = (pmSpan *)psAlloc(sizeof(pmSpan));
+    psMemSetDeallocator(span, (psFreeFunc) spanFree);
+
+    span->y = y;
+    span->x0 = x0;
+    span->x1 = x1;
+
+    return(span);
+}
+
+bool pmSpanTest(const psPtr ptr)
+{
+    return (psMemGetDeallocator(ptr) == (psFreeFunc)spanFree);
+}
+
+//
+// Sort pmSpans by y, then x0, then x1
+//
+int pmSpanSortByYX(const void **a, const void **b) {
+    const pmSpan *sa = *(const pmSpan **)a;
+    const pmSpan *sb = *(const pmSpan **)b;
+
+    if (sa->y < sb->y) {
+	return -1;
+    } else if (sa->y == sb->y) {
+	if (sa->x0 < sb->x0) {
+	    return -1;
+	} else if (sa->x0 == sb->x0) {
+	    if (sa->x1 < sb->x1) {
+		return -1;
+	    } else if (sa->x1 == sb->x1) {
+		return 0;
+	    } else {
+		return 1;
+	    }
+	} else {
+	    return 1;
+	}
+    } else {
+	return 1;
+    }
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphot.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphot.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphot.c	(revision 22157)
@@ -0,0 +1,42 @@
+# include "psphotStandAlone.h"
+
+static void usage (void) {
+    fprintf (stderr, "USAGE: psphot [-file image(s)] [-list imagelist] (output)\n");
+    exit (PS_EXIT_CONFIG_ERROR);
+}
+
+int main (int argc, char **argv) {
+
+    psTimerStart ("complete");
+    pmErrorRegister();			// register psModule's error codes/messages
+    psphotErrorRegister();              // register our error codes/messages
+    psphotModelClassInit ();            // load implementation-specific models
+
+    // load command-line arguments, options, and system config data
+    pmConfig *config = psphotArguments (argc, argv);
+    if (!config) {
+	psErrorStackPrint(stderr, "Error reading arguments\n");
+	usage ();
+    }
+
+    // load input data (config and images (signal, noise, mask)
+    if (!psphotParseCamera (config)) {
+        psErrorStackPrint(stderr, "Error setting up the camera\n");
+        exit (psphotGetExitStatus());
+    }
+
+    // call psphot for each readout
+    if (!psphotImageLoop (config)) {
+        psErrorStackPrint(stderr, "Error in the psphot image loop\n");
+        exit (psphotGetExitStatus());
+    }
+
+    psLogMsg ("psphot", 3, "complete psphot run: %f sec\n", psTimerMark ("complete"));
+
+    psErrorCode exit_status = psphotGetExitStatus();
+    psphotCleanup (config);
+    exit (exit_status);
+}
+
+// all functions which return to this level must raise one of the top-level error codes if they
+// exit with an error.  these error codes are used to specify the program exit status
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphot.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphot.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphot.h	(revision 22157)
@@ -0,0 +1,185 @@
+/* This file defines the library functions available to external programs.  It must be included
+ * by programs which are compiled against psphot functions.
+ */
+
+#ifndef PSPHOT_H
+#define PSPHOT_H
+
+#include <psmodules.h>
+
+#include "psphotErrorCodes.h"
+#include "pmFootprint.h"
+
+#define PSPHOT_RECIPE "PSPHOT" // Name of the recipe to use
+
+#define PSPHOT_RECIPE_PSF_FAKE_ALLOW "PSF.FAKE.ALLOW" // Name for recipe component permitting fake PSFs
+
+
+// top-level psphot functions
+const char     *psphotCVSName(void);
+psString        psphotVersion(void);
+psString        psphotVersionLong(void);
+
+bool            psphotModelTest (pmConfig *config, const pmFPAview *view, psMetadata *recipe);
+bool            psphotReadout (pmConfig *config, const pmFPAview *view);
+bool            psphotReadoutCleanup (pmConfig *config, pmReadout *readout, psMetadata *recipe, pmDetections *detections, pmPSF *psf, psArray *sources);
+bool            psphotDefineFiles (pmConfig *config, pmFPAfile *input);
+bool            psphotSetMaskBits (pmConfig *config);
+
+// XXX test functions
+psArray        *psphotFakeSources ();
+
+// psphotReadout functions
+bool            psphotModelBackground (pmConfig *config, const pmFPAview *view, const char *filename);
+bool            psphotSubtractBackground (pmConfig *config, const pmFPAview *view, const char *filename) ;
+pmDetections   *psphotFindDetections (pmDetections *detections, pmReadout *readout, psMetadata *recipe);
+
+psArray        *psphotSourceStats (pmReadout *readout, psMetadata *recipe, pmDetections *detections);
+bool            psphotRoughClass (psArray *sources, psMetadata *recipe, const bool findPsfClump);
+bool            psphotBasicDeblend (psArray *sources, psMetadata *recipe);
+pmPSF          *psphotChoosePSF (pmReadout *readout, psArray *sources, psMetadata *recipe);
+bool            psphotPSFstats (pmReadout *readout, psMetadata *recipe, pmPSF *psf);
+bool            psphotMomentsStats (pmReadout *readout, psMetadata *recipe, psArray *sources);
+bool            psphotFitSourcesLinear (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf, bool final);
+bool            psphotGuessModels (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf);
+bool            psphotBlendFit (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf);
+bool            psphotReplaceUnfitSources (psArray *sources, psMaskType maskVal);
+bool            psphotReplaceAllSources (psArray *sources, psMetadata *recipe);
+bool            psphotRemoveAllSources (psArray *sources, psMetadata *recipe);
+bool            psphotApResid (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf);
+bool            psphotMagnitudes (psArray *sources, psMetadata *recipe, pmPSF *psf, pmReadout *background);
+bool            psphotSkyReplace (pmConfig *config, const pmFPAview *view);
+bool            psphotExtendedSourceAnalysis (pmReadout *readout, psArray *sources, psMetadata *recipe);
+bool            psphotExtendedSourceFits (pmReadout *readout, psArray *sources, psMetadata *recipe);
+
+// used by psphotFindDetections
+psImage        *psphotSignificanceImage (pmReadout *readout, psMetadata *recipe, const int pass, psMaskType maskVal);
+psArray        *psphotFindPeaks (psImage *significance, pmReadout *readout, psMetadata *recipe, const float threshold, const int nMax);
+bool            psphotFindFootprints (pmDetections *detections, psImage *significance, pmReadout *readout, psMetadata *recipe, const int pass, psMaskType maskVal);
+psErrorCode     psphotCullPeaks(const psImage *img, const psImage *weight, const psMetadata *recipe, psArray *footprints);
+
+// used by ApResid
+bool            psphotMagErrorScale (float *errorScale, float *errorFloor, psVector *dMag, psVector *dap, psVector *mask, int nGroup);
+bool            psphotApResidTrend (pmReadout *readout, pmPSF *psf, int Npsf, int scale, float *errorScale, float *errorFloor, psVector *mask, psVector *xPos, psVector *yPos, psVector *apResid, psVector *dMag);
+
+// basic support functions
+void            psphotModelClassInit (void);
+bool            psphotGrowthCurve (pmReadout *readout, pmPSF *psf, bool ignore, psMaskType maskVal);
+bool            psphotSetMaskAndWeight (pmConfig *config, pmReadout *readout, psMetadata *recipe);
+void            psphotSourceFreePixels (psArray *sources);
+
+// functions to set the correct source pixels
+bool            psphotInitRadiusPSF (const psMetadata *recipe, const pmModelType type);
+bool            psphotCheckRadiusPSF (pmReadout *readout, pmSource *source, pmModel *model, psMaskType markVal);
+bool            psphotCheckRadiusPSFBlend (pmReadout *readout, pmSource *source, pmModel *model, psMaskType markVal, float dR);
+bool            psphotInitRadiusEXT (psMetadata *recipe, pmModelType type);
+bool            psphotCheckRadiusEXT (pmReadout *readout, pmSource *source, pmModel *model, psMaskType markVal);
+
+// output functions
+bool            psphotAddPhotcode (psMetadata *recipe, pmConfig *config, const pmFPAview *view, const char *filerule);
+bool            psphotDumpMoments (psMetadata *recipe, psArray *sources);
+psMetadata     *psphotDefineHeader (psMetadata *recipe);
+int             psphotSaveImage (psMetadata *header, psImage *image, char *filename);
+bool            psphotDumpConfig (pmConfig *config);
+pmReadout      *psphotSelectBackground (pmConfig *config, const pmFPAview *view, const bool stdev);
+
+// PSF / DBL / EXT evaluation functions
+bool            psphotEvalPSF (pmSource *source, pmModel *model);
+bool            psphotEvalDBL (pmSource *source, pmModel *model);
+bool            psphotEvalEXT (pmSource *source, pmModel *model);
+
+//  functions to support the source fitting process
+bool            psphotInitLimitsPSF (psMetadata *recipe, pmReadout *readout);
+bool            psphotInitLimitsEXT (psMetadata *recipe);
+bool            psphotFitBlend (pmReadout *readout, pmSource *source, pmPSF *psf, psMaskType maskVal, psMaskType markVal);
+bool            psphotFitBlob (pmReadout *readout, pmSource *source, psArray *sources, pmPSF *psf, psMaskType maskVal, psMaskType markVal);
+bool            psphotFitPSF (pmReadout *readout, pmSource *source, pmPSF *psf, psMaskType maskVal, psMaskType markVal);
+pmModel        *psphotFitEXT (pmReadout *readout, pmSource *source, pmModelType modelType, psMaskType maskVal, psMaskType markVal);
+psArray        *psphotFitDBL (pmReadout *readout, pmSource *source, psMaskType maskVal, psMaskType markVal);
+
+// functions to support simultaneous multi-source fitting
+bool            psphotFitSet (pmSource *oneSrc, pmModel *oneModel, char *fitset, pmSourceFitMode mode, psMaskType maskVal);
+
+// plotting functions (available if libkapa is installed)
+bool            psphotPlotMoments (pmConfig *config, pmFPAview *view, psArray *sources);
+bool            psphotPlotPSFModel (pmConfig *config, pmFPAview *view, psArray *sources);
+bool            psphotFitInit ();
+bool            psphotFitSummary ();
+bool            psphotMergeSources (psArray *oldSources, psArray *newSources);
+bool            psphotLoadExtSources (pmConfig *config, const pmFPAview *view, psArray *sources);
+pmPSF          *psphotLoadPSF (pmConfig *config, const pmFPAview *view, psMetadata *recipe);
+bool            psphotSetHeaderNstars (psMetadata *recipe, psArray *sources);
+bool            psphotAddNoise (pmReadout *readout, psArray *sources, psMetadata *recipe);
+bool            psphotSubNoise (pmReadout *readout, psArray *sources, psMetadata *recipe);
+bool            psphotAddOrSubNoise (pmReadout *readout, psArray *sources, psMetadata *recipe, bool add);
+bool            psphotRadialPlot (int *kapa, const char *filename, pmSource *source);
+bool            psphotSourcePlots (pmReadout *readout, psArray *sources, psMetadata *recipe);
+bool            psphotMosaicSubimage (psImage *outImage, pmSource *source, int Xo, int Yo, int DX, int DY);
+
+bool            psphotAddWithTest (pmSource *source, bool useState, psMaskType maskVal);
+bool            psphotSubWithTest (pmSource *source, bool useState, psMaskType maskVal);
+bool            psphotSetState (pmSource *source, bool curState, psMaskType maskVal);
+bool            psphotDeblendSatstars (psArray *sources, psMetadata *recipe);
+bool            psphotSourceSize (pmReadout *readout, psArray *sources, psMetadata *recipe, long first);
+
+bool            psphotMakeResiduals (psArray *sources, psMetadata *recipe, pmPSF *psf, psMaskType maskVal);
+
+pmModel        *psphotPSFConvModel (pmReadout *readout, pmSource *source, pmModelType modelType, psMaskType maskVal, psMaskType markVal, int psfSize);
+
+psKernel       *psphotKernelFromPSF (pmSource *source, int nPix);
+
+bool            psphotRadialProfile (pmSource *source, psMetadata *recipe, psMaskType maskVal);
+bool            psphotPetrosian (pmSource *source, psMetadata *recipe, psMaskType maskVal);
+bool            psphotIsophotal (pmSource *source, psMetadata *recipe, psMaskType maskVal);
+bool            psphotAnnuli (pmSource *source, psMetadata *recipe, psMaskType maskVal);
+bool            psphotKron (pmSource *source, psMetadata *recipe, psMaskType maskVal);
+
+// structures & functions to support psf-convolved model fitting
+
+// pmPCMData : PSF Convolved Model data storage structure
+typedef struct {
+    psImage *model;
+    psArray *dmodels;
+    psImage *modelConv;
+    psArray *dmodelsConv;
+} pmPCMData;
+
+
+// psf-convolved model fitting
+bool psphotModelWithPSF_LMM (
+    psMinimization *min,
+    psImage *covar,
+    psVector *params,
+    psMinConstraint *constraint,
+    pmSource *source,
+    const psKernel *psf,
+    psMinimizeLMChi2Func func);
+
+psF32 psphotModelWithPSF_SetABX(
+    psImage  *alpha,
+    psVector *beta,
+    const psVector *params,
+    const psVector *paramMask,
+    pmPCMData *pcm,
+    const pmSource *source,
+    const psKernel *psf,
+    psMinimizeLMChi2Func func);
+
+pmPCMData *pmPCMDataAlloc (
+    const psVector *params,
+    const psVector *paramMask,
+    pmSource *source);
+
+psImage *pmPCMDataSaveImage (pmPCMData *pcm);
+
+int psphotKapaOpen ();
+bool psphotKapaClose ();
+bool psphotImageBackgroundCellHistogram (psVector *values, float mean, float sigma, int ix, int iy);
+bool psphotDiagnosticPlots (pmConfig *config, char *name, ...);
+
+bool psphotMakeFluxScale (psImage *image, psMetadata *recipe, pmPSF *psf);
+
+bool psphotCheckStarDistribution (psArray *sources, psArray *stars, pmPSFOptions *options);
+int psphotSupplementStars (psArray *stars, psArray *sources, psImageBinning *binning, int ix, int iy);
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotAddNoise.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotAddNoise.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotAddNoise.c	(revision 22157)
@@ -0,0 +1,100 @@
+# include "psphotInternal.h"
+
+bool psphotAddNoise (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+  return psphotAddOrSubNoise (readout, sources, recipe, true);
+}
+
+bool psphotSubNoise (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+  return psphotAddOrSubNoise (readout, sources, recipe, false);
+}
+
+bool psphotAddOrSubNoise (pmReadout *readout, psArray *sources, psMetadata *recipe, bool add) {
+
+    bool status = false;
+    psEllipseShape oldshape;
+    psEllipseShape newshape;
+    psEllipseAxes axes;
+
+    PS_ASSERT (readout, false);
+    PS_ASSERT (readout->parent, false);
+    PS_ASSERT (readout->parent->concepts, false);
+
+    psTimerStart ("psphot");
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    // increase variance by factor*(object noise):
+    // weight = flux/gain + rn^2/g^2
+    // we are adding factor*flux/gain
+
+    float FACTOR = psMetadataLookupF32 (&status, recipe, "NOISE.FACTOR");
+    PS_ASSERT (status, false);
+    float SIZE = psMetadataLookupF32 (&status, recipe, "NOISE.SIZE");
+    PS_ASSERT (status, false);
+
+    if (SIZE <= 0) {
+       return true;
+    }
+
+    float GAIN = psMetadataLookupF32(&status, readout->parent->concepts, "CELL.GAIN"); // Cell gain
+    PS_ASSERT (status, false);
+
+    FACTOR /= GAIN;
+
+    // loop over all source
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+
+        // skip sources which were not subtracted
+        if (!(source->mode & PM_SOURCE_MODE_SUBTRACTED)) continue;
+
+        // select appropriate model
+        pmModel *model = pmSourceGetModel (NULL, source);
+        if (model == NULL) continue;  // model must be defined
+
+        if (add) {
+            psTrace ("psphot", 4, "adding noise for object at %f,%f\n", model->params->data.F32[PM_PAR_XPOS], model->params->data.F32[PM_PAR_YPOS]);
+        } else {
+            psTrace ("psphot", 4, "remove noise for object at %f,%f\n", model->params->data.F32[PM_PAR_XPOS], model->params->data.F32[PM_PAR_YPOS]);
+        }
+
+        psF32 *PAR = model->params->data.F32;
+
+        // save original values
+        float oldI0  = PAR[PM_PAR_I0];
+        oldshape.sx  = PAR[PM_PAR_SXX];
+        oldshape.sy  = PAR[PM_PAR_SYY];
+        oldshape.sxy = PAR[PM_PAR_SXY];
+
+	// XXX can this be done more intelligently?
+	if (oldI0 == 0.0) continue;
+	if (!isfinite(oldI0)) continue;
+
+        // increase size and height of source
+        axes = psEllipseShapeToAxes (oldshape, 20.0);
+        axes.major *= SIZE;
+        axes.minor *= SIZE;
+        newshape = psEllipseAxesToShape (axes);
+        PAR[PM_PAR_I0]  = FACTOR*PS_SQR(oldI0);
+        PAR[PM_PAR_SXX] = newshape.sx;
+        PAR[PM_PAR_SYY] = newshape.sy;
+        PAR[PM_PAR_SXY] = newshape.sxy;
+
+        // XXX if we use pmSourceOp, the size (and possibly Io) will not be respected
+        pmSourceOp (source, PM_MODEL_OP_FULL | PM_MODEL_OP_NOISE, add, maskVal, 0, 0);
+
+        // restore original values
+        PAR[PM_PAR_I0]  = oldI0;
+        PAR[PM_PAR_SXX] = oldshape.sx;
+        PAR[PM_PAR_SYY] = oldshape.sy;
+        PAR[PM_PAR_SXY] = oldshape.sxy;
+    }
+    if (add) {
+        psLogMsg ("psphot.noise", PS_LOG_INFO, "add noise for %ld objects: %f sec\n", sources->n, psTimerMark ("psphot"));
+    } else {
+        psLogMsg ("psphot.noise", PS_LOG_INFO, "sub noise for %ld objects: %f sec\n", sources->n, psTimerMark ("psphot"));
+    }
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotAnnuli.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotAnnuli.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotAnnuli.c	(revision 22157)
@@ -0,0 +1,61 @@
+# include "psphotInternal.h"
+
+bool psphotAnnuli (pmSource *source, psMetadata *recipe, psMaskType maskVal) {
+
+  assert (source->extpars);
+  assert (source->extpars->profile);
+  assert (source->extpars->profile->radius);
+  assert (source->extpars->profile->flux);
+
+  bool status;
+
+  psVector *radius = source->extpars->profile->radius;
+  psVector *weight = source->extpars->profile->weight;
+  psVector *flux = source->extpars->profile->flux;
+
+  // XXX how do I define the radii?  we can put a vector in the recipe...
+  // radialBins defines the bounds or start and stop (we can skip some that way...
+  psVector *radialBinsLower = psMetadataLookupPtr (&status, recipe, "RADIAL.ANNULAR.BINS.LOWER");
+  psVector *radialBinsUpper = psMetadataLookupPtr (&status, recipe, "RADIAL.ANNULAR.BINS.UPPER");
+  assert (radialBinsLower->n == radialBinsUpper->n);
+
+  psVector *fluxValues = psVectorAlloc (radialBinsLower->n, PS_TYPE_F32);
+  psVector *fluxSquare = psVectorAlloc (radialBinsLower->n, PS_TYPE_F32);
+  psVector *fluxWeight = psVectorAlloc (radialBinsLower->n, PS_TYPE_F32);
+  psVector *pixelCount = psVectorAlloc (radialBinsLower->n, PS_TYPE_F32);
+  psVectorInit (fluxValues, 0.0);
+  psVectorInit (fluxSquare, 0.0);
+  psVectorInit (fluxWeight, 0.0);
+  psVectorInit (pixelCount, 0.0);
+
+  // XXX this code assumes the radii are in pixels.  convert from arcsec with plate scale
+  // XXX assume the annulii above are not overlapping?  much faster...
+  // XXX this might be must faster in the reverse order: loop over annulii and use disection to 
+  // skip to the start of the annulus.
+  for (int i = 0; i < flux->n; i++) {
+    for (int j = 0; j < radialBinsLower->n; j++) {
+      if (radius->data.F32[i] < radialBinsLower->data.F32[j]) continue;
+      if (radius->data.F32[i] > radialBinsUpper->data.F32[j]) continue;
+      fluxValues->data.F32[j] += flux->data.F32[i];
+      fluxSquare->data.F32[j] += PS_SQR(flux->data.F32[i]);
+      fluxWeight->data.F32[j] += weight->data.F32[i];
+      pixelCount->data.F32[j] += 1.0;
+    }
+  }
+
+  for (int j = 0; j < radialBinsLower->n; j++) {
+    fluxValues->data.F32[j] /= pixelCount->data.F32[j];
+    fluxSquare->data.F32[j] /= pixelCount->data.F32[j];
+    fluxSquare->data.F32[j] -= PS_SQR(fluxValues->data.F32[j]);
+  }
+  
+  source->extpars->annuli = pmSourceAnnuliAlloc ();
+  source->extpars->annuli->flux    = fluxValues;
+  source->extpars->annuli->fluxErr = fluxWeight;
+  source->extpars->annuli->fluxVar = fluxSquare;
+
+  psFree (pixelCount);
+
+  return true;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotApResid.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotApResid.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotApResid.c	(revision 22157)
@@ -0,0 +1,365 @@
+# include "psphotInternal.h"
+
+# define SKIPSTAR(MSG) { psTrace ("psphot", 3, "invalid : %s", MSG); continue; }
+// measure the aperture residual statistics and 2D variations
+
+bool psphotApResid (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf)
+{
+    int Nfail = 0;
+    int Nskip = 0;
+    int Npsf;
+    bool status;
+    pmModel *model;
+    pmSource *source;
+
+    PS_ASSERT_PTR_NON_NULL(psf, false);
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+    PS_ASSERT_PTR_NON_NULL(recipe, false);
+
+    psTimerStart ("psphot");
+
+    bool measureAptrend = psMetadataLookupBool (&status, recipe, "MEASURE.APTREND");
+    if (!measureAptrend) {
+      return true;
+    }
+
+    // bit-masks to test for good/bad pixels
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT");
+    assert (maskVal);
+
+    // bit-mask to mark pixels not used in analysis
+    psMaskType markVal = psMetadataLookupU8(&status, recipe, "MARK.PSPHOT");
+    assert (markVal);
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // S/N limit to perform full non-linear fits
+    float MAX_AP_OFFSET = psMetadataLookupF32 (&status, recipe, "MAX_AP_OFFSET");
+
+    // this is the smallest radius allowed: need to at least extend growth curve down to this...
+    float PSF_FIT_PAD   = psMetadataLookupF32 (&status, recipe, "PSF_FIT_PADDING");
+    bool IGNORE_GROWTH = psMetadataLookupBool (&status, recipe, "IGNORE_GROWTH");
+    bool INTERPOLATE_AP = psMetadataLookupBool (&status, recipe, "INTERPOLATE_AP");
+
+    // XXX is this still needed?  the pmTrend2D stuff should be auto-adjusting...
+    int APTREND_NSTAR_MIN = psMetadataLookupS32(&status, recipe, "APTREND.NSTAR.MIN");
+    assert (status);
+
+    // maximum order for aperture correction
+    int APTREND_ORDER_MAX = psMetadataLookupS32(&status, recipe, "APTREND.ORDER.MAX");
+    assert (status);
+
+    if (APTREND_ORDER_MAX < 1) {
+        psError(PSPHOT_ERR_CONFIG, true, "APTREND.ORDER.MAX must be 1 or more");
+        return false;
+    }
+
+    pmSourcePhotometryMode photMode = 0;
+    if (!IGNORE_GROWTH) photMode |= PM_SOURCE_PHOT_GROWTH;
+    if (INTERPOLATE_AP) photMode |= PM_SOURCE_PHOT_INTERP;
+
+    // set limits on the aperture magnitudes
+    pmSourceMagnitudesInit (recipe);
+
+    // measure the aperture loss as a function of radius for PSF
+    float REF_RADIUS = psMetadataLookupF32 (&status, recipe, "PSF_REF_RADIUS");
+    psf->growth = pmGrowthCurveAlloc (PSF_FIT_PAD, 100.0, REF_RADIUS);
+
+    if (!pmGrowthCurveGenerate (readout, psf, IGNORE_GROWTH, maskVal, markVal)) {
+        psError(PSPHOT_ERR_APERTURE, false, "Fitting aperture corrections");
+        psFree(psf->growth); psf->growth = NULL;
+        return false;
+    }
+
+    psVector *mask    = psVectorAllocEmpty (300, PS_TYPE_U8);
+    psVector *mag     = psVectorAllocEmpty (300, PS_TYPE_F32);
+    psVector *xPos    = psVectorAllocEmpty (300, PS_TYPE_F32);
+    psVector *yPos    = psVectorAllocEmpty (300, PS_TYPE_F32);
+    psVector *apResid = psVectorAllocEmpty (300, PS_TYPE_F32);
+    psVector *dMag    = psVectorAllocEmpty (300, PS_TYPE_F32);
+    Npsf = 0;
+
+    // select all good PM_SOURCE_TYPE_STAR entries
+    for (int i = 0; i < sources->n; i++) {
+        source = sources->data[i];
+        model = source->modelPSF;
+
+        if (source->type != PM_SOURCE_TYPE_STAR) SKIPSTAR ("NOT STAR");
+        if (source->mode &  PM_SOURCE_MODE_SATSTAR) SKIPSTAR ("SATSTAR");
+        if (source->mode &  PM_SOURCE_MODE_BLEND) SKIPSTAR ("BLEND");
+        if (source->mode &  PM_SOURCE_MODE_FAIL) SKIPSTAR ("FAIL STAR");
+        if (source->mode &  PM_SOURCE_MODE_POOR) SKIPSTAR ("POOR STAR");
+
+        // get growth-corrected, apTrend-uncorrected magnitudes in scaled apertures
+        // will fail if below S/N threshold or model is missing
+        if (!pmSourceMagnitudes (source, psf, photMode, maskVal)) {
+            Nskip ++;
+	    psTrace ("psphot", 3, "skip : bad source mag");
+            continue;
+        }
+
+        if (!isfinite(source->apMag) || !isfinite(source->psfMag)) {
+            Nfail ++;
+	    psTrace ("psphot", 3, "fail : nan mags : %f %f", source->apMag, source->psfMag);
+            continue;
+        }
+
+	// aperture residual for this source
+	float dap = source->apMag - source->psfMag;
+
+        // sanity clipping : if the model is very discrepant, put your expectation in the recipe
+        if ((MAX_AP_OFFSET > 0) && (fabs(dap) > MAX_AP_OFFSET)) {
+            Nfail ++;
+	    psTrace ("psphot", 3, "fail : bad dap %f %f", dap, MAX_AP_OFFSET);
+            continue;
+        }
+
+	mag->data.F32[Npsf]     = source->psfMag;
+        apResid->data.F32[Npsf] = dap;
+        xPos->data.F32[Npsf]    = model->params->data.F32[PM_PAR_XPOS];
+        yPos->data.F32[Npsf]    = model->params->data.F32[PM_PAR_YPOS];
+
+        mask->data.U8[Npsf] = 0;
+
+        dMag->data.F32[Npsf] = model->dparams->data.F32[PM_PAR_I0] / model->params->data.F32[PM_PAR_I0];
+
+        psVectorExtend (mag,     100, 1);
+        psVectorExtend (mask,    100, 1);
+        psVectorExtend (xPos,    100, 1);
+        psVectorExtend (yPos,    100, 1);
+        psVectorExtend (dMag,    100, 1);
+        psVectorExtend (apResid, 100, 1);
+        Npsf ++;
+    }
+    
+    psLogMsg ("psphot.apresid", PS_LOG_DETAIL, "measure aperture residuals for %d objects (%d skipped, %d failed, %ld invalid)\n",
+              Npsf, Nskip, Nfail, sources->n - Npsf - Nskip - Nfail);
+
+    // XXX choose a better value here?
+    if (Npsf < APTREND_NSTAR_MIN) {
+        psError(PSPHOT_ERR_APERTURE, true, "Only %d valid aperture residual sources (need %d), giving up",
+                Npsf, APTREND_NSTAR_MIN);
+        return false;
+    }
+
+    // XXX deprecating the old code which allowed the ApResid to be fitted as a function of flux and r^2/flux
+    // XXX is this asymmetric clipping still needed?  this analysis should come after neighbors are subtracted...
+    // 3hi/1lo sigma clipping on the rflux vs metric fit
+    // systematic error information
+    float errorScale = 0.0;
+    float errorFloor = 0.0;
+
+    float errorFloorMin = FLT_MAX;
+    int entryMin = -1;
+
+    // Fit out the dap vs mag trend, iterate over spatial scale until error Floor increases.
+    // Stop if Npsf / (Nx * Ny) < 3
+    for (int i = 1; i <= APTREND_ORDER_MAX; i++) {
+
+	if (!psphotApResidTrend (readout, psf, Npsf, i, &errorScale, &errorFloor, mask, xPos, yPos, apResid, dMag)) {
+	    break;
+	}
+
+	// store the resulting errorFloor values and the scales, redo the best
+	if (errorFloor < errorFloorMin) {
+	    errorFloorMin = errorFloor;
+	    entryMin = i;
+	}
+    }
+    if (entryMin == -1) {
+	psAbort ("failed on ApResid Trend");
+    }
+
+    // XXX catch error condition 
+    psphotApResidTrend (readout, psf, Npsf, entryMin, &errorScale, &errorFloor, mask, xPos, yPos, apResid, dMag);
+
+    // construct the fitted values and the residuals
+    psVector *apResidFit = pmTrend2DEvalVector (psf->ApTrend, xPos, yPos);
+    psVector *apResidRes = (psVector *) psBinaryOp (NULL, (void *) apResid, "-", (void *) apResidFit);
+    psVector *dMagSys = (psVector *) psBinaryOp (NULL, (void *) dMag, "*", (void *) psScalarAlloc(errorScale, PS_TYPE_F32));
+
+    if (psTraceGetLevel("psphot") >= 5) {
+	FILE *dumpFile = fopen ("apresid.dat", "w");
+	for (int i = 0; i < xPos->n; i++) {
+	    fprintf (dumpFile, "%f %f  %f %f %f  %f %f  %x\n", 
+		     xPos->data.F32[i], yPos->data.F32[i], 
+		     mag->data.F32[i], dMag->data.F32[i], dMagSys->data.F32[i],
+		     apResid->data.F32[i], apResidRes->data.F32[i],
+		     mask->data.U8[i]);
+	}
+	fclose (dumpFile);
+    }
+
+    // apply ApTrend results
+    float xc = 0.5*readout->image->numCols + readout->image->col0 + 0.5;
+    float yc = 0.5*readout->image->numRows + readout->image->row0 + 0.5;
+
+    psf->ApResid  = pmTrend2DEval (psf->ApTrend, xc, yc); // ap-fit at chip center
+    psf->dApResid = errorFloor;
+    psf->nApResid = Npsf;
+
+    // save results for later output
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKYBIAS",  PS_DATA_F32 | PS_META_REPLACE, "aperture sky bias",   0.0);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKYSAT",   PS_DATA_F32 | PS_META_REPLACE, "aperture sky bias",   0.0);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "APMIFIT",  PS_DATA_F32 | PS_META_REPLACE, "aperture residual",   psf->ApResid);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "DAPMIFIT", PS_DATA_F32 | PS_META_REPLACE, "ap residual scatter", psf->dApResid);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "APLOSS",   PS_DATA_F32 | PS_META_REPLACE, "ap residual scatter", psf->growth->apLoss);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "NAPMIFIT", PS_DATA_S32 | PS_META_REPLACE, "ap residual scatter", psf->nApResid);
+
+    // psLogMsg ("psphot.apresid", PS_LOG_INFO, "measure full-frame aperture residuals for %d of %d objects: %f sec\n", Nkeep, Npsf, psTimerMark ("psphot"));
+    psLogMsg ("psphot.apresid", PS_LOG_DETAIL, "aperture residual: %f +/- %f\n", psf->ApResid, psf->dApResid);
+
+    psFree (mag);
+    psFree (mask);
+    psFree (xPos);
+    psFree (yPos);
+
+    psFree (apResid);
+    psFree (apResidFit);
+    psFree (apResidRes);
+
+    psFree (dMagSys);
+    psFree (dMag);
+
+    return true;
+}
+
+/*
+  (aprMag' - fitMag) = rflux*skyBias + ApTrend(x,y)
+  (aprMag - rflux*skyBias) - fitMag = ApTrend(x,y)
+  (aprMag - rflux*skyBias) = fitMag + ApTrend(x,y)
+*/
+
+bool psphotMagErrorScale (float *errorScale, float *errorFloor, psVector *dMag, psVector *dap, psVector *mask, int nGroup) {
+
+    psStats *statsS = psStatsAlloc (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
+    psStats *statsM = psStatsAlloc (PS_STAT_SAMPLE_MEAN);
+
+    // measure the trend in bins with 10 values each; if < 10 total, use them all
+    int nBin = PS_MAX (dMag->n / nGroup, 1);
+
+    // output vectors for ApResid trend 
+    psVector *dSo = psVectorAlloc (nBin, PS_TYPE_F32);
+    psVector *dMo = psVectorAlloc (nBin, PS_TYPE_F32);
+    psVector *dRo = psVectorAlloc (nBin, PS_TYPE_F32);
+
+    // use dMag to group the dMag and dap vectors
+    psVector *index = psVectorSortIndex (NULL, dMag);
+
+    // subset vectors for dMag and dap values within the given range
+    psVector *dMSubset = psVectorAllocEmpty (nGroup, PS_TYPE_F32);
+    psVector *dASubset = psVectorAllocEmpty (nGroup, PS_TYPE_F32);
+    psVector *mkSubset = psVectorAllocEmpty (nGroup, PS_TYPE_U8);
+
+    int n = 0;
+    for (int i = 0; i < dMo->n; i++) {
+	int j;
+	for (j = 0; (j < nGroup) && (n < dMag->n); j++, n++) {
+	    int N = index->data.U32[n];
+	    dMSubset->data.F32[j] = dMag->data.F32[N];
+	    dASubset->data.F32[j] = dap->data.F32[N];
+	    mkSubset->data.U8[j]  = mask->data.U8[N];
+	}
+	dMSubset->n = j;
+	dASubset->n = j;
+	mkSubset->n = j;
+
+	psStatsInit (statsS);
+	psStatsInit (statsM);
+
+	if (j > 2) {
+	    bool status = true;
+	    status &= psVectorStats (statsS, dASubset, NULL, mkSubset, 0xff);
+	    status &= psVectorStats (statsM, dMSubset, NULL, mkSubset, 0xff);
+	    if (!status) { psErrorClear (); }
+	    dSo->data.F32[i] = statsS->robustStdev;
+	    dMo->data.F32[i] = statsM->sampleMean;
+	    dRo->data.F32[i] = statsS->robustStdev / statsM->sampleMean;
+	} else {
+	    dSo->data.F32[i] = NAN;
+	    dMo->data.F32[i] = NAN;
+	    dRo->data.F32[i] = NAN;
+	}
+    }
+    psFree (dMSubset);
+    psFree (dASubset);
+    psFree (mkSubset);
+    
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN);
+    if (!psVectorStats (stats, dRo, NULL, NULL, 0)) {
+	// XXX better testing of raised error
+	psErrorClear();
+    }
+
+    *errorScale = stats->sampleMedian;
+    for (int i = 0; i < dSo->n; i++) {
+	*errorFloor = dSo->data.F32[i];
+	if (isfinite(*errorFloor)) break;
+    }
+
+    psFree (stats);
+    psFree (index);
+
+    psFree (dRo);
+    psFree (dMo);
+    psFree (dSo);
+    
+    psFree (statsS);
+    psFree (statsM);
+
+    return true;
+}
+
+bool psphotApResidTrend (pmReadout *readout, pmPSF *psf, int Npsf, int scale, float *errorScale, float *errorFloor, psVector *mask, psVector *xPos, psVector *yPos, psVector *apResid, psVector *dMag) {
+
+    int Nx, Ny;
+	
+    if (readout->image->numCols > readout->image->numRows) {
+	Nx = scale;
+	Ny = PS_MAX (1, Nx * (readout->image->numRows / (float) readout->image->numCols));
+    } else {
+	Ny = scale;
+	Nx = PS_MAX (1, Ny * (readout->image->numCols / (float) readout->image->numRows));
+    }
+
+    if (Npsf < 3*Nx*Ny) {
+	return false;
+    }
+
+    // the mask marks the values not used to calculate the ApTrend
+    psVectorInit (mask, 0);
+
+    // XXX stats structure for use by ApTrend : make parameters user setable
+    psStats *stats = psStatsAlloc (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
+    stats->min = 2.0;
+    stats->max = 3.0;
+
+    // measure Trend2D for the current spatial scale
+    psFree (psf->ApTrend);
+    psf->ApTrend = pmTrend2DAlloc (PM_TREND_MAP, readout->image, Nx, Ny, stats);
+
+    // XXX test for errors here
+    pmTrend2DFit (psf->ApTrend, mask, 0xff, xPos, yPos, apResid, dMag);
+    
+    // construct the fitted values and the residuals
+    psVector *apResidFit = pmTrend2DEvalVector (psf->ApTrend, xPos, yPos);
+    psVector *apResidRes = (psVector *) psBinaryOp (NULL, (void *) apResid, "-", (void *) apResidFit);
+
+    // measure systematic errorFloor & systematic / photon scale factor
+    // XXX this is a bit arbitrary, but it forces ~3 stars from the bright bin per spatial bin
+    int nGroup = PS_MAX (3*Nx*Ny, 10);
+    psphotMagErrorScale (errorScale, errorFloor, dMag, apResidRes, mask, nGroup);
+
+    psLogMsg ("psphot.apresid", PS_LOG_INFO, "result of %d x %d grid (%d stars per bin)\n", Nx, Ny, nGroup);
+    psLogMsg ("psphot.apresid", PS_LOG_INFO, "systematic error / photon error: %f\n", *errorScale);
+    psLogMsg ("psphot.apresid", PS_LOG_INFO, "systematic scatter floor: %f\n", *errorFloor);
+
+    psFree (stats);
+    psFree (apResidFit);
+    psFree (apResidRes);
+
+    return true;
+}
+    
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotArguments.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotArguments.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotArguments.c	(revision 22157)
@@ -0,0 +1,122 @@
+# include "psphotStandAlone.h"
+
+pmConfig *psphotArguments(int argc, char **argv) {
+
+    int N;
+    bool status;
+
+    if (argc == 1) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "Too few arguments: %d", argc);
+        return NULL;
+    }
+
+    if ((N = psArgumentGet (argc, argv, "-version"))) {
+        psString version;
+        version = psphotVersionLong();    fprintf (stdout, "%s\n", version); psFree (version);
+        version = psModulesVersionLong(); fprintf (stdout, "%s\n", version); psFree (version);
+        version = psLibVersionLong();     fprintf (stdout, "%s\n", version); psFree (version);
+        exit (0);
+    }
+
+    // load config data from default locations
+    pmConfig *config = pmConfigRead(&argc, argv, PSPHOT_RECIPE);
+    if (config == NULL) {
+        psError(PSPHOT_ERR_CONFIG, false, "Can't read site configuration");
+        return NULL;
+    }
+
+    // save the following additional recipe values based on command-line options
+    // these options override the PSPHOT recipe values loaded from recipe files
+    psMetadata *options = pmConfigRecipeOptions (config, PSPHOT_RECIPE);
+
+    // run the test model (requires X,Y coordinate)
+    if ((N = psArgumentGet (argc, argv, "-modeltest"))) {
+        psMetadataAddBool (options, PS_LIST_TAIL, "TEST_FIT",   0, "", true);
+        psMetadataAddF32  (options, PS_LIST_TAIL, "TEST_FIT_X", 0, "", atof(argv[N+1]));
+        psMetadataAddF32  (options, PS_LIST_TAIL, "TEST_FIT_Y", 0, "", atof(argv[N+2]));
+
+        psArgumentRemove (N, &argc, argv);
+        psArgumentRemove (N, &argc, argv);
+        psArgumentRemove (N, &argc, argv);
+
+        // specify the modeltest model
+        if ((N = psArgumentGet (argc, argv, "-model"))) {
+            psArgumentRemove (N, &argc, argv);
+            psMetadataAddStr (options, PS_LIST_TAIL, "TEST_FIT_MODEL", 0, "", argv[N]);
+            psArgumentRemove (N, &argc, argv);
+        }
+
+        // specify the test fit mode
+        if ((N = psArgumentGet (argc, argv, "-fitmode"))) {
+            psArgumentRemove (N, &argc, argv);
+            psMetadataAddStr (options, PS_LIST_TAIL, "TEST_FIT_MODE", 0, "", argv[N]);
+            psArgumentRemove (N, &argc, argv);
+        }
+        if ((N = psArgumentGet (argc, argv, "-fitset"))) {
+            psArgumentRemove (N, &argc, argv);
+            psMetadataAddStr (options, PS_LIST_TAIL, "TEST_FIT_SET", 0, "", argv[N]);
+            psArgumentRemove (N, &argc, argv);
+        }
+    }
+
+    // photcode : used in output to supplement header data (argument or recipe?)
+    if ((N = psArgumentGet (argc, argv, "-photcode"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "PHOTCODE", PS_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // break : used from recipe throughout psphotReadout
+    if ((N = psArgumentGet (argc, argv, "-break"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "BREAK_POINT", PS_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // fitmode : used from recipe throughout psphotReadout
+    if ((N = psArgumentGet (argc, argv, "-fitmode"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "FITMODE", PS_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // analysis region : overrides recipe value, used in psphotReadout/psphotEnsemblePSF
+    if ((N = psArgumentGet (argc, argv, "-region"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "ANALYSIS_REGION", 0, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // chip selection is used to limit chips to be processed
+    if ((N = psArgumentGet (argc, argv, "-chip"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (config->arguments, PS_LIST_TAIL, "CHIP_SELECTIONS", PS_DATA_STRING, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // if these command-line options are supplied, load the file name lists into config->arguments
+    // override any configuration-specified source for these files
+    //
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "MASK",   "-mask",   "-masklist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "WEIGHT", "-weight", "-weightlist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "PSPHOT.PSF", "-psf", "-psflist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "SRC",    "-src",    "-srclist");
+
+    // the input file is a required argument; if not found, we will exit
+    status = pmConfigFileSetsMD (config->arguments, &argc, argv, "INPUT", "-file", "-list");
+    if (!status) {
+        psError(PSPHOT_ERR_ARGUMENTS, false, "pmConfigFileSetsMD failed to parse arguments");
+        return NULL;
+    }
+
+    if (argc != 2) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "Expected to see one more argument; saw %d", argc - 1);
+        return NULL;
+    }
+
+    // output position is fixed
+    psMetadataAddStr (config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "", argv[1]);
+
+    psTrace("psphot", 1, "Done with psphotArguments...\n");
+    return (config);
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotBasicDeblend.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotBasicDeblend.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotBasicDeblend.c	(revision 22157)
@@ -0,0 +1,129 @@
+# include "psphotInternal.h"
+
+bool psphotBasicDeblend (psArray *sources, psMetadata *recipe) {
+
+    int N;
+    bool status;
+    float threshold;
+    pmSource *source, *testSource;
+
+    int Nblend = 0;
+
+    psTimerStart ("psphot");
+
+    float FRACTION = psMetadataLookupF32 (&status, recipe, "DEBLEND_PEAK_FRACTION");
+    if (!status) FRACTION = 0.25;
+
+    float NSIGMA   = psMetadataLookupF32 (&status, recipe, "DEBLEND_SKY_NSIGMA");
+    if (!status) NSIGMA = 5.0;
+
+    // we need sources spatially-sorted to find overlaps
+    sources = psArraySort (sources, pmSourceSortByY);
+
+    // source analysis is done in peak order (brightest first)
+    // we use an index for this so the spatial sorting is kept
+    psVector *SN = psVectorAlloc (sources->n, PS_DATA_F32);
+    for (int i = 0; i < SN->n; i++) {
+        source = sources->data[i];
+        SN->data.F32[i] = source->peak->SN;
+    }
+    psVector *index = psVectorSortIndex (NULL, SN);
+    // this results in an index of increasing SN
+
+    // examine sources in decreasing SN order
+    for (int i = sources->n - 1; i >= 0; i--) {
+        N = index->data.U32[i];
+        source = sources->data[N];
+
+        if (source->mode & PM_SOURCE_MODE_BLEND) continue;
+
+        // temporary array for overlapping objects we find
+        psArray *overlap = psArrayAllocEmpty (100);
+
+        // search backwards for overlapping sources
+        for (int j = N - 1; j >= 0; j--) {
+            testSource = sources->data[j];
+            if (testSource->peak->x <  source->pixels->col0) continue;
+            if (testSource->peak->x >= source->pixels->col0 + source->pixels->numCols) continue;
+            if (testSource->peak->y <  source->pixels->row0) break;
+            if (testSource->peak->y >= source->pixels->row0 + source->pixels->numRows) {
+                fprintf (stderr, "warning: invalid condition\n");
+                continue;
+            }
+            psArrayAdd (overlap, 100, testSource);
+        }
+
+        // search forwards for overlapping sources
+        for (int j = N + 1; j < sources->n; j++) {
+            testSource = sources->data[j];
+            if (testSource->peak->x <  source->pixels->col0) continue;
+            if (testSource->peak->x >= source->pixels->col0 + source->pixels->numCols) continue;
+            if (testSource->peak->y <  source->pixels->row0) {
+                fprintf (stderr, "warning: invalid condition\n");
+                continue;
+            }
+            if (testSource->peak->y >= source->pixels->row0 + source->pixels->numRows) break;
+            psArrayAdd (overlap, 100, testSource);
+        }
+
+        if (overlap->n == 0) {
+            psFree (overlap);
+            continue;
+        }
+
+        // this source has overlapping neighbors, check for actual blends
+        // generate source contour (1/4 peak counts)
+        // set the threshold based on user inputs
+
+        // threshold is fraction of the source peak flux
+        // image is background subtracted; source->moments->Sky should always be 0.0
+        threshold = FRACTION * source->peak->SN;
+        // threshold is no less than NSIGMA
+        threshold = PS_MAX (threshold, NSIGMA);
+
+        // generate a basic contour (set of x,y coordinates at-or-below flux level)
+        psArray *contour = pmSourceContour (source->pixels, source->peak->x, source->peak->y, threshold);
+        if (contour == NULL) {
+            psFree (overlap);
+            continue;
+        }
+
+        // the source contour consists of two vectors, xv and yv.  the contour is
+        // a series of coordinate pairs, (xv[i],yv[i]) & (xv[i+1],yv[i+1]).  both
+        // coordinate pairs have the same yv[] value, with xv[i] corresponding to
+        // the left boundary and xv[i+1] corresponding to the right boundary
+
+	// a blend can only be associated with one primary source
+
+        psVector *xv = contour->data[0];
+        psVector *yv = contour->data[1];
+        for (int k = 0; k < overlap->n; k++) {
+            testSource = overlap->data[k];
+	    if (testSource->mode & PM_SOURCE_MODE_BLEND) continue;
+            if (testSource->peak->value > source->peak->value) continue;
+            for (int j = 0; j < xv->n; j+=2) {
+                if (fabs(yv->data.F32[j] - testSource->peak->y) > 0.5) continue;
+                if (xv->data.F32[j+0] > testSource->peak->x) break;
+                if (xv->data.F32[j+1] < testSource->peak->x) break;
+
+                testSource->mode |= PM_SOURCE_MODE_BLEND;
+
+                // add this to the list of source->blends
+                if (source->blends == NULL) {
+                    source->blends = psArrayAllocEmpty (16);
+                }
+                psArrayAdd (source->blends, 16, testSource);
+
+                Nblend ++;
+                j = xv->n;
+            }
+        }
+        psFree (overlap);
+        psFree (contour);
+    }
+    psLogMsg ("psphot.deblend", PS_LOG_INFO, "identified %d blended objects: %f sec\n", Nblend, psTimerMark ("psphot"));
+
+    psFree (SN);
+    psFree (index);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotBlendFit.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotBlendFit.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotBlendFit.c	(revision 22157)
@@ -0,0 +1,120 @@
+# include "psphotInternal.h"
+
+// XXX I don't like this name
+bool psphotBlendFit (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf) {
+
+    int Nfit = 0;
+    int Npsf = 0;
+    int Next = 0;
+    int Nfail = 0;
+    bool status;
+
+    psTimerStart ("psphot");
+
+    // bit-masks to test for good/bad pixels
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT");
+    assert (maskVal);
+
+    // bit-mask to mark pixels not used in analysis
+    psMaskType markVal = psMetadataLookupU8(&status, recipe, "MARK.PSPHOT");
+    assert (markVal);
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // source analysis is done in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // S/N limit to perform full non-linear fits
+    float FIT_SN_LIM = psMetadataLookupF32 (&status, recipe, "FULL_FIT_SN_LIM");
+
+    psphotInitLimitsPSF (recipe, readout);
+    psphotInitLimitsEXT (recipe);
+    psphotInitRadiusPSF (recipe, psf->type);
+
+    psphotFitInit ();
+
+    // option to limit analysis to a specific region
+    char *region = psMetadataLookupStr (&status, recipe, "ANALYSIS_REGION");
+    psRegion AnalysisRegion = psRegionForImage (readout->image, psRegionFromString (region));
+    if (psRegionIsNaN (AnalysisRegion)) psAbort("analysis region mis-defined");
+
+    for (int i = 0; i < sources->n; i++) {
+        // if (i%100 == 0) psphotFitSummary ();
+
+        pmSource *source = sources->data[i];
+
+        // skip non-astronomical objects (very likely defects)
+        if (source->mode &  PM_SOURCE_MODE_BLEND) continue;
+        if (source->mode &  PM_SOURCE_MODE_CR_LIMIT) continue;
+        if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
+        if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+
+        // skip DBL second sources (ie, added by psphotFitBlob)
+        if (source->mode &  PM_SOURCE_MODE_PAIR) continue;
+
+        // limit selection to some SN limit
+        // XXX this should use peak?
+        if (source->moments == NULL) continue;
+        if (source->moments->SN < FIT_SN_LIM) continue;
+
+        // XXX this should use peak?
+        if (source->moments->x < AnalysisRegion.x0) continue;
+        if (source->moments->y < AnalysisRegion.y0) continue;
+        if (source->moments->x > AnalysisRegion.x1) continue;
+        if (source->moments->y > AnalysisRegion.y1) continue;
+
+        // if model is NULL, we don't have a starting guess
+        if (source->modelPSF == NULL) continue;
+
+        // skip sources which are insignificant flux?
+        if (source->modelPSF->params->data.F32[1] < 0.1) {
+            psTrace ("psphot", 5, "skipping near-zero source: %f, %f : %f\n",
+                     source->modelPSF->params->data.F32[1],
+                     source->modelPSF->params->data.F32[2],
+                     source->modelPSF->params->data.F32[3]);
+            continue;
+        }
+
+        // replace object in image
+        if (source->mode & PM_SOURCE_MODE_SUBTRACTED) {
+            pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+        }
+        Nfit ++;
+
+        // try fitting PSFs, then try extended sources
+        // these functions subtract the resulting fitted source (XXX and update the modelFlux?)
+	// XXX re-consider conditions under which the source has EXT fit:
+	// I should try EXT if the source size measurement says it is large
+	if (source->mode & PM_SOURCE_MODE_EXT_LIMIT) {
+	    if (psphotFitBlob (readout, source, sources, psf, maskVal, markVal)) {
+		source->type = PM_SOURCE_TYPE_EXTENDED;
+		psTrace ("psphot", 5, "source at %7.1f, %7.1f is ext", source->moments->x, source->moments->y);
+		Next ++;
+		continue;
+	    }
+	} else {
+	    if (psphotFitBlend (readout, source, psf, maskVal, markVal)) {
+		source->type = PM_SOURCE_TYPE_STAR;
+		psTrace ("psphot", 5, "source at %7.1f, %7.1f is psf", source->moments->x, source->moments->y);
+		Npsf ++;
+		continue;
+	    }
+	}
+
+        psTrace ("psphot", 5, "source at %7.1f, %7.1f failed", source->moments->x, source->moments->y);
+        Nfail ++;
+
+        // re-subtract the object, leave local sky
+        pmSourceCacheModel (source, maskVal);
+        pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+        source->mode |= PM_SOURCE_MODE_SUBTRACTED;
+    }
+
+    if (psTraceGetLevel("psphot") >= 6) {
+      psphotSaveImage (NULL, readout->image,  "image.v2.fits");
+    }
+
+    psLogMsg ("psphot.psphotBlendFit", PS_LOG_INFO, "fit models: %f sec for %d objects (%d psf, %d ext, %d failed, %ld skipped)\n", psTimerMark ("psphot"), Nfit, Npsf, Next, Nfail, sources->n - Nfit);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotCheckStarDistribution.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotCheckStarDistribution.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotCheckStarDistribution.c	(revision 22157)
@@ -0,0 +1,104 @@
+# include "psphotInternal.h"
+
+// examine the x,y distribution of the psfstars and extend the selection if needed
+// desired region and coverage are specified by pmPSFOptions
+// even if stars->n is at the limit, we will add more sources to fill out the area
+
+bool psphotCheckStarDistribution (psArray *stars, psArray *sources, pmPSFOptions *options) {
+
+    // count the number of PSFSTAR sources in each cell.  for polynomial representations, we
+    // use an image (Norder + 1) x (Norder + 1)
+    int Nx = options->psfTrendNx;
+    int Ny = options->psfTrendNy;
+    if (options->psfTrendMode != PM_TREND_MAP) {
+	Nx ++;
+	Ny ++;
+    }
+
+    // set up and image and an image binning structure to cover the field
+    psImage *nCell = psImageAlloc (Nx, Ny, PS_TYPE_S32);
+    psImageInit (nCell, 0);
+
+    psImageBinning *binning = psImageBinningAlloc();
+    binning->nXruff = Nx;
+    binning->nYruff = Ny;
+    binning->nXfine = options->psfFieldNx;
+    binning->nYfine = options->psfFieldNy;
+
+    psImageBinningSetScale (binning, PS_IMAGE_BINNING_CENTER);
+    psImageBinningSetSkipByOffset (binning, options->psfFieldXo, options->psfFieldYo);
+
+    // where are the PSF stars located?
+    for (int i = 0; i < stars->n; i++) {
+	
+	pmSource *source = stars->data[i];
+        if (source->peak == NULL) continue;
+        if (!(source->mode & PM_SOURCE_MODE_PSFSTAR)) continue;
+
+	int binX = psImageBinningGetRuffX (binning, source->peak->xf);
+	int binY = psImageBinningGetRuffY (binning, source->peak->yf);
+
+	assert (binX >= 0);
+	assert (binY >= 0);
+	assert (binX <  nCell->numCols);
+	assert (binY <  nCell->numRows);
+
+	nCell->data.S32[binY][binX] ++;
+    }
+	
+    // do any cells have too few PSFSTAR sources?
+    // we use 3 as a minimum (slightly arbitrary...)
+    for (int iy = 0; iy < nCell->numRows; iy++) { 
+	for (int ix = 0; ix < nCell->numCols; ix++) { 
+	    if (nCell->data.S32[iy][ix] < 3) {
+		int nNew = psphotSupplementStars (stars, sources, binning, ix, iy);
+		if (nNew) {
+		    psLogMsg ("pmObjects", 3, "added %d fainter PSF candidates to %d,%d\n", nNew, ix, iy);
+		} else {
+		    psLogMsg ("pmObjects", 3, "tried to add to %d,%d, but no more sources are available\n", ix, iy);		    
+		}
+	    }
+	}
+    }
+
+    psFree (nCell);
+    psFree (binning);
+    return true;
+}
+
+// select more possible PSF stars in the specified cell
+// sources should be sorted by SN before calling this function
+int psphotSupplementStars (psArray *stars, psArray *sources, psImageBinning *binning, int ix, int iy) {
+
+    int nNew = 0;
+
+    float Xs = psImageBinningGetFineX (binning, ix);
+    float Xe = psImageBinningGetFineX (binning, ix + 1);
+
+    float Ys = psImageBinningGetFineY (binning, iy);
+    float Ye = psImageBinningGetFineY (binning, iy + 1);
+
+    for (int i = 0; i < sources->n; i++) {
+
+	// add sources which are marked as stars, in S/N order until...
+	pmSource *source = sources->data[i];
+        if (source->peak == NULL) continue;
+        if (source->moments == NULL) continue;
+        if (source->mode & PM_SOURCE_MODE_PSFSTAR) continue;
+        if (source->type != PM_SOURCE_TYPE_STAR) continue;
+
+	float x = source->peak->xf;
+	float y = source->peak->yf;
+
+	if (x < Xs) continue;
+	if (y < Ys) continue;
+	if (x > Xe) continue;
+	if (y > Ye) continue;
+
+	source->mode |= PM_SOURCE_MODE_PSFSTAR;
+	psArrayAdd (stars, 200, source);
+
+	nNew ++;
+    }
+    return nNew;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotChoosePSF.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotChoosePSF.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotChoosePSF.c	(revision 22157)
@@ -0,0 +1,490 @@
+# include "psphotInternal.h"
+
+// try PSF models and select best option
+pmPSF *psphotChoosePSF (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+
+    bool status;
+
+    psTimerStart ("psphot");
+
+    // bit-masks to test for good/bad pixels
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT");
+    assert (maskVal);
+
+    // bit-mask to mark pixels not used in analysis
+    psMaskType markVal = psMetadataLookupU8(&status, recipe, "MARK.PSPHOT");
+    assert (markVal);
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // examine PSF sources in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // structure to store user options defining the psf
+    pmPSFOptions *options = pmPSFOptionsAlloc ();
+
+    // load user options from the recipe. no need to check existence -- they are
+    // array to store candidate PSF stars
+    int NSTARS = psMetadataLookupS32 (&status, recipe, "PSF_MAX_NSTARS");
+    assert (status);
+
+    float PSF_MIN_DS = psMetadataLookupF32 (&status, recipe, "PSF_MIN_DS");
+    assert (status);
+
+    // supply the measured sky variance for optional constant errors (non-poissonian)
+    float SKY_SIG = psMetadataLookupF32 (&status, recipe, "SKY_SIG");
+    assert (status);
+
+    // use poissonian errors or local-sky errors
+    options->poissonErrorsPhotLMM = psMetadataLookupBool (&status, recipe, "POISSON.ERRORS.PHOT.LMM");
+    assert (status);
+
+    // use poissonian errors or local-sky errors
+    options->poissonErrorsPhotLin = psMetadataLookupBool (&status, recipe, "POISSON.ERRORS.PHOT.LIN");
+    assert (status);
+
+    // use poissonian errors or local-sky errors
+    options->poissonErrorsParams = psMetadataLookupBool (&status, recipe, "POISSON.ERRORS.PARAMS");
+    assert (status);
+
+    // how to model the PSF variations across the field
+    options->psfTrendMode = pmTrend2DModeFromString (psMetadataLookupStr (&status, recipe, "PSF.TREND.MODE"));
+    assert (status);
+
+    options->psfTrendNx = psMetadataLookupS32 (&status, recipe, "PSF.TREND.NX");
+    assert (status);
+
+    options->psfTrendNy = psMetadataLookupS32 (&status, recipe, "PSF.TREND.NY");
+    assert (status);
+
+    // get the fixed PSF fit radius
+    // XXX check that PSF_FIT_RADIUS < SKY_OUTER_RADIUS
+    options->radius = psMetadataLookupF32 (&status, recipe, "PSF_FIT_RADIUS");
+    assert (status);
+
+    options->stats = psStatsAlloc (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
+
+    // dimensions of the field for which the PSF is defined
+    options->psfFieldNx = readout->image->numCols;
+    options->psfFieldNy = readout->image->numRows;
+    options->psfFieldXo = readout->image->col0;
+    options->psfFieldYo = readout->image->row0;
+
+    pmSourceFitModelInit (15, 0.01, PS_SQR(SKY_SIG), options->poissonErrorsPhotLMM);
+
+    psArray *stars = psArrayAllocEmpty (sources->n);
+
+    // select the candidate PSF stars (pointers to original sources)
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+            // keep NSTARS PSF stars, unmark the rest
+            if (stars->n < NSTARS) {
+                psArrayAdd (stars, 200, source);
+            } else {
+                source->mode &= ~PM_SOURCE_MODE_PSFSTAR;
+            }
+        }
+    }
+    // check that the identified psf stars sufficiently cover the region; if not, extend the
+    // limits somewhat
+    psphotCheckStarDistribution (stars, sources, options);
+
+    psLogMsg ("psphot.pspsf", PS_LOG_DETAIL, "selected candidate %ld PSF objects\n", stars->n);
+
+    // get the list pointers for the PSF_MODEL entries
+    psArray *modelNames = NULL;
+    psMetadataItem *item = psMetadataLookup (recipe, "PSF_MODEL");
+    if (item == NULL) psAbort("missing PSF_MODEL selection");
+
+    if (item->type == PS_DATA_STRING) {
+        modelNames = psArrayAlloc(1);
+        modelNames->data[0] = psStringCopy (item->data.V);
+    } else {
+        if (item->type != PS_DATA_METADATA_MULTI) psAbort("missing PSF_MODEL selection");
+        modelNames = psListToArray (item->data.list);
+    }
+
+    // generate a psf model using the first selection
+    if (stars->n == 0) {
+        psError(PSPHOT_ERR_PSF, false, "Failed to fit any models when choosing PSF");
+        bool mdok;                      // Status of MD lookup
+        if (!psMetadataLookupBool(&mdok, recipe, PSPHOT_RECIPE_PSF_FAKE_ALLOW)) {
+            return false;
+        }
+        psErrorStackPrint(stderr, "Using guess PSF model");
+        psErrorClear();
+
+        psFree(options);
+
+        // unset the PSFSTAR flags (none are used):
+        for (int i = 0; i < sources->n; i++) {
+            pmSource *source = sources->data[i];
+            source->mode &= ~PM_SOURCE_MODE_PSFSTAR;
+        }
+
+        // XXX set sxx, etc from FWHM in recipe
+        pmPSF *psf = pmPSFBuildSimple (modelNames->data[0], 1.0, 1.0, 0.0, 1.0);
+        psf->fieldNx = readout->image->numCols;
+        psf->fieldNy = readout->image->numRows;
+        psFree (modelNames);
+
+        bool status = true;
+        status &= psphotMakeFluxScale (readout->image, recipe, psf);
+        status &= psphotPSFstats (readout, recipe, psf);
+        if (!status) {
+            psError(PSPHOT_ERR_PSF, false, "Failed to fit any models when choosing PSF");
+            psFree (psf);
+            return NULL;
+        }
+
+        // XXX set DSX_MEAN, etc?
+        return psf;
+    }
+
+    // set up an array to store the results
+    psArray *models = psArrayAlloc (modelNames->n);
+
+    // try each model option listed in config
+    for (int i = 0; i < modelNames->n; i++) {
+        char *modelName = modelNames->data[i];
+        pmPSFtry *try = pmPSFtryModel (stars, modelName, options, maskVal, markVal); // Attempt at fit
+        if (!try) {
+            // No big deal --- we'll try another model
+            psErrorClear();
+            continue;
+        }
+        models->data[i] = try;
+    }
+
+    // select the best of the models
+    // here we are using the clippedStdev on the metric as the indicator
+    int bestN = -1;
+    float bestM = 0.0;
+    for (int i = 0; i < models->n; i++) {
+        pmPSFtry *try = models->data[i];
+        if (try == NULL) {
+            psTrace ("psphot", 3, "PSF model %d is NULL", i);
+            continue;
+        }
+        float M = try->psf->dApResid;
+        if (bestN < 0 || M < bestM) {
+            bestM = M;
+            bestN = i;
+        }
+    }
+
+    // use the best model:
+    if (bestN < 0) {
+        psFree (models);
+        psFree (stars);
+
+        psErrorStackPrint (stderr, "Failed to fit any models when choosing PSF");
+        psErrorClear();
+
+        bool mdok;                      // Status of MD lookup
+        if (!psMetadataLookupBool(&mdok, recipe, PSPHOT_RECIPE_PSF_FAKE_ALLOW)) {
+	    psFree (modelNames);
+	    psFree(options);
+            return NULL;
+        }
+
+        // generate a psf model using the first selection
+	psLogMsg ("psphot.pspsf", PS_LOG_INFO, "Using guess PSF model");
+
+        // unset the PSFSTAR flags (none are used):
+        for (int i = 0; i < sources->n; i++) {
+            pmSource *source = sources->data[i];
+            source->mode &= ~PM_SOURCE_MODE_PSFSTAR;
+        }
+
+        // XXX set sxx, etc from FWHM in recipe
+        pmPSF *psf = pmPSFBuildSimple (modelNames->data[0], 1.0, 1.0, 0.0, 1.0);
+        psf->fieldNx = readout->image->numCols;
+        psf->fieldNy = readout->image->numRows;
+        psFree (modelNames);
+
+        bool status = true;
+        status &= psphotMakeFluxScale (readout->image, recipe, psf);
+        status &= psphotPSFstats (readout, recipe, psf);
+        if (!status) {
+            psError(PSPHOT_ERR_PSF, false, "Failed to fit any models when choosing PSF");
+            psFree (psf);
+            return NULL;
+        }
+
+        // XXX set DSX_MEAN, etc?
+        return psf;
+    }
+
+    psFree (modelNames);
+    psFree (stars);
+
+    pmPSFtry *try = models->data[bestN];
+
+    // measure and save the median value of dparams[PM_PAR_SXX],dparams[PM_PAR_SYY]
+    // these are used by psphotEvalPSF to identify extended sources
+    // XXX is this still needed?
+    psVector *Sx = psVectorAllocEmpty (try->sources->n, PS_TYPE_F32);
+    psVector *Sy = psVectorAllocEmpty (try->sources->n, PS_TYPE_F32);
+    psVector *dSx = psVectorAllocEmpty (try->sources->n, PS_TYPE_F32);
+    psVector *dSy = psVectorAllocEmpty (try->sources->n, PS_TYPE_F32);
+    psVector *dSN = psVectorAllocEmpty (try->sources->n, PS_TYPE_F32);
+    for (int i = 0; i < try->sources->n; i++) {
+        // masked for: bad model fit, outlier in parameters
+        if (try->mask->data.U8[i] & PSFTRY_MASK_ALL)
+            continue;
+
+        pmSource *source = try->sources->data[i];
+        Sx->data.F32[Sx->n] = source->modelPSF->params->data.F32[PM_PAR_SXX];
+        Sy->data.F32[Sy->n] = source->modelPSF->params->data.F32[PM_PAR_SYY];
+        dSN->data.F32[dSN->n] = source->modelPSF->dparams->data.F32[PM_PAR_I0] / source->modelPSF->params->data.F32[PM_PAR_I0];
+        dSx->data.F32[dSx->n] = source->modelPSF->dparams->data.F32[PM_PAR_SXX];
+        dSy->data.F32[dSy->n] = source->modelPSF->dparams->data.F32[PM_PAR_SYY];
+        Sx->n ++;
+        Sy->n ++;
+        dSN->n ++;
+        dSx->n ++;
+        dSy->n ++;
+    }
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+
+    psVectorStats (stats, dSx, NULL, NULL, 0);
+    float dSxo = stats->sampleMean;
+    psLogMsg ("psphot.choosePSF", PS_LOG_INFO, "dSxo: mean: %f, median: %f, stdev: %f, npts: %ld", stats->sampleMean, stats->sampleMedian, stats->sampleStdev, dSx->n);
+
+    psVectorStats (stats, dSy, NULL, NULL, 0);
+    float dSyo = stats->sampleMean;
+    psLogMsg ("psphot.choosePSF", PS_LOG_INFO, "dSyo: mean: %f, median: %f, stdev: %f, npts: %ld", stats->sampleMean, stats->sampleMedian, stats->sampleStdev, dSy->n);
+
+    for (int i = 0; i < dSN->n; i++) {
+        dSx->data.F32[i] = (dSx->data.F32[i] - dSxo) / PS_MAX (PSF_MIN_DS, Sx->data.F32[i]*dSN->data.F32[i]);
+        dSy->data.F32[i] = (dSy->data.F32[i] - dSyo) / PS_MAX (PSF_MIN_DS, Sy->data.F32[i]*dSN->data.F32[i]);
+    }
+
+    psVectorStats (stats, dSx, NULL, NULL, 0);
+    float nSx = stats->sampleStdev;
+    psLogMsg ("psphot.choosePSF", PS_LOG_INFO, "ndSx: mean: %f, median: %f, stdev: %f, npts: %ld", stats->sampleMean, stats->sampleMedian, stats->sampleStdev, dSx->n);
+    psVectorStats (stats, dSy, NULL, NULL, 0);
+    float nSy = stats->sampleStdev;
+    psLogMsg ("psphot.choosePSF", PS_LOG_INFO, "ndSy: mean: %f, median: %f, stdev: %f, npts: %ld", stats->sampleMean, stats->sampleMedian, stats->sampleStdev, dSy->n);
+
+    psFree (Sx);
+    psFree (Sy);
+    psFree (dSx);
+    psFree (dSy);
+    psFree (dSN);
+    psFree (stats);
+
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "DSX_MEAN", PS_META_REPLACE, "mean offset of dSXX", dSxo);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "DSY_MEAN", PS_META_REPLACE, "mean offset of dSYY", dSyo);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "DSX_STDV", PS_META_REPLACE, "stdev of mean-corrected dSXX", nSx);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "DSY_STDV", PS_META_REPLACE, "stdev of mean-corrected dSYY", nSy);
+
+    // unset the PSFSTAR flag for stars not used for PSF model
+    for (int i = 0; i < try->sources->n; i++) {
+        pmSource *source = try->sources->data[i];
+        if (try->mask->data.U8[i]) {
+            source->mode &= ~PM_SOURCE_MODE_PSFSTAR;
+        }
+    }
+
+    // build a PSF residual image
+    if (!psphotMakeResiduals (try->sources, recipe, try->psf, maskVal)) {
+        psError(PSPHOT_ERR_PSF, false, "Unable to construct residual table for PSF");
+        psFree (models);
+        psFree(options);
+        return NULL;
+    }
+
+    // build a PSF residual image
+    if (!psphotMakeFluxScale (readout->image, recipe, try->psf)) {
+        psError(PSPHOT_ERR_PSF, false, "Unable to construct flux scale for PSF");
+        psFree (models);
+        psFree(options);
+        return NULL;
+    }
+
+    // XXX test dump of psf star data and psf-subtracted image
+    if (psTraceGetLevel("psphot.psfstars") > 5) {
+        psphotSaveImage (NULL, readout->image,  "rawstars.fits");
+
+        for (int i = 0; i < try->sources->n; i++) {
+            // masked for: bad model fit, outlier in parameters
+            if (try->mask->data.U8[i] & PSFTRY_MASK_ALL)
+                continue;
+
+            pmSource *source = try->sources->data[i];
+            float x = source->modelPSF->params->data.F32[PM_PAR_XPOS];
+            float y = source->modelPSF->params->data.F32[PM_PAR_YPOS];
+
+            // set the mask and subtract the PSF model
+            // XXX should we be using maskObj? should we be unsetting the mask?
+            // use pmModelSub because modelFlux has not been generated
+            assert (source->maskObj);
+            psImageKeepCircle (source->maskObj, x, y, options->radius, "OR", markVal);
+            pmModelSub (source->pixels, source->maskObj, source->modelPSF, PM_MODEL_OP_FULL, maskVal);
+            psImageKeepCircle (source->maskObj, x, y, options->radius, "AND", PS_NOT_U8(markVal));
+        }
+
+        FILE *f = fopen ("shapes.dat", "w");
+        for (int i = 0; i < try->sources->n; i++) {
+            psF32 inPar[10];  // must be psF32 to pmPSF_FitToModel
+
+            // masked for: bad model fit, outlier in parameters
+            if (try->mask->data.U8[i] & PSFTRY_MASK_ALL) continue;
+
+            pmSource *source = try->sources->data[i];
+            psF32 *outPar = source->modelEXT->params->data.F32;
+
+            psEllipseShape shape;
+
+            shape.sx  = outPar[PM_PAR_SXX] / M_SQRT2;
+            shape.sy  = outPar[PM_PAR_SYY] / M_SQRT2;
+            shape.sxy = outPar[PM_PAR_SXY];
+
+            psEllipsePol pol = pmPSF_ModelToFit (outPar);
+            inPar[PM_PAR_E0] = pol.e0;
+            inPar[PM_PAR_E1] = pol.e1;
+            inPar[PM_PAR_E2] = pol.e2;
+            pmPSF_FitToModel (inPar, 0.1);
+
+            psEllipseAxes axes1 = psEllipseShapeToAxes (shape, 20.0);
+            psEllipseAxes axes2;
+            (void)psEllipsePolToAxes(pol, 0.1, &axes2);
+            psEllipsePol pol2 = psEllipseAxesToPol (axes1);
+
+            fprintf (f, "%3d  %7.2f %7.2f  %7.4f %7.4f %7.4f  --  %7.4f %7.4f %7.4f  :  %7.4f %7.4f %7.4f  --  %7.4f %7.4f %7.4f : %7.4f %7.4f %6.1f : %7.4f %7.4f %6.1f\n",
+                     i, outPar[PM_PAR_XPOS], outPar[PM_PAR_YPOS],
+                     outPar[PM_PAR_SXX], outPar[PM_PAR_SXY], outPar[PM_PAR_SYY],
+                     pol.e0, pol.e1, pol.e2,
+                     pol2.e0, pol2.e1, pol2.e2,
+                     inPar[PM_PAR_SXX], inPar[PM_PAR_SXY], inPar[PM_PAR_SYY],
+                     axes1.major, axes1.minor, axes1.theta*PM_DEG_RAD,
+                     axes2.major, axes2.minor, axes2.theta*PM_DEG_RAD
+                     );
+        }
+        fclose (f);
+
+        psphotSaveImage (NULL, readout->image,  "psfstars.fits");
+        pmSourcesWritePSFs (try->sources, "psfstars.dat");
+        pmSourcesWriteEXTs (try->sources, "extstars.dat", false);
+        // XXX need alternative output function
+        // psMetadata *psfData = pmPSFtoMetadata (NULL, try->psf);
+        // psMetadataConfigWrite (psfData, "psfmodel.dat");
+        psLogMsg ("psphot.choosePSF", PS_LOG_INFO, "wrote out psf-subtracted image, psf data, exiting\n");
+        exit (0);
+    }
+
+    // save only the best model;
+    // XXX we are not saving the fitted sources
+    // XXX do we want to keep them so we may optionally write them out?
+    pmPSF *psf = psMemIncrRefCounter(try->psf);
+    psFree (models);
+
+    if (!psphotPSFstats (readout, recipe, psf)) {
+        psError(PSPHOT_ERR_PSF, false, "cannot measure PSF shape terms");
+        psFree(options);
+        psFree(psf);
+        return NULL;
+    }
+
+    char *modelName = pmModelClassGetName (psf->type);
+    psLogMsg ("psphot.pspsf", PS_LOG_INFO, "select psf model: %f sec\n", psTimerMark ("psphot"));
+    psLogMsg ("psphot.pspsf", PS_LOG_INFO, "psf model %s, ApResid: %f +/- %f\n", modelName, psf->ApResid, psf->dApResid);
+
+    psFree (options);
+    return (psf);
+}
+
+// measure average parameters of the PSF model
+bool psphotPSFstats (pmReadout *readout, psMetadata *recipe, pmPSF *psf) {
+
+    psEllipseShape shape;
+    psEllipseAxes axes;
+
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(recipe, false);
+    PS_ASSERT_PTR_NON_NULL(psf, false);
+
+    psImage *image = readout->image;
+    PS_ASSERT_PTR_NON_NULL(image, false);
+
+    // construct a normalized PSF model at this coordinate (Io = 1.0)
+    pmModel *modelPSF = pmModelFromPSFforXY (psf, 0.5*image->numCols, 0.5*image->numRows, 1.0);
+    if (modelPSF == NULL) {
+        psError(PSPHOT_ERR_PSF, false, "Failed to estimate PSF model at image centre");
+        return false;
+    }
+
+    // get the model full-width at half-max
+    double FWHM_X = 2*modelPSF->modelRadius (modelPSF->params, 0.5);
+
+    // XXX make sure this is consistent with the re-definition of PM_PAR_SXX
+    shape.sx  = modelPSF->params->data.F32[PM_PAR_SXX];
+    shape.sy  = modelPSF->params->data.F32[PM_PAR_SYY];
+    shape.sxy = modelPSF->params->data.F32[PM_PAR_SXY];
+    axes = psEllipseShapeToAxes (shape, 20.0);
+
+    double FWHM_Y = FWHM_X * (axes.minor / axes.major);
+
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FWHM_X",   PS_META_REPLACE, "PSF FWHM Major axis", FWHM_X);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FWHM_Y",   PS_META_REPLACE, "PSF FWHM Minor axis", FWHM_Y);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "ANGLE",    PS_META_REPLACE, "PSF angle",           axes.theta);
+    psMetadataAddS32 (recipe, PS_LIST_TAIL, "NPSFSTAR", PS_META_REPLACE, "Number of stars used to make PSF", psf->nPSFstars);
+    psMetadataAddBool(recipe, PS_LIST_TAIL, "PSFMODEL", PS_META_REPLACE, "Valid PSF Model?", true);
+    psFree (modelPSF);
+
+    return true;
+}
+
+// determine approximate PSF shape parameters based on the moments
+bool psphotMomentsStats (pmReadout *readout, psMetadata *recipe, psArray *sources) {
+
+    // without the PSF model, we can only rely on the source->moments
+    // as a measure of the FWHM values
+
+    int FWHM_N = 0;
+    double FWHM_X = 0.0;
+    double FWHM_Y = 0.0;
+    double FWHM_T = 0.0;
+
+    psEllipseMoments moments;
+    psEllipseAxes axes;
+
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(recipe, false);
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        if (!source) continue;
+        if (!(source->mode & PM_SOURCE_MODE_PSFSTAR)) continue;
+
+        // moments->Sx,Sy are roughly sigma_x,y
+        moments.x2 = PS_SQR(source->moments->Sx);
+        moments.y2 = PS_SQR(source->moments->Sy);
+        moments.xy = source->moments->Sxy;
+
+        // limit axis ratio < 20.0
+        axes = psEllipseMomentsToAxes (moments, 20.0);
+
+        FWHM_X += axes.major * 2.35;
+        FWHM_Y += axes.minor * 2.35;
+        FWHM_T += axes.theta;
+        FWHM_N ++;
+    }
+
+    FWHM_X /= FWHM_N;
+    FWHM_Y /= FWHM_N;
+    FWHM_T /= FWHM_N;
+
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FWHM_X",   PS_META_REPLACE, "PSF FWHM Major axis (from moments)", FWHM_X);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "FWHM_Y",   PS_META_REPLACE, "PSF FWHM Minor axis (from moments)", FWHM_Y);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "ANGLE",    PS_META_REPLACE, "PSF angle",           FWHM_T);
+    psMetadataAddS32 (recipe, PS_LIST_TAIL, "NPSFSTAR", PS_META_REPLACE, "Number of stars used to make PSF", 0);
+    psMetadataAddBool(recipe, PS_LIST_TAIL, "PSFMODEL", PS_META_REPLACE, "Valid PSF Model?", false);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotCleanup.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotCleanup.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotCleanup.c	(revision 22157)
@@ -0,0 +1,36 @@
+# include "psphotStandAlone.h"
+
+void psphotCleanup (pmConfig *config) {
+
+    psFree (config);
+
+    psTimerStop ();
+    psMemCheckCorruption (stderr, true);
+    pmModelClassCleanup ();
+    psTimeFinalize ();
+    pmConceptsDone ();
+    pmConfigDone ();
+    // fprintf (stderr, "found %d leaks at %s\n", psMemCheckLeaks (0, NULL, NULL, false), "psphot");
+    fprintf (stderr, "found %d leaks at %s\n", psMemCheckLeaks (0, NULL, stdout, false), "psphot");
+    return;
+}
+
+psExit psphotGetExitStatus () {
+
+    psErrorCode err = psErrorCodeLast ();
+    switch (err) {
+      case PS_ERR_NONE:
+	return PS_EXIT_SUCCESS;
+      case PSPHOT_ERR_SYS:
+        return PS_EXIT_SYS_ERROR;
+      case PSPHOT_ERR_CONFIG:
+        return PS_EXIT_CONFIG_ERROR;
+      case PSPHOT_ERR_PROG:
+        return PS_EXIT_PROG_ERROR;
+      case PSPHOT_ERR_DATA:
+        return PS_EXIT_DATA_ERROR;
+      default:
+        return PS_EXIT_UNKNOWN_ERROR;
+    }
+    return PS_EXIT_UNKNOWN_ERROR;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotCullPeaks.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotCullPeaks.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotCullPeaks.c	(revision 22157)
@@ -0,0 +1,264 @@
+# include "psphotInternal.h"
+
+/*
+ * Cull a set of peaks contained in a psArray of pmFootprints
+ */
+psErrorCode
+psphotCullPeaks(const psImage *image,   // the image wherein lives the footprint
+                const psImage *weight,  // corresponding variance image
+                const psMetadata *recipe,
+                psArray *footprints) {  // array of pmFootprints
+    bool status = false;
+    float nsigma_delta = psMetadataLookupF32(&status, recipe, "FOOTPRINT_CULL_NSIGMA_DELTA");
+    if (!status) {
+        nsigma_delta = 0; // min.
+    }
+    float nsigma_min = psMetadataLookupF32(&status, recipe, "FOOTPRINT_CULL_NSIGMA_MIN");
+    if (!status) {
+        nsigma_min = 0;
+    }
+    const float skyStdev = psMetadataLookupF32(NULL, recipe, "SKY_STDEV");
+
+    return pmFootprintArrayCullPeaks(image, weight, footprints,
+                                     nsigma_delta, nsigma_min*skyStdev);
+}
+
+
+/*
+ * Cull an entire psArray of pmFootprints
+ * XXX drop this intermediate level function?
+ */
+psErrorCode
+pmFootprintArrayCullPeaks(const psImage *img, // the image wherein lives the footprint
+			  const psImage *weight,	// corresponding variance image
+			  psArray *footprints, // array of pmFootprints
+			  const float nsigma_delta, // how many sigma above local background a peak
+    					// needs to be to survive
+			  const float min_threshold) { // minimum permitted coll height
+    for (int i = 0; i < footprints->n; i++) {
+	pmFootprint *fp = footprints->data[i];
+	if (pmFootprintCullPeaks(img, weight, fp, nsigma_delta, min_threshold) != PS_ERR_NONE) {
+	    return psError(PS_ERR_UNKNOWN, false, "Culling pmFootprint %d", fp->id);
+	}
+    }
+    
+    return PS_ERR_NONE;
+}
+
+ /*
+  * Examine the peaks in a pmFootprint, and throw away the ones that are not sufficiently
+  * isolated.  More precisely, for each peak find the highest coll that you'd have to traverse
+  * to reach a still higher peak --- and if that coll's more than nsigma DN below your
+  * starting point, discard the peak.
+  */
+psErrorCode pmFootprintCullPeaks_OLD(const psImage *img, // the image wherein lives the footprint
+				 const psImage *weight,	// corresponding variance image
+				 pmFootprint *fp, // Footprint containing mortal peaks
+				 const float nsigma_delta, // how many sigma above local background a peak
+				 	// needs to be to survive
+				 const float min_threshold) { // minimum permitted coll height
+    assert (img != NULL); assert (img->type.type == PS_TYPE_F32);
+    assert (weight != NULL); assert (weight->type.type == PS_TYPE_F32);
+    assert (img->row0 == weight->row0 && img->col0 == weight->col0);
+    assert (fp != NULL);
+
+    if (fp->peaks == NULL || fp->peaks->n == 0) { // nothing to do
+	return PS_ERR_NONE;
+    }
+
+    psRegion subRegion;			// desired subregion; 1 larger than bounding box (grr)
+    subRegion.x0 = fp->bbox.x0; subRegion.x1 = fp->bbox.x1 + 1;
+    subRegion.y0 = fp->bbox.y0; subRegion.y1 = fp->bbox.y1 + 1;
+    const psImage *subImg = psImageSubset((psImage *)img, subRegion);
+    const psImage *subWt = psImageSubset((psImage *)weight, subRegion);
+    assert (subImg != NULL && subWt != NULL);
+    //
+    // We need a psArray of peaks brighter than the current peak.  We'll fake this
+    // by reusing the fp->peaks but lying about n.
+    //
+    // We do this for efficiency (otherwise I'd need two peaks lists), and we are
+    // rather too chummy with psArray in consequence.  But it works.
+    //
+    psArray *brightPeaks = psArrayAlloc(0);
+    psFree(brightPeaks->data);
+    brightPeaks->data = psMemIncrRefCounter(fp->peaks->data);// use the data from fp->peaks
+    //
+    // The brightest peak is always safe; go through other peaks trying to cull them
+    //
+    for (int i = 1; i < fp->peaks->n; i++) { // n.b. fp->peaks->n can change within the loop
+	const pmPeak *peak = fp->peaks->data[i];
+	int x = peak->x - subImg->col0;
+	int y = peak->y - subImg->row0;
+	//
+	// Find the level nsigma below the peak that must separate the peak
+	// from any of its friends
+	//
+	assert (x >= 0 && x < subImg->numCols && y >= 0 && y < subImg->numRows);
+	const float stdev = sqrt(subWt->data.F32[y][x]);
+	float threshold = subImg->data.F32[y][x] - nsigma_delta*stdev;
+	if (isnan(threshold) || threshold < min_threshold) {
+#if 1	  // min_threshold is assumed to be below the detection threshold,
+	  // so all the peaks are pmFootprint, and this isn't the brightest
+	    // XXX mark peak to be dropped
+	    (void)psArrayRemoveIndex(fp->peaks, i);
+	    i--;			// we moved everything down one
+	    continue;
+#else
+#error n.b. We will be running LOTS of checks at this threshold, so only find the footprint once
+	    threshold = min_threshold;
+#endif
+	}
+
+	// XXX EAM : if stdev >= 0, i'm not sure how this can ever be true?
+	if (threshold > subImg->data.F32[y][x]) {
+	    threshold = subImg->data.F32[y][x] - 10*FLT_EPSILON;
+	}
+
+	// XXX this is a bit expensive: psImageAlloc for every peak contained in this footprint
+	// perhaps this should alloc a single ID image above and pass it in to be set.
+
+	const int peak_id = 1;		// the ID for the peak of interest
+	brightPeaks->n = i;		// only stop at a peak brighter than we are
+
+	// XXX optionally use the faster pmFootprintsFind if the subimage size is large (eg, M31)
+
+	pmFootprint *peakFootprint = pmFootprintsFindAtPoint(subImg, threshold, brightPeaks, peak->y, peak->x);
+	brightPeaks->n = 0;		// don't double free
+	psImage *idImg = pmSetFootprintID(NULL, peakFootprint, peak_id);
+	psFree(peakFootprint);
+
+	// Check if any of the previous (brighter) peaks are within the footprint of this peak
+	// If so, the current peak is bogus; drop it.
+	int j;
+	for (j = 0; j < i; j++) {
+	    const pmPeak *peak2 = fp->peaks->data[j];
+	    int x2 = peak2->x - subImg->col0;
+	    int y2 = peak2->y - subImg->row0;
+	    const int peak2_id = idImg->data.S32[y2][x2]; // the ID for some other peak
+
+	    if (peak2_id == peak_id) {	// There's a brighter peak within the footprint above
+		;			// threshold; so cull our initial peak
+		(void)psArrayRemoveIndex(fp->peaks, i);
+		i--;			// we moved everything down one
+		break;
+	    }
+	}
+	if (j == i) {
+	    j++;
+	}
+
+	psFree(idImg);
+    }
+
+    brightPeaks->n = 0; psFree(brightPeaks);
+    psFree((psImage *)subImg);
+    psFree((psImage *)subWt);
+
+    return PS_ERR_NONE;
+}
+
+ /*
+  * Examine the peaks in a pmFootprint, and throw away the ones that are not sufficiently
+  * isolated.  More precisely, for each peak find the highest coll that you'd have to traverse
+  * to reach a still higher peak --- and if that coll's more than nsigma DN below your
+  * starting point, discard the peak.
+  */
+
+# define IN_PEAK 1 
+psErrorCode pmFootprintCullPeaks(const psImage *img, // the image wherein lives the footprint
+				 const psImage *weight,	// corresponding variance image
+				 pmFootprint *fp, // Footprint containing mortal peaks
+				 const float nsigma_delta, // how many sigma above local background a peak
+				 // needs to be to survive
+				 const float min_threshold) { // minimum permitted coll height
+    assert (img != NULL); assert (img->type.type == PS_TYPE_F32);
+    assert (weight != NULL); assert (weight->type.type == PS_TYPE_F32);
+    assert (img->row0 == weight->row0 && img->col0 == weight->col0);
+    assert (fp != NULL);
+
+    if (fp->peaks == NULL || fp->peaks->n == 0) { // nothing to do
+	return PS_ERR_NONE;
+    }
+
+    psRegion subRegion;			// desired subregion; 1 larger than bounding box (grr)
+    subRegion.x0 = fp->bbox.x0; subRegion.x1 = fp->bbox.x1 + 1;
+    subRegion.y0 = fp->bbox.y0; subRegion.y1 = fp->bbox.y1 + 1;
+
+    psImage *subImg = psImageSubset((psImage *)img, subRegion);
+    psImage *subWt = psImageSubset((psImage *)weight, subRegion);
+    assert (subImg != NULL && subWt != NULL);
+
+    psImage *idImg = psImageAlloc(subImg->numCols, subImg->numRows, PS_TYPE_S32);
+
+    // We need a psArray of peaks brighter than the current peak.  
+    // We reject peaks which either:
+    // 1) are below the local threshold
+    // 2) have a brighter peak within their threshold
+
+    // allocate the full-sized array.  if the final array is much smaller, we can realloc
+    // at that point.
+    psArray *brightPeaks = psArrayAllocEmpty(fp->peaks->n);
+    psArrayAdd (brightPeaks, 128, fp->peaks->data[0]);
+
+    // The brightest peak is always safe; go through other peaks trying to cull them
+    for (int i = 1; i < fp->peaks->n; i++) { // n.b. fp->peaks->n can change within the loop
+	const pmPeak *peak = fp->peaks->data[i];
+	int x = peak->x - subImg->col0;
+	int y = peak->y - subImg->row0;
+	//
+	// Find the level nsigma below the peak that must separate the peak
+	// from any of its friends
+	//
+	assert (x >= 0 && x < subImg->numCols && y >= 0 && y < subImg->numRows);
+	const float stdev = sqrt(subWt->data.F32[y][x]);
+	float threshold = subImg->data.F32[y][x] - nsigma_delta*stdev;
+	if (isnan(threshold) || threshold < min_threshold) {
+	    // min_threshold is assumed to be below the detection threshold,
+	    // so all the peaks are pmFootprint, and this isn't the brightest
+	    continue;
+	}
+
+	// XXX EAM : if stdev >= 0, i'm not sure how this can ever be true?
+	if (threshold > subImg->data.F32[y][x]) {
+	    threshold = subImg->data.F32[y][x] - 10*FLT_EPSILON;
+	}
+
+	// XXX this is a bit expensive: psImageAlloc for every peak contained in this footprint
+	// perhaps this should alloc a single ID image above and pass it in to be set.
+
+	// XXX optionally use the faster pmFootprintsFind if the subimage size is large (eg, M31)
+
+	// at this point brightPeaks only has the peaks brighter than the current
+	pmFootprint *peakFootprint = pmFootprintsFindAtPoint(subImg, threshold, brightPeaks, peak->y, peak->x);
+
+	// XXX need to supply the image here
+	// we set the IDs to either 1 (in peak) or 0 (not in peak)
+	pmSetFootprintID (idImg, peakFootprint, IN_PEAK);
+	psFree(peakFootprint);
+
+	// Check if any of the previous (brighter) peaks are within the footprint of this peak
+	// If so, the current peak is bogus; drop it.
+	bool keep = true;
+	for (int j = 0; keep && (j < brightPeaks->n); j++) {
+	    const pmPeak *peak2 = fp->peaks->data[j];
+	    int x2 = peak2->x - subImg->col0;
+	    int y2 = peak2->y - subImg->row0;
+	    if (idImg->data.S32[y2][x2] == IN_PEAK) 
+		// There's a brighter peak within the footprint above threshold; so cull our initial peak
+		keep = false;
+	}
+	if (!keep) continue;
+
+	psArrayAdd (brightPeaks, 128, fp->peaks->data[i]);
+    }
+
+    psFree (fp->peaks);
+    fp->peaks = brightPeaks;
+
+    psFree(idImg);
+    psFree(subImg);
+    psFree(subWt);
+
+    return PS_ERR_NONE;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotDeblendSatstars.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotDeblendSatstars.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotDeblendSatstars.c	(revision 22157)
@@ -0,0 +1,159 @@
+# include "psphotInternal.h"
+
+bool psphotDeblendSatstars (psArray *sources, psMetadata *recipe) {
+
+    int N;
+    pmSource *source;
+
+    psTimerStart ("psphot");
+
+    int Nblend = 0;
+    float SAT_TEST_LEVEL = 50000;
+    float SAT_MIN_RADIUS = 5.0;
+
+    // we need sources spatially-sorted to find overlaps
+    sources = psArraySort (sources, pmSourceSortByY);
+
+    // source analysis is done in peak order (brightest first)
+    // we use an index for this so the spatial sorting is kept
+    psVector *SN = psVectorAlloc (sources->n, PS_DATA_F32);
+    for (int i = 0; i < SN->n; i++) {
+        source = sources->data[i];
+        SN->data.F32[i] = source->peak->SN;
+    }
+    psVector *index = psVectorSortIndex (NULL, SN);
+    // this results in an index of increasing SN
+
+    // examine sources in decreasing SN order
+    for (int i = sources->n - 1; i >= 0; i--) {
+        N = index->data.U32[i];
+        source = sources->data[N];
+
+        // XXX filter? if (source->mode & PM_SOURCE_MODE_SATSTAR) continue;
+        if (source->mode & PM_SOURCE_MODE_BLEND) continue;
+        if (source->peak->flux < SAT_TEST_LEVEL) continue;
+
+	// save these for reference below
+	int xPeak = source->peak->x;
+	int yPeak = source->peak->y;
+
+        // generate a basic contour (set of x,y coordinates at-or-below flux level)
+        psArray *contour = pmSourceContour (source->pixels, xPeak, yPeak, SAT_TEST_LEVEL);
+        if (contour == NULL) continue;
+
+	// contour consists of a set of X,Y coords giving the boundary
+	psVector *xVec = contour->data[0];
+	psVector *yVec = contour->data[1];
+
+	// XXX should we filter based on the number of pixels in the contour?
+
+	// find the center of the contour (let's just use mid[x,y])
+	int xMin = xVec->data.F32[0];
+	int xMax = xVec->data.F32[0];
+	int yMin = yVec->data.F32[0];
+	int yMax = yVec->data.F32[0];
+	for (int j = 0; j < xVec->n; j++) {
+	    xMin = PS_MIN (xMin, xVec->data.F32[j]);
+	    xMax = PS_MAX (xMax, xVec->data.F32[j]);
+	    yMin = PS_MIN (yMin, yVec->data.F32[j]);
+	    yMax = PS_MAX (yMax, yVec->data.F32[j]);
+	}	
+	int xCenter = 0.5*(xMin + xMax);
+	int yCenter = 0.5*(yMin + yMax);
+	psFree (contour);
+
+	// reset the peak for this source to the value of the center pixel
+	source->peak->x = xCenter;
+	source->peak->y = yCenter;
+	source->peak->xf = xCenter;
+	source->peak->yf = yCenter;
+	
+	// temporary array for overlapping objects we find
+        psArray *overlap = psArrayAllocEmpty (100);
+
+        // search backwards for overlapping sources
+        for (int j = N - 1; j >= 0; j--) {
+            pmSource *testSource = sources->data[j];
+            if (testSource->peak->x <  source->pixels->col0) continue;
+            if (testSource->peak->x >= source->pixels->col0 + source->pixels->numCols) continue;
+            if (testSource->peak->y <  source->pixels->row0) break;
+            if (testSource->peak->y >= source->pixels->row0 + source->pixels->numRows) {
+                fprintf (stderr, "warning: invalid condition\n");
+                continue;
+            }
+            psArrayAdd (overlap, 100, testSource);
+        }
+
+        // search forwards for overlapping sources
+        for (int j = N + 1; j < sources->n; j++) {
+            pmSource *testSource = sources->data[j];
+            if (testSource->peak->x <  source->pixels->col0) continue;
+            if (testSource->peak->x >= source->pixels->col0 + source->pixels->numCols) continue;
+            if (testSource->peak->y <  source->pixels->row0) {
+                fprintf (stderr, "warning: invalid condition\n");
+                continue;
+            }
+            if (testSource->peak->y >= source->pixels->row0 + source->pixels->numRows) break;
+            psArrayAdd (overlap, 100, testSource);
+        }
+
+        if (overlap->n == 0) {
+            psFree (overlap);
+            continue;
+        }
+
+	// now find the contour which is at 0.5*SAT_TEST_LEVEL (xPeak, yPeak is valid high pixel)
+        contour = pmSourceContour (source->pixels, xPeak, yPeak, 0.5*SAT_TEST_LEVEL);
+        if (contour == NULL) {
+	    psFree (overlap);
+	    continue; 
+	}
+	
+	// any peaks within this contour should be dropped (mark as blend, drop after loop is done)
+	// also drop any peaks which are too close to this peal
+        psVector *xv = contour->data[0];
+        psVector *yv = contour->data[1];
+        for (int k = 0; k < overlap->n; k++) {
+            pmSource *testSource = overlap->data[k];
+	    float radius = hypot((testSource->peak->x - xCenter), (testSource->peak->y - yCenter));
+	    if (radius < SAT_MIN_RADIUS) {
+                testSource->mode |= PM_SOURCE_MODE_BLEND;
+                Nblend ++;
+		continue;
+	    }
+            for (int j = 0; j < xv->n; j+=2) {
+                if (fabs(yv->data.F32[j] - testSource->peak->y) > 0.5) continue;
+                if (xv->data.F32[j+0] > testSource->peak->x) break;
+                if (xv->data.F32[j+1] < testSource->peak->x) break;
+                testSource->mode |= PM_SOURCE_MODE_BLEND;
+                Nblend ++;
+                j = xv->n; // skip rest of contour
+            }
+        }
+        psFree (overlap);
+        psFree (contour);
+    }
+
+    // drop the sources marked as BLEND
+    for (int i = 0; i < sources->n;) {
+	pmSource *source = sources->data[i];
+
+        if (!(source->mode & PM_SOURCE_MODE_BLEND)) {
+	    i++;
+	    continue;
+	}
+
+	// shuffle the remaining sources forward
+	for (int j = i; j < sources->n - 1; j++) {
+	    sources->data[j] = sources->data[j+1];
+	}
+	psFree (source);
+	sources->n --;
+    }
+
+    psFree (SN);
+    psFree (index);
+
+    psLogMsg ("psphot", PS_LOG_INFO, "found %d satstar blend peaks, leaving %ld sources: %f sec\n", Nblend, sources->n, psTimerMark ("psphot"));
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotDefineFiles.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotDefineFiles.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotDefineFiles.c	(revision 22157)
@@ -0,0 +1,134 @@
+# include "psphotInternal.h"
+
+// XXX we need to be able to distinguish several cases:
+// 1) the particular output data was not requested
+// 2) the particular output data was requested but was not generated (skipped)
+// 3) the particular output data was requested but was not generated (for valid failure)
+// 4) the particular output data was requested but was not generated (surprising failure)
+
+// define the needed / desired I/O files
+bool psphotDefineFiles (pmConfig *config, pmFPAfile *input) {
+
+    bool status = false;
+
+    // select recipe options supplied on command line
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+
+    // the output sources are carried on the input->fpa structures
+    pmFPAfile *outsources = pmFPAfileDefineOutputFromFile (config, input, "PSPHOT.OUTPUT");
+    if (!outsources) {
+        psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.OUTPUT");
+        return false;
+    }
+    outsources->save = true;
+
+    // if we are choosing not to save the output detections, mark this file inactive so it will
+    // not be written, but will keep the detections in memory for other functions (eg, astrometry)
+    if (!psMetadataLookupBool (NULL, recipe, "SAVE.OUTPUT")) {
+        pmFPAfileActivate (config->files, false, "PSPHOT.OUTPUT");
+        outsources->save = false;
+    }
+
+    // optionally save the residual image
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.RESID")) {
+        pmFPAfile *output = pmFPAfileDefineOutputFromFile (config, input, "PSPHOT.RESID");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.RESID");
+            return false;
+        }
+        output->save = true;
+    }
+    // optionally save the background model (small FITS image)
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.BACKMDL")) {
+        int DX = psMetadataLookupS32 (&status, recipe, "BACKGROUND.XBIN");
+        int DY = psMetadataLookupS32 (&status, recipe, "BACKGROUND.YBIN");
+        pmFPAfile *output = pmFPAfileDefineFromFile (config, input, DX, DY, "PSPHOT.BACKMDL");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.BACKMDL");
+            return false;
+        }
+        output->save = true;
+    }
+    // optionally save the background model's standard deviation (small FITS image)
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.BACKMDL.STDEV")) {
+        int DX = psMetadataLookupS32 (&status, recipe, "BACKGROUND.XBIN");
+        int DY = psMetadataLookupS32 (&status, recipe, "BACKGROUND.YBIN");
+        pmFPAfile *output = pmFPAfileDefineFromFile (config, input, DX, DY, "PSPHOT.BACKMDL.STDEV");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.BACKMDL.STDEV");
+            return false;
+        }
+        output->save = true;
+    }
+    // optionally save the full background image
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.BACKGND")) {
+        pmFPAfile *output = pmFPAfileDefineFromFile (config, input,  1,  1, "PSPHOT.BACKGND");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.BACKGND");
+            return false;
+        }
+        output->save = true;
+    }
+    // optionally save the background-subtracted image
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.BACKSUB")) {
+        pmFPAfile *output = pmFPAfileDefineFromFile (config, input,  1,  1, "PSPHOT.BACKSUB");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.");
+            return false;
+        }
+        output->save = true;
+    }
+    // optionally save the PSF Model
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.PSF")) {
+        pmFPAfile *output = pmFPAfileDefineOutputFromFile (config, input, "PSPHOT.PSF.SAVE");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.PSF.SAVE");
+            return false;
+        }
+        output->save = true;
+    }
+
+    // optionally save output plots
+    // allow specific plots only
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.PLOTS")) {
+        pmFPAfile *output = NULL;
+        output = pmFPAfileDefineOutputFromFile (config, input, "SOURCE.PLOT.MOMENTS");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for SOURCE.PLOT.MOMENTS");
+            return false;
+        }
+        output->save = true;
+        output = pmFPAfileDefineOutputFromFile (config, input, "SOURCE.PLOT.PSFMODEL");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for SOURCE.PLOT.PSFMODEL");
+            return false;
+        }
+        output->save = true;
+        output = pmFPAfileDefineOutputFromFile (config, input, "SOURCE.PLOT.APRESID");
+        if (!output) {
+            psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for SOURCE.PLOT.APRESID");
+            return false;
+        }
+        output->save = true;
+    }
+
+    if (psMetadataLookupPtr(NULL, config->arguments, "SRC")) {
+        if (!pmFPAfileDefineFromArgs (&status, config, "PSPHOT.INPUT.CMF", "SRC")) {
+            psError(PSPHOT_ERR_CONFIG, false, "Failed to find/build PSPHOT.INPUT.CMF");
+            return status;
+        }
+    }
+
+    if (psMetadataLookupPtr(NULL, config->arguments, "PSPHOT.PSF")) {
+        pmFPAfileBindFromArgs(&status, input, config, "PSPHOT.PSF.LOAD", "PSPHOT.PSF");
+        if (!status) {
+            psError(PSPHOT_ERR_CONFIG, false, "Failed to find/build PSPHOT.PSF.LOAD");
+            return status;
+        }
+    }
+
+    // XXX add in example PSF image thumbnails
+    // pmFPAfileConstruct (config->files, format, config->camera, "PSPHOT.PSF_SAMPLE");
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotDiagnosticPlots.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotDiagnosticPlots.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotDiagnosticPlots.c	(revision 22157)
@@ -0,0 +1,189 @@
+# include "psphotInternal.h"
+
+# if (HAVE_KAPA)
+
+static int kapa_fd = -1;
+
+int psphotKapaOpen ()
+{
+    char kapa[64];
+
+    strcpy (kapa, "kapa");
+
+    if (kapa_fd == -1) {
+        kapa_fd = KapaOpenNamedSocket (kapa, "psphot");
+    }
+    return kapa_fd;
+}
+
+bool psphotKapaClose ()
+{
+
+    if (kapa_fd == -1)
+        return true;
+    KapaClose (kapa_fd);
+    kapa_fd = -1;
+    return true;
+}
+
+bool psphotImageBackgroundCellHistogram (psVector *values, float mean, float sigma, int ix, int iy)
+{
+
+    KapaSection section;
+    Graphdata graphdata;
+
+    int kapa = pmKapaOpen (true);
+    if (kapa == -1) {
+        psError(PS_ERR_UNKNOWN, true, "failure to open kapa");
+        return false;
+    }
+
+    psStats *stats = psStatsAlloc (PS_STAT_MAX | PS_STAT_MIN);
+    psVectorStats (stats, values, NULL, NULL, 0);
+
+    psHistogram *histogram = psHistogramAlloc (stats->min, stats->max, 1000);
+    psVectorHistogram (histogram, values, NULL, NULL, 0);
+
+    KapaInitGraph (&graphdata);
+    KapaClearPlots (kapa);
+
+    // plot 1 is the full range
+    section.x  = 0.0;
+    section.y  = 0.0;
+    section.dx = 1.0;
+    section.dy = 0.5;
+    section.name = strcreate ("bottom");
+    KapaSetSection (kapa, &section);
+    free (section.name);
+
+    // set limits based on data values
+    graphdata.xmin = stats->min;
+    graphdata.xmax = stats->max;
+
+    psStatsInit (stats);
+    stats->options = PS_STAT_MAX | PS_STAT_MIN;
+    psVectorStats (stats, histogram->nums, NULL, NULL, 0);
+
+    // scale the plot to hold the histogram
+    graphdata.ymin = stats->min - 0.05*(stats->max - stats->min);
+    graphdata.ymax = stats->max + 0.05*(stats->max - stats->min);
+
+    KapaSetLimits (kapa, &graphdata);
+    KapaSetFont (kapa, "helvetica", 14);
+    KapaBox (kapa, &graphdata);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 0;
+    graphdata.size = 0.5;
+    graphdata.style = 1;
+    KapaPrepPlot (kapa, histogram->nums->n, &graphdata);
+    KapaPlotVector (kapa, histogram->nums->n, histogram->bounds->data.F32, "x");
+    KapaPlotVector (kapa, histogram->nums->n, histogram->nums->data.F32, "y");
+    psFree (histogram);
+
+    // plot 2 is the +/- 10 sigma
+    section.x  = 0.0;
+    section.y  = 0.5;
+    section.dx = 1.0;
+    section.dy = 0.5;
+    section.name = strcreate ("top");
+    KapaSetSection (kapa, &section);
+    free (section.name);
+
+    // +/- 10 sigma
+    graphdata.xmin = mean - 10.0*sigma;
+    graphdata.xmax = mean + 10.0*sigma;
+
+    histogram = psHistogramAlloc (graphdata.xmin, graphdata.xmax, 100);
+    psVectorHistogram (histogram, values, NULL, NULL, 0);
+
+    psStatsInit (stats);
+    stats->options = PS_STAT_MAX | PS_STAT_MIN;
+    psVectorStats (stats, histogram->nums, NULL, NULL, 0);
+
+    // scale the plot to hold the histogram
+    graphdata.ymin = stats->min - 0.05*(stats->max - stats->min);
+    graphdata.ymax = stats->max + 0.05*(stats->max - stats->min);
+
+    KapaSetLimits (kapa, &graphdata);
+    KapaSetFont (kapa, "helvetica", 14);
+    KapaBox (kapa, &graphdata);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 0;
+    graphdata.size = 0.5;
+    graphdata.style = 1;
+    KapaPrepPlot (kapa, histogram->nums->n, &graphdata);
+    KapaPlotVector (kapa, histogram->nums->n, histogram->bounds->data.F32, "x");
+    KapaPlotVector (kapa, histogram->nums->n, histogram->nums->data.F32, "y");
+
+    char line[128];
+    sprintf (line, "sky: %f +/- %f, cell %d,%d", mean, sigma, ix, iy);
+    KapaSendLabel (kapa, line, KAPA_LABEL_XP);
+
+    // pause until user types 'return'
+    fprintf (stdout, "press return");
+    fgets (line, 64, stdin);
+
+    psFree (stats);
+    psFree (histogram);
+
+    return true;
+}
+
+bool psphotDiagnosticPlots (pmConfig *config, char *name, ...) {
+
+    va_list argPtr;
+    bool status;
+
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    assert (recipe);
+
+    psMetadata *plots = psMetadataLookupPtr (&status, recipe, "DIAGNOSTIC.PLOTS");
+    assert (plots);
+
+    // do we want the requested plot?
+    if (!psMetadataLookupBool (&status, plots, name)) return false;
+
+    // Get the variable list parameters to pass to allocation function
+    va_start(argPtr, name);
+
+    if (!strcmp(name, "IMAGE.BACKGROUND.CELL.HISTOGRAM")) {
+
+        int ix = va_arg(argPtr, psS32);
+        int iy = va_arg(argPtr, psS32);
+        float mean  = va_arg(argPtr, double);
+        float sigma = va_arg(argPtr, double);
+        psVector *values = va_arg(argPtr, psPtr);
+
+        int xPlot = psMetadataLookupS32 (&status, plots, "IMAGE.BACKGROUND.CELL.HISTOGRAM.X");
+        assert (status);
+        int yPlot = psMetadataLookupS32 (&status, plots, "IMAGE.BACKGROUND.CELL.HISTOGRAM.Y");
+        assert (status);
+
+        bool gotX = (xPlot < 0) || (xPlot == ix);
+        bool gotY = (yPlot < 0) || (yPlot == iy);
+
+        if (gotX && gotY) {
+            psphotImageBackgroundCellHistogram (values, mean, sigma, ix, iy);
+            goto done;
+        }
+    }
+
+    // Clean up stack after variable arguement has been used
+done:
+    va_end(argPtr);
+    return true;
+}
+
+# else
+
+int psphotKapaOpen () { return -1; }
+bool psphotKapaClose () { return true; }
+bool psphotImageBackgroundCellHistogram (psVector *values, float mean, float sigma, int ix, int iy)
+{
+    return true;
+}
+bool psphotDiagnosticPlots (pmConfig *config, char *name, ...) { return true; }
+
+# endif
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotErrorCodes.c.in
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotErrorCodes.c.in	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotErrorCodes.c.in	(revision 22157)
@@ -0,0 +1,25 @@
+/*
+ * The line
+    { PSPHOT_ERR_$X{ErrorCode}, "$X{ErrorDescription}"},
+ * (without the Xs)
+ * will be replaced by values from errorCodes.dat
+ */
+#include "pslib.h"
+#include "psphotErrorCodes.h"
+
+void psphotErrorRegister(void)
+{
+    static psErrorDescription errors[] = {
+       { PSPHOT_ERR_BASE, "First value we use; lower values belong to psLib" },
+       { PSPHOT_ERR_${ErrorCode}, "${ErrorDescription}"},
+    };
+    static int nerror = PSPHOT_ERR_NERROR - PSPHOT_ERR_BASE; // number of values in enum
+
+    for (int i = 0; i < nerror; i++) {
+       psErrorDescription *tmp = psAlloc(sizeof(psErrorDescription));
+       *tmp = errors[i];
+       psErrorRegister(tmp, 1);
+       psFree(tmp);			/* it's on the internal list */
+    }
+    nerror = 0;			                // don't register more than once
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotErrorCodes.dat
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotErrorCodes.dat	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotErrorCodes.dat	(revision 22157)
@@ -0,0 +1,17 @@
+#
+# This file is used to generate psphotErrorClasses.h
+#
+UNKNOWN			Unknown PM error code
+NOT_IMPLEMENTED		Desired feature is not yet implemented
+FITS			Problem in FITS I/O
+FITS_WCS		Error interpreting FITS WCS information
+PHOTOM			Problem in photometry
+PSF			Problem in PSF
+APERTURE		Problem with aperture photometry
+SKY			Problem in sky determination
+# these errors correspond to standard exit conditions
+ARGUMENTS               Incorrect arguments
+SYS                     System error
+CONFIG                  Problem in configure files
+PROG                    Programming error
+DATA                    invalid data
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotErrorCodes.h.in
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotErrorCodes.h.in	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotErrorCodes.h.in	(revision 22157)
@@ -0,0 +1,18 @@
+#if !defined(PSPHOT_ERROR_CODES_H)
+#define PSPHOT_ERROR_CODES_H
+/*
+ * The line
+ *  PSPHOT_ERR_$X{ErrorCode},
+ * (without the X)
+ *
+ * will be replaced by values from errorCodes.dat
+ */
+typedef enum {
+    PSPHOT_ERR_BASE = 1300,
+    PSPHOT_ERR_${ErrorCode},
+    PSPHOT_ERR_NERROR
+} psphotErrorCode;
+
+void psphotErrorRegister(void);
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotEvalFLT.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotEvalFLT.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotEvalFLT.c	(revision 22157)
@@ -0,0 +1,64 @@
+# include "psphotInternal.h"
+
+bool psphotEvalEXT (pmSource *source, pmModel *model)
+{ 
+    int keep;
+
+    // do we actually have a valid EXT model?
+    if (model == NULL) {
+	source->mode &= ~PM_SOURCE_MODE_FITTED;
+	psTrace ("psphot", 5, "no model fitted?\n");
+	return false;
+    }
+
+    // was the model actually fitted?
+    if (!(model->flags & PM_MODEL_STATUS_FITTED)) {
+	source->mode &= ~PM_SOURCE_MODE_FITTED; 
+	psTrace ("psphot", 5, "no model fitted?\n");
+	return false;
+    }
+
+    // did the model fit fail for one or another reason?
+    if (model->flags & (PM_MODEL_STATUS_BADARGS | 
+			PM_MODEL_STATUS_NONCONVERGE | 
+			PM_MODEL_STATUS_OFFIMAGE)) {
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	psLogMsg ("psphot", 5, "EXT fail fit\n");
+	psTrace ("psphot", 5, "EXT fail fit\n");
+
+	if (model->flags & PM_MODEL_STATUS_OFFIMAGE) {
+	  psTrace ("psphot", 5, "off image\n");
+	}
+	if (model->flags & PM_MODEL_STATUS_BADARGS) {
+	  psTrace ("psphot", 5, "bad args\n");
+	}
+	if (model->flags & PM_MODEL_STATUS_NONCONVERGE) {
+	  psTrace ("psphot", 5, "non converge\n");
+	}
+	return false;
+    }
+
+    // unless we prove otherwise, this object is extended
+    source->type = PM_SOURCE_TYPE_EXTENDED;
+
+    // the following source->mode information pertains to modelEXT:
+    source->mode |= PM_SOURCE_MODE_EXTMODEL;
+
+    // if the object has a fitted peak below 0, the fit did not converge cleanly
+    // XXX this limit is fairly arbitrary, and must be > the value is the model limits
+    if (model->params->data.F32[PM_PAR_I0] <= 0.02) {
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	psTrace ("psphot", 5, "model central intensity ~ zero\n");
+	return false;
+    } 
+
+    keep = model->modelFitStatus(model);
+    if (keep) return true;
+
+    // poor-quality fit; only keep if nothing else works...
+    psLogMsg ("psphot", 5, "EXT poor fit\n");
+    psTrace ("psphot", 5, "EXT poor fit\n");
+
+    source->mode |= PM_SOURCE_MODE_POOR;
+    return false;
+}	
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotEvalPSF.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotEvalPSF.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotEvalPSF.c	(revision 22157)
@@ -0,0 +1,238 @@
+# include "psphotInternal.h"
+
+// given a pmSource which has been fitted using modelPSF, evaluate the
+// resulting fit: did the fit succeed? is this object PSF-like? is this object 
+// extended?  return status is TRUE for a valid PSF, FALSE otherwise.
+
+// identify objects consistent with PSF shape/magnitude distribution
+// we expect dparams[PM_PAR_SXX],dparams[PM_PAR_SXX] to have a scatter of:
+// sigma_x / (S/N)
+
+// any objects which is consistent with the PSF should have 
+// abs(dparams[PM_PAR_SXX]) < N * dsxLine(mag) & abs(dparams[PM_PAR_SYY]) < N * dsyLine(mag)
+// this includes a minimum buffer (DS) for the brighter objects
+
+// EAM : 2006.10.20 : I have re-defined params[PM_PAR_SXX] : it is now sqrt(2)*sigma_x, not 1/sigma_x
+
+// saturated stars should fall outside (larger), but have peaks above SATURATION
+// extended sources should be larger, cosmic rays smaller
+// we also reject objects with S/N too low or ChiSquare to high
+
+// floor for DS value 
+// XXX EAM : add to configuration?
+
+static float SATURATION;
+static float PSF_MIN_SN;
+static float PSF_MIN_DS;
+static float PSF_MAX_CHI;
+static float PSF_SHAPE_NSIGMA;
+static float PSF_DSX_MEAN;
+static float PSF_DSY_MEAN;
+static float PSF_DSX_STDEV;
+static float PSF_DSY_STDEV;
+
+bool psphotInitLimitsPSF (psMetadata *recipe, pmReadout *readout) {
+
+    bool status;
+
+    // XXX do we need to set this differently from the value used to mark saturated pixels?
+    pmCell *cell     = readout->parent;
+    SATURATION       = psMetadataLookupF32 (&status, cell->concepts, "CELL.SATURATION");
+    PSF_MIN_SN       = psMetadataLookupF32 (&status, recipe, "PSF_MIN_SN");
+    PSF_MAX_CHI      = psMetadataLookupF32 (&status, recipe, "PSF_MAX_CHI");
+    PSF_SHAPE_NSIGMA = psMetadataLookupF32 (&status, recipe, "PSF_SHAPE_NSIGMA");
+    PSF_MIN_DS       = psMetadataLookupF32 (&status, recipe, "PSF_MIN_DS");
+    if (!status) PSF_MIN_DS = 0.01;
+
+    PSF_DSX_MEAN     = psMetadataLookupF32 (&status, recipe, "DSX_MEAN");
+    PSF_DSY_MEAN     = psMetadataLookupF32 (&status, recipe, "DSY_MEAN");
+    PSF_DSX_STDEV    = psMetadataLookupF32 (&status, recipe, "DSX_STDV");
+    PSF_DSY_STDEV    = psMetadataLookupF32 (&status, recipe, "DSY_STDV");
+
+    return true;
+}
+
+// examine the model->status, fit parameters, etc and decide if the model succeeded
+// set the source->type and source->mode appropriately
+bool psphotEvalPSF (pmSource *source, pmModel *model) { 
+
+    int keep;
+    float dSX, dSY, SX, SY, SN;
+    float nSx, nSy, Chi;
+
+    // do we actually have a valid PSF model?
+    if (model == NULL) {
+	source->mode &= ~PM_SOURCE_MODE_FITTED;
+	return false;
+    }
+
+    // was the model actually fitted?
+    if (!(model->flags & PM_MODEL_STATUS_FITTED)) {
+	source->mode &= ~PM_SOURCE_MODE_FITTED; 
+	if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	    psTrace ("psphot", 5, "satstar was not fitted");
+	}
+	return false;
+    }
+    // did the model fit fail for one or another reason?
+    if (model->flags & (PM_MODEL_STATUS_BADARGS | 
+			PM_MODEL_STATUS_NONCONVERGE | 
+			PM_MODEL_STATUS_OFFIMAGE)) {
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	    psTrace ("psphot", 5, "satstar failed fit");
+	}
+	return false;
+    }
+
+    // unless we prove otherwise, this object is a star.
+    source->type = PM_SOURCE_TYPE_STAR;
+
+    // the following source->mode information pertains to modelPSF:
+    source->mode |= PM_SOURCE_MODE_PSFMODEL;
+
+    // if the object has fitted peak above saturation, label as SATSTAR
+    // this is a valid PSF object, but ignore the other quality tests
+    // remember: fit does not use saturated pixels (masked)
+    // XXX no extended object can saturate and stay extended...
+    if (model->params->data.F32[PM_PAR_I0] >= SATURATION) {
+	if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+	    psLogMsg ("psphot", 5, "PSFSTAR marked SATSTAR\n");
+	}
+	source->mode |=  PM_SOURCE_MODE_SATSTAR;
+	return true;
+    } 
+
+    // if the object has a fitted peak below 0.02, the source is not viable
+    // perhaps the fit did not converge cleanly
+    if (model->params->data.F32[PM_PAR_I0] <= 0.02) {
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	    psTrace ("psphot", 5, "satstar failed fit (peak below 0)");
+	}
+	return false;
+    } 
+
+    // if the source was predicted to be a SATSTAR, but it fitted below saturation, 
+    // make a note to the user
+    if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	psLogMsg ("psphot", 5, "SATSTAR marked normal (fitted peak below saturation)\n");
+	source->mode &= ~PM_SOURCE_MODE_SATSTAR;
+    }
+
+    SN  = model->params->data.F32[PM_PAR_I0]/model->dparams->data.F32[PM_PAR_I0];
+    SX  = model->params->data.F32[PM_PAR_SXX];
+    SY  = model->params->data.F32[PM_PAR_SYY];
+    dSX = model->dparams->data.F32[PM_PAR_SXX];
+    dSY = model->dparams->data.F32[PM_PAR_SYY];
+    Chi = model->chisqNorm / model->nDOF;
+
+    // swing of sigma_x,y in sigmas
+    nSx = (dSX - PSF_DSX_MEAN) / (PSF_DSX_STDEV * PS_MAX (PSF_MIN_DS, (SX / SN)));
+    nSy = (dSY - PSF_DSY_MEAN) / (PSF_DSY_STDEV * PS_MAX (PSF_MIN_DS, (SY / SN)));
+
+    // assign PM_SOURCE_MODE_GOODSTAR to bright objects within PSF region of dparams[]
+    keep = TRUE;
+    // keep &= (fabs(nSx) < PSF_SHAPE_NSIGMA);
+    // keep &= (fabs(nSy) < PSF_SHAPE_NSIGMA);
+    keep &= (SN > PSF_MIN_SN);
+    keep &= (Chi < PSF_MAX_CHI);
+
+    if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	psTrace ("psphot", 5, "satstar fit results: %f, %f  %d :  %f %f : %f %f : %f %f\n", 
+		     model->params->data.F32[PM_PAR_XPOS], model->params->data.F32[PM_PAR_YPOS], keep,
+		 dSX, nSx, dSY, nSy, SN, Chi);
+    }
+
+    if (keep) {
+	if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+	    psTrace ("psphot", 7, "PSFSTAR kept (%f, %f  :  %f %f : %f %f : %f %f)\n", 
+		     model->params->data.F32[PM_PAR_XPOS], model->params->data.F32[PM_PAR_YPOS], dSX, nSx, dSY, nSy, SN, Chi);
+	}
+	return true;
+    }
+
+    // this source is not a star, warn if it was a PSFSTAR
+    if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+	psTrace ("psphot", 5, "PSFSTAR demoted based on fit quality   (%f, %f  :  %f %f : %f %f : %f %f)\n", 
+		  model->params->data.F32[PM_PAR_XPOS], model->params->data.F32[PM_PAR_YPOS], dSX, nSx, dSY, nSy, SN, Chi);
+    } else {
+	psTrace ("psphot", 5, "fails PSF fit (%f, %f  :  %f %f : %f %f : %f %f)\n", 
+		  model->params->data.F32[PM_PAR_XPOS], model->params->data.F32[PM_PAR_YPOS], dSX, nSx, dSY, nSy, SN, Chi);
+    }
+
+    // object appears to be small, suspected defect.  this is a weak indication for a defect: a
+    // large positive or negative swing can also come from the bright sources for which we are 
+    // more sensitive to the wings than for the faint sources.  
+    // XXX allow -NSIGMA and +NSIGMA to have different values?
+    if ((nSx <= -PSF_SHAPE_NSIGMA) || (nSy <= -PSF_SHAPE_NSIGMA)) {
+	source->type = PM_SOURCE_TYPE_DEFECT;
+	return false;
+    }
+
+    // object appears to be large, suspected extended source
+    if ((nSx >= PSF_SHAPE_NSIGMA) || (nSy >= PSF_SHAPE_NSIGMA)) {
+	source->type = PM_SOURCE_TYPE_EXTENDED;
+	return false;
+    }
+
+    // poor-quality fit; only keep if nothing else works...
+    source->mode |= PM_SOURCE_MODE_POOR;
+    return false;
+}	
+
+// examine the model->status, fit parameters, etc and decide if the model succeeded
+// set the source->type and source->mode appropriately
+bool psphotEvalDBL (pmSource *source, pmModel *model) { 
+
+    // do we actually have a valid PSF model?
+    if (model == NULL) {
+	source->mode &= ~PM_SOURCE_MODE_FITTED;
+	return false;
+    }
+
+    // was the model actually fitted?
+    if (!(model->flags & PM_MODEL_STATUS_FITTED)) {
+	source->mode &= ~PM_SOURCE_MODE_FITTED; 
+	return false;
+    }
+    // did the model fit fail for one or another reason?
+    if (model->flags & (PM_MODEL_STATUS_BADARGS | 
+			PM_MODEL_STATUS_NONCONVERGE | 
+			PM_MODEL_STATUS_OFFIMAGE)) {
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	return false;
+    }
+
+    // unless we prove otherwise, this object is a star.
+    source->type = PM_SOURCE_TYPE_STAR;
+
+    // the following source->mode information pertains to modelPSF:
+    source->mode |= PM_SOURCE_MODE_PSFMODEL;
+
+    // if the object has fitted peak above saturation, label as SATSTAR
+    // this is a valid PSF object, but ignore the other quality tests
+    // remember: fit does not use saturated pixels (masked)
+    // XXX no extended object can saturate and stay extended...
+    if (model->params->data.F32[PM_PAR_I0] >= SATURATION) {
+	if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+	    psLogMsg ("psphot", 5, "PSFSTAR marked SATSTAR\n");
+	}
+	source->mode |=  PM_SOURCE_MODE_SATSTAR;
+	return true;
+    } 
+
+    // if the object has a fitted peak below 0, the fit did not converge cleanly
+    if (model->params->data.F32[PM_PAR_I0] <= 0) {
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	return false;
+    } 
+
+    // if the source was predicted to be a SATSTAR, but it fitted below saturation, 
+    // make a note to the user
+    if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	psLogMsg ("psphot", 5, "SATSTAR marked normal (fitted peak below saturation)\n");
+	source->mode &= ~PM_SOURCE_MODE_SATSTAR;
+    }
+    return true;
+}	
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotExtendedSourceAnalysis.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotExtendedSourceAnalysis.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotExtendedSourceAnalysis.c	(revision 22157)
@@ -0,0 +1,131 @@
+# include "psphotInternal.h"
+
+// aperture-like measurements for extended sources
+bool psphotExtendedSourceAnalysis (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+
+    bool status;
+    int Next = 0;
+    int Npetro = 0;
+    int Nisophot = 0;
+    int Nannuli = 0;
+    int Nkron = 0;
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    // perform full non-linear fits / extended source analysis?
+    if (!psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ANALYSIS")) {
+	psLogMsg ("psphot", PS_LOG_INFO, "skipping extended source measurements\n");
+	return true;
+    }
+
+    // option to limit analysis to a specific region
+    char *region = psMetadataLookupStr (&status, recipe, "ANALYSIS_REGION");
+    psRegion AnalysisRegion = psRegionForImage (readout->image, psRegionFromString (region));
+    if (psRegionIsNaN (AnalysisRegion)) psAbort("analysis region mis-defined");
+
+    // S/N limit to perform full non-linear fits
+    float SN_LIM = psMetadataLookupF32 (&status, recipe, "EXTENDED_SOURCE_SN_LIM");
+
+    // which extended source analyses should we perform?
+    bool doPetrosian    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_PETROSIAN");
+    bool doIsophotal    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ISOPHOTAL");
+    bool doAnnuli       = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ANNULI");
+    bool doKron         = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_KRON");
+
+    // source analysis is done in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // XXX some init functions for the extended source recipe options?
+
+    // choose the sources of interest
+    for (int i = 0; i < sources->n; i++) {
+
+	pmSource *source = sources->data[i];
+
+	// skip PSF-like and non-astronomical objects
+	if (source->type == PM_SOURCE_TYPE_STAR) continue;
+	if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
+	if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+
+	// limit selection to some SN limit
+	assert (source->peak); // how can a source not have a peak?
+	if (source->peak->SN < SN_LIM) continue;
+
+	// limit selection by analysis region
+	if (source->peak->x < AnalysisRegion.x0) continue;
+	if (source->peak->y < AnalysisRegion.y0) continue;
+	if (source->peak->x > AnalysisRegion.x1) continue;
+	if (source->peak->y > AnalysisRegion.y1) continue;
+
+	// replace object in image
+	if (source->mode & PM_SOURCE_MODE_SUBTRACTED) {
+	    pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+	}
+	Next ++;
+
+	// if we request any of these measurements, we require the radial profile
+	if (doPetrosian || doIsophotal || doAnnuli || doKron) {
+	    if (!psphotRadialProfile (source, recipe, maskVal)) {
+		// all measurements below require the radial profile; skip them all
+		// re-subtract the object, leave local sky
+		psTrace ("psphot", 5, "failed to extract radial profile for source at %7.1f, %7.1f", source->moments->x, source->moments->y);
+		pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+		source->mode |= PM_SOURCE_MODE_SUBTRACTED;
+		continue;
+	    }
+	}
+
+	// Isophotal Mags
+	if (doIsophotal) {
+	    if (!psphotIsophotal (source, recipe, maskVal)) {
+		psTrace ("psphot", 5, "failed to measure isophotal mags for source at %7.1f, %7.1f", source->moments->x, source->moments->y);
+	    } else {
+		psTrace ("psphot", 5, "measured isophotal mags for source at %7.1f, %7.1f", source->moments->x, source->moments->y);
+		Nisophot ++;
+	    }
+	}
+
+	// Petrosian Mags
+	if (doPetrosian) {
+	    if (!psphotPetrosian (source, recipe, maskVal)) {
+		psTrace ("psphot", 5, "measured petrosian flux & radius for source at %7.1f, %7.1f", source->moments->x, source->moments->y);
+	    } else {
+		psTrace ("psphot", 5, "measured petrosian flux & radius for source at %7.1f, %7.1f", source->moments->x, source->moments->y);
+		Npetro ++;
+	    }
+	}
+
+	// Kron Mags
+	if (doKron) {
+	    if (!psphotKron (source, recipe, maskVal)) {
+		psTrace ("psphot", 5, "failed to measure kron mags for source at %7.1f, %7.1f", source->moments->x, source->moments->y);
+	    } else {
+		psTrace ("psphot", 5, "measure kron mags for source at %7.1f, %7.1f", source->moments->x, source->moments->y);
+		Nkron ++;
+	    }
+	}
+
+	// Radial Annuli
+	if (doAnnuli) {
+	    if (!psphotAnnuli (source, recipe, maskVal)) {
+		psError(PSPHOT_ERR_UNKNOWN, false, "failure in Annuli analysis");
+		return false;
+	    } 
+	    psTrace ("psphot", 5, "measured annuli for source at %7.1f, %7.1f", source->moments->x, source->moments->y);
+	    Nannuli ++;
+	}
+
+	// re-subtract the object, leave local sky
+	pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+	source->mode |= PM_SOURCE_MODE_SUBTRACTED;
+    }
+
+    psLogMsg ("psphot", PS_LOG_INFO, "extended source analysis: %f sec for %d objects\n", psTimerMark ("psphot"), Next);
+    psLogMsg ("psphot", PS_LOG_INFO, "  %d petrosian\n", Npetro);
+    psLogMsg ("psphot", PS_LOG_INFO, "  %d isophotal\n", Nisophot);
+    psLogMsg ("psphot", PS_LOG_INFO, "  %d annuli\n", Nannuli);
+    psLogMsg ("psphot", PS_LOG_INFO, "  %d kron\n", Nkron);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotExtendedSourceFits.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotExtendedSourceFits.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotExtendedSourceFits.c	(revision 22157)
@@ -0,0 +1,276 @@
+# include "psphotInternal.h"
+
+// non-linear model fitting for extended sources
+bool psphotExtendedSourceFits (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+
+    bool status;
+    int Next = 0;
+    int Nconvolve = 0;
+    int NconvolvePass = 0;
+    int Nplain = 0;
+    int NplainPass = 0;
+    bool savePics = false;
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    psMaskType markVal = psMetadataLookupU8(&status, recipe, "MARK.PSPHOT"); // Mask value for bad pixels
+    assert (markVal);
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // perform full extended source non-linear fits?
+    if (!psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_FITS")) {
+	psLogMsg ("psphot", PS_LOG_INFO, "skipping extended source measurements\n");
+	return true;
+    }
+
+    // select the collection of desired models
+    psMetadata *models = psMetadataLookupMetadata (&status, recipe, "EXTENDED_SOURCE_MODELS");
+    if (!status) {
+	psWarning ("extended source model fits requested but model model is missing (EXTENDED_SOURCE_MODELS)\n");
+	return true;
+    }
+    if (models->list->n == 0) {
+	psWarning ("extended source model fits requested but no models are specified\n");
+	return true;
+    }
+
+    // validate the model entries
+    psMetadataIterator *iter = psMetadataIteratorAlloc (models, PS_LIST_HEAD, NULL);
+    psMetadataItem *item = NULL;
+    while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
+
+      if (item->type != PS_DATA_METADATA) {
+	psAbort ("Invalid type for EXTENDED_SOURCE_MODEL entry %s, not a metadata folder", item->name);
+	// XXX we could cull the bad entries or build a validated model folder
+      }
+
+      psMetadata *model = (psMetadata *) item->data.md;
+
+      // check on the model type
+      char *modelName = psMetadataLookupStr (&status, model, "MODEL");
+      int modelType = pmModelClassGetType (modelName);
+      if (modelType < 0) {
+	psAbort ("Unknown model class for EXTENDED_SOURCE_MODEL entry %s: %s", item->name, modelName);
+      }	
+      psMetadataAddS32 (model, PS_LIST_TAIL, "MODEL_TYPE", PS_META_REPLACE, "", modelType);
+
+      // check on the SNLIM, set a float value
+      char *SNword = psMetadataLookupStr (&status, model, "SNLIM");
+      if (!status) {
+	psAbort("SNLIM not defined for extended source model %s\n", item->name);
+      }
+      float SNlim = atof (SNword);
+      psMetadataAddF32 (model, PS_LIST_TAIL, "SNLIM_VALUE", PS_META_REPLACE, "", SNlim);
+	
+      // check on the PSF-Convolution status
+      char *convolvedWord = psMetadataLookupStr (&status, model, "PSF_CONVOLVED");
+      if (!status || (strcasecmp (convolvedWord, "true") && strcasecmp (convolvedWord, "false"))) {
+	psAbort ("PSF_CONVOLVED entry invalid or missing for EXTENDED_SOURCE_MODEL entry %s", item->name);
+      }	
+      bool convolved = !strcasecmp (convolvedWord, "true");
+      psMetadataAddBool (model, PS_LIST_TAIL, "PSF_CONVOLVED_VALUE", PS_META_REPLACE, "", convolved);
+    }
+    psFree (iter);
+
+    // option to limit analysis to a specific region
+    char *region = psMetadataLookupStr (&status, recipe, "ANALYSIS_REGION");
+    psRegion AnalysisRegion = psRegionForImage (readout->image, psRegionFromString (region));
+    if (psRegionIsNaN (AnalysisRegion)) psAbort("analysis region mis-defined");
+
+    // what fraction of the PSF is used? (radius in pixels : 2 -> 5x5 box)
+    int psfSize = psMetadataLookupS32 (&status, recipe, "PCM_BOX_SIZE");
+    assert (status);
+
+    // source analysis is done in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // choose the sources of interest
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+
+        // skip PSF-like and non-astronomical objects
+        if (source->type == PM_SOURCE_TYPE_STAR) continue;
+        if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
+        if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+
+        // XXX this should use peak?
+        if (source->peak->x < AnalysisRegion.x0) continue;
+        if (source->peak->y < AnalysisRegion.y0) continue;
+        if (source->peak->x > AnalysisRegion.x1) continue;
+        if (source->peak->y > AnalysisRegion.y1) continue;
+
+        // if model is NULL, we don't have a starting guess
+	// XXX use the parameters guessed from moments
+        // if (source->modelEXT == NULL) continue;
+
+        // replace object in image
+        if (source->mode & PM_SOURCE_MODE_SUBTRACTED) {
+            pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+        }
+        Next ++;
+
+	// save the modelFlux here in case we need to subtract it (for failure)
+	psImage *modelFluxStart = psMemIncrRefCounter (source->modelFlux);
+
+	if (savePics) {
+	  psphotSaveImage (NULL, readout->image, "image.xp.fits");
+	}
+
+	// array to store the pointers to the model flux images while the models are being fitted
+	psArray *modelFluxes = psArrayAllocEmpty (4);
+    
+	// allocate the array to store the model fits
+	if (source->modelFits == NULL) {
+	    source->modelFits = psArrayAllocEmpty (4);
+	}
+
+	// loop here over the models chosen for each source (exclude by S/N)
+	psMetadataIterator *iter = psMetadataIteratorAlloc (models, PS_LIST_HEAD, NULL);
+	psMetadataItem *item = NULL;
+	while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
+
+	  // XXX this should have been forced above
+	  assert (item->type == PS_DATA_METADATA);
+	  psMetadata *model = (psMetadata *) item->data.md;
+
+	  // check the SNlim and skip model if source is too faint
+	  float SNlim = psMetadataLookupF32 (&status, model, "SNLIM_VALUE");
+	  assert (status);
+
+	  // limit selection to some SN limit
+	  assert (source->peak); // how can a source not have a peak?
+	  if (source->peak->SN < SNlim) continue;
+
+	  // check on the model type
+	  pmModelType modelType = psMetadataLookupS32 (&status, model, "MODEL_TYPE");
+	  assert (status);
+
+	  // check on the PSF-Convolution status
+	  bool convolved = psMetadataLookupBool (&status, model, "PSF_CONVOLVED_VALUE");
+	  assert (status);
+
+	  // XXX psTraceSetLevel ("psLib.math.psMinimizeLMChi2", 6);
+	  // XXX psTraceSetLevel ("psphot.psphotModelWithPSF_LMM", 6);
+
+	  // fit the model as convolved or not
+	  pmModel *modelFit = NULL;
+	  if (convolved) {
+	      modelFit = psphotPSFConvModel (readout, source, modelType, maskVal, markVal, psfSize);
+	      if (!modelFit) {
+		  psTrace ("psphot", 5, "failed to fit psf-conv model for object at %f, %f", source->moments->x, source->moments->y);
+		  continue;
+	      } 
+	      psTrace ("psphot", 4, "fit psf-conv model for %f, %f : %s chisq = %f\n", source->moments->x, source->moments->y, pmModelClassGetName (modelFit->type), modelFit->chisq);
+	      Nconvolve ++;
+	      if (!(modelFit->flags & (PM_MODEL_STATUS_BADARGS | PM_MODEL_STATUS_NONCONVERGE | PM_MODEL_STATUS_OFFIMAGE))) {
+		  NconvolvePass ++;
+	      }
+	  } else {
+	      psFree (source->modelFlux);
+	      source->modelFlux = NULL;
+	      modelFit = psphotFitEXT (readout, source, modelType, maskVal, markVal);
+	      if (!modelFit) {
+		  psTrace ("psphot", 5, "failed to fit plain model for object at %f, %f", source->moments->x, source->moments->y);
+		  continue;
+	      } 
+	      pmSourceCacheModel (source, maskVal);
+	      psTrace ("psphot", 4, "fit plain model for %f, %f : %s chisq = %f\n", source->moments->x, source->moments->y, pmModelClassGetName (modelFit->type), modelFit->chisq);
+	      Nplain ++;
+	      if (!(modelFit->flags & (PM_MODEL_STATUS_BADARGS | PM_MODEL_STATUS_NONCONVERGE | PM_MODEL_STATUS_OFFIMAGE))) {
+		  NplainPass ++;
+	      }
+	  }
+
+	  // save each of the model flux images and store the best
+	  psArrayAdd (modelFluxes, 4, source->modelFlux);
+
+	  // test for fit quality / result
+	  psArrayAdd (source->modelFits, 4, modelFit);
+
+	  psFree (modelFit);
+	}
+	psFree (iter);
+
+	// evaluate the relative quality of the models, choose one
+	float minChisq = NAN;
+	int minModel = -1;
+	for (int i = 0; i < source->modelFits->n; i++) {
+	    pmModel *model = source->modelFits->data[i];
+	    
+	    if (!(model->flags & PM_MODEL_STATUS_FITTED)) continue;
+	    
+	    if (model->flags & (PM_MODEL_STATUS_BADARGS)) continue;
+	    if (model->flags & (PM_MODEL_STATUS_NONCONVERGE)) continue;
+	    if (model->flags & (PM_MODEL_STATUS_OFFIMAGE)) continue;
+
+	    if ((minModel < 0) || (model->chisq < minChisq)) {
+		minChisq = model->chisq;
+		minModel = i;
+	    }
+	}	    
+
+	if (minModel == -1) {
+	  // re-subtract the object, leave local sky
+	  psTrace ("psphot", 5, "failed to fit extended source model to object at %f, %f", source->moments->x, source->moments->y);
+
+	  // replace original model, subtract it
+	  psFree (source->modelFlux);
+	  source->modelFlux = modelFluxStart;
+
+	  pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+	  source->mode |= PM_SOURCE_MODE_SUBTRACTED;
+
+	  psFree (modelFluxes);
+
+	  if (savePics) {
+	    psphotSaveImage (NULL, readout->image, "image.xp.fits");
+	    char key[10];
+	    fprintf (stdout, "continue? ");
+	    fgets (key, 8, stdin);
+	    if (key[0] == 'n') {
+	      savePics = false;
+	    }
+	  }
+	  continue;
+	}	
+
+	// save the best extended model in modelEXT
+	psFree (source->modelEXT);
+	source->modelEXT = psMemIncrRefCounter (source->modelFits->data[minModel]);
+
+	// save the modelFlux for the best model
+	psFree (source->modelFlux);
+	source->modelFlux = psMemIncrRefCounter (modelFluxes->data[minModel]);
+
+        // subtract the best fit from the object, leave local sky
+        pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+        source->mode |= PM_SOURCE_MODE_SUBTRACTED;
+
+	// the initial model flux is no longer needed
+	psFree (modelFluxStart);
+	psFree (modelFluxes);
+
+	psTrace ("psphot", 4, "best ext model for %f, %f : %s chisq = %f\n", source->moments->x, source->moments->y, pmModelClassGetName (source->modelEXT->type), source->modelEXT->chisq);
+	psTrace ("psphot", 5, "extended source model for source at %7.1f, %7.1f", source->moments->x, source->moments->y);
+
+	if (savePics) {
+	  psphotSaveImage (NULL, readout->image, "image.xm.fits");
+	  char key[10];
+	  fprintf (stdout, "continue? ");
+	  fgets (key, 8, stdin);
+	  if (key[0] == 'n') {
+	    savePics = false;
+	  }
+	}
+    }
+
+    psLogMsg ("psphot", PS_LOG_INFO, "extended source analysis: %f sec for %d objects\n", psTimerMark ("psphot"), Next);
+    psLogMsg ("psphot", PS_LOG_INFO, "  %d convolved models (%d passed)\n", Nconvolve, NconvolvePass);
+    psLogMsg ("psphot", PS_LOG_INFO, "  %d plain models (%d passed)\n", Nplain, NplainPass);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotExtendedSources.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotExtendedSources.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotExtendedSources.c	(revision 22157)
@@ -0,0 +1,137 @@
+# include "psphot.h"
+
+bool psphotExtendedSources (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+
+    bool status;
+    int Next = 0;
+    int Nconvolve = 0;
+    int Npetro = 0;
+    int Nisophot = 0;
+    int Nannuli = 0;
+    int Nkron = 0;
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    // perform full non-linear fits / extended source analysis?
+    if (!psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_FITS")) {
+	psLogMsg ("psphot", PS_LOG_INFO, "skipping extended source measurements\n");
+	return true;
+    }
+
+    // option to limit analysis to a specific region
+    char *region = psMetadataLookupStr (&status, recipe, "ANALYSIS_REGION");
+    psRegion AnalysisRegion = psRegionForImage (readout->image, psRegionFromString (region));
+    if (psRegionIsNaN (AnalysisRegion)) psAbort("analysis region mis-defined");
+
+    // S/N limit to perform full non-linear fits
+    float SN_LIM = psMetadataLookupF32 (&status, recipe, "EXTENDED_SOURCE_SN_LIM");
+
+    // which extended source analyses should we perform?
+    bool doPSFConvModel = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_PSF_CONVOLVED_MODEL");
+    bool doPetrosian    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_PETROSIAN");
+    bool doIsophotal    = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ISOPHOTAL");
+    bool doAnnuli       = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_ANNULI");
+    bool doKron         = psMetadataLookupBool (&status, recipe, "EXTENDED_SOURCE_KRON");
+
+    // source analysis is done in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // XXX some init functions for the extended source recipe options?
+
+    // choose the sources of interest
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+
+        // skip PSF-like and non-astronomical objects
+        if (source->type == PM_SOURCE_TYPE_STAR) continue;
+        if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
+        if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+
+        // limit selection to some SN limit
+        assert (source->peak); // how can a source not have a peak?
+        if (source->peak->SN < SN_LIM) continue;
+
+        // XXX this should use peak?
+        if (source->peak->x < AnalysisRegion.x0) continue;
+        if (source->peak->y < AnalysisRegion.y0) continue;
+        if (source->peak->x > AnalysisRegion.x1) continue;
+        if (source->peak->y > AnalysisRegion.y1) continue;
+
+        // if model is NULL, we don't have a starting guess
+	// XXX this is probably not needed for objects where we only measure aperture-like terms
+        if (source->modelEXT == NULL) continue;
+
+        // replace object in image
+        if (source->mode & PM_SOURCE_MODE_SUBTRACTED) {
+            pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+        }
+        Next ++;
+
+	if (doPSFConvModel && !psphotPSFConvModel (source, recipe, maskVal)) {
+	    psError(PSPHOT_ERR_UNKNOWN, false, "failure in PSF Convolved Model fit");
+	    return false;
+	} else {
+	  // XXX why am I caching the model?
+	    pmSourceCacheModel (source, maskVal); // XXX put this in the source model function?
+	    psTrace ("psphot", 5, "psf-convolved model for source at %7.1f, %7.1f", source->moments->x, source->moments->y);
+            Nconvolve ++;
+	}
+
+	// all of these below require the radial profile
+	// XXX push this as a test and call in each of the functions below?
+	// XXX this constructs a pmSourceExtendedParameters element
+	if (doPetrosian || doIsophotal || doAnnuli || doKron) {
+	  if (!psphotRadialProfile (source, recipe, maskVal)) {
+	    psError(PSPHOT_ERR_UNKNOWN, false, "failure to generate radial profile");
+	    return false;
+	  }
+	}
+
+	if (doPetrosian && !psphotPetrosian (source, recipe, maskVal)) {
+	    psError(PSPHOT_ERR_UNKNOWN, false, "failure in Petrosian analysis");
+	    return false;
+	} else {
+	    psTrace ("psphot", 5, "measured petrosian flux & radius for source at %7.1f, %7.1f", source->moments->x, source->moments->y);
+            Npetro ++;
+	}
+
+	if (doIsophotal && !psphotIsophotal (source, recipe, maskVal)) {
+	    psError(PSPHOT_ERR_UNKNOWN, false, "failure in Isophotal analysis");
+	    return false;
+	} else {
+	    psTrace ("psphot", 5, "psf-convolved model for source at %7.1f, %7.1f", source->moments->x, source->moments->y);
+            Nisophot ++;
+	}
+
+	if (doAnnuli && !psphotAnnuli (source, recipe, maskVal)) {
+	    psError(PSPHOT_ERR_UNKNOWN, false, "failure in Annuli analysis");
+	    return false;
+	} else {
+	    psTrace ("psphot", 5, "psf-convolved model for source at %7.1f, %7.1f", source->moments->x, source->moments->y);
+            Nannuli ++;
+	}
+
+	if (doKron && !psphotKron (source, recipe, maskVal)) {
+	    psError(PSPHOT_ERR_UNKNOWN, false, "failure in Kron analysis");
+	    return false;
+	} else {
+	    psTrace ("psphot", 5, "psf-convolved model for source at %7.1f, %7.1f", source->moments->x, source->moments->y);
+            Nkron ++;
+	}
+
+        // re-subtract the object, leave local sky
+        pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+        source->mode |= PM_SOURCE_MODE_SUBTRACTED;
+    }
+
+    psLogMsg ("psphot", PS_LOG_INFO, "extended source analysis: %f sec for %d objects\n", psTimerMark ("psphot"), Next);
+    psLogMsg ("psphot", PS_LOG_INFO, "  %d convolved models\n", Nconvolve);
+    psLogMsg ("psphot", PS_LOG_INFO, "  %d petrosian\n", Npetro);
+    psLogMsg ("psphot", PS_LOG_INFO, "  %d isophotal\n", Nisophot);
+    psLogMsg ("psphot", PS_LOG_INFO, "  %d annuli\n", Nannuli);
+    psLogMsg ("psphot", PS_LOG_INFO, "  %d kron\n", Nkron);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotFakeSources.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotFakeSources.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotFakeSources.c	(revision 22157)
@@ -0,0 +1,30 @@
+# include "psphotInternal.h"
+
+psArray *psphotFakeSources () {
+
+    // psphotUpdateHeader (header, config);
+
+    psArray *sources = psArrayAlloc (50);
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = pmSourceAlloc ();
+        source->moments = pmMomentsAlloc ();
+        source->moments->x = 10;
+        source->moments->y = 10;
+        source->moments->Sx = 1;
+        source->moments->Sy = 1;
+        source->moments->Sxy = 0;
+        source->moments->Sum = 1000;
+        source->moments->Peak = 100;
+        source->moments->Sky = 10;
+        source->moments->nPixels = 10;
+
+        source->peak = pmPeakAlloc (10, 10, 0, 0);
+        source->type = PM_SOURCE_TYPE_STAR;
+
+        pmModelType modelType = pmModelClassGetType ("PS_MODEL_QGAUSS");
+        source->modelPSF = pmSourceModelGuess (source, modelType);
+        sources->data[i] = source;
+    }
+    return sources;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotFindDetections.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotFindDetections.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotFindDetections.c	(revision 22157)
@@ -0,0 +1,57 @@
+# include "psphotInternal.h"
+
+// smooth the image, search for peaks, optionally define footprints based on the peaks
+pmDetections *psphotFindDetections (pmDetections *detections, pmReadout *readout, psMetadata *recipe) {
+
+    bool status;
+    int pass;
+    float NSIGMA_PEAK = 25.0;
+    int NMAX = 0;
+
+    psTimerStart ("psphot");
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    // Use the new pmFootprints approach?
+    const bool useFootprints = psMetadataLookupBool(NULL, recipe, "USE_FOOTPRINTS");
+
+    // on first pass, detections have not yet been allocated
+    if (!detections) {
+	detections = pmDetectionsAlloc();
+	pass = 1;
+        NSIGMA_PEAK = psMetadataLookupF32 (&status, recipe, "PEAKS_NSIGMA_LIMIT"); PS_ASSERT (status, NULL);
+        NMAX = psMetadataLookupS32 (&status, recipe, "PEAKS_NMAX"); PS_ASSERT (status, NULL);
+    } else {
+	pass = 2;
+        NSIGMA_PEAK = psMetadataLookupF32 (&status, recipe, "PEAKS_NSIGMA_LIMIT_2"); PS_ASSERT (status, NULL);
+	NMAX = 0; // unlimited number of peaks in final pass: allow a limit (PEAKS_NMAX_2) ?
+    }
+
+    float threshold = PS_SQR(NSIGMA_PEAK);
+
+    // move the old peaks array (if it exists) to oldPeaks 
+    // XXX generically, we should be able to call this function an arbitrary number of times
+    assert (detections->oldPeaks == NULL);
+    detections->oldPeaks = detections->peaks;
+    detections->peaks = NULL;
+
+    // generate the smoothed significance image
+    psImage *significance = psphotSignificanceImage (readout, recipe, pass, maskVal);
+
+    // detect the peaks in the significance image
+    detections->peaks = psphotFindPeaks (significance, readout, recipe, threshold, NMAX);
+    psMetadataAddF32  (recipe, PS_LIST_TAIL, "PEAK_THRESHOLD", PS_META_REPLACE, "Peak Detection Threshold", threshold);
+
+    // optionally merge peaks into footprints
+    if (useFootprints) {
+	psphotFindFootprints (detections, significance, readout, recipe, pass, maskVal);
+    }
+
+    psFree (significance);
+    return detections;
+}
+
+// if we use the footprints, the output peaks list contains both old and new peaks,
+// otherwise it only contains the new peaks.
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotFindFootprints.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotFindFootprints.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotFindFootprints.c	(revision 22157)
@@ -0,0 +1,72 @@
+# include "psphotInternal.h"
+
+bool psphotFindFootprints (pmDetections *detections, psImage *significance, pmReadout *readout, psMetadata *recipe, const int pass, psMaskType maskVal) {
+
+    bool status;
+
+    psTimerStart ("footprints");
+
+    int npixMin = psMetadataLookupS32(&status, recipe, "FOOTPRINT_NPIXMIN");
+    PS_ASSERT (status, false);
+
+    float FOOTPRINT_NSIGMA_LIMIT;
+    if (pass == 1) {
+	FOOTPRINT_NSIGMA_LIMIT = psMetadataLookupS32(&status, recipe, "FOOTPRINT_NSIGMA_LIMIT");
+    } else {
+	FOOTPRINT_NSIGMA_LIMIT = psMetadataLookupS32(&status, recipe, "FOOTPRINT_NSIGMA_LIMIT_2");
+    }
+    PS_ASSERT (status, false);
+
+    // XXX do we need to use the same threshold here as for peaks?  does it make sense for
+    // these to be different?
+
+    float threshold = PS_SQR(FOOTPRINT_NSIGMA_LIMIT);
+
+    int growRadius = 0;
+    if (pass == 1) {
+	growRadius = psMetadataLookupS32(&status, recipe, "FOOTPRINT_GROW_RADIUS");
+    } else {
+	growRadius = psMetadataLookupS32(&status, recipe, "FOOTPRINT_GROW_RADIUS_2");
+    }
+    PS_ASSERT (status, false);
+
+    // find the raw footprints & assign the peaks to those footprints 
+    psArray *footprints = pmFootprintsFind (significance, threshold, npixMin);
+    pmFootprintsAssignPeaks(footprints, detections->peaks);
+    // XXX handle the error conditions here
+
+    // footprints now owns the peaks; after culling (below), we will rebuild the peaks array
+    psFree (detections->peaks); 
+
+    psLogMsg ("psphot", PS_LOG_DETAIL, "found %ld footprints: %f sec\n", footprints->n, psTimerMark ("footprints"));
+
+    // optionally grow footprints isotropically by growRadius pixels
+    if (growRadius > 0) {
+	psArray *tmp = pmFootprintArrayGrow(footprints, growRadius);
+	psLogMsg ("psphot", PS_LOG_DETAIL, "grow footprint coverage by %d pixels, %ld footprints become %ld footprints: %f sec\n", growRadius, footprints->n, tmp->n, psTimerMark ("footprints"));
+	psFree(footprints);
+	footprints = tmp;
+    }
+
+    if (pass == 2) {
+	// merge in old peaks;
+	const int includePeaks = 0x0 | 0x2; // i.e. just from newFootprints
+	
+	// XXX EAM : still not sure I understand this: are we double-couning or undercounting peaks
+
+	psArray *mergedFootprints = pmFootprintArraysMerge(detections->footprints, footprints, includePeaks);
+	psLogMsg ("psphot", PS_LOG_DETAIL, "merged %ld new footprints with %ld existing ones: %f sec\n", footprints->n, detections->footprints->n, psTimerMark ("footprints"));
+
+	psFree(footprints);
+	psFree(detections->footprints);
+	detections->footprints = mergedFootprints;
+    } else {
+	detections->footprints = footprints;
+    }
+
+    psphotCullPeaks(readout->image, readout->weight, recipe, detections->footprints);
+    detections->peaks = pmFootprintArrayToPeaks(detections->footprints);
+    psLogMsg ("psphot", PS_LOG_INFO, "%ld peaks, %ld total footprints: %f sec\n", detections->peaks->n, detections->footprints->n, psTimerMark ("footprints"));
+
+    return detections;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotFindPeaks.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotFindPeaks.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotFindPeaks.c	(revision 22157)
@@ -0,0 +1,54 @@
+# include "psphotInternal.h"
+
+// Find peaks in the significance image above a threshold significance level.  The significance
+// image must be constructed to represent (S/N)^2.  If nMax is non-zero, only return a maximum
+// of nMax peaks
+psArray *psphotFindPeaks (psImage *significance, pmReadout *readout, psMetadata *recipe, const float threshold, const int nMax) {
+
+    bool status = false;
+
+    psTimerStart ("peaks");
+
+    // find the peaks in the smoothed image
+    psArray *peaks = pmPeaksInImage (significance, threshold);
+    if (peaks == NULL) {
+	// XXX should we be sending back an empty array instead of NULL?
+        // XXX this may also be due to a programming or config error
+        // XXX do we need to set something in the readout->analysis to indicate that
+        // we tried and failed to find peaks (something in the header data)
+        psError(PSPHOT_ERR_DATA, false, "no peaks found in this image");
+        return false;
+    }
+
+    // correct the peak values to S/N = sqrt(significance)
+    // get the peak flux from the unsmoothed image
+    // the peak pixel coords are guaranteed to be on the image
+    int row0 = readout->image->row0;
+    int col0 = readout->image->col0;
+    for (int i = 0; i < peaks->n; i++) {
+        pmPeak *peak = peaks->data[i];
+        peak->SN = sqrt(peak->value);
+        peak->flux = readout->image->data.F32[peak->y-row0][peak->x-col0];
+    }
+
+    // limit the total number of returned peaks as specified
+    psArraySort (peaks, pmPeakSortBySN);
+    if (nMax && (peaks->n > nMax)) {
+	psArray *tmpPeaks = psArrayAllocEmpty (nMax);
+	for (int i = 0; i < nMax; i++) {
+	    psArrayAdd (tmpPeaks, 100, peaks->data[i]);
+	}
+	psFree (peaks);
+	peaks = tmpPeaks;
+    }
+
+    // optional dump of all peak data 
+    char *output = psMetadataLookupStr (&status, recipe, "PEAKS_OUTPUT_FILE");
+    if (output && strcasecmp (output, "NONE")) {
+        pmPeaksWriteText (peaks, output);
+    }
+    psLogMsg ("psphot", PS_LOG_INFO, "%ld peaks: %f sec\n", peaks->n, psTimerMark ("peaks"));
+
+    return peaks;
+
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotFitSet.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotFitSet.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotFitSet.c	(revision 22157)
@@ -0,0 +1,48 @@
+# include "psphotInternal.h"
+
+// This is only used by psphotModelTest.c
+bool psphotFitSet (pmSource *source, pmModel *oneModel, char *fitset, pmSourceFitMode mode, psMaskType maskVal) {
+
+    double x, y, Io;
+
+    FILE *f = fopen (fitset, "r");
+    if (f == NULL) return false;
+
+    psArray *modelSet = psArrayAllocEmpty (16);
+
+    while (fscanf (f, "%lf %lf %lf", &x, &y, &Io) == 3) {
+        pmModel *model = pmModelAlloc (oneModel->type);
+
+        for (psS32 i = 0; i < model->params->n; i++) {
+            model->params->data.F32[i] = oneModel->params->data.F32[i];
+            model->dparams->data.F32[i] = oneModel->dparams->data.F32[i];
+        }
+        model->params->data.F32[1] = Io;
+        model->params->data.F32[2] = x;
+        model->params->data.F32[3] = y;
+        psArrayAdd (modelSet, 16, model);
+    }
+
+    // XXX pmSourceFitSet must cache the modelFlux?
+    pmSourceFitSet (source, modelSet, mode, maskVal);
+
+    // write out positive object
+    psphotSaveImage (NULL, source->pixels, "object.fits");
+
+    // subtract object, leave local sky
+    for (int i = 0; i < modelSet->n; i++) {
+        pmModel *model = modelSet->data[i];
+        pmModelSub (source->pixels, source->maskObj, model, PM_MODEL_OP_FULL, maskVal);
+
+        fprintf (stderr, "output parameters (obj %d):\n", i);
+        for (int n = 0; n < model->params->n; n++) {
+            fprintf (stderr, "%d : %f\n", n, model->params->data.F32[n]);
+        }
+    }
+
+    // write out
+    psphotSaveImage (NULL, source->pixels, "resid.fits");
+    psphotSaveImage (NULL, source->maskObj, "mask.fits");
+    return true;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotFitSourcesLinear.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotFitSourcesLinear.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotFitSourcesLinear.c	(revision 22157)
@@ -0,0 +1,309 @@
+# include "psphotInternal.h"
+
+// fit flux (and optionally sky model) to all reasonable sources
+// with the linear fitting process.  sources must have an associated
+// model with selected pixels, and the fit radius must be defined
+
+// given the set of sources, each of which points to the pixels in the
+// science image, we construct a set of simulated sources with their own pixels.
+// these are used to determine the simultaneous linear fit of fluxes.
+// the analysis is performed wrt the simulated pixel values
+
+static bool SetBorderMatrixElements (psSparseBorder *border, pmReadout *readout, psArray *sources, bool constant_weights, int SKY_FIT_ORDER, psMaskType markVal);
+
+bool psphotFitSourcesLinear (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf, bool final) {
+
+    bool status;
+    float x;
+    float y;
+    float f;
+    // float r;
+
+    psTimerStart ("psphot");
+
+    // bit-masks to test for good/bad pixels
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT");
+    assert (maskVal);
+
+    // bit-mask to mark pixels not used in analysis
+    psMaskType markVal = psMetadataLookupU8(&status, recipe, "MARK.PSPHOT");
+    assert (markVal);
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // source analysis is done in spatial order
+    sources = psArraySort (sources, pmSourceSortByY);
+
+    // storage array for fitSources
+    psArray *fitSources = psArrayAllocEmpty (sources->n);
+
+    // option to limit analysis to a specific region
+    char *region = psMetadataLookupStr (&status, recipe, "ANALYSIS_REGION");
+    psRegion AnalysisRegion = psRegionFromString (region);
+    AnalysisRegion = psRegionForImage (readout->image, AnalysisRegion);
+    if (psRegionIsNaN (AnalysisRegion)) psAbort("analysis region mis-defined");
+
+    bool CONSTANT_PHOTOMETRIC_WEIGHTS =
+        psMetadataLookupBool(&status, recipe, "CONSTANT_PHOTOMETRIC_WEIGHTS");
+    if (!status) {
+        psAbort("You must provide a value for the BOOL recipe CONSTANT_PHOTOMETRIC_WEIGHTS");
+    }
+    int SKY_FIT_ORDER = psMetadataLookupS32(&status, recipe, "SKY_FIT_ORDER");
+    if (!status) {
+        SKY_FIT_ORDER = 0;
+    }
+    bool SKY_FIT_LINEAR = psMetadataLookupBool(&status, recipe, "SKY_FIT_LINEAR");
+    if (!status) {
+        SKY_FIT_LINEAR = false;
+    }
+
+    // select the sources which will be used for the fitting analysis
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+
+        // skip non-astronomical objects (very likely defects)
+        if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
+        if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+        if (source->type == PM_SOURCE_TYPE_STAR &&
+            source->mode & PM_SOURCE_MODE_SATSTAR) continue;
+        if (final) {
+            if (source->mode &  PM_SOURCE_MODE_SUBTRACTED) continue;
+        } else {
+            if (source->mode &  PM_SOURCE_MODE_BLEND) continue;
+        }
+
+	// generate model for sources without, or skip if we can't
+	if (!source->modelFlux) {
+	    if (!pmSourceCacheModel (source, maskVal)) continue;
+	}
+
+        // save the original coords
+        x = source->peak->xf;
+        y = source->peak->yf;
+
+        // is the source in the region of interest?
+        if (x < AnalysisRegion.x0) continue;
+        if (y < AnalysisRegion.y0) continue;
+        if (x > AnalysisRegion.x1) continue;
+        if (y > AnalysisRegion.y1) continue;
+
+        psArrayAdd (fitSources, 100, source);
+    }
+    psLogMsg ("psphot.ensemble", PS_LOG_MINUTIA, "built fitSources: %f (%ld objects)\n", psTimerMark ("psphot"), sources->n);
+
+    // vectors to store stats for each object
+    // psVector *weight = psVectorAlloc (fitSources->n, PS_TYPE_F32);
+    psVector *errors = psVectorAlloc (fitSources->n, PS_TYPE_F32);
+
+    // create the border matrix (includes the sparse matrix)
+    // for just sky: 1 row; for x,y terms: 3 rows
+    psSparse *sparse = psSparseAlloc (fitSources->n, 100);
+    int nBorder = (SKY_FIT_ORDER == 0) ? 1 : 3;
+    psSparseBorder *border = psSparseBorderAlloc (sparse, nBorder);
+
+    // fill out the sparse matrix elements and border elements (B)
+    // SRCi is the current source of interest
+    // SRCj is a possibly overlapping source
+    for (int i = 0; i < fitSources->n; i++) {
+        pmSource *SRCi = fitSources->data[i];
+
+        // diagonal elements of the sparse matrix (auto-cross-product)
+        f = pmSourceModelDotModel (SRCi, SRCi, CONSTANT_PHOTOMETRIC_WEIGHTS);
+        psSparseMatrixElement (sparse, i, i, f);
+
+        // the formal error depends on the weighting scheme
+        if (CONSTANT_PHOTOMETRIC_WEIGHTS) {
+            float var = pmSourceModelDotModel (SRCi, SRCi, false);
+            errors->data.F32[i] = 1.0 / sqrt(var);
+        } else {
+            errors->data.F32[i] = 1.0 / sqrt(f);
+        }
+
+
+        // find the image x model value
+        f = pmSourceDataDotModel (SRCi, SRCi, CONSTANT_PHOTOMETRIC_WEIGHTS);
+        psSparseVectorElement (sparse, i, f);
+
+        // add the per-source weights (border region)
+        switch (SKY_FIT_ORDER) {
+          case 1:
+            f = pmSourceModelWeight (SRCi, 1, CONSTANT_PHOTOMETRIC_WEIGHTS);
+            psSparseBorderElementB (border, i, 1, f);
+            f = pmSourceModelWeight (SRCi, 2, CONSTANT_PHOTOMETRIC_WEIGHTS);
+            psSparseBorderElementB (border, i, 2, f);
+
+          case 0:
+            f = pmSourceModelWeight (SRCi, 0, CONSTANT_PHOTOMETRIC_WEIGHTS);
+            psSparseBorderElementB (border, i, 0, f);
+            break;
+
+          default:
+            psAbort("Invalid SKY_FIT_ORDER %d\n", SKY_FIT_ORDER);
+            break;
+        }
+
+        // loop over all other stars following this one
+        for (int j = i + 1; j < fitSources->n; j++) {
+            pmSource *SRCj = fitSources->data[j];
+
+            // skip over disjoint source images, break after last possible overlap
+            if (SRCi->pixels->row0 + SRCi->pixels->numRows < SRCj->pixels->row0) break;
+            if (SRCj->pixels->row0 + SRCj->pixels->numRows < SRCi->pixels->row0) continue;
+            if (SRCi->pixels->col0 + SRCi->pixels->numCols < SRCj->pixels->col0) continue;
+            if (SRCj->pixels->col0 + SRCj->pixels->numCols < SRCi->pixels->col0) continue;
+
+            // got an overlap; calculate cross-product and add to output array
+            f = pmSourceModelDotModel (SRCi, SRCj, CONSTANT_PHOTOMETRIC_WEIGHTS);
+            psSparseMatrixElement (sparse, j, i, f);
+        }
+    }
+
+    psSparseResort (sparse);
+    psLogMsg ("psphot.ensemble", PS_LOG_MINUTIA, "built matrix: %f (%d elements)\n", psTimerMark ("psphot"), sparse->Nelem);
+
+    // set the sky, sky_x, sky_y components of border matrix
+    SetBorderMatrixElements (border, readout, fitSources, CONSTANT_PHOTOMETRIC_WEIGHTS, SKY_FIT_ORDER, markVal);
+    psLogMsg ("psphot.ensemble", PS_LOG_MINUTIA, "set border: %f (%d elements)\n", psTimerMark ("psphot"), sparse->Nelem);
+
+    psSparseConstraint constraint;
+    constraint.paramMin   = 0.0;
+    constraint.paramMax   = 1e8;
+    constraint.paramDelta = 1e8;
+
+    // solve for normalization terms (need include local sky?)
+    psVector *norm = NULL;
+    psVector *skyfit = NULL;
+    if (SKY_FIT_LINEAR) {
+        psSparseBorderSolve (&norm, &skyfit, constraint, border, 5);
+        fprintf (stderr, "skyfit: %f\n", skyfit->data.F32[0]);
+    } else {
+        norm = psSparseSolve (NULL, constraint, sparse, 5);
+        skyfit = NULL;
+    }
+    psLogMsg ("psphot.ensemble", PS_LOG_MINUTIA, "solve matrix: %f (%d elements)\n", psTimerMark ("psphot"), sparse->Nelem);
+
+    // adjust I0 for fitSources and subtract
+    for (int i = 0; i < fitSources->n; i++) {
+        pmSource *source = fitSources->data[i];
+        pmModel *model = pmSourceGetModel (NULL, source);
+
+        // assign linearly-fitted normalization
+        if (isnan(norm->data.F32[i])) {
+            psAbort("linear fitted source is nan");
+        }
+        model->params->data.F32[PM_PAR_I0] = norm->data.F32[i];
+        model->dparams->data.F32[PM_PAR_I0] = errors->data.F32[i];
+        // XXX is the value of 'errors' modified by the sky fit?
+
+        // subtract object
+        pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+        source->mode |= PM_SOURCE_MODE_SUBTRACTED;
+    }
+    psLogMsg ("psphot.ensemble", PS_LOG_MINUTIA, "sub models: %f (%d elements)\n", psTimerMark ("psphot"), sparse->Nelem);
+
+    // measure chisq for each source
+    for (int i = 0; final && (i < fitSources->n); i++) {
+        pmSource *source = fitSources->data[i];
+        pmModel *model = pmSourceGetModel (NULL, source);
+        pmSourceChisq (model, source->pixels, source->maskObj, source->weight, maskVal);
+    }
+    psLogMsg ("psphot.ensemble", PS_LOG_MINUTIA, "get chisqs: %f (%d elements)\n", psTimerMark ("psphot"), sparse->Nelem);
+
+    // psFree (index);
+    psFree (sparse);
+    psFree (fitSources);
+    psFree (norm);
+    psFree (skyfit);
+    psFree (errors);
+    psFree (border);
+
+    psLogMsg ("psphot.ensemble", PS_LOG_INFO, "measure ensemble of PSFs: %f sec\n", psTimerMark ("psphot"));
+    return true;
+}
+
+// XXX do we need this?
+
+// Calculate the weight terms for the sky fit component of the matrix.  This function operates
+// on the pixels which correspond to all of the sources of interest.  These elements fill in
+// the border matrix components in the sparse matrix equation.
+static bool SetBorderMatrixElements (psSparseBorder *border, pmReadout *readout, psArray *sources, bool constant_weights, int SKY_FIT_ORDER, psMaskType markVal) {
+
+    // generate the image-wide weight terms
+    // turn on MARK for all image pixels
+    psRegion fullArray = psRegionSet (0, 0, 0, 0);
+    fullArray = psRegionForImage (readout->mask, fullArray);
+    psImageMaskRegion (readout->mask, fullArray, "OR", markVal);
+    psLogMsg ("psphot.ensemble", PS_LOG_INFO, "step 1: %f sec\n", psTimerMark ("psphot"));
+
+    // turn off MARK for all object pixels
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        pmModel *model = pmSourceGetModel (NULL, source);
+        if (model == NULL) continue;
+        float x = model->params->data.F32[PM_PAR_XPOS];
+        float y = model->params->data.F32[PM_PAR_YPOS];
+        psImageMaskCircle (source->maskView, x, y, model->radiusFit, "AND", PS_NOT_U8(markVal));
+    }
+    psLogMsg ("psphot.ensemble", PS_LOG_INFO, "step 2: %f sec\n", psTimerMark ("psphot"));
+
+    // accumulate the image statistics from the masked regions
+    psF32 **image  = readout->image->data.F32;
+    psF32 **weight = readout->weight->data.F32;
+    psU8  **mask   = readout->mask->data.U8;
+
+    double w, x, y, x2, xy, y2, xc, yc, wt, f, fo, fx, fy;
+    w = x = y = x2 = xy = y2 = fo = fx = fy = 0;
+
+    int col0 = readout->image->col0;
+    int row0 = readout->image->row0;
+
+    for (int j = 0; j < readout->image->numRows; j++) {
+        for (int i = 0; i < readout->image->numCols; i++) {
+            if (mask[j][i]) continue;
+            if (constant_weights) {
+                wt = 1.0;
+            } else {
+                wt = weight[j][i];
+            }
+            f = image[j][i];
+            w   += 1/wt;
+            fo  += f/wt;
+            if (SKY_FIT_ORDER == 0) continue;
+
+            xc  = i + col0;
+            yc  = j + row0;
+            x  +=    xc/wt;
+            y  +=    yc/wt;
+            x2 += xc*xc/wt;
+            xy += xc*yc/wt;
+            y2 += yc*yc/wt;
+            fx +=  f*xc/wt;
+            fy +=  f*yc/wt;
+        }
+    }
+    psLogMsg ("psphot.ensemble", PS_LOG_INFO, "step 3: %f sec\n", psTimerMark ("psphot"));
+
+    // turn off MARK for all image pixels
+    psImageMaskRegion (readout->mask, fullArray, "AND", PS_NOT_U8(markVal));
+    psLogMsg ("psphot.ensemble", PS_LOG_INFO, "step 4: %f sec\n", psTimerMark ("psphot"));
+
+    // set the Border T elements
+    psSparseBorderElementG (border, 0, fo);
+    psSparseBorderElementT (border, 0, 0, w);
+    if (SKY_FIT_ORDER > 0) {
+        psSparseBorderElementG (border, 0, fx);
+        psSparseBorderElementG (border, 0, fy);
+        psSparseBorderElementT (border, 1, 0, x);
+        psSparseBorderElementT (border, 2, 0, y);
+        psSparseBorderElementT (border, 0, 1, x);
+        psSparseBorderElementT (border, 1, 1, x2);
+        psSparseBorderElementT (border, 2, 1, xy);
+        psSparseBorderElementT (border, 0, 2, y);
+        psSparseBorderElementT (border, 1, 2, xy);
+        psSparseBorderElementT (border, 2, 2, y2);
+    }
+    psLogMsg ("psphot.ensemble", PS_LOG_INFO, "step 5: %f sec\n", psTimerMark ("psphot"));
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotGuessModels.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotGuessModels.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotGuessModels.c	(revision 22157)
@@ -0,0 +1,106 @@
+# include "psphotInternal.h"
+
+// A guess for when the moments aren't available
+static pmModel *wildGuess(pmSource *source, // Source for which to guess
+                          pmPSF *psf    // The point-spread function
+    )
+{
+    pmModel *model = pmModelAlloc(psf->type);
+    psF32 *PAR = model->params->data.F32;
+    PAR[PM_PAR_SKY]  = 0;
+    // XXX get this from the image pixels
+    PAR[PM_PAR_I0]   = source->peak->flux;
+    PAR[PM_PAR_XPOS] = source->peak->xf;
+    PAR[PM_PAR_YPOS] = source->peak->yf;
+    return model;
+}
+
+// construct an initial PSF model for each object
+bool psphotGuessModels (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf) {
+
+    bool status;
+
+    psTimerStart ("psphot");
+
+    // bit-masks to test for good/bad pixels
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT");
+    assert (maskVal);
+
+    // bit-mask to mark pixels not used in analysis
+    psMaskType markVal = psMetadataLookupU8(&status, recipe, "MARK.PSPHOT");
+    assert (markVal);
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // setup the PSF fit radius details
+    psphotInitRadiusPSF (recipe, psf->type);
+
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = sources->data[i];
+
+	// skip non-astronomical objects (very likely defects)
+	if (source->type == PM_SOURCE_TYPE_DEFECT) continue;
+	if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+
+	// XXX if a source is faint, it will not have moments measured.
+	// it must be modelled as a PSF.  In this case, we need to use
+	// the peak centroid to get the coordinates and get the peak flux
+	// from the image?
+	pmModel *modelEXT;
+	if (!source->moments) {
+	    modelEXT = wildGuess(source, psf);
+	} else {
+	    // use the source moments, etc to guess basic model parameters
+	    modelEXT = pmSourceModelGuess (source, psf->type);
+	    if (!modelEXT) {
+		modelEXT = wildGuess(source, psf);
+	    }
+	    // these valuse are set in pmSourceModelGuess, should this rule be in there as well?
+	    if (source->mode &  PM_SOURCE_MODE_SATSTAR) {
+		modelEXT->params->data.F32[PM_PAR_XPOS] = source->moments->x;
+		modelEXT->params->data.F32[PM_PAR_YPOS] = source->moments->y;
+	    } else {
+		modelEXT->params->data.F32[PM_PAR_XPOS] = source->peak->xf;
+		modelEXT->params->data.F32[PM_PAR_YPOS] = source->peak->yf;
+	    }
+	}
+
+	// set PSF parameters for this model (apply 2D shape model)
+	pmModel *modelPSF = pmModelFromPSF (modelEXT, psf);
+	if (modelPSF == NULL) {
+	    psError(PSPHOT_ERR_PSF, false,
+		    "Failed to determine PSF model at r,c = (%d,%d); trying centre of image",
+		    source->peak->y, source->peak->x);
+	    //
+	    // Try the centre of the image
+	    //
+	    modelEXT->params->data.F32[PM_PAR_XPOS] = 0.5*readout->image->numCols;
+	    modelEXT->params->data.F32[PM_PAR_YPOS] = 0.5*readout->image->numRows;
+	    modelPSF = pmModelFromPSF (modelEXT, psf);
+	    if (modelPSF == NULL) {
+		psError(PSPHOT_ERR_PSF, false,
+			"Failed to determine PSF model at centre of image");
+		psFree(modelEXT);
+		return false;
+	    }
+
+	    source->mode |= PM_SOURCE_MODE_BADPSF;
+	}
+	psFree (modelEXT);
+
+	// XXX need to define the guess flux?
+	// set the fit radius based on the object flux limit and the model
+	psphotCheckRadiusPSF (readout, source, modelPSF, markVal);
+
+	// set the source PSF model
+	source->modelPSF = modelPSF;
+	source->modelPSF->residuals = psf->residuals;
+
+	pmSourceCacheModel (source, maskVal);
+    }
+    psLogMsg ("psphot.models", 4, "built models for %ld objects: %f sec\n", sources->n, psTimerMark ("psphot"));
+    return true;
+}
+
+// XXX do we always know which model is supposed to be used?
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotImageLoop.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotImageLoop.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotImageLoop.c	(revision 22157)
@@ -0,0 +1,101 @@
+# include "psphotStandAlone.h"
+
+# define ESCAPE(MESSAGE) { \
+  psError(PSPHOT_ERR_DATA, false, MESSAGE); \
+  psFree (view); \
+  return false; \
+}
+
+bool psphotImageLoop (pmConfig *config) {
+
+    bool status;
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+
+    pmFPAfile *load = psMetadataLookupPtr (&status, config->files, "PSPHOT.LOAD");
+    if (!status) {
+	psError(PSPHOT_ERR_PROG, false, "Can't find input data!");
+	return false;
+    }
+    pmFPAfile *input = psMetadataLookupPtr (&status, config->files, "PSPHOT.INPUT");
+    if (!status) {
+	psError(PSPHOT_ERR_PROG, false, "Can't find input data!");
+	return false;
+    }
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+    
+    // files associated with the science image
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE ("failed input for fpa in psphot.");
+
+    // for psphot, we force data to be read at the chip level 
+    while ((chip = pmFPAviewNextChip (view, load->fpa, 1)) != NULL) {
+        psLogMsg ("psphot", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (! chip->process || ! chip->file_exists) { continue; }
+
+	// load just the input image data (image, mask, weight)
+	pmFPAfileActivate (config->files, false, NULL);
+	pmFPAfileActivate (config->files, true, "PSPHOT.LOAD");
+	pmFPAfileActivate (config->files, true, "PSPHOT.MASK");
+	pmFPAfileActivate (config->files, true, "PSPHOT.WEIGHT");
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE ("failed input for Chip in psphot.");
+
+	// mosaic the cells of a chip into a single contiguous (trimmed) chip
+        if (!psphotMosaicChip(config, view, "PSPHOT.INPUT", "PSPHOT.LOAD")) ESCAPE ("Unable to mosaic chip.");
+
+	// try to load other supporting data (PSF, SRC, etc).
+	// do not re-load the following three files
+	pmFPAfileActivate (config->files, true, NULL);
+	pmFPAfileActivate (config->files, false, "PSPHOT.LOAD");
+	pmFPAfileActivate (config->files, false, "PSPHOT.MASK");
+	pmFPAfileActivate (config->files, false, "PSPHOT.WEIGHT");
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE ("failed input for Chip in psphot.");
+
+	// re-activate files so they will be closed and freed below
+	pmFPAfileActivate (config->files, true, NULL);
+
+	// there is now only a single chip (multiple readouts?). loop over it and process
+	while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+            psLogMsg ("psphot", 5, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+
+	    // process each of the readouts
+	    while ((readout = pmFPAviewNextReadout (view, input->fpa, 1)) != NULL) {
+		psLogMsg ("psphot", 6, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+		if (! readout->data_exists) { continue; }
+
+		// run the actual photometry analysis on this chip/cell/readout
+		if (!psphotReadout (config, view)) {
+		    psError(psErrorCodeLast(), false, "failure in psphotReadout for chip %d, cell %d, readout %d\n", view->chip, view->cell, view->readout);
+		    psFree (view);
+		    return false;
+		}
+	    }
+	}
+
+	// save output which is saved at the chip level
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE ("failed output for Chip in psphot.");
+    }
+    // save output which is saved at the fpa level
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE ("failed ouput for FPA in psphot.");
+
+    // fail if we failed to handle an error
+    if (psErrorCodeLast() != PS_ERR_NONE) psAbort ("failed to handle an error!");
+
+    psFree (view);
+    return true;
+}
+
+// I/O files related to psphot:
+// PSPHOT.INPUT   : input image file(s)
+// PSPHOT.RESID   : residual image
+// PSPHOT.OUTPUT  : output object tables (object)
+
+// PSPHOT.BACKSUB : background subtracted image
+// PSPHOT.BACKGND : background model (full-scale image?)
+// PSPHOT.BACKMDL : background model (binned image?)
+// PSPHOT.PSF     : sample PSF images
+
+// PSPHOT.MASK
+// PSPHOT.WEIGHT
+// 
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotImageMedian.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotImageMedian.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotImageMedian.c	(revision 22157)
@@ -0,0 +1,392 @@
+# include "psphotInternal.h"
+static int npass = 0;
+
+// generate the median in NxN boxes, clipping heavily
+// linear interpolation to generate full-scale model
+bool psphotImageMedian (pmConfig *config, const pmFPAview *view, psMaskType maskVal)
+{
+    bool status = true;
+    static char *defaultStatsName = "FITTED_MEAN";
+
+    pmReadout *readout = NULL;
+    pmReadout *background = NULL;
+    pmReadout *backSub = NULL;
+
+    psTimerStart ("psphot");
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+
+    // user supplied seed, if available
+    unsigned long seed = psMetadataLookupS32 (&status, recipe, "IMSTATS_SEED");
+    if (!status) {
+        seed = 0;
+    }
+    psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, seed);
+
+    // subtract this amount extra from the sky
+    float SKY_BIAS = psMetadataLookupF32 (&status, recipe, "SKY_BIAS");
+    if (!status) {
+        SKY_BIAS = 0;
+    }
+
+    // supply the sky background statistics options
+    char *statsName = psMetadataLookupStr (&status, recipe, "SKY_STAT");
+    if (statsName == NULL) {
+        statsName = defaultStatsName;
+    }
+    psStatsOptions statsOptionLocation = psStatsOptionFromString(statsName);
+    if (!(statsOptionLocation & (PS_STAT_SAMPLE_MEAN |
+                                 PS_STAT_SAMPLE_MEDIAN |
+                                 PS_STAT_ROBUST_MEDIAN |
+                                 PS_STAT_ROBUST_QUARTILE |
+                                 PS_STAT_CLIPPED_MEAN |
+                                 PS_STAT_FITTED_MEAN |
+                                 PS_STAT_FITTED_MEAN_V2 |
+                                 PS_STAT_FITTED_MEAN_V3 |
+                                 PS_STAT_FITTED_MEAN_V4))) {
+        statsOptionLocation = PS_STAT_FITTED_MEAN;
+    }
+
+    psStatsOptions statsOptionWidth = PS_STAT_NONE;
+    if (statsOptionLocation & (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_MEDIAN)) {
+        statsOptionWidth = PS_STAT_SAMPLE_STDEV;
+    } else if (statsOptionLocation & (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_QUARTILE)) {
+#if 1
+        statsOptionWidth = PS_STAT_ROBUST_STDEV; // not set; => NaN
+#else
+        statsOptionWidth = PS_STAT_FITTED_STDEV;
+#endif
+    } else if (statsOptionLocation & PS_STAT_FITTED_MEAN) {
+        statsOptionWidth = PS_STAT_FITTED_STDEV;
+    } else if (statsOptionLocation & PS_STAT_CLIPPED_MEAN) {
+        statsOptionWidth = PS_STAT_CLIPPED_STDEV;
+    } else if (statsOptionLocation & PS_STAT_FITTED_MEAN_V2) {
+        statsOptionWidth = PS_STAT_FITTED_STDEV_V2;
+    } else if (statsOptionLocation & PS_STAT_FITTED_MEAN_V3) {
+        statsOptionWidth = PS_STAT_FITTED_STDEV_V3;
+    } else if (statsOptionLocation & PS_STAT_FITTED_MEAN_V4) {
+        statsOptionWidth = PS_STAT_FITTED_STDEV_V4;
+    } else {
+        psAbort("Unable to estimate variance of selected statsOptionLocations 0x%x", statsOptionLocation);
+    }
+
+    const psStatsOptions statsOption = statsOptionLocation | statsOptionWidth;
+    psStats *stats = psStatsAlloc (statsOption);
+
+    // set range for old-version of sky statistic
+    if (statsOptionLocation & PS_STAT_ROBUST_QUARTILE) {
+        stats->min = 0.25;
+        stats->max = 0.75;
+    }
+
+    // set user-option for number of pixels per region
+    stats->nSubsample = psMetadataLookupF32 (&status, recipe, "IMSTATS_NPIX");
+    if (!status) {
+        stats->nSubsample = 1000;
+    }
+
+    // optionally set the binsize
+    stats->binsize = psMetadataLookupF32 (&status, recipe, "SKY_HISTOGRAM_BINS");
+    if (status) {
+        stats->options |= PS_STAT_USE_BINSIZE;
+    }
+
+    // optionally set the sigma clipping
+    stats->clipSigma = psMetadataLookupF32 (&status, recipe, "SKY_CLIP_SIGMA");
+    if (!status) {
+        if ((stats->options & PS_STAT_FITTED_MEAN) || (stats->options & PS_STAT_FITTED_MEAN_V2)) {
+            stats->clipSigma = 1.0;
+        } else {
+            stats->clipSigma = 3.0;
+        }
+    }
+
+    // stats is not initialized by psStats???  use this to save the input options
+    // XXX re-work this to use the new psStatsInit function
+    psStats *statsDefaults = psStatsAlloc (statsOption);
+    *statsDefaults = *stats;
+
+    // find the currently selected readout
+    pmFPAfile *file = psMetadataLookupPtr (&status, config->files, "PSPHOT.INPUT");
+    pmFPA *inFPA = file->fpa;
+    readout = pmFPAviewThisReadout (view, inFPA);
+
+    psImage *image = readout->image;
+    psImage *mask  = readout->mask;
+
+    // I have the fine image size, I know the binning factor, determine the ruff image size
+    psImageBinning *binning = psImageBinningAlloc();
+    binning->nXfine = image->numCols;
+    binning->nYfine = image->numRows;
+    binning->nXbin  = psMetadataLookupS32 (&status, recipe, "BACKGROUND.XBIN");
+    binning->nYbin  = psMetadataLookupS32 (&status, recipe, "BACKGROUND.YBIN");
+
+    psImageBinningSetRuffSize(binning, PS_IMAGE_BINNING_CENTER);
+    psImageBinningSetSkip(binning, image);
+    status = psMetadataAddPtr(recipe, PS_LIST_TAIL, "PSPHOT.BACKGROUND.BINNING", PS_DATA_UNKNOWN | PS_META_REPLACE, "Background binning", binning);
+    PS_ASSERT (status, false);
+
+    // we save the binning structure for use in psphotMagnitudes
+    pmReadout *model      = pmFPAGenerateReadout (config, view, "PSPHOT.BACKMDL",       inFPA, binning);
+    pmReadout *modelStdev = pmFPAGenerateReadout (config, view, "PSPHOT.BACKMDL.STDEV", inFPA, binning);
+
+    psF32 **modelData = model->image->data.F32;
+    psF32 **modelStdevData = modelStdev->image->data.F32;
+
+    // measure clipped median for subimages
+    psRegion ruffRegion = {0,0,0,0};
+    psRegion fineRegion = {0,0,0,0};
+    for (int iy = 0; iy < model->image->numRows; iy++) {
+        for (int ix = 0; ix < model->image->numCols; ix++) {
+
+            // convert the ruff grid cell to the equivalent fine grid cell
+            // XXX we need to watch out for row0,col0
+            ruffRegion = psRegionSet (ix, ix + 2, iy, iy + 2);
+            fineRegion = psImageBinningSetFineRegion (binning, ruffRegion);
+            fineRegion = psRegionForImage (image, fineRegion);
+
+            psImage *subset  = psImageSubset (image, fineRegion);
+            if (!subset->numCols || !subset->numRows) {
+                psFree (subset);
+                continue;
+            }
+            psImage *submask = psImageSubset (mask, fineRegion);
+
+            // reset the default values
+            *stats = *statsDefaults;
+
+            // Use the selected background statistic for the first pass
+            // If it fails, fall back on the "ROBUST_MEDIAN" version
+            // If both fail, set the pixel to NAN and (below) interpolate
+            // XXX psImageBackground will probably be renamed psImageStats
+            // XXX don't bother trying if there are no valid pixels...
+
+	    psVector *sample = NULL;
+
+	    // turn on stats tracing in desired cells
+	    # if (0)
+	    psMetadata *plots = psMetadataLookupPtr (&status, recipe, "DIAGNOSTIC.PLOTS");
+	    assert (plots);
+	    
+	    int xPlot = psMetadataLookupS32 (&status, plots, "IMAGE.BACKGROUND.CELL.HISTOGRAM.X");
+	    assert (status);
+	    int yPlot = psMetadataLookupS32 (&status, plots, "IMAGE.BACKGROUND.CELL.HISTOGRAM.Y");
+	    assert (status);
+
+	    bool gotX = (xPlot < 0) || (xPlot == ix);
+	    bool gotY = (yPlot < 0) || (yPlot == iy);
+
+	    if (gotX && gotY) {
+		psTraceSetLevel ("psLib.math.vectorFittedStats_v4", 6);
+		psTraceSetLevel ("psLib.math.vectorRobustStats", 6);
+	    } else {
+		psTraceSetLevel ("psLib.math.vectorFittedStats_v4", 0);
+		psTraceSetLevel ("psLib.math.vectorRobustStats", 0);
+	    }	    
+	    # endif
+
+            if (psImageBackground(stats, &sample, subset, submask, maskVal, rng)) {
+                if (stats->options & PS_STAT_ROBUST_QUARTILE) {
+                    modelData[iy][ix] = stats->robustMedian;
+                } else {
+                    modelData[iy][ix] = psStatsGetValue(stats, statsOptionLocation);
+                }
+                modelStdevData[iy][ix] = psStatsGetValue(stats, statsOptionWidth);
+
+		// supply sample to plotting routing
+		psphotDiagnosticPlots (config, "IMAGE.BACKGROUND.CELL.HISTOGRAM", ix, iy, modelData[iy][ix], modelStdevData[iy][ix], sample);
+		psFree (sample);
+            } else {
+                psStatsOptions currentOptions = stats->options;
+                stats->options = PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV;
+                if (!psImageBackground(stats, &sample, subset, submask, maskVal, rng)) {
+                    psLogMsg ("psphot", PS_LOG_WARN, "Failed to estimate background using ROBUST_MEDIAN for "
+                               "(%dx%d, (row0,col0) = (%d,%d)",
+                               subset->numRows, subset->numCols, subset->row0, subset->col0);
+                    modelData[iy][ix] = modelStdevData[iy][ix] = NAN;
+                } else {
+                    modelData[iy][ix] = psStatsGetValue (stats, PS_STAT_ROBUST_MEDIAN);
+                    modelStdevData[iy][ix] = psStatsGetValue(stats, PS_STAT_ROBUST_STDEV);
+
+		    // supply sample to plotting routing
+		    psphotDiagnosticPlots (config, "IMAGE.BACKGROUND.CELL.HISTOGRAM", ix, iy, modelData[iy][ix], modelStdevData[iy][ix], sample);
+		    psFree (sample);
+                }
+                // drop errors caused by psImageBackground failures
+                // XXX we probably should trap and exit on serious failures
+                psErrorClear();
+                stats->options = currentOptions;
+            }
+            modelData[iy][ix] += SKY_BIAS;
+            psFree (subset);
+            psFree (submask);
+        }
+    }
+
+    if (psTraceGetLevel("psphot") > 5) {
+        char name[256];
+        sprintf (name, "backraw.%02d.fits", npass);
+        psphotSaveImage (NULL, model->image, name);
+    }
+
+    // patch over bad regions (use average of 8 possible neighbor pixels)
+    // XXX consider testing all pixels against the 8 neighbors and replacing outliers...
+    double Count = 0;                   // number of good pixels
+    double Value = 0;                   // sum of good pixel's value
+    double ValueStdev = 0;              // sum of good pixel's standard deviations
+    for (int iy = 0; iy < model->image->numRows; iy++) {
+        for (int ix = 0; ix < model->image->numCols; ix++) {
+            if (!isnan(modelData[iy][ix])) {
+                Value += modelData[iy][ix];
+                ValueStdev += modelStdevData[iy][ix];
+                Count++;
+                continue;
+            }
+
+            double value = 0;
+            double count = 0;
+            for (int jy = iy - 1; jy <= iy + 1; jy++) {
+                if (jy <   0) continue;
+                if (jy >= model->image->numRows) continue;
+                for (int jx = ix - 1; jx <= ix + 1; jx++) {
+                    if (!jx && !jy) continue;
+                    if (jx   <   0) continue;
+                    if (jx   >= model->image->numCols) continue;
+                    value += modelData[jy][jx];
+                    count += 1.0;
+                }
+            }
+            if (count > 0) modelData[iy][ix] = value / count;
+        }
+    }
+    if (Count == 0) {
+        psError (PSPHOT_ERR_DATA, true, "failed to build background image");
+	psFree(stats);
+	psFree(statsDefaults);
+	psFree(binning);
+	psFree(rng);
+        return false;
+    }
+
+    Value /= Count;
+    ValueStdev /= Count;
+
+    // patch over remaining bad regions (use global average)
+    for (int iy = 0; iy < model->image->numRows; iy++) {
+        for (int ix = 0; ix < model->image->numCols; ix++) {
+            if (!isnan(modelData[iy][ix])) continue;
+            modelData[iy][ix] = Value;
+            modelStdevData[iy][ix] = ValueStdev;
+        }
+    }
+
+    psLogMsg ("psphot", PS_LOG_MINUTIA, "build median image: %f sec\n", psTimerMark ("psphot"));
+
+    psMetadataAddF32(recipe, PS_LIST_TAIL, "SKY_MEAN", PS_META_REPLACE, "sky mean", Value);
+    psMetadataAddF32(recipe, PS_LIST_TAIL, "SKY_STDEV", PS_META_REPLACE, "sky stdev", ValueStdev);
+    psLogMsg ("psphot", PS_LOG_INFO, "image sky : mean %f stdev %f", Value, ValueStdev);
+
+    // measure image and background stats and save for later output
+    psStats *statsBck = psStatsAlloc (PS_STAT_SAMPLE_MEAN |
+                                      PS_STAT_SAMPLE_STDEV |
+                                      PS_STAT_MIN |
+                                      PS_STAT_MAX);
+    psImageStats (statsBck, model->image, NULL, 0);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "MSKY_MN",
+                      PS_META_REPLACE, "sky model mean",          statsBck->sampleMean);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "MSKY_SIG",
+                      PS_META_REPLACE, "sky model stdev",         statsBck->sampleStdev);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "MSKY_MAX",
+                      PS_META_REPLACE, "sky model maximum value", statsBck->max);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "MSKY_MIN",
+                      PS_META_REPLACE, "sky model minimum value", statsBck->min);
+    psMetadataAddS32 (recipe, PS_LIST_TAIL, "MSKY_NX",
+                      PS_META_REPLACE, "sky model size (x)",      model->image->numCols);
+    psMetadataAddS32 (recipe, PS_LIST_TAIL, "MSKY_NY",
+                      PS_META_REPLACE, "sky model size (y)",      model->image->numRows);
+    psLogMsg ("psphot", PS_LOG_INFO, "background sky : min %f mean %f max %f stdev %f",
+              statsBck->min, statsBck->sampleMean, statsBck->max, statsBck->sampleStdev);
+    psFree (statsBck);
+
+    // select background pixels, from output background file, or create
+    file = psMetadataLookupPtr (&status, config->files, "PSPHOT.BACKGND");
+    if (file) {
+        // we are using PSPHOT.BACKGND as an I/O file: select readout or create
+        if (file->mode == PM_FPA_MODE_INTERNAL) {
+            background = file->readout;
+        } else {
+            background = pmFPAviewThisReadout (view, file->fpa);
+        }
+        if (background == NULL) {
+            // readout does not yet exist: create from input
+            pmFPAfileCopyStructureView (file->fpa, inFPA, 1, 1, view);
+            background = pmFPAviewThisReadout (view, file->fpa);
+            if ((image->numCols != background->image->numCols) || (image->numRows != background->image->numRows)) {
+                psError (PSPHOT_ERR_PROG, true, "inconsistent sizes for background dimensions");
+                return false;
+            }
+        }
+    } else {
+        background = pmFPAfileDefineInternal (config->files, "PSPHOT.BACKGND", image->numCols, image->numRows, PS_TYPE_F32);
+    }
+    psF32 **backData = background->image->data.F32;
+
+    // linear interpolation to full-scale
+    if (!psImageUnbin (background->image, model->image, binning)) {
+        psError (PSPHOT_ERR_PROG, true, "inconsistent sizes for unbinning");
+        return false;
+    }
+
+    psLogMsg ("psphot", PS_LOG_MINUTIA, "build resampled image: %f sec\n", psTimerMark ("psphot"));
+
+    // back-sub image pixels, from output background file (don't create if not requested)
+    file = psMetadataLookupPtr (&status, config->files, "PSPHOT.BACKSUB");
+    if (file) {
+        // we are using PSPHOT.BACKSUB as an I/O file: select readout or create
+        backSub = pmFPAviewThisReadout (view, file->fpa);
+        if (backSub == NULL) {
+            // readout does not yet exist: create from input
+            pmFPAfileCopyStructureView (file->fpa, inFPA, 1, 1, view);
+            backSub = pmFPAviewThisReadout (view, file->fpa);
+        }
+    }
+
+    if (psTraceGetLevel("psphot") > 5) {
+        char name[256];
+        sprintf (name, "image.%02d.fits", npass);
+        psphotSaveImage (NULL, image, name);
+        sprintf (name, "back.%02d.fits", npass);
+        psphotSaveImage (NULL, background->image, name);
+        sprintf (name, "mask.%02d.fits", npass);
+        psphotSaveImage (NULL, mask, name);
+        sprintf (name, "backmdl.%02d.fits", npass);
+        psphotSaveImage (NULL, model->image, name);
+    }
+
+    // subtract the background model (save in backSub, if requested)
+    for (int j = 0; j < image->numRows; j++) {
+        for (int i = 0; i < image->numCols; i++) {
+            image->data.F32[j][i] -= backData[j][i];
+            if (backSub) {
+                backSub->image->data.F32[j][i] = image->data.F32[j][i];
+            }
+        }
+    }
+
+    if (psTraceGetLevel("psphot") > 5) {
+        char name[256];
+        sprintf (name, "backsub.%02d.fits", npass);
+        psphotSaveImage (NULL, image, name);
+    }
+    npass ++;
+
+    psLogMsg ("psphot", PS_LOG_INFO, "subtracted background model: %f sec\n", psTimerMark ("psphot"));
+    psFree(stats);
+    psFree(statsDefaults);
+    psFree(binning);
+    psFree(rng);
+
+    // the pmReadout selected in this function are all view on entries in config->files
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotInternal.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotInternal.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotInternal.h	(revision 22157)
@@ -0,0 +1,17 @@
+/* this file is included by the internal, library functions. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifndef PSPHOT_INTERNAL_H
+#define PSPHOT_INTERNAL_H
+
+#include <stdio.h>
+#include <strings.h>  // for strcasecmp
+#include <unistd.h>   // for unlink
+#include <pslib.h>
+#include <psmodules.h>
+#include "psphot.h"
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotIsophotal.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotIsophotal.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotIsophotal.c	(revision 22157)
@@ -0,0 +1,78 @@
+# include "psphotInternal.h"
+
+bool psphotIsophotal (pmSource *source, psMetadata *recipe, psMaskType maskVal) {
+
+  assert (source->extpars);
+  assert (source->extpars->profile);
+  assert (source->extpars->profile->radius);
+  assert (source->extpars->profile->flux);
+
+  bool status;
+
+  psVector *radius = source->extpars->profile->radius;
+  psVector *flux = source->extpars->profile->flux;
+
+  // flux at which to measure isophotal parameters
+  // XXX ISOPHOTAL_FLUX should be specified in mags, need the zero point to get counts/sec
+  float ISOPHOT_FLUX = psMetadataLookupF32 (&status, recipe, "ISOPHOTAL_FLUX");
+  assert (status);
+
+  // find the first bin below the flux level and the last above the level
+  // XXX can this be done faster with bisection?
+  // XXX do I need to worry about crazy outliers?
+  // XXX should i be smoothing or fitting the curve?
+  int firstBelow = -1;
+  int lastAbove = -1;
+  for (int i = 0; i < flux->n; i++) {
+    if (flux->data.F32[i] > ISOPHOT_FLUX) lastAbove = i;
+    if ((firstBelow < 0) && (flux->data.F32[i] < ISOPHOT_FLUX)) firstBelow = i;
+  }
+  // if we don't go out far enough, we have a problem...
+  if (lastAbove == flux->n - 1) {
+    psTrace ("psphot", 5, "did not go out far enough to reach isophotal magnitude");
+    // XXX raise a flag ?
+    return false;
+  }
+  if (firstBelow < 0) {
+    psTrace ("psphot", 5, "did not go out far enough to bound isophotal magnitude: error unmeasured");
+    // XXX raise a flag ?
+    lastAbove = firstBelow;
+    return false;
+  }
+
+  // need to examine pixels in this vicinity
+  float isophotalFluxFirst = 0;
+  float isophotalFluxLast = 0;
+  for (int i = 0; i <= PS_MAX(firstBelow, lastAbove); i++) {
+    if (i <= firstBelow) {
+      isophotalFluxFirst += flux->data.F32[i];
+    }
+    if (i <= lastAbove) {
+      isophotalFluxLast += flux->data.F32[i];
+    }
+  }
+  float isophotalFlux    = 0.5*(isophotalFluxLast + isophotalFluxFirst);
+  float isophotalFluxErr = 0.5*fabs(isophotalFluxLast - isophotalFluxFirst);
+
+  float isophotalRad     = 0.5*(radius->data.F32[firstBelow] + radius->data.F32[lastAbove]);
+  float isophotalRadErr  = 0.5*fabs(radius->data.F32[firstBelow] - radius->data.F32[lastAbove]);
+
+  if (!source->extpars->isophot) {
+    source->extpars->isophot = pmSourceIsophotalValuesAlloc ();
+  }
+
+  // these are uncalibrated: instrumental mags and pixel units
+  source->extpars->isophot->mag    = -2.5*log10(isophotalFlux);
+  source->extpars->isophot->magErr = isophotalFluxErr / isophotalFlux;
+
+  source->extpars->isophot->rad    = isophotalRad;
+  source->extpars->isophot->radErr = isophotalRadErr;
+
+  psTrace ("psphot", 5, "Isophot flux:%f +/- %f @ %f +/- %f for %f, %f\n",
+           source->extpars->isophot->mag, source->extpars->isophot->magErr,
+           source->extpars->isophot->rad, source->extpars->isophot->radErr,
+           source->peak->xf, source->peak->yf);
+
+  return true;
+
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotKernelFromPSF.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotKernelFromPSF.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotKernelFromPSF.c	(revision 22157)
@@ -0,0 +1,45 @@
+# include "psphotInternal.h"
+
+psKernel *psphotKernelFromPSF (pmSource *source, int nPix) {
+
+    assert (source);
+    assert (source->psfFlux); // XXX build if needed?
+
+    int x0 = source->peak->xf - source->psfFlux->col0;
+    int y0 = source->peak->yf - source->psfFlux->row0;
+
+    // need to decide on the size: dynamically? statically?
+    psKernel *psf = psKernelAlloc (-nPix, +nPix, -nPix, +nPix);
+
+    // XXX we should just re-construct a PSF at this location 
+    // psModelAdd (psf->image, NULL, source->modelPSF, PM_MODEL_OP_FULL | PM_MODEL_OP_NORM | PM_MODEL_OP_CENTER);
+  
+    // if the realized PSF for this object does not cover the full kernel, give up for now
+    if (x0 + psf->xMin < 0) goto escape;
+    if (x0 + psf->xMax >= source->psfFlux->numCols) goto escape;
+    if (y0 + psf->yMin < 0) goto escape;
+    if (y0 + psf->yMax >= source->psfFlux->numRows) goto escape;
+
+    double sum = 0.0;
+    for (int j = psf->yMin; j <= psf->yMax; j++) {
+	for (int i = psf->xMin; i <= psf->xMax; i++) {
+	    double value = source->psfFlux->data.F32[y0 + j][x0 + i];
+	    psf->kernel[j][i] = value;
+	    sum += value;
+	}
+    }
+    assert (sum > 0.0);
+
+    // psf must be normalized (integral = 1.0)
+    for (int i = 0; i < psf->image->numRows; i++) {
+	for (int j = 0; j < psf->image->numCols; j++) {
+	    psf->image->data.F32[i][j] /= sum;
+	}
+    }
+
+    return psf;
+
+escape:
+    psFree (psf);
+    return NULL;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotKron.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotKron.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotKron.c	(revision 22157)
@@ -0,0 +1,8 @@
+# include "psphotInternal.h"
+
+bool psphotKron (pmSource *source, psMetadata *recipe, psMaskType maskVal) {
+
+  psLogMsg ("psphot", PS_LOG_INFO, "not implemented\n");
+  return true;
+
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotLoadPSF.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotLoadPSF.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotLoadPSF.c	(revision 22157)
@@ -0,0 +1,28 @@
+# include "psphotInternal.h"
+
+// load an externally supplied psf model
+pmPSF *psphotLoadPSF (pmConfig *config, const pmFPAview *view, psMetadata *recipe) {
+
+    // find the currently selected chip
+    pmChip *chip = pmFPAfileThisChip (config->files, view, "PSPHOT.PSF.LOAD");
+    if (!chip) return NULL;
+
+    // find the currently selected readout
+    pmReadout *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.PSF.LOAD");
+    if (!readout) return NULL;
+
+    // check if a PSF model is supplied by the user
+    pmPSF *psf = psMetadataLookupPtr (NULL, chip->analysis, "PSPHOT.PSF");
+    if (psf == NULL) {
+        psLogMsg ("psphot", 3, "no psf supplied for this chip");
+        return NULL;
+    }
+
+    if (!psphotPSFstats (readout, recipe, psf)) psAbort("cannot measure PSF shape terms");
+
+    psLogMsg ("psphot", 3, "using externally supplied PSF model for this readout");
+
+    // we return a psf which can be free (and which may be removed from the metadata)
+    psMemIncrRefCounter (psf);
+    return psf;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotMagnitudes.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotMagnitudes.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotMagnitudes.c	(revision 22157)
@@ -0,0 +1,56 @@
+# include "psphotInternal.h"
+
+bool psphotMagnitudes(psArray *sources, psMetadata *recipe, pmPSF *psf, pmReadout *background) {
+
+    bool status = false;
+    int Nap = 0;
+
+    psTimerStart ("psphot");
+
+    // bit-masks to test for good/bad pixels
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT");
+    assert (maskVal);
+
+    // bit-mask to mark pixels not used in analysis
+    psMaskType markVal = psMetadataLookupU8(&status, recipe, "MARK.PSPHOT");
+    assert (markVal);
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    pmSourceMagnitudesInit (recipe);
+
+    // XXX require that we have a background model, or
+    // allow it to be missing, setting local sky to 0.0?
+    PS_ASSERT (background, false);
+
+    // the binning details are saved on the analysis metadata
+    psImageBinning *binning = psMetadataLookupPtr(&status, recipe, "PSPHOT.BACKGROUND.BINNING");
+    PS_ASSERT (status, false);
+
+    bool IGNORE_GROWTH = psMetadataLookupBool (&status, recipe, "IGNORE_GROWTH");
+    bool INTERPOLATE_AP = psMetadataLookupBool (&status, recipe, "INTERPOLATE_AP");
+
+    pmSourcePhotometryMode photMode = PM_SOURCE_PHOT_APCORR | PM_SOURCE_PHOT_WEIGHT;
+    if (!IGNORE_GROWTH) photMode |= PM_SOURCE_PHOT_GROWTH;
+    if (INTERPOLATE_AP) photMode |= PM_SOURCE_PHOT_INTERP;
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+        status = pmSourceMagnitudes (source, psf, photMode, maskVal);
+        if (status) Nap ++;
+
+        source->sky = psImageUnbinPixel(source->peak->x, source->peak->y, background->image, binning);
+        if (isnan(source->sky) && false) {
+          psError(PSPHOT_ERR_SKY, false, "Setting pmSource.sky");
+          psErrorStackPrint(NULL, " ");
+          psErrorClear();
+        }
+    }
+
+    psLogMsg ("psphot.magnitudes", PS_LOG_DETAIL, "measure magnitudes : %f sec for %ld objects (%d with apertures)\n", psTimerMark ("psphot"), sources->n, Nap);
+    return true;
+}
+
+// XXX add in a measurement of the bright and faint completeness values
+// XXX push these into the recipe
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotMakeFluxScale.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotMakeFluxScale.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotMakeFluxScale.c	(revision 22157)
@@ -0,0 +1,63 @@
+# include "psphotInternal.h"
+
+bool psphotMakeFluxScale (psImage *image, psMetadata *recipe, pmPSF *psf) {
+
+    bool status = false;
+
+    psTimerStart ("residuals");
+
+    int Nx = psMetadataLookupS32(&status, recipe, "PSF.FLUXSCALE.NX");
+    int Ny = psMetadataLookupS32(&status, recipe, "PSF.FLUXSCALE.NY");
+
+    // stats structure for use by ApTrend : XXX make parameters user setable
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV);
+
+    pmTrend2D *trend = pmTrend2DAlloc (PM_TREND_MAP, image, Nx, Ny, stats);
+    
+    psVector *xPts = psVectorAlloc (Nx*Ny, PS_TYPE_F32);
+    psVector *yPts = psVectorAlloc (Nx*Ny, PS_TYPE_F32);
+    psVector *fPts = psVectorAlloc (Nx*Ny, PS_TYPE_F32);
+
+    int Npts = 0;
+
+    // generate a set of test normalized PSF fluxes filling the grid 
+    for (int ix = 0; ix < Nx; ix++) {
+	for (int iy = 0; iy < Ny; iy++) {
+	    
+	    float x = psImageBinningGetFineX (trend->map->binning, ix + 0.5);
+	    float y = psImageBinningGetFineY (trend->map->binning, iy + 0.5);
+
+	    // create normalized model object at xc,yc 
+	    pmModel *model = pmModelFromPSFforXY (psf, x, y, 1.0);
+
+	    // measure the fitMag for this model
+	    float fitSum = model->modelFlux (model->params);
+	    assert (fitSum > 0);
+	    assert (isfinite(fitSum));
+
+	    xPts->data.F32[Npts] = x;
+	    yPts->data.F32[Npts] = y;
+	    fPts->data.F32[Npts] = fitSum;
+	    Npts ++;
+	    assert (Npts <= xPts->nalloc);
+	    psFree (model);
+	}
+    }
+    xPts->n = Npts;
+    yPts->n = Npts;
+    fPts->n = Npts;
+    
+    // XXX test for errors here
+    pmTrend2DFit (trend, NULL, 0xff, xPts, yPts, fPts, NULL);
+    
+    // XXX do something useful to measure residual statistics
+
+    psf->FluxScale = trend;
+
+    psFree (xPts);
+    psFree (yPts);
+    psFree (fPts);
+    psFree (stats);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotMakeResiduals.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotMakeResiduals.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotMakeResiduals.c	(revision 22157)
@@ -0,0 +1,278 @@
+# include "psphotInternal.h"
+
+bool psphotMakeResiduals (psArray *sources, psMetadata *recipe, pmPSF *psf, psMaskType maskVal) {
+
+    bool status, isPSF;
+    double flux, dflux;
+    psU8 mflux;
+
+    psTimerStart ("residuals");
+
+    if (!psMetadataLookupBool(&status, recipe, "PSF.RESIDUALS")) return true;
+
+    int SPATIAL_ORDER = psMetadataLookupS32(&status, recipe, "PSF.RESIDUALS.SPATIAL_ORDER");
+    PS_ASSERT (status, false);
+    if (SPATIAL_ORDER != 0 && SPATIAL_ORDER != 1) {
+        psError(PSPHOT_ERR_CONFIG, true, "PSF.RESIDUALS.SPATIAL_ORDER must be 0 or 1 (not %d)",
+                SPATIAL_ORDER);
+        return false;
+    }
+
+    int xBin = psMetadataLookupS32(&status, recipe, "PSF.RESIDUALS.XBIN");
+    PS_ASSERT (status, false);
+    PS_ASSERT (xBin != 0, false);
+
+    int yBin = psMetadataLookupS32(&status, recipe, "PSF.RESIDUALS.YBIN");
+    PS_ASSERT (status, false);
+    PS_ASSERT (yBin != 0, false);
+
+    float nSigma = psMetadataLookupF32(&status, recipe, "PSF.RESIDUALS.NSIGMA");
+    PS_ASSERT (status, false);
+
+    float pixelSN = psMetadataLookupF32(&status, recipe, "PSF.RESIDUALS.PIX.SN");
+    PS_ASSERT (status, false);
+
+    char *modeString = psMetadataLookupStr(&status, recipe, "PSF.RESIDUALS.INTERPOLATION");
+    PS_ASSERT (status, false);
+
+    psImageInterpolateMode mode = psImageInterpolateModeFromString (modeString);
+    if (mode == PS_INTERPOLATE_NONE) {
+        psError(PSPHOT_ERR_CONFIG, false, "invalid interpolation in psphot.config");
+        return false;
+    }
+
+    char *statString = psMetadataLookupStr(&status, recipe, "PSF.RESIDUALS.STATISTIC");
+    PS_ASSERT (status, false);
+
+    psStatsOptions statOption = psStatsOptionFromString (statString);
+    if (!statOption) {
+        psError(PSPHOT_ERR_CONFIG, false, "invalid residual statistic in psphot.config");
+        return false;
+    }
+
+    // user parameters:
+    // size of aperture (determine from source images?)
+    // binning factor
+
+    // select the subset of sources which are the PSFSTARs
+    // for each input source:
+    // - construct a residual image, renormalized
+    // - construct a renormalized weight image
+    // - construct a new mask image
+
+    // construct the output residual table (Nx*DX,Ny*DY)
+    // for each output pixel:
+    // - construct a histogram of the values & weights (interpolate to the common pixel coordinate)
+    // - measure the robust median & sigma
+    // - reject (mask) input pixels which are outliers
+    // - re-measure the robust median & sigma
+    // - set output pixel, weight, and mask
+
+    const int badMask = 1;              // mask bits
+    const int poorMask = 2;             //       from psImageInterpolate
+    const int clippedMask = 4;          // mask bit set for clipped values
+
+    bool offImage = false;              // pixel is off the image
+
+    // determine the maximum image size from the input sources
+    int xSize = 0;
+    int ySize = 0;
+
+    psVector *xC = psVectorAllocEmpty (100, PS_TYPE_F32);
+    psVector *yC = psVectorAllocEmpty (100, PS_TYPE_F32);
+
+    // build (DATA - MODEL) [an image] for each psf star
+    psArray *input = psArrayAllocEmpty (100);
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+
+        if (!(source->mode & PM_SOURCE_MODE_PSFSTAR)) continue;
+
+        // which model to use?
+        pmModel *model = pmSourceGetModel (&isPSF, source);
+        if (model == NULL) continue;  // model must be defined
+
+        psImage *image  = psImageCopy (NULL, source->pixels,   PS_TYPE_F32);
+        psImage *weight = psImageCopy (NULL, source->weight,   PS_TYPE_F32);
+        psImage *mask   = psImageCopy (NULL, source->maskView, PS_TYPE_U8);
+        pmModelSub (image, mask, model, PM_MODEL_OP_FUNC, maskVal);
+
+        // re-normalize image and weight
+        float Io = model->params->data.F32[PM_PAR_I0];
+        psBinaryOp (image, image, "/", psScalarAlloc(Io, PS_TYPE_F32));
+        psBinaryOp (weight, weight, "/", psScalarAlloc(Io*Io, PS_TYPE_F32));
+
+        // we will interpolate the image and weight - include the mask or not?
+        // XXX consider better values for the mask bits
+        psImageInterpolateOptions *interp =
+            psImageInterpolateOptionsAlloc(mode, image, weight, NULL, 0xff, 0.0, 0.0, badMask, poorMask, 0.0);
+        psArrayAdd (input,  100, interp);
+
+        // save the X,Y position for future reference
+        xC->data.F32[xC->n] = model->params->data.F32[PM_PAR_XPOS];
+        yC->data.F32[yC->n] = model->params->data.F32[PM_PAR_YPOS];
+        psVectorExtend (xC, 100, 1);
+        psVectorExtend (yC, 100, 1);
+
+        xSize = PS_MAX (xSize, image->numCols);
+        ySize = PS_MAX (ySize, image->numRows);
+
+        // free up the excess references
+        psFree (mask);
+        psFree (image);
+        psFree (weight);
+        psFree (interp);
+    }
+    pmResiduals *resid = pmResidualsAlloc (xSize, ySize, xBin, yBin);
+
+    // x(resid) = (x(image) - Xo)*xBin + xCenter
+
+    psVector *fluxes  = psVectorAlloc (input->n, PS_TYPE_F32);
+    psVector *dfluxes = psVectorAlloc (input->n, PS_TYPE_F32);
+    psVector *fmasks  = psVectorAlloc (input->n, PS_TYPE_U8);
+
+    // statistic to use to determine baseline for clipping
+    psStats *fluxClip     = psStatsAlloc (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
+    psStats *fluxClipDef  = psStatsAlloc (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
+    // statistic to use to determine output flux
+    // XXX make API to convert statOption for MEAN/MEDIAN in to corresponding STDEV?
+    psStats *fluxStats    = psStatsAlloc (statOption | PS_STAT_SAMPLE_STDEV);
+    psStats *fluxStatsDef = psStatsAlloc (statOption | PS_STAT_SAMPLE_STDEV);
+
+    // Use psF64 to minimize overflow problems?
+    psImage *A = psImageAlloc(3, 3, PS_TYPE_F64); // Least-squares matrix
+    psVector *B = psVectorAlloc(3, PS_TYPE_F64); // Least-squares vector
+
+    // Solve MODEL = R + x R_x + y R_y in pixel-by-pixel a least-squares sense
+    // (If SPATIAL_ORDER == 0, just solve MODEL = R)
+    for (int oy = 0; resid != NULL && oy < resid->Ro->numRows; oy++) {
+        for (int ox = 0; ox < resid->Ro->numCols; ox++) {
+
+            // build the vector of data values for this output pixel
+            for (int i = 0; i < input->n; i++) {
+
+                psImageInterpolateOptions *interp = input->data[i];
+
+                // fractional image position
+                float ix = (ox + 0.5 - resid->xCenter) / (float) xBin + xC->data.F32[i] - interp->image->col0;
+                float iy = (oy + 0.5 - resid->yCenter) / (float) yBin + yC->data.F32[i] - interp->image->row0;
+
+                mflux = 0;
+                offImage = false;
+                if (psImageInterpolate (&flux, &dflux, &mflux, ix, iy, interp) == PS_INTERPOLATE_STATUS_OFF) {
+                    // This pixel is off the image
+                    offImage = true;
+                }
+                fluxes->data.F32[i] = flux;
+                dfluxes->data.F32[i] = dflux;
+                fmasks->data.U8[i] = mflux;
+		if (isnan(flux)) { 
+		    fmasks->data.U8[i] = 1;
+		}
+                // fprintf (stderr, "%f %f : %f %f (%d)\n", ix, iy, flux, dflux, fmasks->data.U8[i]);
+            }
+
+            // measure the robust median to determine a baseline reference value
+            *fluxClip = *fluxClipDef;
+            psVectorStats (fluxClip, fluxes, NULL, fmasks, 0xff);
+            psErrorClear();             // clear (ignore) any outstanding errors
+
+            // mark input pixels which are more than N sigma from the median
+            for (int i = 0; i < fluxes->n; i++) {
+                float delta = fluxes->data.F32[i] - fluxClip->robustMedian;
+                float sigma = sqrt (dfluxes->data.F32[i]);
+                float swing = fabs(delta) / sigma;
+
+                // make this a user option
+                if (swing > nSigma) {
+                    fmasks->data.U8[i] = clippedMask;
+                }
+            }
+
+            if (offImage || SPATIAL_ORDER == 0) {
+                // measure the desired statistic on the unclipped pixels
+                *fluxStats = *fluxStatsDef;
+                psVectorStats (fluxStats, fluxes, NULL, fmasks, 0xff);
+                psErrorClear();         // clear (ignore) any outstanding errors
+
+                resid->Ro->data.F32[oy][ox] = psStatsGetValue(fluxStats, statOption);
+                resid->Rx->data.F32[oy][ox] = resid->Ry->data.F32[oy][ox] = 0.0;
+                //resid->weight->data.F32[oy][ox] = fluxStats->sampleStdev;
+
+		if (resid->Ro->data.F32[oy][ox] < pixelSN*fluxStats->sampleStdev) {
+		  resid->mask->data.U8[oy][ox] = 1;
+		}
+
+            } else {
+                assert (SPATIAL_ORDER == 1);
+                psImageInit(A, 0.0);
+                psVectorInit(B, 0.0);
+                for (int i = 0; i < fluxes->n; i++) {
+                    if (fmasks->data.U8[i]) continue;
+                    B->data.F64[0] += fluxes->data.F32[i]/dfluxes->data.F32[i];
+                    B->data.F64[1] += fluxes->data.F32[i]*xC->data.F32[i]/dfluxes->data.F32[i];
+                    B->data.F64[2] += fluxes->data.F32[i]*yC->data.F32[i]/dfluxes->data.F32[i];
+
+                    A->data.F64[0][0] += 1.0/dfluxes->data.F32[i];
+                    A->data.F64[1][0] += xC->data.F32[i]/dfluxes->data.F32[i];
+                    A->data.F64[2][0] += yC->data.F32[i]/dfluxes->data.F32[i];
+
+                    A->data.F64[1][1] += PS_SQR(xC->data.F32[i])/dfluxes->data.F32[i];
+                    A->data.F64[2][2] += PS_SQR(yC->data.F32[i])/dfluxes->data.F32[i];
+                    A->data.F64[1][2] += xC->data.F32[i]*yC->data.F32[i]/dfluxes->data.F32[i];
+                }
+
+                A->data.F64[0][1] = A->data.F64[1][0];
+                A->data.F64[0][2] = A->data.F64[2][0];
+                A->data.F64[2][1] = A->data.F64[1][2];
+
+                if (!psMatrixGJSolve(A, B)) {
+                    psError(PSPHOT_ERR_PSF, false, "Singular matrix solving for (y,x) = (%d,%d)'s residuals",
+                            oy, ox);
+                    psFree(resid); resid = NULL;
+                    break;
+                }
+
+                resid->Ro->data.F32[oy][ox] = B->data.F64[0];
+                resid->Rx->data.F32[oy][ox] = B->data.F64[1];
+                resid->Ry->data.F32[oy][ox] = B->data.F64[2];
+
+		float dRo = sqrt(A->data.F32[0][0]);
+		if (resid->Ro->data.F32[oy][ox] < pixelSN*dRo) {
+		  resid->mask->data.U8[oy][ox] = 1;
+		}
+                //resid->weight->data.F32[oy][ox] = XXX;
+            }
+        }
+    }
+
+    psFree (A);
+    psFree (B);
+
+    psLogMsg ("psphot.pspsf", PS_LOG_INFO, "generate residuals for %ld objects: %f sec\n", input->n, psTimerMark ("residuals"));
+
+    psFree (xC);
+    psFree (yC);
+    psFree (input);
+
+    psFree (fluxes);
+    psFree (dfluxes);
+    psFree (fmasks);
+
+    psFree (fluxStats);
+    psFree (fluxStatsDef);
+    psFree (fluxClip);
+    psFree (fluxClipDef);
+
+    if (resid != NULL && psTraceGetLevel("psphot") > 5) {
+      psphotSaveImage (NULL, resid->Ro,     "resid.ro.fits");
+      psphotSaveImage (NULL, resid->Rx,     "resid.rx.fits");
+      psphotSaveImage (NULL, resid->Ry,     "resid.ry.fits");
+      psphotSaveImage (NULL, resid->weight, "resid.wt.fits");
+      psphotSaveImage (NULL, resid->mask,   "resid.mk.fits");
+    }
+
+    psf->residuals = resid;
+    return (resid != NULL) ? true : false;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotMaskReadout.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotMaskReadout.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotMaskReadout.c	(revision 22157)
@@ -0,0 +1,54 @@
+# include "psphotInternal.h"
+
+// generate mask and weight if not defined, additional mask for restricted subregion 
+bool psphotSetMaskAndWeight (pmConfig *config, pmReadout *readout, psMetadata *recipe) {
+
+    bool status;
+
+    // ** Interpret the mask values:
+    // XXX drop the write to recipe and move config into psphotRoughClass?
+    psMaskType maskSat  = pmConfigMaskGet("SAT", config); // Mask value for saturated pixels
+    psMetadataAddU8 (recipe, PS_LIST_TAIL, "MASK.SAT", PS_META_REPLACE, "user-defined mask", maskSat);
+
+    psMaskType maskBad  = pmConfigMaskGet("BAD", config); // Mask value for bad pixels
+    psMetadataAddU8 (recipe, PS_LIST_TAIL, "MASK.BAD", PS_META_REPLACE, "user-defined mask", maskBad);
+
+    // generate mask & weight images if they don't already exit
+    if (!readout->mask) {
+        if (!pmReadoutGenerateMask(readout, maskSat, maskBad)) {
+            psError (PSPHOT_ERR_CONFIG, false, "trouble creating mask");
+            return false;
+        }
+    }
+    if (!readout->weight) {
+        if (!pmReadoutGenerateWeight(readout, true)) {
+            psError (PSPHOT_ERR_CONFIG, false, "trouble creating weight");
+            return false;
+        }
+    }
+
+    // mask the excluded outer pixels
+    // these coordinates refer to the parent image
+    // these bounds will saturate on the subimage
+    // negative upper bounds will subtract from the *subimage*
+    float XMIN  = psMetadataLookupF32 (&status, recipe, "XMIN");
+    float XMAX  = psMetadataLookupF32 (&status, recipe, "XMAX");
+    float YMIN  = psMetadataLookupF32 (&status, recipe, "YMIN");
+    float YMAX  = psMetadataLookupF32 (&status, recipe, "YMAX");
+    psRegion valid = psRegionSet (XMIN, XMAX, YMIN, YMAX);
+
+    // restrict the supplied region above to the valid area on the image
+    psRegion keep = psRegionForImage (readout->image, valid);
+
+    // psImageKeepRegion assumes the region refers to the parent coordinates
+    psImageKeepRegion (readout->mask, keep, "OR", maskBad);
+
+    // test output of files at this stage
+    if (psTraceGetLevel("psphot") >= 5) {
+        psphotSaveImage (NULL, readout->image,  "image.fits");
+        psphotSaveImage (NULL, readout->mask,   "mask.fits");
+        psphotSaveImage (NULL, readout->weight, "weight.fits");
+    }
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotMergeSources.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotMergeSources.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotMergeSources.c	(revision 22157)
@@ -0,0 +1,52 @@
+# include "psphotInternal.h"
+
+// add newly selected sources to the existing list of sources
+bool psphotMergeSources (psArray *oldSources, psArray *newSources) {
+
+    for (int i = 0; i < newSources->n; i++) {
+        pmSource *source = newSources->data[i];
+        psArrayAdd (oldSources, 100, source);
+    }
+    return true;
+}
+
+// merge the externally supplied sources with the existing sources.  mark them as having
+// mode PM_SOURCE_MODE_EXTERNAL
+bool psphotLoadExtSources (pmConfig *config, const pmFPAview *view, psArray *sources) {
+
+    // find the currently selected readout
+    pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT.CMF");
+    if (!readout) {
+        psLogMsg ("psphot", 3, "no external sources supplied");
+        return true;
+    }
+
+    psArray *extSources = psMetadataLookupPtr (NULL, readout->analysis, "PSPHOT.SOURCES");
+    if (!extSources) {
+        psLogMsg ("psphot", 3, "no external sources for this readout");
+        return true;
+    }
+
+    for (int i = 0; i < extSources->n; i++) {
+        pmSource *source = extSources->data[i];
+        source->mode |= PM_SOURCE_MODE_EXTERNAL;
+        pmModel *model = source->modelPSF;
+
+        float xpos = model->params->data.F32[PM_PAR_XPOS];
+        float ypos = model->params->data.F32[PM_PAR_YPOS];
+
+        source->peak = pmPeakAlloc(xpos, ypos, 1.0, PM_PEAK_LONE);
+        source->peak->xf = xpos;
+        source->peak->yf = ypos;
+        source->peak->flux = 1.0;
+
+        // drop the loaded source modelPSF
+        psFree (source->modelPSF);
+        source->modelPSF = NULL;
+    }
+
+    psphotMergeSources (sources, extSources);
+    psLogMsg ("psphot", 3, "%ld external sources merged to yield %ld total sources", extSources->n, sources->n);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotModelBackground.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotModelBackground.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotModelBackground.c	(revision 22157)
@@ -0,0 +1,318 @@
+# include "psphotInternal.h"
+static int npass = 0;
+
+// generate the median in NxN boxes, clipping heavily
+// linear interpolation to generate full-scale model
+bool psphotModelBackground (pmConfig *config, const pmFPAview *view, const char *filename)
+{
+    bool status = true;
+    static char *defaultStatsName = "FITTED_MEAN";
+
+    psTimerStart ("psphot");
+
+    // find the currently selected readout
+    pmFPAfile *file = psMetadataLookupPtr (&status, config->files, filename);
+    pmFPA *inFPA = file->fpa;
+    pmReadout *readout = pmFPAviewThisReadout (view, inFPA);
+    psImage *image = readout->image;
+    psImage *mask  = readout->mask;
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    assert (recipe);
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    // user supplied seed, if available
+    unsigned long seed = psMetadataLookupS32 (&status, recipe, "IMSTATS_SEED");
+    if (!status) {
+        seed = 0;
+    }
+    psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, seed);
+
+    // subtract this amount extra from the sky
+    float SKY_BIAS = psMetadataLookupF32 (&status, recipe, "SKY_BIAS");
+    if (!status) {
+        SKY_BIAS = 0;
+    }
+
+    // supply the sky background statistics options
+    char *statsName = psMetadataLookupStr (&status, recipe, "SKY_STAT");
+    if (statsName == NULL) {
+        statsName = defaultStatsName;
+    }
+    psStatsOptions statsOptionLocation = psStatsOptionFromString(statsName);
+    if (!(statsOptionLocation & (PS_STAT_SAMPLE_MEAN |
+                                 PS_STAT_SAMPLE_MEDIAN |
+                                 PS_STAT_ROBUST_MEDIAN |
+                                 PS_STAT_ROBUST_QUARTILE |
+                                 PS_STAT_CLIPPED_MEAN |
+                                 PS_STAT_FITTED_MEAN |
+                                 PS_STAT_FITTED_MEAN_V2 |
+                                 PS_STAT_FITTED_MEAN_V3 |
+                                 PS_STAT_FITTED_MEAN_V4))) {
+        statsOptionLocation = PS_STAT_FITTED_MEAN;
+    }
+
+    psStatsOptions statsOptionWidth = PS_STAT_NONE;
+    if (statsOptionLocation & (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_MEDIAN)) {
+        statsOptionWidth = PS_STAT_SAMPLE_STDEV;
+    } else if (statsOptionLocation & (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_QUARTILE)) {
+#if 1
+        statsOptionWidth = PS_STAT_ROBUST_STDEV; // not set; => NaN
+#else
+        statsOptionWidth = PS_STAT_FITTED_STDEV;
+#endif
+    } else if (statsOptionLocation & PS_STAT_FITTED_MEAN) {
+        statsOptionWidth = PS_STAT_FITTED_STDEV;
+    } else if (statsOptionLocation & PS_STAT_CLIPPED_MEAN) {
+        statsOptionWidth = PS_STAT_CLIPPED_STDEV;
+    } else if (statsOptionLocation & PS_STAT_FITTED_MEAN_V2) {
+        statsOptionWidth = PS_STAT_FITTED_STDEV_V2;
+    } else if (statsOptionLocation & PS_STAT_FITTED_MEAN_V3) {
+        statsOptionWidth = PS_STAT_FITTED_STDEV_V3;
+    } else if (statsOptionLocation & PS_STAT_FITTED_MEAN_V4) {
+        statsOptionWidth = PS_STAT_FITTED_STDEV_V4;
+    } else {
+        psAbort("Unable to estimate variance of selected statsOptionLocations 0x%x", statsOptionLocation);
+    }
+
+    const psStatsOptions statsOption = statsOptionLocation | statsOptionWidth;
+    psStats *stats = psStatsAlloc (statsOption);
+
+    // set range for old-version of sky statistic
+    if (statsOptionLocation & PS_STAT_ROBUST_QUARTILE) {
+        stats->min = 0.25;
+        stats->max = 0.75;
+    }
+
+    // set user-option for number of pixels per region
+    stats->nSubsample = psMetadataLookupF32 (&status, recipe, "IMSTATS_NPIX");
+    if (!status) {
+        stats->nSubsample = 1000;
+    }
+
+    // optionally set the binsize
+    stats->binsize = psMetadataLookupF32 (&status, recipe, "SKY_HISTOGRAM_BINS");
+    if (status) {
+        stats->options |= PS_STAT_USE_BINSIZE;
+    }
+
+    // optionally set the sigma clipping
+    stats->clipSigma = psMetadataLookupF32 (&status, recipe, "SKY_CLIP_SIGMA");
+    if (!status) {
+        if ((stats->options & PS_STAT_FITTED_MEAN) || (stats->options & PS_STAT_FITTED_MEAN_V2)) {
+            stats->clipSigma = 1.0;
+        } else {
+            stats->clipSigma = 3.0;
+        }
+    }
+
+    // stats is not initialized by psStats???  use this to save the input options
+    // XXX re-work this to use the new psStatsInit function
+    psStats *statsDefaults = psStatsAlloc (statsOption);
+    *statsDefaults = *stats;
+
+    // I have the fine image size, I know the binning factor, determine the ruff image size
+    psImageBinning *binning = psImageBinningAlloc();
+    binning->nXfine = image->numCols;
+    binning->nYfine = image->numRows;
+    binning->nXbin  = psMetadataLookupS32 (&status, recipe, "BACKGROUND.XBIN");
+    binning->nYbin  = psMetadataLookupS32 (&status, recipe, "BACKGROUND.YBIN");
+
+    psImageBinningSetRuffSize(binning, PS_IMAGE_BINNING_CENTER);
+    psImageBinningSetSkip(binning, image);
+    status = psMetadataAddPtr(recipe, PS_LIST_TAIL, "PSPHOT.BACKGROUND.BINNING", PS_DATA_UNKNOWN | PS_META_REPLACE, "Background binning", binning);
+    PS_ASSERT (status, false);
+
+    // we save the binning structure for use in psphotMagnitudes
+    pmReadout *model      = pmFPAGenerateReadout (config, view, "PSPHOT.BACKMDL",       inFPA, binning);
+    pmReadout *modelStdev = pmFPAGenerateReadout (config, view, "PSPHOT.BACKMDL.STDEV", inFPA, binning);
+
+    psF32 **modelData = model->image->data.F32;
+    psF32 **modelStdevData = modelStdev->image->data.F32;
+
+    // measure clipped median for subimages
+    psRegion ruffRegion = {0,0,0,0};
+    psRegion fineRegion = {0,0,0,0};
+    for (int iy = 0; iy < model->image->numRows; iy++) {
+        for (int ix = 0; ix < model->image->numCols; ix++) {
+
+            // convert the ruff grid cell to the equivalent fine grid cell
+            // XXX we need to watch out for row0,col0
+            ruffRegion = psRegionSet (ix, ix + 2, iy, iy + 2);
+            fineRegion = psImageBinningSetFineRegion (binning, ruffRegion);
+            fineRegion = psRegionForImage (image, fineRegion);
+
+            psImage *subset  = psImageSubset (image, fineRegion);
+            if (!subset->numCols || !subset->numRows) {
+                psFree (subset);
+                continue;
+            }
+            psImage *submask = psImageSubset (mask, fineRegion);
+
+            // reset the default values
+            *stats = *statsDefaults;
+
+            // Use the selected background statistic for the first pass
+            // If it fails, fall back on the "ROBUST_MEDIAN" version
+            // If both fail, set the pixel to NAN and (below) interpolate
+            // XXX psImageBackground will probably be renamed psImageStats
+            // XXX don't bother trying if there are no valid pixels...
+
+            psVector *sample = NULL;
+
+            // turn on stats tracing in desired cells
+            # if (0)
+            psMetadata *plots = psMetadataLookupPtr (&status, recipe, "DIAGNOSTIC.PLOTS");
+            assert (plots);
+
+            int xPlot = psMetadataLookupS32 (&status, plots, "IMAGE.BACKGROUND.CELL.HISTOGRAM.X");
+            assert (status);
+            int yPlot = psMetadataLookupS32 (&status, plots, "IMAGE.BACKGROUND.CELL.HISTOGRAM.Y");
+            assert (status);
+
+            bool gotX = (xPlot < 0) || (xPlot == ix);
+            bool gotY = (yPlot < 0) || (yPlot == iy);
+
+            if (gotX && gotY) {
+                psTraceSetLevel ("psLib.math.vectorFittedStats_v4", 6);
+                psTraceSetLevel ("psLib.math.vectorRobustStats", 6);
+            } else {
+                psTraceSetLevel ("psLib.math.vectorFittedStats_v4", 0);
+                psTraceSetLevel ("psLib.math.vectorRobustStats", 0);
+            }
+            # endif
+
+            if (psImageBackground(stats, &sample, subset, submask, maskVal, rng)) {
+                if (stats->options & PS_STAT_ROBUST_QUARTILE) {
+                    modelData[iy][ix] = stats->robustMedian;
+                } else {
+                    modelData[iy][ix] = psStatsGetValue(stats, statsOptionLocation);
+                }
+                modelStdevData[iy][ix] = psStatsGetValue(stats, statsOptionWidth);
+
+                // supply sample to plotting routing
+                psphotDiagnosticPlots (config, "IMAGE.BACKGROUND.CELL.HISTOGRAM", ix, iy, modelData[iy][ix], modelStdevData[iy][ix], sample);
+            } else {
+                psStatsOptions currentOptions = stats->options;
+                stats->options = PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV;
+                if (!psImageBackground(stats, &sample, subset, submask, maskVal, rng)) {
+                    psLogMsg ("psphot", PS_LOG_WARN, "Failed to estimate background using ROBUST_MEDIAN for "
+                               "(%dx%d, (row0,col0) = (%d,%d)",
+                               subset->numRows, subset->numCols, subset->row0, subset->col0);
+                    modelData[iy][ix] = modelStdevData[iy][ix] = NAN;
+                } else {
+                    modelData[iy][ix] = psStatsGetValue (stats, PS_STAT_ROBUST_MEDIAN);
+                    modelStdevData[iy][ix] = psStatsGetValue(stats, PS_STAT_ROBUST_STDEV);
+
+                    // supply sample to plotting routing
+                    psphotDiagnosticPlots (config, "IMAGE.BACKGROUND.CELL.HISTOGRAM", ix, iy, modelData[iy][ix], modelStdevData[iy][ix], sample);
+                }
+                // drop errors caused by psImageBackground failures
+                // XXX we probably should trap and exit on serious failures
+                psErrorClear();
+                stats->options = currentOptions;
+            }
+            psFree(sample);
+            modelData[iy][ix] += SKY_BIAS;
+            psFree (subset);
+            psFree (submask);
+        }
+    }
+
+    if (psTraceGetLevel("psphot") > 5) {
+        char name[256];
+        sprintf (name, "backraw.%02d.fits", npass);
+        psphotSaveImage (NULL, model->image, name);
+    }
+
+    // patch over bad regions (use average of 8 possible neighbor pixels)
+    // XXX consider testing all pixels against the 8 neighbors and replacing outliers...
+    double Count = 0;                   // number of good pixels
+    double Value = 0;                   // sum of good pixel's value
+    double ValueStdev = 0;              // sum of good pixel's standard deviations
+    for (int iy = 0; iy < model->image->numRows; iy++) {
+        for (int ix = 0; ix < model->image->numCols; ix++) {
+            if (!isnan(modelData[iy][ix])) {
+                Value += modelData[iy][ix];
+                ValueStdev += modelStdevData[iy][ix];
+                Count++;
+                continue;
+            }
+
+            double value = 0;
+            double count = 0;
+            for (int jy = iy - 1; jy <= iy + 1; jy++) {
+                if (jy <   0) continue;
+                if (jy >= model->image->numRows) continue;
+                for (int jx = ix - 1; jx <= ix + 1; jx++) {
+                    if (!jx && !jy) continue;
+                    if (jx   <   0) continue;
+                    if (jx   >= model->image->numCols) continue;
+                    value += modelData[jy][jx];
+                    count += 1.0;
+                }
+            }
+            if (count > 0) modelData[iy][ix] = value / count;
+        }
+    }
+    if (Count == 0) {
+        psError (PSPHOT_ERR_DATA, true, "failed to build background image");
+        psFree(stats);
+        psFree(statsDefaults);
+        psFree(binning);
+        psFree(rng);
+        return false;
+    }
+
+    Value /= Count;
+    ValueStdev /= Count;
+
+    // patch over remaining bad regions (use global average)
+    for (int iy = 0; iy < model->image->numRows; iy++) {
+        for (int ix = 0; ix < model->image->numCols; ix++) {
+            if (!isnan(modelData[iy][ix])) continue;
+            modelData[iy][ix] = Value;
+            modelStdevData[iy][ix] = ValueStdev;
+        }
+    }
+
+    psLogMsg ("psphot", PS_LOG_MINUTIA, "build median image: %f sec\n", psTimerMark ("psphot"));
+
+    psMetadataAddF32(recipe, PS_LIST_TAIL, "SKY_MEAN", PS_META_REPLACE, "sky mean", Value);
+    psMetadataAddF32(recipe, PS_LIST_TAIL, "SKY_STDEV", PS_META_REPLACE, "sky stdev", ValueStdev);
+    psLogMsg ("psphot", PS_LOG_INFO, "image sky : mean %f stdev %f", Value, ValueStdev);
+
+    // measure image and background stats and save for later output
+    psStats *statsBck = psStatsAlloc (PS_STAT_SAMPLE_MEAN |
+                                      PS_STAT_SAMPLE_STDEV |
+                                      PS_STAT_MIN |
+                                      PS_STAT_MAX);
+    psImageStats (statsBck, model->image, NULL, 0);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "MSKY_MN",
+                      PS_META_REPLACE, "sky model mean",          statsBck->sampleMean);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "MSKY_SIG",
+                      PS_META_REPLACE, "sky model stdev",         statsBck->sampleStdev);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "MSKY_MAX",
+                      PS_META_REPLACE, "sky model maximum value", statsBck->max);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "MSKY_MIN",
+                      PS_META_REPLACE, "sky model minimum value", statsBck->min);
+    psMetadataAddS32 (recipe, PS_LIST_TAIL, "MSKY_NX",
+                      PS_META_REPLACE, "sky model size (x)",      model->image->numCols);
+    psMetadataAddS32 (recipe, PS_LIST_TAIL, "MSKY_NY",
+                      PS_META_REPLACE, "sky model size (y)",      model->image->numRows);
+    psLogMsg ("psphot", PS_LOG_INFO, "background sky : min %f mean %f max %f stdev %f",
+              statsBck->min, statsBck->sampleMean, statsBck->max, statsBck->sampleStdev);
+
+    psFree(stats);
+    psFree(statsBck);
+    psFree(statsDefaults);
+    psFree(binning);
+    psFree(rng);
+
+    npass ++;
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotModelGroupInit.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotModelGroupInit.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotModelGroupInit.c	(revision 22157)
@@ -0,0 +1,25 @@
+# include "psphotInternal.h"
+
+// Add locally-defined models here.  As these mature, they can be moved to 
+// psModule/src/objects/models
+
+# include "models/pmModel_TEST1.c"
+# include "models/pmModel_STRAIL.c"
+
+static pmModelClass userModels[] = {
+    {"PS_MODEL_TEST1", 7, pmModelFunc_TEST1,  pmModelFlux_TEST1,  pmModelRadius_TEST1,  pmModelLimits_TEST1,  pmModelGuess_TEST1, pmModelFromPSF_TEST1, pmModelParamsFromPSF_TEST1, pmModelFitStatus_TEST1},
+    {"PS_MODEL_STRAIL", 9, pmModelFunc_STRAIL,  pmModelFlux_STRAIL,  pmModelRadius_STRAIL,  pmModelLimits_STRAIL,  pmModelGuess_STRAIL, pmModelFromPSF_STRAIL, pmModelParamsFromPSF_STRAIL, pmModelFitStatus_STRAIL},
+};
+
+void psphotModelClassInit (void) 
+{ 
+
+    // if pmModelClassInit returns false, we have already init'ed
+    if (!pmModelClassInit ()) return;
+
+    int Nmodels = sizeof (userModels) / sizeof (pmModelClass);
+    for (int i = 0; i < Nmodels; i++) {
+	pmModelClassAdd (&userModels[i]);
+    }
+    return;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotModelTest.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotModelTest.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotModelTest.c	(revision 22157)
@@ -0,0 +1,250 @@
+# include "psphotInternal.h"
+# define PM_SOURCE_FIT_PSF_X_EXT PM_SOURCE_FIT_PSF_AND_SKY
+
+// XXX add more test information?
+bool psphotModelTest (pmConfig *config, const pmFPAview *view, psMetadata *recipe) {
+
+    bool status;
+    int modelType = -1;
+    float obsMag, fitMag, value;
+    char name[64];
+    pmPSF *psf = NULL;
+    pmSourceFitMode fitMode;
+
+    // bit-masks to test for good/bad pixels
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT");
+    assert (maskVal);
+
+    // bit-mask to mark pixels not used in analysis
+    psMaskType markVal = psMetadataLookupU8(&status, recipe, "MARK.PSPHOT");
+    assert (markVal);
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // run model fitting tests on a single source?
+    if (!psMetadataLookupBool (&status, recipe, "TEST_FIT")) return false;
+
+    psTimerStart ("modelTest");
+
+    // find the currently selected readout
+    pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
+    PS_ASSERT_PTR_NON_NULL (readout, false);
+
+    // use poissonian errors or local-sky errors
+    bool POISSON_ERRORS = psMetadataLookupBool (&status, recipe, "POISSON_ERRORS");
+    if (!status) POISSON_ERRORS = true;
+    pmSourceFitModelInit (15, 0.1, 1.0, POISSON_ERRORS);
+
+    // find the various fitting parameters (try test values first)
+    float INNER = psMetadataLookupF32 (&status, recipe, "TEST_FIT_INNER_RADIUS");
+    if (!status || !isfinite(INNER)) {
+        INNER = psMetadataLookupF32 (&status, recipe, "SKY_INNER_RADIUS");
+    }
+    float OUTER = psMetadataLookupF32 (&status, recipe, "TEST_FIT_OUTER_RADIUS");
+    if (!status || !isfinite(OUTER)) {
+        OUTER = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
+    }
+    float RADIUS = psMetadataLookupF32 (&status, recipe, "TEST_FIT_RADIUS");
+    if (!status || !isfinite(RADIUS)) {
+        RADIUS = psMetadataLookupF32 (&status, recipe, "PSF_FIT_RADIUS");
+    }
+    float mRADIUS = psMetadataLookupF32 (&status, recipe, "TEST_MOMENTS_RADIUS");
+    if (!status || !isfinite(mRADIUS)) {
+        mRADIUS = psMetadataLookupF32 (&status, recipe, "PSF_MOMENTS_RADIUS");
+    }
+
+    // define the source of interest
+    float xObj     = psMetadataLookupF32 (&status, recipe, "TEST_FIT_X");
+    float yObj     = psMetadataLookupF32 (&status, recipe, "TEST_FIT_Y");
+    if (!isfinite(xObj) || !isfinite(yObj)) psAbort ("object position is not defined");
+
+    // what fitting mode to use?
+    fitMode = PM_SOURCE_FIT_EXT;
+    char *fitModeWord = psMetadataLookupStr (&status, recipe, "TEST_FIT_MODE");
+    if (fitModeWord && !strcasecmp (fitModeWord, "PSF")) fitMode = PM_SOURCE_FIT_PSF;
+    if (fitModeWord && !strcasecmp (fitModeWord, "CONV")) fitMode = PM_SOURCE_FIT_PSF_X_EXT;
+    if (fitModeWord && !strcasecmp (fitModeWord, "DEFAULT")) fitMode = PM_SOURCE_FIT_EXT;
+
+    // construct the source structures
+    pmSource *source = pmSourceAlloc();
+    source->peak = pmPeakAlloc (xObj, yObj, 0, 0);
+    pmSourceDefinePixels (source, readout, xObj, yObj, OUTER);
+
+    // in fitMode, psf sets the model type
+    if (fitMode == PM_SOURCE_FIT_PSF) {
+        psf = psphotLoadPSF (config, view, recipe);
+        if (!psf) psAbort("PSF_INPUT_FILE not supplied");
+        modelType = psf->type;
+        source->type = PM_SOURCE_TYPE_STAR;
+    }
+    if (fitMode == PM_SOURCE_FIT_EXT) {
+        // find the model: supplied by user or first in the PSF_MODEL list
+        char *modelName  = psMetadataLookupStr (&status, recipe, "TEST_FIT_MODEL");
+        if (!status || !strcasecmp (modelName, "DEFAULT")) {
+            // get the list pointers for the PSF_MODEL entries
+
+            psList *list = NULL;
+            psMetadataItem *mdi = psMetadataLookup (recipe, "PSF_MODEL");
+            if (mdi == NULL) psAbort("missing PSF_MODEL selection");
+            if (mdi->type == PS_DATA_STRING) {
+                list = psListAlloc(NULL);
+                psListAdd (list, PS_LIST_HEAD, mdi);
+            } else {
+                if (mdi->type != PS_DATA_METADATA_MULTI) psAbort("missing PSF_MODEL selection");
+                list = psMemIncrRefCounter(mdi->data.list);
+            }
+
+            // take the first list element
+            psMetadataItem *item = psListGet (list, PS_LIST_HEAD);
+            modelName = item->data.V;
+        }
+        modelType = pmModelClassGetType (modelName);
+        if (modelType < 0) psAbort("unknown model %s", modelName);
+        source->type = PM_SOURCE_TYPE_EXTENDED;
+    }
+    if (fitMode == PM_SOURCE_FIT_PSF_X_EXT) {
+        // we need to load BOTH a psf and an ext model
+        psf = psphotLoadPSF (config, view, recipe);
+        if (!psf) psAbort("PSF_INPUT_FILE not supplied");
+
+        // find the model: supplied by user or first in the PSF_MODEL list
+        char *modelName  = psMetadataLookupStr (&status, recipe, "TEST_FIT_MODEL");
+        if (!status || !strcasecmp (modelName, "DEFAULT")) {
+            // get the list pointers for the PSF_MODEL entries
+
+            psList *list = NULL;
+            psMetadataItem *mdi = psMetadataLookup (recipe, "PSF_MODEL");
+            if (mdi == NULL) psAbort("missing PSF_MODEL selection");
+            if (mdi->type == PS_DATA_STRING) {
+                list = psListAlloc(NULL);
+                psListAdd (list, PS_LIST_HEAD, mdi);
+            } else {
+                if (mdi->type != PS_DATA_METADATA_MULTI) psAbort("missing PSF_MODEL selection");
+                list = psMemIncrRefCounter(mdi->data.list);
+            }
+
+            // take the first list element
+            psMetadataItem *item = psListGet (list, PS_LIST_HEAD);
+            modelName = item->data.V;
+        }
+        modelType = pmModelClassGetType (modelName);
+        if (modelType < 0) psAbort("unknown model %s", modelName);
+        source->type = PM_SOURCE_TYPE_EXTENDED;
+    }
+
+    // find the local sky
+    status = pmSourceLocalSky (source, PS_STAT_SAMPLE_MEDIAN, INNER, maskVal, markVal);
+    if (!status) psAbort("pmSourceLocalSky error");
+
+    // get the source moments
+    status = pmSourceMoments (source, mRADIUS);
+    if (!status) psAbort("psSourceMoments error");
+    source->peak->value = source->moments->Peak;
+
+    fprintf (stderr, "sum: %f @ (%f, %f)\n", source->moments->Sum, source->moments->x, source->moments->y);
+    fprintf (stderr, "moments: %f, %f - %f\n", source->moments->Sx, source->moments->Sy, source->moments->Sxy);
+
+    psEllipseMoments moments;
+    moments.x2 = source->moments->Sx;
+    moments.y2 = source->moments->Sy;
+    moments.xy = source->moments->Sxy;
+    psEllipseAxes axes = psEllipseMomentsToAxes (moments, 20.0);
+
+    fprintf (stderr, "axes: %f @ (%f, %f)\n", axes.theta*180/M_PI, axes.major, axes.minor);
+
+    // get the initial model parameter guess
+    pmModel *model = pmSourceModelGuess (source, modelType);
+    source->modelEXT = model;
+
+    // if any parameters are defined by the user, take those values
+    int nParams = pmModelClassParameterCount (modelType);
+    psF32 *params = model->params->data.F32;
+    params[PM_PAR_XPOS] = xObj; // XXX use the user-supplied value,
+    params[PM_PAR_YPOS] = yObj; // XXX or use the centroid
+    for (int i = 0; i < nParams; i++) {
+        if (i == PM_PAR_XPOS) continue;
+        if (i == PM_PAR_YPOS) continue;
+
+        sprintf (name, "TEST_FIT_PAR%d", i);
+        value = psMetadataLookupF32 (&status, recipe, name);
+        if (status && isfinite (value)) {
+            params[i] = value;
+        }
+    }
+
+    float area = params[4]*params[5];
+    fprintf (stderr, "peak: %f @ (%f, %f)\n", source->moments->Sum*area, (double)source->peak->x, (double)source->peak->y);
+
+    // for PSF fitting, set the shape parameters based on the PSF & source position
+    if (fitMode == PM_SOURCE_FIT_PSF) {
+        source->modelPSF = pmModelFromPSF (model, psf);
+        psFree (model);
+        model = source->modelPSF;
+        params = model->params->data.F32;
+    }
+
+    // list model input shape
+    psEllipseShape shape;
+    shape.sx  = 1.4 / model->params->data.F32[4];
+    shape.sy  = 1.4 / model->params->data.F32[5];
+    shape.sxy = model->params->data.F32[6];
+    axes = psEllipseShapeToAxes (shape, 20.0);
+
+    fprintf (stderr, "guess: %f @ (%f, %f)\n", axes.theta*180/M_PI, axes.major, axes.minor);
+
+    fprintf (stderr, "input parameters: \n");
+    for (int i = 0; i < nParams; i++) {
+        fprintf (stderr, "%d : %f\n", i, params[i]);
+    }
+
+    // define the pixels used for the fit
+    psImageKeepCircle (source->maskObj, xObj, yObj, RADIUS, "OR", markVal);
+    psphotSaveImage (NULL, source->maskObj, "mask1.fits");
+
+    char *fitset = psMetadataLookupStr (&status, recipe, "TEST_FIT_SET");
+    if (status) {
+        status = psphotFitSet (source, model, fitset, fitMode, maskVal);
+        exit (0);
+    }
+
+    if (fitMode == PM_SOURCE_FIT_PSF_X_EXT) {
+        // build the psf for the object
+        source->modelPSF = pmModelFromPSF (model, psf);
+        source->modelEXT = model;
+
+	// what fraction of the PSF is used? (radius in pixels : 2 -> 5x5 box)
+	int psfSize = psMetadataLookupS32 (&status, recipe, "PCM_BOX_SIZE");
+	assert (status);
+
+        model = psphotPSFConvModel (readout, source, modelType, maskVal, markVal, psfSize);
+        params = model->params->data.F32;
+    } else {
+        status = pmSourceFitModel (source, model, fitMode, maskVal);
+    }
+
+    // measure the source mags
+    pmSourcePhotometryModel (&fitMag, model);
+    pmSourcePhotometryAper  (&obsMag, model, source->pixels, source->maskObj, maskVal);
+    fprintf (stderr, "ap: %f, fit: %f, apmifit: %f, nIter: %d\n", obsMag, fitMag, obsMag - fitMag, model->nIter);
+
+    // write out positive object
+    psphotSaveImage (NULL, source->pixels, "object.fits");
+
+    // subtract object, leave local sky
+    // pmModelSub (source->pixels, source->maskObj, model, PM_MODEL_OP_FULL, maskVal);
+    pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+
+    fprintf (stderr, "output parameters: \n");
+    for (int i = 0; i < nParams; i++) {
+        fprintf (stderr, "%d : %f\n", i, params[i]);
+    }
+
+    // write out
+    psphotSaveImage (NULL, source->pixels, "resid.fits");
+    psphotSaveImage (NULL, source->maskObj, "mask.fits");
+
+    psLogMsg ("psphot", PS_LOG_INFO, "model test : %f sec\n", psTimerMark ("modelTest"));
+
+    exit (0);
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotModelWithPSF.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotModelWithPSF.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotModelWithPSF.c	(revision 22157)
@@ -0,0 +1,417 @@
+# include "psphotInternal.h"
+# define SAVE_IMAGES 0
+
+bool psphotModelWithPSF_LMM (
+    psMinimization *min,
+    psImage *covar,
+    psVector *params,
+    psMinConstraint *constraint,
+    pmSource *source,
+    const psKernel *psf,
+    psMinimizeLMChi2Func func)
+{
+    psTrace("psphot", 3, "---- begin ----\n");
+    PS_ASSERT_PTR_NON_NULL(min, false);
+    PS_ASSERT_VECTOR_NON_NULL(params, false);
+    PS_ASSERT_VECTOR_NON_EMPTY(params, false);
+    PS_ASSERT_VECTOR_TYPE(params, PS_TYPE_F32, false);
+    psVector *paramMask = NULL;
+    if (constraint != NULL) {
+        paramMask = constraint->paramMask;
+        if (paramMask != NULL) {
+            PS_ASSERT_VECTOR_TYPE(paramMask, PS_TYPE_U8, false);
+            PS_ASSERT_VECTORS_SIZE_EQUAL(params, paramMask, false);
+        }
+    }
+    PS_ASSERT_PTR_NON_NULL(func, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+
+    psMinimizeLMLimitFunc checkLimits = NULL;
+    if (constraint) {
+        checkLimits = constraint->checkLimits;
+    }
+
+    // this function has test values and current values for several things
+    // the current value is in lower case
+    // the test value is in upper case
+
+    // allocate internal arrays (current vs Guess)
+    psImage *Alpha = NULL;
+    psVector *Beta = NULL;
+
+    // Alpha & Beta only contain elements to represent the unmasked parameters
+    if (!psMinLM_AllocAB (&Alpha, &Beta, params, paramMask)) {
+	psAbort ("programming error: no unmasked parameters to be fit\n");
+    }
+    
+    // allocate internal arrays (current vs Guess)
+    psImage *alpha   = psImageAlloc(Alpha->numCols, Alpha->numRows, PS_TYPE_F32);
+    psVector *beta   = psVectorAlloc(Beta->n, PS_TYPE_F32);
+    psVector *Params = psVectorAlloc(params->n, PS_TYPE_F32);
+
+    psF32 Chisq = 0.0;
+    psF32 lambda = 0.001;
+    psF32 dLinear = 0.0;
+
+    // generate PCM data storage structure
+    pmPCMData *pcm = pmPCMDataAlloc (params, paramMask, source);
+
+    // calculate initial alpha and beta, set chisq (min->value)
+    min->value = psphotModelWithPSF_SetABX(alpha, beta, params, paramMask, pcm, source, psf, func);
+    if (isnan(min->value)) {
+        min->iter = min->maxIter;
+        return(false);
+    }
+    // dump some useful info if trace is defined
+    if (psTraceGetLevel("psphot") >= 6) {
+        p_psImagePrint(psTraceGetDestination(), alpha, "alpha guess (0)");
+        p_psVectorPrint(psTraceGetDestination(), beta, "beta guess (0)");
+    }
+    if (psTraceGetLevel("psphot") >= 5) {
+        p_psVectorPrint(psTraceGetDestination(), params, "params guess (0)");
+    }
+
+    // iterate until the tolerance is reached, or give up
+    while ((min->iter < min->maxIter) && ((min->lastDelta > min->tol) || !isfinite(min->lastDelta))) {
+        psTrace("psphot", 5, "Iteration number %d.  (max iterations is %d).\n", min->iter, min->maxIter);
+        psTrace("psphot", 5, "Last delta is %f.  Min->tol is %f.\n", min->lastDelta, min->tol);
+
+
+        // set a new guess for Alpha, Beta, Params
+        if (!psMinLM_GuessABP(Alpha, Beta, Params, alpha, beta, params, paramMask, checkLimits, lambda, &dLinear)) {
+            min->iter ++;
+            lambda *= 10.0;
+            continue;
+        }
+
+        // dump some useful info if trace is defined
+        if (psTraceGetLevel("psphot") >= 6) {
+            p_psImagePrint(psTraceGetDestination(), Alpha, "Alpha guess (1)");
+            p_psVectorPrint(psTraceGetDestination(), Beta, "Beta guess (1)");
+            p_psVectorPrint(psTraceGetDestination(), beta, "beta current (1)");
+        }
+        if (psTraceGetLevel("psphot") >= 5) {
+            p_psVectorPrint(psTraceGetDestination(), Params, "params guess (1)");
+        }
+
+        // calculate Chisq for new guess, update Alpha & Beta
+        Chisq = psphotModelWithPSF_SetABX(Alpha, Beta, Params, paramMask, pcm, source, psf, func);
+        if (isnan(Chisq)) {
+            min->iter ++;
+            lambda *= 10.0;
+            continue;
+        }
+
+        // convergence criterion:
+        // compare the delta (min->value - Chisq) with the
+        // expected delta from the linear model (dLinear)
+        // accept new guess if it is an improvement (rho > 0), or else increase lambda
+        psF32 rho = (min->value - Chisq) / dLinear;
+
+        psTrace("psphot", 5, "last chisq: %f, new chisq %f, delta: %f, rho: %f\n", min->value,
+                Chisq, min->lastDelta, rho);
+
+        // dump some useful info if trace is defined
+        if (psTraceGetLevel("psphot") >= 6) {
+            p_psImagePrint(psTraceGetDestination(), Alpha, "alpha guess (2)");
+            p_psVectorPrint(psTraceGetDestination(), Beta, "beta guess (2)");
+        }
+
+        /* if (Chisq < min->value) {  */
+        if (rho > 0.0) {
+            min->lastDelta = (min->value - Chisq) / (source->pixels->numCols*source->pixels->numRows - params->n);
+            min->value = Chisq;
+            alpha  = psImageCopy(alpha, Alpha, PS_TYPE_F32);
+            beta   = psVectorCopy(beta, Beta, PS_TYPE_F32);
+            params = psVectorCopy(params, Params, PS_TYPE_F32);
+            lambda *= 0.25;
+
+	    // save the new convolved model image
+	    psFree (source->modelFlux);
+	    source->modelFlux = pmPCMDataSaveImage(pcm);
+        } else {
+            lambda *= 10.0;
+        }
+        min->iter++;
+    }
+    psTrace("psphot", 5, "chisq: %f, last delta: %f, Niter: %d\n", min->value, min->lastDelta, min->iter);
+
+    // construct & return the covariance matrix (if requested)
+    if (covar != NULL) {
+        if (!psMinLM_GuessABP(Alpha, Beta, Params, alpha, beta, params, paramMask, NULL, 0.0, NULL)) {
+            psTrace ("psphot", 5, "failure to calculate covariance matrix\n");
+        }
+	// set covar values which are not masked
+	psImageInit (covar, 0.0);
+	for (int j = 0, J = 0; j < params->n; j++) {
+	    if (paramMask && (paramMask->data.U8[j])) {
+		covar->data.F32[j][j] = 1.0;
+		continue;
+	    }
+	    for (int k = 0, K = 0; k < params->n; k++) {
+		if (paramMask && (paramMask->data.U8[k])) continue;
+		covar->data.F32[j][k] = Alpha->data.F32[J][K];
+		K++;
+	    }
+	    J++;
+	}
+    }
+
+    // free the internal temporary data
+    psFree(alpha);
+    psFree(Alpha);
+    psFree(beta);
+    psFree(Beta);
+    psFree(Params);
+    psFree(pcm);
+
+    if (min->iter == min->maxIter) {
+        psTrace("psphot", 3, "---- end (false) ----\n");
+        return(false);
+    }
+
+    psTrace("psphot", 3, "---- end (true) ----\n");
+    return(true);
+}
+
+psF32 psphotModelWithPSF_SetABX(
+    psImage  *alpha,
+    psVector *beta,
+    const psVector *params,
+    const psVector *paramMask,
+    pmPCMData *pcm,
+    const pmSource *source,
+    const psKernel *psf,
+    psMinimizeLMChi2Func func)
+{
+    // XXX: Check vector sizes.
+    PS_ASSERT_IMAGE_NON_NULL(alpha, NAN);
+    PS_ASSERT_VECTOR_NON_NULL(beta, NAN);
+    PS_ASSERT_VECTOR_NON_NULL(params, NAN);
+
+    PS_ASSERT_PTR_NON_NULL(source, NAN);
+    PS_ASSERT_IMAGE_NON_NULL(source->pixels, NAN);
+    PS_ASSERT_IMAGE_NON_NULL(source->weight, NAN);
+    PS_ASSERT_IMAGE_NON_NULL(source->maskObj, NAN);
+
+    PS_ASSERT_VECTOR_TYPE(params, PS_TYPE_F32, false);
+    if (paramMask) {
+        PS_ASSERT_VECTOR_TYPE(paramMask, PS_TYPE_MASK, false);
+    }
+
+    // 1 *** generate the model and derivative images for this parameter set
+
+    // storage for model derivatives
+    psVector *deriv = psVectorAlloc(params->n, PS_TYPE_F32);
+
+    // working vector to store local coordinate
+    psVector *coord = psVectorAlloc(2, PS_TYPE_F32);
+
+    psImageInit (pcm->model, 0.0);
+    for (int n = 0; n < params->n; n++) {
+	if (!pcm->dmodels->data[n]) continue;
+	psImageInit (pcm->dmodels->data[n], 0.0);
+    }
+
+    // fill in the coordinate and value entries
+    for (psS32 i = 0; i < source->pixels->numRows; i++) {
+        for (psS32 j = 0; j < source->pixels->numCols; j++) {
+
+	    // XXX can we skip some of the data points where the model
+	    // is not going to be fitted??
+
+            // skip masked points
+	    // XXX probably should not skipped masked points: 
+	    // XXX skip if convolution of unmasked pixels will not see this pixel
+            // if (source->maskObj->data.U8[i][j]) {
+	    // continue;
+	    // }
+
+            // skip zero-weight points
+	    // XXX why is this not masked?
+            // if (source->weight->data.F32[i][j] == 0) {
+	    // continue;
+	    // }
+            // skip nan value points
+	    // XXX why is this not masked?
+            // if (!isfinite(source->pixels->data.F32[i][j])) {
+	    // continue;
+	    // }
+
+            // Convert i/j to image space:
+            coord->data.F32[0] = (psF32) (j + source->pixels->col0);
+            coord->data.F32[1] = (psF32) (i + source->pixels->row0);
+
+	    pcm->model->data.F32[i][j] = func (deriv, params, coord);
+
+	    for (int n = 0; n < params->n; n++) {
+		if ((paramMask != NULL) && (paramMask->data.U8[n])) { continue; }
+		psImage *dmodel = pcm->dmodels->data[n];
+		dmodel->data.F32[i][j] = deriv->data.F32[n];
+	    }
+        }
+    }
+    psFree(coord);
+    psFree(deriv);
+
+    // convolve model and dmodel arrays with PSF
+    psImageConvolveDirect (pcm->modelConv, pcm->model, psf);
+    for (int n = 0; n < pcm->dmodels->n; n++) {
+	if (pcm->dmodels->data[n] == NULL) continue;
+	psImage *dmodel = pcm->dmodels->data[n];
+	psImage *dmodelConv = pcm->dmodelsConv->data[n];
+	psImageConvolveDirect (dmodelConv, dmodel, psf);
+    }
+
+    // XXX TEST : SAVE IMAGES
+# if (SAVE_IMAGES) 
+    psphotSaveImage (NULL, psf->image, "psf.fits");
+    psphotSaveImage (NULL, pcm->model, "model.fits");
+    psphotSaveImage (NULL, pcm->modelConv, "modelConv.fits");
+    psphotSaveImage (NULL, source->pixels, "obj.fits");
+    psphotSaveImage (NULL, source->maskObj, "mask.fits");
+    psphotSaveImage (NULL, source->weight, "weight.fits");
+# endif
+
+    // 2 *** accumulate alpha & beta 
+
+    // zero alpha and beta for summing below
+    psImageInit (alpha, 0.0);
+    psVectorInit (beta, 0.0);
+    float chisq = 0.0;
+
+    for (psS32 i = 0; i < source->pixels->numRows; i++) {
+        for (psS32 j = 0; j < source->pixels->numCols; j++) {
+	    // XXX are we doing the right thing with the mask?
+            // skip masked points
+            if (source->maskObj->data.U8[i][j]) {
+                continue;
+            }
+            // skip zero-weight points
+            if (source->weight->data.F32[i][j] == 0) {
+                continue;
+            }
+            // skip nan value points
+            if (!isfinite(source->pixels->data.F32[i][j])) {
+                continue;
+            }
+
+	    float ymodel  = pcm->modelConv->data.F32[i][j];
+	    float yweight = 1.0 / source->weight->data.F32[i][j];
+	    float delta = ymodel - source->pixels->data.F32[i][j];
+
+	    chisq += PS_SQR(delta) * yweight;
+
+	    if (isnan(delta)) psAbort("nan in delta");
+	    if (isnan(chisq)) psAbort("nan in chisq");
+
+	    // alpha & beta only contain unmasked elements 
+	    for (int n1 = 0, N1 = 0; n1 < params->n; n1++) {
+		if ((paramMask != NULL) && (paramMask->data.U8[n1])) continue;
+		psImage *dmodel = pcm->dmodelsConv->data[n1];
+		float weight = dmodel->data.F32[i][j] * yweight;
+		for (int n2 = 0, N2 = 0; n2 <= n1; n2++) {
+		    if ((paramMask != NULL) && (paramMask->data.U8[n2])) continue;
+		    dmodel = pcm->dmodelsConv->data[n2];
+		    alpha->data.F32[N1][N2] += weight * dmodel->data.F32[i][j];
+		    N2++;
+		}
+		beta->data.F32[N1] += weight * delta;
+		N1++;
+	    }
+	}
+    }
+
+    // calculate lower-left half of alpha
+    for (psS32 j = 1; j < alpha->numCols; j++) {
+        for (psS32 k = 0; k < j; k++) {
+            alpha->data.F32[k][j] = alpha->data.F32[j][k];
+        }
+    }
+
+    return(chisq);
+}
+
+static void pmPCMDataFree (pmPCMData *pcm) {
+
+    if (pcm == NULL) return;
+
+    psFree (pcm->model);
+    psFree (pcm->modelConv);
+    psFree (pcm->dmodels);
+    psFree (pcm->dmodelsConv);
+    return;
+}
+
+pmPCMData *pmPCMDataAlloc (
+    const psVector *params,
+    const psVector *paramMask,
+    pmSource *source) {
+
+    pmPCMData *pcm = (pmPCMData *) psAlloc(sizeof(pmPCMData));
+    psMemSetDeallocator(pcm, (psFreeFunc) pmPCMDataFree);
+
+    // Allocate storage images for raw model and derivative images
+    pcm->model = psImageCopy (NULL, source->pixels, PS_TYPE_F32);
+    pcm->dmodels = psArrayAlloc (params->n);
+    for (psS32 n = 0; n < params->n; n++) {
+	pcm->dmodels->data[n] = NULL;
+	if ((paramMask != NULL) && (paramMask->data.U8[n])) { continue; }
+	pcm->dmodels->data[n] = psImageCopy (NULL, source->pixels, PS_TYPE_F32);
+    }
+
+    // Allocate storage images for convolved model and derivative images
+    pcm->modelConv = psImageCopy (NULL, source->pixels, PS_TYPE_F32);
+    pcm->dmodelsConv = psArrayAlloc (params->n);
+    for (psS32 n = 0; n < params->n; n++) {
+	pcm->dmodelsConv->data[n] = NULL;
+	if ((paramMask != NULL) && (paramMask->data.U8[n])) { continue; }
+	pcm->dmodelsConv->data[n] = psImageCopy (NULL, source->pixels, PS_TYPE_F32);
+    }
+
+    return pcm;
+}
+
+psImage *pmPCMDataSaveImage (pmPCMData *pcm) {
+
+    psImage *model = psImageCopy (NULL, pcm->modelConv, PS_TYPE_F32);
+   
+    return model;
+}
+
+/*
+ *
+ * we have a function func(param; value)
+ 
+ * basic LMM:
+ 
+ - fill in the data (x, y)
+ 
+ chisq = SetABX (alpha, beta, params, paramMask, x, y, dy, func)
+ 
+ while () {
+ GuessABP (Alpha, Beta, Params, alpha, beta, params, paramMask, checkLimits, lambda)
+ dLinear = dLinear(Beta, beta, lambda);
+ chisq = SetABX (alpha, beta, params, paramMask, x, y, dy, func)
+ convergence tests...
+ }
+ 
+ 
+
+ ** GuessABP:
+
+ f_c = sum_i (kern_i * func (x_i; p_o))
+
+ df_c/dp_o = d/dp_o [sum_i (kern_i * func (x_i; p_o))]
+
+ df_c/dp_o = sum_i (d/dp_o [kern_i * func (x_i; p_o)])
+
+ df_c/dp_o = sum_i (kern_i * d/dp_o [func (x_i; p_o)])
+
+ - generate image arrays for func, dfunc/dp_j (not masked)
+ - convolve each with psf
+ - measure delta = f_conv - data
+ - etc
+*/
+
+
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotMosaicChip.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotMosaicChip.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotMosaicChip.c	(revision 22157)
@@ -0,0 +1,39 @@
+# include "psphotInternal.h"
+
+bool psphotMosaicChip(pmConfig *config, const pmFPAview *view, char *outFile, char *inFile)
+{
+    bool status;                        // Status of MD lookup
+
+    pmFPAfile *in = psMetadataLookupPtr(&status, config->files, inFile); // Input file
+    if (!status) {
+        psErrorStackPrint(stderr, "Can't find required I/O file!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    pmFPAfile *out = psMetadataLookupPtr(&status, config->files, outFile); // Output file
+    if (!status) {
+        psErrorStackPrint(stderr, "Can't find required I/O file!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    // XXXX we are failing to get the output hdu right
+    pmChip *outChip = pmFPAviewThisChip(view, out->fpa);
+    pmChip *inChip = pmFPAviewThisChip(view, in->fpa);
+    if (!outChip->hdu && !outChip->parent->hdu) {
+        const char *name = psMetadataLookupStr(&status, in->fpa->concepts, "FPA.NAME"); // Name of FPA
+        pmFPAAddSourceFromView(out->fpa, name, view, out->format);
+    }
+
+    psMaskType blankMask = pmConfigMaskGet("BLANK", config);
+
+    // mosaic the chip, forcing a deep copy (resulting images are not subimages)
+    psTrace("pmChipMosaic", 5, "mosaic chip %s to %s (xbin,ybin: %d,%d to %d,%d)\n",
+            in->name, out->name, in->xBin, in->yBin, out->xBin, out->yBin);
+    status = pmChipMosaic(outChip, inChip, true, blankMask);
+    return status;
+}
+
+// XXX does this do everything needed?
+// * mask & weight
+// * loaded PSF model (in readout->analysis)
+// * loaded SRC sources (in readout->analysis)
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotMosaicSubimage.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotMosaicSubimage.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotMosaicSubimage.c	(revision 22157)
@@ -0,0 +1,46 @@
+# include "psphotInternal.h"
+
+// insert the source image into the outimage at Xo, Yo
+bool psphotMosaicSubimage (psImage *outImage, pmSource *source, int Xo, int Yo, int DX, int DY) {
+
+    psRegion inRegion, outRegion;
+    psImage *inImage = source->pixels;
+
+    // identify the region in the output image
+    outRegion = psRegionSet (Xo, Xo + DX, Yo, Yo + DX);
+    outRegion = psRegionForImage (outImage, outRegion);
+    int DXo = outRegion.x1 - outRegion.x0;
+    int DYo = outRegion.y1 - outRegion.y0;
+    if (DXo <= 0) return false;
+    if (DYo <= 0) return false;
+    
+    // center the input source in the output box
+    int dX = (DXo - 1) / 2;
+    int dY = (DYo - 1) / 2;
+
+    // int xo = inImage->col0 + inImage->numCols / 2;
+    // int yo = inImage->row0 + inImage->numRows / 2;
+
+    int xo = source->peak->xf;
+    int yo = source->peak->yf;
+
+    // adjust region to overlay input image pixels
+    inRegion = psRegionSet (xo - dX, xo + dX + 1, yo - dY, yo + dY + 1);
+    inRegion = psRegionForImage (inImage, inRegion);
+
+    float peak = source->peak->flux;
+
+    psImage *subImage = psImageSubset (inImage, inRegion);
+    psImage *newImage = psImageAlloc (subImage->numCols, subImage->numRows, PS_TYPE_F32);
+    for (int iy = 0; iy < newImage->numRows; iy++) {
+	for (int ix = 0; ix < newImage->numCols; ix++) {
+	    newImage->data.F32[iy][ix] = subImage->data.F32[iy][ix] / peak;
+	}
+    }
+
+    psImageOverlaySection (outImage, newImage, Xo, Yo, "=");
+
+    psFree (subImage);
+    psFree (newImage);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotOutput.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotOutput.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotOutput.c	(revision 22157)
@@ -0,0 +1,158 @@
+# include "psphotInternal.h"
+
+pmReadout *psphotSelectBackground (pmConfig *config,
+                                   const pmFPAview *view,
+                                   const bool stdev // return background's standard deviation?
+                                   ) {
+
+    bool status;
+    pmReadout *background;
+
+    pmFPAfile *file = psMetadataLookupPtr (&status, config->files, "PSPHOT.BACKMDL");
+    if (!file) return NULL;
+    if (file->mode == PM_FPA_MODE_INTERNAL) {
+        background = file->readout;
+    } else {
+        background = pmFPAviewThisReadout (view, file->fpa);
+    }
+    return background;
+}
+
+// XXX replace this with a call to a pmConfig function (pmConfigDump...)
+bool psphotDumpConfig (pmConfig *config) {
+
+  psMetadataConfigWrite (config->complete, "complete.md");
+  psMetadataConfigWrite (config->camera, "camera.md");
+  psMetadataConfigWrite (config->recipes, "recipes.md");
+  psMetadataConfigWrite (config->arguments, "arguments.md");
+  psMetadataConfigWrite (config->files, "files.md");
+  return true;
+}
+
+int psphotSaveImage (psMetadata *header, psImage *image, char *filename) {
+
+    psFits *fits = psFitsOpen (filename, "w");
+    psFitsWriteImage (fits, NULL, image, 0, NULL);
+    psFitsClose (fits);
+    return (TRUE);
+}
+
+bool psphotDumpMoments (psMetadata *recipe, psArray *sources) {
+
+    bool status;
+
+    // optional dump of all rough source data
+    char *output = psMetadataLookupStr (&status, recipe, "MOMENTS_OUTPUT_FILE");
+    if (!output) return false;
+    if (output[0] == 0) return false;
+    if (!strcasecmp (output, "NONE")) return false;
+
+    pmMomentsWriteText (sources, output);
+    return true;
+}
+
+bool psphotDumpSource (pmSource *source, char *name) {
+
+    FILE *f = fopen (name, "w");
+    if (f == NULL) psAbort("can't open file");
+
+    for (int i = 0; i < source->pixels->numRows; i++) {
+        for (int j = 0; j < source->pixels->numCols; j++) {
+            // skip masked points
+            if (source->maskObj->data.U8[i][j]) {
+                continue;
+            }
+            // skip zero-weight points
+            if (source->weight->data.F32[i][j] == 0) {
+                continue;
+            }
+
+            fprintf (f, "%d %d %f %f %d\n",
+                     (j + source->pixels->col0),
+                     (i + source->pixels->row0),
+                     source->pixels->data.F32[i][j],
+                     1.0 / source->weight->data.F32[i][j],
+                     source->maskObj->data.U8[i][j]);
+        }
+    }
+    fclose (f);
+    return true;
+}
+
+bool psphotAddPhotcode (psMetadata *recipe, pmConfig *config, const pmFPAview *view, const char *filerule) {
+
+    bool status;
+
+    pmFPAfile *input = psMetadataLookupPtr (&status, config->files, filerule);
+    PS_ASSERT (status, false);
+
+    // determine PHOTCODE from fpa & view, overwrite in recipe
+    char *photcode = pmConceptsPhotcodeForView (config, input, view);
+    PS_ASSERT (photcode, false);
+
+    psMetadataAddStr (recipe, PS_LIST_TAIL, "PHOTCODE", PS_META_REPLACE, "photcode from FPA concepts", photcode);
+    psLogMsg ("psphot", 3, "PHOTCODE is %s", photcode);
+
+    psFree (photcode);
+    return true;
+}
+
+bool psphotSetHeaderNstars (psMetadata *recipe, psArray *sources) {
+
+    int nSrc = 0;
+
+    // count the number of sources which will be written
+    for (int i = 0; (sources != NULL) && (i < sources->n); i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+        pmModel *model = pmSourceGetModel (NULL, source);
+        if (model == NULL)
+            continue;
+        nSrc ++;
+    }
+    psMetadataAdd (recipe, PS_LIST_TAIL, "NSTARS", PS_DATA_S32 | PS_META_REPLACE, "NUMBER OF STARS", nSrc);
+    return true;
+}
+
+// these values are saved in an output header stub - they are added to either the
+// PHU header (CMP) or the MEF table header (CMF)
+psMetadata *psphotDefineHeader (psMetadata *recipe) {
+
+    psMetadata *header = psMetadataAlloc ();
+
+    // write necessary information to output header
+    psMetadataItemSupplement (header, recipe, "ZERO_PT");
+    psMetadataItemSupplement (header, recipe, "PHOTCODE");
+
+    psMetadataItemSupplement (header, recipe, "APMIFIT");
+    psMetadataItemSupplement (header, recipe, "DAPMIFIT");
+    psMetadataItemSupplement (header, recipe, "NAPMIFIT");
+    psMetadataItemSupplement (header, recipe, "SKYBIAS");
+    psMetadataItemSupplement (header, recipe, "SKYSAT");
+
+    // PSF model parameters (shape values for image center)
+    psMetadataItemSupplement (header, recipe, "NPSFSTAR");
+    psMetadataItemSupplement (header, recipe, "APLOSS");
+    psMetadataItemSupplement (header, recipe, "FWHM_X");
+    psMetadataItemSupplement (header, recipe, "FWHM_Y");
+    psMetadataItemSupplement (header, recipe, "ANGLE");
+
+    // XXX these need to be defined from elsewhere
+    psMetadataAdd (header, PS_LIST_TAIL, "FSATUR",   PS_DATA_F32 | PS_META_REPLACE, "SATURATION MAG",      0.0);
+    psMetadataAdd (header, PS_LIST_TAIL, "FLIMIT",   PS_DATA_F32 | PS_META_REPLACE, "COMPLETENESS MAG",    0.0);
+    psMetadataItemSupplement (header, recipe, "NSTARS");
+
+    // sky background model statistics
+    psMetadataItemSupplement (header, recipe, "MSKY_MN");
+    psMetadataItemSupplement (header, recipe, "MSKY_SIG");
+    psMetadataItemSupplement (header, recipe, "MSKY_MIN");
+    psMetadataItemSupplement (header, recipe, "MSKY_MAX");
+    psMetadataItemSupplement (header, recipe, "MSKY_NX");
+    psMetadataItemSupplement (header, recipe, "MSKY_NY");
+
+    psMetadataAddF32 (header, PS_LIST_TAIL, "DT_PHOT", PS_META_REPLACE, "elapsed psphot time", psTimerMark ("psphotReadout"));
+
+    // XXX : don't require any of these about values to exist
+    psErrorClear ();
+
+    return header;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotPSFConvModel.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotPSFConvModel.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotPSFConvModel.c	(revision 22157)
@@ -0,0 +1,139 @@
+# include "psphotInternal.h"
+# define USE_DELTA_PSF 0
+
+// save as static values so they may be set externally
+static psF32 PM_SOURCE_FIT_MODEL_NUM_ITERATIONS = 15;
+static psF32 PM_SOURCE_FIT_MODEL_TOLERANCE = 0.1;
+
+// input source has both modelPSF and modelEXT.  on successful exit, we set the
+// modelConv to contain the fitted parameters, and the modelFlux to contain the 
+// convolved model image.
+pmModel *psphotPSFConvModel (pmReadout *readout, pmSource *source, pmModelType modelType, psMaskType maskVal, psMaskType markVal, int psfSize) {
+    
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // make sure we save a cached copy of the psf flux
+    pmSourceCachePSF (source, maskVal);
+
+    // convert the cached cached psf model for this source to a psKernel
+    psKernel *psf = psphotKernelFromPSF (source, psfSize);
+    if (!psf) return NULL;
+
+# if (USE_DELTA_PSF)
+    psImageInit (psf->image, 0.0);
+    psf->image->data.F32[(int)(0.5*psf->image->numRows)][(int)(0.5*psf->image->numCols)] = 1.0;
+# endif
+
+    // generate copy of the model
+    // XXX we could modify the parameter values or even the model 
+    // here based on the observed seeing (some lookup table...)
+
+    // use the source moments, etc to guess basic model parameters
+    pmModel *modelConv = pmSourceModelGuess (source, modelType);
+    if (!modelConv) {
+	psFree (psf);
+	return NULL;
+    }
+
+    // XXX test : modify the Io, SXX, SYY terms based on the psf SXX, SYY terms:
+    psEllipseShape psfShape;
+    psfShape.sx  = source->modelPSF->params->data.F32[PM_PAR_SXX] / M_SQRT2;
+    psfShape.sxy = source->modelPSF->params->data.F32[PM_PAR_SXY];
+    psfShape.sy  = source->modelPSF->params->data.F32[PM_PAR_SYY] / M_SQRT2;
+    psEllipseAxes psfAxes = psEllipseShapeToAxes (psfShape, 20.0);
+
+    // XXX test : modify the Io, SXX, SYY terms based on the psf SXX, SYY terms:
+    psEllipseShape extShape;
+    extShape.sx  = modelConv->params->data.F32[PM_PAR_SXX] / M_SQRT2;
+    extShape.sxy = modelConv->params->data.F32[PM_PAR_SXY];
+    extShape.sy  = modelConv->params->data.F32[PM_PAR_SYY] / M_SQRT2;
+    psEllipseAxes extAxes = psEllipseShapeToAxes (extShape, 20.0);
+
+    // decrease the initial guess ellipse by psf_minor axis:
+    psEllipseAxes extAxesMod;
+    extAxesMod.major = sqrt (PS_MAX (1.0, PS_SQR(extAxes.major) - PS_SQR(psfAxes.minor)));
+    extAxesMod.minor = sqrt (PS_MAX (1.0, PS_SQR(extAxes.minor) - PS_SQR(psfAxes.minor)));
+    extAxesMod.theta = extAxes.theta;
+
+    psEllipseShape extShapeMod = psEllipseAxesToShape (extAxesMod);
+    modelConv->params->data.F32[PM_PAR_SXX] = extShapeMod.sx * M_SQRT2;
+    modelConv->params->data.F32[PM_PAR_SXY] = extShapeMod.sxy;
+    modelConv->params->data.F32[PM_PAR_SYY] = extShapeMod.sy * M_SQRT2;
+
+    // increase the initial guess central intensity by 2pi r^2:
+    modelConv->params->data.F32[PM_PAR_I0] *= (1.0 + PS_SQR(psfAxes.minor) / PS_SQR(extAxesMod.minor));
+
+    psVector *params  = modelConv->params;
+    psVector *dparams = modelConv->dparams;
+
+    psphotCheckRadiusEXT (readout, source, modelConv, markVal);
+
+    // create the minimization constraints
+    psMinConstraint *constraint = psMinConstraintAlloc();
+    constraint->paramMask = psVectorAlloc (params->n, PS_TYPE_U8);
+    constraint->checkLimits = modelConv->modelLimits;
+
+    // set parameter mask based on fitting mode
+    // we fit a model without a floating sky term
+    int nParams = params->n - 1;
+    psVectorInit (constraint->paramMask, 0);
+    constraint->paramMask->data.U8[PM_PAR_SKY] = 1;
+
+    // force the floating parameters to fall within the contraint ranges
+    for (int i = 0; i < params->n; i++) {
+	modelConv->modelLimits (PS_MINIMIZE_PARAM_MIN, i, params->data.F32, NULL);
+	modelConv->modelLimits (PS_MINIMIZE_PARAM_MAX, i, params->data.F32, NULL);
+    }
+
+    // set up the minimization process
+    psMinimization *myMin = psMinimizationAlloc (PM_SOURCE_FIT_MODEL_NUM_ITERATIONS, PM_SOURCE_FIT_MODEL_TOLERANCE);
+
+    psImage *covar = psImageAlloc (params->n, params->n, PS_TYPE_F32);
+
+    bool fitStatus = psphotModelWithPSF_LMM (myMin, covar, params, constraint, source, psf, modelConv->modelFunc);
+    for (int i = 0; i < dparams->n; i++) {
+        if (psTraceGetLevel("psphot") >= 4) {
+            fprintf (stderr, "%f ", params->data.F32[i]);
+        }
+        if ((constraint->paramMask != NULL) && constraint->paramMask->data.U8[i])
+            continue;
+        dparams->data.F32[i] = sqrt(covar->data.F32[i][i]);
+    }
+    psTrace ("psphot", 4, "niter: %d, chisq: %f", myMin->iter, myMin->value);
+
+    // renormalize output model image (generated by fitting process)
+    float Io = params->data.F32[PM_PAR_I0];
+    for (int iy = 0; iy < source->modelFlux->numRows; iy++) {
+	for (int ix = 0; ix < source->modelFlux->numCols; ix++) {
+	    source->modelFlux->data.F32[iy][ix] /= Io;
+	}
+    }
+
+    // save the resulting chisq, nDOF, nIter
+    modelConv->chisq = myMin->value;
+    modelConv->nIter = myMin->iter;
+
+    // XXX I actually need to count the number of unmasked pixels here
+    modelConv->nDOF  = source->pixels->numCols*source->pixels->numRows  -  nParams;
+
+    modelConv->flags |= PM_MODEL_STATUS_FITTED;
+    if (!fitStatus) modelConv->flags |= PM_MODEL_STATUS_NONCONVERGE;
+
+    // models can go insane: reject these
+    bool onPic = true;
+    onPic &= (params->data.F32[PM_PAR_XPOS] >= source->pixels->col0);
+    onPic &= (params->data.F32[PM_PAR_XPOS] <  source->pixels->col0 + source->pixels->numCols);
+    onPic &= (params->data.F32[PM_PAR_YPOS] >= source->pixels->row0);
+    onPic &= (params->data.F32[PM_PAR_YPOS] <  source->pixels->row0 + source->pixels->numRows);
+    if (!onPic) modelConv->flags |= PM_MODEL_STATUS_OFFIMAGE;
+
+    source->mode |= PM_SOURCE_MODE_FITTED; // XXX is this needed?
+
+    psFree(psf);
+    psFree(myMin);
+    psFree(covar);
+    psFree(constraint);
+
+    return modelConv;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotParseCamera.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotParseCamera.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotParseCamera.c	(revision 22157)
@@ -0,0 +1,81 @@
+# include "psphotStandAlone.h"
+
+// define the needed / desired I/O files
+bool psphotParseCamera (pmConfig *config) {
+
+    bool status = false;
+
+    // the file to be loaded may have subdivisions at the cell and readout level
+    // we load into pmFPAfile *load, then reformat into pmFPAfile *input
+    pmFPAfile *load = pmFPAfileDefineFromArgs (&status, config, "PSPHOT.LOAD", "INPUT");
+    if (!status) {
+	psError(PSPHOT_ERR_CONFIG, false, "Failed to build FPA from PSPHOT.LOAD");
+	return status;
+    }
+    load->dataLevel = PM_FPA_LEVEL_CHIP; // force load at the CHIP level
+
+    // if MASK or WEIGHT was supplied on command line, bind files to 'load'
+    // the mask and weight will be mosaicked with the image
+    pmFPAfile *mask = pmFPAfileBindFromArgs (&status, load, config, "PSPHOT.MASK", "MASK");
+    if (!status) {
+	psError (PS_ERR_UNKNOWN, false, "failed to load find definition");
+	return NULL;
+    }
+    if (mask) {
+	// XXX need to load the mask bit names from one of the headers
+	// this grabs the first available hdu : no guarantee that it will be valid, though
+	pmHDU *hdu = pmHDUGetFirst (mask->fpa);
+	if (!hdu) {
+	    psError(PS_ERR_IO, true, "no valid HDU for PSPHOT.MASK");
+	    return NULL;
+	}
+	// XXX should this be done in pmFPAfileIO.c read functions?
+	if (!pmConfigMaskReadHeader (config, hdu->header)) {
+	    psError(PS_ERR_IO, false, "error in mask bits");
+	    return NULL;
+	}
+    }
+    if (!psphotSetMaskBits (config)) {
+	psError (PS_ERR_UNKNOWN, false, "failed to set mask bit values");
+	return NULL;
+    }
+    
+    pmFPAfileBindFromArgs (&status, load, config, "PSPHOT.WEIGHT", "WEIGHT");
+    if (!status) {
+	psError (PS_ERR_UNKNOWN, false, "failed to load find definition");
+	return NULL;
+    }
+
+    // the psphot analysis is performed on chips
+    pmFPAfile *input = pmFPAfileDefineChipMosaic(config, load->fpa, "PSPHOT.INPUT");
+    if (!input) {
+        psError(PSPHOT_ERR_CONFIG, false, _("Unable to generate new file from PSPHOT.INPUT"));
+        return NULL;
+    }
+
+    // define the additional input/output files associated with psphot 
+    if (!psphotDefineFiles (config, input)) {
+	psError(PSPHOT_ERR_CONFIG, false, "Trouble defining the additional input/output files");
+	return false;
+    }
+
+    // Chip selection: turn on only the chips specified (pass status to suppress missing-key log msg)
+    char *chipLine = psMetadataLookupStr(&status, config->arguments, "CHIP_SELECTIONS");
+    psArray *chips = psStringSplitArray (chipLine, ",", false);
+    if (chips->n > 0) {
+	// select on the basis of extname?
+	pmFPASelectChip (load->fpa, -1, true); // deselect all chips
+	for (int i = 0; i < chips->n; i++) {
+	    int chipNum = atoi(chips->data[i]);
+	    if (! pmFPASelectChip(load->fpa, chipNum, false)) {
+		psError(PSPHOT_ERR_CONFIG, false, "Chip number %d doesn't exist in camera.\n", chipNum);
+		return false;
+	    }
+        }
+    }
+    psFree (chips);
+    psTrace("psphot", 1, "Done with psphotParseCamera...\n");
+
+    psErrorClear();			// some metadata lookup may have failed
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotPetrosian.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotPetrosian.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotPetrosian.c	(revision 22157)
@@ -0,0 +1,109 @@
+# include "psphotInternal.h"
+
+bool psphotPetrosian (pmSource *source, psMetadata *recipe, psMaskType maskVal) {
+
+  bool status;
+
+  assert (source->extpars);
+  assert (source->extpars->profile);
+  assert (source->extpars->profile->radius);
+  assert (source->extpars->profile->flux);
+
+  psVector *radius = source->extpars->profile->radius;
+  psVector *flux = source->extpars->profile->flux;
+
+  // flux at which to measure isophotal parameters
+  float PETROSIAN_R0 = psMetadataLookupF32 (&status, recipe, "PETROSIAN_R0");
+  float PETROSIAN_RF = psMetadataLookupF32 (&status, recipe, "PETROSIAN_FLUX_RATIO");
+  assert (status);
+
+  // first find flux at R0
+  int firstAbove = -1;
+  int lastBelow = -1;
+  for (int i = 0; i < radius->n; i++) {
+    if (radius->data.F32[i] < PETROSIAN_R0) lastBelow = i;
+    if ((firstAbove < 0) && (radius->data.F32[i] > PETROSIAN_R0)) firstAbove = i;
+  }
+  // if we don't go out far enough, we have a problem...
+  if (lastBelow == radius->n - 1) {
+    psTrace ("psphot", 5, "did not go out far enough to reach petrosian reference radius...");
+    // XXX skip object? raise a flag ?
+    return false;
+  }
+  if (firstAbove < 0) {
+    psTrace ("psphot", 5, "did not go out far enough to bound petrosian reference radius");
+    // XXX raise a flag ?
+    return false;
+  }
+
+  // average flux in this range
+  float fluxR0 = 0.0;
+  int fluxRn = 0;
+  for (int i = PS_MIN(firstAbove, lastBelow); i <= PS_MAX(firstAbove, lastBelow); i++) {
+    fluxR0 += flux->data.F32[i];
+    fluxRn ++;
+  }
+  fluxR0 /= (float)(fluxRn);
+
+  // target flux for petrosian radius
+  float fluxRP = fluxR0 * PETROSIAN_RF;
+
+  // find the first bin below the flux level and the last above the level
+  // XXX can this be done faster with bisection?
+  // XXX do I need to worry about crazy outliers?
+  // XXX should i be smoothing or fitting the curve?
+  int firstBelow = -1;
+  int lastAbove = -1;
+  for (int i = 0; i < flux->n; i++) {
+    if (flux->data.F32[i] > fluxRP) lastAbove = i;
+    if ((firstBelow < 0) && (flux->data.F32[i] < fluxRP)) firstBelow = i;
+  }
+  // if we don't go out far enough, we have a problem...
+  if (lastAbove == radius->n - 1) {
+    psTrace ("psphot", 5, "did not go out far enough to reach petrosian radius...");
+    // XXX skip object? raise a flag ?
+    return false;
+  }
+  if (firstBelow < 0) {
+    psTrace ("psphot", 5, "did not go out far enough to bound petrosian radius");
+    // XXX raise a flag ?
+    return false;
+  }
+
+  // need to examine pixels in this vicinity
+  float fluxFirst = 0;
+  float fluxLast = 0;
+  for (int i = 0; i <= PS_MAX(firstBelow, lastAbove); i++) {
+    if (i <= firstBelow) {
+      fluxFirst += flux->data.F32[i];
+    }
+    if (i <= lastAbove) {
+      fluxLast += flux->data.F32[i];
+    }
+  }
+  float fluxRPSum    = 0.5*(fluxLast + fluxFirst);
+  float fluxRPSumErr = 0.5*fabs(fluxLast - fluxFirst);
+  // XXX need to use the weight appropriately here...
+
+  float rad     = 0.5*(radius->data.F32[firstBelow] + radius->data.F32[lastAbove]);
+  float radErr  = 0.5*fabs(radius->data.F32[firstBelow] - radius->data.F32[lastAbove]);
+
+  if (!source->extpars->petrosian) {
+    source->extpars->petrosian = pmSourcePetrosianValuesAlloc ();
+  }
+
+  // these are uncalibrated: instrumental mags and pixel units
+  source->extpars->petrosian->mag    = -2.5*log10(fluxRPSum);
+  source->extpars->petrosian->magErr = fluxRPSumErr / fluxRPSum;
+
+  source->extpars->petrosian->rad    = rad;
+  source->extpars->petrosian->radErr = radErr;
+
+  psTrace ("psphot", 5, "Petrosian flux:%f +/- %f @ %f +/- %f for %f, %f\n",
+           source->extpars->petrosian->mag, source->extpars->petrosian->magErr,
+           source->extpars->petrosian->rad, source->extpars->petrosian->radErr,
+           source->peak->xf, source->peak->yf);
+
+  return true;
+
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotRadialPlot.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotRadialPlot.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotRadialPlot.c	(revision 22157)
@@ -0,0 +1,118 @@
+# include "psphotInternal.h"
+
+// this variable is defined in psmodules.h if ohana-config is found
+# if (HAVE_KAPA)
+
+# include <kapa.h>
+
+static int nCount = 0;
+
+bool psphotRadialPlot (int *kapa, const char *filename, pmSource *source) {
+
+    Graphdata graphdata;
+
+    // only plot 50 stars for now...
+    if (nCount > 00) {
+	if (*kapa != 0) {
+	    KiiClose (*kapa);
+	    *kapa = 0;
+	}
+	return true;
+    }
+
+    // XXX get the 'showWindow' option from the recipes somewhere
+    // XXX 'showWindow = false' is broken
+    if (*kapa == 0) {
+	*kapa = pmKapaOpen (false);
+	KapaResize (*kapa, 500, 500);
+	unlink (filename);
+    }
+    if (*kapa == -1) {
+	psError(PSPHOT_ERR_UNKNOWN, true, "failure to open kapa");
+	return false;
+    }
+
+    KapaInitGraph (&graphdata);
+    KapaClearPlots (*kapa);
+
+    // examine sources to set data range
+    graphdata.xmin =  -0.05;
+    graphdata.xmax = +30.05;
+    graphdata.ymin = -0.05;
+    graphdata.ymax = +5.05;
+    KapaSetLimits (*kapa, &graphdata);
+  
+    KapaSetFont (*kapa, "helvetica", 14);
+    KapaBox (*kapa, &graphdata);
+    KapaSendLabel (*kapa, "radius (pixels)", KAPA_LABEL_XM);
+    KapaSendLabel (*kapa, "log flux (counts)", KAPA_LABEL_YM);
+	       
+    int nPts = source->pixels->numRows * source->pixels->numCols;
+    psVector *rg = psVectorAllocEmpty (nPts, PS_TYPE_F32);
+    psVector *fg = psVectorAllocEmpty (nPts, PS_TYPE_F32);
+    psVector *rb = psVectorAllocEmpty (nPts, PS_TYPE_F32);
+    psVector *fb = psVectorAllocEmpty (nPts, PS_TYPE_F32);
+
+    int ng = 0;
+    int nb = 0;
+    float Xo = source->modelPSF->params->data.F32[PM_PAR_XPOS] - source->pixels->col0;
+    float Yo = source->modelPSF->params->data.F32[PM_PAR_YPOS] - source->pixels->row0;
+    for (int iy = 0; iy < source->pixels->numRows; iy++) {
+	for (int ix = 0; ix < source->pixels->numCols; ix++) {
+	    if (source->maskObj->data.U8[iy][ix]) {
+		rb->data.F32[nb] = hypot (ix - Xo, iy - Yo) ;
+		fb->data.F32[nb] = log10(source->pixels->data.F32[iy][ix]);
+		nb++;
+	    } else {
+		rg->data.F32[ng] = hypot (ix - Xo, iy - Yo) ;
+		fg->data.F32[ng] = log10(source->pixels->data.F32[iy][ix]);
+		ng++;
+	    }
+	}
+    }
+  
+    // set the plot range here based on lflux, radius
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 2;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot (*kapa, ng, &graphdata);
+    KapaPlotVector (*kapa, ng, rg->data.F32, "x");
+    KapaPlotVector (*kapa, ng, fg->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 0;
+    graphdata.size = 0.3;
+    graphdata.style = 2;
+    KapaPrepPlot (*kapa, nb, &graphdata);
+    KapaPlotVector (*kapa, nb, rb->data.F32, "x");
+    KapaPlotVector (*kapa, nb, fb->data.F32, "y");
+  
+    psLogMsg ("psphot", 3, "saving plot to %s", filename);
+
+    char pagename[16];
+    sprintf (pagename, "%02d", nCount);
+    if (nCount == 0) {
+	KiiPS (*kapa, filename, false, KAPA_PS_NEWPLOT, pagename);
+    } else {
+	KiiPS (*kapa, filename, false, KAPA_PS_NEWPAGE, pagename);
+    }
+
+    psFree (rg);
+    psFree (fg);
+    psFree (rb);
+    psFree (fb);
+
+    nCount ++;
+    return true;
+}
+
+# else
+
+bool psphotRadialPlot (int *kapa, const char *filename, pmSource *source) {
+    psLogMsg ("psphot", 3, "skipping source radial plots");
+    return true;
+}
+
+# endif
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotRadialProfile.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotRadialProfile.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotRadialProfile.c	(revision 22157)
@@ -0,0 +1,71 @@
+# include "psphotInternal.h"
+
+# define COMPARE_RADIUS(A,B) (radius->data.F32[A] < radius->data.F32[B])
+# define SWAP_RADIUS(TYPE,A,B) { \
+  float tmp; \
+  if (A != B) { \
+    tmp = radius->data.F32[A]; \
+    radius->data.F32[A] = radius->data.F32[B]; \
+    radius->data.F32[B] = tmp; \
+    tmp = flux->data.F32[A]; \
+    flux->data.F32[A] = flux->data.F32[B]; \
+    flux->data.F32[B] = tmp; \
+    tmp = weight->data.F32[A]; \
+    weight->data.F32[A] = weight->data.F32[B]; \
+    weight->data.F32[B] = tmp; \
+  } \
+}
+
+bool psphotRadialProfile (pmSource *source, psMetadata *recipe, psMaskType maskVal) {
+
+    // allocate pmSourceExtendedParameters, if not already defined
+    if (!source->extpars) {
+        source->extpars = pmSourceExtendedParsAlloc ();
+    }
+
+    if (!source->extpars->profile) {
+        source->extpars->profile = pmSourceRadialProfileAlloc ();
+    }
+
+    int nPts = source->pixels->numRows * source->pixels->numCols;
+    source->extpars->profile->radius = psVectorAllocEmpty (nPts, PS_TYPE_F32);
+    source->extpars->profile->flux   = psVectorAllocEmpty (nPts, PS_TYPE_F32);
+    source->extpars->profile->weight = psVectorAllocEmpty (nPts, PS_TYPE_F32);
+
+    psVector *radius = source->extpars->profile->radius;
+    psVector *flux   = source->extpars->profile->flux;
+    psVector *weight = source->extpars->profile->weight;
+
+    // XXX use the extended source model here for Xo, Yo?
+    // XXX define a radius scaled to the elliptical contour?
+
+    int n = 0;
+    
+    float Xo = 0.0;
+    float Yo = 0.0;
+
+    if (source->modelEXT) {
+      Xo = source->modelEXT->params->data.F32[PM_PAR_XPOS] - source->pixels->col0;
+      Yo = source->modelEXT->params->data.F32[PM_PAR_YPOS] - source->pixels->row0;
+    } else {
+      Xo = source->peak->xf - source->pixels->col0;
+      Yo = source->peak->yf - source->pixels->row0;
+    }
+    for (int iy = 0; iy < source->pixels->numRows; iy++) {
+        for (int ix = 0; ix < source->pixels->numCols; ix++) {
+            if (source->maskObj->data.U8[iy][ix]) continue;
+            radius->data.F32[n] = hypot (ix - Xo, iy - Yo) ;
+            flux->data.F32[n]   = source->pixels->data.F32[iy][ix];
+            weight->data.F32[n] = source->weight->data.F32[iy][ix];
+            n++;
+        }
+    }
+    radius->n = n;
+    weight->n = n;
+    flux->n = n;
+
+    // sort the vector set by the radius
+    PSSORT (radius->n, COMPARE_RADIUS, SWAP_RADIUS, NONE);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotRadiusChecks.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotRadiusChecks.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotRadiusChecks.c	(revision 22157)
@@ -0,0 +1,105 @@
+# include "psphotInternal.h"
+# define RADIUS_TYPE int
+
+static float PSF_FIT_NSIGMA;
+static float PSF_FIT_PADDING;
+static float PSF_FIT_RADIUS = 0;	// radius to use in fitting (ignored if <= 0,
+					// and a per-object radius is calculated)
+
+bool psphotInitRadiusPSF(const psMetadata *recipe, const pmModelType type) {
+
+    bool status = true;
+
+    PSF_FIT_NSIGMA  = psMetadataLookupF32(&status, recipe, "PSF_FIT_NSIGMA");
+    PSF_FIT_PADDING = psMetadataLookupF32(&status, recipe, "PSF_FIT_PADDING");
+    PSF_FIT_RADIUS =  psMetadataLookupF32(&status, recipe, "PSF_FIT_RADIUS");
+
+    return true;
+}
+
+// call this function whenever you (re)-define the PSF model
+bool psphotCheckRadiusPSF (pmReadout *readout, pmSource *source, pmModel *model, psMaskType markVal)
+{
+    psF32 *PAR = model->params->data.F32;
+
+    // XXX do we have a better value for the sky noise level?  not really...
+    pmMoments *moments = source->moments;
+
+    // set the fit radius based on the object flux limit and the model
+    float radiusFit = PSF_FIT_RADIUS;
+    if (radiusFit <= 0) {		// use fixed radius
+	if (moments == NULL) {
+	    radiusFit = model->modelRadius(model->params, PSF_FIT_NSIGMA*moments->dSky);
+	} else {
+	    radiusFit = model->modelRadius(model->params, 1.0);
+	}
+    }
+    model->radiusFit = (RADIUS_TYPE)(radiusFit + PSF_FIT_PADDING);
+
+    if (isnan(model->radiusFit)) psAbort("error in radius");
+	
+    if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	model->radiusFit *= 2;
+    }
+
+    bool status = pmSourceRedefinePixels (source, readout, PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], model->radiusFit);
+
+    // set the mask to flag the excluded pixels
+    psImageKeepCircle (source->maskObj, PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], model->radiusFit, "OR", markVal);
+    return status;
+}
+
+bool psphotCheckRadiusPSFBlend (pmReadout *readout, pmSource *source, pmModel *model, psMaskType markVal, float dR) {
+
+    psF32 *PAR = model->params->data.F32;
+
+    pmMoments *moments = source->moments;
+    if (moments == NULL) return false;
+
+    // set the fit radius based on the object flux limit and the model
+    model->radiusFit = (RADIUS_TYPE) (model->modelRadius (model->params, PSF_FIT_NSIGMA*moments->dSky) + dR + PSF_FIT_PADDING);
+    if (isnan(model->radiusFit)) psAbort("error in radius");
+	
+    if (source->mode &  PM_SOURCE_MODE_SATSTAR) {
+	model->radiusFit *= 2;
+    }
+
+    bool status = pmSourceRedefinePixels (source, readout, PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], model->radiusFit);
+
+    // set the mask to flag the excluded pixels
+    psImageKeepCircle (source->maskObj, PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], model->radiusFit, "OR", markVal);
+    return status;
+}
+
+static float EXT_FIT_NSIGMA;
+static float EXT_FIT_PADDING;
+
+bool psphotInitRadiusEXT (psMetadata *recipe, pmModelType type) {
+
+    bool status;
+
+    EXT_FIT_NSIGMA   = psMetadataLookupF32 (&status, recipe, "EXT_FIT_NSIGMA");
+    EXT_FIT_PADDING  = psMetadataLookupF32 (&status, recipe, "EXT_FIT_PADDING");
+
+    return true;
+}
+
+// call this function whenever you (re)-define the EXT model
+bool psphotCheckRadiusEXT (pmReadout *readout, pmSource *source, pmModel *model, psMaskType markVal) {
+
+    psF32 *PAR = model->params->data.F32;
+
+    pmMoments *moments = source->moments;
+    if (moments == NULL) return false;
+
+    // set the fit radius based on the object flux limit and the model
+    model->radiusFit = (RADIUS_TYPE) (model->modelRadius (model->params, EXT_FIT_NSIGMA*moments->dSky) + EXT_FIT_PADDING);
+    if (isnan(model->radiusFit)) psAbort("error in radius");
+
+    // redefine the pixels if needed
+    bool status = pmSourceRedefinePixels (source, readout, PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], model->radiusFit);
+
+    // set the mask to flag the excluded pixels
+    psImageKeepCircle (source->maskObj, PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], model->radiusFit, "OR", markVal);
+    return status;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotReadout.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotReadout.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotReadout.c	(revision 22157)
@@ -0,0 +1,251 @@
+# include "psphotInternal.h"
+
+bool psphotReadout(pmConfig *config, const pmFPAview *view) {
+
+    // measure the total elapsed time in psphotReadout.  XXX the current threading plan
+    // for psphot envisions threading within psphotReadout, not multiple threads calling
+    // the same psphotReadout.  In the current plan, this dtime is the elapsed time used
+    // jointly by the multiple threads, not the total time used by all threads.
+    psTimerStart ("psphotReadout");
+
+    // select the current recipe
+    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSPHOT_RECIPE);
+    if (!recipe) {
+        psError(PSPHOT_ERR_CONFIG, false, "missing recipe %s", PSPHOT_RECIPE);
+        return false;
+    }
+
+    // set the photcode for this image
+    if (!psphotAddPhotcode (recipe, config, view, "PSPHOT.INPUT")) {
+        psError (PSPHOT_ERR_CONFIG, false, "trouble defining the photcode");
+        return false;
+    }
+
+    // find the currently selected readout
+    pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
+    PS_ASSERT_PTR_NON_NULL (readout, false);
+
+    // optional break-point for processing
+    char *breakPt = psMetadataLookupStr (NULL, recipe, "BREAK_POINT");
+    PS_ASSERT_PTR_NON_NULL (breakPt, false);
+
+    // Generate the mask and weight images, including the user-defined analysis region of interest
+    psphotSetMaskAndWeight (config, readout, recipe);
+    if (!strcasecmp (breakPt, "NOTHING")) {
+        return psphotReadoutCleanup(config, readout, recipe, NULL, NULL, NULL);
+    }
+
+    // generate a background model (median, smoothed image)
+    if (!psphotModelBackground (config, view, "PSPHOT.INPUT")) {
+        return psphotReadoutCleanup (config, readout, recipe, NULL, NULL, NULL);
+    }
+    if (!psphotSubtractBackground (config, view, "PSPHOT.INPUT")) {
+        return psphotReadoutCleanup (config, readout, recipe, NULL, NULL, NULL);
+    }
+    if (!strcasecmp (breakPt, "BACKMDL")) {
+        return psphotReadoutCleanup (config, readout, recipe, NULL, NULL, NULL);
+    }
+
+    // run a single-model test if desired (exits from here if test is run)
+    psphotModelTest (config, view, recipe);
+
+    // load the psf model, if suppled.  FWHM_X,FWHM_Y,etc are saved in the recipe
+    pmPSF *psf = psphotLoadPSF (config, view, recipe);
+
+    // several functions below behave differently if we have a PSF model already
+    bool havePSF = (psf != NULL);
+
+    // find the detections (by peak and/or footprint) in the image.
+    pmDetections *detections = psphotFindDetections (NULL, readout, recipe);
+    if (!detections) {
+        psLogMsg ("psphot", 3, "unable to find detections in this image");
+        return psphotReadoutCleanup (config, readout, recipe, detections, psf, NULL);
+    }
+
+    // construct sources and measure basic stats
+    psArray *sources = psphotSourceStats (readout, recipe, detections);
+    if (!sources) return false;
+    if (!strcasecmp (breakPt, "PEAKS")) {
+        return psphotReadoutCleanup(config, readout, recipe, detections, psf, sources);
+    }
+
+    // find blended neighbors of very saturated stars
+    // XXX merge this with Basic Deblend?
+    psphotDeblendSatstars (sources, recipe);
+
+    // mark blended peaks PS_SOURCE_BLEND
+    if (!psphotBasicDeblend (sources, recipe)) {
+        psLogMsg ("psphot", 3, "failed on deblend analysis");
+        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+    }
+
+    // classify sources based on moments, brightness
+    if (!psphotRoughClass (sources, recipe, havePSF)) {
+        psLogMsg ("psphot", 3, "failed to find a valid PSF clump for image");
+        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+    }
+    if (!strcasecmp (breakPt, "MOMENTS")) {
+        return psphotReadoutCleanup(config, readout, recipe, detections, psf, sources);
+    }
+
+    // if we were not supplied a PSF, choose one here
+    if (psf == NULL) {
+        // use bright stellar objects to measure PSF
+        // XXX if we do not have enough stars to generate the PSF, build one
+        // from the SEEING guess and model class
+        psf = psphotChoosePSF (readout, sources, recipe);
+        if (psf == NULL) {
+            psLogMsg ("psphot", 3, "failure to construct a psf model");
+            return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+        }
+        havePSF = true;
+    }
+    if (!strcasecmp (breakPt, "PSFMODEL")) {
+        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+    }
+
+    // include externally-supplied sources
+    psphotLoadExtSources (config, view, sources);
+
+    // construct an initial model for each object
+    psphotGuessModels (readout, sources, recipe, psf);
+
+    // XXX test output of models
+    // psphotTestSourceOutput (readout, sources, recipe, psf);
+
+    // linear PSF fit to source peaks
+    psphotFitSourcesLinear (readout, sources, recipe, psf, FALSE);
+
+    if (0) { 
+	FILE *out = fopen ("out.pass1.dat", "w");
+	
+	for (int i = 0; i < sources->n; i++) {
+	    pmSource *source = sources->data[i];
+	    if (!source) continue;
+	    pmPeak *peak = source->peak;
+	    if (!peak) continue;
+	    pmModel *model = source->modelPSF;
+	    if (!model) continue;
+	    if (!model->params) continue;
+	    
+	    fprintf (out, "%d %f %f  %f %f  %f %f\n", 
+		     i, peak->xf, peak->yf, peak->flux, peak->SN,
+		     model->params->data.F32[PM_PAR_I0],
+		     model->dparams->data.F32[PM_PAR_I0]);
+	}
+	fclose (out);
+    }
+
+    // identify CRs and extended sources
+    psphotSourceSize (readout, sources, recipe, 0);
+    if (!strcasecmp (breakPt, "ENSEMBLE")) {
+        goto finish;
+    }
+
+    // non-linear PSF and EXT fit to brighter sources
+    psphotBlendFit (readout, sources, recipe, psf);
+
+    // replace all sources
+    psphotReplaceAllSources (sources, recipe);
+
+    // linear fit to include all sources
+    psphotFitSourcesLinear (readout, sources, recipe, psf, TRUE);
+
+    // if we only do one pass, skip to extended source analysis
+    if (!strcasecmp (breakPt, "PASS1")) {
+        goto pass1finish;
+    }
+
+    // XXX for the moment, drop the re-calc of the background (prove this works)
+    // replace background in residual image
+    // psphotSkyReplace (config, view);
+    // re-measure background model (median, smoothed image)
+    // psphotImageMedian (config, view);
+
+    // add noise for subtracted objects
+    psphotAddNoise (readout, sources, recipe);
+
+    // find fainter sources (pass 2)
+    detections = psphotFindDetections (detections, readout, recipe);
+
+    // remove noise for subtracted objects (ie, return to normal noise level)
+    psphotSubNoise (readout, sources, recipe);
+
+    // define new sources based on only the new peaks
+    psArray *newSources = psphotSourceStats (readout, recipe, detections);
+
+    // set source type
+    if (!psphotRoughClass (newSources, recipe, havePSF)) {
+        psLogMsg ("psphot", 3, "failed to find a valid PSF clump for image");
+        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+    }
+
+    // create full input models
+    psphotGuessModels (readout, newSources, recipe, psf);
+
+    // replace all sources so fit below applies to all at once
+    psphotReplaceAllSources (sources, recipe);
+
+    // merge the newly selected sources into the existing list
+    psphotMergeSources (sources, newSources);
+    psFree (newSources);
+
+    // linear fit to all sources
+    psphotFitSourcesLinear (readout, sources, recipe, psf, TRUE);
+
+    if (0) { 
+	FILE *out = fopen ("out.pass2.dat", "w");
+	
+	for (int i = 0; i < sources->n; i++) {
+	    pmSource *source = sources->data[i];
+	    if (!source) continue;
+	    pmPeak *peak = source->peak;
+	    if (!peak) continue;
+	    pmModel *model = source->modelPSF;
+	    if (!model) continue;
+	    if (!model->params) continue;
+	    
+	    fprintf (out, "%d %f %f  %f %f  %f %f\n", 
+		     i, peak->xf, peak->yf, peak->flux, peak->SN,
+		     model->params->data.F32[PM_PAR_I0],
+		     model->dparams->data.F32[PM_PAR_I0]);
+	}
+	fclose (out);
+    }
+
+pass1finish:
+
+    // measure source size for the remaining sources
+    psphotSourceSize (readout, sources, recipe, 0);
+
+    psphotExtendedSourceAnalysis (readout, sources, recipe);
+
+    psphotExtendedSourceFits (readout, sources, recipe);
+
+finish:
+
+    // plot positive sources 
+    // psphotSourcePlots (readout, sources, recipe); 
+
+    // measure aperture photometry corrections
+    if (!psphotApResid (readout, sources, recipe, psf)) {
+        psLogMsg ("psphot", 3, "failed on psphotApResid");
+        return psphotReadoutCleanup (config, readout, recipe, detections, psf, sources);
+    }
+
+    // calculate source magnitudes
+    pmReadout *background = psphotSelectBackground (config, view, false);
+    psphotMagnitudes(sources, recipe, psf, background);
+
+    // replace failed sources?
+    // psphotReplaceUnfitSources (sources);
+
+    // replace background in residual image
+    psphotSkyReplace (config, view);
+
+    // drop the references to the image pixels held by each source
+    psphotSourceFreePixels (sources);
+
+    // create the exported-metadata and free local data
+    return psphotReadoutCleanup(config, readout, recipe, detections, psf, sources);
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotReadoutCleanup.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotReadoutCleanup.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotReadoutCleanup.c	(revision 22157)
@@ -0,0 +1,88 @@
+# include "psphotInternal.h"
+
+// psphotReadoutCleanup is called on exit from psphotReadout.  If the last raised error is
+// not a DATA error, then there was a serious problem.  Only in this case, or if the fail
+// on the stats measurement, do we return false
+bool psphotReadoutCleanup (pmConfig *config, pmReadout *readout, psMetadata *recipe, pmDetections *detections, pmPSF *psf, psArray *sources) {
+
+    // remove internal pmFPAfiles, if created
+    bool status = true;
+    status &= pmFPAfileDropInternal (config->files, "PSPHOT.BACKMDL");
+    status &= pmFPAfileDropInternal (config->files, "PSPHOT.BACKMDL.STDEV");
+    status &= pmFPAfileDropInternal (config->files, "PSPHOT.BACKGND");
+    if (!status) {
+	psError(PSPHOT_ERR_PROG, false, "trouble dropping internal files");
+	psFree (psf);
+	psFree (sources);
+	psFree (detections);
+	return false;
+    }
+
+    if (psErrorCodeLast() == PSPHOT_ERR_DATA) {
+        psErrorStackPrint(stderr, "Error in the psphot readout analysis");
+	psErrorClear();
+    } 
+    if (psErrorCodeLast() != PS_ERR_NONE) { 
+	psFree (psf);
+	psFree (sources);
+	psFree (detections);
+	return false;
+    }
+
+    // use the psf-model to measure FWHM stats
+    if (psf) {
+        if (!psphotPSFstats (readout, recipe, psf)) {
+            psError(PSPHOT_ERR_PROG, false, "Failed to measure PSF shape parameters");
+	    psFree (psf);
+	    psFree (sources);
+	    psFree (detections);
+            return false;
+        }
+    }
+    // otherwise, use the source moments to measure FWHM stats
+    if (!psf && sources) {
+        if (!psphotMomentsStats (readout, recipe, sources)) {
+            psError(PSPHOT_ERR_PROG, false, "Failed to measure Moment shape parameters");
+	    psFree (psf);
+	    psFree (sources);
+	    psFree (detections);
+            return false;
+        }
+    }
+
+    // write NSTARS to the image header
+    psphotSetHeaderNstars (recipe, sources);
+
+    // create an output header with stats results
+    psMetadata *header = psphotDefineHeader (recipe);
+
+    // save the results of the analysis
+    psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.HEADER",  PS_DATA_METADATA, "header stats", header);
+    if (sources) {
+	psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.SOURCES", PS_DATA_ARRAY, "psphot sources", sources);
+    }
+    if (psf) {
+	// save the psf for possible output.  if there was already an entry, it was loaded from external sources
+	// the new one may have been updated or modified, so replace the existing entry.  We
+	// are required to save it on the chip, but this will cause problems if we ever want to
+	// run psphot on an unmosaiced image
+	pmCell *cell = readout->parent;
+	pmChip *chip = cell->parent;
+	psMetadataAdd (chip->analysis, PS_LIST_TAIL, "PSPHOT.PSF", PS_DATA_UNKNOWN | PS_META_REPLACE,  "psphot psf", psf);
+    }
+
+    if (psErrorCodeLast() != PS_ERR_NONE) {
+        psErrorStackPrint(stderr, "unexpected remaining errors");
+	abort();
+    }
+
+    // XXX move this to top of loop?
+    pmKapaClose ();
+
+    psFree (detections);
+    psFree (psf);
+    psFree (header);
+    psFree (sources);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotReplaceUnfit.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotReplaceUnfit.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotReplaceUnfit.c	(revision 22157)
@@ -0,0 +1,115 @@
+# include "psphotInternal.h"
+
+// replace the flux for sources which failed
+bool psphotReplaceUnfitSources (psArray *sources, psMaskType maskVal) {
+
+    pmSource *source;
+
+    psTimerStart ("psphot");
+
+    for (int i = 0; i < sources->n; i++) {
+      source = sources->data[i];
+
+      // replace other sources?
+      if (source->mode & PM_SOURCE_MODE_FAIL) goto replace;
+      continue;
+
+    replace:
+        pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+        source->mode &= ~PM_SOURCE_MODE_SUBTRACTED;
+    }
+    psLogMsg ("psphot.replace", 3, "replace unfitted models: %f sec (%ld objects)\n", psTimerMark ("psphot"), sources->n);
+    return true;
+}
+
+bool psphotReplaceAllSources (psArray *sources, psMetadata *recipe) {
+
+    bool status;
+    pmSource *source;
+
+    psTimerStart ("psphot");
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    for (int i = 0; i < sources->n; i++) {
+      source = sources->data[i];
+
+      // replace other sources?
+      if (!(source->mode & PM_SOURCE_MODE_SUBTRACTED)) continue;
+
+      pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+      source->mode &= ~PM_SOURCE_MODE_SUBTRACTED;
+    }
+    psLogMsg ("psphot.replace", PS_LOG_INFO, "replaced models for %ld objects: %f sec\n", sources->n, psTimerMark ("psphot"));
+    return true;
+}
+
+bool psphotRemoveAllSources (psArray *sources, psMetadata *recipe) {
+
+    bool status;
+    pmSource *source;
+
+    psTimerStart ("psphot");
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    for (int i = 0; i < sources->n; i++) {
+      source = sources->data[i];
+
+      // replace other sources?
+      if (source->mode & PM_SOURCE_MODE_SUBTRACTED) continue;
+
+      pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+      source->mode |= PM_SOURCE_MODE_SUBTRACTED;
+    }
+    psLogMsg ("psphot.replace", PS_LOG_INFO, "replaced models for %ld objects: %f sec\n", sources->n, psTimerMark ("psphot"));
+    return true;
+}
+
+// add source, if the source has been subtracted (or if we ignore the state)
+bool psphotAddWithTest (pmSource *source, bool useState, psMaskType maskVal) {
+
+    // what is current state? (true : add; false : sub)
+    bool state = !(source->mode & PM_SOURCE_MODE_SUBTRACTED);
+    if (state && useState) return true;
+
+    // replace the model if 1) state says it is missing or 2) useState is false (just do it)
+    if (!state || !useState) {
+        pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+    }
+    return true;
+}
+
+// sub source, if the source has been added (or if we ignore the state)
+bool psphotSubWithTest (pmSource *source, bool useState, psMaskType maskVal) {
+
+    // what is current state? (true : sub; false : add)
+    bool state = (source->mode & PM_SOURCE_MODE_SUBTRACTED);
+    if (state && useState) return true;
+
+    // replace the model if 1) state says it is missing or 2) useState is false (just do it)
+    if (!state || !useState) {
+        pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+    }
+    return true;
+}
+
+// add or sub source replace or if the source has
+bool psphotSetState (pmSource *source, bool curState, psMaskType maskVal) {
+
+    // what is desired state? (true : add; false : sub)
+    bool newState = !(source->mode & PM_SOURCE_MODE_SUBTRACTED);
+    if (curState == newState) return true;
+
+    if (curState && !newState) {
+        pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+    }
+    if (newState && !curState) {
+        pmSourceAdd (source, PM_MODEL_OP_FULL, maskVal);
+    }
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotRoughClass.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotRoughClass.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotRoughClass.c	(revision 22157)
@@ -0,0 +1,60 @@
+# include "psphotInternal.h"
+
+# define CHECK_STATUS(S,MSG) { \
+  if (!status) { \
+    psError(PSPHOT_ERR_CONFIG, false, "missing PSF Clump entry: %s\n", MSG); \
+    return false; \
+  } }
+
+// 2006.02.02 : no leaks
+bool psphotRoughClass (psArray *sources, psMetadata *recipe, const bool havePSF) {
+
+    bool status;
+    static pmPSFClump psfClump;
+
+    psTimerStart ("psphot");
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskSat = psMetadataLookupU8(&status, recipe, "MASK.SAT"); // Mask value for bad pixels
+    assert (maskSat);
+
+    if (!havePSF) {
+        // determine the PSF parameters from the source moment values
+        psfClump = pmSourcePSFClump (sources, recipe);
+	psMetadataAddF32 (recipe, PS_LIST_TAIL, "PSF.CLUMP.X",  PS_META_REPLACE, "psf clump center", psfClump.X);
+	psMetadataAddF32 (recipe, PS_LIST_TAIL, "PSF.CLUMP.Y",  PS_META_REPLACE, "psf clump center", psfClump.Y);
+	psMetadataAddF32 (recipe, PS_LIST_TAIL, "PSF.CLUMP.DX", PS_META_REPLACE, "psf clump center", psfClump.dX);
+	psMetadataAddF32 (recipe, PS_LIST_TAIL, "PSF.CLUMP.DY", PS_META_REPLACE, "psf clump center", psfClump.dY);
+    } else {
+        // pull FWHM_X,Y from the recipe, use to define psfClump.X,Y
+        psfClump.X  = psMetadataLookupF32 (&status, recipe, "PSF.CLUMP.X");  CHECK_STATUS (status, "PSF.CLUMP.X");
+        psfClump.Y  = psMetadataLookupF32 (&status, recipe, "PSF.CLUMP.Y");  CHECK_STATUS (status, "PSF.CLUMP.Y");
+        psfClump.dX = psMetadataLookupF32 (&status, recipe, "PSF.CLUMP.DX");  CHECK_STATUS (status, "PSF.CLUMP.DX");
+        psfClump.dY = psMetadataLookupF32 (&status, recipe, "PSF.CLUMP.DY");  CHECK_STATUS (status, "PSF.CLUMP.DY");
+    }
+
+    if (psfClump.X < 0) {
+        psError(PSPHOT_ERR_PROG, false, "programming error calling pmSourcePSFClump");
+        return false;
+    }
+    if (!psfClump.X || !psfClump.Y) {
+        psError(PSPHOT_ERR_DATA, true, "Failed to find a valid PSF clump");
+        return false;
+    }
+    psLogMsg ("psphot", 3, "psf clump  X,  Y: %f, %f\n", psfClump.X, psfClump.Y);
+    psLogMsg ("psphot", 3, "psf clump DX, DY: %f, %f\n", psfClump.dX, psfClump.dY);
+
+    // group into STAR, COSMIC, EXTENDED, SATURATED, etc.
+    if (!pmSourceRoughClass (sources, recipe, psfClump, maskSat)) {
+        psError(PSPHOT_ERR_PROG, false, "programming error calling pmSourceRoughClass");
+        return false;
+    }
+
+    // optional printout of source moments only
+    psphotDumpMoments (recipe, sources);
+
+    psLogMsg ("psphot.roughclass", PS_LOG_INFO, "rough classification: %f sec\n", psTimerMark ("psphot"));
+
+    return true;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSavePSFStars.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSavePSFStars.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSavePSFStars.c	(revision 22157)
@@ -0,0 +1,148 @@
+# include "psphotInternal.h"
+
+bool psphotSourcePlots (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+
+    // bool status = false;
+    psTimerStart ("psphot");
+
+    // if this file is defined, create the necessary output data
+    pmFPAfile *file = psMetadataLookupPtr(&status, config->files, "PSPHOT.PSF.STARS");
+
+    // the source images are written to an image 10x the size of a PSF object
+    // float OUTER = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
+    // PS_ASSERT (status, false);
+
+    int DX = 21;
+    int DY = 21;
+
+    // examine PSF sources in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // counters to track the size of the image and area used in a row
+    int dX = 0;				// starting corner of next box
+    int dY = 0;				// height of row so far
+    int NX = 20*DX;			// full width of output image
+    int NY = 0;				// total height of output image
+
+    // first, examine the PSF and SAT stars:
+    // - determine bounding boxes for summary image
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+
+	bool keep = false;
+        keep |= (source->mode & PM_SOURCE_MODE_PSFSTAR);
+        keep |= (source->mode & PM_SOURCE_MODE_SATSTAR);
+	if (!keep) continue;
+
+	// how does this subimage get placed into the output image?
+	// DX = source->pixels->numCols
+	// DY = source->pixels->numRows
+
+	if (dX + DX > NX) {
+	    // too wide for the rest of this row
+	    if (dX == 0) {
+		// alone on this row
+		NY += DY;
+		dX = 0;
+		dY = 0;
+	    } else {
+		// start the next row
+		NY += dY;
+		dX = DX;
+		dY = DY;
+	    }
+	} else {
+	    // extend this row
+	    dX += DX;
+	    dY = PS_MAX (dY, DY);
+	}
+    }
+
+    // allocate output image
+    psImage *outpos = psImageAlloc (NX, NY, PS_TYPE_F32);
+    psImage *outsub = psImageAlloc (NX, NY, PS_TYPE_F32);
+
+    int Xo = 0;				// starting corner of next box
+    int Yo = 0;				// starting corner of next box
+    dY = 0;				// height of row so far
+
+    int nPSF = 0;
+    int nSAT = 0;
+    int kapa = 0;			// file descriptor for plotting routine
+
+    // first, examine the PSF and SAT stars:
+    // - generate radial plots (PS plots)
+    // - create output image array
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+
+	bool keep = false;
+        if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+	    nPSF ++;
+	    keep = true;
+	}
+        if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	    nSAT ++;
+	    keep = true;
+	}	    
+	if (!keep) continue;
+
+	// how does this subimage get placed into the output image?
+	// DX = source->pixels->numCols
+	// DY = source->pixels->numRows
+
+	if (Xo + DX > NX) {
+	    // too wide for the rest of this row
+	    if (Xo == 0) {
+		// place source alone on this row
+		psphotAddWithTest (source, true); // replace source if subtracted
+		psphotRadialPlot (&kapa, "radial.plots.ps", source);
+		psphotMosaicSubimage (outpos, source, Xo, Yo, DX, DY);
+
+		psphotSubWithTest (source, false); // remove source (force)
+		psphotMosaicSubimage (outsub, source, Xo, Yo, DX, DY);
+
+		psphotSetState (source, false); // replace source (has been subtracted)
+		Yo += DY;
+		Xo = 0;
+		dY = 0;
+	    } else {
+		// start the next row
+		Yo += dY;
+		Xo = 0;
+		psphotAddWithTest (source, true); // replace source if subtracted
+		psphotRadialPlot (&kapa, "radial.plots.ps", source);
+		psphotMosaicSubimage (outpos, source, Xo, Yo, DX, DY);
+
+		psphotSubWithTest (source, false); // remove source (force)
+		psphotMosaicSubimage (outsub, source, Xo, Yo, DX, DY);
+		psphotSetState (source, false); // replace source (has been subtracted)
+
+		Xo = DX;
+		dY = DY;
+	    }
+	} else {
+	    // extend this row
+	    psphotAddWithTest (source, true); // replace source if subtracted
+	    psphotRadialPlot (&kapa, "radial.plots.ps", source);
+	    psphotMosaicSubimage (outpos, source, Xo, Yo, DX, DY);
+
+	    psphotSubWithTest (source, false); // remove source (force)
+	    psphotMosaicSubimage (outsub, source, Xo, Yo, DX, DY);
+	    psphotSetState (source, false); // replace source (has been subtracted)
+
+	    Xo += DX;
+	    dY = PS_MAX (dY, DY);
+	}
+    }
+
+    psphotSaveImage (NULL, outpos, "outpos.fits");
+    psphotSaveImage (NULL, outsub, "outsub.fits");
+    psLogMsg ("psphot", PS_LOG_INFO, "plotted %d sources (%d psf, %d sat): %f sec\n", nPSF + nSAT, nPSF, nSAT, psTimerMark ("psphot"));
+
+    psFree (outpos);
+    psFree (outsub);
+    return (sources);
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSetMaskBits.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSetMaskBits.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSetMaskBits.c	(revision 22157)
@@ -0,0 +1,32 @@
+# include "psphotInternal.h"
+
+// XXX Should it be an error for any of these to not exist?
+
+// This function is called by the stand-alone psphot program to set the mask values in the
+// config file.  It sets the named mask values MASK.PSPHOT and MARK.PSPHOT in the PSPHOT
+// recipe.  Functions or programs which call psphotReadout as a library function must set these
+// named mask values in the PSPHOT recipe on their own.
+
+bool psphotSetMaskBits (pmConfig *config) {
+
+    psMaskType maskValue;
+    psMaskType markValue;
+
+    if (!pmConfigMaskSetBits (&maskValue, &markValue, config)) {
+	psError (PS_ERR_UNKNOWN, true, "Unable to define the mask bit values");
+	return false;
+    }
+
+    // select the current recipe
+    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSPHOT_RECIPE);
+    if (!recipe) {
+        psError(PSPHOT_ERR_CONFIG, false, "missing recipe %s", PSPHOT_RECIPE);
+        return false;
+    }
+
+    // set maskValue and markValue in the psphot recipe
+    psMetadataAddU8 (recipe, PS_LIST_TAIL, "MARK.PSPHOT", PS_META_REPLACE, "user-defined mask", markValue);
+    psMetadataAddU8 (recipe, PS_LIST_TAIL, "MASK.PSPHOT", PS_META_REPLACE, "user-defined mask", maskValue);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSignificanceImage.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSignificanceImage.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSignificanceImage.c	(revision 22157)
@@ -0,0 +1,119 @@
+# include "psphotInternal.h"
+
+// In this function, we smooth the image and weight, then generate the significance image :
+// (S/N)^2.  If FWMH_X,Y have been recorded, use them, otherwise use PEAKS_SMOOTH_SIGMA for the
+// smoothing kernel.
+psImage *psphotSignificanceImage (pmReadout *readout, psMetadata *recipe, const int pass, psMaskType maskVal) {
+
+    float SIGMA_SMTH, NSIGMA_SMTH;
+    bool status = false;
+    bool guess = false;
+
+    // smooth the image and weight map
+    psTimerStart ("smooth");
+
+    bool status_x, status_y;
+    float FWHM_X = psMetadataLookupF32 (&status_x, recipe, "FWHM_X");
+    float FWHM_Y = psMetadataLookupF32 (&status_y, recipe, "FWHM_Y");
+    if (status_x && status_y) {
+      // if we know the FHWM, use that to set the smoothing kernel (XXX allow an optional override?)
+      SIGMA_SMTH  = 0.5*(FWHM_X + FWHM_Y) / (2.0*sqrt(2.0*log(2.0)));
+      NSIGMA_SMTH = psMetadataLookupF32 (&status, recipe, "PEAKS_SMOOTH_NSIGMA");
+      guess = false;
+    } else {
+      // if we do not know the FWHM, use the guess smoothing kernel supplied.
+      // it is a configuration error if these are not supplied
+      SIGMA_SMTH  = psMetadataLookupF32 (&status, recipe, "PEAKS_SMOOTH_SIGMA"); 
+      PS_ASSERT (status, NULL);
+      NSIGMA_SMTH = psMetadataLookupF32 (&status, recipe, "PEAKS_SMOOTH_NSIGMA"); 
+      PS_ASSERT (status, NULL);
+      guess = true;
+    }
+    // record the actual smoothing sigma
+    psMetadataAddF32  (recipe, PS_LIST_TAIL, "SIGMA_SMOOTH", PS_META_REPLACE, "Smoothing sigma for detections", SIGMA_SMTH);
+
+    // smooth the image, applying the mask as we go
+    psImage *smooth_im = psImageCopy (NULL, readout->image, PS_TYPE_F32);
+    psImageSmoothMaskF32 (smooth_im, readout->mask, maskVal, SIGMA_SMTH, NSIGMA_SMTH);
+    psLogMsg ("psphot", PS_LOG_MINUTIA, "smooth image: %f sec\n", psTimerMark ("smooth"));
+
+    // Smooth the weight, applying the mask as we go.  The variance is smoothed by the PSF^2,
+    // renomalized to maintain the input level of the variance.  We achieve this by smoothing
+    // with a Gaussian with sigma = SIGMA_SMTH/sqrt(2) with unity normalization.  Note that
+    // this process yields a smoothed image with correlated errors.  The pixel-to-pixel
+    // variations in smooth_im will be decreased by a factor of 4*pi*SIGMA_SMTH^2, but for
+    // measurements based on apertures comparable to or larger than the smoothing kernel, the
+    // effective per-pixel variance is maintained.
+    psImage *smooth_wt = psImageCopy (NULL, readout->weight, PS_TYPE_F32);
+    psImageSmoothMaskF32 (smooth_wt, readout->mask, maskVal, SIGMA_SMTH/sqrt(2), NSIGMA_SMTH);
+    psLogMsg ("psphot", PS_LOG_MINUTIA, "smooth weight: %f sec\n", psTimerMark ("smooth"));
+
+    psImage *mask = readout->mask;
+
+    // optionally save example images under trace
+    // XXX change these to recipe value checks
+    if (psTraceGetLevel("psphot") > 5) {
+        char name[64];
+        sprintf (name, "imsmooth.v%d.fits", pass);
+        psphotSaveImage (NULL, smooth_im, name);
+        sprintf (name, "wtsmooth.v%d.fits", pass);
+        psphotSaveImage (NULL, smooth_wt, name);
+    }
+
+    // We have an input image with PSF size sigma_r.  We have smoothed it with a kernel of size
+    // sigma_s.  The result is an image with PSF size sigma_o: sigma_o^2 = sigma_r^2 +
+    // sigma_s^2.  Ideally, we are choosing sigma_s = sigma_r, in which case sigma_o^2 = 2
+    // sigma_s^2.  If we do not know the input image PSF size (initial detection stage), then
+    // we are assuming that sigma_r = sigma_s.  
+
+    // Build the significance image on top of smooth_im.  We need to correct the ratio im/wt by
+    // 2 factors: 1) the relationship the peak value and the integrated flux, and 2) the
+    // relationship between the per-pixel variance (var_i) and the total variance included in
+    // the flux measurement (effective area).  These latter correction comes from: flux = Io *
+    // 2\pi\sigma_o^2 and total variance = var_i * 4\pi\sigma_o^2, thus (S/N)^2 = flux^2 / var
+    // = (Io/var_i) \pi sigma_o^2
+
+    // in fact, because of the smoothing and the resulting correlated errors, the per-pixel
+    // varience is a bit smaller than the value predicted here.  The total variance should be
+    // \alpha 4\pi(\sigma_r_^2 + \sigma_s^2) where \alpha approaches 1 for \sigma_s / \sigma_r
+    // << 1, and \alpha approaches 0.5 for \sigma_s / \sigma_r >> 1, with a value of 0.7
+    // (actually 1/1.5) for \sigma_s = \sigma_r
+
+    // if the smoothing kernel size is known to be the psf size (\sigma_s = \sigma_r), then we
+    // can use the scaling factor of 1/1.5; otherwise, we want to be conservative and use \alpha
+    // = 0.5.  Thus, for the guess smoothing kernel, (S/N)^2 = 3.0 (Io/var_i) \pi sigma_s^2 for
+    // the guess case and (S/N)^2 = 2.0 (Io/var_i) \pi sigma_s^2 for the known case
+
+    // XXX hmm, somehow the simulations are coming up with 4pi, not 3pi (as if \alpha is 1/2.0,
+    // not 1/1.5 for sigma_s = sigma_r...
+
+    float factor = guess ? 4.0 * M_PI * PS_SQR(SIGMA_SMTH) : 4.0 * M_PI * PS_SQR(SIGMA_SMTH);
+    // record the effective area and significance scaling factor
+    float effArea = 8.0 * M_PI * PS_SQR(SIGMA_SMTH);
+    psMetadataAddF32  (recipe, PS_LIST_TAIL, "EFFECTIVE_AREA", PS_META_REPLACE, "Effective Area", effArea);
+    psMetadataAddF32  (recipe, PS_LIST_TAIL, "SIGNIFICANCE_SCALE_FACTOR", PS_META_REPLACE, "Signicance scale factor", factor);
+
+    for (int j = 0; j < smooth_im->numRows; j++) {
+        for (int i = 0; i < smooth_im->numCols; i++) {
+            float value = smooth_im->data.F32[j][i];
+            if (value < 0 || smooth_wt->data.F32[j][i] <= 0 || (mask->data.U8[j][i] & maskVal)) {
+                smooth_im->data.F32[j][i] = 0.0;
+            } else {
+                smooth_im->data.F32[j][i] = factor * PS_SQR(value) / smooth_wt->data.F32[j][i];
+            }
+        }
+    }
+    psLogMsg ("psphot", PS_LOG_INFO, "built smoothed signficance image: %f sec\n", psTimerMark ("smooth"));
+
+    // optionally save example images under trace
+    // XXX change these to recipe value checks
+    if (psTraceGetLevel("psphot") > 5) {
+        char name[64];
+        sprintf (name, "snsmooth.v%d.fits", pass);
+        psphotSaveImage (NULL, smooth_im, name);
+    }
+
+    psFree (smooth_wt);
+    return smooth_im;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSkyReplace.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSkyReplace.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSkyReplace.c	(revision 22157)
@@ -0,0 +1,33 @@
+# include "psphotInternal.h"
+
+// XXX make this an option?
+// in order to  successfully replace the sky, we must define a corresponding file...
+bool psphotSkyReplace (pmConfig *config, const pmFPAview *view) {
+
+    psTimerStart ("psphot");
+
+    // find the currently selected readout
+    pmReadout *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
+    if (readout == NULL) psAbort("input not defined");
+
+    // select background pixels, from output background file, or create
+    pmReadout *background = pmFPAfileThisReadout (config->files, view, "PSPHOT.BACKGND");
+    if (background == NULL) psAbort("background not defined");
+
+    // select the corresponding images
+    psF32 **image = readout->image->data.F32;
+    psU8  **mask  = readout->mask->data.U8;
+    psF32 **back  = background->image->data.F32;
+
+    // replace the background model
+    for (int j = 0; j < readout->image->numRows; j++) {
+        for (int i = 0; i < readout->image->numCols; i++) {
+            if (!mask[j][i]) {
+                image[j][i] += back[j][i];
+            }
+        }
+    }
+    psLogMsg ("psphot.sky", PS_LOG_DETAIL, "replace background flux : %f sec\n", psTimerMark ("psphot"));
+    return true;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSortBySN.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSortBySN.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSortBySN.c	(revision 22157)
@@ -0,0 +1,1 @@
+
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSourceFits.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSourceFits.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSourceFits.c	(revision 22157)
@@ -0,0 +1,386 @@
+# include "psphotInternal.h"
+
+// given a source with an existing modelPSF, attempt a full PSF fit, subtract if successful
+// XXX this function does not call pmSourceFitModelInit : fix this?
+
+static int NfitPSF = 0;
+static int NfitBlend = 0;
+static int NfitDBL = 0;
+static int NfitEXT = 0;
+
+bool psphotFitInit () {
+    psTimerStart ("psphot.fits");
+    return true;
+}
+
+bool psphotFitSummary () {
+
+    fprintf (stderr, "fitted %5d psf, %5d blend, %5d ext, %5d dbl : %6.2f sec\n",
+             NfitPSF, NfitBlend, NfitEXT, NfitDBL, psTimerMark ("psphot.fits"));
+    return true;
+}
+
+bool psphotFitBlend (pmReadout *readout, pmSource *source, pmPSF *psf, psMaskType maskVal, psMaskType markVal) {
+
+    float x, y, dR;
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // if this source is not a possible blend, just fit as PSF
+    if ((source->blends == NULL) || (source->mode & PM_SOURCE_MODE_SATSTAR)) {
+        bool status = psphotFitPSF (readout, source, psf, maskVal, markVal);
+        return status;
+    }
+
+    // save the PSF model from the Ensemble fit
+    pmModel *PSF = pmModelCopy (source->modelPSF);
+
+    if (isnan(PSF->params->data.F32[PM_PAR_I0])) psAbort("nan in blend fit primary");
+
+    x = PSF->params->data.F32[PM_PAR_XPOS];
+    y = PSF->params->data.F32[PM_PAR_YPOS];
+
+    psArray *modelSet = psArrayAllocEmpty (source->blends->n + 1);
+    psArrayAdd (modelSet, 16, PSF);
+
+    psArray *sourceSet = psArrayAllocEmpty (source->blends->n + 1);
+    psArrayAdd (sourceSet, 16, source);
+
+    // we need to include all blends in the fit (unless primary is saturated?)
+    dR = 0;
+    for (int i = 0; i < source->blends->n; i++) {
+        pmSource *blend = source->blends->data[i];
+
+        // find the blend which is furthest from source
+        dR = PS_MAX (dR, hypot (blend->peak->xf - x, blend->peak->yf - y));
+
+        // create the model and guess parameters for this blend
+        pmModel *model = pmModelAlloc (PSF->type);
+        for (int j = 0; j < model->params->n; j++) {
+            model->params->data.F32[j] = PSF->params->data.F32[j];
+            model->dparams->data.F32[j] = PSF->dparams->data.F32[j];
+        }
+
+        // XXX assume local sky is 0.0?
+        model->params->data.F32[PM_PAR_I0] = blend->peak->flux;
+        model->params->data.F32[PM_PAR_XPOS] = blend->peak->xf;
+        model->params->data.F32[PM_PAR_YPOS] = blend->peak->yf;
+
+        // these should never be invalid values
+        // XXX drop these tests eventually
+        if (isnan(model->params->data.F32[PM_PAR_I0]))   psAbort("nan in blend fit");
+        if (isnan(model->params->data.F32[PM_PAR_XPOS])) psAbort("nan in blend fit");
+        if (isnan(model->params->data.F32[PM_PAR_YPOS])) psAbort("nan in blend fit");
+
+        // add this blend to the list
+        psArrayAdd (modelSet, 16, model);
+        psArrayAdd (sourceSet, 16, blend);
+
+        // free to avoid double counting model
+        psFree (model);
+    }
+
+    // extend source radius as needed
+    psphotCheckRadiusPSFBlend (readout, source, PSF, markVal, dR);
+
+    // fit PSF model (set/unset the pixel mask)
+    pmSourceFitSet (source, modelSet, PM_SOURCE_FIT_PSF, maskVal);
+
+    // correct model chisq for flux trend
+    double chiTrend = psPolynomial1DEval (psf->ChiTrend, PSF->params->data.F32[PM_PAR_I0]);
+    PSF->chisqNorm = PSF->chisq / chiTrend;
+
+    // evaluate the blend objects, subtract if good, free otherwise
+    for (int i = 1; i < modelSet->n; i++) {
+        pmSource *blend = sourceSet->data[i];
+        pmModel *model  = modelSet->data[i];
+
+        // correct model chisq for flux trend
+        chiTrend = psPolynomial1DEval (psf->ChiTrend, model->params->data.F32[PM_PAR_I0]);
+        model->chisqNorm = model->chisq / chiTrend;
+
+        // if this one failed, skip it
+        if (!psphotEvalPSF (blend, model)) {
+            psTrace ("psphot", 4, "failed on blend %d of %ld\n", i, modelSet->n);
+            continue;
+        }
+
+        // otherwise, supply the resulting model to the corresponding blend
+        psFree(blend->modelPSF);
+        blend->modelPSF = psMemIncrRefCounter (model);
+        psTrace ("psphot", 5, "fitted blend as PSF\n");
+
+        // build cached model and subtract
+        pmSourceCacheModel (blend, maskVal);
+        pmSourceSub (blend, PM_MODEL_OP_FULL, maskVal);
+        blend->mode |=  PM_SOURCE_MODE_SUBTRACTED;
+    }
+    NfitBlend += modelSet->n;
+
+    // evaluate the primary object
+    if (!psphotEvalPSF (source, PSF)) {
+        psTrace ("psphot", 4, "failed on blend 0 of %ld\n", modelSet->n);
+        psFree (PSF);
+        psFree (modelSet);
+        psFree (sourceSet);
+        return false;
+    }
+    psFree (modelSet);
+    psFree (sourceSet);
+
+    // save the new, successful model
+    psFree (source->modelPSF);
+    source->modelPSF = PSF;
+    psTrace ("psphot", 5, "fitted primary as PSF\n");
+
+    // build cached model and subtract
+    pmSourceCacheModel (source, maskVal);
+    pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+    source->mode |=  PM_SOURCE_MODE_SUBTRACTED;
+    return true;
+}
+
+bool psphotFitPSF (pmReadout *readout, pmSource *source, pmPSF *psf, psMaskType maskVal, psMaskType markVal) {
+
+    double chiTrend;
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    NfitPSF ++;
+
+    // save the PSF model from the Ensemble fit
+    pmModel *PSF = pmModelCopy (source->modelPSF);
+    if (isnan(PSF->params->data.F32[1])) psAbort("nan in psf fit");
+
+    // extend source radius as needed
+    psphotCheckRadiusPSF (readout, source, PSF, markVal);
+
+    // fit PSF model (set/unset the pixel mask)
+    pmSourceFitModel (source, PSF, PM_SOURCE_FIT_PSF, maskVal);
+
+    // correct model chisq for flux trend
+    chiTrend = psPolynomial1DEval (psf->ChiTrend, PSF->params->data.F32[PM_PAR_I0]);
+    PSF->chisqNorm = PSF->chisq / chiTrend;
+
+    // does the PSF model succeed?
+    if (!psphotEvalPSF (source, PSF)) {
+        psFree (PSF);
+        return false;
+    }
+
+    // free old model, save new model
+    psFree (source->modelPSF);
+    source->modelPSF = PSF;
+    psTrace ("psphot", 5, "fitted as PSF\n");
+
+    // build cached model and subtract
+    pmSourceCacheModel (source, maskVal);
+    pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+    source->mode |=  PM_SOURCE_MODE_SUBTRACTED;
+    return true;
+}
+
+static float EXT_MIN_SN;
+static float EXT_MOMENTS_RAD;
+static pmModelType modelTypeEXT;
+
+bool psphotInitLimitsEXT (psMetadata *recipe) {
+
+    bool status;
+
+    // extended source model parameters
+    EXT_MIN_SN       = psMetadataLookupF32 (&status, recipe, "EXT_MIN_SN");
+    EXT_MOMENTS_RAD  = psMetadataLookupF32 (&status, recipe, "EXT_MOMENTS_RADIUS");
+
+    // extended source model descriptions
+    char *modelNameEXT = psMetadataLookupStr (&status, recipe, "EXT_MODEL");
+    modelTypeEXT = pmModelClassGetType (modelNameEXT);
+    psphotInitRadiusEXT (recipe, modelTypeEXT);
+
+    return true;
+}
+
+bool psphotFitBlob (pmReadout *readout, pmSource *source, psArray *sources, pmPSF *psf, psMaskType maskVal, psMaskType markVal) {
+
+    bool okEXT, okDBL;
+    float chiEXT, chiDBL;
+    double chiTrend;
+    pmModel *ONE = NULL;
+
+    // skip the source if we don't think it is extended
+    if (source->type == PM_SOURCE_TYPE_UNKNOWN) return false;
+    if (source->type == PM_SOURCE_TYPE_DEFECT) return false;
+    if (source->type == PM_SOURCE_TYPE_SATURATED) return false;
+    if (source->moments->SN < EXT_MIN_SN) return false;
+
+    // recalculate the source moments using the larger extended-source moments radius
+    if (!pmSourceMoments (source, EXT_MOMENTS_RAD)) return false;
+
+    psTrace ("psphot", 5, "trying blob...\n");
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // this temporary source is used as a place-holder by the psphotEval functions below
+    pmSource *tmpSrc = pmSourceAlloc ();
+
+    pmModel *EXT = psphotFitEXT (readout, source, modelTypeEXT, maskVal, markVal);
+    okEXT = psphotEvalEXT (tmpSrc, EXT);
+    chiEXT = EXT->chisq / EXT->nDOF;
+
+    psArray *DBL = psphotFitDBL (readout, source, maskVal, markVal);
+    okDBL  = psphotEvalDBL (tmpSrc, DBL->data[0]);
+    okDBL &= psphotEvalDBL (tmpSrc, DBL->data[1]);
+    // XXX should I keep / save the flags set in the eval functions?
+
+    // correct first model chisqs for flux trend
+    ONE = DBL->data[0];
+    chiTrend = psPolynomial1DEval (psf->ChiTrend, ONE->params->data.F32[1]);
+    ONE->chisqNorm = ONE->chisq / chiTrend;
+
+    // save chisq for double-star/galaxy comparison
+    chiDBL = ONE->chisq / ONE->nDOF;
+
+    // correct second model chisqs for flux trend
+    ONE = DBL->data[1];
+    chiTrend = psPolynomial1DEval (psf->ChiTrend, ONE->params->data.F32[1]);
+    ONE->chisqNorm = ONE->chisq / chiTrend;
+
+    psFree (tmpSrc);
+
+    // psTraceSetLevel("psModules.objects.pmSourceFitSet", 0);
+
+    if (okEXT && okDBL) {
+        psTrace ("psphot", 5, "blob chisq: %f vs %f\n", chiEXT, chiDBL);
+        // XXX EAM : a bogus bias: need to examine this better
+        if (3*chiEXT > chiDBL) goto keepDBL;
+        goto keepEXT;
+    }
+
+    if (okEXT && !okDBL) goto keepEXT;
+    if (!okEXT && okDBL) goto keepDBL;
+
+    // both models failed; reject them both
+    psFree (EXT);
+    psFree (DBL);
+    return false;
+
+keepEXT:
+    // sub EXT
+    psFree (DBL);
+
+    // save new model
+    source->modelEXT = EXT;
+    source->type = PM_SOURCE_TYPE_EXTENDED;
+    source->mode |= PM_SOURCE_MODE_EXTMODEL;
+
+    // build cached model and subtract
+    pmSourceCacheModel (source, maskVal);
+    pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+    source->mode |=  PM_SOURCE_MODE_SUBTRACTED;
+    psTrace ("psphot", 5, "blob as EXT: %f %f\n", EXT->params->data.F32[PM_PAR_XPOS], EXT->params->data.F32[PM_PAR_YPOS]);
+    return true;
+
+keepDBL:
+    // sub DLB
+    psFree (EXT);
+
+    // drop old model, save new second model...
+    psFree (source->modelPSF);
+    source->modelPSF = psMemIncrRefCounter (DBL->data[0]);
+    source->mode    |= PM_SOURCE_MODE_SUBTRACTED;
+    source->mode    |= PM_SOURCE_MODE_PAIR;
+
+    // copy most data from the primary source (modelEXT, blends stay NULL)
+    // XXX use pmSourceCopy?
+    pmSource *newSrc = pmSourceCopy (source);
+    newSrc->modelPSF = psMemIncrRefCounter (DBL->data[1]);
+
+    // build cached models and subtract
+    pmSourceCacheModel (source, maskVal);
+    pmSourceSub (source, PM_MODEL_OP_FULL, maskVal);
+    pmSourceCacheModel (newSrc, maskVal);
+    pmSourceSub (newSrc, PM_MODEL_OP_FULL, maskVal);
+    psTrace ("psphot", 5, "blob as DBL: %f %f\n", ONE->params->data.F32[PM_PAR_XPOS], ONE->params->data.F32[PM_PAR_YPOS]);
+
+    psArrayAdd (sources, 100, newSrc);
+    psFree (newSrc);
+    psFree (DBL);
+    return true;
+}
+
+// fit a double PSF source to an extended blob
+psArray *psphotFitDBL (pmReadout *readout, pmSource *source, psMaskType maskVal, psMaskType markVal) {
+
+    float dx, dy;
+    pmModel *DBL;
+    pmModel *PSF;
+    psEllipseAxes axes;
+    psEllipseMoments moments;
+    psArray *modelSet;
+
+    NfitDBL ++;
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // make a guess at the position of the two sources
+    moments.x2 = source->moments->Sx;
+    moments.y2 = source->moments->Sy;
+    moments.xy = source->moments->Sxy;
+    axes = psEllipseMomentsToAxes (moments, 20.0);
+
+    // XXX this is really arbitrary: 4 pixel separation?
+    dx = 2 * cos (axes.theta);
+    dy = 2 * sin (axes.theta);
+
+    // save the PSF model from the Ensemble fit
+    PSF = source->modelPSF;
+    psphotCheckRadiusPSFBlend (readout, source, PSF, markVal, 8.0);
+    if (isnan(PSF->params->data.F32[1])) psAbort("nan in dbl fit");
+
+    modelSet = psArrayAlloc (2);
+
+    DBL = pmModelCopy (PSF);
+    DBL->params->data.F32[PM_PAR_I0]  *= 0.5;
+    DBL->params->data.F32[PM_PAR_XPOS] = source->peak->xf + dx;
+    DBL->params->data.F32[PM_PAR_YPOS] = source->peak->yf + dy;
+    modelSet->data[0] = DBL;
+
+    DBL = pmModelCopy (PSF);
+    DBL->params->data.F32[PM_PAR_I0]  *= 0.5;
+    DBL->params->data.F32[PM_PAR_XPOS] = source->peak->xf - dx;
+    DBL->params->data.F32[PM_PAR_YPOS] = source->peak->yf - dy;
+    modelSet->data[1] = DBL;
+
+    // fit PSF model (set/unset the pixel mask)
+    pmSourceFitSet (source, modelSet, PM_SOURCE_FIT_PSF, maskVal);
+    return (modelSet);
+}
+
+pmModel *psphotFitEXT (pmReadout *readout, pmSource *source, pmModelType modelType, psMaskType maskVal, psMaskType markVal) {
+
+    NfitEXT ++;
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // use the source moments, etc to guess basic model parameters
+    pmModel *EXT = pmSourceModelGuess (source, modelType);
+    PS_ASSERT (EXT, NULL);
+
+    // if (isnan(EXT->params->data.F32[1])) psAbort("nan in ext fit");
+
+    psphotCheckRadiusEXT (readout, source, EXT, markVal);
+
+    if ((source->moments->Sx < 1e-3) || (source->moments->Sx < 1e-3)) {
+        psTrace ("psphot", 5, "problem source: moments: %f %f\n", source->moments->Sx, source->moments->Sy);
+    }
+
+    // fit EXT (not PSF) model (set/unset the pixel mask)
+    pmSourceFitModel (source, EXT, PM_SOURCE_FIT_EXT, maskVal);
+    return (EXT);
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSourceFreePixels.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSourceFreePixels.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSourceFreePixels.c	(revision 22157)
@@ -0,0 +1,12 @@
+# include "psphotInternal.h"
+
+void psphotSourceFreePixels (psArray *sources) {
+
+    if (!sources) return;
+
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = sources->data[i];
+	pmSourceFreePixels (source);
+    }
+    return;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSourcePlots.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSourcePlots.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSourcePlots.c	(revision 22157)
@@ -0,0 +1,162 @@
+# include "psphotInternal.h"
+
+bool psphotSourcePlots (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+
+    bool status = false;
+    psTimerStart ("psphot");
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    // the source images are written to an image 10x the size of a PSF object
+    // float OUTER = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
+    // PS_ASSERT (status, false);
+
+    int DX = 21;
+    int DY = 21;
+
+    // examine PSF sources in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // counters to track the size of the image and area used in a row
+    int dX = 0;                         // starting corner of next box
+    int dY = 0;                         // height of row so far
+    int NX = 20*DX;                     // full width of output image
+    int NY = DY;                        // total height of output image so far
+
+    // first, examine the PSF and SAT stars to set output image size:
+    // - add stamp widths until we exceed output image width,
+    // - then start a new row offset by max height
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+
+        bool keep = false;
+        keep |= (source->mode & PM_SOURCE_MODE_PSFSTAR);
+        keep |= (source->mode & PM_SOURCE_MODE_SATSTAR);
+        if (!keep) {
+	    psTrace ("psphot", 4, "not plotting star %d: %x", i, source->mode);
+	    continue;
+	}
+
+        // how does this subimage get placed into the output image?
+        // DX = source->pixels->numCols
+        // DY = source->pixels->numRows
+
+        if (dX + DX > NX) {
+            // too wide for the rest of this row
+            if (dX == 0) {
+                // alone on this row
+                NY += DY;
+                dX = 0;
+                dY = 0;
+            } else {
+                // start the next row
+                NY += dY;
+                dX = DX;
+                dY = DY;
+            }
+        } else {
+            // extend this row
+            dX += DX;
+            dY = PS_MAX (dY, DY);
+        }
+    }
+
+    if (NY == 0) {
+	psWarning ("no PSF or SAT stars to plot? skipping.\n");
+	return false;
+    }
+
+    // allocate output image
+    psImage *outpos = psImageAlloc (NX, NY, PS_TYPE_F32);
+    psImage *outsub = psImageAlloc (NX, NY, PS_TYPE_F32);
+
+    psImageInit (outpos, 0.0);
+    psImageInit (outsub, 0.0);
+
+    int Xo = 0;                         // starting corner of next box
+    int Yo = 0;                         // starting corner of next box
+    dY = 0;                             // height of row so far
+
+    int nPSF = 0;
+    int nSAT = 0;
+    int kapa = 0;                       // file descriptor for plotting routine
+
+    // first, examine the PSF and SAT stars:
+    // - generate radial plots (PS plots)
+    // - create output image array
+    for (int i = 0; i < sources->n; i++) {
+
+        pmSource *source = sources->data[i];
+
+        bool keep = false;
+        if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+            nPSF ++;
+            keep = true;
+        }
+        if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+            nSAT ++;
+            keep = true;
+        }
+        if (!keep) continue;
+
+        // how does this subimage get placed into the output image?
+        // DX = source->pixels->numCols
+        // DY = source->pixels->numRows
+
+        if (Xo + DX > NX) {
+            // too wide for the rest of this row
+            if (Xo == 0) {
+                // place source alone on this row
+                psphotAddWithTest (source, true, maskVal); // replace source if subtracted
+                psphotRadialPlot (&kapa, "radial.plots.ps", source);
+                psphotMosaicSubimage (outpos, source, Xo, Yo, DX, DY);
+
+                psphotSubWithTest (source, false, maskVal); // remove source (force)
+                psphotMosaicSubimage (outsub, source, Xo, Yo, DX, DY);
+
+                psphotSetState (source, false, maskVal); // replace source (has been subtracted)
+                Yo += DY;
+                Xo = 0;
+                dY = 0;
+            } else {
+                // start the next row
+                Yo += dY;
+                Xo = 0;
+                psphotAddWithTest (source, true, maskVal); // replace source if subtracted
+                psphotRadialPlot (&kapa, "radial.plots.ps", source);
+                psphotMosaicSubimage (outpos, source, Xo, Yo, DX, DY);
+
+                psphotSubWithTest (source, false, maskVal); // remove source (force)
+                psphotMosaicSubimage (outsub, source, Xo, Yo, DX, DY);
+                psphotSetState (source, false, maskVal); // replace source (has been subtracted)
+
+                Xo = DX;
+                dY = DY;
+            }
+        } else {
+            // extend this row
+            psphotAddWithTest (source, true, maskVal); // replace source if subtracted
+            psphotRadialPlot (&kapa, "radial.plots.ps", source);
+            psphotMosaicSubimage (outpos, source, Xo, Yo, DX, DY);
+
+            psphotSubWithTest (source, false, maskVal); // remove source (force)
+            psphotMosaicSubimage (outsub, source, Xo, Yo, DX, DY);
+            psphotSetState (source, false, maskVal); // replace source (has been subtracted)
+
+            Xo += DX;
+            dY = PS_MAX (dY, DY);
+        }
+    }
+
+    psphotSaveImage (NULL, outpos, "outpos.fits");
+    psphotSaveImage (NULL, outsub, "outsub.fits");
+    psLogMsg ("psphot", PS_LOG_INFO, "plotted %d sources (%d psf, %d sat): %f sec\n", nPSF + nSAT, nPSF, nSAT, psTimerMark ("psphot"));
+
+    psFree (outpos);
+    psFree (outsub);
+    return true;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSourceSize.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSourceSize.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSourceSize.c	(revision 22157)
@@ -0,0 +1,241 @@
+# include "psphotInternal.h"
+# include <gsl/gsl_sf_gamma.h>
+
+float psphotModelContour (psImage *image, psImage *weight, psImage *mask, pmModel *model, float Ro);
+
+// we need to call this function after sources have been fitted to the PSF model and
+// subtracted.  To determine the CR-nature, this function examines the 9 pixels in the 3x3
+// square containing the peak and compares the observed flux to the model.  To determine
+// the EXT-nature, this function measures the amount of positive or negative total
+// deviation from the psf model at the r = FWHM/2 position
+
+bool psphotSourceSize (pmReadout *readout, psArray *sources, psMetadata *recipe, long first) {
+
+    bool status;
+
+    psTimerStart ("psphot");
+
+    float CR_NSIGMA_LIMIT = psMetadataLookupF32 (&status, recipe, "PSPHOT.CR.NSIGMA.LIMIT");
+    assert (status);
+
+    float EXT_NSIGMA_LIMIT = psMetadataLookupF32 (&status, recipe, "PSPHOT.EXT.NSIGMA.LIMIT");
+    assert (status);
+
+    // loop over all source
+    for (int i = first; i < sources->n; i++) {
+	pmSource *source = sources->data[i];
+
+	// skip source if it was already measured
+	if (isfinite(source->crNsigma)) continue;
+
+	// source must have been subtracted
+	if (!(source->mode & PM_SOURCE_MODE_SUBTRACTED)) continue;
+
+	psF32 **resid  = source->pixels->data.F32;
+	psF32 **weight = source->weight->data.F32;
+	psU8 **mask    = source->maskObj->data.U8;
+
+	// check for extendedness: measure the delta flux significance at the 1 sigma contour
+	source->extNsigma = psphotModelContour (source->pixels, source->weight, source->maskObj, source->modelPSF, 1.0);
+
+	// XXX prevent a source from being both CR and EXT?
+	if (source->extNsigma > EXT_NSIGMA_LIMIT) {
+	  source->mode |= PM_SOURCE_MODE_EXT_LIMIT;
+	}
+
+	int xPeak = source->peak->xf - source->pixels->col0 + 0.5;
+	int yPeak = source->peak->yf - source->pixels->row0 + 0.5;
+
+	// XXX for now, skip sources which are too close to a boundary
+	// XXX raise a flag?
+	if (xPeak < 1) continue;
+	if (xPeak > source->pixels->numCols - 2) continue;
+	if (yPeak < 1) continue;
+	if (yPeak > source->pixels->numRows - 2) continue;
+
+	// XXX for now, just skip any sources with masked pixels
+	// XXX raise a flag?
+	bool keep = true;
+	for (int iy = -1; (iy <= +1) && keep; iy++) {
+	    for (int ix = -1; (ix <= +1) && keep; ix++) {
+		if (mask[yPeak+iy][xPeak+ix]) { keep &= false; }
+	    }
+	}
+	if (!keep) continue;
+
+	// XXX need to deal with edge peaks... and mask
+	float cX = 2*resid[yPeak][xPeak] - resid[yPeak+0][xPeak-1] - resid[yPeak+0][xPeak+1];
+	float cY = 2*resid[yPeak][xPeak] - resid[yPeak+1][xPeak+0] - resid[yPeak+1][xPeak+0];
+	float cL = 2*resid[yPeak][xPeak] - resid[yPeak-1][xPeak-1] - resid[yPeak+1][xPeak+1];
+	float cR = 2*resid[yPeak][xPeak] - resid[yPeak+1][xPeak-1] - resid[yPeak-1][xPeak+1];
+
+	float dcX = 4*weight[yPeak][xPeak] + weight[yPeak+0][xPeak-1] + weight[yPeak+0][xPeak+1];
+	float dcY = 4*weight[yPeak][xPeak] + weight[yPeak+1][xPeak+0] + weight[yPeak+1][xPeak+0];
+	float dcL = 4*weight[yPeak][xPeak] + weight[yPeak-1][xPeak-1] + weight[yPeak+1][xPeak+1];
+	float dcR = 4*weight[yPeak][xPeak] + weight[yPeak+1][xPeak-1] + weight[yPeak-1][xPeak+1];
+
+	float nX = cX / sqrt(dcX);
+	float nY = cY / sqrt(dcY);
+	float nL = cL / sqrt(dcL);
+	float nR = cR / sqrt(dcR);
+
+	// P(chisq > chisq_obs; Ndof) = gamma_Q (Ndof/2, chisq/2)
+	// Ndof = 4 ? (four measurements, no free parameters)
+	// XXX this value is going to be biased low because of systematic errors.
+	// we need to calibrate it somehow
+	// source->psfProb = gsl_sf_gamma_inc_Q (2, 0.5*chisq);
+
+	// not strictly accurate: overcounts the chisq contribution from the center pixel (by factor of 4)
+	source->psfChisq = PS_SQR (nX) + PS_SQR (nY) + PS_SQR (nX) + PS_SQR (nR);
+
+	float fCR = 0.0;
+	float fEXT = 0.0;
+	int nCR = 0;
+	int nEXT = 0;
+	if (nX > 0.0) {
+	    fCR += nX;
+	    nCR ++;
+	} else {
+	    fEXT += nX;
+	    nEXT ++;
+	}
+	if (nY > 0.0) {
+	    fCR += nY;
+	    nCR ++;
+	} else {
+	    fEXT += nY;
+	    nEXT ++;
+	}
+	if (nL > 0.0) {
+	    fCR += nL;
+	    nCR ++;
+	} else {
+	    fEXT += nL;
+	    nEXT ++;
+	}
+	if (nR > 0.0) {
+	    fCR += nR;
+	    nCR ++;
+	} else {
+	    fEXT += nR;
+	    nEXT ++;
+	}
+	source->crNsigma  = (nCR > 0)  ? fCR / nCR : 0.0;
+	// NOTE: abs needed to make the Nsigma value positive 
+
+	if (!isfinite(source->crNsigma)) {
+	  fprintf (stderr, ".");
+	  source->crNsigma = -6.0;
+	}
+
+	if (source->crNsigma > CR_NSIGMA_LIMIT) {
+	  source->mode |= PM_SOURCE_MODE_CR_LIMIT;
+	}
+    }
+
+    psLogMsg ("psphot.size", PS_LOG_INFO, "measure source sizes for %ld sources: %f sec\n", sources->n - first, psTimerMark ("psphot"));
+
+    return true;
+}
+
+
+// some possible classes:
+// all 4 curvatures are highly negative : CR
+// all 4 curvatures are highly positive : EXT
+
+// at least 2 are significantly negative, none are significantly positive : CR
+// at least 2 are significantly positive, none are significantly negative : EXT
+
+// any are significantly negative, some may be significantly positive : CR
+// any are significantly positive, none may be significantly positive : EXT
+	
+/* Nn  Np  No
+   4   0   0   CR_1
+   3   1   0   CR_1
+   3   0   1   CR_1
+   2   2   0   CR_2
+   2   1   1   CR_2
+   2   0   2   CR_2
+   1   3   0   CR_3
+   1   2   1   CR_3
+   1   1   2   CR_3
+   1   0   3   CR_3
+   0   4   0   EXT
+   0   3   1   EXT
+   0   2   2   EXT
+   0   1   3   PSF
+   0   0   4   PSF
+*/
+
+/* Alternatively, write a f(CR) = Sum(nX,etc if >0) */
+	  
+
+/* I can write the formal probability that the 4 measurements are consistent with a PSF
+ * based on how large the error values are (Nsigma -> erf -> P(Nsigma)).
+ * I should examine this value as a function of flux and also examine the distribution of
+ * the probabilities for the PSF sources.
+ */
+
+
+// given the PSF ellipse parameters, navigate around the 1sigma contour, return the total
+// deviation in sigmas.  This is measure on the residual image - should we ignore negative
+// deviations?
+float psphotModelContour (psImage *image, psImage *weight, psImage *mask, pmModel *model, float Ro) {
+
+    psF32 *PAR = model->params->data.F32;
+
+    // Ro = (x / SXX)^2 + (y / SYY)^2 + x y SXY;
+    // y^2 (1/SYY^2) + y (x SXY) + (x / SXX)^2 - Ro = 0;
+    // y = [-(x SXY) +/- sqrt ((x SXY)^2 - 4 (1/SYY^2) ((x/SXX)^2 - Ro))] * [SYY^2 / 2];
+    // y = [-B +/- sqrt (B^2 - 4 A C)] / [2 A];
+
+    // min/max value of x is where T -> 0
+    // solve this for x2:
+    float Q = Ro * PS_SQR(PAR[PM_PAR_SXX]) / (1.0 - PS_SQR(PAR[PM_PAR_SXX]*PAR[PM_PAR_SYY]*PAR[PM_PAR_SXY]) / 4.0);
+    if (Q < 0.0) return NAN; // ellipse is imaginary
+
+    int xMax = sqrt(Q);
+    int xMin = -1.0*xMax;
+
+    int nPts = 0;
+    float nSigma = 0.0;
+
+    for (int x = xMin; x <= xMax; x++) {
+	float A = PS_SQR (1.0 / PAR[PM_PAR_SYY]);
+	float B = x * PAR[PM_PAR_SXY];
+	float C = PS_SQR (x / PAR[PM_PAR_SXX]) - Ro;
+
+	float T = PS_SQR(B) - 4*A*C;
+	if (T < 0.0) continue;
+    
+	float yP = (-B + sqrt (T)) / (2.0 * A);
+	float yM = (-B - sqrt (T)) / (2.0 * A);
+
+	int xPix  = x  + PAR[PM_PAR_XPOS] - image->col0 + 0.5;
+	int yPixM = yM + PAR[PM_PAR_YPOS] - image->row0 + 0.5;
+	int yPixP = yP + PAR[PM_PAR_YPOS] - image->row0 + 0.5;
+
+	if (xPix < 0) continue;
+	if (xPix >= image->numCols) continue;
+
+	if ((yPixM >= 0) && (yPixM < image->numRows)) {
+	    if (!mask || !mask->data.U8[yPixM][xPix]) {
+		float dSigma = image->data.F32[yPixM][xPix] / sqrt (weight->data.F32[yPixM][xPix]);
+		nSigma += dSigma;
+		nPts ++;
+	    }
+	}
+	
+	if (yPixM == yPixP) continue;
+
+	if ((yPixP >= 0) && (yPixP < image->numRows)) {
+	    if (!mask || !mask->data.U8[yPixP][xPix]) {
+		float dSigma = image->data.F32[yPixP][xPix] / sqrt (weight->data.F32[yPixP][xPix]);
+		nSigma += dSigma;
+		nPts ++;
+	    }
+	}
+    }	
+    nSigma /= nPts;
+    return nSigma;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSourceStats.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSourceStats.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSourceStats.c	(revision 22157)
@@ -0,0 +1,126 @@
+# include "psphotInternal.h"
+
+psArray *psphotSourceStats (pmReadout *readout, psMetadata *recipe, pmDetections *detections)
+{
+    bool     status  = false;
+    psArray *sources = NULL;
+    float BIG_RADIUS;
+
+    psTimerStart ("psphot");
+
+    // bit-masks to test for good/bad pixels
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT");
+    assert (maskVal);
+
+    // bit-mask to mark pixels not used in analysis
+    psMaskType markVal = psMetadataLookupU8(&status, recipe, "MARK.PSPHOT");
+    assert (markVal);
+
+    // maskVal is used to test for rejected pixels, and must include markVal
+    maskVal |= markVal;
+
+    // determine properties (sky, moments) of initial sources
+    float INNER    = psMetadataLookupF32 (&status, recipe, "SKY_INNER_RADIUS");
+    if (!status) return NULL;
+    float OUTER    = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
+    if (!status) return NULL;
+    float RADIUS   = psMetadataLookupF32 (&status, recipe, "PSF_MOMENTS_RADIUS");
+    if (!status) return NULL;
+    float MIN_SN   = psMetadataLookupF32 (&status, recipe, "MOMENTS_SN_MIN");
+    if (!status) return NULL;
+    char *breakPt  = psMetadataLookupStr (&status, recipe, "BREAK_POINT");
+    if (!status) return NULL;
+
+    psArray *peaks = detections->peaks;
+    if (!peaks) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "No peaks found!");
+        return NULL;
+    }
+
+    sources = psArrayAllocEmpty (peaks->n);
+
+    int Nfail = 0;
+    int Nmoments = 0;
+    for (int i = 0; i < peaks->n; i++) {
+
+        pmPeak *peak = peaks->data[i];
+        if (peak->assigned) continue;
+
+        // create a new source, add peak
+        pmSource *source = pmSourceAlloc();
+        source->peak = psMemIncrRefCounter(peak);
+
+        // allocate image, weight, mask arrays for each peak (square of radius OUTER)
+        pmSourceDefinePixels (source, readout, source->peak->x, source->peak->y, OUTER);
+        if (!strcasecmp (breakPt, "PEAKS")) {
+            peak->assigned = true;
+            psArrayAdd (sources, 100, source);
+            psFree (source);
+            continue;
+        }
+
+        // skip faint sources for moments measurement
+        if (source->peak->SN < MIN_SN) {
+            peak->assigned = true;
+            psArrayAdd (sources, 100, source);
+            psFree (source);
+            continue;
+        }
+
+        // measure a local sky value
+        // the local sky is now ignored; kept here for reference only
+        status = pmSourceLocalSky (source, PS_STAT_SAMPLE_MEDIAN, INNER, maskVal, markVal);
+        if (!status) {
+          psFree (source);
+          Nfail ++;
+          psErrorClear();
+          continue;
+        }
+
+        // measure the local sky variance (needed if noise is not sqrt(signal))
+        // XXX EAM : this should use ROBUST not SAMPLE median, but it is broken
+        status = pmSourceLocalSkyVariance (source, PS_STAT_SAMPLE_MEDIAN, INNER, maskVal, markVal);
+        if (!status) {
+          psFree (source);
+          Nfail ++;
+          psErrorClear();
+          continue;
+        }
+
+        // measure basic source moments
+        status = pmSourceMoments (source, RADIUS);
+        if (status) {
+            // add to the source array
+            peak->assigned = true;
+            psArrayAdd (sources, 100, source);
+            psFree (source);
+            Nmoments ++;
+            continue;
+        }
+
+        // if no valid pixels, or massive swing, likely saturated source,
+        // try a much larger box
+        BIG_RADIUS = PS_MIN (INNER, 3*RADIUS);
+        psTrace ("psphot", 4, "retrying moments for %d, %d\n", source->peak->x, source->peak->y);
+        status = pmSourceMoments (source, BIG_RADIUS);
+        if (status) {
+            // add to the source array
+            peak->assigned = true;
+            psArrayAdd (sources, 100, source);
+            psFree (source);
+            Nmoments ++;
+            continue;
+        }
+
+        psFree (source);
+        Nfail ++;
+        psErrorClear();
+        continue;
+    }
+
+    psLogMsg ("psphot", PS_LOG_INFO, "%ld sources, %d moments, %d failed: %f sec\n", sources->n, Nmoments, Nfail, psTimerMark ("psphot"));
+
+    return (sources);
+}
+
+// XXX EAM : filter out bad peaks (eg, no valid pixels available for sky)
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotStandAlone.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotStandAlone.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotStandAlone.h	(revision 22157)
@@ -0,0 +1,21 @@
+# ifdef HAVE_CONFIG_H
+# include <config.h>
+# endif
+
+#ifndef PSPHOT_STAND_ALONE_H
+#define PSPHOT_STAND_ALONE_H
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "psphot.h"
+
+// Top level functions
+pmConfig       *psphotArguments (int argc, char **argv);
+bool            psphotParseCamera (pmConfig *config);
+bool            psphotImageLoop (pmConfig *config);
+bool            psphotMosaicChip(pmConfig *config, const pmFPAview *view, char *outFile, char *inFile);
+void            psphotCleanup (pmConfig *config);
+psExit          psphotGetExitStatus ();
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSubtractBackground.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSubtractBackground.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSubtractBackground.c	(revision 22157)
@@ -0,0 +1,112 @@
+# include "psphotInternal.h"
+static int npass = 0;
+
+// generate the median in NxN boxes, clipping heavily
+// linear interpolation to generate full-scale model
+bool psphotSubtractBackground (pmConfig *config, const pmFPAview *view, const char *filename) 
+{
+    bool status = true;
+    pmReadout *background = NULL;
+    pmReadout *backSub = NULL;
+
+    psTimerStart ("psphot");
+
+    // find the currently selected readout
+    pmFPAfile *file = psMetadataLookupPtr (&status, config->files, filename);
+    pmFPA *inFPA = file->fpa;
+    pmReadout *readout = pmFPAviewThisReadout (view, inFPA);
+    psImage *image = readout->image;
+    psImage *mask  = readout->mask;
+
+    // find the currently selected readout
+    pmReadout *model = pmFPAfileThisReadout (config->files, view, "PSPHOT.BACKMDL");
+    assert (model);
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
+    assert (recipe);
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMaskType maskVal = psMetadataLookupU8(&status, recipe, "MASK.PSPHOT"); // Mask value for bad pixels
+    assert (maskVal);
+
+    psImageBinning *binning = psMetadataLookupPtr(&status, recipe, "PSPHOT.BACKGROUND.BINNING");
+    assert (binning);
+
+    // select background pixels, from output background file, or create
+    file = psMetadataLookupPtr (&status, config->files, "PSPHOT.BACKGND");
+    if (file) {
+        // we are using PSPHOT.BACKGND as an I/O file: select readout or create
+        if (file->mode == PM_FPA_MODE_INTERNAL) {
+            background = file->readout;
+        } else {
+            background = pmFPAviewThisReadout (view, file->fpa);
+        }
+        if (background == NULL) {
+            // readout does not yet exist: create from input
+            pmFPAfileCopyStructureView (file->fpa, inFPA, 1, 1, view);
+            background = pmFPAviewThisReadout (view, file->fpa);
+            if ((image->numCols != background->image->numCols) || (image->numRows != background->image->numRows)) {
+                psError (PSPHOT_ERR_PROG, true, "inconsistent sizes for background dimensions");
+                return false;
+            }
+        }
+    } else {
+        background = pmFPAfileDefineInternal (config->files, "PSPHOT.BACKGND", image->numCols, image->numRows, PS_TYPE_F32);
+    }
+    psF32 **backData = background->image->data.F32;
+
+    // linear interpolation to full-scale
+    if (!psImageUnbin (background->image, model->image, binning)) {
+        psError (PSPHOT_ERR_PROG, true, "inconsistent sizes for unbinning");
+        return false;
+    }
+
+    psLogMsg ("psphot", PS_LOG_MINUTIA, "build resampled image: %f sec\n", psTimerMark ("psphot"));
+
+    // back-sub image pixels, from output background file (don't create if not requested)
+    file = psMetadataLookupPtr (&status, config->files, "PSPHOT.BACKSUB");
+    if (file) {
+        // we are using PSPHOT.BACKSUB as an I/O file: select readout or create
+        backSub = pmFPAviewThisReadout (view, file->fpa);
+        if (backSub == NULL) {
+            // readout does not yet exist: create from input
+            pmFPAfileCopyStructureView (file->fpa, inFPA, 1, 1, view);
+            backSub = pmFPAviewThisReadout (view, file->fpa);
+        }
+    }
+
+    if (psTraceGetLevel("psphot") > 5) {
+        char name[256];
+        sprintf (name, "image.%02d.fits", npass);
+        psphotSaveImage (NULL, image, name);
+        sprintf (name, "back.%02d.fits", npass);
+        psphotSaveImage (NULL, background->image, name);
+        sprintf (name, "mask.%02d.fits", npass);
+        psphotSaveImage (NULL, mask, name);
+        sprintf (name, "backmdl.%02d.fits", npass);
+        psphotSaveImage (NULL, model->image, name);
+    }
+
+    // subtract the background model (save in backSub, if requested)
+    for (int j = 0; j < image->numRows; j++) {
+        for (int i = 0; i < image->numCols; i++) {
+            image->data.F32[j][i] -= backData[j][i];
+            if (backSub) {
+                backSub->image->data.F32[j][i] = image->data.F32[j][i];
+            }
+        }
+    }
+
+    if (psTraceGetLevel("psphot") > 5) {
+        char name[256];
+        sprintf (name, "backsub.%02d.fits", npass);
+        psphotSaveImage (NULL, image, name);
+    }
+    psLogMsg ("psphot", PS_LOG_INFO, "subtracted background model: %f sec\n", psTimerMark ("psphot"));
+
+    // the pmReadout selected in this function are all view on entries in config->files
+
+    npass ++;
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSummaryPlots.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSummaryPlots.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotSummaryPlots.c	(revision 22157)
@@ -0,0 +1,280 @@
+# include "psphotInternal.h"
+
+// the top portion of this file defines plotting functions which use kapa for plotting.
+// if kapa is not available, these functions are defined in the bottom portion as stubs
+// which perform NOP and return false (XXX should this be true??)
+
+// this variable is defined in psmodules.h if ohana-config is found
+# if (HAVE_KAPA)
+
+# include <kapa.h>
+
+// plot the sx, sy moments plane (faint and bright sources)
+bool psphotPlotMoments (pmConfig *config, pmFPAview *view, psArray *sources) {
+
+    // select model pixels (from output background model file, or create internal file)
+    pmFPAfile *file = psMetadataLookupPtr (NULL, config->files, "PSPHOT.MOMENT.PLT");
+    if (file == NULL) {
+	psLogMsg ("psphot", 3, "skipping moments plot");
+	return false;
+    }
+
+    // pmFPAfileOpen defers disk I/O for KAPA files: just get the correct name
+    pmFPAfileOpen (file, view, config);
+
+    Graphdata graphdata;
+
+    psLogMsg ("psphot", 3, "creating moments plot");
+
+    // XXX get the 'showWindow' option from the recipes somewhere
+    int kapa = pmKapaOpen (false);
+    if (kapa == -1) {
+	psError(PSPHOT_ERR_UNKNOWN, true, "failure to open kapa");
+	return false;
+    }
+
+    KapaResize (kapa, 500, 500);
+    KapaInitGraph (&graphdata);
+
+    // examine sources to set data range
+    graphdata.xmin = -0.05;
+    graphdata.ymin = -0.05;
+    graphdata.xmax = +2.05;
+    graphdata.ymax = +2.05;
+    KapaSetLimits (kapa, &graphdata);
+  
+    KapaSetFont (kapa, "helvetica", 14);
+    KapaBox (kapa, &graphdata);
+    KapaSendLabel (kapa, "&ss&h_x| (pixels)", KAPA_LABEL_XM);
+    KapaSendLabel (kapa, "&ss&h_y| (pixels)", KAPA_LABEL_YM);
+	       
+    psVector *xBright = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *yBright = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *xFaint  = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+    psVector *yFaint  = psVectorAllocEmpty (sources->n, PS_TYPE_F32);
+
+    // construct the vectors
+    int nB = 0;
+    int nF = 0;
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = sources->data[i];
+	if (source->moments == NULL) continue;
+    
+	xFaint->data.F32[nF] = source->moments->Sx;
+	yFaint->data.F32[nF] = source->moments->Sy;
+	nF++;
+    
+	// XXX make this a user-defined cutoff
+	if (source->moments->SN < 25) continue;
+
+	xBright->data.F32[nB] = source->moments->Sx;
+	yBright->data.F32[nB] = source->moments->Sy;
+	nB++;
+    }
+    xFaint->n = nF;
+    yFaint->n = nF;
+
+    xBright->n = nB;
+    yBright->n = nB;
+  
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 0;
+    graphdata.size = 0.3;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, nF, &graphdata);
+    KapaPlotVector (kapa, nF, xFaint->data.F32, "x");
+    KapaPlotVector (kapa, nF, yFaint->data.F32, "y");
+  
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 2;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, nB, &graphdata);
+    KapaPlotVector (kapa, nB, xBright->data.F32, "x");
+    KapaPlotVector (kapa, nB, yBright->data.F32, "y");
+
+    psLogMsg ("psphot", 3, "saving plot to %s", file->filename);
+    KapaPNG (kapa, file->filename);
+
+    psFree (xBright);
+    psFree (yBright);
+    psFree (xFaint);
+    psFree (yFaint);
+
+    return true;
+}
+
+// plot the sx, sy, sxy as vector field, 
+// plot the PSF measured sx, sy, sxy as vector field
+// pull the sources from the config / file?
+bool psphotPlotPSFModel (pmConfig *config, pmFPAview *view, psArray *sources) {
+
+    // select model pixels (from output background model file, or create internal file)
+    pmFPAfile *file = psMetadataLookupPtr (NULL, config->files, "PSPHOT.PSFMODEL.PLT");
+    if (file == NULL) {
+	psLogMsg ("psphot", 3, "skipping psf model plot");
+	return false;
+    }
+
+    // pmFPAfileOpen defers disk I/O for KAPA files: just get the correct name
+    pmFPAfileOpen (file, view, config);
+
+    Graphdata graphdata;
+
+    psLogMsg ("psphot", 3, "creating psf model plot");
+
+    int kapa = pmKapaOpen (false);
+    if (kapa == -1) {
+	psError(PSPHOT_ERR_UNKNOWN, true, "failure to open kapa");
+	return false;
+    }
+
+    // XXX make the aspect-ratio match the image
+    KapaResize (kapa, 800, 800);
+    KapaInitGraph (&graphdata);
+
+    psVector *xMNT = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+    psVector *yMNT = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+    psVector *xPSF = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+    psVector *yPSF = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+    psVector *xMIN = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+    psVector *yMIN = psVectorAllocEmpty (2*sources->n, PS_TYPE_F32);
+
+    // construct the plot vectors
+    int nMNT = 0;
+    int nPSF = 0;
+    int nMIN = 0;
+    float dx = 0;
+    float dy = 0;
+    float scale = 10;
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = sources->data[i];
+	if (source->moments == NULL) continue;
+	if (source->moments->SN < 25) continue;
+        if (source->type != PM_SOURCE_TYPE_STAR) continue;
+    
+	pmModel *model = source->modelPSF;
+        if (model == NULL) continue;
+
+	psF32 *PAR = model->params->data.F32;
+
+	psEllipseMoments moments;
+	moments.x2 = source->moments->Sx;
+	moments.y2 = source->moments->Sy;
+	moments.xy = source->moments->Sxy;
+
+	psEllipseShape shape;
+	shape.sx  = PAR[PM_PAR_SXX] / sqrt(2.0);
+	shape.sy  = PAR[PM_PAR_SYY] / sqrt(2.0);
+	shape.sxy = PAR[PM_PAR_SXY];
+
+	// force the axis ratio to be < 20.0
+	psEllipseAxes axes_mnt = psEllipseMomentsToAxes (moments, 20.0);
+	psEllipseAxes axes_psf = psEllipseShapeToAxes (shape, 20.0);
+
+	// moments major axis
+	dx = scale*axes_mnt.major*cos(axes_mnt.theta);
+	dy = scale*axes_mnt.major*sin(axes_mnt.theta);
+	xMNT->data.F32[nMNT] = PAR[PM_PAR_XPOS] - dx;
+	yMNT->data.F32[nMNT] = PAR[PM_PAR_YPOS] - dy;
+	nMNT++;
+	xMNT->data.F32[nMNT] = PAR[PM_PAR_XPOS] + dx;
+	yMNT->data.F32[nMNT] = PAR[PM_PAR_YPOS] + dy;
+	nMNT++;
+    
+	// psf major axis
+	dx = scale*axes_psf.major*cos(axes_psf.theta);
+	dy = scale*axes_psf.major*sin(axes_psf.theta);
+	xPSF->data.F32[nPSF] = PAR[PM_PAR_XPOS] - dx;
+	yPSF->data.F32[nPSF] = PAR[PM_PAR_YPOS] - dy;
+	nPSF++;
+	xPSF->data.F32[nPSF] = PAR[PM_PAR_XPOS] + dx;
+	yPSF->data.F32[nPSF] = PAR[PM_PAR_YPOS] + dy;
+	nPSF++;
+
+	// minor axis (to show size)
+	dy = +scale*axes_psf.minor*cos(axes_psf.theta);
+	dx = -scale*axes_psf.minor*sin(axes_psf.theta);
+	xMIN->data.F32[nMIN] = PAR[PM_PAR_XPOS] - dx;
+	yMIN->data.F32[nMIN] = PAR[PM_PAR_YPOS] - dy;
+	nMIN++;
+	xMIN->data.F32[nMIN] = PAR[PM_PAR_XPOS] + dx;
+	yMIN->data.F32[nMIN] = PAR[PM_PAR_YPOS] + dy;
+	nMIN++;
+
+	graphdata.xmin = PS_MIN(graphdata.xmin, PAR[PM_PAR_XPOS]);
+	graphdata.xmax = PS_MAX(graphdata.xmax, PAR[PM_PAR_XPOS]);
+	graphdata.ymin = PS_MIN(graphdata.ymin, PAR[PM_PAR_YPOS]);
+	graphdata.ymax = PS_MAX(graphdata.ymax, PAR[PM_PAR_YPOS]);
+    }
+    xMNT->n = yMNT->n = nMNT;
+    xPSF->n = yPSF->n = nPSF;
+    xMIN->n = yMIN->n = nMIN;
+
+    float range;
+    range = graphdata.xmax - graphdata.xmin;
+    graphdata.xmax += 0.05*range;
+    graphdata.xmin -= 0.05*range;
+    range = graphdata.ymax - graphdata.ymin;
+    graphdata.ymax += 0.05*range;
+    graphdata.ymin -= 0.05*range;
+
+    // XXX set the plot range to match the image
+    KapaSetLimits (kapa, &graphdata);
+  
+    KapaSetFont (kapa, "helvetica", 14);
+    KapaBox (kapa, &graphdata);
+    KapaSendLabel (kapa, "x (pixels)", KAPA_LABEL_XM);
+    KapaSendLabel (kapa, "y (pixels)", KAPA_LABEL_YM);
+    KapaSendLabel (kapa, "vector is major axis (scale by 20) : black are moments, blue are psf model, red is psf minor axis", KAPA_LABEL_XP);
+	       
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 100;
+    graphdata.size = 0.3;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, nMNT, &graphdata);
+    KapaPlotVector (kapa, nMNT, xMNT->data.F32, "x");
+    KapaPlotVector (kapa, nMNT, yMNT->data.F32, "y");
+  
+    graphdata.color = KapaColorByName ("blue");
+    graphdata.ptype = 100;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, nPSF, &graphdata);
+    KapaPlotVector (kapa, nPSF, xPSF->data.F32, "x");
+    KapaPlotVector (kapa, nPSF, yPSF->data.F32, "y");
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 100;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+    KapaPrepPlot (kapa, nMIN, &graphdata);
+    KapaPlotVector (kapa, nMIN, xMIN->data.F32, "x");
+    KapaPlotVector (kapa, nMIN, yMIN->data.F32, "y");
+
+    psLogMsg ("psphot", 3, "saving plot to %s", file->filename);
+    KapaPNG (kapa, file->filename);
+
+    psFree (xMNT);
+    psFree (yMNT);
+    psFree (xPSF);
+    psFree (yPSF);
+    psFree (xMIN);
+    psFree (yMIN);
+
+    return true;
+}
+
+# else
+
+bool psphotPlotMoments (pmConfig *config, pmFPAview *view, psArray *sources) {
+    psLogMsg ("psphot", 3, "skipping moments plot");
+    return true;
+}
+
+bool psphotPlotPSFModel (pmConfig *config, pmFPAview *view, psArray *sources) {
+    psLogMsg ("psphot", 3, "skipping psf model plot");
+    return true;
+}
+
+# endif
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotTest.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotTest.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotTest.c	(revision 22157)
@@ -0,0 +1,83 @@
+# include "psphotInternal.h"
+
+void psExit (int status, char *process, char *format, ...) {
+
+    va_list ap;
+
+    va_start (ap, format);
+    fprintf (stderr, "exiting %s\n", process);
+    vfprintf (stderr, format, ap);
+    va_end (ap);
+
+    exit (status);
+}
+
+int main (int argc, char **argv) {
+
+    psRegion region = {0,0,0,0};        // a region representing the entire array
+    psphotTestArguments (&argc, argv);
+
+    psFits *file = psFitsOpen (argv[1], "r");
+    psMetadata *header = psFitsReadHeader (NULL, file);
+    psImage *image = psFitsReadImage (NULL, file, region, 0);
+    psFitsClose (file);
+
+    psImageJpegColormap (argv[5]);
+
+    // psImage *fimage = psImageCopy (NULL, image, PS_TYPE_F32);
+
+    int binning = atof(argv[6]);
+
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEAN);
+    psImage *fimage = psImageRebin (NULL, image, NULL, 0, binning, stats);
+
+    float min = atof(argv[3]);
+    float max = atof(argv[4]);
+
+    psImageJpeg (fimage, argv[2], min, max);
+
+    psFree (header);
+    psFree (image);
+    exit (0);
+}
+
+
+# if (0)
+
+    psMetadata *row;
+    psArray *table;
+
+    psMetadataItem *mdi;
+
+    psMetadataConfigWrite (header, argv[2]);
+
+    // attempt to write image with NAXIS = 0
+    mdi = psMetadataLookup (header, "NAXIS");
+    mdi->data.S32 = 0;
+    mdi->type = PS_DATA_S32;
+
+    // create a test image
+    // psImage *tmpimage = psImageAlloc (10, 10, PS_DATA_F32);
+
+    // create a test table
+    table = psArrayAllocEmpty (10);
+
+    for (int i = 0; i < 10; i++) {
+        row = psMetadataAlloc ();
+        psMetadataAdd (row, PS_LIST_TAIL, "ROW",   PS_DATA_S32,    "", i);
+        psMetadataAdd (row, PS_LIST_TAIL, "FROW",  PS_TYPE_F32,    "", 0.1*i);
+        psMetadataAdd (row, PS_LIST_TAIL, "DUMMY", PS_DATA_STRING, "", "test line");
+
+        table->data[i] = row;
+    }
+    table->n = 10;
+
+    psMetadata *theader = psMetadataAlloc ();
+    psMetadataAdd (theader, PS_LIST_HEAD, "EXTNAME", PS_DATA_STRING, "extension name", "SMPFILE");
+
+    psFits *fits = psFitsOpen (argv[3], "w");
+    // psFitsWriteImage (fits, header, tmpimage, 0);
+    psFitsWriteHeader (header, fits);
+    psFitsWriteTable (fits, theader, table);
+
+# endif
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotTestArguments.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotTestArguments.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotTestArguments.c	(revision 22157)
@@ -0,0 +1,19 @@
+# include "psphotInternal.h"
+static int usage ();
+
+void psphotTestArguments (int *argc, char **argv) {
+
+  // basic pslib options
+  psLogSetFormat ("M");
+  psArgumentVerbosity (argc, argv);
+
+  if (*argc != 7) usage ();
+
+  return;
+}
+
+static int usage () {
+
+    fprintf (stderr, "USAGE: psphotTest (input.fits) (output.jpg) (zero) (scale) (colormap) (rebin)\n");
+    exit (2);
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotTestPSF.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotTestPSF.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotTestPSF.c	(revision 22157)
@@ -0,0 +1,131 @@
+# include "psphotInternal.h"
+
+bool psphotTestPSF (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+
+    bool            status;
+    char           *modelName;
+    pmPSF          *psf = NULL;
+    psArray        *stars = NULL;
+
+    psTimerStart ("psphot");
+
+    psphotSaveImage (NULL, readout->image,  "image.fits");
+
+    // check if a PSF model is supplied by the user
+    psf = psMetadataLookupPtr (NULL, readout->analysis, "PSPHOT.PSF");
+    if (psf != NULL) return psf;
+
+    // examine PSF sources in S/N order (brightest first)
+    sources = psArraySort (sources, pmSourceSortBySN);
+
+    // array to store candidate PSF stars
+    int NSTARS = psMetadataLookupS32 (&status, recipe, "PSF_MAX_NSTARS");
+    if (!status) {
+        NSTARS = PS_MIN (sources->n, 200);
+        psWarning("PSF_MAX_NSTARS is not set in the recipe --- defaulting to %d\n", NSTARS);
+    }
+
+    // use poissonian errors or local-sky errors
+    bool POISSON_ERRORS = psMetadataLookupBool (&status, recipe, "POISSON_ERRORS");
+    if (!status) {
+        POISSON_ERRORS = true;
+        psWarning("POISSON_ERRORS is not set in the recipe --- defaulting to true.\n");
+    }
+    pmSourceFitModelInit (15, 0.1, 1.0, POISSON_ERRORS);
+
+    // how to model the PSF variations across the field
+    // XXX make a default value?  or not?
+    psMetadata *md = psMetadataLookupMetadata (&status, recipe, "PSF.TREND.MASK");
+    psPolynomial2D *psfTrendMask;
+    if (!status || !md) {
+        psWarning("PSF.TREND.MASK is not set in the recipe --- defaulting to use zeroth order.\n");
+        psfTrendMask = psPolynomial2DAlloc(PS_POLYNOMIAL_ORD, 0, 0);
+    } else {
+        psfTrendMask = psPolynomial2DfromMetadata (md);
+        if (!psfTrendMask) {
+            psError(PSPHOT_ERR_PSF, true, "Unable to construct polynomial from PSF.TREND.MASK in the recipe");
+            return false;
+        }
+    }
+
+    stars = psArrayAllocEmpty (sources->n);
+
+    // select the candidate PSF stars (pointers to original sources)
+    for (int i = 0; (i < sources->n) && (stars->n < NSTARS); i++) {
+        pmSource *source = sources->data[i];
+        // if (source->mode & PM_SOURCE_MODE_PSFSTAR) psArrayAdd (stars, 200, source);
+        psArrayAdd (stars, 200, source);
+    }
+    psLogMsg ("psphot.pspsf", 4, "selected candidate %ld PSF objects\n", stars->n);
+
+    if (stars->n == 0) {
+        psError(PSPHOT_ERR_PSF, true, "Failed to find any PSF candidates");
+        return NULL;
+    }
+
+    // get the fixed PSF fit radius
+    // XXX EAM : check that PSF_FIT_RADIUS < SKY_OUTER_RADIUS
+    float RADIUS = psMetadataLookupF32 (&status, recipe, "PSF_FIT_RADIUS");
+    if (!status) {
+        psWarning("PSF_FIT_RADIUS is not set in the recipe --- defaulting to 20.0\n");
+        RADIUS = 20.0;
+    }
+
+    // for this test, require a single model
+    psMetadataItem *mdi = psMetadataLookup (recipe, "PSF_MODEL");
+    if (mdi == NULL) psAbort("missing PSF_MODEL selection");
+    if (mdi->type != PS_DATA_STRING) psAbort("choose a single PSF_MODEL");
+    modelName = mdi->data.V;
+
+    pmPSFtestModel (stars, modelName, RADIUS, POISSON_ERRORS, psfTrendMask);
+
+    psphotSaveImage (NULL, readout->image,  "resid.fits");
+    psphotSaveImage (NULL, readout->mask,   "mask.fits");
+    psphotSaveImage (NULL, readout->weight, "weight.fits");
+    
+    return true;
+}
+
+bool pmPSFtestModel (psArray *sources, char *modelName, float RADIUS, bool poissonErrors, psPolynomial2D *psfTrendMask)
+{
+    bool status;
+    float x;
+    float y;
+
+    pmModelType type = pmModelSetType (modelName);
+    pmPSF *psf = pmPSFAlloc (type, poissonErrors, psfTrendMask);
+    if (psf == NULL) psAbort("unknown model");
+
+    FILE *f = fopen ("params.dat", "w");
+
+    // stage 1:  fit an independent model (freeModel) to all sources
+    psTimerStart ("fit");
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+        pmModel  *model  = pmSourceModelGuess (source, psf->type);
+        x = source->peak->x;
+        y = source->peak->y;
+
+        // set temporary object mask and fit object
+        // fit model as EXT, not PSF
+        psImageKeepCircle (source->mask, x, y, RADIUS, "OR", markVal);
+        status = pmSourceFitModel (source, model, PM_SOURCE_FIT_EXT);
+        psImageKeepCircle (source->mask, x, y, RADIUS, "AND", PS_NOT_U8(markVal));
+
+	// write fitted parameters to file
+	fprintf (f, "%f ", model->params->data.F32[PM_PAR_XPOS]);
+	fprintf (f, "%f ", model->params->data.F32[PM_PAR_YPOS]);
+
+	fprintf (f, "%f ", model->params->data.F32[PM_PAR_SXX]);
+	fprintf (f, "%f ", model->params->data.F32[PM_PAR_SYY]);
+	fprintf (f, "%f ", model->params->data.F32[PM_PAR_SXY]);
+
+	fprintf (f, "%f %d\n", model->chisq, model->nIter);
+
+	// subtract model flux
+	pmModelSub (source->pixels, source->mask, model, PM_MODEL_OP_FULL);
+    }
+    fclose (f);
+    psLogMsg ("psphot.psftest", 4, "fit ext: %f sec for %ld sources\n", psTimerMark ("fit"), sources->n);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotTestSourceOutput.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotTestSourceOutput.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotTestSourceOutput.c	(revision 22157)
@@ -0,0 +1,157 @@
+# include "psphotInternal.h"
+
+enum {
+    PSPHOT_ADD_NONE = 0,
+    PSPHOT_ADD_MODEL = 1,
+    PSPHOT_ADD_R0 = 2,
+    PSPHOT_ADD_R1 = 4,
+};
+
+bool psphotAddModel(psImage *image,
+		    pmModel *model,
+		    int mode
+    )
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+
+    PS_ASSERT_PTR_NON_NULL(model, false);
+    PS_ASSERT_IMAGE_NON_NULL(image, false);
+    PS_ASSERT_IMAGE_TYPE(image, PS_TYPE_F32, false);
+
+    psVector *x = psVectorAlloc(2, PS_TYPE_F32);
+    psVector *params = model->params;
+    psS32 imageCol;
+    psS32 imageRow;
+    psF32 skyValue = params->data.F32[0];
+    psF32 pixelValue;
+    
+    float xCenter = model->params->data.F32[PM_PAR_XPOS];
+    float yCenter = model->params->data.F32[PM_PAR_YPOS];
+    float Io = model->params->data.F32[PM_PAR_I0];
+
+    int xBin = 1;
+    int yBin = 1;
+    float xResidCenter = 0.0;
+    float yResidCenter = 0.0;
+
+    psImageInterpolateOptions *Ro = NULL;
+    psImageInterpolateOptions *Rx = NULL;
+    psImageInterpolateOptions *Ry = NULL;
+    if (model->residuals && (mode & (PSPHOT_ADD_R0 | PSPHOT_ADD_R1))) {
+	Ro = psImageInterpolateOptionsAlloc(
+	    PS_INTERPOLATE_BILINEAR,
+	    model->residuals->Ro, NULL, NULL, 0, 0.0, 0.0, 1, 0, 0.0);
+	Rx = psImageInterpolateOptionsAlloc(
+	    PS_INTERPOLATE_BILINEAR,
+	    model->residuals->Rx, NULL, NULL, 0, 0.0, 0.0, 1, 0, 0.0);
+	Ry = psImageInterpolateOptionsAlloc(
+	    PS_INTERPOLATE_BILINEAR,
+	    model->residuals->Ry, NULL, NULL, 0, 0.0, 0.0, 1, 0, 0.0);
+
+	xBin = model->residuals->xBin;
+	yBin = model->residuals->yBin;
+	xResidCenter = model->residuals->xCenter;
+	yResidCenter = model->residuals->yCenter;
+    }
+
+    for (psS32 iy = 0; iy < image->numRows; iy++) {
+        for (psS32 ix = 0; ix < image->numCols; ix++) {
+
+            // Convert i/j to image coord space:
+	    imageCol = ix + image->col0;
+	    imageRow = iy + image->row0;
+
+            x->data.F32[0] = (float) imageCol;
+            x->data.F32[1] = (float) imageRow;
+
+            // set the appropriate pixel value for this coordinate
+	    if (mode & PSPHOT_ADD_MODEL) {
+		pixelValue = model->modelFunc (NULL, params, x) - skyValue;
+	    } else {
+		pixelValue = 0.0;
+	    }
+
+	    // get the contribution from the residual model
+	    // XXX for a test, do this for all sources and all pixels
+	    if (Ro) {
+		// fractional image position
+		// this is wrong for the 'center' case
+		float ox = xBin*(ix + 0.5 + image->col0 - xCenter) + xResidCenter;
+		float oy = yBin*(iy + 0.5 + image->row0 - yCenter) + yResidCenter;
+
+		psU8 mflux = 0;
+		double Fo = 0.0;
+		double Fx = 0.0;
+		double Fy = 0.0;
+		psImageInterpolate (&Fo, NULL, &mflux, ox, oy, Ro);
+		psImageInterpolate (&Fx, NULL, &mflux, ox, oy, Rx);
+		psImageInterpolate (&Fy, NULL, &mflux, ox, oy, Ry);
+
+		if (!mflux && isfinite(Fo) && isfinite(Fx) && isfinite(Fy)) {
+		    if (mode & PSPHOT_ADD_R0) {
+			pixelValue += Io*Fo;
+		    }
+		    if (mode & PSPHOT_ADD_R1) {
+			pixelValue += Io*(xCenter*Fx + yCenter*Fy);
+		    }
+		}
+	    }
+	    image->data.F32[iy][ix] += pixelValue;
+        }
+    }
+    psFree(x);
+    psFree(Ro);
+    psFree(Rx);
+    psFree(Ry);
+    psTrace("psModules.objects", 3, "---- %s(true) end ----\n", __func__);
+    return(true);
+}
+
+// construct an initial PSF model for each object 
+bool psphotTestSourceOutput (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf) {
+
+    psImage *imMo = psImageAlloc (readout->image->numCols, readout->image->numRows, PS_TYPE_F32);
+    psImage *imR0 = psImageAlloc (readout->image->numCols, readout->image->numRows, PS_TYPE_F32);
+    psImage *imR1 = psImageAlloc (readout->image->numCols, readout->image->numRows, PS_TYPE_F32);
+    
+    // create template model
+    pmModel *modelRef = pmModelAlloc(psf->type);
+    modelRef->params->data.F32[PM_PAR_SKY] = 0;
+    modelRef->params->data.F32[PM_PAR_I0] = 1000;
+
+    int dx = 25;
+    int dy = 25;
+
+    // generate a grid of fake sources with amplitude 1000
+    for (int iy = 50; iy < imMo->numRows; iy += 100) {
+	for (int ix = 50; ix < imMo->numCols; ix += 100) {
+	    
+	    // assign the x and y coords to the image center
+	    modelRef->params->data.F32[PM_PAR_XPOS] = ix;
+	    modelRef->params->data.F32[PM_PAR_YPOS] = iy;
+	    
+	    // create modelPSF from this model
+	    pmModel *model = pmModelFromPSF (modelRef, psf);
+	    model->residuals = psf->residuals;
+
+	    // generate working image for this source
+	    psRegion region = {ix - dx, ix + dx, iy - dy, iy + dy};
+
+	    psImage *vM = psImageSubset (imMo, region);
+	    psImage *v0 = psImageSubset (imR0, region);
+	    psImage *v1 = psImageSubset (imR1, region);
+
+	    // we want to make one image o
+	    psphotAddModel (vM, model, PSPHOT_ADD_MODEL);
+	    psphotAddModel (v0, model, PSPHOT_ADD_R0);
+	    psphotAddModel (v1, model, PSPHOT_ADD_R1);
+	}
+    }
+
+    psphotSaveImage (NULL, imMo, "grid.Mo.fits");
+    psphotSaveImage (NULL, imR0, "grid.R0.fits");
+    psphotSaveImage (NULL, imR1, "grid.R1.fits");
+
+    exit (0);
+}
+
Index: /branches/eam_branches/eam_branch_20080706/psphot/src/psphotVersion.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/psphot/src/psphotVersion.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/psphot/src/psphotVersion.c	(revision 22157)
@@ -0,0 +1,40 @@
+# include "psphotInternal.h"
+
+# if (HAVE_KAPA)
+# include <kapa.h>
+# endif
+
+static const char *cvsTag = "$Name: not supported by cvs2svn $";// CVS tag name
+
+psString psphotVersion(void)
+{
+    psString version = NULL;            // Version, to return
+    psStringAppend(&version, "%s-%s",PACKAGE_NAME,PACKAGE_VERSION);
+    return version;
+}
+
+psString psphotVersionLong(void)
+{
+    psString version = psphotVersion(); // Version, to return
+    psString tag = psStringStripCVS(cvsTag, "Name"); // CVS tag
+    psStringAppend(&version, " (cvs tag %s) %s, %s", tag, __DATE__, __TIME__);
+
+# if (HAVE_KAPA)
+    psString ohanaVersion = psStringStripCVS (ohana_version(), "Name");
+    psString libdvoVersion = psStringStripCVS (libdvo_version(), "Name");
+
+    psStringAppend (&version, " with libkapa (ohana %s, libdvo: %s)", ohanaVersion, libdvoVersion);
+    psFree (ohanaVersion);
+    psFree (libdvoVersion);
+# else
+    psStringAppend (&version, " WITHOUT libkapa");
+# endif
+
+    psFree(tag);
+    return version;
+}
+
+// Defined by RHL; leaving for backwards compatibility.
+const char *psphotCVSName(void) {
+   return cvsTag;
+}
Index: /branches/eam_branches/eam_branch_20080706/pswarp/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/.cvsignore	(revision 22157)
@@ -0,0 +1,18 @@
+bin
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+compile
+config.log
+config.status
+configure
+depcomp
+install-sh
+missing
+config.guess
+libtool
+ltmain.sh
+stamp-h1
+config.sub
+pswarp.pc
Index: /branches/eam_branches/eam_branch_20080706/pswarp/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/Makefile.am	(revision 22157)
@@ -0,0 +1,3 @@
+SUBDIRS = src
+
+CLEANFILES = *.pyc *~ core core.*
Index: /branches/eam_branches/eam_branch_20080706/pswarp/autogen.sh
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/autogen.sh	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/autogen.sh	(revision 22157)
@@ -0,0 +1,103 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+ORIGDIR=`pwd`
+cd $srcdir
+
+PROJECT=pswarp
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL="aclocal $ACLOCAL_FLAGS"
+AUTOHEADER=autoheader
+AUTOMAKE=automake
+AUTOCONF=autoconf
+
+($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $LIBTOOlIZE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/libtool/"
+        DIE=1
+}
+
+($ACLOCAL --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $ACLOCAL installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOHEADER --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOHEADER installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOMAKE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOCONF installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+if test "$DIE" -eq 1; then
+        exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+        echo "You must run this script in the top-level $PROJECT directory"
+        exit 1
+}
+
+if test -z "$*"; then
+        echo "I am going to run ./configure with no arguments - if you wish "
+        echo "to pass any to it, please specify them on the $0 command line."
+fi
+
+$LIBTOOLIZE --copy --force || echo "$LIBTOOlIZE failed"
+$ACLOCAL || echo "$ACLOCAL failed"
+$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE  failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /branches/eam_branches/eam_branch_20080706/pswarp/configure.ac
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/configure.ac	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/configure.ac	(revision 22157)
@@ -0,0 +1,47 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_PREREQ(2.61)
+
+AC_INIT([pswarp], [0.1.1], [ipp-support@ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([src])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_CONFIG_HEADER([src/config.h])
+AM_MAINTAINER_MODE
+
+IPP_STDCFLAGS
+
+AC_LANG(C)
+AC_GNU_SOURCE
+AC_PROG_CC_C99
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+AC_SYS_LARGEFILE
+
+dnl ------------------------------------------------------------
+
+AC_PATH_PROG([ERRORCODES], [psParseErrorCodes], [missing])
+if test "$ERRORCODES" = "missing" ; then
+  AC_MSG_ERROR([psParseErrorCodes is required])
+fi
+
+
+PKG_CHECK_MODULES([PSLIB], [pslib >= 1.0.0])
+PKG_CHECK_MODULES([PSMODULE], [psmodules >= 1.0.0])
+PKG_CHECK_MODULES([PPSTATS], [ppStats >= 1.0.0]) 
+PKG_CHECK_MODULES([PSPHOT], [psphot >= 0.8.0]) 
+
+dnl Set CFLAGS for build
+IPP_STDOPTS
+CFLAGS="${CFLAGS} -Wall -Werror"
+echo "PSWARP_CFLAGS: $PSWARP_CFLAGS"
+echo "PSWARP_LIBS: $PSWARP_LIBS"
+
+AC_SUBST([PSWARP_CFLAGS])
+AC_SUBST([PSWARP_LIBS])
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+])
+
+AC_OUTPUT
Index: /branches/eam_branches/eam_branch_20080706/pswarp/doc/notes.txt
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/doc/notes.txt	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/doc/notes.txt	(revision 22157)
@@ -0,0 +1,29 @@
+
+- pswarp takes an image (and corresponding mask and weight images) and
+  warps the pixels to match the specified skycell projection and pixel
+  grid.  the astrometry of the input image is supplied by the header
+  of the image or by a specified SMP file. 
+
+- the input image, weight, mask are loaded into pmFPA structures with
+  the layout determined by the camera/format/layout
+
+- the skycell is loaded into a pmFPA structure.  at a minimum, a
+  single skycell may contain a single chip, cell, and readout, which
+  in turn correspond to the single set of pixels representing the
+  image region.  
+
+  * is there a need or use for a skycell to have a multilayered
+    structure equivalent in any way to the fpa->chip->cell->readout
+    layer?
+
+    - perhaps we will want to use a single fpa for a larger region
+      with small sky cells representing contiguous pixel regions.  
+
+    - perhaps we will want to warp an entire fpa exposure at once and
+      use a skyfpa to represent all of the possible output skycells
+      for a single input fpa exposure.
+
+- the output is a file or set of files corresponding to the skycell
+  files.  the details should be specified by the PSWARP.OUTPUT file
+  rule.
+
Index: /branches/eam_branches/eam_branch_20080706/pswarp/src/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/src/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/src/.cvsignore	(revision 22157)
@@ -0,0 +1,14 @@
+*.o
+*.lo
+.libs
+.deps
+Makefile
+Makefile.in
+pswarp
+pswarp.loT
+pswarpErrorCodes.h
+pswarpErrorCodes.c
+libpswarp.la
+config.h
+config.h.in
+stamp-h1
Index: /branches/eam_branches/eam_branch_20080706/pswarp/src/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/src/Makefile.am	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/src/Makefile.am	(revision 22157)
@@ -0,0 +1,40 @@
+bin_PROGRAMS = pswarp
+pswarp_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(PPSTATS_CFLAGS) $(PSPHOT_CFLAGS) $(PSWARP_CFLAGS)
+pswarp_LDFLAGS = $(PSLIB_LIBS) $(PSMODULE_LIBS) $(PPSTATS_LIBS) $(PSPHOT_LIBS) $(PSWARP_LIBS)
+
+pswarp_SOURCES = \
+	pswarp.c			\
+	pswarpArguments.c		\
+	pswarpCleanup.c			\
+	pswarpLoop.c			\
+	pswarpDefine.c			\
+	pswarpDefineSkycell.c		\
+	pswarpErrorCodes.c		\
+	pswarpMapGrid.c			\
+	pswarpMatchRange.c		\
+	pswarpParseCamera.c		\
+	pswarpPixelFraction.c		\
+	pswarpSetMaskBits.c		\
+	pswarpTransformReadout_Opt.c	\
+	pswarpVersion.c            
+
+noinst_HEADERS = \
+	pswarp.h \
+	pswarpErrorCodes.h
+
+clean-local:
+	-rm -f TAGS
+
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
+
+### Error codes.
+BUILT_SOURCES = pswarpErrorCodes.h pswarpErrorCodes.c
+CLEANFILES = pswarpErrorCodes.h pswarpErrorCodes.c
+
+pswarpErrorCodes.h : pswarpErrorCodes.dat pswarpErrorCodes.h.in
+	$(ERRORCODES) --data=pswarpErrorCodes.dat --outdir=. pswarpErrorCodes.h
+
+pswarpErrorCodes.c : pswarpErrorCodes.dat pswarpErrorCodes.c.in pswarpErrorCodes.h
+	$(ERRORCODES) --data=pswarpErrorCodes.dat --outdir=. pswarpErrorCodes.c
Index: /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarp.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarp.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarp.c	(revision 22157)
@@ -0,0 +1,53 @@
+# include "pswarp.h"
+
+static void usage (void) {
+    fprintf (stderr, "USAGE: pswarp [-file image(s)] [-list imagelist] [options] (output) (skycell)\n");
+    fprintf (stderr, "  options:\n");
+    fprintf (stderr, "    [-astrom astrom.cmp] : provide an alternative astrometry calibration\n");
+    fprintf (stderr, "    [-mask mask.fits] : provide a corresponding mask image\n");
+    fprintf (stderr, "    [-weight weight.fits] : provide a corresponding weight image (pixel varience)\n");
+    psErrorStackPrint(stderr, "\n");
+    exit (2);
+}
+
+int main (int argc, char **argv)
+{
+    psTimerStart("pswarp");
+
+    psLibInit(NULL);
+
+    // model inits are needed in pmSourceIO
+    // models defined in psphot/src/models are not available in psastro
+    pmModelClassInit();
+
+    pmConfig *config = pswarpArguments(argc, argv);
+    if (!config) usage();
+
+    if (!pswarpOptions(config)) {
+        psErrorStackPrint(stderr, "error parsing options\n");
+        exit(1);
+    }
+
+    // load identify the data sources
+    if (!pswarpParseCamera(config)) {
+        psErrorStackPrint(stderr, "error setting up the camera\n");
+        exit (1);
+    }
+
+    // load the skycell layout information
+    if (!pswarpDefine(config)) {
+        psErrorStackPrint(stderr, "error loading output definition\n");
+        exit (1);
+    }
+
+    // load and warp
+    if (!pswarpLoop(config)) {
+        psErrorStackPrint(stderr, "error warping data\n");
+        exit (1);
+    }
+
+    psLogMsg("pswarp", 3, "complete pswarp run: %f sec\n", psTimerMark("pswarp"));
+    pswarpCleanup(config);
+    psLibFinalize();
+    exit(EXIT_SUCCESS);
+}
Index: /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarp.h
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarp.h	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarp.h	(revision 22157)
@@ -0,0 +1,69 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>  // for strcasecmp
+#include <unistd.h>   // for unlink
+#include <pslib.h>
+#include <psmodules.h>
+#include <psphot.h>
+
+#include "pswarpErrorCodes.h"
+#define PSWARP_RECIPE  "PSWARP" // Name of the recipe to use
+#define PSASTRO_RECIPE "PSASTRO" // Name of the recipe to use
+
+#define PSWARP_ANALYSIS_VARFACTOR "PSWARP.VARFACTOR" // Name for variance factor in analysis metadata
+#define PSWARP_ANALYSIS_GOODPIX   "PSWARP.GOODPIX" // Name for number of good pixels in analysis metadata
+
+// a single pswarpMap converts coordinates from one image to a second image
+// the linear model is only valid over a limited range of pixels
+typedef struct {
+    double Xo, Xx, Xy;
+    double Yo, Yx, Yy;
+    int xo;
+    int yo;
+} pswarpMap;
+
+// the pswarpMapGrid carries a collection of pswarpMag structures representing the
+// local value of the pswarpMap at different locations in the image.
+typedef struct {
+    pswarpMap ***maps;
+    int nXpts, nYpts;                   // number of x,y samples in the grid
+    int nXpix, nYpix;                   // x,y spacing in src image pixels of grid samples
+    int xMin,  yMin;                    // coordinate of first grid sample
+} pswarpMapGrid;
+
+pmConfig *pswarpArguments (int argc, char **argv);
+bool pswarpOptions(pmConfig *config);
+bool pswarpSetMaskBits (pmConfig *config);
+bool pswarpParseCamera (pmConfig *config);
+bool pswarpDefine (pmConfig *config);
+bool pswarpLoop (pmConfig *config);
+void pswarpCleanup (pmConfig *config);
+bool pswarpTransformReadout (pmReadout *output, pmReadout *input, pmConfig *config);
+bool pswarpTransformReadout_Opt (pmReadout *output, pmReadout *input, pmConfig *config);
+
+bool pswarpMatchRange (int *minX, int *minY, int *maxX, int *maxY, pmReadout *dest, pmReadout *src);
+
+pswarpMap *pswarpMapAlloc ();
+pswarpMapGrid *pswarpMapGridAlloc (int Nx, int Ny);
+
+pswarpMapGrid *pswarpMapGridFromImage (pmReadout *dest, pmReadout *src, int nXpix, int nYpix);
+bool pswarpMapGridSetGrid (pswarpMapGrid *grid, int ix, int iy, int *gridX, int *gridY);
+int pswarpMapGridNextGrid_X (pswarpMapGrid *grid, int gridX);
+int pswarpMapGridNextGrid_Y (pswarpMapGrid *grid, int gridY);
+double pswarpMapGridMaxError (pswarpMapGrid *grid);
+bool pswarpMapApply (double *outX, double *outY, pswarpMap *map, double inX, double inY);
+bool pswarpMapSetLocalModel (pswarpMap *map, pmReadout *dest, pmReadout *src, int ix, int iy);
+
+bool pswarpDefineSkycell (pmFPAfile **outFile, pmConfig **outConfig, pmConfig *config,
+                          const char *filename, const char *argname);
+
+/// Check to see if the readout has a minimum fraction of "lit" pixels
+bool pswarpPixelFraction(const pmReadout *readout, ///< Readout to inspect
+                         psMetadata *stats, ///< Statistics to update with the result
+                         const pmConfig *config ///< Configuration
+    );
+
Index: /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpArguments.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpArguments.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpArguments.c	(revision 22157)
@@ -0,0 +1,137 @@
+# include "pswarp.h"
+# include <glob.h>
+
+pmConfig *pswarpArguments (int argc, char **argv) {
+
+    bool status;
+    int N;
+
+    if (argc == 1) {
+        psError(PSWARP_ERR_ARGUMENTS, true, "No arguments supplied");
+        return NULL;
+    }
+
+    // load config data from default locations
+    pmConfig *config = pmConfigRead(&argc, argv, PSWARP_RECIPE);
+    if (config == NULL) {
+        psError(PSWARP_ERR_CONFIG, false, "Can't read site configuration");
+        return NULL;
+    }
+
+    // save the following additional recipe values based on command-line options
+    // these options override the PSWARP recipe values loaded from recipe files
+    pmConfigRecipeOptions (config, PSWARP_RECIPE);
+
+    pmConfigFileSetsMD(config->arguments, &argc, argv, "ASTROM",   "-astrom", "-astromlist");
+
+    // chip selection is used to limit chips to be processed
+    if ((N = psArgumentGet(argc, argv, "-chip"))) {
+        psArgumentRemove(N, &argc, argv);
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "CHIP_SELECTIONS", PS_DATA_STRING,
+                         "Only process these chips", argv[N]);
+        psArgumentRemove(N, &argc, argv);
+    }
+
+    // Statistics file
+    if ((N = psArgumentGet(argc, argv, "-stats"))) {
+        psArgumentRemove(N, &argc, argv);
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "STATS", PS_DATA_STRING,
+                         "Filename for statistics of output image", argv[N]);
+        psArgumentRemove(N, &argc, argv);
+    }
+
+    // PSF determination?
+    if ((N = psArgumentGet(argc, argv, "-psf"))) {
+        psArgumentRemove(N, &argc, argv);
+        psMetadataAddBool(config->arguments, PS_LIST_TAIL, "PSF", 0, "Do PSF determination?", true);
+    }
+
+    if (!pmConfigFileSetsMD (config->arguments, &argc, argv, "INPUT", "-file", "-list")) {
+        psError(PSWARP_ERR_ARGUMENTS, true, "Missing -file (input) or -list (input)");
+        return NULL;
+    }
+
+    // the mask and weight entries are optional (build from gain?)
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "MASK",   "-mask",   "-masklist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "WEIGHT", "-weight", "-weightlist");
+
+    if (argc != 3) {
+        psError(PSWARP_ERR_ARGUMENTS, true, "Incorrect arguments supplied");
+        return NULL;
+    }
+
+
+    psArray *array;
+
+    // output position is fixed
+    psMetadataAddStr (config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "", argv[1]);
+
+    // skycell position is fixed
+    array = psArrayAlloc(1);
+    array->data[0] = psStringCopy (argv[2]);
+    status = psMetadataAddPtr (config->arguments, PS_LIST_TAIL, "SKYCELL", PS_DATA_ARRAY, "", array);
+    psFree (array);
+
+    psTrace("pswarp", 1, "Done with pswarpArguments...\n");
+    return (config);
+}
+
+// Parse the recipe and format into the arguments
+bool pswarpOptions(pmConfig *config)
+{
+    // Select the appropriate recipe
+    psMetadata *recipe  = psMetadataLookupPtr (NULL, config->recipes, PSWARP_RECIPE);
+    if (!recipe) {
+        psError(PSWARP_ERR_CONFIG, true, "Can't find %s recipe!\n", PSWARP_RECIPE);
+        return false;
+    }
+
+    // Get grid size
+    bool status;                        // Status of MD lookup
+    int nGridX = psMetadataLookupS32 (&status, recipe, "GRID.NX");
+    if (!status) nGridX = 128;
+    int nGridY = psMetadataLookupS32 (&status, recipe, "GRID.NY");
+    if (!status) nGridY = 128;
+
+    // Get interpolation mode
+    psImageInterpolateMode interpolationMode; // Mode for interpolation
+    const char *name = psMetadataLookupStr (&status, recipe, "INTERPOLATION.MODE"); // Name of interp mode
+    if (!name) {
+        interpolationMode = PS_INTERPOLATE_BILINEAR;
+        psLogMsg ("pswarp", 3, "defaulting to bilinear interpolation\n");
+    } else {
+        interpolationMode = psImageInterpolateModeFromString (name);
+        if (interpolationMode == PS_INTERPOLATE_NONE) {
+            interpolationMode = PS_INTERPOLATE_BILINEAR;
+            psLogMsg ("pswarp", 3,
+                      "Unknown interpolation mode %s, defaulting to bilinear interpolation\n", name);
+        }
+    }
+
+    float poorFrac = psMetadataLookupF32(&status, recipe, "POOR.FRAC"); // Frac of bad flux for a "poor"
+    if (!status) {
+        poorFrac = 0.0;
+        psWarning("POOR.FRAC is not set in the %s recipe --- defaulting to %f.", PSWARP_RECIPE, poorFrac);
+    }
+
+    float acceptFrac = psMetadataLookupF32(&status, recipe, "ACCEPT.FRAC"); // Min fraction of good pixels
+    if (!status) {
+        acceptFrac = 0.0;
+        psWarning("ACCEPT.FRAC is not set in the %s recipe --- defaulting to %f.", PSWARP_RECIPE, poorFrac);
+    }
+
+    // Set recipe values in the arguments
+    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "GRID.NX", 0,
+                     "Iso-astrom grid spacing in x", nGridX);
+    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "GRID.NY", 0,
+                     "Iso-astrom grid spacing in y", nGridY);
+    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "INTERPOLATION.MODE", 0,
+                     "Interpolation mode", interpolationMode);
+    psMetadataAddF32(config->arguments, PS_LIST_TAIL, "POOR.FRAC", 0,
+                     "Fraction of bad flux for a pixel to be marked as poor", poorFrac);
+    psMetadataAddF32(config->arguments, PS_LIST_TAIL, "ACCEPT.FRAC", 0,
+                     "Minimum fraction of good pixels for result to be accepted", acceptFrac);
+
+    psTrace("pswarp", 1, "Done with pswarpArguments...\n");
+    return (config);
+}
Index: /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpCleanup.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpCleanup.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpCleanup.c	(revision 22157)
@@ -0,0 +1,17 @@
+# include "pswarp.h"
+
+void pswarpCleanup (pmConfig *config) {
+
+    psFree (config);
+
+    psTimerStop ();
+    psMemCheckCorruption(stderr, true);
+    pmModelClassCleanup ();
+    psTimeFinalize ();
+    pmConceptsDone ();
+    pmConfigDone ();
+    // fprintf (stderr, "found %d leaks at %s\n", psMemCheckLeaks (0, NULL, stdout, false), "pswarp");
+    fprintf (stderr, "found %d leaks at %s\n", psMemCheckLeaks (0, NULL, NULL, false), "pswarp");
+
+    return;
+}
Index: /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpDefine.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpDefine.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpDefine.c	(revision 22157)
@@ -0,0 +1,112 @@
+# include "pswarp.h"
+// this function loads the skycell metadata
+
+bool pswarpDefine (pmConfig *config) {
+
+    // load the PSWARP recipe
+    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSWARP_RECIPE);
+    if (!recipe) {
+        psError(PSWARP_ERR_CONFIG, true, "Can't find PSWARP recipe!\n");
+        return false;
+    }
+
+    // select the input data sources
+    pmFPAfile *skycell = psMetadataLookupPtr (NULL, config->files, "PSWARP.SKYCELL");
+    if (!skycell) {
+        psError(PSWARP_ERR_CONFIG, false, "Can't find skycell data!\n");
+        return false;
+    }
+    pmFPAfile *output = psMetadataLookupPtr(NULL, config->files, "PSWARP.OUTPUT");
+    if (!output) {
+        psError(PSWARP_ERR_CONFIG, false, "Can't find output data!\n");
+        return false;
+    }
+    pmFPAfile *input = psMetadataLookupPtr(NULL, config->files, "PSWARP.INPUT");
+    if (!input) {
+        psError(PSWARP_ERR_CONFIG, false, "Can't find input data!\n");
+        return false;
+    }
+
+    // open the full skycell file; no need to defer different depths. only load the header data
+    pmFPAview *view = pmFPAviewAlloc (0);
+    pmFPAfileOpen (skycell, view, config);
+
+    // Read header and create target
+    {
+        if (!pmFPAReadHeaderSet(skycell->fpa, skycell->fits, config)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to read headers for skycell.");
+            psFree(view);
+            return false;
+        }
+        view->chip = 0;
+        view->cell = 0;
+        view->readout = 0;
+        pmCell *source = pmFPAfileThisCell(config->files, view, "PSWARP.SKYCELL"); // Source cell
+        pmHDU *hdu = pmHDUFromCell(source); // HDU for source
+        if (!hdu || !hdu->header) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find header for sky cell.");
+            psFree(view);
+            return false;
+        }
+        int numCols = psMetadataLookupS32(NULL, hdu->header, "NAXIS1"); // Number of columns
+        int numRows = psMetadataLookupS32(NULL, hdu->header, "NAXIS2"); // Number of rows
+
+        pmCell *target = pmFPAviewThisCell(view, output->fpa); // Target cell
+        pmReadout *readout = pmReadoutAlloc(target); // Target readout
+        readout->image = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+        psImageInit(readout->image, NAN);
+        psFree(readout);                // Drop reference
+
+        psMetadataItemSupplement(target->concepts, source->concepts, "CELL.XBIN");
+        psMetadataItemSupplement(target->concepts, source->concepts, "CELL.YBIN");
+        psMetadataItemSupplement(target->concepts, source->concepts, "CELL.XSIZE");
+        psMetadataItemSupplement(target->concepts, source->concepts, "CELL.YSIZE");
+        psMetadataItemSupplement(target->concepts, source->concepts, "CELL.XPARITY");
+        psMetadataItemSupplement(target->concepts, source->concepts, "CELL.YPARITY");
+        psMetadataItemSupplement(target->concepts, source->concepts, "CELL.X0");
+        psMetadataItemSupplement(target->concepts, source->concepts, "CELL.Y0");
+    }
+
+    // XXX this is not a sufficient test
+    view->chip = 0;
+    view->cell = 0;
+    view->readout = -1;
+    pmHDU *phu = pmFPAviewThisPHU(view, skycell->fpa); // Skycell PHU
+    pmHDU *hdu = pmFPAviewThisHDU(view, skycell->fpa); // Skycell header
+    bool bilevelAstrometry = false;
+    if (phu) {
+        char *ctype = psMetadataLookupStr(NULL, phu->header, "CTYPE1");
+        if (ctype) {
+            bilevelAstrometry = !strcmp(&ctype[4], "-DIS");
+        }
+    }
+
+    // We read from the skycell into the output.  i.e., the output receives the desired astrometry.
+    pmChip *outputChip = pmFPAviewThisChip(view, output->fpa); // Chip in the output
+    if (bilevelAstrometry) {
+        if (!pmAstromReadBilevelMosaic(output->fpa, phu->header)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to read bilevel mosaic astrometry for skycell.");
+            psFree(view);
+            return false;
+        }
+        if (!pmAstromReadBilevelChip(outputChip, hdu->header)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to read bilevel chip astrometry for skycell.");
+            psFree(view);
+            return false;
+        }
+    } else {
+        // we use a default FPA pixel scale of 1.0
+        if (!pmAstromReadWCS(output->fpa, outputChip, hdu->header, 1.0)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to read WCS astrometry for skycell.");
+            psFree(view);
+            return false;
+        }
+    }
+
+    const char *name = psMetadataLookupStr(NULL, input->fpa->concepts, "FPA.NAME"); // Name of FPA
+    view->chip = view->cell = view->readout = -1;
+    pmFPAAddSourceFromView(output->fpa, name, view, output->format);
+
+    psFree (view);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpDefineSkycell.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpDefineSkycell.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpDefineSkycell.c	(revision 22157)
@@ -0,0 +1,144 @@
+# include "pswarp.h"
+
+// XXX this function is based on pmFPAfileDefineFromArgs
+// a skycell consists of only one file
+
+// search for argname on the config->argument list
+// construct an FPA based on the files in this list (must represent a single FPA)
+// built the association between the FPA elements (CHIP/CELL) and the files
+// define the pmFPAfile filename and bind it to this FPA
+// save the pmFPAfile on config->files
+// return the pmFPAfile (a view to the one saved on config->files)
+bool pswarpDefineSkycell (pmFPAfile **outFile, pmConfig **outConfig, pmConfig *config, const char *filename, const char *argname)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(argname, NULL);
+
+    bool status;
+    pmFPA *fpa = NULL;
+    psFits *fits = NULL;
+    pmFPAfile *file = NULL;
+    psMetadata *phu = NULL;
+    psMetadata *format = NULL;
+    pmConfig *skyConfig = NULL;
+
+    *outFile = NULL;
+    *outConfig = NULL;
+
+    // we search the argument data for the named fileset (argname)
+    psArray *infiles = psMetadataLookupPtr(&status, config->arguments, argname);
+    if (!status) {
+        return false;
+    }
+    if (infiles->n != 1) {
+        psError(PS_ERR_IO, false, "Found n == %ld files in %s in arguments\n", infiles->n, argname);
+        return false;
+    }
+
+    // this function is implicitly an INPUT operation: do not create the file
+    psString realName = pmConfigConvertFilename (infiles->data[0], config, false, false);
+    if (!realName) {
+        psError(PS_ERR_IO, false, "Failed to convert file name %s\n", (char *) infiles->data[0]);
+        return false;
+    }
+
+    // load the header of the image
+    fits = psFitsOpen (realName, "r");
+    if (!fits) {
+        psError(PS_ERR_IO, false, "Failed to open file %s\n", realName);
+        psFree (realName);
+        return false;
+    }
+    phu = psFitsReadHeader (NULL, fits);
+    if (!phu) {
+        psError(PS_ERR_IO, false, "Failed to read file header %s\n", realName);
+        psFree (realName);
+        return false;
+    }
+    psFitsClose(fits);
+
+    // XXX format needs to be equivalent to SIMPLE
+    // determine the current format from the header
+    // determine camera if not specified already
+    // XXX EAM : this operation should be defined as a pmConfig function (pmConfigCopy?)
+    skyConfig = pmConfigAlloc(0, NULL);
+    skyConfig->complete = psMemIncrRefCounter (config->complete);
+
+    psFree (skyConfig->files);
+    skyConfig->files = psMemIncrRefCounter (config->files);
+    psFree (skyConfig->arguments);
+    skyConfig->arguments = psMemIncrRefCounter (config->arguments);
+
+    format = pmConfigCameraFormatFromHeader (NULL, NULL, skyConfig, phu, false);
+    if (!format) {
+        psError(PS_ERR_IO, false, "Failed to read CCD format from %s\n", realName);
+        psFree(phu);
+        psFree (realName);
+        return false;
+    }
+
+    // build the template fpa, set up the basic view
+    fpa = pmFPAConstruct (skyConfig->camera, skyConfig->cameraName);
+    if (!fpa) {
+        psError(PS_ERR_IO, false, "Failed to construct FPA from %s", realName);
+        psFree (realName);
+        return false;
+    }
+    psFree (realName);
+
+    // load the given filerule (from config->camera) and bind it to the fpa
+    // the returned file is just a view to the entry on config->files
+    file = pmFPAfileDefineInput (skyConfig, fpa, filename);
+    if (!file) {
+        psError(PS_ERR_IO, false, "file %s not defined\n", filename);
+        psFree(phu);
+        psFree(fpa);
+        psFree(format);
+        return false;
+    }
+    psFree (format);
+    file->format = psMemIncrRefCounter(format);
+
+    // adjust the rules to identify these files in the file->names data
+    psFree (file->filerule);
+    file->filerule = psStringCopy ("@FILES");
+    file->filesrc = psStringCopy ("{CHIP.NAME}.{CELL.NAME}");
+
+    file->fileLevel = pmFPAPHULevel(format);
+    if (file->fileLevel == PM_FPA_LEVEL_NONE) {
+        psError(PS_ERR_IO, true, "Unable to determine file level for %s\n", file->name);
+        psFree(phu);
+        psFree(fpa);
+        psFree(format);
+        psFree(file);
+        return false;
+    }
+
+    // set the view to the corresponding entry for this phu
+    pmFPAview *view = pmFPAAddSourceFromHeader (fpa, phu, format);
+    if (!view) {
+        psError(PS_ERR_IO, false, "Unable to determine source for %s", file->name);
+        psFree(phu);
+        psFree (fpa);
+        psFree (format);
+        return false;
+    }
+
+    // associate the filename with the FPA element
+    char *name = pmFPAfileNameFromRule (file->filesrc, file, view);
+
+    // save the name association in the pmFPAfile structure
+    psMetadataAddStr (file->names, PS_LIST_TAIL, name, 0, "", infiles->data[0]);
+
+    psFree (view);
+    psFree (name);
+    psFree (phu);
+    psFree (fpa);
+
+    *outFile = file;
+    *outConfig = skyConfig;
+
+    return true;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpErrorCodes.c.in
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpErrorCodes.c.in	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpErrorCodes.c.in	(revision 22157)
@@ -0,0 +1,25 @@
+/*
+ * The line
+    { PSWARP_ERR_$X{ErrorCode}, "$X{ErrorDescription}"},
+ * (without the Xs)
+ * will be replaced by values from errorCodes.dat
+ */
+#include "pslib.h"
+#include "pswarpErrorCodes.h"
+
+void pswarpErrorRegister(void)
+{
+    static psErrorDescription errors[] = {
+       { PSWARP_ERR_BASE, "First value we use; lower values belong to psLib" },
+       { PSWARP_ERR_${ErrorCode}, "${ErrorDescription}"},
+    };
+    static int nerror = PSWARP_ERR_NERROR - PSWARP_ERR_BASE; // number of values in enum
+
+    for (int i = 0; i < nerror; i++) {
+       psErrorDescription *tmp = psAlloc(sizeof(psErrorDescription));
+       *tmp = errors[i];
+       psErrorRegister(tmp, 1);
+       psFree(tmp);			/* it's on the internal list */
+    }
+    nerror = 0;			                // don't register more than once
+}
Index: /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpErrorCodes.dat
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpErrorCodes.dat	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpErrorCodes.dat	(revision 22157)
@@ -0,0 +1,10 @@
+#
+# This file is used to generate pswarpErrorClasses.h
+#
+BASE = 800		First value we use; lower values belong to psLib
+UNKNOWN			Unknown PM error code
+NOT_IMPLEMENTED		Desired feature is not yet implemented
+ARGUMENTS		Incorrect arguments
+CONFIG			Problem in configure files
+IO			Problem in FITS I/O
+DATA                    Problem in data values
Index: /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpErrorCodes.h.in
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpErrorCodes.h.in	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpErrorCodes.h.in	(revision 22157)
@@ -0,0 +1,18 @@
+#if !defined(PSWARP_ERROR_CODES_H)
+#define PSWARP_ERROR_CODES_H
+/*
+ * The line
+ *  PSWARP_ERR_$X{ErrorCode},
+ * (without the X)
+ *
+ * will be replaced by values from errorCodes.dat
+ */
+typedef enum {
+    PSWARP_ERR_BASE = 600,
+    PSWARP_ERR_${ErrorCode},
+    PSWARP_ERR_NERROR
+} pswarpErrorCode;
+
+void pswarpErrorRegister(void);
+
+#endif
Index: /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpHeadersLoad.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpHeadersLoad.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpHeadersLoad.c	(revision 22157)
@@ -0,0 +1,117 @@
+# include "pswarp.h"
+
+// XXX this function should load all of the PSWARP.INPUT headers
+// it should examine the overlap between each chip in PSWARP.INPUT
+// and the output (pswarpMatchRange) and select/de-select the chips
+// and/or cell which contribute pixels.
+
+// pswarpDataLoad should then load the pixel of the needed chips
+
+// all of the different astrometry analysis modes use the same data load loop
+bool pswarpHeadersLoad (pmConfig *config) {
+
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+    pmFPAview *view;
+
+    // select the input data sources
+    pmFPAfile *input = psMetadataLookupPtr (NULL, config->files, "PSWARP.INPUT");
+    if (!input) {
+        psError(PSWARP_ERR_CONFIG, true, "Can't find input data!\n");
+        return false;
+    }
+
+    // use the external astrometry source if supplied
+    pmFPAfile *astrom = psMetadataLookupPtr (NULL, config->files, "PSWARP.ASTROM");
+    if (!astrom) {
+        astrom = input;
+    }
+
+    // select the output readout
+    view = pmFPAviewAlloc (0);
+    view->chip = 0;
+    view->cell = 0;
+    view->readout = 0;
+    pmReadout  *output = pmFPAfileThisReadout (config->files, view, "PSWARP.OUTPUT");
+    if (!output) {
+        psError(PSWARP_ERR_CONFIG, true, "Can't find output data!\n");
+        return false;
+    }
+    psFree (view);
+
+    // de-activate PSWARP.SKYCELL and PSWARP.OUTPUT
+    pmFPAfileActivate(config->files, false, "PSWARP.SKYCELL");
+    pmFPAfileActivate(config->files, false, "PSWARP.OUTPUT");
+
+    view = pmFPAviewAlloc (0);
+
+    // XXX need to read only the headers for the skycell
+    // XXX these pmAstromReadBilevel functions seem to be broken
+
+    // find the FPA phu
+    bool bilevelAstrometry = false;
+    pmHDU *phu = pmFPAviewThisPHU(view, astrom->fpa);
+    if (phu) {
+        char *ctype = psMetadataLookupStr (NULL, phu->header, "CTYPE1");
+        if (ctype) {
+            bilevelAstrometry = !strcmp(&ctype[4], "-DIS");
+        }
+    }
+    if (bilevelAstrometry) {
+        if (!pmAstromReadBilevelMosaic(input->fpa, phu->header)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to read bilevel mosaic astrometry for input.");
+            psFree(view);
+            return false;
+        }
+    }
+
+    // files associated with the science image
+    pmFPAfileIOChecks (config, view, PM_FPA_BEFORE);
+
+    while ((chip = pmFPAviewNextChip (view, input->fpa, 1)) != NULL) {
+        psTrace ("pswarp", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+        pmFPAfileIOChecks (config, view, PM_FPA_BEFORE);
+
+        // read WCS data from the corresponding header
+        pmHDU *hdu = pmFPAviewThisHDU (view, astrom->fpa);
+        if (bilevelAstrometry) {
+            if (!pmAstromReadBilevelChip (chip, hdu->header)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to read bilevel chip astrometry for input.");
+                psFree(view);
+                return false;
+            }
+        } else {
+            // we use a default FPA pixel scale of 1.0
+            if (!pmAstromReadWCS(input->fpa, chip, hdu->header, 1.0)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to read WCS astrometry for input.");
+                psFree(view);
+                return false;
+            }
+        }
+
+        while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+            psTrace ("pswarp", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+            pmFPAfileIOChecks (config, view, PM_FPA_BEFORE);
+
+            // process each of the readouts
+            while ((readout = pmFPAviewNextReadout (view, input->fpa, 1)) != NULL) {
+                pmFPAfileIOChecks (config, view, PM_FPA_BEFORE);
+                if (! readout->data_exists) { continue; }
+
+                // XXX Replace with a function to examine the overlap and turn on/off chips
+                // based on that result
+                // pswarpTransformReadout_Opt (output, readout, config);
+
+                pmFPAfileIOChecks (config, view, PM_FPA_AFTER);
+            }
+            pmFPAfileIOChecks (config, view, PM_FPA_AFTER);
+        }
+        pmFPAfileIOChecks (config, view, PM_FPA_AFTER);
+    }
+    pmFPAfileIOChecks (config, view, PM_FPA_AFTER);
+    psFree (view);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpLoop.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpLoop.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpLoop.c	(revision 22157)
@@ -0,0 +1,400 @@
+#include "pswarp.h"
+#include <ppStats.h>
+
+#define WCS_NONLIN_TOL 0.001            // Non-linear tolerance for header WCS
+
+// Lists of file rules for the detectors and skycells, and an independent list of everything else
+// Lists must end with a NULL
+static char *detectorFiles[] = { "PSWARP.INPUT", "PSWARP.MASK", "PSWARP.WEIGHT", NULL };
+static char *skycellFiles[] = { "PSWARP.OUTPUT", "PSWARP.OUTPUT.MASK", "PSWARP.OUTPUT.WEIGHT",
+                                "PSPHOT.OUTPUT", "PSPHOT.RESID", "PSPHOT.BACKMDL", "PSPHOT.BACKMDL.STDEV",
+                                "PSPHOT.BACKGND", "PSPHOT.BACKSUB", "PSPHOT.PSF.SAVE", "SOURCE.PLOT.MOMENTS",
+                                "SOURCE.PLOT.PSFMODEL", "SOURCE.PLOT.APRESID", NULL };
+static char *independentFiles[] = { "PSWARP.ASTROM", // Read independently from the input pixels
+                                    "PSWARP.SKYCELL", // Don't care about the skycell once we have its WCS
+                                    "PSPHOT.INPUT", // This is just a pointer to PSWARP.OUTPUT
+                                    "PSWARP.OUTPUT.SOURCES", // Save these independently so we can do the PSF
+                                    NULL };
+
+// Activate a list of files
+static void fileActivation(pmConfig *config, // Configuration
+                           char **files, // Files to turn on/off
+                           bool state   // Activation state
+    )
+{
+    for (int i = 0; files[i] != NULL; i++) {
+        pmFPAfileActivate(config->files, state, files[i]);
+    }
+    return;
+}
+
+// Run down the FPA hierarchy, checking files
+static bool ioChecksBefore(pmConfig *config // Configuration
+                           )
+{
+    pmFPAview *view = pmFPAviewAlloc(0); // View for checking
+    bool status = true;                 // Status of checks
+    status &= pmFPAfileIOChecks(config, view, PM_FPA_BEFORE);
+    view->chip = 0;
+    status &= pmFPAfileIOChecks(config, view, PM_FPA_BEFORE);
+    view->cell = 0;
+    status &= pmFPAfileIOChecks(config, view, PM_FPA_BEFORE);
+    psFree(view);
+    return status;
+}
+
+// Run up the FPA hierarchy, checking files
+static bool ioChecksAfter(pmConfig *config // Configuration
+                          )
+{
+    pmFPAview *view = pmFPAviewAlloc(0); // View for checking
+    view->chip = view->cell = 0;
+    bool status = true;                 // Status of checks
+    status &= pmFPAfileIOChecks(config, view, PM_FPA_AFTER);
+    view->cell = -1;
+    status &= pmFPAfileIOChecks(config, view, PM_FPA_AFTER);
+    view->chip = -1;
+    status &= pmFPAfileIOChecks(config, view, PM_FPA_AFTER);
+    psFree(view);
+    return status;
+}
+
+
+// Loop over the inputs, warp them to the output skycell and then write out the output.
+bool pswarpLoop(pmConfig *config)
+{
+    bool status; 
+
+    // load the recipe
+    psMetadata *recipe = psMetadataLookupPtr (&status, config->recipes, PSWARP_RECIPE);
+    if (!recipe) {
+        psError(PSPHOT_ERR_CONFIG, false, "missing recipe %s", PSWARP_RECIPE);
+        return false;
+    }
+
+    // output mask bits
+    psMaskType maskValue = psMetadataLookupU8(&status, recipe, "MASK.OUTPUT"); 
+    psAssert (status, "MASK.OUTPUT was not defined");
+
+    // select the input data sources
+    pmFPAfile *input = psMetadataLookupPtr(NULL, config->files, "PSWARP.INPUT");
+    if (!input) {
+        psError(PSWARP_ERR_CONFIG, true, "Can't find input data!\n");
+        return false;
+    }
+
+    // use the external astrometry source if supplied
+    pmFPAfile *astrom = psMetadataLookupPtr(NULL, config->files, "PSWARP.ASTROM");
+    if (!astrom) {
+        astrom = input;
+    }
+
+    if (astrom->camera != input->camera) {
+        psError(PS_ERR_UNKNOWN, true, "Input camera and astrometry camera do not match.");
+        return false;
+    }
+
+    // select the output readout
+    pmFPAview *view = pmFPAviewAlloc(0);
+    view->chip = 0;
+    view->cell = 0;
+    view->readout = 0;
+    pmReadout *output = pmFPAfileThisReadout(config->files, view, "PSWARP.OUTPUT");
+    if (!output) {
+        psError(PSWARP_ERR_CONFIG, true, "Can't find output data!\n");
+        return false;
+    }
+    psFree (view);
+
+    bool mdok;                          // Status of MD lookup
+    const char *statsName = psMetadataLookupStr(&mdok, config->arguments, "STATS"); // Filename for statistics
+    psMetadata *stats = NULL;           // Container for statistics
+    FILE *statsFile = NULL;             // File stream for statistics
+    if (mdok && statsName && strlen(statsName) > 0) {
+        psString resolved = pmConfigConvertFilename(statsName, config, true, true);
+        statsFile = fopen(resolved, "w");
+        if (!statsFile) {
+            psError(PS_ERR_IO, true, "Unable to open statistics file %s for writing.\n", resolved);
+            psFree(resolved);
+            return false;
+        }
+        psFree(resolved);
+        stats = psMetadataAlloc();
+    }
+
+    // Turn all skycell files on to generate them, and then turn them off for the loop over the input images
+    // the input, which is in a different format.
+    {
+        fileActivation(config, detectorFiles, false);
+        fileActivation(config, independentFiles, false);
+        fileActivation(config, skycellFiles, true);
+        ioChecksBefore(config);
+        fileActivation(config, skycellFiles, false);
+    }
+
+    // Read the input astrometry
+    {
+        pmFPAfileActivate(config->files, true, "PSWARP.ASTROM");
+
+        pmChip *chip;
+        pmFPAview *view = pmFPAviewAlloc(0);
+        pmFPAfileIOChecks (config, view, PM_FPA_BEFORE);
+        while ((chip = pmFPAviewNextChip (view, input->fpa, 1)) != NULL) {
+            psTrace ("pswarp", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+            if (!chip->process || !chip->file_exists) { continue; }
+            pmFPAfileIOChecks(config, view, PM_FPA_BEFORE);
+            pmCell *cell;
+            while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+                psTrace ("pswarp", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+                if (!cell->process || !cell->file_exists) { continue; }
+                pmFPAfileIOChecks (config, view, PM_FPA_BEFORE);
+                pmFPAfileIOChecks (config, view, PM_FPA_AFTER);
+            }
+            pmFPAfileIOChecks (config, view, PM_FPA_AFTER);
+        }
+        pmFPAfileIOChecks (config, view, PM_FPA_AFTER);
+        psFree(view);
+
+        fileActivation(config, detectorFiles, true);
+        pmFPAfileActivate(config->files, false, "PSWARP.ASTROM");
+    }
+
+    // Turn on the source output --- we need to get rid of these so that we can measure the PSF
+    pmFPAfileActivate(config->files, true, "PSWARP.OUTPUT.SOURCES");
+
+    // Don't care about the skycell anymore --- we've read it, and that's all we need to do.
+    pmFPAfileActivate(config->files, false, "PSWARP.SKYCELL");
+    view = pmFPAviewAlloc(0);
+
+    // find the FPA phu
+    bool bilevelAstrometry = false;
+    pmHDU *phu = pmFPAviewThisPHU(view, astrom->fpa);
+    if (phu) {
+        char *ctype = psMetadataLookupStr(NULL, phu->header, "CTYPE1");
+        if (ctype) {
+            bilevelAstrometry = !strcmp (&ctype[4], "-DIS");
+        }
+    }
+    if (bilevelAstrometry) {
+        if (!pmAstromReadBilevelMosaic(input->fpa, phu->header)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to read bilevel mosaic astrometry for input FPA.");
+            psFree(view);
+            psFree(stats);
+            return false;
+        }
+    }
+
+    psList *cells = psListAlloc(NULL);  // List of cells, for concepts averaging
+
+    // files associated with the science image
+    pmFPAfileIOChecks (config, view, PM_FPA_BEFORE);
+
+    pmChip *chip;
+    while ((chip = pmFPAviewNextChip (view, input->fpa, 1)) != NULL) {
+        psTrace ("pswarp", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+        pmFPAfileIOChecks (config, view, PM_FPA_BEFORE);
+
+        // read WCS data from the corresponding header
+        pmHDU *hdu = pmFPAviewThisHDU (view, astrom->fpa);
+        if (bilevelAstrometry) {
+            if (!pmAstromReadBilevelChip (chip, hdu->header)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to read bilevel chip astrometry for input FPA.");
+                psFree(view);
+                psFree(stats);
+                return false;
+            }
+        } else {
+            // we use a default FPA pixel scale of 1.0
+            if (!pmAstromReadWCS (input->fpa, chip, hdu->header, 1.0)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to read WCS astrometry for input FPA.");
+                psFree(view);
+                psFree(stats);
+                return false;
+            }
+        }
+
+        pmCell *cell;
+        while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+            psTrace ("pswarp", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+            pmFPAfileIOChecks (config, view, PM_FPA_BEFORE);
+
+            psListAdd(cells, PS_LIST_TAIL, cell);
+
+            // process each of the readouts
+            pmReadout *readout;
+            while ((readout = pmFPAviewNextReadout(view, input->fpa, 1)) != NULL) {
+                pmFPAfileIOChecks(config, view, PM_FPA_BEFORE);
+                if (!readout->data_exists) {
+                    continue;
+                }
+
+                // Copy the sources from the astrometry carrier to the input, so they can be accessed by
+                // pswarpTransformReadout
+                pmReadout *astromRO = pmFPAviewThisReadout(view, astrom->fpa); // Readout for astrometry
+                psArray *sources = psMetadataLookupPtr(&mdok, astromRO->analysis,
+                                                       "PSPHOT.SOURCES"); // Sources from astrometry
+                if (sources) {
+                    psMetadataAddPtr(readout->analysis, PS_LIST_TAIL, "PSPHOT.SOURCES", PS_DATA_ARRAY,
+                                     "Sources from input astrometry", sources);
+                }
+
+                pswarpTransformReadout_Opt(output, readout, config);
+
+                pmFPAfileIOChecks(config, view, PM_FPA_AFTER);
+            }
+            pmFPAfileIOChecks(config, view, PM_FPA_AFTER);
+        }
+        pmFPAfileIOChecks(config, view, PM_FPA_AFTER);
+    }
+
+    pmCell *outCell = output->parent;   // Output cell
+    pmChip *outChip = outCell->parent;  // Output chip
+    pmFPA *outFPA = outChip->parent;    // Output FP
+
+    if (!pswarpPixelFraction(output, stats, config)) {
+        // Don't write output images, and don't bother about anything else
+        output->data_exists = outCell->data_exists = outChip->data_exists = false;
+        psFree(cells);
+        psFree(view);
+        goto COMPLETED;
+    }
+
+    // Set variance factor
+    {
+        float varFactor = psMetadataLookupF32(NULL, output->analysis, PSWARP_ANALYSIS_VARFACTOR);
+        psAssert(isfinite(varFactor), "Should be something here.");
+        long goodPix = psMetadataLookupS64(NULL, output->analysis, PSWARP_ANALYSIS_GOODPIX);
+        psAssert(goodPix > 0, "Should be something here.");
+        varFactor /= goodPix;
+
+        psMetadataItem *vfItem = psMetadataLookup(outCell->concepts, "CELL.VARFACTOR"); // Item to update
+        psAssert(vfItem && vfItem->type == PS_TYPE_F32, "Concept should be as specified.");
+        if (!isfinite(vfItem->data.F32)) {
+            vfItem->data.F32 = varFactor;
+        } else {
+            vfItem->data.F32 *= varFactor;
+        }
+    }
+
+    if (!pmConceptsAverageCells(outCell, cells, NULL, NULL, false)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to average cell concepts.");
+        psFree(stats);
+        psFree(cells);
+        psFree(view);
+        return false;
+    }
+    psFree(cells);
+
+    if (!psMetadataCopy(outFPA->concepts, input->fpa->concepts)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to copy FPA concepts from input to output.");
+        psFree(stats);
+        psFree(view);
+        return false;
+    }
+
+    pmFPAfileIOChecks(config, view, PM_FPA_AFTER);
+
+    // Done with the detector side of things
+    fileActivation(config, detectorFiles, false);
+    fileActivation(config, independentFiles, false);
+    fileActivation(config, skycellFiles, true);
+
+    // We need a new PSF model for the warped frame
+    // It would be good to generate this analytically, but that's going to be tricky.
+    // We have a list of sources, so we could use those to redetermine the PSF model.
+    // Until Gene makes the necessary adaptations to psphot, we will simply redetermine the PSF model from
+    // scratch.
+    if (psMetadataLookupBool(&mdok, config->arguments, "PSF")) {
+        psMetadata *psphotRecipe = psMetadataLookupPtr(NULL, config->recipes,
+                                                       PSPHOT_RECIPE); // Recipe for psphot
+        if (!psphotRecipe) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s recipe.", PSPHOT_RECIPE);
+            return false;
+        }
+
+        psMetadataAddStr(psphotRecipe, PS_LIST_TAIL, "BREAK_POINT", PS_META_REPLACE,
+                         "Break point for psphot operations", "PSFMODEL");
+        psMetadataAddBool(psphotRecipe, PS_LIST_TAIL, PSPHOT_RECIPE_PSF_FAKE_ALLOW, PS_META_REPLACE,
+                          "Don't allow psphot to fit a fake PSF!", false);
+
+        // Get rid of the transformed sources so that we can measure the PSF
+        psMetadataRemoveKey(output->analysis, "PSPHOT.SOURCES");
+
+#if 0
+        pmFPAfile *photFile = psMetadataLookupPtr(NULL, config->files, "PSPHOT.INPUT");
+        pmFPACopy(photFile->fpa, outFPA);
+#endif
+
+        pmFPAview *view = pmFPAviewAlloc(0); // View into skycell
+        view->chip = view->cell = view->readout = 0;
+        if (!psphotReadout(config, view)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to determine PSF for warped image.");
+            return false;
+        }
+        psFree(view);
+
+        pmFPAfileActivate(config->files, true, "PSPHOT.INPUT");
+        pmFPAfileActivate(config->files, true, "PSPHOT.PSF.SAVE");
+    }
+
+    // Perform statistics on the output image
+    if (stats) {
+        if (!ppStatsFPA(stats, output->parent->parent->parent, view, maskValue, config)) {
+            psWarning("Unable to perform statistics on warped image.");
+        }
+    }
+
+    pmHDU *hdu = outFPA->hdu;           // HDU for the output warped image
+    if (!hdu->header) {
+        hdu->header = psMetadataAlloc();
+    }
+
+    if (!pmAstromWriteWCS(hdu->header, outFPA, outChip, WCS_NONLIN_TOL)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to generate WCS header.");
+        psFree(stats);
+        return false;
+    }
+
+    // Add MD5 information for readout
+    const char *chipName = psMetadataLookupStr(NULL, output->parent->parent->concepts, "CHIP.NAME");
+    const char *cellName = psMetadataLookupStr(NULL, output->parent->concepts, "CELL.NAME");
+    psString headerName = NULL; // Header name for MD5
+    psStringAppend(&headerName, "MD5_%s_%s_%d", chipName, cellName, view->readout);
+    psVector *md5 = psImageMD5(output->image); // md5 hash
+    psString md5string = psMD5toString(md5); // String
+    psFree(md5);
+    psMetadataAddStr(hdu->header, PS_LIST_TAIL, headerName, PS_META_REPLACE,
+                     "Image MD5", md5string);
+    psFree(md5string);
+    psFree(headerName);
+    psFree(view);
+
+    // Ensure everything is written out, at every level
+    ioChecksAfter(config);
+
+    // Now done with the skycell side of things
+
+    COMPLETED:
+    // Write out summary statistics
+    if (stats) {
+        psMetadataAddF32(stats, PS_LIST_TAIL, "DT_WARP", 0, "Time for warp completion",
+                         psTimerMark("pswarp"));
+
+        const char *statsMDC = psMetadataConfigFormat(stats);
+        if (!statsMDC) {
+            psError(PS_ERR_IO, false, "Unable to get statistics MDC file.\n");
+            psFree(stats);
+            fclose(statsFile);
+            return false;
+        }
+        fprintf(statsFile, "%s", statsMDC);
+        psFree((void*)statsMDC);
+        fclose(statsFile);
+
+        psFree(stats);
+    }
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpMapGrid.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpMapGrid.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpMapGrid.c	(revision 22157)
@@ -0,0 +1,227 @@
+# include "pswarp.h"
+
+// pswarpMapGridFromImage builds a set (a grid) of locally-linear maps which convert the source
+// coordinates (src) to destination coordinates (dest).  we construct a grid with superpixel
+// spacing of nXpix, nYpix.  The transformation for each grid cell is valid for the superpixel.
+// The grid over-fills the source image so ever source image pixel is guaranteed to have a map.
+pswarpMapGrid *pswarpMapGridFromImage (pmReadout *dest, pmReadout *src, int nXpix, int nYpix) {
+
+    int i, ni;
+    int j, nj;
+
+    // start counting from the center of the superpixels
+    int xMin = 0.5*nXpix;
+    int yMin = 0.5*nYpix;
+
+    // the map is defined for coordinates in the image parent frame.
+    int Nx = src->image->numCols + src->image->col0;
+    int Ny = src->image->numRows + src->image->row0;
+
+    // allocate an extra superpixel to carry the remainder
+    int nXpts = Nx / nXpix;
+    int nYpts = Ny / nYpix;
+    if (Nx % nXpix) nXpts ++;
+    if (Ny % nYpix) nYpts ++;
+
+    // create the grid of maps
+    pswarpMapGrid *grid = pswarpMapGridAlloc (nXpts, nYpts);
+
+    // measure the map for the center of each superpixel
+    for (ni = 0, i = xMin; ni < nXpts; i += nXpix, ni++) {
+	for (nj = 0, j = yMin; nj < nYpts; j += nYpix, nj++) {
+	    pswarpMapSetLocalModel (grid->maps[ni][nj], dest, src, i, j);
+	}
+    }
+	    
+    grid->nXpix = nXpix;
+    grid->nYpix = nYpix;
+    grid->xMin = xMin;
+    grid->yMin = yMin;
+    return grid;
+}
+
+// set the grid coordinate (gridX,gridY) for the given source image coordinate (ix,iy)
+bool pswarpMapGridSetGrid (pswarpMapGrid *grid, int ix, int iy, int *gridX, int *gridY) {
+
+    *gridX = 0.5 + (ix - grid->xMin) / (double) grid->nXpix;
+    *gridY = 0.5 + (iy - grid->yMin) / (double) grid->nYpix;
+    return true;
+}
+
+// given the specified grid coordinate (gridX), return the x-coordinate for the source image
+// corresponding to the next grid cell
+int pswarpMapGridNextGrid_X (pswarpMapGrid *grid, int gridX) {
+
+    int nextX = (gridX + 0.5)*grid->nXpix + grid->xMin;
+    return nextX;
+}
+
+// given the specified grid coordinate (gridY), return the y-coordinate for the source image
+// corresponding to the next grid cell
+int pswarpMapGridNextGrid_Y (pswarpMapGrid *grid, int gridY) {
+
+    int nextY = (gridY + 0.5)*grid->nYpix + grid->yMin;
+    return nextY;
+}
+
+// measure the max error accumulated in applying one grid point to its neighbors
+// XXX double-check this
+double pswarpMapGridMaxError (pswarpMapGrid *grid) {
+
+    double xRaw, yRaw;
+    double xRef, yRef;
+    double maxError = 0;
+
+    for (int i = 0; i < grid->nXpts - 1; i++) {
+	for (int j = 0; j < grid->nYpts - 1; j++) {
+
+	    // measure the output coordinates for the next grid position using the current grid map
+	    // compare with the coordinates measured using the next grid map
+	    pswarpMapApply (&xRaw, &yRaw, grid->maps[i][j], grid->maps[i][j]->xo + grid->nXpix, grid->maps[i][j]->yo);
+	    pswarpMapApply (&xRef, &yRef, grid->maps[i+1][j], grid->maps[i][j]->xo + grid->nXpix, grid->maps[i][j]->yo);
+
+	    double posError = hypot (xRaw-xRef, yRaw-yRef);
+	    maxError = PS_MAX (maxError, posError);
+	}
+    }	
+    return maxError;
+}
+
+// given the source coordinate (inX,inY), return the destination coordinate (outX,outY)
+bool pswarpMapApply (double *outX, double *outY, pswarpMap *map, double inX, double inY) {
+
+    *outX = map->Xo + map->Xx*inX + map->Xy*inY;
+    *outY = map->Yo + map->Yx*inX + map->Yy*inY;
+
+    return true;
+}
+
+// determine the (linear) map for the given pixel (ix,iy) from source image (src) to the destination image (dest)
+// pixel is in src coords. input and output pixel coordinates are in the parent frame of the image (Note that the
+// astrometric transformations are supplied for the parent image coordinate frame.
+bool pswarpMapSetLocalModel (pswarpMap *map, pmReadout *dest, pmReadout *src, int ix, int iy) {
+
+    pmCell *cell = NULL;
+
+    cell = src->parent;
+    pmChip *chipSrc = cell->parent;
+    pmFPA *fpaSrc = chipSrc->parent;
+
+    cell = dest->parent;
+    pmChip *chipDest = cell->parent;
+    pmFPA *fpaDest = chipDest->parent;
+
+    // XXX save these as static for speed?
+    psPlane *offset = psPlaneAlloc();
+
+    psPlane *FP = psPlaneAlloc();
+    psPlane *TP = psPlaneAlloc();
+    psSphere *sky = psSphereAlloc();
+
+    psPlane *V00 = psPlaneAlloc();
+    psPlane *V10 = psPlaneAlloc();
+    psPlane *V01 = psPlaneAlloc();
+
+    // XXX need to include readout->cell->chip offsets
+
+    // V(0,0) position
+    offset->x = ix;
+    offset->y = iy;
+    psPlaneTransformApply(FP, chipSrc->toFPA, offset);
+    psPlaneTransformApply (TP, fpaSrc->toTPA, FP);
+    psDeproject (sky, TP, fpaSrc->toSky);
+    psProject (TP, sky, fpaDest->toSky);
+    psPlaneTransformApply (FP, fpaDest->fromTPA, TP);
+    psPlaneTransformApply (V00, chipDest->fromFPA, FP);
+    
+    // V(1,0) position
+    offset->x = ix + 1;
+    offset->y = iy;
+    psPlaneTransformApply(FP, chipSrc->toFPA, offset);
+    psPlaneTransformApply (TP, fpaSrc->toTPA, FP);
+    psDeproject (sky, TP, fpaSrc->toSky);
+    psProject (TP, sky, fpaDest->toSky);
+    psPlaneTransformApply (FP, fpaDest->fromTPA, TP);
+    psPlaneTransformApply (V10, chipDest->fromFPA, FP);
+    
+    // V(0,1) position
+    offset->x = ix;
+    offset->y = iy + 1;
+    psPlaneTransformApply(FP, chipSrc->toFPA, offset);
+    psPlaneTransformApply (TP, fpaSrc->toTPA, FP);
+    psDeproject (sky, TP, fpaSrc->toSky);
+    psProject (TP, sky, fpaDest->toSky);
+    psPlaneTransformApply (FP, fpaDest->fromTPA, TP);
+    psPlaneTransformApply (V01, chipDest->fromFPA, FP);
+
+    map->Xx = V10->x - V00->x;
+    map->Xy = V01->x - V00->x;
+    map->Xo = V00->x - map->Xx*ix - map->Xy*iy;
+
+    map->Yx = V10->y - V00->y;
+    map->Yy = V01->y - V00->y;
+    map->Yo = V00->y - map->Yx*ix - map->Yy*iy;
+ 
+    map->xo = ix;
+    map->yo = iy;
+
+    psFree (offset);
+    psFree (FP);
+    psFree (TP);
+    psFree (sky);
+
+    psFree (V00);
+    psFree (V10);
+    psFree (V01);
+
+    return true;
+}
+
+static void pswarpMapFree (pswarpMap *map) {
+  return;
+}
+
+pswarpMap *pswarpMapAlloc () {
+
+  pswarpMap *map = (pswarpMap *) psAlloc (sizeof(pswarpMap));
+  psMemSetDeallocator(map, (psFreeFunc) pswarpMapFree);
+
+  return map;
+}
+
+static void pswarpMapGridFree (pswarpMapGrid *grid) {
+
+    if (grid == NULL) return;
+    if (grid->maps == NULL) return;
+
+    for (int i = 0; i < grid->nXpts; i++) {
+	for (int j = 0; j < grid->nYpts; j++) {
+	    psFree (grid->maps[i][j]);
+	}
+	psFree (grid->maps[i]);
+    }
+    psFree (grid->maps);
+    return;
+}
+
+pswarpMapGrid *pswarpMapGridAlloc (int nXpts, int nYpts) {
+
+  pswarpMapGrid *grid = (pswarpMapGrid *) psAlloc (sizeof(pswarpMapGrid));
+  psMemSetDeallocator(grid, (psFreeFunc) pswarpMapGridFree);
+
+  grid->maps = psAlloc (nXpts*sizeof(void **));
+  for (int i = 0; i < nXpts; i++) {
+      grid->maps[i] = psAlloc (nYpts*sizeof(void *));
+      for (int j = 0; j < nYpts; j++) {
+	  grid->maps[i][j] = pswarpMapAlloc();
+      }
+  }
+  grid->nXpts = nXpts;
+  grid->nYpts = nYpts;
+
+  grid->nXpix = 0;
+  grid->nYpix = 0;
+  
+  return grid;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpMatchRange.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpMatchRange.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpMatchRange.c	(revision 22157)
@@ -0,0 +1,119 @@
+# include "pswarp.h"
+
+# define TEST_MINMAX \
+    psPlaneTransformApply(destFP, chipDest->toFPA, destPix); \
+    psPlaneTransformApply (destTP, fpaDest->toTPA, destFP); \
+    psDeproject (sky, destTP, fpaDest->toSky); \
+    psProject (srcTP, sky, fpaSrc->toSky); \
+    psPlaneTransformApply (srcFP, fpaSrc->fromTPA, srcTP); \
+    psPlaneTransformApply (srcPix, chipSrc->fromFPA, srcFP); \
+    *minX = PS_MIN (*minX, srcPix->x); \
+    *minY = PS_MIN (*minY, srcPix->y); \
+    *maxX = PS_MAX (*maxX, srcPix->x); \
+    *maxY = PS_MAX (*maxY, srcPix->y);
+
+// we are warping from src to dest.  find the max overlapping pixels in the INPUT (src)
+// coordinate frame.  NOTE: these are in the parent pixel frame since the astrometric
+// transformation refers to the parent frame
+bool pswarpMatchRange (int *minX, int *minY, int *maxX, int *maxY, pmReadout *dest, pmReadout *src) {
+
+    // transform input corners and edge centers to output coords
+    // find max overlapping region of output image
+    
+    pmCell *cell = NULL;
+
+    cell = src->parent;
+    pmChip *chipSrc = cell->parent;
+    pmFPA *fpaSrc = chipSrc->parent;
+
+    cell = dest->parent;
+    pmChip *chipDest = cell->parent;
+    pmFPA *fpaDest = chipDest->parent;
+
+    *minX = src->image->numCols + src->image->col0 - 1;
+    *minY = src->image->numRows + src->image->row0 - 1;
+    *maxX = src->image->col0;
+    *maxY = src->image->row0;
+
+    // XXX save these as static for speed?
+    psPlane *srcPix = psPlaneAlloc();
+    psPlane *srcFP  = psPlaneAlloc();
+    psPlane *srcTP  = psPlaneAlloc();
+
+    psPlane *destPix = psPlaneAlloc();
+    psPlane *destFP  = psPlaneAlloc();
+    psPlane *destTP  = psPlaneAlloc();
+
+    psSphere *sky = psSphereAlloc();
+
+    destPix->x = dest->image->col0;
+    destPix->y = dest->image->row0;
+    TEST_MINMAX;
+    
+    destPix->x = dest->image->col0 + dest->image->numCols;
+    destPix->y = dest->image->row0;
+    TEST_MINMAX;
+    
+    destPix->x = dest->image->col0 + dest->image->numCols;
+    destPix->y = dest->image->row0 + dest->image->numRows;
+    TEST_MINMAX;
+    
+    destPix->x = dest->image->col0;
+    destPix->y = dest->image->row0 + dest->image->numRows;
+    TEST_MINMAX;
+    
+    destPix->x = dest->image->col0 + 0.5*dest->image->numCols;
+    destPix->y = dest->image->row0;
+    TEST_MINMAX;
+    
+    destPix->x = dest->image->col0;
+    destPix->y = dest->image->row0 + 0.5*dest->image->numRows;
+    TEST_MINMAX;
+    
+    destPix->x = dest->image->col0 + 0.5*dest->image->numCols;
+    destPix->y = dest->image->row0;
+    TEST_MINMAX;
+
+    destPix->x = dest->image->col0;
+    destPix->y = dest->image->row0 + 0.5*dest->image->numRows;
+    TEST_MINMAX;
+
+    *minX = PS_MAX (*minX, src->image->col0);
+    *minY = PS_MAX (*minY, src->image->row0);
+    *maxX = PS_MIN (*maxX, src->image->numCols + src->image->col0);
+    *maxY = PS_MIN (*maxY, src->image->numRows + src->image->row0);
+
+    // demo forward and backward transformation
+# if (0)
+    srcPix->x = *minX;
+    srcPix->y = *minY;
+    psPlaneTransformApply(srcFP, chipSrc->toFPA, srcPix); 
+    psPlaneTransformApply (srcTP, fpaSrc->toTPA, srcFP); 
+    psDeproject (sky, srcTP, fpaSrc->toSky); 
+    psProject (destTP, sky, fpaDest->toSky); 
+    psPlaneTransformApply (destFP, fpaDest->fromTPA, destTP); 
+    psPlaneTransformApply (destPix, chipDest->fromFPA, destFP); 
+    fprintf (stderr, "%f,%f -> %f,%f ", srcPix->x, srcPix->y, destPix->x, destPix->y);
+
+    psPlaneTransformApply(destFP, chipDest->toFPA, destPix); 
+    psPlaneTransformApply (destTP, fpaDest->toTPA, destFP); 
+    psDeproject (sky, destTP, fpaDest->toSky); 
+    psProject (srcTP, sky, fpaSrc->toSky); 
+    psPlaneTransformApply (srcFP, fpaSrc->fromTPA, srcTP); 
+    psPlaneTransformApply (srcPix, chipSrc->fromFPA, srcFP); 
+    fprintf (stderr, "-> %f,%f ", srcPix->x, srcPix->y);
+# endif
+
+    psFree (srcPix);
+    psFree (srcFP);
+    psFree (srcTP);
+
+    psFree (destPix);
+    psFree (destFP);
+    psFree (destTP);
+
+    psFree (sky);
+
+    return true;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpParseCamera.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpParseCamera.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpParseCamera.c	(revision 22157)
@@ -0,0 +1,148 @@
+# include "pswarp.h"
+
+bool pswarpParseCamera (pmConfig *config) {
+
+    bool status;
+    bool mdok;                          // Status of MD lookup
+    pmFPAfile *skycell = NULL;
+    pmConfig *skyConfig = NULL;
+
+    // the input image(s) are required arguments; they define the camera
+    status = false;
+    pmFPAfile *input = pmFPAfileDefineFromArgs(&status, config, "PSWARP.INPUT", "INPUT");
+    if (!input || !status) {
+        psError(PSWARP_ERR_CONFIG, false, "Failed to build FPA from PSWARP.INPUT");
+        return false;
+    }
+
+    // the input image(s) are required arguments; they define the camera
+    status = false;
+    pmFPAfile *astrom = pmFPAfileDefineFromArgs(&status, config, "PSWARP.ASTROM", "ASTROM");
+    if (!status) {
+        psError (PS_ERR_UNKNOWN, false, "failed to load find definition");
+        return NULL;
+    }
+    if (astrom) {
+        psLogMsg ("pswarp", 3, "using supplied astrometry\n");
+    } else {
+        psLogMsg ("pswarp", 3, "using header astrometry\n");
+    }
+
+    // the mask is not required - but must conform to input camera
+    pmFPAfile *inMask = pmFPAfileBindFromArgs(&status, input, config, "PSWARP.MASK", "MASK");
+    if (!status) {
+        psError (PS_ERR_UNKNOWN, false, "failed to load find definition");
+        return NULL;
+    }
+    if (!inMask) {
+        psLogMsg ("pswarp", 3, "no mask supplied\n");
+    }
+    
+    // loading the mask here should have invoked pmConfigMaskReadHeader()
+    if (!pswarpSetMaskBits (config)) {
+        psError(PS_ERR_IO, false, "failed to set mask bits");
+        return NULL;
+    }
+
+    pmFPAfile *inWeight = pmFPAfileBindFromArgs(&status, input, config, "PSWARP.WEIGHT", "WEIGHT");
+    if (!status) {
+        psError (PS_ERR_UNKNOWN, false, "failed to load find definition");
+        return NULL;
+    }
+    if (!inWeight) {
+        psLogMsg ("pswarp", 3, "no weight supplied\n");
+    }
+
+    // the input skycell is a required argument: it defines the output image
+    // XXX we may need a different skycell structure here
+    status = pswarpDefineSkycell(&skycell, &skyConfig, config, "PSWARP.SKYCELL", "SKYCELL");
+    if (!status) {
+        psError(PSWARP_ERR_CONFIG, false, "Failed to build FPA from PSWARP.SKYCELL");
+        return false;
+    }
+    psFree(skyConfig);
+
+    // The output skycell
+    pmFPAfile *output = pmFPAfileDefineSkycell(config, NULL, "PSWARP.OUTPUT");
+    if (!output) {
+        psError(PSWARP_ERR_CONFIG, false, "Failed to build FPA from PSWARP.OUTPUT");
+        return false;
+    }
+    pmFPAfile *outMask = pmFPAfileDefineSkycell(config, output->fpa, "PSWARP.OUTPUT.MASK");
+    if (!outMask) {
+        psError(PSWARP_ERR_CONFIG, false, "Failed to build FPA from PSWARP.OUTPUT.MASK");
+        return false;
+    }
+
+    output->save = true;
+    outMask->save = true;
+
+    if (inWeight) {
+        pmFPAfile *outWeight = pmFPAfileDefineSkycell(config, output->fpa, "PSWARP.OUTPUT.WEIGHT");
+        if (!outWeight) {
+            psError(PSWARP_ERR_CONFIG, false, "Failed to build FPA from PSWARP.OUTPUT.WEIGHT");
+            return false;
+        }
+        outWeight->save = true;
+    }
+
+    if (astrom) {
+        pmFPAfile *outSources = pmFPAfileDefineSkycell(config, output->fpa, "PSWARP.OUTPUT.SOURCES");
+        if (!outSources) {
+            psError(PSWARP_ERR_CONFIG, false, "Failed to build FPA from PSWARP.OUTPUT.SOURCES");
+            return false;
+        }
+        outSources->save = true;
+    }
+
+    if (psMetadataLookupBool(&mdok, config->arguments, "PSF")) {
+        // This file, PSPHOT.INPUT, is just used as a carrier; output files (eg, PSPHOT.RESID) are defined by
+        // psphotDefineFiles
+        pmFPAfile *psphotInput = pmFPAfileDefineSkycell(config, output->fpa, "PSPHOT.INPUT");
+        if (!psphotInput) {
+            psError(PS_ERR_IO, false, _("Unable to generate output file from PSPHOT.INPUT"));
+            return false;
+        }
+
+        // Ensure psphot defines an output file for the PSF.
+        psMetadata *psphotRecipe = psMetadataLookupPtr(NULL, config->recipes, PSPHOT_RECIPE); // Recipe
+        if (!psphotRecipe) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find %s recipe.", PSPHOT_RECIPE);
+            return false;
+        }
+        psMetadataAddBool(psphotRecipe, PS_LIST_TAIL, "SAVE.PSF", PS_META_REPLACE, "Save PSF?", true);
+
+        // Define associated psphot input/output files
+        if (!psphotDefineFiles(config, psphotInput)) {
+            psError(PSPHOT_ERR_CONFIG, false,
+                    "Unable to define the additional input/output files for psphot");
+            return false;
+        }
+
+        // Turn off writing the sources we find as part of deriving the PSF --- they might clobber the ones we
+        // get from transforming the input coordinates.
+        pmFPAfile *psphotSources = psMetadataLookupPtr(NULL, config->files, "PSPHOT.OUTPUT");
+        assert(psphotSources);
+        psphotSources->save = false;
+    }
+
+    // Chip selection: turn on only the chips specified
+    char *chipLine = psMetadataLookupStr(&mdok, config->arguments, "CHIP_SELECTIONS");
+    if (mdok) {
+        psArray *chips = psStringSplitArray (chipLine, ",", false);
+        if (chips->n > 0) {
+            pmFPASelectChip (input->fpa, -1, true); // deselect all chips
+            for (int i = 0; i < chips->n; i++) {
+                int chipNum = atoi(chips->data[i]);
+                if (! pmFPASelectChip(input->fpa, chipNum, false)) {
+                    psError(PSWARP_ERR_CONFIG, true, "Chip number %d doesn't exist in camera.\n", chipNum);
+                    return false;
+                }
+            }
+        }
+        psFree (chips);
+    }
+
+    psTrace("pswarp", 1, "Done with pswarpParseCamera...\n");
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpPixelFraction.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpPixelFraction.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpPixelFraction.c	(revision 22157)
@@ -0,0 +1,90 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "pswarp.h"
+
+bool pswarpPixelFraction(const pmReadout *readout, psMetadata *stats, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_IMAGE_NON_NULL(readout->image, false);
+    PS_ASSERT_IMAGE_TYPE(readout->image, PS_TYPE_F32, false);
+    if (!readout->mask) {
+        // Can't do anything
+        return true;
+    }
+    PS_ASSERT_IMAGE_NON_NULL(readout->mask, false);
+    PS_ASSERT_IMAGES_SIZE_EQUAL(readout->mask, readout->image, false);
+    PS_ASSERT_IMAGE_TYPE(readout->mask, PS_TYPE_MASK, false);
+
+    if (stats) {
+        PS_ASSERT_METADATA_NON_NULL(stats, false);
+    }
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_METADATA_NON_NULL(config->arguments, false);
+
+    bool status; 
+
+    float minFrac = psMetadataLookupF32(NULL, config->arguments, "ACCEPT.FRAC"); // Minimum fraction
+
+    // load the recipe
+    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSWARP_RECIPE);
+    if (!recipe) {
+        psError(PSPHOT_ERR_CONFIG, false, "missing recipe %s", PSWARP_RECIPE);
+        return false;
+    }
+
+    // output mask bits
+    psMaskType maskValue = psMetadataLookupU8(&status, recipe, "MASK.OUTPUT"); 
+    psAssert (status, "MASK.OUTPUT was not defined");
+
+    psImage *image = readout->image;    // Image of interest
+    psImage *mask = readout->mask;      // Mask image
+
+    int numCols = image->numCols, numRows = image->numRows; // Size of image
+    long numPix = numCols * numRows;
+
+    // Range of valid pixels
+    int xMin = INT_MAX, xMax = -INT_MAX, yMin = INT_MAX, yMax = -INT_MAX;
+
+    long numBad = 0;                     // Number of bad pixels
+    for (int y = 0; y < numRows; y++) {
+        for (int x = 0; x < numCols; x++) {
+            if (mask->data.PS_TYPE_MASK_DATA[y][x] & maskValue) {
+                numBad++;
+            } else {
+                if (y > yMax) {
+                    yMax = y;
+                }
+                if (y < yMin) {
+                    yMin = y;
+                }
+                if (x > xMax) {
+                    xMax = x;
+                }
+                if (x < xMin) {
+                    xMin = x;
+                }
+            }
+        }
+    }
+
+    float goodFrac = (numPix - numBad) / (float)numPix; // Fraction of good pixels
+    bool accept = (goodFrac >= minFrac ? true : false); // Accept this readout?
+
+    if (stats) {
+        psMetadataAddBool(stats, PS_LIST_HEAD, "ACCEPT", 0, "Accept this readout?", accept);
+
+        psMetadataAddS32(stats, PS_LIST_TAIL, "RANGE.XMIN", 0, "Minimum valid x value", xMin);
+        psMetadataAddS32(stats, PS_LIST_TAIL, "RANGE.XMAX", 0, "Maximum valid x value", xMax);
+        psMetadataAddS32(stats, PS_LIST_TAIL, "RANGE.YMIN", 0, "Minimum valid y value", yMin);
+        psMetadataAddS32(stats, PS_LIST_TAIL, "RANGE.YMAX", 0, "Maximum valid y value", yMax);
+    }
+
+    return accept;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpSetMaskBits.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpSetMaskBits.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpSetMaskBits.c	(revision 22157)
@@ -0,0 +1,83 @@
+# include "pswarp.h"
+
+// This function is called by the stand-alone pswarp program to set the mask values in the
+// config file.  It sets the named mask values MASK.PSPHOT and MARK.PSPHOT in the PSPHOT
+// recipe.  Functions or programs which call psphotReadout as a library function must set these
+// named mask values in the PSPHOT recipe on their own.  This function should only be called
+// after the first header of the input mask image has been loaded and the named mask bits
+// updated in the config metadata.
+
+bool pswarpSetMaskBits (pmConfig *config) {
+
+    psMaskType maskIn = 0x00;			// mask for the input image
+    psMaskType markIn = 0x00;			// mark for the input image
+    psMaskType maskOut = 0x00;			// mask for the output image
+    psMaskType markOut = 0x00;			// mark for the output image
+
+    // this function sets the required single-image mask bits
+    if (!pmConfigMaskSetBits (&maskIn, &markIn, config)) {
+	psError (PS_ERR_UNKNOWN, true, "Unable to define the mask bit values");
+	return false;
+    }
+
+    // mask for non-linear flat regions (default to DETECTOR if not defined)
+    psMaskType badMask = pmConfigMaskGet("BAD.WARP", config); 
+    if (!badMask) {
+	badMask = 0x01;
+	pmConfigMaskSet (config, "BAD.WARP", badMask);
+    }
+    maskOut |= badMask;
+
+    // mask for non-linear flat regions (default to DETECTOR if not defined)
+    psMaskType poorMask = pmConfigMaskGet("POOR.WARP", config); 
+    if (!poorMask) {
+	poorMask = 0x02;
+	pmConfigMaskSet (config, "POOR.WARP", poorMask);
+    }
+    maskOut |= poorMask;
+
+    // search for an unset bit to use for MARK:
+    markOut = 0x80;
+
+    int nBits = sizeof(psMaskType) * 8;
+    for (int i = 0; !markOut && (i < nBits); i++) {
+	if (maskOut & markOut) {
+	    markOut >>= 1;
+	} else {
+	    markOut = markOut;
+	}
+    }
+
+    if (!markOut) {
+	psError (PS_ERR_UNKNOWN, true, "Unable to define the MARK bit mask: all bits taken!");
+	return false;
+    }
+
+    // update the pswarp recipe
+    psMetadata *warpRecipe = psMetadataLookupPtr (NULL, config->recipes, PSWARP_RECIPE);
+    if (!warpRecipe) {
+        psError(PSPHOT_ERR_CONFIG, false, "missing recipe %s", PSWARP_RECIPE);
+        return false;
+    }
+
+    // set maskOut and markOut in the psphot recipe
+    // NOTE: psphot works on the output images, not input images, so set the MARK and MASK correctly here
+    psMetadataAddU8 (warpRecipe, PS_LIST_TAIL, "MASK.INPUT",  PS_META_REPLACE, "user-defined mask", maskIn);
+    psMetadataAddU8 (warpRecipe, PS_LIST_TAIL, "MARK.INPUT",  PS_META_REPLACE, "user-defined mask", markIn);
+    psMetadataAddU8 (warpRecipe, PS_LIST_TAIL, "MASK.OUTPUT", PS_META_REPLACE, "user-defined mask", maskOut);
+    psMetadataAddU8 (warpRecipe, PS_LIST_TAIL, "MARK.OUTPUT", PS_META_REPLACE, "user-defined mask", markOut);
+
+    // update the psphot recipe
+    psMetadata *psphotRecipe = psMetadataLookupPtr (NULL, config->recipes, PSPHOT_RECIPE);
+    if (!psphotRecipe) {
+        psError(PSPHOT_ERR_CONFIG, false, "missing recipe %s", PSPHOT_RECIPE);
+        return false;
+    }
+
+    // set maskOut and markOut in the psphot recipe
+    // NOTE: psphot works on the output images, not input images, so set the MARK and MASK correctly here
+    psMetadataAddU8 (psphotRecipe, PS_LIST_TAIL, "MARK.PSPHOT", PS_META_REPLACE, "user-defined mask", markOut);
+    psMetadataAddU8 (psphotRecipe, PS_LIST_TAIL, "MASK.PSPHOT", PS_META_REPLACE, "user-defined mask", maskOut);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpTransformReadout.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpTransformReadout.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpTransformReadout.c	(revision 22157)
@@ -0,0 +1,79 @@
+# include "pswarp.h"
+
+bool pswarpTransformReadout (pmReadout *output, pmReadout *input, pmConfig *config) {
+
+    // XXX this implementation currently ignores the use of the region
+    psImage *region = NULL;
+    pmCell *cell = NULL;
+
+    // select the current recipe
+    // psMetadata *recipe  = psMetadataLookupPtr (NULL, config->recipes, PSWARP_RECIPE);
+
+    int outNx = output->image->numCols;
+    int outNy = output->image->numRows;
+
+    psPlane *inPix = psPlaneAlloc();    // Coordinates on the input detector
+    psPlane *outPix = psPlaneAlloc();   // Coordinates on the output detector
+
+    psPlane *FP = psPlaneAlloc();       // Coordinates on the focal plane
+    psPlane *TP = psPlaneAlloc();       // Coordinates on the tangent plane
+    psSphere *sky = psSphereAlloc();    // Coordinates on the sky
+
+    cell = input->parent;
+    pmChip *chipInput = cell->parent;
+    pmFPA *fpaInput = chipInput->parent;
+
+    cell = output->parent;
+    pmChip *chipOutput = cell->parent;
+    pmFPA *fpaOutput = chipOutput->parent;
+
+    psF32 **outData = output->image->data.F32;
+    psImage *inImage = input->image;
+
+    psImageInterpolateOptions *interp = psImageInterpolateOptionsAlloc(PS_INTERPOLATE_BILINEAR, inImage,
+                                                                       NULL, NULL, 0, NAN, NAN, 0, 0, 0.0);
+
+    // Iterate over the output image pixels
+    for (int y = 0; y < outNy; y++) {
+        for (int x = 0; x < outNx; x++) {
+            // Only transform those pixels requested
+            if (region && region->data.U8[y][x]) continue;
+
+            // XXX double check this 1/2 pixel offset
+            outPix->x = (double)x + 0.5;
+            outPix->y = (double)y + 0.5;
+
+            psPlaneTransformApply(FP, chipOutput->toFPA, outPix);
+            psPlaneTransformApply (TP, fpaOutput->toTPA, FP);
+            psDeproject (sky, TP, fpaOutput->toSky);
+
+            psProject (TP, sky, fpaInput->toSky);
+            psPlaneTransformApply (FP, fpaInput->fromTPA, TP);
+            psPlaneTransformApply (inPix, chipInput->fromFPA, FP);
+
+            // XXX get interpolation method from the recipe
+            double value;
+            if (!psImageInterpolate(&value, NULL, NULL, inPix->x, inPix->y, interp)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to interpolate image.");
+                psFree(interp);
+                psFree(inPix);
+                psFree(outPix);
+                psFree(FP);
+                psFree(TP);
+                psFree(sky);
+                return false;
+            }
+
+            outData[y][x] = value;
+            // modify zero and scale?
+        }
+    }
+    psFree(interp);
+    psFree(inPix);
+    psFree(outPix);
+    psFree(FP);
+    psFree(TP);
+    psFree(sky);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpTransformReadout_Opt.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpTransformReadout_Opt.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpTransformReadout_Opt.c	(revision 22157)
@@ -0,0 +1,274 @@
+# include "pswarp.h"
+
+#define SOURCE_ARRAY_BUFFER 100         // Size to grow the array of sources at a time
+
+// NOTE: in this function, the coordinates are transformed from the OUTPUT to the INPUT
+bool pswarpTransformReadout_Opt(pmReadout *output, pmReadout *input, pmConfig *config)
+{
+    // XXX this implementation currently ignores the use of the region
+    psImage *region = NULL;
+
+    psTimerStart ("warp");
+
+    // Get warp parameters
+    bool mdok;
+    int nGridX = psMetadataLookupS32(NULL, config->arguments, "GRID.NX");
+    int nGridY = psMetadataLookupS32(NULL, config->arguments, "GRID.NY");
+    psImageInterpolateMode interpolationMode = psMetadataLookupS32(NULL, config->arguments,
+                                                                   "INTERPOLATION.MODE");
+    // load the recipe
+    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSWARP_RECIPE);
+    if (!recipe) {
+        psError(PSPHOT_ERR_CONFIG, false, "missing recipe %s", PSWARP_RECIPE);
+        return false;
+    }
+
+    // output mask bits
+    psMaskType maskIn   = psMetadataLookupU8(&mdok, recipe, "MASK.INPUT"); 
+    psMaskType maskPoor = pmConfigMaskGet("POOR.WARP", config); 
+    psMaskType maskBad  = pmConfigMaskGet("BAD.WARP", config); 
+    psAssert (mdok, "MASK.INPUT was not defined");
+
+    float poorFrac = psMetadataLookupF32(NULL, config->arguments, "POOR.FRAC"); // Flux fraction for "poor"
+
+    // we need to apply the offset to convert parent coordinates to child coordinates for
+    // psImagePixelInterpolate below
+    int inCol0 = input->image->col0;
+    int inRow0 = input->image->row0;
+    int outCol0 = output->image->col0;
+    int outRow0 = output->image->row0;
+
+    // we might want to do the rectangular regions outside of the selection independently
+    // psImageInit (output->image, NAN);
+
+    // find the output pixel range
+    int minX, minY, maxX, maxY;
+    pswarpMatchRange (&minX, &minY, &maxX, &maxY, input, output);
+
+    // pswarpMapGridFromImage builds a set of locally-linear maps which convert the
+    // output coordinates to input coordinates
+    pswarpMapGrid *grid = pswarpMapGridFromImage (input, output, nGridX, nGridY);
+
+    // XXX need to modify the grid based on this result and force the maxError < XXX
+    double maxError = pswarpMapGridMaxError (grid);
+    psLogMsg ("pswarp", 3, "maximum error using this grid sampling: %f\n", maxError);
+
+    int gridX, gridY, nextGridX, nextGridY;
+    pswarpMapGridSetGrid (grid, minX, minY, &gridX, &gridY);
+    nextGridY = pswarpMapGridNextGrid_Y (grid, gridY);
+    pswarpMap *map = grid->maps[gridX][gridY];
+
+    assert ((int)(minX - outCol0) >= 0);
+    assert ((int)(maxX - outCol0) <= output->image->numCols);
+    assert ((int)(minY - outRow0) >= 0);
+    assert ((int)(maxY - outRow0) <= output->image->numRows);
+
+    int gridXo = gridX;
+    int nextGridXo = pswarpMapGridNextGrid_X (grid, gridX);
+
+    psImage *inImage = input->image;    // Input image
+    psImage *inVar   = input->weight;   // Input weight map
+    psImage *inMask  = input->mask;     // Input mask
+
+    // Interpolation options
+    psImageInterpolateOptions *interp = psImageInterpolateOptionsAlloc(interpolationMode, inImage, 
+                                                                       inVar, inMask, maskIn, NAN, NAN,
+                                                                       maskBad, maskPoor, poorFrac);
+
+    psPlane *inPix = psPlaneAlloc();    // Coordinates on the input detector
+    psF32 **outImageData = output->image->data.F32; // Output image pixels
+    psF32 **outVarData = NULL;          // Output variance pixels
+    psMaskType **outMaskData = NULL;    // Output mask pixels
+    if (inVar) {
+        if (!output->weight) {
+            output->weight = psImageAlloc(output->image->numCols, output->image->numRows, PS_TYPE_F32);
+            psImageInit(output->weight, NAN);
+        }
+        outVarData = output->weight->data.F32;
+    }
+    if (inMask || maskPoor || maskBad) {
+        if (!output->mask) {
+            output->mask = psImageAlloc(output->image->numCols, output->image->numRows, PS_TYPE_MASK);
+            psImageInit(output->mask, maskBad);
+        }
+        outMaskData = output->mask->data.PS_TYPE_MASK_DATA;
+    }
+
+    // Iterate over the output image pixels (parent frame)
+    long goodPixels = 0;                // Number of input pixels landing on the output image
+    for (int y = minY; y < maxY; y++) {
+        if (y >= nextGridY) {
+            gridY ++;
+            nextGridY = pswarpMapGridNextGrid_Y (grid, gridY);
+            map = grid->maps[gridX][gridY];
+        }
+
+        gridX = gridXo;
+        nextGridX = nextGridXo;
+        map = grid->maps[gridX][gridY];
+        int yOut = y - outRow0;         // Position on image
+        for (int x = minX; x < maxX; x++) {
+            if (x >= nextGridX) {
+                gridX ++;
+                nextGridX = pswarpMapGridNextGrid_X (grid, gridX);
+                map = grid->maps[gridX][gridY];
+            }
+
+            // Only transform those pixels requested
+            if (region && region->data.U8[y][x]) continue;
+
+            // pswarpMapApply converts the output coordinate (x,y) to the input coordinate.
+            // both are in the parent frames of the input and output images.
+            pswarpMapApply (&inPix->x, &inPix->y, map, x + 0.5, y + 0.5);
+
+            if (inPix->x - inCol0 < 0) continue;
+            if (inPix->x - inCol0 >= inImage->numCols) continue;
+            if (inPix->y - inRow0 < 0) continue;
+            if (inPix->y - inRow0 >= inImage->numRows) continue;
+
+            goodPixels++;
+
+            // XXX include mask
+            // XXX apply scale and offset?
+            // psImagePixelInterpolate determines the value at pixel coordinate (x,y) in child coordinates
+            double imageValue, varValue;
+            psMaskType maskValue;
+            if (!psImageInterpolate(&imageValue, &varValue, &maskValue,
+                                    inPix->x - inCol0, inPix->y - inRow0, interp)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to interpolate image.");
+                psFree(interp);
+                psFree(inPix);
+                psFree(grid);
+                return false;
+            }
+            int xOut = x - outCol0;     // Position on image
+            outImageData[yOut][xOut] = imageValue;
+            if (inVar) {
+                outVarData[yOut][xOut] = varValue;
+            }
+            if (outMaskData) {
+                outMaskData[yOut][xOut] = maskValue;
+            }
+        }
+    }
+    psFree(interp);
+    psFree(inPix);
+    psFree(grid);
+
+    // Store the variance factor and number of good pixels
+    if (goodPixels > 0) {
+        float varFactor = psImageInterpolateVarianceFactor(inImage->numCols / 2.0 + inCol0,
+                                                           inImage->numRows / 2.0 + inRow0,
+                                                           interp); // Variance factor: large --> small scale
+        psMetadataItem *vfItem = psMetadataLookup(output->analysis, PSWARP_ANALYSIS_VARFACTOR);
+        if (vfItem) {
+            psMetadataItem *goodpixItem = psMetadataLookup(output->analysis, PSWARP_ANALYSIS_GOODPIX);
+            psAssert(goodpixItem, "It should be where we left it!");
+            psAssert(vfItem->type == PS_TYPE_F32 && goodpixItem->type == PS_TYPE_S64,
+                     "Should be the type we said.");
+
+            vfItem->data.F32 += varFactor * goodPixels;
+            goodpixItem->data.S64 += goodPixels;
+        } else {
+            psMetadataAddF32(output->analysis, PS_LIST_TAIL, PSWARP_ANALYSIS_VARFACTOR, 0,
+                             "Variance factor weighted by the good pixels", varFactor * goodPixels);
+            psMetadataAddS64(output->analysis, PS_LIST_TAIL, PSWARP_ANALYSIS_GOODPIX, 0,
+                             "Number of good pixels", goodPixels);
+        }
+    }
+
+    // Transform sources
+    psArray *inSources = psMetadataLookupPtr(&mdok, input->analysis, "PSPHOT.SOURCES"); // Sources in source
+    if (goodPixels > 0 && mdok && inSources) {
+        pswarpMapGrid *sourceGrid = pswarpMapGridFromImage(output, input, nGridX, nGridY); // Grid for sources
+
+        psArray *outSources = psMemIncrRefCounter(psMetadataLookupPtr(&mdok, output->analysis,
+                                                                      "PSPHOT.SOURCES")); // Target sources
+        if (!outSources) {
+            outSources = psArrayAllocEmpty(SOURCE_ARRAY_BUFFER);
+            psMetadataAddPtr(output->analysis, PS_LIST_TAIL, "PSPHOT.SOURCES", PS_DATA_ARRAY,
+                             "Warped sources", outSources);
+        }
+
+        for (int i = 0; i < inSources->n; i++) {
+            pmSource *source = inSources->data[i]; // Source of interest
+            pmModel *model = source->modelPSF; // Model for this source
+            float xIn, yIn;             // Coordinates of source
+            xIn = model->params->data.F32[PM_PAR_XPOS] - inCol0;
+            yIn = model->params->data.F32[PM_PAR_YPOS] - inRow0;
+            int xGrid, yGrid;           // Grid coordinates for local map
+            if (!pswarpMapGridSetGrid(sourceGrid, xIn + 0.5, yIn + 0.5, &xGrid, &yGrid)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to get grid coordinates for source at %f,%f\n",
+                        xIn, yIn);
+                psFree(outSources);
+                psFree(sourceGrid);
+                return false;
+            }
+            if (xGrid < 0 || xGrid >= sourceGrid->nXpts || yGrid < 0 || yGrid >= sourceGrid->nYpts) {
+                // It's not even on the grid
+                continue;
+            }
+
+            pswarpMap *map = sourceGrid->maps[xGrid][yGrid]; // Locally linear transformation
+            double xOut, yOut;          // Output coordinates
+            if (!pswarpMapApply(&xOut, &yOut, map, xIn + 0.5, yIn + 0.5)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to transform coordinates for source at %f,%f\n",
+                        xIn, yIn);
+                psFree(outSources);
+                psFree(sourceGrid);
+                return false;
+            }
+            xOut += outCol0 - 0.5;
+            yOut += outRow0 - 0.5;
+            if (xOut < minX || xOut > maxX || yOut < minY || yOut > maxY) {
+                // It's not in the output image
+                continue;
+            }
+
+            // Generate the new source in the output frame
+            pmSource *new = pmSourceAlloc(); // New source
+            new->peak = pmPeakAlloc(xOut, yOut, source->peak->flux, PM_PEAK_LONE);
+            new->peak->flux = source->peak->flux;
+            new->type = PM_SOURCE_TYPE_STAR;
+            new->psfMag = source->psfMag;
+            new->errMag = source->errMag;
+            new->sky = source->sky;
+            new->skyErr = source->skyErr;
+            new->pixWeight = source->pixWeight;
+            new->modelPSF = pmModelAlloc(source->modelPSF->type);
+
+#if 0
+            // XXX Note that this will not set the correct axes
+            pmPSF_AxesToModel(new->modelPSF->params->data.F32,
+                              pmPSF_ModelToAxes(source->modelPSF->params->data.F32, 20.0));
+#endif
+
+            // Propagate the position erorrs
+            float dxIn, dyIn;           // Errors in input coordinates
+            dxIn = model->dparams->data.F32[PM_PAR_XPOS];
+            dyIn = model->dparams->data.F32[PM_PAR_YPOS];
+
+            float dxOut, dyOut;         // Errors in output coordinates
+            dxOut = sqrt(PS_SQR(map->Xx * dxIn) + PS_SQR(map->Xy * dyIn));
+            dyOut = sqrt(PS_SQR(map->Yx * dxIn) + PS_SQR(map->Yy * dyIn));
+
+            new->modelPSF->params->data.F32[PM_PAR_XPOS] = xOut;
+            new->modelPSF->params->data.F32[PM_PAR_YPOS] = yOut;
+            new->modelPSF->dparams->data.F32[PM_PAR_XPOS] = dxOut;
+            new->modelPSF->dparams->data.F32[PM_PAR_YPOS] = dyOut;
+
+            psArrayAdd(outSources, SOURCE_ARRAY_BUFFER, new);
+            psFree(new);                // Drop reference
+        }
+        psFree(sourceGrid);
+        psFree(outSources);             // Drop reference
+    }
+
+    output->data_exists = true;
+    output->parent->data_exists = true;
+    output->parent->parent->data_exists = true;
+
+    psLogMsg ("pswarp", 3, "warping analysis: %f sec\n", psTimerMark ("warp"));
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpVersion.c
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpVersion.c	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/src/pswarpVersion.c	(revision 22157)
@@ -0,0 +1,27 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "pswarp.h"
+
+static const char *cvsTag = "$Name: not supported by cvs2svn $";// CVS tag name
+
+psString pswarpVersion(void)
+{
+    psString version = NULL;            // Version, to return
+    psStringAppend(&version, "%s-%s",PACKAGE_NAME,PACKAGE_VERSION);
+    return version;
+}
+
+psString pswarpVersionLong(void)
+{
+    psString version = pswarpVersion(); // Version, to return
+    psString tag = psStringStripCVS(cvsTag, "Name"); // CVS tag
+    psStringAppend(&version, " (cvs tag %s) %s, %s", tag, __DATE__, __TIME__);
+    psFree(tag);
+    return version;
+}
+
Index: /branches/eam_branches/eam_branch_20080706/pswarp/test/.cvsignore
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/test/.cvsignore	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/test/.cvsignore	(revision 22157)
@@ -0,0 +1,1 @@
+*
Index: /branches/eam_branches/eam_branch_20080706/pswarp/test/simple.image.op
===================================================================
--- /branches/eam_branches/eam_branch_20080706/pswarp/test/simple.image.op	(revision 22157)
+++ /branches/eam_branches/eam_branch_20080706/pswarp/test/simple.image.op	(revision 22157)
@@ -0,0 +1,56 @@
+
+$NX = 2048
+$NY = 2048
+
+macro go
+  mkref
+  mksrc
+  exec pswarp -file src.fits out ref.fits
+  exec rm -f src.fits out.fits ref.fits
+end
+
+macro mkref
+  mcreate ref $NX $NY
+  keyword ref CTYPE1 -w "RA---TAN"
+  keyword ref CTYPE2 -w "DEC--TAN"
+  keyword ref CRVAL1 -wf 0.0
+  keyword ref CRVAL2 -wf 0.0
+  keyword ref CRPIX1 -wf {$NX/2}
+  keyword ref CRPIX2 -wf {$NY/2}
+
+  keyword ref CDELT1 -wf {1/3600}
+  keyword ref CDELT2 -wf {1/3600}
+
+  keyword ref PC001001 -wf 1.0
+  keyword ref PC001002 -wf 0.0
+  keyword ref PC002001 -wf 0.0
+  keyword ref PC002002 -wf 1.0
+
+  wd ref ref.fits -bitpix -32 -bzero 0 -bscale 1
+end
+
+macro mksrc
+  mcreate src $NX $NY
+  for i 0 $NX 64
+    for j 0 $NY 64
+      zap src $i $j 1 1 -v 1
+    end
+  end
+
+  keyword src CTYPE1 -w "RA---TAN"
+  keyword src CTYPE2 -w "DEC--TAN"
+  keyword src CRVAL1 -wf 0.0
+  keyword src CRVAL2 -wf 0.0
+  keyword src CRPIX1 -wf {$NX/2}
+  keyword src CRPIX2 -wf {$NY/2}
+
+  keyword src CDELT1 -wf {1/3600}
+  keyword src CDELT2 -wf {1/3600}
+
+  keyword src PC001001 -wf {dcos(20)}
+  keyword src PC001002 -wf {-1*dsin(20)}
+  keyword src PC002001 -wf {dsin(20)}
+  keyword src PC002002 -wf {dcos(20)}
+
+  wd src src.fits -bitpix -32 -bzero 0 -bscale 1
+end
