File Coverage

blib/lib/Astro/FITS/HdrTrans/ESO.pm
Criterion Covered Total %
statement 20 266 7.5
branch 0 94 0.0
condition 0 24 0.0
subroutine 7 48 14.5
pod 4 41 9.7
total 31 473 6.5


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3              
4             Astro::FITS::HdrTrans::ESO - Base class for translation of ESO instruments
5              
6             =head1 SYNOPSIS
7              
8             use Astro::FITS::HdrTrans::ESO;
9              
10             =head1 DESCRIPTION
11              
12             This class provides a generic set of translations that are common to
13             instrumentation from the European Southern Observatory. It should not be used
14             directly for translation of instrument FITS headers.
15              
16             =cut
17              
18             use 5.006;
19 13     13   21588778 use warnings;
  13         42  
20 13     13   70 use strict;
  13         23  
  13         348  
21 13     13   59 use Carp;
  13         45  
  13         250  
22 13     13   52  
  13         28  
  13         815  
23             # Inherit from the Base translation class and not HdrTrans itself
24             # (which is just a class-less wrapper).
25              
26             use base qw/ Astro::FITS::HdrTrans::FITS /;
27 13     13   75  
  13         27  
  13         2520  
28             use Astro::FITS::HdrTrans::FITS;
29 13     13   72  
  13         22  
  13         71  
30             use vars qw/ $VERSION /;
31 13     13   50  
  13         22  
  13         24775  
32             $VERSION = "1.65";
33              
34             # in each class we have three sets of data.
35             # - constant mappings
36             # - unit mappings
37             # - complex mappings
38              
39             # For a constant mapping, there is no FITS header, just a generic
40             # header that is constant.
41             my %CONST_MAP = (
42             SCAN_INCREMENT => 1,
43             NSCAN_POSITIONS => 1,
44             );
45              
46             # Unit mapping implies that the value propagates directly
47             # to the output with only a keyword name change.
48              
49             my %UNIT_MAP = (
50             DEC_SCALE => "CDELT1",
51             RA_SCALE => "CDELT2",
52              
53             # then the spectroscopy...
54             SLIT_NAME => "HIERARCH.ESO.INS.OPTI1.ID",
55             X_DIM => "HIERARCH.ESO.DET.WIN.NX",
56             Y_DIM => "HIERARCH.ESO.DET.WIN.NY",
57              
58             # then the general.
59             CHOP_ANGLE => "HIERARCH.ESO.SEQ.CHOP.POSANGLE",
60             CHOP_THROW => "HIERARCH.ESO.SEQ.CHOP.THROW",
61             EXPOSURE_TIME => "EXPTIME",
62             NUMBER_OF_EXPOSURES => "HIERARCH.ESO.DET.NDIT",
63             NUMBER_OF_READS => "HIERARCH.ESO.DET.NCORRS",
64             OBSERVATION_NUMBER => "OBSNUM",
65             );
66              
67             # Create the translation methods.
68             __PACKAGE__->_generate_lookup_methods( \%CONST_MAP, \%UNIT_MAP );
69              
70             =head1 COMPLEX CONVERSIONS
71              
72             These methods are more complicated than a simple mapping. We have to
73             provide both from- and to-FITS conversions All these routines are
74             methods and the to_ routines all take a reference to a hash and return
75             the translated value (a many-to-one mapping) The from_ methods take a
76             reference to a generic hash and return a translated hash (sometimes
77             these are many-to-many)
78              
79             =over 4
80              
81             =cut
82              
83             my $self = shift;
84             my $FITS_headers = shift;
85 0     0 0   my $end_airmass = 1.0;
86 0           if ( exists $FITS_headers->{"HIERARCH.ESO.TEL.AIRM.END"} ) {
87 0           $end_airmass = $FITS_headers->{"HIERARCH.ESO.TEL.AIRM.END"};
88 0 0         } elsif ( exists $FITS_headers->{AIRMASS} ) {
    0          
89 0           $end_airmass = $FITS_headers->{AIRMASS};
90             }
91 0           return $end_airmass;
92             }
93 0            
94             my $self = shift;
95             my $generic_headers = shift;
96             "HIERARCH.ESO.TEL.AIRM.END", $generic_headers->{ "AIRMASS_END" };
97 0     0 0   }
98 0            
99 0           my $self = shift;
100             my $FITS_headers = shift;
101             my $start_airmass = 1.0;
102             if ( exists $FITS_headers->{"HIERARCH.ESO.TEL.AIRM.START"} ) {
103 0     0 0   $start_airmass = $FITS_headers->{"HIERARCH.ESO.TEL.AIRM.START"};
104 0           } elsif ( exists $FITS_headers->{AIRMASS} ) {
105 0           $start_airmass = $FITS_headers->{AIRMASS};
106 0 0         }
    0          
