File Coverage

blib/lib/Astro/FITS/HdrTrans/LCO.pm
Criterion Covered Total %
statement 17 166 10.2
branch 0 62 0.0
condition 0 36 0.0
subroutine 6 27 22.2
pod 16 20 80.0
total 39 311 12.5


line stmt bran cond sub pod time code
1             # -*-perl-*-
2              
3             package Astro::FITS::HdrTrans::LCO;
4              
5             =head1 NAME
6              
7             Astro::FITS::HdrTrans::LCO - Base class for translation of LCO instruments
8              
9             =head1 SYNOPSIS
10              
11             use Astro::FITS::HdrTrans::LCO;
12              
13             =head1 DESCRIPTION
14              
15             This class provides a generic set of translations that are common to
16             instrumentation from LCO. It should not be use directly for translation of
17             instrument FITS headers.
18              
19             =cut
20              
21 19     19   29030649 use 5.006;
  19         97  
22 19     19   152 use warnings;
  19         49  
  19         769  
23 19     19   135 use strict;
  19         60  
  19         499  
24 19     19   114 use Carp;
  19         47  
  19         1526  
25              
26             # Inherit from the Base translation class and not HdrTrans itself
27             # (which is just a class-less wrapper).
28              
29 19     19   140 use base qw/ Astro::FITS::HdrTrans::FITS /;
  19         79  
  19         7154  
30              
31 19     19   148 use vars qw/ $VERSION /;
  19         51  
  19         36630  
