File Coverage

blib/lib/Astro/FITS/HdrTrans/JCMT.pm
Criterion Covered Total %
statement 157 171 91.8
branch 41 62 66.1
condition 19 36 52.7
subroutine 23 23 100.0
pod 14 14 100.0
total 254 306 83.0


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Astro::FITS::HdrTrans::JCMT - class combining common behaviour for modern JCMT instruments
4              
5             =cut
6              
7              
8             use strict;
9 13     13   31172254 use warnings;
  13         27  
  13         337  
10 13     13   67  
  13         23  
  13         411  
11             use Astro::Coords;
12 13     13   2019 use Astro::Telescope;
  13         1493380  
  13         248  
13 13     13   2043 use DateTime;
  13         11344  
  13         219  
14 13     13   54 use DateTime::TimeZone;
  13         52  
  13         227  
15 13     13   59  
  13         37  
  13         414  
16             our $VERSION = '1.65';
17              
18             use base qw/ Astro::FITS::HdrTrans::JAC /;
19 13     13   61  
  13         37  
  13         4764  
20             # Unit mapping implies that the value propogates directly
21             # to the output with only a keyword name change.
22             my %UNIT_MAP =
23             (
24             AIRMASS_START => 'AMSTART',
25             AZIMUTH_START => 'AZSTART',
26             ELEVATION_START => 'ELSTART',
27             FILENAME => 'FILE_ID',
28             DR_RECIPE => "RECIPE",
29             HUMIDITY => 'HUMSTART',
30             LATITUDE => 'LAT-OBS',
31             LONGITUDE => 'LONG-OBS',
32             OBJECT => 'OBJECT',
33             OBSERVATION_NUMBER => 'OBSNUM',
34             PROJECT => 'PROJECT',
35             SCAN_PATTERN => 'SCAN_PAT',
36             STANDARD => 'STANDARD',
37             TAI_UTC_CORRECTION => 'DTAI',
38             UT1_UTC_CORRECTION => 'DUT1',
39             WIND_BLIND => 'WND_BLND',
40             X_APERTURE => 'INSTAP_X',
41             Y_APERTURE => 'INSTAP_Y',
42             );
43              
44             my %CONST_MAP = ();
45              
46             # Create the translation methods
47             __PACKAGE__->_generate_lookup_methods( \%CONST_MAP, \%UNIT_MAP );
48              
49             our $COORDS;
50              
51             =head1 METHODS
52              
53             =over 4
54              
55             =item B<translate_from_FITS>
56              
57             This routine overrides the base class implementation to enable the
58             caches to be cleared for target location.
59              
60             This means that some conversion methods (in particular those using time in
61             a base class) may not work properly outside the context of a full translation
62             unless they have been subclassed locally.
63              
64             Date fixups are handled in a super class.
65              
66             =cut
67              
68             my $class = shift;
69             my $headers = shift;
70 4     4 1 9  
71 4         8 # clear cache
72             $COORDS = undef;
73              
74 4         41 # Go to the base class
75             return $class->SUPER::translate_from_FITS( $headers, @_ );
76             }
77 4         32  
78             =item B<to_UTDATE>
79              
80             Converts the date in a date-obs header into a number of form YYYYMMDD.
81              
82             =cut
83              
84             my $class = shift;
85             my $FITS_headers = shift;
86              
87 4     4 1 6 $class->_fix_dates( $FITS_headers );
88 4         6 return $class->SUPER::to_UTDATE( $FITS_headers, @_ );
89             }
90 4         12  
91 4         17 =item B<to_UTEND>
92              
93             Converts UT date in a date-end header into C<Time::Piece> object
94              
95             =cut
96              
97             my $class = shift;
98             my $FITS_headers = shift;
99              
100             $class->_fix_dates( $FITS_headers );
101 5     5 1 12 return $class->SUPER::to_UTEND( $FITS_headers, @_ );
102 5         8 }
103              
104 5         19 =item B<to_UTSTART>
105 5         27  
106             Converts UT date in a date-obs header into C<Time::Piece> object.
107              
108             =cut
109              
110             my $class = shift;
111             my $FITS_headers = shift;
112              
113             $class->_fix_dates( $FITS_headers );
114             return $class->SUPER::to_UTSTART( $FITS_headers, @_ );
115 9     9 1 16 }
116 9         14  
117             =item B<to_RA_BASE>
118 9         25  
119 9         33 Uses the elevation, azimuth, telescope name, and observation start
120             time headers (ELSTART, AZSTART, TELESCOP, and DATE-OBS headers,
121             respectively) to calculate the base RA.
122              
123             Returns the RA in degrees.
124              
125             =cut
126              
127             my $self = shift;
128             my $FITS_headers = shift;
129              
130             my $coords = $self->_calc_coords( $FITS_headers );
131             return undef unless defined $coords;
132             return $coords->ra( format => 'deg' );
133 4     4 1 10 }
134 4         8  
135             =item B<to_DEC_BASE>
136 4         16  
137 4 50       30 Uses the elevation, azimuth, telescope name, and observation start
138 4         29 time headers (ELSTART, AZSTART, TELESCOP, and DATE-OBS headers,
139             respectively) to calculate the base declination.
140              
141             Returns the declination in degrees.
142              
143             =cut
144              
145             my $self = shift;
146             my $FITS_headers = shift;
147              
148             my $coords = $self->_calc_coords( $FITS_headers );
149              
150             return undef unless defined $coords;
151             return $coords->dec( format => 'deg' );
152 4     4 1 9 }
153 4         9  
154             =item B<to_TAU>
155 4         46  
156             Use the average WVM tau measurements.
157 4 50       13  
158 4         26 =cut
159              
160             my $self = shift;
161             my $FITS_headers = shift;
162              
163             my $tau = 0.0;
164             for my $src (qw/ TAU225 WVMTAU /) {
165             my $st = $src . "ST";
166             my $en = $src . "EN";
167              
168 4     4 1 8 my @startvals = $self->via_subheader_undef_check( $FITS_headers, $st );
169 4         10 my @endvals = $self->via_subheader_undef_check( $FITS_headers, $en );
170             my $startval = $startvals[0];
171 4         8 my $endval = $endvals[-1];
172 4         10  
173 6         16 if (defined $startval && defined $endval) {
174 6         11 $tau = ($startval + $endval) / 2;
175             last;
176 6         12 } elsif (defined $startval) {
177 6         19 $tau = $startval;
178 6         12 } elsif (defined $endval) {
179 6         8 $tau = $endval;
180             }
181 6 100 66     31 }
    50          
    50          
