File Coverage

blib/lib/Astro/FITS/CFITSIO/Utils.pm
Criterion Covered Total %
statement 143 154 92.8
branch 56 70 80.0
condition 27 44 61.3
subroutine 16 18 88.8
pod 4 4 100.0
total 246 290 84.8


line stmt bran cond sub pod time code
1             package Astro::FITS::CFITSIO::Utils;
2              
3             # ABSTRACT: FITS utility routines
4              
5 3     3   252399 use v5.10;
  3         34  
6 3     3   14 use strict;
  3         4  
  3         59  
7 3     3   14 use warnings;
  3         3  
  3         111  
8              
9             our $VERSION = '0.14';
10              
11 3     3   1328 use Astro::FITS::Header::Item;
  3         12388  
  3         107  
12              
13             {
14             package #
15             Astro::FITS::CFITSIO::Utils::Item;
16              
17 3     3   1098 use parent 'Astro::FITS::Header::Item';
  3         667  
  3         13  
18              
19             sub new {
20 22     22   163 my $class = shift;
21 22   33     74 $class = ref $class || $class;
22              
23 22         45 my ( $keyw, $value );
24             # clean up input list, removing things that the superclass won't
25             # understand. must be a better way to do this.
26 22         0 my %args;
27 22         46 my @o_args = @_;
28 22         28 my @args;
29              
30 22         62 while ( ( $keyw, $value ) = splice( @o_args, 0, 2 ) ) {
31 44 100       148 if ( $keyw =~ /^(?:hdu_num|)$/i ) {
32 22         73 $args{ lc $keyw } = $value;
33             }
34             else {
35 22         61 push @args, $keyw, $value;
36             }
37             }
38              
39 22         57 my $self = $class->SUPER::new( @args );
40              
41             # handle the attributes that we know about
42 22         2213 $self->$keyw( $value ) while ( ( $keyw, $value ) = each %args );
43              
44 22         77 return $self;
45             }
46              
47             sub hdu_num {
48 27     27   127 my $self = shift;
49 27 100       58 if ( @_ ) {
50 22         46 $self->{hdu_num} = uc( shift );
51             }
52 27         85 return $self->{hdu_num};
53             }
54              
55             }
56              
57 3     3   1793 use Astro::FITS::Header::CFITSIO;
  3         51825  
  3         115  
58 3         180 use Astro::FITS::CFITSIO qw[ READONLY CASEINSEN
59             ANY_HDU ASCII_TBL BINARY_TBL
60             BAD_HDU_NUM END_OF_FILE
61 3     3   22 ];
  3         6  
62 3     3   1388 use Astro::FITS::CFITSIO::CheckStatus;
  3         2280  
  3         85  
63 3     3   1221 use Params::Validate qw( validate_with SCALAR );
  3         23553  
  3         180  
64              
65 3     3   20 use parent 'Exporter';
  3         7  
  3         21  
