File Coverage

blib/lib/Astro/FITS/HdrTrans/UFTI.pm
Criterion Covered Total %
statement 123 152 80.9
branch 27 68 39.7
condition 17 51 33.3
subroutine 21 21 100.0
pod 15 16 93.7
total 203 308 65.9


line stmt bran cond sub pod time code
1             package Astro::FITS::HdrTrans::UFTI;
2              
3             =head1 NAME
4              
5             Astro::FITS::HdrTrans::UFTI - UKIRT UFTI translations
6              
7             =head1 SYNOPSIS
8              
9             use Astro::FITS::HdrTrans::UFTI;
10              
11             %gen = Astro::FITS::HdrTrans::UFTI->translate_from_FITS( %hdr );
12              
13             =head1 DESCRIPTION
14              
15             This class provides a generic set of translations that are specific to
16             the UFTI camera of the United Kingdom Infrared Telescope.
17              
18             =cut
19              
20 11     11   47198191 use 5.006;
  11         149  
21 11     11   177 use warnings;
  11         37  
  11         976  
22 11     11   88 use strict;
  11         24  
  11         383  
23 11     11   60 use Carp;
  11         29  
  11         1340  
24              
25             # Inherit from UKIRTNew
26 11     11   86 use base qw/ Astro::FITS::HdrTrans::UKIRTNew /;
  11         31  
  11         27626  
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              
34             );
35              
36             # NULL mappings used to override base class implementations
37             my @NULL_MAP = qw/ DETECTOR_INDEX /;
38              
39             # unit mapping implies that the value propogates directly
40             # to the output with only a keyword name change
41              
42             my %UNIT_MAP = (
43             # CGS4 + IRCAM
44             DETECTOR_READ_TYPE => "MODE",
45              
46             # MICHELLE + IRCAM compatible
47             SPEED_GAIN => "SPD_GAIN",
48             );
49              
50              
51             # Create the translation methods
52             __PACKAGE__->_generate_lookup_methods( \%CONST_MAP, \%UNIT_MAP, \@NULL_MAP );
53              
54             =head1 METHODS
55              
56             =over 4
57              
58             =item B<this_instrument>
59              
60             The name of the instrument required to match (case insensitively)
61             against the INSTRUME/INSTRUMENT keyword to allow this class to
62             translate the specified headers. Called by the default
63             C<can_translate> method.
64              
65             $inst = $class->this_instrument();
66              
67             Returns "UFTI".
68              
69             =cut
70              
71             sub this_instrument {
72 20     20 1 66 return "UFTI";
73             }
74              
75             =back
76              
77             =head1 COMPLEX CONVERSIONS
78              
79             These methods are more complicated than a simple mapping. We have to
80             provide both from- and to-FITS conversions. All these routines are
81             methods and the to_ routines all take a reference to a hash and return
82             the translated value (a many-to-one mapping). The from_ methods take a
83             reference to a generic hash and return a translated hash (sometimes
84             these are many-to-many).
85              
86             =over 4
87              
88             =item B<to_DEC_SCALE>
89              
90             Sets the declination scale in arcseconds per pixel derived
91             from keyword C<CDELT2>. The default is time dependent, as tabulated
92             in the UFTI web page.
93             L<http://www.jach.hawaii.edu/UKIRT/instruments/ufti/PARAMETERS.html#1>
94             The default scale assumes north is to the top.
95              
96             The actual C<CDELT2> value is scaled if its unit is degree/pixel,
97             as suggested by its size, and the presence of header C<CTYPE2> set
98             to 'DEC--TAN' indicating that the WCS follows the AIPS convention.
99              
100             =cut
101              
102             sub to_DEC_SCALE {
103 1     1 1 21 my $self = shift;
104 1         3 my $FITS_headers = shift;
105              
106             # Default from 20011115.
107 1         3 my $scale = 0.09085;
108              
109             # Note in the raw data these are in arcseconds, not degrees.
110 1 50       6 if ( defined( $FITS_headers->{CDELT2} ) ) {
111 1         114 $scale = $FITS_headers->{CDELT2};
112              
113             # Allow for missing values using measured scales.
114             } else {
115 0         0 my $date = $self->to_UTDATE( $FITS_headers );
116 0 0       0 if ( defined( $date ) ) {
117 0 0       0 if ( $date < 19990701 ) {
    0          
    0          
118 0         0 $scale = 0.09075;
119             } elsif ( $date < 20010401 ) {
120 0         0 $scale = 0.09088;
121             } elsif ( $date < 20011115 ) {
122 0         0 $scale = 0.09060;
123             }
124             }
125             }
126              
127             # Allow for D notation, which is not recognised by Perl, so that
128             # supplied strings are valid numbers.
129 1         87 $scale =~ s/D/E/;
130              
131             # The CDELTn headers are either part of a WCS in expressed in the
132             # AIPS-convention, or the values we require. Angles for the former
133             # are measured in degrees. The sign of the scale may be negative.
134 1 50 33     4 if ( defined $FITS_headers->{CTYPE2} &&
      33        
135             $FITS_headers->{CTYPE2} eq "DEC--TAN" &&
136             abs( $scale ) < 1.0E-3 ) {
137 0         0 $scale *= 3600.0;
138             }
139 1         225 return $scale;
140             }
141              
142             =item B<to_FILE_FORMAT>
143              
144             Determines the file format being used. It is either C<"HDS"> (meaning
145             an HDS container file of NDFs) or C<"FITS"> and is determined by the
146             presence of the DHSVER header.
147              
148             =cut
149              
150             sub to_FILE_FORMAT {
151 2     2 1 6 my $self = shift;
152 2         4 my $FITS_headers = shift;
153 2         5 my $format = "HDS";
154 2 50       11 if ( ! exists( $FITS_headers->{DHSVER} ) ) {
155 0         0 $format = "FITS";
156             }
157 2         91 return $format;
158             }
159              
160             =item B<to_POLARIMETRY>
161              
162             Checks the filter name.
163              
164             =cut
165              
166             sub to_POLARIMETRY {
167 1     1 1 3 my $self = shift;
168 1         3 my $FITS_headers = shift;
169 1 50 33     6 if ( exists( $FITS_headers->{FILTER} ) &&
170             $FITS_headers->{FILTER} =~ /pol/i ) {
171 0         0 return 1;
172             } else {
173 1         154 return 0;
174             }
175             }
176              
177             =item B<to_RA_BASE>
178              
179             Converts the decimal hours in the FITS header C<RABASE> into
180             decimal degrees for the generic header C<RA_BASE>.
181              
182             Note that this is different from the original translation within
183             ORAC-DR where it was to decimal hours.
184              
185             There was a period from 2000-05-07 to 2000-07-19 inclusive, where
186             degrees, not hours, were written whenever the data were stored as NDF
187             format. However, there wasn't a clean changover during ORAC-DR
188             commissioning. So use the FILE_FORMAT to discriminate between the two
189             formats.
190              
191             =cut
192              
193             sub to_RA_BASE {
194 1     1 1 3 my $self = shift;
195 1         3 my $FITS_headers = shift;
196 1         2 my $return;
197 1 50       6 if ( exists($FITS_headers->{RABASE} ) ) {
198 1         75 my $date = $self->to_UTDATE( $FITS_headers );
199 1         6 my $format = $self->to_FILE_FORMAT( $FITS_headers );
200              
201 1 50 33     19 if ( defined( $format ) && $format eq "HDS" &&
      33        
      33        
      33        
202             defined( $date ) && $date > 20000507 && $date < 20000720 ) {
203 0         0 $return = $FITS_headers->{RABASE};
204             } else {
205 1         6 $return = $FITS_headers->{RABASE} * 15;
206             }
207             }
208 1         102 return $return;
209             }
210              
211             =item B<from_RA_BASE>
212              
213             Converts the decimal degrees in the generic header C<RA_BASE>
214             into decimal hours for the FITS header C<RABASE>.
215              
216             %fits = $class->from_RA_BASE( \%generic );
217              
218             There was a period from 2000-05-07 to 2000-07-19 inclusive, where
219             degrees, not hours, were written whenever the data were stored as NDF
220             format. However, there was not a clean changover during ORAC-DR
221             commissioning. So use the generic header FILE_FORMAT to discriminate
222             between the two formats. For symmetry and consistency, retain these
223             units during the problem period.
224              
225             =cut
226              
227             sub from_RA_BASE {
228 1     1 1 3 my $self = shift;
229 1         3 my $generic_headers = shift;
230 1         3 my %return_hash;
231 1 50       6 if ( defined( $generic_headers->{RA_BASE} ) ) {
232 1         4 my $date = $self->to_UTDATE( $generic_headers );
233              
234 1 50 33     16 if ( defined( $generic_headers->{FILE_FORMAT} ) &&
      33        
      33        
      33        
235             $generic_headers->{FILE_FORMAT} eq "HDS" &&
236             defined( $date ) && $date > 20000507 && $date < 20000720 ) {
237 0         0 $return_hash{'RABASE'} = $generic_headers->{RA_BASE};
238             } else {
239 1         6 $return_hash{'RABASE'} = $generic_headers->{RA_BASE} / 15;
240             }
241             }
242 1         34 return %return_hash;
243             }
244              
245             =item B<to_RA_SCALE>
246              
247             Sets the right-ascension scale in arcseconds per pixel derived
248             from keyword C<CDELT1>. The default is time dependent, as tabulated
249             in the UFTI web page.
250             L<http://www.jach.hawaii.edu/UKIRT/instruments/ufti/PARAMETERS.html#1>
251             The default scale assumes east is to the left.
252              
253             It corrects for an erroneous sign in early data.
254              
255             The actual C<CDELT1> value is scaled if its unit is degree/pixel,
256             as suggested by its size, and the presence of header C<CTYPE1> set
257             to 'RA---TAN' indicating that the WCS follows the AIPS convention.
258              
259             =cut
260              
261             sub to_RA_SCALE {
262 1     1 1 3 my $self = shift;
263 1         3 my $FITS_headers = shift;
264              
265             # Default from 20011115.
266 1         2 my $scale = -0.09085;
267              
268             # Note in the raw data these are in arcseconds, not degrees.
269 1 50       7 if ( defined( $FITS_headers->{CDELT1} ) ) {
270 1         89 $scale = $FITS_headers->{CDELT1};
271              
272             # Allow for missing values using measured scales.
273             } else {
274 0         0 my $date = $self->to_UTDATE( $FITS_headers );
275 0 0       0 if ( defined( $date ) ) {
276 0 0       0 if ( $date < 19990701 ) {
    0          
    0          
277 0         0 $scale = -0.09075;
278             } elsif ( $date < 20010401 ) {
279 0         0 $scale = -0.09088;
280             } elsif ( $date < 20011115 ) {
281 0         0 $scale = -0.09060;
282             }
283             }
284             }
285              
286             # Allow for D notation, which is not recognised by Perl, so that
287             # supplied strings are valid numbers.
288 1         80 $scale =~ s/D/E/;
289              
290             # Correct the RA scale. The RA scale originates from the erroneous
291             # positive CDELT1. Reverse the sign to give the correct increment
292             # per pixel.
293 1 50       8 if ( $scale > 0.0 ) {
294 1         3 $scale *= -1.0;
295             }
296              
297             # The CDELTn headers are either part of a WCS in expressed in the
298             # AIPS-convention, or the values we require. Angles for the former
299             # are measured in degrees. The sign of the scale may be negative.
300 1 50 33     5 if ( defined $FITS_headers->{CTYPE1} &&
      33        
301             $FITS_headers->{CTYPE1} eq "RA---TAN" &&
302             abs( $scale ) < 1.0E-3 ) {
303 0         0 $scale *= 3600.0;
304             }
305              
306 1         194 return $scale;
307             }
308              
309             =item B<from_RA_SCALE>
310              
311             Converts the generic header C<RA_SCALE> to the FITS header C<CDELT1>
312             by ensuring it has a positive sign as in the input data. This
313             sign is wrong because the right ascension increases with decreasing
314             pixel index, however this conversion permits a cycle from FITS to
315             generic and back to FITS to retain the original value.
316              
317             %fits = $class->from_RA_SCALE( \%generic );
318              
319             =cut
320              
321             sub from_RA_SCALE {
322 1     1 1 4 my $self = shift;
323 1         2 my $generic_headers = shift;
324 1         4 my %return_hash;
325 1 50       5 if ( defined( $generic_headers->{RA_SCALE} ) ) {
326 1         4 $return_hash{'CDELT1'} = -1.0 * $generic_headers->{RA_SCALE};
327             }
328 1         16 return %return_hash;
329             }
330              
331             =item B<to_UTDATE>
332              
333             Converts FITS header values into C<Time::Piece> object. This differs
334             from the base class in the use of the C<DATE> rather than C<UTDATE>
335             header item and the formatting of the DATE keyword is not an integer.
336              
337             =cut
338              
339             sub to_UTDATE {
340 6     6 1 17 my $self = shift;
341 6         11 my $FITS_headers = shift;
342 6         12 my $return;
343 6 100       25 if ( exists( $FITS_headers->{DATE} ) ) {
344 5         196 my $utdate = $FITS_headers->{DATE};
345              
346             # This is a kludge to work with old data which has multiple values of
347             # the DATE keyword with the last value being blank (these were early
348             # UFTI data). Return the first value, since the last value can be
349             # blank.
350 5 50       441 if ( ref( $utdate ) eq 'ARRAY' ) {
351 0         0 $utdate = $utdate->[0];
352             }
353 5         52 $return = $self->_parse_yyyymmdd_date( $utdate, "-" );
354 5         216 $return = $return->strftime( '%Y%m%d' );
355             }
356              
357 6         502 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 DATE header. This differs from the base class in the use of the
364             C<DATE> rather than C<UTDATE> header item.
365              
366             =cut
367              
368             sub from_UTDATE {
369 1     1 1 3 my $self = shift;
370 1         3 my $generic_headers = shift;
371 1         2 my %return_hash;
372 1 50       5 if ( exists( $generic_headers->{UTDATE} ) ) {
373 1         4 my $date = $generic_headers->{UTDATE};
374 1         6 $date = $self->_parse_yyyymmdd_date( $date, '' );
375 1 50       47 return () unless defined $date;
376 1         5 $return_hash{DATE} = sprintf( "%04d-%02d-%02d",
377             $date->year, $date->mon, $date->mday );
378             }
379 1         43 return %return_hash;
380             }
381              
382             =item B<to_UTEND>
383              
384             Converts UT date in C<DATE-END> header into C<Time::Piece> object.
385             Allows for blank C<DATE-END> string present in early UFTI data.
386              
387             =cut
388              
389             sub to_UTEND {
390 1     1 1 4 my $self = shift;
391 1         3 my $FITS_headers = shift;
392             my $dateend = ( exists $FITS_headers->{"DATE-END"} ?
393 1 50       6 $FITS_headers->{"DATE-END"} : undef );
394              
395             # Some early data had blank DATE-OBS strings.
396 1 50 33     193 if ( defined( $dateend ) && $dateend !~ /\d/ ) {
397 0         0 $dateend = undef;
398             }
399              
400 1         8 my @rutend = sort {$a<=>$b} $self->via_subheader( $FITS_headers, "UTEND" );
  0         0  
401 1         4 my $utend = $rutend[-1];
402 1         5 return $self->_parse_date_info( $dateend,
403             $self->to_UTDATE( $FITS_headers ),
404             $utend );
405             }
406              
407             =item B<to_UTSTART>
408              
409             Converts UT date in C<DATE-OBS> header into C<Time::Piece> object.
410             Allows for blank C<DATE-OBS> string present in early UFTI data.
411              
412             =cut
413              
414             sub to_UTSTART {
415 2     2 1 5 my $self = shift;
416 2         6 my $FITS_headers = shift;
417             my $dateobs = ( exists $FITS_headers->{"DATE-OBS"} ?
418 2 50       11 $FITS_headers->{"DATE-OBS"} : undef );
419              
420             # Some early data had blank DATE-OBS strings.
421 2 50 33     287 if ( defined( $dateobs ) && $dateobs !~ /\d/ ) {
422 0         0 $dateobs = undef;
423             }
424              
425 2         12 my @rutstart = sort {$a<=>$b} $self->via_subheader( $FITS_headers, "UTSTART" );
  0         0  
426 2         5 my $utstart = $rutstart[0];
427 2         26 return $self->_parse_date_info( $dateobs,
428             $self->to_UTDATE( $FITS_headers ),
429             $utstart );
430             }
431              
432              
433             =item B<to_X_REFERENCE_PIXEL>
434              
435             Specify the reference pixel, which is normally near the frame centre.
436             There may be small displacements to avoid detector joins or for
437             polarimetry using a Wollaston prism.
438              
439             =cut
440              
441             sub to_X_REFERENCE_PIXEL{
442 1     1 1 3 my $self = shift;
443 1         3 my $FITS_headers = shift;
444 1         3 my $xref;
445              
446             # Use the average of the bounds to define the centre and dimension.
447 1 50 33     6 if ( exists $FITS_headers->{RDOUT_X1} && exists $FITS_headers->{RDOUT_X2} ) {
448 1         72 my $xl = $FITS_headers->{RDOUT_X1};
449 1         112 my $xu = $FITS_headers->{RDOUT_X2};
450 1         114 my $xdim = $xu - $xl + 1;
451 1         15 my $xmid = $self->nint( ( $xl + $xu ) / 2 );
452              
453             # UFTI is at the centre for a sub-array along an axis but offset slightly
454             # for a sub-array to avoid the joins between the four sub-array sections
455             # of the frame. Ideally these should come through the headers...
456 1 50       7 if ( $xdim == 1024 ) {
457 1         17 $xref = $xmid + 20;
458             } else {
459 0         0 $xref = $xmid;
460             }
461              
462             # Correct for IRPOL beam splitting with a 6" E offset.
463 1 50       7 if ( $FITS_headers->{FILTER} =~ m/pol/ ) {
464 0         0 $xref -= 65.5;
465             }
466              
467             # Use a default which assumes the full array (slightly offset from the
468             # centre).
469             } else {
470 0         0 $xref = 533;
471             }
472 1         110 return $xref;
473             }
474              
475             =item B<from_X_REFERENCE_PIXEL>
476              
477             Always returns CRPIX1 of "0.5".
478              
479             =cut
480              
481             sub from_X_REFERENCE_PIXEL {
482 1     1 1 22 return ( "CRPIX1" => 0.5 );
483             }
484              
485             =item B<to_Y_REFERENCE_PIXEL>
486              
487             Specify the reference pixel, which is normally near the frame centre.
488             There may be small displacements to avoid detector joins or for
489             polarimetry using a Wollaston prism.
490              
491             =cut
492              
493             sub to_Y_REFERENCE_PIXEL{
494 1     1 1 3 my $self = shift;
495 1         4 my $FITS_headers = shift;
496 1         3 my $yref;
497              
498             # Use the average of the bounds to define the centre and dimension.
499 1 50 33     5 if ( exists $FITS_headers->{RDOUT_Y1} && exists $FITS_headers->{RDOUT_Y2} ) {
500 1         83 my $yl = $FITS_headers->{RDOUT_Y1};
501 1         96 my $yu = $FITS_headers->{RDOUT_Y2};
502 1         87 my $ydim = $yu - $yl + 1;
503 1         7 my $ymid = $self->nint( ( $yl + $yu ) / 2 );
504              
505             # UFTI is at the centre for a sub-array along an axis but offset slightly
506             # for a sub-array to avoid the joins between the four sub-array sections
507             # of the frame. Ideally these should come through the headers...
508 1 50       5 if ( $ydim == 1024 ) {
509 1         4 $yref = $ymid - 25;
510             } else {
511 0         0 $yref = $ymid;
512             }
513              
514             # Correct for IRPOL beam splitting with a " N offset.
515 1 50       4 if ( $FITS_headers->{FILTER} =~ m/pol/ ) {
516 0         0 $yref += 253;
517             }
518              
519             # Use a default which assumes the full array (slightly offset from the
520             # centre).
521             } else {
522 0         0 $yref = 488;
523             }
524 1         89 return $yref;
525             }
526              
527             =item B<from_X_REFERENCE_PIXEL>
528              
529             Always returns CRPIX2 of "0.5".
530              
531             =cut
532              
533             sub from_Y_REFERENCE_PIXEL {
534 1     1 0 20 return ( "CRPIX2" => 0.5 );
535             }
536              
537              
538             =back
539              
540             =head1 SEE ALSO
541              
542             C<Astro::FITS::HdrTrans>, C<Astro::FITS::HdrTrans::UKIRT>.
543              
544             =head1 AUTHOR
545              
546             Malcolm J. Currie E<lt>mjc@star.rl.ac.ukE<gt>
547             Brad Cavanagh E<lt>b.cavanagh@jach.hawaii.eduE<gt>,
548             Tim Jenness E<lt>t.jenness@jach.hawaii.eduE<gt>.
549              
550             =head1 COPYRIGHT
551              
552             Copyright (C) 2008 Science and Technology Facilities Council.
553             Copyright (C) 2003-2007 Particle Physics and Astronomy Research Council.
554             All Rights Reserved.
555              
556             This program is free software; you can redistribute it and/or modify it under
557             the terms of the GNU General Public License as published by the Free Software
558             Foundation; either Version 2 of the License, or (at your option) any later
559             version.
560              
561             This program is distributed in the hope that it will be useful,but WITHOUT ANY
562             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
563             PARTICULAR PURPOSE. See the GNU General Public License for more details.
564              
565             You should have received a copy of the GNU General Public License along with
566             this program; if not, write to the Free Software Foundation, Inc., 59 Temple
567             Place, Suite 330, Boston, MA 02111-1307, USA.
568              
569             =cut
570              
571             1;