107 0           return $start_airmass;
108             }
109 0            
110             my $self = shift;
111 0           my $generic_headers = shift;
112             "HIERARCH.ESO.TEL.AIRM.START", $generic_headers->{ "AIRMASS_START" };
113             }
114              
115 0     0 0   my $self = shift;
116 0           my $FITS_headers = shift;
117 0           my $instindex = 0;
118             if ( exists $FITS_headers->{"HIERARCH.ESO.INS.GRAT.ENC"} ) {
119             $instindex = $FITS_headers->{"HIERARCH.ESO.INS.GRAT.ENC"};
120             }
121 0     0 0   return $instindex;
122 0           }
123 0            
124 0 0         my $self = shift;
125 0           my $FITS_headers = shift;
126             my $dec = 0.0;
127 0           if ( exists ( $FITS_headers->{DEC} ) ) {
128             $dec = $FITS_headers->{DEC};
129             }
130             $dec = defined( $dec ) ? $dec: 0.0;
131 0     0 0   return $dec;
132 0           }
133 0            
134 0 0         # This is guesswork at present. It's rather tied to the UKIRT names
135 0           # and we need generic names or use instrument-specific values in
136             # instrument-specific primitives, and pass the actual value for the
137 0 0         # night log. Could do with separate CHOPPING, BIAS booleans
138 0           # to indicate whether or not chopping is enabled and whether or not the
139             # detector mode needs a bias removed, like UKIRT's STARE mode.
140             my $self = shift;
141             my $FITS_headers = shift;
142             my $read_type;
143             my $chop = $FITS_headers->{"HIERARCH.ESO.TEL.CHOP.ST"};
144             $chop = defined( $chop ) ? $chop : 0;
145             my $detector_mode = exists( $FITS_headers->{"HIERARCH.ESO.DET.MODE.NAME"} ) ?
146             $FITS_headers->{"HIERARCH.ESO.DET.MODE.NAME"} : "NDSTARE";
147             if ( $detector_mode =~ /Uncorr/ ) {
148 0     0 0   if ( $chop ) {
149 0           $read_type = "CHOP";
150 0           } else {
151 0           $read_type = "STARE";
152 0 0         }
153             } else {
154 0 0         if ( $chop ) {
155 0 0         $read_type = "NDCHOP";
156 0 0         } else {
157 0           $read_type = "NDSTARE";
158             }
159 0           }
160             return $read_type;
161             }
162 0 0          
163 0           # Equinox may be absent for calibrations such as darks.
164             my $self = shift;
165 0           my $FITS_headers = shift;
166             my $equinox = 0;
167             if ( exists $FITS_headers->{EQUINOX} ) {
168 0           $equinox = $FITS_headers->{EQUINOX};
169             }
170             return $equinox;
171             }
172              
173 0     0 0   my $self = shift;
174 0           my $FITS_headers = shift;
175 0           my $name = "UNKNOWN";
176 0 0         if ( exists $FITS_headers->{"HIERARCH.ESO.INS.GRAT.NAME"} ) {
177 0           $name = $FITS_headers->{"HIERARCH.ESO.INS.GRAT.NAME"};
178             }
179 0           return $name;
180             }
181              
182             my $self = shift;
183 0     0 0   my $FITS_headers = shift;
184 0           my $order = 1;
185 0           if ( exists $FITS_headers->{"HIERARCH.ESO.INS.GRAT.ORDER"} ) {
186 0 0         $order = $FITS_headers->{"HIERARCH.ESO.INS.GRAT.ORDER"};
187 0           }
188             return $order;
189 0           }
190              
191             my $self = shift;
192             my $FITS_headers = shift;
193 0     0 0   my $wavelength = 0;
194 0           if ( exists $FITS_headers->{"HIERARCH.ESO.INS.GRAT.WLEN"} ) {
195 0           $wavelength = $FITS_headers->{"HIERARCH.ESO.INS.GRAT.WLEN"};
196 0 0         }
197 0           return $wavelength;
198             }
199 0            
200             my $self = shift;
201             my $FITS_headers = shift;
202             return $FITS_headers->{"HIERARCH.ESO.TPL.NEXP"} + 1;
203 0     0 0   }
204 0            
205 0           my $self = shift;
206 0 0         my $generic_headers = shift;
207 0           "HIERARCH.ESO.TPL.NEXP", $generic_headers->{ "NUMBER_OF_OFFSETS" } - 1;
208             }
209 0            
210             my $self = shift;
211             my $FITS_headers = shift;
212             return $self->get_instrument_mode($FITS_headers);
213 0     0 0   }
214 0            
215 0           my $self = shift;
216             my $generic_headers = shift;
217             "HIERARCH.ESO.DPR.TECH", $generic_headers->{ "OBSERVATION_MODE" };
218             }
219 0     0 0    
220 0           # OBJECT, SKY, and DARK need no change.
221 0           my $self = shift;
222             my $FITS_headers = shift;
223             my $type = $FITS_headers->{"HIERARCH.ESO.DPR.TYPE"};
224             $type = exists( $FITS_headers->{"HIERARCH.ESO.DPR.TYPE"} ) ? uc( $FITS_headers->{"HIERARCH.ESO.DPR.TYPE"} ) : "OBJECT";
225 0     0 0   if ( $type eq "STD" ) {
226 0           $type = "OBJECT";
227 0           } elsif ( $type eq "SKY,FLAT" || $type eq "FLAT,SKY" ) {
228             $type = "SKY";
229             } elsif ( $type eq "LAMP,FLAT" || $type eq "FLAT,LAMP" ) {
230             $type = "LAMP";
231 0     0 0   } elsif ( $type eq "LAMP" || $type eq "WAVE,LAMP" ) {
232 0           $type = "ARC";
233 0           }
234             return $type;
235             }
236              
237             my $self = shift;
238 0     0 0   my $generic_headers = shift;
239 0           "HIERARCH.ESO.DPR.TYPE", $generic_headers->{ "OBSERVATION_TYPE" };
240 0           }
241 0 0          
242 0 0 0       # Cater for OBJECT keyword with unhelpful value.
    0 0        
    0 0        
    0          
