File Coverage

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