File Coverage

blib/lib/Astro/FITS/HdrTrans/JCMT_GSD_DB.pm
Criterion Covered Total %
statement 102 139 73.3
branch 20 66 30.3
condition 19 93 20.4
subroutine 18 18 100.0
pod 9 9 100.0
total 168 325 51.6


line stmt bran cond sub pod time code
1             package Astro::FITS::HdrTrans::JCMT_GSD_DB;
2              
3             =head1 NAME
4              
5             Astro::FITS::HdrTrans::JCMT_GSD_DB - JCMT GSD Database header translations
6              
7             =head1 DESCRIPTION
8              
9             Converts information contained in JCMT heterodyne database headers
10             to and from generic headers. See Astro::FITS::HdrTrans for a list of
11             generic headers.
12              
13             =cut
14              
15 10     10   21120649 use 5.006;
  10         56  
16 10     10   67 use warnings;
  10         30  
  10         361  
17 10     10   65 use strict;
  10         21  
  10         277  
18 10     10   54 use Carp;
  10         29  
  10         894  
19              
20 10     10   956 use Time::Piece;
  10         13605  
  10         64  
21              
22             # Inherit from Base
23 10     10   818 use base qw/ Astro::FITS::HdrTrans::Base /;
  10         24  
  10         1980  
24              
25 10     10   73 use vars qw/ $VERSION /;
  10         22  
  10         17834  