243 0           my $self = shift;
244             my $FITS_headers = shift;
245 0           my $object = undef;
246              
247 0           # The object name should be in OBJECT...
248             if ( exists $FITS_headers->{OBJECT} ) {
249 0           $object = $FITS_headers->{OBJECT};
250              
251 0           # Sometimes it's the generic STD for standard.
252             if ( $object =~ /STD/ ) {
253             if ( exists $FITS_headers->{"HIERARCH.ESO.OBS.TARG.NAME"} ) {
254             $object = $FITS_headers->{"HIERARCH.ESO.OBS.TARG.NAME"};
255 0     0 0   } else {
256 0           $object = undef;
257 0           }
258             }
259             }
260             return $object;
261             }
262 0     0 0    
263 0           my $self = shift;
264 0           my $FITS_headers = shift;
265             my $ra = 0.0;
266             if ( exists ( $FITS_headers->{RA} ) ) {
267 0 0         $ra = $FITS_headers->{RA};
268 0           }
269             $ra = defined( $ra ) ? $ra: 0.0;
270             return $ra;
271 0 0         }
272 0 0          
273 0           =item B<to_ROTATION>
274              
275 0           Derives the rotation angle from the rotation matrix.
276              
277             =cut
278              
279 0           my $self = shift;
280             my $FITS_headers = shift;
281             return $self->rotation( $FITS_headers );
282             }
283 0     0 0    
284 0           my $self = shift;
285 0           my $FITS_headers = shift;
286 0 0         my $slitangle = 0.0;
287 0           if ( exists $FITS_headers->{"HIERARCH.ESO.ADA.POSANG"} ) {
288             $slitangle = $FITS_headers->{"HIERARCH.ESO.ADA.POSANG"};
289 0 0         }
290 0           return $slitangle;
291             }
292              
293             my $self = shift;
294             my $generic_headers = shift;
295             "HIERARCH.ESO.ADA.POSANG", $generic_headers->{ "SLIT_ANGLE" };
296             }
297              
298             my $self = shift;
299             my $FITS_headers = shift;
300 0     0 1   my $standard = 0;
301 0           my $type = $FITS_headers->{"HIERARCH.ESO.DPR.TYPE"};
302 0           if ( uc( $type ) =~ /STD/ ) {
303             $standard = 1;
304             }
305             return $standard;
306 0     0 0   }
307 0            
308 0           my $self = shift;
309 0 0         my $generic_headers = shift;
310 0           "STANDARD", $generic_headers->{ "STANDARD" };
311             }
312 0            
313             my $self = shift;
314             my $FITS_headers = shift;
315             return $self->get_UT_date( $FITS_headers );
316 0     0 0   }
317 0            
318 0           my $self = shift;
319             my $FITS_headers = shift;
320              
321             # Obtain the start time.
322 0     0 0   my $start = $self->to_UTSTART( $FITS_headers );
323 0            
324 0           # Approximate end time.
325 0           return $self->_add_seconds( $start, $FITS_headers->{EXPTIME} );
326 0 0         }
327 0            
328             my $self = shift;
329 0           my $FITS_headers = shift;
330             my $return;
331             if ( exists $FITS_headers->{'DATE-OBS'} ) {
332             $return = $self->_parse_iso_date( $FITS_headers->{'DATE-OBS'} );
333 0     0 0   } elsif (exists $FITS_headers->{UTC}) {
334 0            
335 0           # Converts the UT date in YYYYMMDD format obytained from the headers to a
336             # date object at midnight. Then add the seconds past midnight to the
337             # object.
338             my $base = $self->to_UTDATE( $FITS_headers );
339 0     0 1   my $basedate = $self->_utdate_to_object( $base );
340 0           $return = $self->_add_seconds( $basedate, $FITS_headers->{UTC} );
341 0            
342             } elsif ( exists( $FITS_headers->{"HIERARCH.ESO.OBS.START"} ) ) {
343              
344             # Use the backup of the observation start header, which is encoded in
345 0     0 1   # FITS data format, i.e. yyyy-mm-ddThh:mm:ss.
346 0           $return = $self->_parse_iso_date( $FITS_headers->{"HIERARCH.ESO.OBS.START"});
347             }
348             return $return;
349 0           }
350              
351             my $self = shift;
352 0           my $FITS_headers = shift;
353             my $polangle = 0.0;
354             if ( exists $FITS_headers->{"HIERARCH.ESO.ADA.POSANG"} ) {
355             $polangle = $FITS_headers->{"HIERARCH.ESO.ADA.POSANG"};
356 0     0 1   } elsif ( exists $FITS_headers->{"HIERARCH.ESO.SEQ.ROT.OFFANGLE"} ) {
357 0           $polangle = $FITS_headers->{"HIERARCH.ESO.SEQ.ROT.OFFANGLE"};
358 0           } elsif ( exists $FITS_headers->{CROTA1} ) {
359 0 0         $polangle = abs( $FITS_headers->{CROTA1} );
    0          
    0          
360 0           }
361             return $polangle;
362             }
363              
364             my $self = shift;
365             my $generic_headers = shift;
366 0           "HIERARCH.ESO.ADA.POSANG", $generic_headers->{ "WAVEPLATE_ANGLE" };
367 0           }
368 0            
369             # Use the nominal reference pixel if correctly supplied, failing that
370             # take the average of the bounds, and if these headers are also absent,
371             # use a default which assumes the full array.
372             my $self = shift;
373             my $FITS_headers = shift;
374 0           my $xref;
375             if ( exists $FITS_headers->{CRPIX1} ) {
376 0           $xref = $FITS_headers->{CRPIX1};
377             } elsif ( exists $FITS_headers->{"HIERARCH.ESO.DET.WIN.STARTX"} && exists $FITS_headers->{"HIERARCH.ESO.DET.WIN.NX"} ) {
378             my $xl = $FITS_headers->{"HIERARCH.ESO.DET.WIN.STARTX"};
379             my $xu = $FITS_headers->{"HIERARCH.ESO.DET.WIN.NX"};
380 0     0 0   $xref = $self->nint( ( $xl + $xu ) / 2 );
381 0           } else {
382 0           $xref = 504;
383 0 0         }
    0          
    0          
