File Coverage

blib/lib/Astro/FITS/HdrTrans/UFTI.pm
Criterion Covered Total %
statement 126 155 81.2
branch 27 68 39.7
condition 17 51 33.3
subroutine 22 22 100.0
pod 15 16 93.7
total 207 312 66.3


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