182 4         19 return $tau;
183 4         11 }
184              
185 0         0 =item B<to_SEEING>
186              
187 0         0 Use the average seeing measurements.
188              
189             =cut
190 4         9  
191             my $self = shift;
192             my $FITS_headers = shift;
193              
194             my $seeing = 0.0;
195              
196              
197             my @startvals = $self->via_subheader_undef_check( $FITS_headers, "SEEINGST" );
198             my @endvals = $self->via_subheader_undef_check( $FITS_headers, "SEEINGEN" );
199             my $startval = $startvals[0];
200 4     4 1 9 my $endval = $endvals[-1];
201 4         7  
202             if (defined $startval && defined $endval) {
203 4         6 $seeing = ($startval + $endval) / 2;
204             } elsif (defined $startval) {
205             $seeing = $startval;
206 4         15 } elsif (defined $endval) {
207 4         13 $seeing = $endval;
208 4         8 }
209 4         8  
210             return $seeing;
211 4 100 66     25 }
    50          
    50          
212 3         15  
213              
214 0         0  
215              
216 0         0 =item B<to_OBSERVATION_ID_SUBSYSTEM>
217              
218             Returns the subsystem observation IDs associated with the header.
219 4         10 Returns a reference to an array. Will be empty if the OBSIDSS header
220             is missing.
221              
222             =cut
223              
224             my $self = shift;
225             my $FITS_headers = shift;
226             # Try multiple headers since the database is different to the file
227             my @obsidss;
228             for my $h (qw/ OBSIDSS OBSID_SUBSYSNR /) {
229             my @found = $self->via_subheader( $FITS_headers, $h );
230             if (@found) {
231             @obsidss = @found;
232             last;
233             }
234 4     4 1 9 }
235 4         16 my @all;
236             if (@obsidss) {
237 4         6 # Remove duplicates
238 4         10 my %seen;
239 5         16 @all = grep { ! $seen{$_}++ } @obsidss;
240 5 100       15 }
241 3         9 return \@all;
242 3         8 }
243              
244             =item B<to_SUBSYSTEM_IDKEY>
245 4         9  
246 4 100       17 =cut
247              
248 3         8 my $self = shift;
249 3         6 my $FITS_headers = shift;
  3         17  