384 0           return $xref;
385             }
386 0            
387             my $self = shift;
388 0           my $FITS_headers = shift;
389             return $self->nint( $FITS_headers->{"HIERARCH.ESO.DET.WIN.STARTX"} );
390 0           }
391              
392             my $self = shift;
393             my $generic_headers = shift;
394 0     0 0   "CRPIX1", $generic_headers->{"X_REFERENCE_PIXEL"};
395 0           }
396 0            
397             my $self = shift;
398             my $FITS_headers = shift;
399             return $FITS_headers->{"HIERARCH.ESO.DET.WIN.STARTX"} - 1 + $FITS_headers->{"HIERARCH.ESO.DET.WIN.NX"};
400             }
401              
402             my $self = shift;
403 0     0 0   my $FITS_headers = shift;
404 0           return $self->nint( $FITS_headers->{"HIERARCH.ESO.DET.WIN.STARTY"} );
405 0           }
406 0 0 0        
    0          
407 0           # Use the nominal reference pixel if correctly supplied, failing that
408             # take the average of the bounds, and if these headers are also absent,
409 0           # use a default which assumes the full array.
410 0           my $self = shift;
411 0           my $FITS_headers = shift;
412             my $yref;
413 0           if ( exists $FITS_headers->{CRPIX2} ) {
414             $yref = $FITS_headers->{CRPIX2};
415 0           } elsif ( exists $FITS_headers->{"HIERARCH.ESO.DET.WIN.STARTY"} && exists $FITS_headers->{"HIERARCH.ESO.DET.WIN.NY"} ) {
416             my $yl = $FITS_headers->{"HIERARCH.ESO.DET.WIN.STARTY"};
417             my $yu = $FITS_headers->{"HIERARCH.ESO.DET.WIN.NY"};
418             $yref = $self->nint( ( $yl + $yu ) / 2 );
419 0     0 0   } else {
420 0           $yref = 491;
421 0           }
422             return $yref;
423             }
424              
425 0     0 0   my $self = shift;
426 0           my $generic_headers = shift;
427 0           "CRPIX2", $generic_headers->{"Y_REFERENCE_PIXEL"};
428             }
429              
430             my $self = shift;
431 0     0 0   my $FITS_headers = shift;
432 0           return $FITS_headers->{"HIERARCH.ESO.DET.WIN.STARTY"} - 1 + $FITS_headers->{"HIERARCH.ESO.DET.WIN.NY"};
433 0           }
434              
435             # Supplementary methods for the translations
436             # ------------------------------------------
437 0     0 0    
438 0           # Get the observation mode.
439 0           my $self = shift;
440             my $FITS_headers = shift;
441             my $mode = uc( $FITS_headers->{"HIERARCH.ESO.DPR.TECH"} );
442             if ( $mode eq "IMAGE" || $mode eq "POLARIMETRY" ) {
443             $mode = "imaging";
444             } elsif ( $mode =~ /SPECTRUM/ ) {
445             $mode = "spectroscopy";
446 0     0 0   }
447 0           return $mode;
448 0           }
449 0 0 0        
    0          
