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