File Coverage

blib/lib/Astro/FITS/HdrTrans/LCO.pm
Criterion Covered Total %
statement 14 163 8.5
branch 0 62 0.0
condition 0 36 0.0
subroutine 5 26 19.2
pod 16 20 80.0
total 35 307 11.4


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