250              
251 4         12 for my $try ( qw/ OBSIDSS OBSID_SUBSYSNR / ) {
252             my @results = $self->via_subheader( $FITS_headers, $try );
253             return $try if @results;
254             }
255             return;
256             }
257              
258             =item B<to_DOME_OPEN>
259 4     4 1 11  
260 4         7 Uses the roof and door status at start and end of observation headers
261             to generate a combined value which, if true, confirms that the dome
262 4         10 was fully open throughout. (Unless it closed and reopened during
263 5         16 the observation.)
264 5 100       19  
265             =cut
266 1         2  
267             my $self = shift;
268             my $FITS_headers = shift;
269              
270             my ($n_open, $n_closed, $n_other) = (0, 0, 0);
271              
272             foreach my $header (qw/DOORSTST DOORSTEN ROOFSTST ROOFSTEN/) {
273             foreach my $value ($self->via_subheader($FITS_headers, $header)) {
274             if ($value =~ /^open$/i) {
275             $n_open ++;
276             }
277             elsif ($value =~ /^closed$/i) {
278             $n_closed ++;
279 4     4 1 20 }
280 4         10 else {
281             $n_other ++;
282 4         9 }
283             }
284 4         11 }
285 16         34  
286 12 100       45 if ($n_open and not ($n_closed or $n_other)) {
    50          
287 4         8 return 1;
288             }
289              
290 8         14 if ($n_closed and not ($n_open or $n_other)) {
291             return 0;
292             }
293 0         0  
294             return undef;
295             }
296              
297             =item B<from_DOME_OPEN>
298 4 100 33     29  
      66        
299 1         4 Converts the DOME_OPEN value back to individual roof and door
300             status headers.
301              
302 3 100 33     20 =cut
      66        
303 2         5  
304             my $self = shift;
305             my $generic_headers = shift;
306 1         3  
307             my $value = undef;
308              
309             if (exists $generic_headers->{'DOME_OPEN'}) {
310             my $dome = $generic_headers->{'DOME_OPEN'};
311             if (defined $dome) {
312             $value = $dome ? 'Open' : 'Closed';
313             }
314             }
315              
316             return map {$_ => $value} qw/DOORSTST DOORSTEN ROOFSTST ROOFSTEN/;
317 2     2 1 4 }
318 2         5  
319             =item B<to_REMOTE>
320 2         4  
321             Convert between the JCMT's OPER_LOC header and a standardised 'REMOTE value'.
322 2 100       7  
323 1         3 REMOTE = 1
324 1 50       5 LOCAL = 0
325 1 50       4  
326             If not defined or has a different value, return 'undef'
327             =cut
328              
329 2         6 my $self = shift;
  8         33  
330             my $FITS_headers = shift;
331             my $remote;
332             if (exists( $FITS_headers->{'REMOTE'})) {
333             $remote = $FITS_headers->{'REMOTE'};
334             } else {
335             $remote = ''
336             }
337             if (uc($remote) =~ /REMOTE/) {
338             $remote = 1;
339             } elsif (uc($remote) =~ /LOCAL/) {
340             $remote = 0;
341             } else {
342             $remote = undef;
343 4     4 1 8 }
344 4         8  
345 4         8 return $remote;
346 4 50       12 }
347 0         0  
348              
349 4         39 =item B<from_REMOTE>
350              
351 4 50       22 Converts the REMOTE value back to the OPER_LOC header
    50          
