Index: /trunk/PS-IPP-PStamp/Build.PL
===================================================================
--- /trunk/PS-IPP-PStamp/Build.PL	(revision 23264)
+++ /trunk/PS-IPP-PStamp/Build.PL	(revision 23264)
@@ -0,0 +1,15 @@
+use Module::Build;
+# See perldoc Module::Build for details of how this works
+
+Module::Build->new(
+    module_name         => 'PStamp',
+    dist_version_from   => 'lib/PStamp.pm',
+    author              => 'Bill Sweeney',
+    license             => 'gpl',
+    create_makefile_pl  => 'passthrough',
+    requires            => {
+        'IPC::Cmd'              => '0.36',
+        'PS::IPP::Config'       => '1.01',
+        'PS::IPP::Metadata::List' => '0.01',
+    },
+)->create_build_script;
Index: /trunk/PS-IPP-PStamp/LICENSE
===================================================================
--- /trunk/PS-IPP-PStamp/LICENSE	(revision 23264)
+++ /trunk/PS-IPP-PStamp/LICENSE	(revision 23264)
@@ -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: /trunk/PS-IPP-PStamp/MANIFEST
===================================================================
--- /trunk/PS-IPP-PStamp/MANIFEST	(revision 23264)
+++ /trunk/PS-IPP-PStamp/MANIFEST	(revision 23264)
@@ -0,0 +1,6 @@
+Build.PL
+LICENSE
+MANIFEST
+lib/PStamp.pm
+lib/PStamp/Job.pm
+lib/PStamp/RequestFile.pm
Index: /trunk/PS-IPP-PStamp/lib/PStamp.pm
===================================================================
--- /trunk/PS-IPP-PStamp/lib/PStamp.pm	(revision 23264)
+++ /trunk/PS-IPP-PStamp/lib/PStamp.pm	(revision 23264)
@@ -0,0 +1,87 @@
+
+package PStamp;
+
+use strict;
+use warnings;
+
+use vars qw($VERSION);
+$VERSION = '1.0';
+
+=pod
+
+=head1 NAME
+
+PStamp - Perl Module of Postage Stamp Server functions
+
+=head1 SYNOPSIS
+
+    use PStamp;
+
+    # equivalent to:
+
+    use Pstamp::RequestFile;
+
+=head1 DESCRIPTION
+
+This is a convenience module so that that don't have to individualy load (C<use
+...;>) all of the common PStamp inteface modules.  Please see the POD of the
+individual modules for usage information. 
+
+=head1 USAGE
+
+=head2 Import Parameters
+
+This module accepts no arguments to it's C<import> method and exports no
+I<symbols>.
+
+=head2 Methods
+
+None.
+
+=head1 EXAMPLE PROGRAM
+
+=cut
+
+use PStamp::RequestFile qw( :standard );
+use PStamp::Job qw( :standard );
+
+=head1 CREDITS
+
+
+=head1 SUPPORT
+
+Please contact the author directly via e-mail.
+
+=head1 AUTHOR
+
+Bill Sweeney
+
+=head1 COPYRIGHT
+
+Copyright (C) 2008  Bill Sweeney.  All rights reserved.
+
+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.
+
+The full text of the license can be found in the LICENSE file included with
+this module, or in the L<perlgpl> Pod as supplied with Perl 5.8.1 and later.
+
+=head1 SEE ALSO
+
+L<PStamp::RequestFile>
+
+=cut
+
+1;
+
+__END__
Index: /trunk/PS-IPP-PStamp/lib/PStamp/Job.pm
===================================================================
--- /trunk/PS-IPP-PStamp/lib/PStamp/Job.pm	(revision 23264)
+++ /trunk/PS-IPP-PStamp/lib/PStamp/Job.pm	(revision 23264)
@@ -0,0 +1,576 @@
+###
+###     PStamp/Job.pm
+###     subroutines and constants related to Postage Stamp Jobs
+###
+
+package PStamp::Job;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.0';
+
+use base qw( Exporter );
+
+our @EXPORT_OK = qw( 
+                    locate_images
+                    resolve_project
+                    );
+our %EXPORT_TAGS = (standard => [@EXPORT_OK]);
+
+
+use IPC::Cmd 0.36 qw( can_run run );
+
+use PS::IPP::Metadata::List qw( parse_md_list );
+use PS::IPP::Config qw( :standard );
+
+### my @images = locate_images($image_db, $req_type, $img_type, $id, $component,
+###            $mjd_min, $mjd_max, $filter);
+
+sub locate_images {
+    my $ipprc    = shift;   # required
+    my $image_db = shift;   # required
+    my $req_type = shift;   # required
+    my $img_type = shift;   # required
+    my $id       = shift;   # required unless req_type eq bycoord
+    my $component= shift;   # class_id or skycell_id
+    my $x        = shift;
+    my $y        = shift;
+    my $mjd_min  = shift;
+    my $mjd_max  = shift;
+    my $filter   = shift;
+    my $verbose  = shift;
+
+    # we die in response to bad data in request files
+    die "Unknown req_type: $req_type" if ($req_type ne "byid") and ($req_type ne "byexp")
+                                        and ($req_type ne "bycoord") and ($req_type ne "bydiff");
+    if (($req_type eq "byid") and ($img_type eq "diff")) {
+        # lookups of all of the information for diff images requires a two level lookup to
+        # get the exposure information. Switching the req_type allows us to keep that code
+        # in one place
+        $req_type = "bydiff";
+    }
+    if ($req_type eq "bydiff") {
+        my $results = lookup_bydiff($ipprc, $image_db, $id, $img_type, $verbose);
+        if (!$results) {
+            return undef;
+        }
+        if (($img_type eq "warp") or ($img_type eq "diff")) {
+            # lookup_bydiff has done all of the work
+            return [$results];
+        } elsif (($img_type eq "raw") or ($img_type eq "chip")) {
+            $req_type = "byexp";
+            $id = $results->{exp_name};
+            return undef if !$id;
+            # fall through and lookup byexp
+#        } elsif ($img_type eq "warp") {
+#            $req_type = "byid";
+#            $id = $results->{warp_id};
+#            $component = $results->{skycell_id};
+#            return undef if !$id;
+#            # fall through and lookup by warp_id 
+        } elsif ($img_type eq "stack") {
+            $req_type = "byid";
+            $id = $results->{stack_id};
+            return undef if !$id;
+            # fall though and lookup by stack id
+        } else {
+            # shouldn't I check this elsewhere?
+            print STDERR "Error: $img_type is an unknown image type\n";
+            return undef;
+        }
+
+    } elsif ($req_type eq "bycoord") {
+
+        # run
+        # regtool -dbname $image_db -processedimfile -time_begin $mjd_min -time_end = $mjd_max -filter $filter
+        #
+        my $results = lookup_bycoord($ipprc, $image_db, $x, $y, $mjd_min, $mjd_max, $filter, $verbose);
+
+        # now take the results and lookup byexp
+        # XXX: This needs work. What stops results from returning multiple images????
+        # We should loop over the set of images returned building up the full results array
+        $req_type = "byexp";
+        $id = $results->{exp_name};
+    }
+
+    my $results = lookup($ipprc, $image_db, $req_type, $img_type, $id, $component, $verbose);
+
+    return $results;
+}
+
+sub lookup {
+    my $ipprc    = shift;
+    my $image_db = shift;
+    my $req_type = shift;
+    my $img_type = shift;
+    my $id       = shift;
+    my $component= shift;
+    my $verbose  = shift;
+
+    my $missing_tools;
+    my $regtool = can_run('regtool') or (warn "Can't find regtool" and $missing_tools = 1);
+    my $chiptool = can_run('chiptool') or (warn "Can't find chiptool" and $missing_tools = 1);
+    my $warptool = can_run('warptool') or (warn "Can't find warptool" and $missing_tools = 1);
+    my $difftool = can_run('difftool') or (warn "Can't find difftool" and $missing_tools = 1);
+    my $stacktool = can_run('stacktool') or (warn "Can't find stacktool" and $missing_tools = 1);
+    if ($missing_tools) {
+        warn("Can't find required tools.");
+        exit ($PS_EXIT_CONFIG_ERROR);
+    }
+    my $command;
+    my $id_opt;     # option for the lookup
+    my $mask_name;
+    my $weight_name;
+    my $base_name;
+    my $want_astrom;
+    my $set_class_id;
+    my $class_id;
+    my $skycell_id;
+
+    # special class_id value "null" means ignore
+    if ($component eq "null") {
+        $component = undef;
+    }
+
+    if ($img_type eq "raw") {
+        $class_id = $component;
+        $command = "$regtool -processedimfile -dbname $image_db";
+        $id_opt = "-exp_id";
+        $command .= " -class_id $class_id" if $component;
+        $want_astrom = 1;
+        $set_class_id = 1;
+    } elsif ($img_type eq "chip") {
+        $class_id = $component;
+        $command = "$chiptool -processedimfile -dbname $image_db";
+        $command .= " -class_id $class_id" if $class_id;
+        $id_opt = "-chip_id";
+        $mask_name    = "PPIMAGE.CHIP.MASK";
+        $weight_name  = "PPIMAGE.CHIP.WEIGHT";
+        $base_name    = "path_base"; # name of the field for the chiptool output
+        $want_astrom  = 1;
+        $set_class_id = 1;
+    } elsif ($img_type eq "warp") {
+        $skycell_id = $component;
+        $command = "$warptool -warped -dbname $image_db";
+        $command .= " -skycell_id $skycell_id" if $skycell_id;
+        $id_opt = "-warp_id";
+        $mask_name    = "PSWARP.OUTPUT.MASK";
+        $weight_name  = "PSWARP.OUTPUT.WEIGHT";
+        $base_name    = "path_base"; # name of the field for the warptool output
+    } elsif ($img_type eq "diff") {
+        $skycell_id = $component;
+        $command = "$difftool -diffskyfile -dbname $image_db";
+        $command .= " -skycell_id $skycell_id" if $skycell_id;
+        $id_opt = "-diff_id";
+        $mask_name   = "PPSUB.OUTPUT.MASK";
+        $weight_name = "PPSUB.OUTPUT.WEIGHT";
+        $base_name   = "path_base";
+    } elsif ($img_type eq "stack") {
+        $command = "$stacktool -sumskyfile -dbname $image_db";
+        $id_opt = "-stack_id";
+
+        $mask_name   = "PPSTACK.OUTPUT.MASK";
+        $weight_name = "PPSTACK.OUTPUT.WEIGHT";
+        $base_name   = "path_base";
+    } else {
+        die "Unknown img_type supplied: $img_type";
+    }
+
+    if ($req_type eq "byid") {
+        $command .= " $id_opt $id";
+    } elsif ($req_type eq "byexp") {
+        $command .= " -exp_name $id";
+    } else {
+        die "Unknown req_type supplied: $req_type";
+    }
+
+    # run the tool and parse the output
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+                run(command => $command, verbose => $verbose);
+    unless ($success) {
+        # not sure if we should die here
+        print STDERR @$stderr_buf;
+        return undef;
+    }
+
+    my $buf = join "", @$stdout_buf;
+    if (!$buf) {
+        return;
+    }
+
+    my $mdcParser = PS::IPP::Metadata::Config->new;
+    my $images = parse_md_fast($mdcParser, $buf)
+        or die ("Unable to parse metadata config doc");
+
+    my $output = [];
+
+    my $camera;
+    foreach my $image (@$images) {
+        my $base;
+
+        next if ($img_type eq "warp" and $image->{ignored});
+
+        if ($base_name) {
+            $base = $image->{$base_name};
+            # if base is undef then the output pruducts for this image are incomplete
+            # This may only happen for warps. (Actually the test for ignored that I added
+            # just above probably solved the problem I was trying to solve with this test)
+            next if !$base;
+        }
+        if (!$camera) {
+            # This assumes that all images have the same camera
+            $camera = $image->{camera};
+            $ipprc->define_camera($camera);
+        }
+        my $out = {};
+        $out->{exp_id} = $image->{exp_id};
+        $out->{exp_name} = $image->{exp_name};
+        $out->{image}  = $image->{uri};
+        $out->{state}  = $image->{state}; # state is undef for rawExp, but that's ok
+        if ($set_class_id) {
+            $class_id = $image->{class_id};
+            $out->{class_id} = $class_id;
+        }
+
+        # find the mask and weight images
+        if ($base) {
+            $out->{mask}   = $ipprc->filename($mask_name,   $base, $class_id) if $mask_name;
+            $out->{weight} = $ipprc->filename($weight_name, $base, $class_id) if $weight_name;
+        }
+        $out->{camera} = $camera;
+
+        $out->{astrom} = find_astrometry($ipprc, $image_db, $image, $verbose) if $want_astrom;
+
+        push @$output, $out;
+    }
+
+    return $output;
+}
+sub lookup_bydiff {
+    my $ipprc    = shift;
+    my $image_db = shift;
+    my $id       = shift;
+    my $img_type = shift;
+    my $verbose = shift;
+
+    my $missing_tools;
+    my $difftool = can_run('difftool') or (warn "Can't find difftool" and $missing_tools = 1);
+    if ($missing_tools) {
+        warn("Can't find required tools.");
+        exit ($PS_EXIT_CONFIG_ERROR);
+    }
+
+    my $command = "$difftool -diffskyfile -diff_image_id $id -dbname $image_db";
+
+    # run the tool and parse the output
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+                run(command => $command, verbose => $verbose);
+    unless ($success) {
+        # not sure if we should die here
+        print STDERR @$stderr_buf;
+        return undef;
+    }
+
+    my $buf = join "", @$stdout_buf;
+    if (!$buf) {
+        return undef;
+    }
+
+    my $mdcParser = PS::IPP::Metadata::Config->new;
+    my $images = parse_md_fast($mdcParser, $buf)
+        or die ("Unable to parse metadata config doc");
+
+    my $n = @$images;
+    if ($n > 1) {
+        die ("difftool returned an unexpected number of diffskyfiles: $n");
+    } elsif ($n == 0) {
+        return undef;
+    }
+
+    my $image = $images->[0];
+
+    my $skycell_id = $image->{skycell_id};
+
+    if ($image->{fault}) {
+        print STDERR "selected difference image $id $image->{diff_id} $skycell_id has fault: $image->{fault}\n";
+        return undef;
+    }
+
+    # The standard way to do a diff is warp - stack 
+    # so we interpret the requested image in that way
+
+    my $stack_id = $image->{stack2};
+    # XXX difftool currently returns max long long for null
+    # this line is ready if we switch the code to return zero for null
+    if ($stack_id and ($stack_id != 9223372036854775807)) {
+        $image->{stack_id} = $stack_id;
+    }
+
+    my $warp_id =  $image->{warp1};
+    # XXX difftool currently returns max long long for null
+    # this line is ready if we switch the code to return zero for null
+    if ($warp_id and ($warp_id != 9223372036854775807)) {
+        $image->{warp_id} = $warp_id;
+
+        ## now use the warp and go get the exposure information
+        $command = "warptool -warped -warp_id $warp_id -skycell_id $skycell_id -dbname $image_db -limit 1";
+        my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+                    run(command => $command, verbose => $verbose);
+        unless ($success) {
+            # not sure if we should die here
+            print STDERR @$stderr_buf;
+            return undef;
+        }
+
+        my $buf = join "", @$stdout_buf;
+        if (!$buf) {
+            return undef;
+        }
+
+        my $mdcParser = PS::IPP::Metadata::Config->new;
+        my $warps = parse_md_fast($mdcParser, $buf)
+            or die ("Unable to parse metadata config doc");
+        my $warp = $warps->[0];
+
+        if ($img_type eq "warp") {
+            $image = $warp;
+        } else {
+            $image->{exp_id} = $warp->{exp_id};
+            $image->{exp_name} = $warp->{exp_name};
+            $image->{camera} = $warp->{camera};
+        }
+    }
+
+    if (($img_type eq "diff") or ($img_type eq "warp")) {
+        # the $image is going to be returned directly in this case so we need to duplicate
+        # some of processing that lookup does for other img_types
+        $image->{image} = $image->{uri};
+        if ($image->{camera}) {
+            $ipprc->define_camera($image->{camera});
+            if ($img_type eq "diff") {
+                $image->{mask}   = $ipprc->filename("PPSUB.OUTPUT.MASK", $image->{path_base});
+                $image->{weight} = $ipprc->filename("PPSUB.OUTPUT.WEIGHT", $image->{path_base});
+            } else {
+                $image->{mask}   = $ipprc->filename("PSWARP.OUTPUT.MASK",   $image->{path_base});
+                $image->{weight} = $ipprc->filename("PSWARP.OUTPUT.WEIGHT", $image->{path_base});
+            }
+        } else {
+            # XXX this will only happen if the minuend is not a warp
+            print STDERR "WARNING: cannot resolve camera so cannot get weight and mask images\n";
+        }
+    }
+
+    return $image;
+}
+
+sub lookup_bycoord {
+    my $ipprc    = shift;
+    my $image_db = shift;
+    my $x        = shift;
+    my $y        = shift;
+    my $mjd_min  = shift;
+    my $mjd_max  = shift;
+    my $filter   = shift;
+    my $verbose  = shift;
+
+    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);
+    }
+
+    # XXX TODO:
+    # Full lookups by coordinate require looking at the DVO database. However, 
+    # dateobs and filter which is all that MOPS has asked for.
+
+    my $args;
+    if ($mjd_min) {
+        my $dateobs_min = mjd_to_dateobs($mjd_min);
+        $args .= " -dateobs_begin $dateobs_min";
+    }
+    if ($mjd_max) {
+        my $dateobs_max = mjd_to_dateobs($mjd_max);
+        $args .= " -dateobs_end $dateobs_max";
+    }
+    if ($filter) {
+        $args .= " -filter $filter";
+    }
+
+    if (!$args) {
+        # avoid returning every exposure in the DB
+        print STDERR "no query arguments provided for bycoord\n";
+        return undef;
+    }
+
+    # XXX TODO: This query doesn't work Something appears to be out of
+    # sync about the times I'm passing and what regtool and or the DB expect
+    # the query I used in the C version of locateimages used
+    # WHERE dateobs >= FROM_UNIXTIME(%ld) AND dateobs <= FROM_UNIXTIME(%ld + exp_time)
+
+    my $command = "$regtool -processedexp -dbname $image_db $args";
+
+    # run the tool and parse the output
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+                run(command => $command, verbose => $verbose);
+    unless ($success) {
+        # not sure if we should die here
+        print STDERR @$stderr_buf;
+        return undef;
+    }
+    my $mdcParser = PS::IPP::Metadata::Config->new; # Parser for metadata config files
+
+    my $output = join "", @$stdout_buf;
+    if (!$output) {
+        print STDERR "no output returned from $command\n" if $verbose;
+        return undef;
+    }
+    my $images = parse_md_fast($mdcParser, $output) or die ("Unable to parse metadata config doc");
+
+    return $images;
+}
+
+# find the astrometry file for a given exposure
+# return undef if no completed camRun exists
+sub find_astrometry {
+    my $ipprc = shift;
+    my $image_db = shift;
+    my $image = shift;      # hashref to output of the lookup tool
+    my $verbose = shift;
+
+    my $missing_tools;
+    my $camtool = can_run("camtool") or (warn "Can't find camtool" 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 $command = "$camtool -dbname $image_db -processedexp -exp_id $image->{exp_id}";
+    # run the tool and parse the output
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+                run(command => $command, verbose => $verbose);
+    unless ($success) {
+        print STDERR @$stderr_buf;
+        return undef;
+    }
+
+    my $mdcParser = PS::IPP::Metadata::Config->new; # Parser for metadata config files
+
+    my $output = join "", @$stdout_buf;
+    if (!$output) {
+        print STDERR "no output returned from $command\n" if $verbose;
+        return undef;
+    }
+    my $camruns = parse_md_fast($mdcParser, $output);
+    if (!$camruns) {
+        return undef;
+    }
+
+    # If there are multiple cam runs for this exposure, take the last one
+    # assuming that it has the best astrometry 
+    my $camdata = pop @$camruns;
+    if (!$camdata) {
+        # no cam runs for this exposure id therefore best astrometry is whatever is in the header
+        return undef;
+    }
+    my $camRoot = $camdata->{path_base};
+    my $camera = $image->{camera};
+    my $astromSource;
+    {
+       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);
+            die("Unable to perform ppConfigDump: $error_code");
+        }
+        my $metadata = $mdcParser->parse(join "", @$stdout_buf) or
+            die("Unable to parse metadata config doc");
+        $astromSource = metadataLookupStr($metadata, 'ASTROM.SOURCE');
+    }
+
+    my $astromFile = $ipprc->filename($astromSource, $camRoot);
+
+    return $astromFile;
+}
+
+# splits meta data config input stream into single units to work around the pathalogically
+# slow parser. This is similar to and adapted from code in various ippScripts.
+sub parse_md_fast {
+    my $mdcParser = shift;
+    my $input = shift;
+    my $output = ();
+
+    my @whole = split /\n/, $input;
+    my @single = ();
+
+    my $n;
+    while ( ($n = @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
+                print STDERR "Unable to parse metdata config doc" and return undef;
+#            my $num = @$list;
+#            print STDERR "list has $num elments\n";
+            push @$output, $list->[0];
+
+            @single = ();
+        }
+    }
+    return $output;
+}
+
+sub mjd_to_dateobs {
+    my $mjd = shift;
+
+    my $ticks = ($mjd - 40587.0) * 86400;
+
+    my ($sec, $min, $hr, $day, $mon, $year) = gmtime($ticks);
+
+    return sprintf "'%4d-%02d-%02d %02d:%02d:%02d'", $year+1900, $mon+1, $day, $hr, $min, $sec;
+}
+
+# resolve_project()
+# get project specific information
+sub resolve_project {
+    my $ipprc = shift;
+    my $project_name = shift;
+    my $dbname = shift;
+    die "project is not defined" if !$project_name;
+
+    my $verbose = 0;
+
+    my $missing_tools;
+    my $pstamptool = can_run("pstamptool") or (warn "Can't find pstamptool" and $missing_tools = 1);
+    if ($missing_tools) {
+        warn("Can't find required tools.");
+        exit ($PS_EXIT_CONFIG_ERROR);
+    }
+
+    my $command = "$pstamptool -project -name $project_name";
+    $command .= " -dbname $dbname" if defined $dbname;
+    # run the tool and parse the output
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+                run(command => $command, verbose => $verbose);
+    unless ($success) {
+        print STDERR @$stderr_buf;
+        return undef;
+    }
+
+    my $mdcParser = PS::IPP::Metadata::Config->new; # Parser for metadata config files
+
+    my $output = join "", @$stdout_buf;
+    if (!$output) {
+        print STDERR "no output returned from $command\n" if $verbose;
+        return undef;
+    }
+    my $proj_hash = parse_md_fast($mdcParser, $output);
+
+    return $proj_hash->[0];
+}
+1;
Index: /trunk/PS-IPP-PStamp/lib/PStamp/RequestFile.pm
===================================================================
--- /trunk/PS-IPP-PStamp/lib/PStamp/RequestFile.pm	(revision 23264)
+++ /trunk/PS-IPP-PStamp/lib/PStamp/RequestFile.pm	(revision 23264)
@@ -0,0 +1,129 @@
+#!/bin/env perl
+
+###
+###     PStamp/RequestFile.pm
+###     subroutines and constants related to Postage Stamp Request Files
+###
+
+package PStamp::RequestFile;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.0';
+
+use base qw( Exporter );
+
+our @EXPORT_OK = qw( 
+                    read_request_file
+                    $PSTAMP_CENTER_IN_PIXELS
+                    $PSTAMP_RANGE_IN_PIXELS
+                    $PSTAMP_SELECT_IMAGE
+                    $PSTAMP_SELECT_MASK
+                    $PSTAMP_SELECT_WEIGHT
+                    );
+our %EXPORT_TAGS = (standard => [@EXPORT_OK]);
+
+
+our $PSTAMP_CENTER_IN_PIXELS = 1;
+our $PSTAMP_RANGE_IN_PIXELS  = 2;
+
+our $PSTAMP_SELECT_IMAGE     = 1;
+our $PSTAMP_SELECT_MASK      = 2;
+our $PSTAMP_SELECT_WEIGHT    = 4;
+
+use IPC::Cmd 0.36 qw( can_run run );
+
+use PS::IPP::Metadata::List qw( parse_md_list );
+use PS::IPP::Config qw( :standard );
+
+sub read_request_file {
+    my $request_file_name = shift;
+    die "need request file name\n" unless $request_file_name;
+
+    my $verbose = shift;
+
+    my $ipprc = PS::IPP::Config->new(); # IPP Configuration
+
+    my $missing_tools;
+
+    my $pstampdump  = can_run('pstampdump') or (warn "Can't find pstampdump" and $missing_tools = 1);
+    my $fields  = can_run('fields') or (warn "Can't find fields" and $missing_tools = 1);
+
+    if ($missing_tools) {
+        warn("Can't find required tools.");
+        exit ($PS_EXIT_CONFIG_ERROR);
+    }
+
+    # Parser for metadata config files
+    my $mdcParser = PS::IPP::Metadata::Config->new;
+
+    #
+    # get the data from the extension header
+    #
+    my $fields_output;
+    {
+        my $command = "echo $request_file_name | $fields -x 0 EXTNAME EXTVER REQ_NAME";
+        my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+            run(command => $command, verbose => $verbose);
+        $fields_output = join "", @$stdout_buf;
+    }
+    my (undef, $extname, $extver, $req_name) = split " ", $fields_output;
+
+    # make sure the file contains what we are expecting
+
+    die "$request_file_name is not a PS1_PS_REQUEST" 
+                    if !$extname or ($extname ne "PS1_PS_REQUEST");
+    die "REQ_NAME not found in $request_file_name"  if (!$req_name);
+    die "wrong EXTVER $extver found in $request_file_name" if ($extver ne "1");
+
+    my %header;
+    $header{REQ_NAME} = $req_name;
+    $header{EXTVER}   = $extver;
+    $header{EXTNAME}  = $extname;
+
+    #
+    # now convert the request table to an array of hashes
+    #
+
+    my $rows;
+    {
+        my $command = "$pstampdump $request_file_name";
+        my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+            run(command => $command, verbose => $verbose);
+        unless ($success) {
+            print STDERR @$stderr_buf;
+        }
+        my $table =  $mdcParser->parse(join "", @$stdout_buf) or
+            die("Unable to parse metdata config doc");
+
+        $rows = parse_md_list($table);
+    }
+
+    my %req_specs;
+    foreach my $row (@$rows) {
+        my $rownum   = $row->{ROWNUM};
+        $req_specs{$rownum} = $row;
+
+        my $job_type = $row->{JOB_TYPE};
+        my $project  = $row->{PROJECT};
+        my $req_type = $row->{REQ_TYPE};
+        my $img_type = $row->{IMG_TYPE};
+        my $id       = $row->{ID};
+        my $class_id = $row->{CLASS_ID};
+        my $stamp_name   = $row->{STAMP_NAME};
+        my $filter   = $row->{REQFILT};
+        my $mjd_min = $row->{MJD_MIN};
+        my $mjd_max = $row->{MJD_MAX};
+        my $x = $row->{CENTER_X};
+        my $y = $row->{CENTER_Y};
+        my $w = $row->{WIDTH};
+        my $h = $row->{HEIGHT};
+        my $coord_mask = $row->{COORD_MASK};
+        my $option_mask= $row->{OPTION_MASK};
+
+        print "$rownum $req_type $img_type $id\n" if $verbose;
+    }
+    return (\%header, \%req_specs);
+}
+1;