66              
67             our %EXPORT_TAGS = (
68             'all' => [ qw(
69             keypar
70             keyval
71             colkeys
72             croak_status
73             ) ] );
74              
75             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
76              
77              
78             sub _croak {
79 0     0   0 require Carp;
80 0         0 goto &croak;
81             }
82              
83             # Preloaded methods go here.
84              
85             # this is just a convenience wrapper around keypar
86             sub keyval {
87 0 0 0 0 1 0 my $opts = ( @_ && 'HASH' eq ref $_[-1] ) ? pop @_ : undef;
88              
89 0 0       0 keypar( @_, { defined $opts ? %$opts : (), Value => 1 } );
90             }
91              
92              
93              
94             # $k1 = keypar( $file, $kw1 );
95             # implicit OnePerHDU = 1, Accumulate = 0
96             # $k1 = first matching card
97              
98             # @k = keypar( $file, $kw1 );
99             # implicit OnePerHDU = 0, Accumulate = 1
100             # @k = all matching cards
101              
102             # ( $k1, $k2 ) = keypar( $file, [ $kw1, $kw2 ] );
103             # implicit OnePerHDU = 1, Accumulate = 0
104             # $k1 = first matching card
105             # $k2 = first matching card
106              
107             # $k = keypar( $file, [$kw1, $kw2] )
108             # illegal
109              
110              
111              
112             sub keypar {
113 15     15 1 4583 my $file = shift;
114 15 100 66     100 my $opts = ( @_ && 'HASH' eq ref $_[-1] ) ? pop @_ : undef;
115              
116 15 50       42 @_ == 1
117             or _croak( __PACKAGE__, "::keypar: incorrect number of arguments\n" );
118              
119             # set up defaults. they change to make things DWIM
120 15 100 100     122 my %opt = (
    100 100        
    100          
121             Accumulate => ref $_[0] || !wantarray() ? 0 : 1,
122             OnePerHDU => ref $_[0] || !wantarray() ? 1 : 0,
123             Value => 0,
124             $opts ? %$opts : () );
125              
126             $opt{CurrentHDU} = 0
127 15 100 66     84 unless defined $opt{CurrentHDU} || $file =~ /\[/;
128              
129 15         22 my $keyword;
130              
131 15 100       40 if ( 'ARRAY' eq ref $_[0] ) {
    50          
132 3         6 $keyword = [ map { uc( $_ ) } @{ $_[0] } ];
  6         14  
  3         8  
133             }
134              
135             elsif ( !ref $keyword ) {
136             # don't do more work than the caller requests
137 12 100       26 unless ( wantarray() ) {
138 9         14 $opt{Accumulate} = 0;
139 9         17 $opt{OnePerHDU} = 1;
140             }
141 12         38 $keyword = [ uc $_[0] ];
142             }
143             else {
144 0         0 _croak( __PACKAGE__, "::keypar: illegal type for keyword\n" );
145             }
146              
147              
148 15         35 my %keywords = map { $_ => [] } @$keyword;
  18         56  
149              
150             # are we passed a pointer to an open file?
151 15         27 my $file_is_open = eval { $file->isa( 'fitsfilePtr' ) };
  15         97  
152 15         24 my $fptr;
153              
154 15         66 tie my $status, 'Astro::FITS::CFITSIO::CheckStatus';
155              
156 15 100       654 if ( $file_is_open ) {
157 2         4 $fptr = $file;
158             }
159              
160             else {
161 13         55 $fptr = Astro::FITS::CFITSIO::open_file( $file, READONLY,
162             $status = __PACKAGE__ . "::keypar: error reading $file: " );
163             }
164              
165 14         2911 $fptr->get_hdu_num( my $init_hdu_num );
166 14         55 $fptr->movabs_hdu( 1, undef, $status );
167 14         214 $fptr->get_hdu_num( my $ext );
168              
169             # number of keywords found. used to short circuit search if
170             # Accumulate == 0
171 14         23 my $nfound = 0;
172              
173 14         21 for ( ; ; $ext++ ) {
174 22         87 my $hdr = Astro::FITS::Header::CFITSIO->new(
175             fitsID => $fptr,
176             ReadOnly => 1
177             );
178              
179             # loop over keywords
180 22         45268 while ( my ( $keyw, $found ) = each %keywords ) {
181             # ignore this keyword if we've found a match and Accumulate
182             # hasn't been set.
183 26 50 66     74 next if @$found && !$opt{Accumulate};
184              
185 26         62 my @newfound = $hdr->itembyname( $keyw );
186 26 100       503 if ( @newfound ) {
187 19 100       67 $#newfound = 0 if $opt{OnePerHDU};
188 19         42 foreach ( @newfound ) {
189 22         58 my $item = Astro::FITS::CFITSIO::Utils::Item->new( Card => $_->card, HDU_NUM => $ext );
190 22 100 66     82 push @$found, $opt{Value} && defined $item ? $item->value : $item;
191             }
192 19         70 $nfound++;
193             }
194             }
195              
196             last
197             if $opt{CurrentHDU}
198 22 100 100     196 || !$opt{Accumulate} && $nfound == @$keyword;
      66        
199              
200 13         385 $fptr->movrel_hdu( 1, undef, my $lstatus = 0 );
201              
202 13 100 66     54 last if $lstatus == BAD_HDU_NUM || $lstatus == END_OF_FILE;
203 8         201 croak_status( $lstatus );
204             }
205              
206             # done mucking about in the file; if it was an existing opened file
207             # return to the initial HDU
208 14 100       173 $fptr->movabs_hdu( $init_hdu_num, my $dummy, $status )
209             if $file_is_open;
210              
211             # if passed an array ref for $keyword, prepare to handle multiple
212             # keywords
213 14 100       94 if ( ref $_[0] ) {
214 3         4 my @found;
215              
216             # a single value per keyword. return list of scalars
217 3 100 66     12 if ( $opt{OnePerHDU} && !$opt{Accumulate} ) {
218 1         4 @found = map { @{$_}[0] } @keywords{@$keyword};
  2         2  
  2         5  
219             }
220              
221             # multiple values per keyword. return list of arrayrefs.
222             else {
223 2         5 @found = @keywords{@$keyword};
224             }
225 3 50       93 return wantarray ? @found : \@found;
226             }
227              
228             else {
229 11         19 my $found = $keywords{ @{$keyword}[0] };
  11         23  
230              
231 11 100       193 return wantarray ? @$found : @$found ? @{$found}[0] : undef;
  7 100       206  
232             }
233              
234             # NOT REACHED
235              
236             }
237              
238             sub colkeys {
239              
240 4     4 1 7483 my $file = shift;
241              
242             my %opt = validate_with(
243             params => \@_,
244             spec => {
245             extname => {
246             type => SCALAR,
247             optional => 1
248             },
249             extver => {
250             type => SCALAR,
251             regex => qr/^\d+$/,
252             optional => 1,
253             depends => ['extname'] }
254             },
255 11     11   62 normalize_keys => sub { lc $_[0] },
256 4         97 );
257              
258             # are we passed a pointer to an open file?
259 4         32 my $file_is_open = eval { $file->isa( 'fitsfilePtr' ) };
  4         28  
260 4         7 my $fptr;
261             my $init_hdu_num;
262              
263 4         17 tie my $error, 'Astro::FITS::CFITSIO::CheckStatus';
264              
265 4 100       173 if ( $file_is_open ) {
266 1         2 $fptr = $file;
267 1         3 $fptr->get_hdu_num( $init_hdu_num );
268             }
269             else {
270 3         14 $error = "Error reading $file: ";
271 3         41 $fptr = Astro::FITS::CFITSIO::open_file( $file, READONLY, $error );
272             }
273              
274             # move to specified HDU
275 4 100       1435 if ( $opt{extname} ) {
276 3   50     16 $opt{extver} ||= 0;
277 3 50       9 my $extname = $opt{extname} . ( $opt{extver} ? $opt{extver} : '' );
278              
279 3         13 $error = "$file does not contain an extension of $extname";
280 3         30 $fptr->movnam_hdu( ANY_HDU, $opt{extname}, $opt{extver}, $error );
281              
282 2         138 $fptr->get_hdu_type( my $hdutype, $error );
283              
284 2 50 33     27 _croak( "$file\[$extname] is not a table\n" )
285             if $hdutype != ASCII_TBL and $hdutype != BINARY_TBL;
286             }
287              
288             # find the first Table HDU
289             else {
290 1         2 while ( 1 ) {
291 1         3 $error = "$file has no table extension";
292 1         11 $fptr->movrel_hdu( 1, my $hdutype, $error );
293 1 50 33     80 last if $hdutype == ASCII_TBL || $hdutype == BINARY_TBL;
294             }
295             }
296              
297 3         83 my $hdr = Astro::FITS::Header::CFITSIO->new( fitsID => $fptr );
298              
299 3         11786 $fptr->get_num_cols( my $ncols, $error );
300              
301 3         56 my %colkeys;
302 3         14 for my $coln ( 1 .. $ncols ) {
303              
304 27         37 my %info;
305              
306 27         63 $fptr->get_colname( CASEINSEN, $coln, my $oname, undef, $error );
307 27         597 my $name = lc $oname;
308              
309             # blank name! can't have that. just # number 'em after the
310             # actual column position.
311 27 50       51 $name = "col_$coln" if '' eq $name;
312              
313 27 50       48 if ( exists $colkeys{$name} ) {
314 0         0 my $idx = 1;
315              
316 0         0 $idx++ while exists $colkeys{"${name}_${idx}"};
317 0         0 $name = "${name}_${idx}";
318             }
319              
320 27         466 for my $item ( grep { $_ !~ /^NAXIS/ } $hdr->itembyname( qr/\D+$coln([a-z])?$/i ) ) {
  66         2937  
321 60         457 my $key = lc join( '', grep { defined } $item->keyword =~ /(.*)$coln(.*)$/ );
  120         748  
322 60         115 $info{$key} = $item->value;
323             }
324              
325 27         231 $colkeys{$name} = {
326             hdr => \%info,
327             idx => $coln
328             };
329             }
330              
331             # done mucking about in the file; if it was an existing opened file
332             # return to the initial HDU
333 3 100       13 $fptr->movabs_hdu( $init_hdu_num, undef, $error )
334             if $file_is_open;
335              
336 3         164 return %colkeys;
337             }
338              
339             sub croak_status {
340 8     8 1 14 my $s = shift;
341              
342 8 50       78 if ( $s ) {
343 0           Astro::FITS::CFITSIO::fits_get_errstatus( $s, my $txt );
344              
345 0           local $Carp::CarpLevel = $Carp::CarpLevel + 1;
346 0           _croak @_, "CFITSIO Error: $txt\n";
347             }
348             }
349              
350             1;
351              
352             #
353             # This file is part of Astro-FITS-CFITSIO-Utils
354             #
355             # This software is Copyright (c) 2023 by Smithsonian Astrophysical Observatory.
356             #
357             # This is free software, licensed under:
358             #
359             # The GNU General Public License, Version 3, June 2007
360             #
361              
362             __END__