#!/bin/perl -w #+ # obslog -- generate logsheet data from image headers # # Purpose: # Given a list of images, parse the image headers and print # out a formatted list of data. With no arguments, parse all of # the images in the current directory. # # Usage: # obslog [-D] [-l] [-v] {-i ] [n | images] # # Arguments: # n = number of latest images to parse. # images = names of images to parse. If no images are listed, # then all images in the current directory will be read # (EXCEPT for backup.fits). # # Options: # -D = enable debug mode # -v = verbose mode # -l = enable "looping" mode # -i = re-print header at the specified interval [default = 20 images] # -L = print linefeed on each header after first # # Output: # to STDOUT # # Restrictions # None # # Note: # The output from this program is extra wide. To print it, use # this command: # enscript -1r -c -f Courier9 -P lw4s # # Exit values: # 0 = normal completion # 1 = wrong number of arguments # # Example: # 1) Generate log data for all images in the current directory: # obslog # # ) Generate log data for the last 5 images in the current directory: # obslog 5 # # 3) Generate log data for all images in the directory # /s/sdata1001/deimos1/2003jan01: # obslog /s/sdata1001/deimos1/2003jan01/*.fits #- # Modification history: # 2002-Dec-30 GDW Original version # 2003-Aug-27 GDW Changed TTIME to EXPTIME # 2004-Oct-10 GDW Added "last n images" option # 2011-Jul-29 LR Added the looping option # 2012-Apr-16 GDW Renamed as "obslog", various enhancements # 2013-Sep-27 GDW Fix bug in ProcessImage (next->return) #----------------------------------------------------------------------- use Getopt::Std; use File::Basename; use strict; # declarations... $| = 1; my( $observers, $date, $directory); my( @images); my( $i, $j); my( $m, $n, $title_format); my( %HeadValue); my( $null) = 'INDEF'; my $loop_mode; my $line; my $space; my $buf; my $headline; my $name; my $rest; my $lastquote; my $value; my $new_image; my $header_interval = 20; my $linefeed = 0; $X::debug = 0; $X::verbose = 1; # define logsheet fields... &AddColumn( 'FILENAME', 'Filename', '%-10s '); &AddColumn( 'TARGNAME', 'Obs Name', '%-14s '); &AddColumn( 'ROTPOSN', 'SKYPA', '%7.2f '); &AddColumn( 'LAMPS', 'Lamps', '%-8s '); &AddColumn( 'SLMSKNAM', 'Slitmask', '%-8s '); &AddColumn( 'GRATENAM', 'Grating', '%-7s '); &AddColumn( 'WAVELEN', 'Wavelen', '%7d '); &AddColumn( 'DWFILNAM', 'Filter', '%-7s '); &AddColumn( 'DWFOCVAL', 'Focus', '%7d '); &AddColumn( 'EXPTIME', 'Exp', '%5d '); &AddColumn( 'AIRMASS', 'Airmass', '%7.2f '); &AddColumn( 'OBJECT', 'Comments', '%-30s '); # check args... die "Usage: $0 [images] [-loop]\n" if ( @ARGV < 0 ); # default mode is no looping $loop_mode=0; # get options... my( %option); getopts("Dlqi:L", \%option); if ( $option{'l'} ) { $loop_mode = 1; } if ( $option{'q'} ) { $X::verbose = 0; } if ( $option{'D'} ) { $X::debug = 1; } if ( $option{'i'} ) { $header_interval = $option{'i'}; } if ( $option{'L'} ) { $linefeed = 1; } # build the image list @images=&BuildImageList; # parse args... if ( @ARGV == 1 and $ARGV[0] =~ /^\d+$/ ) { $m = $ARGV[0]; if ( $m > @images ) { $m = scalar(@images); } $m = -$m; @images = @images[ $m .. -1 ]; # extract last $m elements of image list } elsif ( @ARGV > 0 ) { @images = @ARGV; } # verify number of images... unless( @images ){ if ( $X::verbose) { die "ERROR: No images found" } else { exit 1 } } # get some information from the first DEIMOS image... for ( my $i=0 ; $i < @images ; $i++) { &GetFitsHead( $images[$i], \%HeadValue) or next; last if (defined($HeadValue{'INSTRUME'}) and $HeadValue{'INSTRUME'} =~ m/DEIMOS/) } unless (defined $HeadValue{'INSTRUME'} and $HeadValue{'INSTRUME'} =~ m/DEIMOS/) { if ( $X::verbose) { die "ERROR: No DEIMOS images found"; } else { exit 1; } } # print header... print "\n"; printf "%-72s", "Project:"; printf "UT Date: %s", &BlankIfUndef($HeadValue{"DATE-OBS"}); print "\n"; printf "%-72s", "Observers: " . &BlankIfUndef($HeadValue{"OBSERVER"}); print "Weather:"; print "\n"; printf "%-72s", "Data directory: " . &BlankIfUndef($HeadValue{"OUTDIR"}); print "Seeing:"; print "\n"; &PrintHeader(0); # loop over images... my $counter=0; for ( $j=0 ; $j<@images ; $j++ ) { ProcessImage ($images[$j]); # re-display header every 20 lines... if (++$counter>=$header_interval) { &PrintHeader($linefeed) ; $counter=0; } } # quit unless we want to loop... exit unless $loop_mode; my $last_image=$images[@images-1]; # start the waiting loop while (1) { # wait for new image... $new_image = `wfi`; # skip if we got the same image again... next if $new_image eq $last_image; # re-display header every so often... if (++$counter>=$header_interval) { &PrintHeader($linefeed); $counter=0; } # print image... ProcessImage($new_image); # store name... $last_image = $new_image; } #------------------------------------------------------------------ sub PrintHeader { #------------------------------------------------------------------ my( $linefeed) = shift; my( $line1, $line2); if ($linefeed) { print " "; } else { print "\n"; } $line1 = ''; $line2 = ''; for ( $i=0 ; $i<@X::keyword ; $i++ ) { $X::format[$i] =~ m/^%-?(\d+)/; $n = $1; $X::format[$i] =~ m/(\s+)$/; $space = $1; $buf = sprintf "%-${n}s$space", $X::title[$i]; $line1 .= $buf; $buf =~ s/./-/g; $line2 .= $buf; } print "$line2\n"; print "$line1\n"; print "$line2\n"; } #------------------------------------------------------------------ sub BuildImageList { #------------------------------------------------------------------ # build default image list... opendir DATADIR, '.'; my @images = sort grep /\.fits/, readdir DATADIR; #@images = sort @images ; closedir DATADIR; # expunge bad filenames from image list. # NOTE: that we traverse the list BACKWARDS because we are removing elements from the list as we go... for ( $i=@images-1 ; $i>=0 ; $i--) { if ( ($images[$i] =~ m/backup.fits/) or # remove backup files ($images[$i] =~ m/^cam/) or # remove cam files not ($images[$i] =~ m/.fits$/ or $images[$i] =~ m/.fits.gz$/)) { splice( @images, $i, 1) } } return @images; } #----------------------------------------------------------------------- sub ProcessImage { #----------------------------------------------------------------------- my $infile = shift; # read image header... &GetFitsHead( $infile, \%HeadValue); # skip non-DEIMOS images... return unless (defined($HeadValue{'INSTRUME'}) and $HeadValue{'INSTRUME'} =~ m/DEIMOS/); # build filename field... $HeadValue{'FILENAME'} = (fileparse( $infile, '\..*'))[0]; # fix grating/wavelength fields... if ( not defined $HeadValue{'GRATEPOS'}) { warn "GRATEPOS not defined" if $X::debug; undef $HeadValue{'WAVELEN'}; } elsif ($HeadValue{'GRATEPOS'} eq '3' ) { $HeadValue{'WAVELEN'} = nint($HeadValue{'G3TLTWAV'}) } elsif ( $HeadValue{'GRATEPOS'} eq '4' ) { $HeadValue{'WAVELEN'} = nint($HeadValue{'G4TLTWAV'}) } else { $HeadValue{'WAVELEN'} = $null; } # fix lamps... if ( defined $HeadValue{'LAMPS'} ) { $HeadValue{'LAMPS'} =~ s/ //g; # trim blanks from lamps... if ( $HeadValue{'LAMPS'} =~ m/^off/i ) { $HeadValue{'LAMPS'} = ""; } if ( defined $HeadValue{'FLIMAGIN'} and $HeadValue{'FLIMAGIN'} !~ m/^off/i ) { $HeadValue{'LAMPS'} .= "Dome/Im" } if ( defined $HeadValue{'FLSPECTR'} and $HeadValue{'FLSPECTR'} !~ m/^off/i ) { $HeadValue{'LAMPS'} .= "Dome/Sp" } } # round off exposure time.. $HeadValue{'EXPTIME'} = nint( $HeadValue{'EXPTIME'}); # print results... for ( $i=0 ; $i<@X::keyword ; $i++ ) { if ( defined $HeadValue{$X::keyword[$i]} and $HeadValue{$X::keyword[$i]} ne $null ) { $HeadValue{$X::keyword[$i]} =~ s/^\s+//; # remove leading whitespace $HeadValue{$X::keyword[$i]} =~ s/\s+$//; # remove trailing whitespace printf $X::format[$i], $HeadValue{$X::keyword[$i]} } else { $X::format[$i] =~ m/^%-?(\d+)/; $n = $1; if ( not defined $HeadValue{$X::keyword[$i]}) { $buf = '?' } else { $buf = '' } printf "%-${n}s ", $buf; } } print "\n"; } #----------------------------------------------------------------------- sub GetFitsHead { #----------------------------------------------------------------------- # get file name to read header from my( $infile, $array) = @_; my( $comment); my( $name, $rest, $lastquote, $value, $value2); my( $status); # reset output hash to null %$array = (); # open the file with a shared lock to prevent it being modified # during reading... unless (open(TEST, "<$infile")){ warn "error opening file $infile: $!"; return 0; } unless (flock(TEST, 1)){ warn "error attempting to lock file $infile: $!"; return 0; } # open input file using appropriate method... if ($infile =~ /\.gz$/i) { open INFITS, "gunzip --stdout $infile |"; $| = 1; # enable autoflush on gunzip output } else { open INFITS, $infile; } # initialize input variable to allow for end-of-header trapping my($headline) = " "; # loop thru FITS file 80 bytes at a time until end-of-header mark found until (substr($headline,0,8) eq "END ") { $status = read(INFITS, $headline, 80); unless ( $status){ warn "unexpected end of file while reading image $infile"; return undef; } chomp($headline); # DATE-OBS= 1333663730.392 / universal date of observation (2012-04-05) if ( $headline =~ m|^(........)=\s*(.*) / (.*)$| ) { # identify keyword name from first 8 bytes $name = $1; $value = $2; $comment = $3; } elsif ( $headline =~ m|^(........)=\s*(.*\S)\s*$| ) { $name = $1; $value = $2; $comment = ''; } else { next } # strip trailing spaces off of keyword names $name =~ s/\s+$//g; # test if value is a string, handle single quotes if it is if ( $value =~ m|'([^']+)'| ) { $value = $1; } # add next value and comment to respective hashes unless ($name eq "END" or $name eq "") { print "name=$name value=$value\n" if $X::debug; $$array{$name} = $value; } # check for alternate value in the comment... print "name=$name value=$value comment=$comment\n" if $X::debug; if ( $comment =~ m|\((.+)\)\s*$| ) { $value2 = $1; print "value2=$value2\n" if $X::debug; $$array{"$name-USER"} = $value2; } } # close input FITS file close INFITS; close TEST; return 1; } #----------------------------------------------------------------------- sub AddColumn { #----------------------------------------------------------------------- my( $keyword, $title, $format) = @_; push( @X::keyword, $keyword); push( @X::title, $title); push( @X::format, $format); } #----------------------------------------------------------------------- sub BlankIfUndef { #----------------------------------------------------------------------- my( $value) = @_; if ( defined $value) { return $value } else { return '' } } #----------------------------------------------------------------------- sub nint { #----------------------------------------------------------------------- my $x = shift; if ( $x==0 ) { return 0 } my $y=abs($x); my $sign=$x/$y; return $sign*int($y+0.5) }