File Coverage

blib/lib/Astro/FITS/HdrTrans/SPEX.pm
Criterion Covered Total %
statement 15 202 7.4
branch 0 54 0.0
condition 0 18 0.0
subroutine 6 34 17.6
pod 4 29 13.7
total 25 337 7.4


line stmt bran cond sub pod time code
1             package Astro::FITS::HdrTrans::SPEX;
2              
3             =head1 NAME
4              
5             Astro::FITS::HdrTrans::SPEX - IRTF SPEX translations
6              
7             =head1 SYNOPSIS
8              
9             use Astro::FITS::HdrTrans::SPEX;
10              
11             %gen = Astro::FITS::HdrTrans::SPEX->translate_from_FITS( %hdr );
12              
13             =head1 DESCRIPTION
14              
15             This class provides a generic set of translations that are specific to
16             the SPEX camera of the IRTF.
17              
18             =cut
19              
20 10     10   45975007 use 5.006;
  10         62  
21 10     10   75 use warnings;
  10         38  
  10         6182  
22 10     10   74 use strict;
  10         25  
  10         409  
23 10     10   77 use Carp;
  10         46  
  10         1395  
24              
25             # Inherit from ESO
26 10     10   78 use base qw/ Astro::FITS::HdrTrans::FITS /;
  10         39  
  10         26652  
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             # Value in headers is too imprecise
34             DEC_SCALE => (-0.1182/3600.0),
35             DETECTOR_READ_TYPE => 'NDSTARE',
36             GAIN => 13.0,
37             OBSERVATION_MODE => 'imaging',
38             NSCAN_POSITIONS => 1,
39             # Value in headers is too imprecise
40             RA_SCALE => (-0.116/3600.0),
41             ROTATION => -1.03,
42             SPEED_GAIN => 'Normal',
43             );
44              
45             # NULL mappings used to override base class implementations
46             my @NULL_MAP = qw/ /;
47              
48             # unit mapping implies that the value propogates directly
49             # to the output with only a keyword name change
50              
51             my %UNIT_MAP = (
52             EXPOSURE_TIME => "ITIME",
53             FILTER => "GFLT",
54             OBJECT => 'OBJECT',
55             );
56              
57              
58             # Create the translation methods
59             __PACKAGE__->_generate_lookup_methods( \%CONST_MAP, \%UNIT_MAP, \@NULL_MAP );
60              
61             =head1 METHODS
62              
63             =over 4
64              
65             =item B<this_instrument>
66              
67             The name of the instrument required to match (case insensitively)
68             against the INSTRUME/INSTRUMENT keyword to allow this class to
69             translate the specified headers. Called by the default
70             C<can_translate> method.
71              
72             $inst = $class->this_instrument();
73              
74             Returns "INGRID".
75              
76             =cut
77              
78             sub this_instrument {
79 20     20 1 132 return qr/^SPEX/i;
80             }
81              
82             =back
83              
84             =head1 COMPLEX CONVERSIONS
85              
86             =over 4
87              
88             =cut
89              
90             sub to_AIRMASS_START {
91 0     0 0   my $self = shift;
92 0           my $FITS_headers = shift;
93 0           my $airmass = 1.0;
94 0 0         if ( defined( $FITS_headers->{AIRMASS} ) ) {
95 0           $airmass = $FITS_headers->{AIRMASS};
96             }
97 0           return $airmass;
98             }
99              
100             sub to_AIRMASS_END {
101 0     0 0   my $self = shift;
102 0           my $FITS_headers = shift;
103 0           my $airmass = 1.0;
104 0 0         if ( defined( $FITS_headers->{AIRMASS} ) ) {
105 0           $airmass = $FITS_headers->{AIRMASS};
106             }
107 0           return $airmass;
108             }
109              
110             sub from_AIRMASS_END {
111 0     0 0   my $self = shift;
112 0           my $generic_headers = shift;
113 0           "AMEND", $generic_headers->{ "AIRMASS_END" };
114             }
115              
116             # Convert from sexagesimal d:m:s to decimal degrees.
117             sub to_DEC_BASE {
118 0     0 0   my $self = shift;
119 0           my $FITS_headers = shift;
120 0           my $dec = 0.0;
121 0           my $sexa = $FITS_headers->{"DECBASE"};
122 0 0         if ( defined( $sexa ) ) {
123 0           $dec = $self->dms_to_degrees( $sexa );
124             }
125 0           return $dec;
126             }
127              
128             # Assume that the initial offset is 0.0, i.e. the base is the
129             # source position. This also assumes that the reference pixel
130             # is unchanged in the group, as is created in the conversion
131             # script. The other headers are measured in sexagesimal, but
132             # the offsets are in arcseconds.
133             sub to_DEC_TELESCOPE_OFFSET {
134 0     0 0   my $self = shift;
135 0           my $FITS_headers = shift;
136 0           my $offset;
137 0           my $base = $self->to_DEC_BASE($FITS_headers);
138              
139             # Convert from sexagesimal d:m:s to decimal degrees.
140 0           my $sexadec = $FITS_headers->{DEC};
141 0 0         if ( defined( $sexadec ) ) {
142 0           my $dec = $self->dms_to_degrees( $sexadec );
143              
144             # The offset is arcseconds with respect to the base position.
145 0           $offset = 3600.0 * ( $dec - $base );
146             } else {
147 0           $offset = 0.0;
148             }
149 0           return $offset;
150             }
151              
152             sub to_DR_RECIPE {
153 0     0 0   my $self = shift;
154 0           my $FITS_headers = shift;
155 0           my $recipe = "JITTER_SELF_FLAT";
156 0 0         if ( $self->to_OBSERVATION_TYPE($FITS_headers) eq "DARK" ) {
    0          
157 0           $recipe = "REDUCE_DARK";
158             } elsif ( $self->to_STANDARD($FITS_headers) ) {
159 0           $recipe = "JITTER_SELF_FLAT_APHOT";
160             }
161 0           return $recipe;
162             }
163              
164              
165             sub to_NUMBER_OF_EXPOSURES {
166 0     0 0   my $self = shift;
167 0           my $FITS_headers = shift;
168 0           my $coadds = 1;
169 0 0         if ( defined $FITS_headers->{CO_ADDS} ) {
170 0           $coadds = $FITS_headers->{CO_ADDS};
171             }
172              
173             }
174              
175             sub to_NUMBER_OF_OFFSETS {
176 0     0 0   my $self = shift;
177 0           my $FITS_headers = shift;
178              
179             # Allow for the UKIRT convention of the final offset to 0,0, and a
180             # default dither pattern of 5.
181 0           my $noffsets = 6;
182              
183             # The number of gripu members appears to be given by keyword LOOP.
184 0 0         if ( defined $FITS_headers->{NOFFSETS} ) {
185 0           $noffsets = $FITS_headers->{NOFFSETS};
186             }
187              
188 0           return $noffsets;
189             }
190              
191             sub to_OBSERVATION_TYPE {
192 0     0 0   my $self = shift;
193 0           my $FITS_headers = shift;
194 0           my $type = "OBJECT";
195 0 0 0       if ( defined $FITS_headers->{OBJECT} && defined $FITS_headers->{GFLT}) {
196 0           my $object = uc( $FITS_headers->{OBJECT} );
197 0           my $filter = uc( $FITS_headers->{GFLT} );
198 0 0         if ( $filter =~ /blank/i ) {
    0          
199 0           $type = "DARK";
200             } elsif ( $object =~ /flat/i ) {
201 0           $type = "FLAT";
202             }
203             }
204 0           return $type;
205             }
206              
207             # Convert from sexagesimal h:m:s to decimal degrees then to decimal
208             # hours.
209             sub to_RA_BASE {
210 0     0 0   my $self = shift;
211 0           my $FITS_headers = shift;
212 0           my $ra = 0.0;
213 0           my $sexa = $FITS_headers->{"RABASE"};
214 0 0         if ( defined( $sexa ) ) {
215 0           $ra = $self->hms_to_degrees( $sexa );
216             }
217 0           return $ra;
218             }
219              
220             # Assume that the initial offset is 0.0, i.e. the base is the
221             # source position. This also assumes that the reference pixel
222             # is unchanged in the group, as is created in the conversion
223             # script. The other headers are measured in sexagesimal, but
224             # the offsets are in arcseconds.
225             sub to_RA_TELESCOPE_OFFSET {
226 0     0 0   my $self = shift;
227 0           my $FITS_headers = shift;
228 0           my $offset;
229              
230             # Base RA is in degrees.
231 0           my $base = $self->to_RA_BASE($FITS_headers);
232              
233             # Convert from sexagesimal right ascension h:m:s and declination
234             # d:m:s to decimal degrees.
235 0           my $sexara = $FITS_headers->{RA};
236 0           my $sexadec = $FITS_headers->{DEC};
237 0 0 0       if ( defined( $base ) && defined( $sexara ) && defined( $sexadec ) ) {
      0        
238 0           my $dec = $self->dms_to_degrees( $sexadec );
239 0           my $ra = $self->hms_to_degrees( $sexara );
240              
241             # The offset is arcseconds with respect to the base position.
242 0           $offset = 3600.0 * ( $ra - $base ) * $self->cosdeg( $dec );
243             } else {
244 0           $offset = 0.0;
245             }
246 0           return $offset;
247             }
248              
249             # Take a pragmatic way of defining a standard. Not perfect, but
250             # should suffice unitl we know all the names.
251             sub to_STANDARD {
252 0     0 0   my $self = shift;
253 0           my $FITS_headers = shift;
254 0           my $standard = 0;
255 0           my $object = $FITS_headers->{"OBJECT"};
256 0 0 0       if ( defined( $object ) && $object =~ /^FS/ ) {
257 0           $standard = 1;
258             }
259 0           return $standard;
260             }
261              
262             # Allow for multiple occurences of the date, the first being valid and
263             # the second is blank.
264             sub to_UTDATE {
265 0     0 1   my $self = shift;
266 0           my $FITS_headers = shift;
267 0           my $utdate;
268 0 0         if ( exists $FITS_headers->{"DATE-OBS"} ) {
    0          
269 0           $utdate = $FITS_headers->{"DATE-OBS"};
270              
271             # This is a kludge to work with old data which has multiple values of
272             # the DATE keyword with the last value being blank (these were early
273             # SPEX data). Return the first value, since the last value can be
274             # blank.
275 0 0         if ( ref( $utdate ) eq 'ARRAY' ) {
276 0           $utdate = $utdate->[0];
277             }
278             } elsif (exists $FITS_headers->{'DATE_OBS'}) {
279 0           $utdate = $FITS_headers->{'DATE_OBS'};
280             }
281 0 0         $utdate =~ s/-//g if $utdate;
282 0           return $utdate;
283             }
284              
285             # Derive from the start time, plus the exposure time and some
286             # allowance for the read time taken from
287             # http://irtfweb.ifa.hawaii.edu/~spex
288             # http://irtfweb.ifa.hawaii.edu/Facility/spex/work/array_params/array_params.html
289             sub to_UTEND {
290 0     0 1   my $self = shift;
291 0           my $FITS_headers = shift;
292 0           my $utend = $self->to_UTSTART($FITS_headers);
293 0 0 0       if ( defined $FITS_headers->{ITIME} && defined $FITS_headers->{NDR} ) {
294 0           $utend += ( $FITS_headers->{ITIME} * $FITS_headers->{NDR}) ;
295             }
296 0           return $utend;
297             }
298              
299             sub to_UTSTART {
300 0     0 1   my $self = shift;
301 0           my $FITS_headers = shift;
302 0           my $base = $self->to_UTDATE( $FITS_headers );
303 0 0         return unless defined $base;
304 0 0         if (exists $FITS_headers->{TIME_OBS}) {
305 0           my $ymd = substr($base,0,4). "-". substr($base,4,2)."-". substr($base,6,2);
306 0           my $iso = $ymd. "T" . $FITS_headers->{TIME_OBS};
307 0           return $self->_parse_iso_date( $iso );
308             }
309 0           return;
310             }
311              
312             sub to_X_LOWER_BOUND {
313 0     0 0   my $self = shift;
314 0           my $FITS_headers = shift;
315 0           my @bounds = $self->get_bounds($FITS_headers);
316 0           return $bounds[ 0 ];
317             }
318              
319             # Specify the reference pixel, which is normally near the frame centre.
320             sub to_X_REFERENCE_PIXEL{
321 0     0 0   my $self = shift;
322 0           my $FITS_headers = shift;
323 0           my $xref;
324              
325             # Use the average of the bounds to define the centre and dimension.
326 0           my @bounds = $self->get_bounds($FITS_headers);
327 0           my $xdim = $bounds[ 2 ] - $bounds[ 0 ] + 1;
328 0           my $xmid = $self->nint( ( $bounds[ 2 ] + $bounds[ 0 ] ) / 2 );
329              
330             # SPEX is at the centre for a sub-array along an axis but offset slightly
331             # for a sub-array to avoid the joins between the four sub-array sections
332             # of the frame. Ideally these should come through the headers...
333 0 0         if ( $xdim == 512 ) {
334 0           $xref = $xmid - 36;
335             } else {
336 0           $xref = $xmid;
337             }
338 0           return $xref;
339             }
340              
341             sub from_X_REFERENCE_PIXEL {
342 0     0 0   my $self = shift;
343 0           my $generic_headers = shift;
344 0           "CRPIX1", $generic_headers->{"X_REFERENCE_PIXEL"};
345             }
346              
347             sub to_X_UPPER_BOUND {
348 0     0 0   my $self = shift;
349 0           my $FITS_headers = shift;
350 0           my @bounds = $self->get_bounds( $FITS_headers );
351 0           return $bounds[ 2 ];
352             }
353              
354             sub to_Y_LOWER_BOUND {
355 0     0 0   my $self = shift;
356 0           my $FITS_headers = shift;
357 0           my @bounds = $self->get_bounds( $FITS_headers );
358 0           return $bounds[ 1 ];
359             }
360              
361             # Specify the reference pixel, which is normally near the frame centre.
362             sub to_Y_REFERENCE_PIXEL{
363 0     0 0   my $self = shift;
364 0           my $FITS_headers = shift;
365 0           my $yref;
366              
367             # Use the average of the bounds to define the centre and dimension.
368 0           my @bounds = $self->get_bounds($FITS_headers);
369 0           my $ydim = $bounds[ 3 ] - $bounds[ 1 ] + 1;
370 0           my $ymid = $self->nint( ( $bounds[ 3 ] + $bounds[ 1 ] ) / 2 );
371              
372             # SPEX is at the centre for a sub-array along an axis but offset slightly
373             # for a sub-array to avoid the joins between the four sub-array sections
374             # of the frame. Ideally these should come through the headers...
375 0 0         if ( $ydim == 512 ) {
376 0           $yref = $ymid - 40;
377             } else {
378 0           $yref = $ymid;
379             }
380              
381 0           return $yref;
382             }
383              
384             sub from_Y_REFERENCE_PIXEL {
385 0     0 0   my $self = shift;
386 0           my $generic_headers = shift;
387 0           "CRPIX2", $generic_headers->{"Y_REFERENCE_PIXEL"};
388             }
389              
390             sub to_Y_UPPER_BOUND {
391 0     0 0   my $self = shift;
392 0           my $FITS_headers = shift;
393 0           my @bounds = $self->get_bounds( $FITS_headers );
394 0           return $bounds[ 3 ];
395             }
396              
397             # Supplementary methods for the translations
398             # ------------------------------------------
399              
400             # Converts a sky angle specified in d:m:s format into decimal degrees.
401             # Argument is the sexagesimal format angle.
402             sub dms_to_degrees {
403 0     0 0   my $self = shift;
404 0           my $sexa = shift;
405 0           my $dms;
406 0 0         if ( defined( $sexa ) ) {
407 0           my @pos = split( /:/, $sexa );
408 0           $dms = $pos[ 0 ] + $pos[ 1 ] / 60.0 + $pos [ 2 ] / 3600.;
409             }
410 0           return $dms;
411             }
412              
413             sub get_bounds {
414 0     0 0   my $self = shift;
415 0           my $FITS_headers = shift;
416 0           my @bounds = ( 1, 1, 512, 512 );
417 0 0         if ( exists $FITS_headers->{ARRAY0} ) {
418 0           my $boundlist = $FITS_headers->{ARRAY0};
419 0           @bounds = split( ",", $boundlist );
420              
421             # Bounds count from zero.
422 0           $bounds[ 0 ]++;
423 0           $bounds[ 1 ]++;
424             }
425 0           return @bounds;
426             }
427              
428             # Returns the UT date in yyyyMMdd format.
429             sub get_UT_date {
430 0     0 0   my $self = shift;
431 0           my $FITS_headers = shift;
432 0           my $date = $FITS_headers->{"DATE-OBS"};
433 0           $date =~ s/-//g;
434 0           return $date;
435             }
436              
437             # Returns the UT time of observation in decimal hours.
438             sub get_UT_hours {
439 0     0 0   my $self = shift;
440 0           my $FITS_headers = shift;
441 0 0 0       if ( exists $FITS_headers->{"TIME-OBS"} && $FITS_headers->{"TIME-OBS"} =~ /:/ ) {
442 0           my ($hour, $minute, $second) = split( /:/, $FITS_headers->{"TIME-OBS"} );
443 0           return $hour + ($minute / 60) + ($second / 3600);
444             } else {
445 0           return $FITS_headers->{"TIME-OBS"};
446             }
447             }
448              
449             # Converts a sky angle specified in h:m:s format into decimal degrees.
450             # It takes no account of latitude. Argument is the sexagesimal format angle.
451             sub hms_to_degrees {
452 0     0 0   my $self = shift;
453 0           my $sexa = shift;
454 0           my $hms;
455 0 0         if ( defined( $sexa ) ) {
456 0           my @pos = split( /:/, $sexa );
457 0           $hms = 15.0 * ( $pos[ 0 ] + $pos[ 1 ] / 60.0 + $pos [ 2 ] / 3600. );
458             }
459 0           return $hms;
460             }
461              
462              
463             =back
464              
465             =head1 SEE ALSO
466              
467             C<Astro::FITS::HdrTrans>, C<Astro::FITS::HdrTrans::UKIRT>.
468              
469             =head1 AUTHOR
470              
471             Malcolm J. Currie E<lt>mjc@star.rl.ac.ukE<gt>,
472             Tim Jenness E<lt>t.jenness@jach.hawaii.eduE<gt>.
473              
474             =head1 COPYRIGHT
475              
476             Copyright (C) 2008 Science and Technology Facilities Council.
477             Copyright (C) 2003-2005 Particle Physics and Astronomy Research Council.
478             All Rights Reserved.
479              
480             This program is free software; you can redistribute it and/or modify it under
481             the terms of the GNU General Public License as published by the Free Software
482             Foundation; either Version 2 of the License, or (at your option) any later
483             version.
484              
485             This program is distributed in the hope that it will be useful,but WITHOUT ANY
486             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
487             PARTICULAR PURPOSE. See the GNU General Public License for more details.
488              
489             You should have received a copy of the GNU General Public License along with
490             this program; if not, write to the Free Software Foundation, Inc., 59 Temple
491             Place, Suite 330, Boston, MA 02111-1307, USA.
492              
493             =cut
494              
495             1;