450 0           # Returns the UT date in YYYYMMDD format.
451             my $self = shift;
452 0           my $FITS_headers = shift;
453 0            
454 0           # This is UT start and time.
455             my $dateobs = $FITS_headers->{"DATE-OBS"};
456 0            
457             # Extract out the data in yyyymmdd format.
458 0           return substr( $dateobs, 0, 4 ) . substr( $dateobs, 5, 2 ) . substr( $dateobs, 8, 2 )
459             }
460              
461             # Returns the UT time of start of observation in decimal hours.
462 0     0 0   my $self = shift;
463 0           my $FITS_headers = shift;
464 0            
465             # This is approximate. UTC is time in seconds.
466             my $startsec = 0.0;
467             if ( exists ( $FITS_headers->{UTC} ) ) {
468 0     0 0   $startsec = $FITS_headers->{UTC};
469 0            
470 0           # Use the backup of the observation start header, which is encoded in
471             # FITS data format, i.e. yyyy-mm-ddThh:mm:ss. So convert ot seconds.
472             } elsif ( exists( $FITS_headers->{"HIERARCH.ESO.OBS.START"} ) ) {
473             my $t = $FITS_headers->{"HIERARCH.ESO.OBS.START"};
474             $startsec = substr( $t, 11, 2 ) * 3600.0 +
475             substr( $t, 14, 2 ) * 60.0 + substr( $t, 17, 2 );
476             }
477              
478 0     0 0   # Convert from seconds to decimal hours.
479 0           return $startsec / 3600.0;
480 0           }
481 0 0 0        
    0          
