File Coverage

blib/lib/Astro/FITS/HdrTrans/ClassicCam.pm
Criterion Covered Total %
statement 21 199 10.5
branch 1 62 1.6
condition 2 15 13.3
subroutine 7 33 21.2
pod 27 27 100.0
total 58 336 17.2


line stmt bran cond sub pod time code
1             package Astro::FITS::HdrTrans::ClassicCam;
2              
3             =head1 NAME
4              
5             Astro::FITS::HdrTrans::ClassicCam - Magellan ClassicCam translations
6              
7             =head1 SYNOPSIS
8              
9             use Astro::FITS::HdrTrans::ClassicCam;
10              
11             %gen = Astro::FITS::HdrTrans::ClassicCam->translate_from_FITS( %hdr );
12              
13             =head1 DESCRIPTION
14              
15             This class provides a generic set of translations that are specific to
16             Magellan ClassicCam observations.
17              
18             =cut
19              
20 10     10   235501 use 5.006;
  10         44  
21 10     10   63 use warnings;
  10         28  
  10         383  
22 10     10   58 use strict;
  10         52  
  10         241  
23 10     10   51 use Carp;
  10         30  
  10         804  
24              
25             # Inherit from FITS.
26 10     10   61 use base qw/ Astro::FITS::HdrTrans::FITS /;
  10         52  
  10         1696  
27              
28 10     10   71 use vars qw/ $VERSION /;
  10         22  
  10         20851  
