File Coverage

blib/lib/Astro/FITS/HdrTrans/SCUBA.pm
Criterion Covered Total %
statement 132 157 84.0
branch 30 76 39.4
condition 18 60 30.0
subroutine 22 22 100.0
pod 16 16 100.0
total 218 331 65.8


line stmt bran cond sub pod time code
1             package Astro::FITS::HdrTrans::SCUBA;
2              
3             =head1 NAME
4              
5             Astro::FITS::HdrTrans::SCUBA - JCMT SCUBA translations
6              
7             =head1 DESCRIPTION
8              
9             Converts information contained in SCUBA FITS headers to and from
10             generic headers. See Astro::FITS::HdrTrans for a list of generic
11             headers.
12              
13             =cut
14              
15 10     10   14614984 use 5.006;
  10         48  
16 10     10   59 use warnings;
  10         19  
  10         369  
17 10     10   54 use strict;
  10         29  
  10         245  
18 10     10   49 use Carp;
  10         29  
  10         830  
19              
20             # Inherit from Base
21 10     10   82 use base qw/ Astro::FITS::HdrTrans::JAC /;
  10         47  
  10         1985  
22              
23 10     10   71 use vars qw/ $VERSION /;
  10         20  
  10         20579  
24              
25             $VERSION = "1.64";
26              
27             # for a constant mapping, there is no FITS header, just a generic
28             # header that is constant
29             my %CONST_MAP = (
30             COORDINATE_UNITS => 'sexagesimal',
31             INSTRUMENT => "SCUBA",
32             INST_DHS => 'SCUBA_SCUBA',
33             NUMBER_OF_OFFSETS => 1,
34             ROTATION => 0,
35             SLIT_ANGLE => 0,
36             SPEED_GAIN => 'normal',
37             TELESCOPE => 'JCMT',
38             );
39              
40             # NULL mappings used to override base class implementations
41             my @NULL_MAP = qw/ DETECTOR_INDEX WAVEPLATE_ANGLE /;
42              
43             # unit mapping implies that the value propogates directly
44             # to the output with only a keyword name change
45              
46             my %UNIT_MAP = (
47             AIRMASS_START => "AMSTART",
48             AIRMASS_END => "AMEND",
49             BOLOMETERS => "BOLOMS",
50             CHOP_ANGLE => "CHOP_PA",
51             CHOP_THROW => "CHOP_THR",
52             DEC_BASE => "LAT",
53             DEC_TELESCOPE_OFFSET => "MAP_Y",
54             DETECTOR_READ_TYPE => "MODE",
55             DR_RECIPE => "DRRECIPE",
56             FILENAME => "SDFFILE",
57             FILTER => "FILTER",
58             GAIN => "GAIN",
59             NUMBER_OF_EXPOSURES => "EXP_NO",
60             OBJECT => "OBJECT",
61             OBSERVATION_NUMBER => "RUN",
62             POLARIMETER => "POL_CONN",
63             PROJECT => "PROJ_ID",
64             RA_TELESCOPE_OFFSET => "MAP_X",
65             SCAN_INCREMENT => "SAM_DX",
66             SEEING => "SEEING",
67             STANDARD => "STANDARD",
68             TAU => "TAU_225",
69             X_BASE => "LONG",
70             Y_BASE => "LAT",
71             X_OFFSET => "MAP_X",
72             Y_OFFSET => "MAP_Y"
73             );
74              
75              
76             # Create the translation methods
77             __PACKAGE__->_generate_lookup_methods( \%CONST_MAP, \%UNIT_MAP, \@NULL_MAP );
78              
79             =head1 METHODS
80              
81             =over 4
82              
83             =item B<this_instrument>
84              
85             The name of the instrument required to match (case insensitively)
86             against the INSTRUME/INSTRUMENT keyword to allow this class to
87             translate the specified headers. Called by the default
88             C<can_translate> method.
89              
90             $inst = $class->this_instrument();
91              
92             Returns "SCUBA".
93              
94             =cut
95              
96             sub this_instrument {
97 21     21 1 68 return "SCUBA";
98             }
99              
100             =item B<can_translate>
101              
102             The database tables do not include an instrument field so we need to determine
103             suitability by looking at other fields instead of using the base implementation.
104              
105             $cando = $class->can_translate( \%hdrs );
106              
107             For SCUBA we first check for BOLOMS and SCU# headers and then use the base
108             implementation that will look at the INSTRUME field.
109              
110             =cut
111              
112             sub can_translate {
113 21     21 1 61 my $self = shift;
114 21         47 my $headers = shift;
115              
116 21 50 66     126 if (exists $headers->{BOLOMS} && defined $headers->{BOLOMS} &&
      66        
      33        
117             exists $headers->{"SCU#"} && defined $headers->{"SCU#"}) {
118 0         0 return 1;
119             } else {
120 21         690 return $self->SUPER::can_translate( $headers );
121             }
122             }
123              
124             =back
125              
126             =head1 COMPLEX CONVERSIONS
127              
128             These methods are more complicated than a simple mapping. We have to
129             provide both from- and to-FITS conversions All these routines are
130             methods and the to_ routines all take a reference to a hash and return
131             the translated value (a many-to-one mapping) The from_ methods take a
132             reference to a generic hash and return a translated hash (sometimes
133             these are many-to-many)
134              
135             =over 4
136              
137             =item B<to_CHOP_COORDINATE_SYSTEM>
138              
139             Uses the C<CHOP_CRD> FITS header to determine the chopper coordinate
140             system, and then places that coordinate type in the C<CHOP_COORDINATE_SYSTEM>
141             generic header.
142              
143             A FITS header value of 'LO' translates to 'Tracking', 'AZ' translates to
144             'Alt/Az', and 'NA' translates to 'Focal Plane'. Any other values will return
145             undef.
146              
147             =cut
148              
149             sub to_CHOP_COORDINATE_SYSTEM {
150 2     2 1 6 my $self = shift;
151 2         5 my $FITS_headers = shift;
152 2         5 my $return;
153              
154 2 50       8 if (exists($FITS_headers->{'CHOP_CRD'})) {
155 2         60 my $fits_eq = $FITS_headers->{'CHOP_CRD'};
156 2 50       151 if ( $fits_eq =~ /LO/i ) {
    50          
    0          
157 0         0 $return = "Tracking";
158             } elsif ( $fits_eq =~ /AZ/i ) {
159 2         7 $return = "Alt/Az";
160             } elsif ( $fits_eq =~ /NA/i ) {
161 0         0 $return = "Focal Plane";
162             }
163             }
164 2         10 return $return;
165             }
166              
167             =item B<to_COORDINATE_TYPE>
168              
169             Uses the C<CENT_CRD> FITS header to determine the coordinate type
170             (galactic, B1950, J2000) and then places that coordinate type in
171             the C<COORDINATE_TYPE> generic header.
172              
173             =cut
174              
175             sub to_COORDINATE_TYPE {
176 2     2 1 7 my $self = shift;
177 2         4 my $FITS_headers = shift;
178 2         5 my $return;
179 2 50       10 if (exists($FITS_headers->{'CENT_CRD'})) {
180 2         112 my $fits_eq = $FITS_headers->{'CENT_CRD'};
181 2 50       167 if ( $fits_eq =~ /RB/i ) {
    50          
    50          
    50          
182 0         0 $return = "B1950";
183             } elsif ( $fits_eq =~ /RJ/i ) {
184 0         0 $return = "J2000";
185             } elsif ( $fits_eq =~ /AZ/i ) {
186 0         0 $return = "galactic";
187             } elsif ( $fits_eq =~ /planet/i ) {
188 2         6 $return = "planet";
189             }
190             }
191 2         10 return $return;
192             }
193              
194             =item B<to_EQUINOX>
195              
196             Translates EQUINOX header into valid equinox value. The following
197             translation is done:
198              
199             =over 4
200              
201             =item * RB => 1950
202              
203             =item * RJ => 2000
204              
205             =item * RD => current
206              
207             =item * AZ => AZ/EL
208              
209             =back
210              
211             =cut
212              
213             sub to_EQUINOX {
214 2     2 1 6 my $self = shift;
215 2         6 my $FITS_headers = shift;
216 2         5 my $return;
217 2 50       8 if (exists($FITS_headers->{'CENT_CRD'})) {
218 2         58 my $fits_eq = $FITS_headers->{'CENT_CRD'};
219 2 50       187 if ( $fits_eq =~ /RB/i ) {
    50          
    50          
    50          
    0          
220 0         0 $return = "1950";
221             } elsif ( $fits_eq =~ /RJ/i ) {
222 0         0 $return = "2000";
223             } elsif ( $fits_eq =~ /RD/i ) {
224 0         0 $return = "current";
225             } elsif ( $fits_eq =~ /PLANET/i ) {
226 2         9 $return = "planet";
227             } elsif ( $fits_eq =~ /AZ/i ) {
228 0         0 $return = "AZ/EL";
229             }
230             }
231 2         10 return $return;
232             }
233              
234             =item B<from_EQUINOX>
235              
236             Translates generic C<EQUINOX> values into SCUBA FITS
237             equinox values for the C<CENT_CRD> header.
238              
239             =cut
240              
241             sub from_EQUINOX {
242 1     1 1 4 my $self = shift;
243 1         2 my $generic_headers = shift;
244 1         5 my %return_hash;
245             my $return;
246 1 50 33     18 if (exists($generic_headers->{EQUINOX}) &&
247             defined $generic_headers->{EQUINOX}) {
248 1         4 my $equinox = $generic_headers->{EQUINOX};
249 1 50       12 if ( $equinox =~ /1950/ ) {
    50          
    50          
    50          
    0          
250 0         0 $return = 'RB';
251             } elsif ( $equinox =~ /2000/ ) {
252 0         0 $return = 'RJ';
253             } elsif ( $equinox =~ /current/ ) {
254 0         0 $return = 'RD';
255             } elsif ( $equinox =~ /planet/ ) {
256 1         3 $return = 'PLANET';
257             } elsif ( $equinox =~ /AZ\/EL/ ) {
258 0         0 $return = 'AZ';
259             } else {
260 0         0 $return = $equinox;
261             }
262             }
263 1         5 $return_hash{'CENT_CRD'} = $return;
264 1         12 return %return_hash;
265             }
266              
267             =item B<to_OBSERVATION_MODE>
268              
269             Returns C<photometry> if the FITS header value for C<MODE>
270             is C<PHOTOM>, otherwise returns C<imaging>.
271              
272             =cut
273              
274             sub to_OBSERVATION_MODE {
275 2     2 1 9 my $self = shift;
276 2         16 my $FITS_headers = shift;
277 2         4 my $return;
278 2 50 33     13 if ( defined( $FITS_headers->{'MODE'} ) &&
279             $FITS_headers->{'MODE'} =~ /PHOTOM/i ) {
280 0         0 $return = "photometry";
281             } else {
282 2         294 $return = "imaging";
283             }
284 2         12 return $return;
285             }
286              
287             =item B<to_OBSERVATION_TYPE>
288              
289             Converts the observation type. If the FITS header is equal to
290             C<PHOTOM>, C<MAP>, C<POLPHOT>, or C<POLMAP>, then the generic
291             header value is C<OBJECT>. Else, the FITS header value is
292             copied directly to the generic header value.
293              
294             =cut
295              
296             sub to_OBSERVATION_TYPE {
297 2     2 1 6 my $self = shift;
298 2         4 my $FITS_headers = shift;
299 2         4 my $return;
300 2         9 my $mode = $FITS_headers->{'MODE'};
301 2 50 33     162 if ( defined( $mode ) && $mode =~ /PHOTOM|MAP|POLPHOT|POLMAP/i) {
302 0         0 $return = "OBJECT";
303             } else {
304 2         7 $return = $mode;
305             }
306 2         7 return $return;
307             }
308              
309             =item B<to_POLARIMETRY>
310              
311             Sets the C<POLARIMETRY> generic header to 'true' if the
312             value for the FITS header C<MODE> is 'POLMAP' or 'POLPHOT',
313             otherwise sets it to 'false'.
314              
315             =cut
316              
317             sub to_POLARIMETRY {
318 2     2 1 7 my $self = shift;
319 2         5 my $FITS_headers = shift;
320 2         4 my $return;
321 2         23 my $mode = $FITS_headers->{'MODE'};
322 2 50 33     150 if (defined( $mode ) && $mode =~ /POLMAP|POLPHOT/i) {
323 0         0 $return = 1;
324             } else {
325 2         4 $return = 0;
326             }
327 2         7 return $return;
328             }
329              
330             =item B<to_UTDATE>
331              
332             Converts either the C<UTDATE> or C<DATE> header into a C<Time::Piece> object.
333              
334             =cut
335              
336             sub to_UTDATE {
337 2     2 1 7 my $self = shift;
338 2         5 my $FITS_headers = shift;
339 2         15 my $return;
340 2 50 33     13 if ( exists( $FITS_headers->{'UTDATE'} ) &&
    0 0        
    0 0        
341             defined( $FITS_headers->{'UTDATE'} ) ) {
342 2         243 my $utdate = $FITS_headers->{'UTDATE'};
343 2         144 $return = $self->_parse_yyyymmdd_date( $utdate, ":" );
344             } elsif ( exists( $FITS_headers->{'DATE'} ) &&
345             defined( $FITS_headers->{'DATE'} ) ) {
346 0         0 my $utdate = $FITS_headers->{'DATE'};
347 0         0 $return = $self->_parse_iso_date( $utdate );
348             } elsif ( exists( $FITS_headers->{'DATE-OBS'} ) &&
349             defined( $FITS_headers->{'DATE-OBS'} ) ) {
350 0         0 my $utdate = $FITS_headers->{'DATE-OBS'};
351 0         0 $return = $self->_parse_iso_date( $utdate );
352             }
353 2 50       137 if (defined $return) {
354 2         12 $return = sprintf('%04d%02d%02d',$return->year,
355             $return->mon, $return->mday);
356             }
357 2         38 return $return;
358             }
359              
360             =item B<from_UTDATE>
361              
362             Converts UT date in C<Time::Piece> object into C<YYYY:MM:DD> format
363             for C<UTDATE> header.
364              
365             =cut
366              
367             sub from_UTDATE {
368 1     1 1 2 my $self = shift;
369 1         3 my $generic_headers = shift;
370 1         2 my %return_hash;
371 1 50       5 if (exists($generic_headers->{UTDATE}) ) {
372 1         2 my $date = $generic_headers->{UTDATE};
373 1         8 $return_hash{UTDATE} = join(':',
374             substr($date,0,4),
375             substr($date,4,2),
376             substr($date,6,2));
377             }
378 1         13 return %return_hash;
379             }
380              
381             =item B<to_UTSTART>
382              
383             Combines C<UTDATE> and C<UTSTART> into a unified C<UTSTART>
384             generic header. If those headers do not exist, uses C<DATE>.
385              
386             =cut
387              
388             sub to_UTSTART {
389 4     4 1 14 my $self = shift;
390 4         9 my $FITS_headers = shift;
391 4         8 my $return;
392 4 50 33     20 if ( exists( $FITS_headers->{'UTDATE'} ) &&
    0 33        
    0 33        
      0        
      0        
393             defined( $FITS_headers->{'UTDATE'} ) &&
394             exists $FITS_headers->{UTSTART} &&
395             defined $FITS_headers->{UTSTART} ) {
396              
397             # To convert to ISO replace colons with dashes
398 4         810 my $utdate = $FITS_headers->{UTDATE};
399 4         285 $utdate =~ s/:/\-/g;
400              
401 4         21 my $ut = $utdate . "T" . $FITS_headers->{'UTSTART'};
402 4         283 $return = $self->_parse_iso_date( $ut );
403              
404             } elsif (exists $FITS_headers->{"DATE-OBS"}) {
405             # reduced data
406 0         0 $return = $self->_parse_iso_date( $FITS_headers->{"DATE-OBS"} );
407              
408             } elsif ( exists( $FITS_headers->{'DATE'} ) &&
409             defined( $FITS_headers->{'DATE'} ) &&
410             $FITS_headers->{'DATE'} =~ /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d/ ) {
411              
412 0         0 $return = $self->_parse_iso_date( $FITS_headers->{"DATE"} );
413              
414             }
415              
416 4         19 return $return;
417             }
418              
419             =item B<from_UTSTART>
420              
421             Converts the unified C<UTSTART> generic header into C<UTDATE>
422             and C<UTSTART> FITS headers of the form C<YYYY:MM:DD> and C<HH:MM:SS>.
423              
424             =cut
425              
426             sub from_UTSTART {
427 1     1 1 3 my $self = shift;
428 1         3 my $generic_headers = shift;
429 1         1 my %return_hash;
430 1 50 33     10 if (exists($generic_headers->{UTSTART}) &&
431             UNIVERSAL::isa( $generic_headers->{UTSTART}, "Time::Piece" ) ) {
432 1         4 my $ut = $generic_headers->{UTSTART};
433 1         5 $return_hash{'UTDATE'} = join ':', $ut->year, $ut->mon, $ut->mday;
434 1         19 $return_hash{'UTSTART'} = join ':', $ut->hour, $ut->minute, $ut->second;
435 1         15 $return_hash{'DATE'} = $ut->datetime;
436             }
437 1         42 return %return_hash;
438             }
439              
440             =item B<to_UTEND>
441              
442             Converts the <UTDATE> and C<UTEND> headers into a combined
443             C<Time::Piece> object.
444              
445             =cut
446              
447             sub to_UTEND {
448 2     2 1 6 my $self = shift;
449 2         5 my $FITS_headers = shift;
450 2         4 my $return;
451              
452 2 50 33     11 if ( exists( $FITS_headers->{'UTDATE'} ) &&
    0 33        
      33        
453             defined( $FITS_headers->{'UTDATE'} ) &&
454             exists $FITS_headers->{UTEND} &&
455             defined $FITS_headers->{UTEND} ) {
456              
457             # need to replace colons with -
458 2         432 my $utdate = $FITS_headers->{"UTDATE"};
459 2         134 $utdate =~ s/:/\-/g;
460              
461 2         10 my $ut = $utdate . "T" . $FITS_headers->{'UTEND'};
462              
463 2         133 $return = $self->_parse_iso_date( $ut );
464              
465             } elsif (exists $FITS_headers->{"DATE-END"}) {
466             # reduced data
467 0         0 $return = $self->_parse_iso_date( $FITS_headers->{"DATE-END"} );
468             }
469 2         9 return $return;
470             }
471              
472             =item B<from_UTEND>
473              
474             Converts the unified C<UTEND> generic header into C<UTDATE> and
475             C<UTEND> FITS headers of the form C<YYYY:MM:DD> and C<HH:MM:SS>.
476              
477             =cut
478              
479             sub from_UTEND {
480 1     1 1 2 my $self = shift;
481 1         3 my $generic_headers = shift;
482 1         2 my %return_hash;
483 1 50 33     10 if (exists($generic_headers->{UTEND}) &&
484             UNIVERSAL::isa( $generic_headers->{UTEND}, "Time::Piece" ) ) {
485 1         17 my $ut = $generic_headers->{UTEND};
486 1         9 $generic_headers->{UTEND} =~ /(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)/;
487 1         35 $return_hash{'UTDATE'} = join ':', $ut->year, $ut->mon, $ut->mday;
488 1         16 $return_hash{'UTEND'} = join ':', $ut->hour, $ut->minute, $ut->second;
489             }
490 1         45 return %return_hash;
491             }
492              
493             =item B<to_MSBID>
494              
495             Converts the MSBID field to an MSBID. Complication is that the SCUBA
496             header and database store a blank MSBID as a single space rather than
497             an empty string and this causes difficulty in some subsystems.
498              
499             This routine replaces a single space with a null string.
500              
501             =cut
502              
503             sub to_MSBID {
504 2     2 1 7 my $self = shift;
505 2         6 my $FITS_headers = shift;
506 2         12 my $msbid = $FITS_headers->{MSBID};
507 2 50       156 $msbid =~ s/\s+$// if defined $msbid;
508 2         9 return $msbid;
509             }
510              
511             =back
512              
513             =head1 SEE ALSO
514              
515             C<Astro::FITS::HdrTrans>, C<Astro::FITS::HdrTrans::Base>
516              
517             =head1 AUTHOR
518              
519             Brad Cavanagh E<lt>b.cavanagh@jach.hawaii.eduE<gt>,
520             Tim Jenness E<lt>t.jenness@jach.hawaii.eduE<gt>
521              
522             =head1 COPYRIGHT
523              
524             Copyright (C) 2007 Science and Technology Facilities Council.
525             Copyright (C) 2003-2005 Particle Physics and Astronomy Research Council.
526             All Rights Reserved.
527              
528             This program is free software; you can redistribute it and/or modify it under
529             the terms of the GNU General Public License as published by the Free Software
530             Foundation; either version 2 of the License, or (at your option) any later
531             version.
532              
533             This program is distributed in the hope that it will be useful,but WITHOUT ANY
534             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
535             PARTICULAR PURPOSE. See the GNU General Public License for more details.
536              
537             You should have received a copy of the GNU General Public License along with
538             this program; if not, write to the Free Software Foundation, Inc., 59 Temple
539             Place,Suite 330, Boston, MA 02111-1307, USA
540              
541             =cut
542              
543             1;