File Coverage

blib/lib/Astro/FITS/HdrTrans/ESO.pm
Criterion Covered Total %
statement 17 263 6.4
branch 0 94 0.0
condition 0 24 0.0
subroutine 6 47 12.7
pod 4 41 9.7
total 27 469 5.7


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