29              
30             $VERSION = "1.64";
31              
32             # For a constant mapping, there is no FITS header, just a generic
33             # header that is constant.
34             my %CONST_MAP = (
35             DETECTOR_READ_TYPE => "NDSTARE",
36             GAIN => 7.5,
37             INSTRUMENT => "ClassicCam",
38             NSCAN_POSITIONS => 1,
39             NUMBER_OF_EXPOSURES => 1,
40             OBSERVATION_MODE => 'imaging',
41             ROTATION => 0, # assume good alignment for now
42             );
43              
44             # NULL mappings used to override base-class implementations
45             my @NULL_MAP = qw/ /;
46              
47             # Unit mapping implies that the value propogates directly
48             # to the output with only a keyword name change.
49             my %UNIT_MAP = (
50             AIRMASS_END => "AIRMASS",
51             DEC_TELESCOPE_OFFSET => "DSECS",
52             EQUINOX => "EQUINOX",
53             EXPOSURE_TIME => "EXPTIME",
54             FILTER => "FILTER",
55             OBJECT => "OBJECT",
56             OBSERVATION_NUMBER => "IRPICNO",
57             RA_TELESCOPE_OFFSET => "ASECS",
58             SPEED_GAIN => "SPEED",
59             X_DIM => "NAXIS1",
60             Y_DIM => "NAXIS2"
61             );
62              
63              
64             # Create the translation methods.
65             __PACKAGE__->_generate_lookup_methods( \%CONST_MAP, \%UNIT_MAP, \@NULL_MAP );
66              
67             =head1 METHODS
68              
69             =over 4
70              
71             =item B<this_instrument>
72              
73             The name of the instrument required to match (case insensitively)
74             against the C<CHIP> keyword to allow this class to translate the
75             specified headers. Called by the default C<can_translate> method.
76              
77             $inst = $class->this_instrument();
78              
79             Returns "ClassicCam".
80              
81             =cut
82              
83             sub this_instrument {
84 0     0 1 0 return qr/^ClassicCam/i;
85             }
86              
87             =item B<can_translate>
88              
89             Returns true if the supplied headers can be handled by this class.
90              
91             $cando = $class->can_translate( \%hdrs );
92              
93             For this class, in the absence of an C<INSTRUME> keyword, the method
94             will return true if the C<CHIP> header exists and matches 'C-CAM'.
95             It is not clear how robust this is, or over what range of epochs this
96             keyword was written.
97              
98             =cut
99              
100             sub can_translate {
101 20     20 1 60 my $self = shift;
102 20         51 my $headers = shift;
103              
104 20 50 33     129 if ( exists $headers->{CHIP} &&
      33        
105             defined $headers->{CHIP} &&
106             $headers->{CHIP} =~ /^C-CAM/i ) {
107 0         0 return 1;
108             } else {
109 20         399 return 0;
110             }
111             }
112              
113             =back
114              
115             =head1 COMPLEX CONVERSIONS
116              
117             These methods are more complicated than a simple mapping. We have to
118             provide both from- and to-FITS conversions. All these routines are
119             methods and the to_ routines all take a reference to a hash and return
120             the translated value (a many-to-one mapping). The from_ methods take a
121             reference to a generic hash and return a translated hash (sometimes
122             these are many-to-many).
123              
124             =over 4
125              
126             =item B<to_AIRMASS_START>
127              
128             Sets a default airmass at the start of the observation if the C<AIRMASS>
129             keyword is not defined.
130              
131             =cut
132              
133             sub to_AIRMASS_START {
134 0     0 1   my $self = shift;
135 0           my $FITS_headers = shift;
136 0           my $airmass = 1.0;
137 0 0         if ( defined( $FITS_headers->{AIRMASS} ) ) {
138 0           $airmass = $FITS_headers->{AIRMASS};
139             }
140 0           return $airmass;
141             }
142              
143             =item B<to_DEC_BASE>
144              
145             Converts the base declination from sexagesimal d:m:s to decimal
146             degrees using the C<DEC> keyword, defaulting to 0.0.
147              
148             =cut
149              
150             sub to_DEC_BASE {
151 0     0 1   my $self = shift;
152 0           my $FITS_headers = shift;
153 0           my $dec = 0.0;
154 0           my $sexa = $FITS_headers->{"DEC"};
155 0 0         if ( defined( $sexa ) ) {
156 0           $dec = $self->dms_to_degrees( $sexa );
157             }
158 0           return $dec;
159             }
160              
161             =item B<to_DEC_SCALE>
162              
163             Sets the declination scale in arcseconds per pixel. It has a
164             fixed absolute value of 0.115 arcsec/pixel, but its sign depends on
165             the declination. The scale increases with pixel index, i.e. has north
166             to the top, for declinations south of -29 degrees. It is flipped
167             north of -29 degrees. The default scale assumes north is up.
168              
169             =cut
170              
171             sub to_DEC_SCALE {
172 0     0 1   my $self = shift;
173 0           my $FITS_headers = shift;
174 0           my $scale = 0.115;
175              
176             # Find the declination, converting from sexagesimal d:m:s.
177 0           my $sexa = $FITS_headers->{"DEC"};
178 0 0         if ( defined( $sexa ) ) {
179 0           my $dec = $self->dms_to_degrees( $sexa );
180 0 0         if ( $dec > -29 ) {
181 0           $scale *= -1;
182             }
183             }
184 0           return $scale;
185             }
186              
187             =item B<to_DR_RECIPE>
188              
189             Returns the data-reduction recipe name. The selection depends on the
190             value of the C<OBJECT> keyword. The default is "QUICK_LOOK". A dark
191             returns "REDUCE_DARK", a sky flat "SKY_FLAT_MASKED", a dome flat
192             "SKY_FLAT", and an object's recipe is "JITTER_SELF_FLAT".
193              
194             =cut
195              
196             sub to_DR_RECIPE {
197 0     0 1   my $self = shift;
198 0           my $FITS_headers = shift;
199 0           my $type = "OBJECT";
200 0           my $recipe = "QUICK_LOOK";
201 0 0         if ( defined $FITS_headers->{OBJECT} ) {
202 0           my $object = uc( $FITS_headers->{OBJECT} );
203 0 0         if ( $object eq "DARK" ) {
    0          
    0          
204 0           $recipe = "REDUCE_DARK";
205             } elsif ( $object =~ /SKY*FLAT/ ) {
206 0           $recipe = "SKY_FLAT_MASKED";
207             } elsif ( $object =~ /DOME*FLAT/ ) {
208 0           $recipe = "SKY_FLAT";
209             } else {
210 0           $recipe = "JITTER_SELF_FLAT";
211             }
212             }
213 0           return $recipe;
214             }
215              
216             =item B<to_NUMBER_OF_OFFSETS>
217              
218             Stores the number of offsets using the UKIRT convention, i.e. adding
219             one to the number of dither positions, and a default dither pattern of
220             5.
221              
222             =cut
223              
224             sub to_NUMBER_OF_OFFSETS {
225 0     0 1   my $self = shift;
226 0           my $FITS_headers = shift;
227              
228             # Allow for the UKIRT convention of the final offset to 0,0, and a
229             # default dither pattern of 5.
230 0           my $noffsets = 6;
231              
232             # The number of group members appears to be given by keyword NOFFSETS.
233 0 0         if ( defined $FITS_headers->{NOFFSETS} ) {
234 0           $noffsets = $FITS_headers->{NOFFSETS};
235             }
236 0           return $noffsets;
237             }
238              
239             =item B<to_NUMBER_OF_READS>
240              
241             Stores the number of reads of the detector, with a default of 2, from
242             the sum of keywords C<REASDS_EP> and C<PRE_EP>.
243              
244             =cut
245              
246             sub to_NUMBER_OF_READS {
247 0     0 1   my $self = shift;
248 0           my $FITS_headers = shift;
249 0           my $reads = 2;
250 0 0 0       if ( defined $FITS_headers->{READS_EP} && $FITS_headers->{PRE_EP} ) {
251 0           $reads = $FITS_headers->{READS_EP} + $FITS_headers->{PRE_EP};
252             }
253 0           return $reads;
254             }
255              
256             =item B<to_OBSERVATION_TYPE>
257              
258             Determines the observation type from the C<OBJECT> keyword provided it is
259             "DARK" for a dark frame, or "FLAT" for a flat-field frame. All other
260             values of C<OBJECT> return a value of "OBJECT", i.e. of a source.
261              
262             =cut
263              
264             sub to_OBSERVATION_TYPE {
265 0     0 1   my $self = shift;
266 0           my $FITS_headers = shift;
267 0           my $type = "OBJECT";
268 0 0         if ( defined $FITS_headers->{OBJECT} ) {
269 0           my $object = uc( $FITS_headers->{OBJECT} );
270 0 0         if ( $object eq "DARK" ) {
    0          
271 0           $type = $object;
272             } elsif ( $object =~ /FLAT/ ) {
273 0           $type = "FLAT";
274             }
275             }
276 0           return $type;
277             }
278              
279             =item B<to_RA_BASE>
280              
281             Converts the base right ascension from sexagesimal h:m:s to decimal degrees
282             using the C<RA> keyword, defaulting to 0.0.
283              
284             =cut
285              
286             sub to_RA_BASE {
287 0     0 1   my $self = shift;
288 0           my $FITS_headers = shift;
289 0           my $ra = 0.0;
290 0           my $sexa = $FITS_headers->{"RA"};
291 0 0         if ( defined( $sexa ) ) {
292 0           $ra = $self->hms_to_degrees( $sexa );
293             }
294 0           return $ra;
295             }
296              
297             =item B<to_RA_SCALE>
298              
299             Sets the right-ascension scale in arcseconds per pixel. It has a
300             fixed absolute value of 0.115 arcsec/pixel, but its sign depends on
301             the declination. The scale increases with pixel index, i.e. has east
302             to the right, for declinations south of -29 degrees. It is flipped
303             north of -29 degrees. The default scale assumes east is to the right.
304              
305             =cut
306              
307             sub to_RA_SCALE {
308 0     0 1   my $self = shift;
309 0           my $FITS_headers = shift;
310 0           my $scale = 0.115;
311 0           my $sexa = $FITS_headers->{"DEC"};
312 0 0         if ( defined( $sexa ) ) {
313 0           my $dec = $self->dms_to_degrees( $sexa );
314 0 0         if ( $dec > -29 ) {
315 0           $scale *= -1;
316             }
317             }
318 0           return $scale;
319             }
320              
321             =item B<to_UTDATE>
322              
323             Returns the UT date as C<Time::Piece> object. It copes with non-standard
324             format in C<DATE-OBS>.
325              
326             =cut
327              
328             sub to_UTDATE {
329 0     0 1   my $self = shift;
330 0           my $FITS_headers = shift;
331              
332             # Guessing the format is ddmmmyy, which is not supported by
333             # Time::DateParse, so parse it.
334 0           return $self->get_UT_date( $FITS_headers );
335             }
336              
337             =item B<to_UTEND>
338              
339             Returns the UT time of the end of the observation as a C<Time::Piece> object.
340              
341             =cut
342              
343             # UT header gives end of observation in HH:MM:SS format.
344             sub to_UTEND {
345 0     0 1   my $self = shift;
346 0           my $FITS_headers = shift;
347              
348             # Get the UTDATE in YYYYMMDD format.
349 0           my $ymd = $self->to_UTDATE( $FITS_headers );
350             my $iso = sprintf( "%04d-%02d-%02dT%s",
351             substr( $ymd, 0, 4 ),
352             substr( $ymd, 4, 2 ),
353             substr( $ymd, 6, 2 ),
354 0           $FITS_headers->{UT} );
355 0           return $self->_parse_iso_date( $iso );
356             }
357              
358             =item B<from_UTEND>
359              
360             Returns the UT time of the end of the observation in HH:MM:SS format
361             and stores it in the C<UTEND> keyword.
362              
363             =cut
364              
365             sub from_UTEND {
366 0     0 1   my $self = shift;
367 0           my $generic_headers = shift;
368 0           my $utend = $generic_headers->{"UTEND"};
369 0 0         if (defined $utend) {
370 0           return $utend->strftime("%T");
371             }
372 0           return;
373             }
374              
375             =item B<to_UTSTART>
376              
377             Returns an estimated UT time of the start of the observation as a
378             C<Time::Piece> object. The start time is derived from the end time,
379             less the C<EXPTIME> exposure time and some allowance for the read time.
380              
381             =cut
382              
383             sub to_UTSTART {
384 0     0 1   my $self = shift;
385 0           my $FITS_headers = shift;
386              
387 0           my $utend = $self->to_UTEND( $FITS_headers );
388              
389 0           my $nreads = $self->to_NUMBER_OF_READS( $FITS_headers );
390 0           my $speed = $self->get_speed_sec( $FITS_headers );
391 0 0         if ( defined $FITS_headers->{EXPTIME} ) {
392 0           my $offset = -1 * ( $FITS_headers->{EXPTIME} + $speed * $nreads );
393 0           $utend = $self->_add_seconds( $utend, $offset );
394             }
395 0           return $utend;
396             }
397              
398             =item B<to_X_LOWER_BOUND>
399              
400             Returns the lower bound along the X-axis of the area of the detector
401             as a pixel index.
402              
403             =cut
404              
405             sub to_X_LOWER_BOUND {
406 0     0 1   my $self = shift;
407 0           my $FITS_headers = shift;
408 0           my @bounds = $self->quad_bounds( $FITS_headers );
409 0           return $bounds[ 0 ];
410             }
411              
412             =item B<to_X_REFERENCE_PIXEL>
413              
414             Specifies the X-axis reference pixel near the frame centre.
415              
416             =cut
417              
418             sub to_X_REFERENCE_PIXEL {
419 0     0 1   my $self = shift;
420 0           my $FITS_headers = shift;
421 0           my @bounds = $self->quad_bounds( $FITS_headers );
422 0           return int( ( $bounds[ 0 ] + $bounds[ 2 ] ) / 2 ) + 1;
423             }
424              
425             =item B<to_X_UPPER_BOUND>
426              
427             Returns the upper bound along the X-axis of the area of the detector
428             as a pixel index.
429              
430             =cut
431              
432             sub to_X_UPPER_BOUND {
433 0     0 1   my $self = shift;
434 0           my $FITS_headers = shift;
435 0           my @bounds = $self->quad_bounds( $FITS_headers );
436 0           return $bounds[ 2 ];
437             }
438              
439             =item B<to_Y_LOWER_BOUND>
440              
441             Returns the lower bound along the Y-axis of the area of the detector
442             as a pixel index.
443              
444             =cut
445              
446             sub to_Y_LOWER_BOUND {
447 0     0 1   my $self = shift;
448 0           my $FITS_headers = shift;
449              
450             # Find the pixel bounds of the quadrant or whole detector.
451 0           my @bounds = $self->quad_bounds( $FITS_headers );
452 0           return $bounds[ 1 ];
453             }
454              
455             =item B<to_Y_REFERENCE_PIXEL>
456              
457             Specifies the Y-axis reference pixel near the frame centre.
458              
459             =cut
460              
461             sub to_Y_REFERENCE_PIXEL {
462 0     0 1   my $self = shift;
463 0           my $FITS_headers = shift;
464 0           my @bounds = $self->quad_bounds($FITS_headers);
465 0           return int( ( $bounds[ 1 ] + $bounds[ 3 ] ) / 2 ) + 1;
466             }
467              
468             =item B<to_Y_UPPER_BOUND>
469              
470             Returns the upper bound along the Y-axis of the area of the detector
471             as a pixel index.
472              
473             =cut
474              
475             sub to_Y_UPPER_BOUND {
476 0     0 1   my $self = shift;
477 0           my $FITS_headers = shift;
478              
479             # Find the pixel bounds of the quadrant or whole detector.
480 0           my @bounds = $self->quad_bounds( $FITS_headers );
481 0           return $bounds[ 3 ];
482             }
483              
484             =back
485              
486             =cut
487              
488             # Supplementary methods for the translations
489             # ------------------------------------------
490              
491             =head1 HELPER ROUTINES
492              
493             These are ClassicCam-specific helper routines.
494              
495             =over 4
496              
497             =item B<dms_to_degrees>
498              
499             Converts a sky angle specified in d:m:s format into decimal degrees.
500             The argument is the sexagesimal-format angle.
501              
502             =cut
503              
504             sub dms_to_degrees {
505 0     0 1   my $self = shift;
506 0           my $sexa = shift;
507 0           my $dms;
508 0 0         if ( defined( $sexa ) ) {
509 0           my @pos = split( /:/, $sexa );
510 0           $dms = $pos[ 0 ] + $pos[ 1 ] / 60.0 + $pos [ 2 ] / 3600.;
511             }
512 0           return $dms;
513             }
514              
515             =item B<get_speed_sec>
516              
517             Returns the detector speed in seconds. It uses the C<SPEED> to derive a
518             time in decimal seconds. The default is 0.743.
519              
520             =cut
521              
522             sub get_speed_sec {
523 0     0 1   my $self = shift;
524 0           my $FITS_headers = shift;
525 0           my $speed = 0.743;
526 0 0         if ( exists $FITS_headers->{SPEED} ) {
527 0           my $s_speed = $FITS_headers->{SPEED};
528 0 0         $speed = 2.01 if ( $s_speed eq "2.0s" );
529 0 0         $speed = 1.005 if ( $s_speed eq "1.0s" );
530 0 0         $speed = 0.743 if ( $s_speed eq "743ms" );
531 0 0         $speed = 0.405 if ( $s_speed eq "405ms" );
532             }
533 0           return $speed;
534             }
535              
536             =item B<get_UT_date>
537              
538             Returns the UT date in YYYYMMDD format. It parses the non-standard
539             ddMmmyy C<DATE-OBS> keyword.
540              
541             =cut
542              
543             sub get_UT_date {
544 0     0 1   my $self = shift;
545 0           my $FITS_headers = shift;
546 0           my @months = qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec );
547 0           my $junk = $FITS_headers->{"DATE-OBS"};
548 0           my $day = substr( $junk, 0, 2 );
549 0           my $smonth = substr( $junk, 2, 3 );
550 0           my $mindex = 0;
551 0   0       while ( $mindex < 11 && uc( $smonth ) ne uc( $months[ $mindex ] ) ) {
552 0           $mindex++;
553             }
554 0           $mindex++;
555 0           my $month = "0" x ( 2 - length( $mindex ) ) . $mindex;
556 0           my $year = substr( $junk, 5, 2 );
557 0 0         if ( $year > 90 ) {
558 0           $year += 1900;
559             } else {
560 0           $year += 2000;
561             }
562 0           return join "", $year, $month, $day;
563             }
564              
565             =item B<get_UT_hours>
566              
567             Returns the UT time of the end of observation in decimal hours from
568             the C<UT> keyeword.
569              
570             =cut
571              
572             sub get_UT_hours {
573 0     0 1   my $self = shift;
574 0           my $FITS_headers = shift;
575 0 0 0       if ( exists $FITS_headers->{UT} && $FITS_headers->{UT} =~ /:/ ) {
576 0           my ($hour, $minute, $second) = split( /:/, $FITS_headers->{UT} );
577 0           return $hour + ($minute / 60) + ($second / 3600);
578             } else {
579 0           return $FITS_headers->{UT};
580             }
581             }
582              
583             =item B<hms_to_degrees>
584              
585             Converts a sky angle specified in h:m:s format into decimal degrees.
586             It takes no account of latitude. The argument is the sexagesimal
587             format angle.
588              
589             =cut
590              
591             sub hms_to_degrees {
592 0     0 1   my $self = shift;
593 0           my $sexa = shift;
594 0           my $hms;
595 0 0         if ( defined( $sexa ) ) {
596 0           my @pos = split( /:/, $sexa );
597 0           $hms = 15.0 * ( $pos[ 0 ] + $pos[ 1 ] / 60.0 + $pos [ 2 ] / 3600. );
598             }
599 0           return $hms;
600             }
601              
602             =item B<quad_bounds>
603              
604             Returns the detector bounds in pixels of the region of the detector
605             used. The region will be one of the four quadrants or the full
606             detector. We guess for the moment that keword C<QUAD> values of
607             1, 2, 3, 4 correspond to lower-left, lower-right, upper-left,
608             upper-right quadrants respectively, and 5 is the whole
609             256x256-pixel array.
610              
611             =cut
612              
613             sub quad_bounds {
614 0     0 1   my $self = shift;
615 0           my $FITS_headers = shift;
616 0           my @bounds = ( 1, 1, 256, 256 );
617 0           my $quad = $FITS_headers->{"QUAD"};
618 0 0         if ( defined( $quad ) ) {
619 0 0         if ( $quad < 5 ) {
620 0           $bounds[ 0 ] += 128 * ( $quad + 1 ) % 2;
621 0           $bounds[ 2 ] -= 128 * $quad % 2;
622 0 0         if ( $quad > 2 ) {
623 0           $bounds[ 1 ] += 128;
624             } else {
625 0           $bounds[ 3 ]-= 128;
626             }
627             }
628             }
629 0           return @bounds;
630             }
631              
632             =back
633              
634             =head1 SEE ALSO
635              
636             C<Astro::FITS::HdrTrans>, C<Astro::FITS::HdrTrans::UKIRT>.
637              
638             =head1 AUTHOR
639              
640             Malcolm J. Currie E<lt>mjc@jach.hawaii.eduE<gt>
641             Tim Jenness E<lt>t.jenness@jach.hawaii.eduE<gt>.
642              
643             =head1 COPYRIGHT
644              
645             Copyright (C) 2008 Science and Technology Facilities Council.
646             Copyright (C) 1998-2007 Particle Physics and Astronomy Research Council.
647             All Rights Reserved.
648              
649             This program is free software; you can redistribute it and/or modify it under
650             the terms of the GNU General Public License as published by the Free Software
651             Foundation; either Version 2 of the License, or (at your option) any later
652             version.
653              
654             This program is distributed in the hope that it will be useful,but WITHOUT ANY
655             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
656             PARTICULAR PURPOSE. See the GNU General Public License for more details.
657              
658             You should have received a copy of the GNU General Public License along with
659             this program; if not, write to the Free Software Foundation, Inc., 59 Temple
660             Place, Suite 330, Boston, MA 02111-1307, USA.
661              
662             =cut
663              
664             1;