352 0         0 if REMOTE=1, oper_loc='REMOTE'
353             if REMOTE=0, oper_loc='LOCAL'
354 0         0 if REMOTE is anything else, return undef;
355              
356 4         8 =cut
357              
358             my $self = shift;
359 4         10 my $generic_headers = shift;
360              
361             my $value = undef;
362              
363             if (exists $generic_headers->{'REMOTE'}) {
364             my $remote = $generic_headers->{'REMOTE'};
365             if (defined $remote) {
366             $value = $remote ? 'REMOTE' : 'LOCAL';
367             }
368             }
369              
370             return (OPER_LOC => $value);
371             }
372              
373 2     2 1 5  
374 2         4  
375             =back
376 2         3  
377             =head1 PRIVATE METHODS
378 2 50       8  
379 0         0 =over 4
380 0 0       0  
381 0 0       0 =item B<_calc_coords>
382              
383             Function to calculate the coordinates at the start of the observation by using
384             the elevation, azimuth, telescope, and observation start time. Caches
385 2         19 the result if it's already been calculated.
386              
387             Returns an Astro::Coords object.
388              
389             =cut
390              
391             my $self = shift;
392             my $FITS_headers = shift;
393              
394             # Force dates to be standardized
395             $self->_fix_dates( $FITS_headers );
396              
397             # Here be dragons. Possibility that cache will not be cleared properly
398             # if a user comes in outside of the translate_from_FITS() method.
399             if ( defined( $COORDS ) &&
400             UNIVERSAL::isa( $COORDS, "Astro::Coords" ) ) {
401             return $COORDS;
402             }
403              
404             my $telescope = $FITS_headers->{'TELESCOP'};
405              
406             # We can try DATE-OBS and AZEL START or DATE-END and AZEL END
407 8     8   16 my ($dateobs, $az, $el);
408 8         12  
409             my @keys = ( { date => "DATE-OBS", az => "AZSTART", el => "ELSTART" },
410             { date => "DATE-END", az => "AZEND", el => "ELEND" } );
411 8         29  
412             for my $keys_to_try ( @keys ) {
413              
414             # We might have subheaders, especially for the AZEL
415 8 100 66     47 # values so we read into arrays and check them.
416              
417 4         11 my @dateobs = $self->via_subheader( $FITS_headers, $keys_to_try->{date} );
418             my @azref = $self->via_subheader( $FITS_headers, $keys_to_try->{az} );
419             my @elref = $self->via_subheader( $FITS_headers, $keys_to_try->{el} );
420 4         13  
421             # try to ensure that we use the same index everywhere
422             my $idx;
423 4         220 ($idx, $dateobs) = _middle_value(\@dateobs, $idx);
424             ($idx, $az) = _middle_value(\@azref, $idx);
425 4         27 ($idx, $el) = _middle_value(\@elref, $idx);
426              
427             # if we have a set of values we can stop looking
428 4         16 last if (defined $dateobs && defined $az && defined $el);
429             }
430              
431             # only proceed if we have a defined value
432             if (defined $dateobs && defined $telescope
433 5         20 && defined $az && defined $el ) {
434 5         17 my $coords = new Astro::Coords( az => $az,
435 5         17 el => $el,
436             units => 'degrees',
437             );
438 5         8 $coords->telescope( new Astro::Telescope( $telescope ) );
439 5         15  
440 5         11 # convert ISO date to object
441 5         18 my $dt = Astro::FITS::HdrTrans::Base->_parse_iso_date( $dateobs );
442             return unless defined $dt;
443              
444 5 100 66     52 $coords->datetime( $dt );
      66        
445              
446             $COORDS = $coords;
447             return $COORDS;
448 4 50 33     49 }
      33        
      33        
449              
450 4         34 return undef;
451             }
452              
453             =item B<_middle_value>
454 4         984  
455             Returns the value from the middle of an array reference. If that is
456             not defined we start from the beginning until we find a defined
457 4         36650 value. Return undef if we can not find anything.
458 4 50       15  
459             =cut
460 4         34  
461             my $arr = shift;
462 4         830 my $idx = shift;
463 4         30  
464             $idx = int ((scalar @$arr) / 2) unless defined $idx;
465              
466 0         0 return ($idx, $arr->[$idx]) if (defined $arr->[$idx]);
467              
468             # No luck scan them all
469             for my $idx (0..$#$arr) {
470             my $val = $arr->[$idx];
471             return ($idx, $val) if defined $val;
472             }
473             return (undef, undef);
474             }
475              
476             =back
477              
478 15     15   22 =head1 SEE ALSO
479 15         17  
480             C<Astro::FITS::HdrTrans>,
481 15 100       36 C<Astro::FITS::HdrTrans::Base>,
482             C<Astro::FITS::HdrTrans::JAC>.
483 15 100       43  
484             =head1 AUTHORS
485              
486 2         5 Anubhav E<lt>a.agarwal@jach.hawawii.eduE<gt>,
487 0         0 Brad Cavanagh E<lt>b.cavanagh@jach.hawaii.eduE<gt>,
488 0 0       0 Tim Jenness E<lt>t.jenness@jach.hawaii.eduE<gt>.
489              
490 2         4 =head1 COPYRIGHT
491              
492             Copyright (C) 2009, 2011, 2012, 2014 Science and Technology Facilities Council.
493             Copyright (C) 2016 East Asian Observatory.
494             All Rights Reserved.
495              
496             This program is free software; you can redistribute it and/or modify it under
497             the terms of the GNU General Public License as published by the Free Software
498             Foundation; either version 2 of the License, or (at your option) any later
499             version.
500              
501             This program is distributed in the hope that it will be useful,but WITHOUT ANY
502             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
503             PARTICULAR PURPOSE. See the GNU General Public License for more details.
504              
505             You should have received a copy of the GNU General Public License along with
506             this program; if not, write to the Free Software Foundation, Inc., 59 Temple
507             Place,Suite 330, Boston, MA 02111-1307, USA
508              
509             =cut
510              
511             1;