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