32              
33             $VERSION = "1.63";
34              
35             # for a constant mapping, there is no FITS header, just a generic
36             # header that is constant
37             my %CONST_MAP = (
38             DETECTOR_READ_TYPE => "STARE",
39             OBSERVATION_MODE => "imaging",
40             NUMBER_OF_EXPOSURES => 1,
41             POLARIMETRY => 0,
42             SPEED_GAIN => "NORMAL"
43             );
44              
45             my %UNIT_MAP = (
46             EXPOSURE_TIME => "EXPTIME",
47             GAIN => "GAIN",
48             READNOISE => "RDNOISE",
49             INSTRUMENT => "INSTRUME",
50             OBJECT => "OBJECT",
51             );
52              
53              
54             # Create the translation methods
55             __PACKAGE__->_generate_lookup_methods( \%CONST_MAP, \%UNIT_MAP );
56              
57             =head1 COMPLEX CONVERSIONS
58              
59             These methods are more complicated than a simple mapping. We have to
60             provide both from- and to-FITS conversions All these routines are
61             methods and the to_ routines all take a reference to a hash and return
62             the translated value (a many-to-one mapping) The from_ methods take a
63             reference to a generic hash and return a translated hash (sometimes
64             these are many-to-many)
65              
66             =over 4
67              
68             =cut
69              
70             =item B<to_AIRMASS_END>
71              
72             Set's the airmass at the end of the exposure. The C<AMEND> is used if it exists,
73             otherwise C<AIRMASS> is used. In the case of neither existing, it is set to 1.0.
74              
75             =cut
76              
77             sub to_AIRMASS_END {
78 0     0 1   my $self = shift;
79 0           my $FITS_headers = shift;
80 0           my $end_airmass = 1.0;
81 0 0 0       if ( exists $FITS_headers->{"AMEND"} && $FITS_headers->{"AMEND"} !~ /^UNKNOWN/ ) {
    0 0        
82 0           $end_airmass = $FITS_headers->{"AMEND"};
83             } elsif ( exists $FITS_headers->{"AIRMASS"} && $FITS_headers->{"AIRMASS"} !~ /^UNKNOWN/ ) {
84 0           $end_airmass = $FITS_headers->{"AIRMASS"};
85             }
86 0           return $end_airmass;
87             }
88              
89             =item B<to_AIRMASS_START>
90              
91             Set's the airmass at the start of the exposure. The C<AMSTART> is used if it
92             exists, otherwise C<AIRMASS> is used. In the case of neither existing, it is set
93             to 1.0.
94              
95             =cut
96              
97             sub to_AIRMASS_START {
98 0     0 1   my $self = shift;
99 0           my $FITS_headers = shift;
100 0           my $start_airmass = 1.0;
101 0 0 0       if ( exists $FITS_headers->{"AMSTART"} && $FITS_headers->{"AMSTART"} !~ /^UNKNOWN/ ) {
    0 0        
102 0           $start_airmass = $FITS_headers->{"AMSTART"};
103             } elsif ( exists $FITS_headers->{"AIRMASS"} && $FITS_headers->{"AIRMASS"} !~ /^UNKNOWN/ ) {
104 0           $start_airmass = $FITS_headers->{"AIRMASS"};
105             }
106 0           return $start_airmass;
107             }
108              
109             =item B<to_DEC_BASE>
110              
111             Converts the base declination from sexagesimal d:m:s to decimal
112             degrees using the C<DEC> keyword, defaulting to 0.0.
113              
114             =cut
115              
116             sub to_DEC_BASE {
117 0     0 1   my $self = shift;
118 0           my $FITS_headers = shift;
119 0           my $dec = 0.0;
120 0           my $sexa = $FITS_headers->{"DEC"};
121 0 0         if ( defined( $sexa ) ) {
122 0           $dec = $self->dms_to_degrees( $sexa );
123             }
124 0           return $dec;
125             }
126              
127              
128             =item B<to_DR_RECIPE>
129              
130             Returns the data-reduction recipe name. The selection depends on the
131             values of the C<OBJECT> and C<OBSTYPE> keywords. The default is
132             "QUICK_LOOK". A dark returns "REDUCE_DARK", and an object's recipe is
133             "JITTER_SELF_FLAT".
134              
135             =cut
136              
137             sub to_DR_RECIPE {
138 0     0 1   my $self = shift;
139 0           my $FITS_headers = shift;
140 0           my $recipe = "QUICK_LOOK";
141              
142 0 0         if ( exists $FITS_headers->{OBSTYPE} ) {
143 0 0         if ( $FITS_headers->{OBSTYPE} =~ /BIAS/i ) {
    0          
    0          
    0          
    0          
144 0           $recipe = "REDUCE_BIAS";
145             } elsif ( $FITS_headers->{OBSTYPE} =~ /DARK/i ) {
146 0           $recipe = "REDUCE_DARK";
147             } elsif ( $FITS_headers->{OBSTYPE} =~ /FLAT/i ) {
148 0           $recipe = "SKY_FLAT";
149             } elsif ( $FITS_headers->{OBSTYPE} =~ /EXPOSE/i ) {
150             # $recipe = "JITTER_SELF_FLAT";
151 0           $recipe = "OFFLINE_REDUCTION";
152             } elsif ( $FITS_headers->{OBSTYPE} =~ /STANDARD/i ) {
153             # $recipe = "BRIGHT_POINT_SOURCE_NCOLOUR_APHOT";
154 0           $recipe = "OFFLINE_REDUCTION";
155             }
156             }
157              
158 0           return $recipe;
159             }
160              
161             # Equinox may be absent...
162             sub to_EQUINOX {
163 0     0 0   my $self = shift;
164 0           my $FITS_headers = shift;
165 0           my $equinox = 2000.0;
166 0 0         if ( exists $FITS_headers->{EQUINOX} ) {
167 0           $equinox = $FITS_headers->{EQUINOX};
168             }
169 0           return $equinox;
170             }
171              
172             =item B<to_FILTER>
173              
174             Look for C<FILTER> keyword first and if not found, concatenate the individual
175             C<FILTERx> keywords together, minus any that say "air"
176             =cut
177              
178             sub to_FILTER {
179 0     0 1   my $self = shift;
180 0           my $FITS_headers = shift;
181 0           my $filter = "";
182 0 0         if (exists $FITS_headers->{"FILTER"} ) {
183 0           $filter = $FITS_headers->{"FILTER"};
184             } else {
185 0           my $filter1 = $FITS_headers->{ "FILTER1" };
186 0           my $filter2 = $FITS_headers->{ "FILTER2" };
187 0           my $filter3 = $FITS_headers->{ "FILTER3" };
188              
189 0 0         if ( $filter1 =~ "air" ) {
190 0           $filter = $filter2;
191             }
192              
193 0 0         if ( $filter2 =~ "air" ) {
194 0           $filter = $filter1;
195             }
196              
197 0 0 0       if ( $filter1 =~ "air" && $filter2 =~ "air" ) {
198 0           $filter = $filter3;
199             }
200              
201 0 0 0       if ( ( $filter1 =~ "air" ) &&
      0        
202             ( $filter2 =~ "air" ) &&
203             ( $filter3 =~ "air" ) ) {
204 0           $filter = "air";
205             }
206             }
207 0           return $filter;
208             }
209              
210             sub from_FILTER {
211 0     0 0   my $self = shift;
212 0           my $generic_headers = shift;
213 0           my %return_hash;
214 0           $return_hash{'FILTER'} = $generic_headers->{FILTER};
215              
216 0           return %return_hash;
217             }
218              
219              
220             =item B<to_NUMBER_OF_OFFSETS>
221              
222             Return the number of offsets. (dithers)
223              
224             =cut
225              
226             sub to_NUMBER_OF_OFFSETS {
227 0     0 1   my $self = shift;
228 0           my $FITS_headers = shift;
229 0 0         my $ndither = ( defined( $FITS_headers->{FRMTOTAL} ) ? $FITS_headers->{FRMTOTAL} : 1 );
230              
231 0           return $ndither + 1;
232              
233             }
234              
235             =item B<_to_OBSERVATION_NUMBER>
236              
237             Converts to the observation number. This uses the C<FRAMENUM> keyword if it
238             exists, otherwise it is obtained from the filename
239              
240             =cut
241              
242             sub to_OBSERVATION_NUMBER {
243 0     0 0   my $self = shift;
244 0           my $FITS_headers = shift;
245 0           my $obsnum = 0;
246 0 0         if ( exists ( $FITS_headers->{FRAMENUM} ) ) {
247 0           $obsnum = $FITS_headers->{FRAMENUM};
248             }
249              
250 0           return $obsnum;
251             }
252              
253             =item B<to_OBSERVATION_TYPE>
254              
255             Determines the observation type from the C<OBSTYPE> keyword. Almost a direct
256             mapping except "EXPOSE" which needs mapping to OBJECT. Lambert may need extra
257             handling in future
258              
259             =cut
260              
261             sub to_OBSERVATION_TYPE {
262 0     0 1   my $self = shift;
263 0           my $FITS_headers = shift;
264 0           my $obstype = uc( $FITS_headers->{OBSTYPE} );
265 0 0 0       if ( $obstype eq "EXPOSE" || $obstype eq "STANDARD" ) {
266 0           $obstype = "OBJECT";
267             }
268 0           return $obstype;
269             }
270              
271             =item B<to_RA_BASE>
272              
273             Converts the base right ascension from sexagesimal h:m:s to decimal degrees
274             using the C<RA> keyword, defaulting to 0.0.
275              
276             =cut
277              
278             sub to_RA_BASE {
279 0     0 1   my $self = shift;
280 0           my $FITS_headers = shift;
281 0           my $ra = 0.0;
282 0           my $sexa = $FITS_headers->{"RA"};
283 0 0         if ( defined( $sexa ) ) {
284 0           $ra = $self->hms_to_degrees( $sexa );
285             }
286 0           return $ra;
287             }
288              
289             =item B<to_STANDARD>
290              
291             Returns whether or not the observation is of a standard source. It is
292             deemed to be a standard when the C<OBSTYPE> keyword is "STANDARD".
293              
294             =cut
295              
296             sub to_STANDARD {
297 0     0 1   my $self = shift;
298 0           my $FITS_headers = shift;
299 0           my $standard = 0;
300 0           my $type = $FITS_headers->{OBSTYPE};
301 0 0         if ( uc( $type ) eq "STANDARD" ) {
302 0           $standard = 1;
303             }
304 0           return $standard;
305             }
306              
307             sub to_UTDATE {
308 0     0 1   my $self = shift;
309 0           my $FITS_headers = shift;
310             # use Data::Dumper;
311             # print Dumper $FITS_headers;
312 0           return $self->_get_UT_date( $FITS_headers );
313             }
314              
315             sub from_UTEND {
316 0     0 1   my $self = shift;
317 0           my $generic_headers = shift;
318 0           my $utend = $generic_headers->{UTEND}->strptime( '%T' );
319 0           return ( "UTEND"=> $utend );
320             }
321              
322             sub from_UTSTART {
323 0     0 1   my $self = shift;
324 0           my $generic_headers = shift;
325 0           my $utstart = $generic_headers->{UTSTART}->strptime('%T');
326 0           return ( "UTSTART"=> $utstart );
327             }
328              
329             sub from_UTDATE {
330 0     0 0   my $self = shift;
331 0           my $generic_headers = shift;
332 0           my $ymd = $generic_headers->{DATE};
333 0           my $dobs = substr( $ymd, 0, 4 ) . "-" . substr( $ymd, 4, 2 ) ."-" . substr( $ymd, 6, 2 );
334 0           return ( "DATE-OBS"=>$dobs );
335             }
336              
337             =item B<to_XBINNING>
338              
339             Determines the binning in the X direction of the frame. We look for C<XBINNING>
340             if it exists, otherwise we look for the C<CCDSUM> keyword and extract the first
341             part.
342              
343             =cut
344              
345             sub to_XBINNING {
346 0     0 1   my $self = shift;
347 0           my $FITS_headers = shift;
348 0           my $xbinning = 2;
349 0 0         if ( exists ( $FITS_headers->{XBINNING} ) ) {
    0          
350 0           $xbinning = $FITS_headers->{XBINNING};
351             } elsif ( exists ( $FITS_headers->{CCDSUM} ) ) {
352 0           my $ccdsum = $FITS_headers->{CCDSUM};
353 0           my @pos = split( / /, $ccdsum );
354 0           $xbinning = $pos[ 0 ];
355             }
356 0           return $xbinning;
357             }
358              
359             =item B<to_YBINNING>
360              
361             Determines the binning in the Y direction of the frame. We look for C<YBINNING>
362             if it exists, otherwise we look for the C<CCDSUM> keyword and extract the second
363             part.
364              
365             =cut
366              
367             sub to_YBINNING {
368 0     0 1   my $self = shift;
369 0           my $FITS_headers = shift;
370 0           my $ybinning = 2;
371 0 0         if ( exists ( $FITS_headers->{YBINNING} ) ) {
    0          
372 0           $ybinning = $FITS_headers->{YBINNING};
373             } elsif ( exists ( $FITS_headers->{CCDSUM} ) ) {
374 0           my $ccdsum = $FITS_headers->{CCDSUM};
375 0           my @pos = split( / /, $ccdsum );
376 0           $ybinning = $pos[ 1 ];
377             }
378 0           return $ybinning;
379             }
380              
381             # Supplementary methods for the translations
382             # ------------------------------------------
383              
384             =item B<dms_to_degrees>
385              
386             Converts a sky angle specified in d m s format into decimal degrees.
387             The argument is the sexagesimal-format angle.
388              
389             =cut
390              
391             sub dms_to_degrees {
392 0     0 1   my $self = shift;
393 0           my $sexa = shift;
394 0           my $dms;
395 0 0         if ( defined( $sexa ) ) {
396 0 0 0       if ($sexa =~ /UNKNOWN/i or $sexa eq "N/A" or $sexa eq "NaN" ) {
      0        
397 0           $dms = 0.0;
398             } else {
399 0           my @pos = split( /:/, $sexa );
400 0           $dms = abs($pos[ 0 ]) + $pos[ 1 ] / 60.0 + $pos [ 2 ] / 3600.0;
401 0 0         if ( $pos[ 0 ] =~ /-/ ) {
402 0           $dms = -$dms;
403             }
404             }
405             }
406 0           return $dms;
407             }
408              
409             =item B<hms_to_degrees>
410              
411             Converts a sky angle specified in h m s format into decimal degrees.
412             It takes no account of latitude. The argument is the sexagesimal
413             format angle.
414              
415             =cut
416              
417             sub hms_to_degrees {
418 0     0 1   my $self = shift;
419 0           my $sexa = shift;
420 0           my $hms;
421 0 0         if ( defined( $sexa ) ) {
422 0 0 0       if ($sexa =~ /UNKNOWN/i or $sexa eq "N/A" or $sexa eq "NaN" ) {
      0        
423 0           $hms = 0.0;
424             } else {
425 0           my @pos = split( /:/, $sexa );
426 0           $hms = 15.0 * ( $pos[ 0 ] + $pos[ 1 ] / 60.0 + $pos [ 2 ] / 3600.0 );
427             }
428             }
429 0           return $hms;
430             }
431              
432             # Returns the UT date in YYYYMMDD format.
433             sub _get_UT_date {
434 0     0     my $self = shift;
435 0           my $FITS_headers = shift;
436              
437             # use Data::Dumper;print Dumper $FITS_headers;die;
438             # This is UT start and time.
439 0           my $dateobs = $FITS_headers->{"DATE-OBS"};
440             # print "DATE-OBS=$dateobs\n";
441 0           my $utdate = substr( $dateobs, 0, 4 ) . substr( $dateobs, 5, 2 ) . substr( $dateobs, 8, 2 );
442             # print "UTDATE=$utdate\n";
443             # Extract out the data in yyyymmdd format.
444 0           return $utdate;
445             }
446              
447             =back
448              
449             =head1 SEE ALSO
450              
451             C<Astro::FITS::HdrTrans>, C<Astro::FITS::HdrTrans::Base>.
452              
453             =head1 AUTHOR
454              
455             Tim Lister E<lt>tlister@lcogt.netE<gt>
456              
457             =head1 COPYRIGHT
458              
459             =cut
460              
461             1;