26              
27             $VERSION = "1.63";
28              
29             # for a constant mapping, there is no FITS header, just a generic
30             # header that is constant
31             my %CONST_MAP = (
32             INST_DHS => 'HET_GSD',
33             COORDINATE_UNITS => 'decimal',
34             EQUINOX => 'current',
35             TELESCOPE => 'JCMT',
36             );
37              
38             # NULL mappings used to override base class implementations
39             my @NULL_MAP = ();
40              
41             # unit mapping implies that the value propogates directly
42             # to the output with only a keyword name change
43              
44             my %UNIT_MAP = (
45             AMBIENT_TEMPERATURE => "TAMB",
46             APERTURE => "APERTURE",
47             AZIMUTH_START => "AZ",
48             BACKEND => "BACKEND",
49             BACKEND_SECTIONS => "NORSECT",
50             CHOP_FREQUENCY => "CHOPFREQ",
51             CHOP_THROW => "CHOPTHRW",
52             COORDINATE_SYSTEM => "COORDCD",
53             # COORDINATE_TYPE => "C4LSC",
54             CYCLE_LENGTH => "CYCLLEN",
55             # DEC_BASE => "",
56             ELEVATION_START => "EL",
57             FILENAME => "GSDFILE",
58             FILTER => "FILTER",
59             FREQUENCY_RESOLUTION => "FREQRES",
60             FRONTEND => "FRONTEND",
61             HUMIDITY => "HUMIDITY",
62             NUMBER_OF_CYCLES => "NOCYCLES",
63             NUMBER_OF_SUBSCANS => "NOSCANS",
64             OBJECT => "OBJECT",
65             OBSERVATION_MODE => "OBSMODE",
66             OBSERVATION_NUMBER => "SCAN",
67             PROJECT => "PROJID",
68             # RA_BASE => "C4RADATE",
69             RECEIVER_TEMPERATURE => "TRX",
70             ROTATION => "YPOSANG",
71             REST_FREQUENCY => "RESTFRQ1",
72             SEEING => "PHA",
73             SWITCH_MODE => "SWMODE",
74             SYSTEM_TEMPERATURE => "STSYS",
75             TAU => "TAU",
76             USER_AZ_CORRECTION => "UXPNT",
77             USER_EL_CORRECTION => "UYPNT",
78             VELOCITY => "VELOCITY",
79             VELOCITY_REFERENCE_FRAME => "VREF",
80             VELOCITY_TYPE => "VDEF",
81             X_BASE => "XREF",
82             Y_BASE => "YREF",
83             X_DIM => "NOXPTS",
84             Y_DIM => "NOYPTS",
85             X_REQUESTED => "XSOURCE",
86             Y_REQUESTED => "YSOURCE",
87             X_SCALE => "DELTAX",
88             Y_SCALE => "DELTAY",
89             );
90              
91             # Create the translation methods
92             __PACKAGE__->_generate_lookup_methods( \%CONST_MAP, \%UNIT_MAP, \@NULL_MAP );
93              
94             =head1 METHODS
95              
96             =over 4
97              
98             =item B<can_translate>
99              
100             Returns true if the supplied headers can be handled by this class.
101              
102             $cando = $class->can_translate( \%hdrs );
103              
104             For this class, the method will return true if the "GSDFILE" header
105             exists and the "SCA#" header exist.
106              
107             =cut
108              
109             sub can_translate {
110 20     20 1 48 my $self = shift;
111 20         47 my $headers = shift;
112              
113 20 50 66     101 if (exists $headers->{GSDFILE} && exists $headers->{"SCA#"}) {
114 1         4 return 1;
115             }
116 19         284 return 0;
117             }
118              
119             =back
120              
121             =head1 COMPLEX CONVERSIONS
122              
123             These methods are more complicated than a simple mapping. We have to
124             provide both from- and to-FITS conversions All these routines are
125             methods and the to_ routines all take a reference to a hash and return
126             the translated value (a many-to-one mapping) The from_ methods take a
127             reference to a generic hash and return a translated hash (sometimes
128             these are many-to-many)
129              
130             =over 4
131              
132             =item B<to_INSTRUMENT>
133              
134             Sets the C<INSTRUMENT> generic header. For RxA3i, sets the value
135             to RXA3. For RxB, sets the value to RXB3.
136              
137             =cut
138              
139             sub to_INSTRUMENT {
140 1     1 1 2 my $self = shift;
141 1         2 my $FITS_headers = shift;
142 1         3 my $return;
143              
144 1 50       15 if ( exists( $FITS_headers->{'FRONTEND'} ) ) {
145 1         5 $return = $FITS_headers->{'FRONTEND'};
146 1 50       16 if ( $return =~ /^rxa3/i ) {
    50          
147 0         0 $return = "RXA3";
148             } elsif ( $return =~ /^rxb/i ) {
149 1         4 $return = "RXB3";
150             }
151             }
152 1         5 return $return;
153             }
154              
155             =item B<to_OBSERVATION_ID>
156              
157             Calculate a unique Observation ID.
158              
159             =cut
160              
161             # Note this routine is generic for JCMT heterodyne instrumentation.
162             # Would be completely generic if BACKEND was not used in preference to instrument.
163              
164             sub to_OBSERVATION_ID {
165 1     1 1 3 my $self = shift;
166 1         2 my $FITS_headers = shift;
167 1         27 my $backend = lc( $self->to_BACKEND( $FITS_headers ) );
168 1         25 my $obsnum = $self->to_OBSERVATION_NUMBER( $FITS_headers );
169 1         14 my $dateobs = $self->to_UTSTART( $FITS_headers );
170 1         8 my $datetime = $dateobs->datetime;
171 1         205 $datetime =~ s/-//g;
172 1         5 $datetime =~ s/://g;
173              
174 1         3 my $obsid = join('_', $backend, $obsnum, $datetime);
175 1         9 return $obsid;
176             }
177              
178             =item B<to_UTDATE>
179              
180             Translates the C<DATE_OBS> or C<LONGDATEOBS> header into a
181             YYYYMMDD integer.
182              
183             =cut
184              
185             sub to_UTDATE {
186 1     1 1 2 my $self = shift;
187 1         2 my $FITS_headers = shift;
188 1         4 my $date = _mysql_convert_date( _date_header( $FITS_headers ), 1);
189 1         5 return $date->strftime('%Y%m%d');
190             }
191              
192             =item B<to_UTSTART>
193              
194             Translates the DB date header into a C<Time::Piece> object.
195              
196             =cut
197              
198             sub to_UTSTART {
199 2     2 1 5 my $self = shift;
200 2         4 my $FITS_headers = shift;
201 2         9 return _mysql_convert_date( _date_header( $FITS_headers ));
202             }
203              
204             =item B<to_UTEND>
205              
206             Translates the database date header into a C<Time::Piece> object and adds
207             on the exposure time.
208              
209             =cut
210              
211             sub to_UTEND {
212 1     1 1 3 my $self = shift;
213 1         1 my $FITS_headers = shift;
214              
215 1         4 my $return = _mysql_convert_date( _date_header( $FITS_headers ) );
216 1 50       5 return undef unless defined $return;
217 1         5 my $expt = $self->to_EXPOSURE_TIME( $FITS_headers );
218              
219 1         6 $return += $expt;
220 1         101 return $return;
221             }
222              
223             =item B<to_BANDWIDTH_MODE>
224              
225             Uses the NORSECT (number of backend sections), NOFCHAN (number of
226             frontend output channels) and NOBCHAN (number of channels) to form a
227             string that is of the format 250MHzx2048. To obtain this, the
228             bandwidth (250MHz in this example) is calculated as 125MHz * NORSECT /
229             NOFCHAN. The number of channels is taken directly and not manipulated
230             in any way.
231              
232             If appropriate, the bandwidth may be given in GHz.
233              
234             =cut
235              
236             sub to_BANDWIDTH_MODE {
237 1     1 1 11 my $self = shift;
238 1         5 my $FITS_headers = shift;
239              
240 1         3 my $return;
241              
242 1 50 33     18 if ( exists( $FITS_headers->{'NORSECT'} ) && defined( $FITS_headers->{'NORSECT'} ) &&
      33        
      33        
      33        
      33        
243             exists( $FITS_headers->{'NOFCHAN'} ) && defined( $FITS_headers->{'NOFCHAN'} ) &&
244             exists( $FITS_headers->{'NOBCHAN'} ) && defined( $FITS_headers->{'NOBCHAN'} ) ) {
245              
246 1         5 my $bandwidth = 125 * $FITS_headers->{'NORSECT'} / $FITS_headers->{'NOFCHAN'};
247              
248 1 50       7 if ( $bandwidth >= 1000 ) {
249 0         0 $bandwidth /= 1000;
250 0         0 $return = sprintf( "%dGHzx%d", $bandwidth, $FITS_headers->{'NOBCHAN'} );
251             } else {
252 1         7 $return = sprintf( "%dMHzx%d", $bandwidth, $FITS_headers->{'NOBCHAN'} );
253             }
254             }
255              
256 1         3 return $return;
257              
258             }
259              
260              
261             =item B<to_EXPOSURE_TIME>
262              
263             =cut
264              
265             sub to_EXPOSURE_TIME {
266 2     2 1 5 my $self = shift;
267 2         4 my $FITS_headers = shift;
268 2         4 my $expt = 0;
269              
270 2 50 33     12 if ( exists( $FITS_headers->{'OBSMODE'} ) && defined( $FITS_headers->{'OBSMODE'} ) ) {
271              
272 2         6 my $obsmode = uc( $FITS_headers->{'OBSMODE'} );
273              
274 2 50 33     38 if ( $obsmode eq 'RASTER' ) {
    50          
275              
276 0 0 0     0 if ( exists( $FITS_headers->{'NSCAN'} ) && defined( $FITS_headers->{'NSCAN'} ) &&
      0        
      0        
      0        
      0        
277             exists( $FITS_headers->{'CYCLLEN'} ) && defined( $FITS_headers->{'CYCLLEN'} ) &&
278             exists( $FITS_headers->{'NOCYCPTS'} ) && defined( $FITS_headers->{'NOCYCPTS'} ) ) {
279              
280 0         0 my $nscan = $FITS_headers->{'NSCAN'};
281 0         0 my $cycllen = $FITS_headers->{'CYCLLEN'};
282 0         0 my $nocycpts = $FITS_headers->{'NOCYCPTS'};
283              
284             # raster.
285 0         0 $expt = 15 + $nscan * $cycllen * ( 1 + 1 / sqrt( $nocycpts ) ) * 1.4;
286             }
287             } elsif ( $obsmode eq 'PATTERN' or $obsmode eq 'GRID' ) {
288              
289 0         0 my $swmode = '';
290              
291 0 0 0     0 if ( exists( $FITS_headers->{'SWMODE'} ) && defined( $FITS_headers->{'SWMODE'} ) ) {
292 0         0 $swmode = $FITS_headers->{'SWMODE'};
293             } else {
294 0         0 $swmode = 'BEAMSWITCH';
295             }
296              
297 0 0 0     0 if ( exists( $FITS_headers->{'NSCAN'} ) && defined( $FITS_headers->{'NSCAN'} ) &&
      0        
      0        
      0        
      0        
298             exists( $FITS_headers->{'NCYCLE'} ) && defined( $FITS_headers->{'NCYCLE'} ) &&
299             exists( $FITS_headers->{'CYCLLEN'} ) && defined( $FITS_headers->{'CYCLLEN'} ) ) {
300              
301 0         0 my $nscan = $FITS_headers->{'NSCAN'};
302 0         0 my $ncycle = $FITS_headers->{'NCYCLE'};
303 0         0 my $cycllen = $FITS_headers->{'CYCLLEN'};
304              
305 0 0       0 if ( $swmode eq 'POSITION_SWITCH' ) {
    0          
    0          
306              
307             # position switch pattern/grid.
308 0         0 $expt = 6 + $nscan * $ncycle * $cycllen * 1.35;
309              
310             } elsif ( $swmode eq 'BEAMSWITCH' ) {
311              
312             # beam switch pattern/grid.
313 0         0 $expt = 6 + $nscan * $ncycle * $cycllen * 1.35;
314              
315             } elsif ( $swmode eq 'CHOPPING' ) {
316 0 0 0     0 if ( exists( $FITS_headers->{'FRONTEND'} ) && defined( $FITS_headers->{'FRONTEND'} ) ) {
317 0         0 my $frontend = uc( $FITS_headers->{'FRONTEND'} );
318 0 0       0 if ( $frontend eq 'RXA3I' ) {
    0          
319              
320             # fast frequency switch pattern/grid, receiver A.
321 0         0 $expt = 15 + $nscan * $ncycle * $cycllen * 1.20;
322              
323             } elsif ( $frontend eq 'RXB' ) {
324              
325             # slow frequency switch pattern/grid, receiver B.
326 0         0 $expt = 18 + $nscan * $ncycle * $cycllen * 1.60;
327              
328             }
329             }
330             }
331             }
332             } else {
333              
334 2         8 my $swmode;
335 2 50 33     11 if ( exists( $FITS_headers->{'SWMODE'} ) && defined( $FITS_headers->{'SWMODE'} ) ) {
336 2         7 $swmode = uc( $FITS_headers->{'SWMODE'} );
337             } else {
338 0         0 $swmode = 'BEAMSWITCH';
339             }
340              
341 2 50 33     40 if ( exists( $FITS_headers->{'NSCAN'} ) && defined( $FITS_headers->{'NSCAN'} ) &&
      33        
      33        
      33        
      33        
342             exists( $FITS_headers->{'NCYCLE'} ) && defined( $FITS_headers->{'NCYCLE'} ) &&
343             exists( $FITS_headers->{'CYCLLEN'} ) && defined( $FITS_headers->{'CYCLLEN'} ) ) {
344              
345 2         5 my $nscan = $FITS_headers->{'NSCAN'};
346 2         5 my $ncycle = $FITS_headers->{'NCYCLE'};
347 2         4 my $cycllen = $FITS_headers->{'CYCLLEN'};
348              
349 2 50       8 if ( $swmode eq 'POSITION_SWITCH' ) {
    50          
    0          
350              
351             # position switch sample.
352 0         0 $expt = 4.8 + $nscan * $ncycle * $cycllen * 1.10;
353              
354             } elsif ( $swmode eq 'BEAMSWITCH' ) {
355              
356             # beam switch sample.
357 2         6 $expt = 4.8 + $nscan * $ncycle * $cycllen * 1.25;
358              
359             } elsif ( $swmode eq 'CHOPPING' ) {
360 0 0 0     0 if ( exists( $FITS_headers->{'FRONTEND'} ) && defined( $FITS_headers->{'FRONTEND'} ) ) {
361 0         0 my $frontend = uc( $FITS_headers->{'FRONTEND'} );
362 0 0       0 if ( $frontend eq 'RXA3I' ) {
    0          
363              
364             # fast frequency switch sample, receiver A.
365 0         0 $expt = 3 + $nscan * $ncycle * $cycllen * 1.10;
366              
367             } elsif ( $frontend eq 'RXB' ) {
368              
369             # slow frequency switch sample, receiver B.
370 0         0 $expt = 3 + $nscan * $ncycle * $cycllen * 1.40;
371             }
372             }
373             }
374             }
375             }
376             }
377              
378 2         7 return $expt;
379             }
380              
381             =item B<to_SYSTEM_VELOCITY>
382              
383             Translate the C<VREF> and C<C12VDEF> headers into one combined header.
384              
385             =cut
386              
387             sub to_SYSTEM_VELOCITY {
388 1     1 1 3 my $self = shift;
389 1         2 my $FITS_headers = shift;
390 1         3 my $return;
391 1 50 33     26 if ( exists( $FITS_headers->{'VREF'} ) && defined( $FITS_headers->{'VREF'} ) &&
      33        
      33        
392             exists( $FITS_headers->{'VDEF'} ) && defined( $FITS_headers->{'VDEF'} ) ) {
393 1         8 $return = uc( substr( $FITS_headers->{'VDEF'}, 0, 3 ) . substr( $FITS_headers->{'VREF'}, 0, 3 ) );
394             }
395 1         4 return $return;
396             }
397              
398             =back
399              
400             =begin __PRIVATE
401              
402             =over 4
403              
404             =item B<_date_header>
405              
406             Works out which header corresponds to the date field in MySQL format and returns the value.
407              
408             $value = _date_header( $FITS_headers );
409              
410             Returns undef if none found.
411              
412             =cut
413              
414             sub _date_header {
415 4     4   8 my $FITS_headers = shift;
416             # For compatability with Sybase database, also accept LONGDATEOBS LONGDATE
417 4         9 for my $key (qw/ DATE_OBS LONGDATEOBS LONGDATE / ) {
418 4 50 33     22 if (exists $FITS_headers->{$key} && defined $FITS_headers->{$key}) {
419 4         19 return $FITS_headers->{$key};
420             }
421             }
422 0         0 return;
423             }
424              
425             =item B<_mysql_convert_date>
426              
427             Converts a MySQL date to Time::Piece object.
428              
429             $date = _mysql_convert_date( $string );
430              
431             Optional flag can be set to true to drop Hours, minutes and seconds from parse.
432              
433             $utday = _mysql_convert_date( $string, 1);
434              
435             Returns undef if no string is supplied.
436              
437             =cut
438              
439             sub _mysql_convert_date {
440 4     4   21 my $date = shift;
441 4         7 my $drophms = shift;
442 4 50       10 return undef unless $date;
443              
444             # The UT header is in MySQL format, which is something like
445             # "2002-03-15 07:04:35".
446             #
447             # Also support the Sybase format, which is something like
448             # "Mar 15 2002 7:04:35:234AM ". We first need to remove the number
449             # of milliseconds, then the whitespace at the end, then use the
450             # "%b%t%d%t%Y%t%I:%M:%S%p" format.
451              
452             # General cleanup
453 4         31 $date =~ s/\s*$//;
454              
455 4         9 my $return;
456              
457 4 50       17 if ($date =~ /^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/) {
458 4 100       12 if ($drophms) {
459 1         8 $date =~ s/\s*\d\d?:\d\d:\d\d$//;
460 1         15 $return = Time::Piece->strptime( $date,
461             "%Y-%m-%d" );
462             } else {
463 3         16 $return = Time::Piece->strptime( $date,
464             "%Y-%m-%d %T" );
465             }
466             } else {
467 0         0 $date =~ s/:\d\d\d//;
468              
469 0 0       0 if ($drophms) {
470 0         0 $date =~ s/\s*\d\d?:\d\d:\d\d[A|P]M$//;
471 0         0 $return = Time::Piece->strptime( $date,
472             "%b %e %Y" );
473             } else {
474 0         0 $return = Time::Piece->strptime( $date,
475             "%b %e %Y %l:%M:%S%p" );
476             }
477             }
478 4         307 return $return;
479             }
480              
481             =back
482              
483             =end __PRIVATE
484              
485             =head1 AUTHOR
486              
487             Brad Cavanagh E<lt>b.cavanagh@jach.hawaii.eduE<gt>,
488             Tim Jenness E<lt>t.jenness@jach.hawaii.eduE<gt>
489              
490             =head1 COPYRIGHT
491              
492             Copyright (C) 2008 Science and Technology Facilities Council.
493             Copyright (C) 2003-2007 Particle Physics and Astronomy Research Council.
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;