482 0           my $self = shift;
483             my $FITS_headers = shift;
484 0           my $rotangle;
485              
486 0           # Define degrees-to-radians conversion.
487             my $dtor = atan2( 1, 1 ) / 45.0;
488              
489             # The PC matrix first.
490             if ( exists $FITS_headers->{PC001001} ) {
491 0     0 0   my $pc11 = $FITS_headers->{PC001001};
492 0           my $pc21 = $FITS_headers->{PC002001};
493             $rotangle = $dtor * atan2( -$pc21 / $dtor, $pc11 / $dtor );
494              
495 0           # Instead try CD matrix. Testing for existence of first column should
496             # be adequate.
497             } elsif ( exists $FITS_headers->{CD1_1} && exists $FITS_headers->{CD2_1}) {
498 0            
499             my $cd11 = $FITS_headers->{CD1_1};
500             my $cd12 = $FITS_headers->{CD1_2};
501             my $cd21 = $FITS_headers->{CD2_1};
502             my $cd22 = $FITS_headers->{CD2_2};
503 0     0 0   my $sgn;
504 0           if ( ( $cd11 * $cd22 - $cd12 * $cd21 ) < 0 ) {
505             $sgn = -1;
506             } else {
507 0           $sgn = 1;
508 0 0         }
    0          
509 0           my $cdelt1 = $sgn * sqrt( $cd11**2 + $cd21**2 );
510             my $sgn2;
511             if ( $cdelt1 < 0 ) {
512             $sgn2 = -1;
513             } else {
514 0           $sgn2 = 1;
515 0           }
516             $rotangle = atan2( -$cd21 * $dtor, $sgn2 * $cd11 * $dtor ) / $dtor;
517              
518             # Orientation may be encapsulated in the slit position angle for
519             # spectroscopy.
520 0           } else {
521             if ( uc( $self->get_instrument_mode($FITS_headers) ) eq "SPECTROSCOPY" &&
522             exists $FITS_headers->{"HIERARCH.ESO.ADA.POSANG"} ) {
523             $rotangle = $FITS_headers->{"HIERARCH.ESO.ADA.POSANG"};
524 0     0 0   } else {
525 0           $rotangle = 180.0;
526 0           }
527             }
528             return $rotangle;
529 0           }
530              
531             =back
532 0 0 0        
    0          
533 0           =head1 SEE ALSO
534 0            
535 0           C<Astro::FITS::HdrTrans>, C<Astro::FITS::HdrTrans::Base>.
536              
537             =head1 AUTHOR
538              
539             Malcolm J. Currie E<lt>mjc@star.rl.ac.ukE<gt>
540             Tim Jenness E<lt>t.jenness@jach.hawaii.eduE<gt>
541 0            
542 0           =head1 COPYRIGHT
543 0            
544 0           Copyright (C) 2007-2008 Science and Technology Facilities Council.
545 0           Copyright (C) 2006-2007 Particle Physics and Astronomy Research Council.
546 0 0         All Rights Reserved.
547 0            
548             This program is free software; you can redistribute it and/or modify
549 0           it under the terms of the GNU General Public License as published by
550             the Free Software Foundation; either Version 2 of the License, or (at
551 0           your option) any later version.
552 0            
553 0 0         This program is distributed in the hope that it will be useful,but
554 0           WITHOUT ANY WARRANTY; without even the implied warranty of
555             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
556 0           General Public License for more details.
557              
558 0           You should have received a copy of the GNU General Public License
559             along with this program; if not, write to the Free Software
560             Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
561             USA.
562              
563 0 0 0       =cut
564              
565 0           1;