File Coverage

blib/lib/PDL/IO/FITS.pm
Criterion Covered Total %
statement 715 870 82.1
branch 318 524 60.6
condition 104 197 52.7
subroutine 38 48 79.1
pod 3 10 30.0
total 1178 1649 71.4


line stmt bran cond sub pod time code
1             package PDL::IO::FITS;
2              
3 14     14   18179 use strict;
  14         35  
  14         592  
4 14     14   94 use warnings;
  14         28  
  14         2101  
5              
6             our $VERSION = 0.92; # Will be 1.0 when ascii table read/write works.
7             our @EXPORT_OK = qw( rfits rfitshdr wfits );
8             our %EXPORT_TAGS = (Func=>[@EXPORT_OK]);
9             our @ISA = ('PDL::Exporter');
10              
11 14     14   121 use PDL::Core;
  14         27  
  14         133  
12 14     14   3008 use PDL::IO::Misc;
  14         33  
  14         146  
13 14     14   116 use PDL::Exporter;
  14         32  
  14         76  
14 14     14   77 use PDL::Primitive;
  14         79  
  14         134  
15 14     14   139 use PDL::Types;
  14         31  
  14         2968  
16 14     14   109 use PDL::Options;
  14         30  
  14         1111  
17 14     14   136 use PDL::Bad;
  14         44  
  14         107  
18 14     14   127 use Carp;
  14         40  
  14         19759  
19              
20             =head1 NAME
21              
22             PDL::IO::FITS - Simple FITS support for PDL
23              
24             =head1 SYNOPSIS
25              
26             use PDL;
27             use PDL::IO::FITS;
28              
29             $x = rfits('foo.fits'); # read a FITS file
30             $x->wfits('bar.fits'); # write a FITS file
31              
32             =head1 DESCRIPTION
33              
34             This module provides basic FITS support for PDL, in the sense of
35             reading and writing whole FITS files. For more complex operations,
36             such as prefiltering rows out of tables or performing operations on
37             the FITS file in-place on disk, you can use the L
38             module that is available on CPAN.
39              
40             Basic FITS image files are supported, along with BINTABLE and IMAGE extensions.
41             ASCII Table support is planned, as are the HEASARC bintable extensions that
42             are recommended in the 1999 FITS standard.
43              
44             Table support is based on hashes and named columns, rather than the
45             less convenient (but slightly more congruent) technique of perl lists
46             of numbered columns.
47              
48             The principle interface routines are C and C, for
49             reading and writing respectively. FITS headers are returned as perl
50             hashes or (if the module is present) Astro::FITS::Header objects that
51             are tied to perl hashes. Astro::FITS::Header objects provide
52             convenient access through the tied hash interface, but also allow you
53             to control the card structure in more detail using a separate method
54             interface; see the L
55             documentation for details.
56              
57             =head1 AUTHOR
58              
59             Copyright (C) Karl Glazebrook, Craig DeForest, and Doug Burke, 1997-2010.
60             There is no warranty. You are allowed to redistribute and/or modify
61             this software under certain conditions. For details, see the file
62             COPYING in the PDL distribution. If this file is separated from the
63             PDL distribution, the copyright notice should be pasted into in this file.
64              
65             =head1 FUNCTIONS
66              
67             =cut
68              
69             # Check if there's Astro::FITS::Header support, and set flag.
70             $PDL::Astro_FITS_Header = eval {
71             require Astro::FITS::Header;
72             Astro::FITS::Header->VERSION(1.12);
73             1;
74             };
75              
76             =head2 rfits()
77              
78             =for ref
79              
80             Simple ndarray FITS reader.
81              
82             =for example
83              
84             $pdl = rfits('file.fits'); # Read a simple FITS image
85              
86             Suffix magic:
87              
88             $pdl = rfits('file.fits.gz'); # Read a file with gunzip(1)
89             $pdl = rfits('file.fits.Z'); # Read a file with uncompress(1)
90              
91             $pdl = rfits('file.fits[2]'); # Read 2nd extension
92             $pdl = rfits('file.fits.gz[3]'); # Read 3rd extension
93             @pdls = rfits('file.fits'); # Read primary data and extensions
94              
95             Tilde expansion:
96              
97             #expand leading ~ to home directory (using glob())
98             $pdl = rfits '~/filename.fits';
99              
100             $hdr = rfits('file.fits',{data=>0}); # Options hash changes behavior
101              
102             In list context, C reads the primary image and all possible
103             extensions, returning them in the same order that they occurred in the
104             file -- except that, by default, the primary HDU is skipped if it
105             contains no data. In scalar context, the default is to read the first
106             HDU that contains data. One can read other HDU's by using the [n]
107             syntax. Using the [0] syntax forces a read of the first HDU,
108             regardless of whether it contains data or no. Currently recognized
109             extensions are IMAGE and BINTABLE. (See the addendum on EXTENSIONS
110             for details).
111              
112             C accepts several options that may be passed in as a hash ref
113             if desired:
114              
115             =over 3
116              
117             =item bscale (default=1)
118              
119             Determines whether the data are linearly scaled using the BSCALE/BZERO keywords
120             in the FITS header. To read in the exact data values in the file, set this
121             to 0.
122              
123             =item data (default=1)
124              
125             Determines whether to read the data, or just the header. If you set this to
126             0, you will get back the FITS header rather than the data themselves. (Note
127             that the header is normally returned as the C field of the returned PDL;
128             this causes it to be returned as a hash ref directly.)
129              
130             =item hdrcpy (default=0)
131              
132             Determines whether the L flag is set in the returned
133             PDL. Setting the flag will cause an explicit deep copy of the header whenever
134             you use the returned PDL in an arithmetic or slicing operation. That is useful
135             in many circumstances but also causes a hit in speed. When two or more PDLs
136             with hdrcpy set are used in an expression, the result gets the header of the
137             first PDL in the expression. See L for an example.
138              
139             =item expand (default=1)
140              
141             Determines whether auto-expansion of tile-compressed images should happen.
142             Tile-compressed images are transmitted as binary tables with particular
143             fields ("ZIMAGE") set. Leaving this alone does what you want most of the
144             time, unpacking such images transparently and returning the data and header
145             as if they were part of a normal IMAGE extension. Setting "expand" to 0
146             delivers the binary table, rather than unpacking it into an image.
147              
148             =item afh (default=1)
149              
150             By default rfits uses Astro::FITS::Header tied-hash objects to contain
151             the FITS header information. This permits explicit control over FITS
152             card information, and conforms well with the FITS specification. But
153             Astro::FITS::Header objects are about 40-60x more memory intensive
154             than comparable perl hashes, and also use ~10x more CPU to manage.
155             For jobs where header processing performance is important (e.g. reading
156             just the headers of 1,000 FITS files), set afh to 0 to use the legacy parser
157             and get a large boost in speed.
158              
159             =back
160              
161             FITS image headers are stored in the output PDL and can be retrieved
162             with L or L. The
163             L flag of the PDL is set so that the header
164             is copied to derived ndarrays by default. (This is inefficient if you
165             are planning to do lots of small operations on the data; clear
166             the flag with "->hcpy(0)" or via the options hash if that's the case.)
167              
168             The header is a hash whose keys are the keywords in the FITS header.
169             If you have the "Astro::FITS::Header" module installed, the header is
170             actually a tied hash to a FITS header object, which can give you
171             more control over card order, comment fields, and variable types.
172             (see L for details).
173              
174             The header keywords are converted to I per the FITS
175             standard. Access is case-insensitive on the perl side, provided that
176             Astro::FITS::Header is installed.
177              
178             If Astro::FITS::Header is not installed, then a built-in legacy parser
179             is used to generate the header hash. Keyword-associated comments in
180             the headers are stored under the hash key
181             C<< _COMMENT> >>. All HISTORY cards in the header are
182             collected into a single multiline string stored in the C key.
183             All COMMENT cards are similarly collected under the C key.
184              
185             =head3 BSCALE/BZERO
186              
187             If the BSCALE and/or BZERO keywords are set, they are applied to the
188             image before it is returned. The returned PDL is promoted as
189             necessary to contain the multiplied values, and the BSCALE and BZERO
190             keywords are deleted from the header for clarity. If you don't want
191             this type of processing, set 'bscale=>0' in the options hash.
192              
193             =head3 EXTENSIONS
194              
195             Sometimes a FITS file contains only extensions and a stub header in
196             the first header/data unit ("primary HDU"). In scalar context, you
197             normally only get back the primary HDU -- but in this special case,
198             you get back the first extension HDU. You can force a read of the
199             primary HDU by adding a '[0]' suffix to the file name.
200              
201             =head3 BINTABLE EXTENSIONS
202              
203             Binary tables are handled. Currently only the following PDL
204             datatypes are supported: byte, short, ushort, long, float, and
205             double. At present ushort() data is written as a long rather than
206             as a short with TSCAL/ZERO; this may change.
207              
208             The return value for a binary table is a hash ref containing the names
209             of the columns in the table (in UPPER CASE as per the FITS standard).
210             Each element of the hash contains a PDL (for numerical values) or a
211             perl list (for string values). The PDL's 0th dimension runs across
212             rows; the 1st dimension runs across the repeat index within the row
213             (for rows with more than one value). (Note that this is different from
214             standard broadcasting order - but it allows Least Surprise to work when
215             adding more complicated objects such as collections of numbers (via
216             the repeat count) or variable length arrays.)
217              
218             Thus, if your table contains a column named C with type C<5D>,
219             the expression
220              
221             $x->{FOO}->((2))
222              
223             returns a 5-element double-precision PDL containing the values of FOO
224             from the third row of the table.
225              
226             The header of the table itself is parsed as with a normal FITS HDU,
227             and is returned in the element 'hdr' of the returned hash. You can
228             use that to preserve the original column order or access the table at a low
229             level, if you like.
230              
231             Scaling and zero-point adjustment are performed as with BSCALE/BZERO:
232             the appropriate keywords are deleted from the as-returned header. To avoid
233             this behavior, set 'bscale=>0' in the options hash.
234              
235             As appropriate, TSCAL/ZERO and TUNIT are copied into each column-PDL's
236             header as BSCALE/BZERO and BUNIT.
237              
238             The main hash also contains the element 'tbl', which is set
239             to 'binary' to distinguish it from an ASCII table.
240              
241             Because different columns in the table might have identical names in a
242             FITS file, the binary table reader practices collision avoidance. If
243             you have multiple columns named "FOO", then the first one encountered
244             (numerically) gets the name "FOO", the next one gets "FOO_1", and the
245             next "FOO_2", etc. The appropriate TTYPEn fields in the header are
246             changed to match the renamed column fields.
247              
248             Columns with no name are assigned the name "COL_", where starts
249             at 1 and increments for each no-name column found.
250              
251             Variable-length arrays are supported for reading. They are unpacked
252             into PDLs that appear exactly the same as the output for fixed-length
253             rows, except that each row is padded to the maximum length given
254             in the extra characters -- e.g. a row with TFORM of 1PB(300) will
255             yield an NAXIS2x300 output field in the final hash. The padding
256             uses the TNULL keyword for the column, or 0 if TNULL is not
257             present. The output hash also gets an additional field, "len_",
258             that contains the number of elements in each table row.
259              
260             =head3 TILE-COMPRESSED IMAGES
261              
262             CFITSIO and several large projects (including NASA's Solar Dynamics
263             Observatory) now support an unofficial extension to FITS that stores
264             images as a collection of individually compressed tiles within a
265             BINTABLE extension. These images are automagically uncompressed by
266             default, and delivered as if they were normal image files. You can
267             override this behavior by supplying the "expand" key in the options hash.
268              
269             Currently, only Rice compression is supported, though there is a framework
270             in place for adding other compression schemes.
271              
272             =for bad
273              
274             =head3 BAD VALUE HANDLING
275              
276             If a FITS file contains the C keyword (and has C 0>),
277             the ndarray will have its bad flag set, and those elements which equal the
278             C value will be set bad. For C 0>, any NaN's are
279             converted to bad (if necessary).
280              
281              
282             =head2 rfitshdr()
283              
284             =for ref
285              
286             Read only the header of a FITS file or an extension within it.
287              
288             This is syntactic sugar for the C0> option to L.
289              
290             See L for details on header handling. rfitshdr() runs
291             the same code to read the header, but returns it rather than
292             reading in a data structure as well.
293              
294             =cut
295              
296             our $rfits_options = PDL::Options->new( { bscale=>1, data=>1, hdrcpy=>0, expand=>1, afh=>1 } );
297              
298             sub PDL::rfitshdr {
299 0     0 0 0 my $class = shift;
300 0         0 my $file = shift;
301 0         0 my $u_opt = ifhref(shift);
302 0         0 $u_opt->{data} = 0;
303 0         0 PDL::rfits($class,$file,$u_opt);
304             }
305              
306             sub PDL::rfits {
307 90     90 0 436 my $class = shift;
308 90 50 33     568 barf 'Usage: $x = rfits($file) -or- $x = PDL->rfits($file)' if (@_ < 1 || @_ > 2);
309 90         183 my $file = shift;
310 90         430 my $u_opt = ifhref(shift);
311 90         481 my $opt = $rfits_options->options($u_opt);
312 90         210 my($nbytes, $line, $name, $rest, $size, $i, $bscale, $bzero, $extnum);
313              
314 90         137 $nbytes = 0;
315              
316             # Modification 02/04/2005 - JB. Earlier version stripped the extension
317             # indicator which cancelled the check for empty primary data array at the end.
318 90 100       498 my $explicit_extension = ($file =~ m/\[\d+\]$/ ? 1 : 0);
319 90 100       361 $extnum = ( ($file =~ s/\[(\d+)\]$//) ? $1 : 0 );
320              
321 90         209 $file =~ s/^(~)/glob($1)/e; #tilde expansion.
  0         0  
322              
323 90 50       430 $file = "gunzip -c $file |" if $file =~ /\.gz$/; # Handle compression
324 90 50       258 $file = "uncompress -c $file |" if $file =~ /\.Z$/;
325              
326 90 50       4416 open my $fh, $file or barf "FITS file $file not found: $!";
327 90         336 binmode $fh;
328              
329 90         170 my @extensions; # This accumulates the list in list context...
330 90         130 my $currentext=0;
331 90         184 my $pdl;
332              
333 90   100     131 hdu:{do { # Runs over extensions, in list context
  90         167  
334 94         202 my $ext_type = 'IMAGE'; # Gets the type of XTENSION if one is detected.
335 94         139 my $foo={}; # To go in pdl
336 94         146 my @history=();
337 94         161 my @cards = ();
338              
339 94         738 $pdl = $class->new;
340              
341             # If $opt->{data} is false, then the reading routines leave the
342             # file alone, so the file pointer is left at the end of the last
343             # header. Skip over the unread data to the next extension...
344              
345 94 50 66     425 if( wantarray and !$opt->{data} and @extensions) {
      33        
346 0   0     0 while( read($fh,$line,80) && ($line !~ /^XTENSION=/) && !$fh->eof() ) {
      0        
347 0         0 read($fh,$line,2880-80);
348             };
349             return @extensions
350 0 0       0 if(eof $fh);
351              
352             } else {
353 94         2776 my $ct = read $fh, $line,80;
354 94 50 66     978 barf "file $file is not in FITS-format:\n$line\n"
355             if( $nbytes==0 && ($line !~ /^SIMPLE = +T/));
356 94 50 33     481 last hdu if(eof($fh) || !$ct);
357             }
358              
359 94         185 $nbytes = 80; # Number of bytes read from this extension (1 line so far)
360              
361 94 100       610 if($line =~ /^XTENSION= \'(\w+)\s*\'/) {
    50          
362 4         12 $ext_type = $1;
363             } elsif( @extensions ) {
364              
365 0 0       0 print "Warning: expected XTENSION, found '$line'. Exiting.\n"
366             if($PDL::verbose);
367 0         0 last hdu;
368             }
369              
370 94 100       374 push(@cards,$line) if($PDL::Astro_FITS_Header);
371              
372             #
373             # If we are in scalar context, skip to the desired extension
374             # number. [This implementation is really slow since we have
375             # to read the whole file. Someone Really Ought To rework it to
376             # read individual headers and skip forward an extension at a
377             # a time with seek() calls. ]
378             # --CD
379             #
380              
381 94 100 100     437 if(!wantarray and $currentext != $extnum) {
382              
383 11         18 skipper: while(1) {
384             # Move to next record
385 11         80 $nbytes += read $fh,$line,2880-80;
386 11 50       31 barf "Unexpected end of FITS file\n" if eof $fh;
387             # Read start of next record
388 11         24 $nbytes += read $fh,$line,80;
389 11 50       54 barf "Unexpected end of FITS file\n" if eof $fh;
390             # Check if we have found the new extension
391             # if not move on
392              
393 11 50       64 $currentext++ if $line =~ /^XTENSION\= \'(\w+)\s*\'/;
394 11 50       47 if ($currentext == $extnum) {
395 11         31 $ext_type = $1;
396 11         29 last skipper;
397             }
398             }
399             } # End of skipping to desired extension
400              
401             #
402             # Snarf up the found header, and parse it if Astro::FITS::Header
403             # does not exist.
404             #
405              
406 94 100 66     371 if($PDL::Astro_FITS_Header and $opt->{afh}) {
407              
408             ## Astro::FITS::Header parsing. Snarf lines to the END card,
409             ## and pass them to Astro::FITS::Header.
410              
411 59   66     118 do {
412 704         1330 $nbytes += read $fh, $line, 80;
413 704         2343 push(@cards,$line);
414             } while(!eof($fh) && $line !~ m/^END(\s|\000)/);
415              
416 59         307 $nbytes += read $fh, my $dummy, 2879 - ($nbytes-1)%2880;
417              
418 59         750 my($hdr) = Astro::FITS::Header->new(Cards => \@cards);
419 59         108376 my(%hdrhash);
420 59         358 tie %hdrhash,"Astro::FITS::Header",$hdr;
421 59         777 $foo = \%hdrhash;
422              
423             } else {
424              
425             ## Legacy (straight header-to-hash-ref) parsing.
426             ## Cheesy but fast.
427              
428 35         44 hdr_legacy: { do {
  35         36  
429 14     14   122 no strict 'refs';
  14         25  
  14         245826  
430             # skip if the first eight characters are ' '
431             # - as seen in headers from the DSS at STScI
432 353 50       593 if (substr($line,0,8) ne " " x 8) { # If non-blank
433              
434 353         572 $name = (split(' ',substr($line,0,8)))[0];
435              
436 353         441 $rest = substr($line,8);
437              
438 353 100       446 if ($name =~ m/^HISTORY/) {
439 16         28 push @history, $rest;
440             } else {
441 337         622 $$foo{$name} = "";
442              
443 337 100       1154 $$foo{$name}=$1 if $rest =~ m|^= +([^\/\' ][^\/ ]*) *( +/(.*))?$| ;
444 337 100       1371 $$foo{$name}=$1 if $rest =~ m|^= \'(.*)\' *( +/(.*))?$| ;
445             }
446             } # non-blank
447 353 100 66     984 last hdr_legacy if ((defined $name) && $name eq "END");
448 318         602 $nbytes += read $fh, $line, 80;
449             } while !eof $fh; }
450              
451             # Clean up HISTORY card
452 35 100       74 $$foo{HISTORY} = \@history if $#history >= 0;
453              
454             # Step to end of header block in file
455 35         68 my $skip = 2879 - ($nbytes-1)%2880;
456 35 50       141 read $fh, my $dummy, $skip if $skip;
457 35         62 $nbytes += $skip;
458              
459             } # End of legacy header parsing
460              
461              
462              
463             ##############################
464             # Special case: if the file only contains
465             # extensions then read the first extension in scalar context,
466             # instead of the null zeroth extension.
467             #
468 94 100 100     518 if( !(defined $foo->{XTENSION}) # Primary header
      100        
      66        
469             and $foo->{NAXIS} == 0 # No data
470             and !wantarray # Scalar context
471             and !$explicit_extension # No HDU specifier
472             ) {
473 11 50       2014 print "rfits: Skipping null primary HDU (use [0] to force read of primary)...\n"
474             if($PDL::verbose);
475 11         89 return PDL::rfits($class,$file.'[1]',$opt);
476             }
477              
478              
479             ##########
480             # If we don't want data, return the header from the HDU. Likewise,
481             # if NAXIS is 0 then there are no data, so return the header instead.
482 83 100 66     7136 if( ! $opt->{data} || $foo->{NAXIS}==0 ) {
483             # If we're NOT reading data, then return the header instead of the
484             # image.
485              
486 2         62 $pdl = $foo;
487              
488             } else {
489             ##########
490             # Switch based on extension type to do the dirty work of reading
491             # the data. Handlers are listed in the _Extension patch-panel.
492 81 50       3279 if (ref(my $reader = $PDL::IO::FITS::_Extension->{$ext_type})) {
493             # Pass $pdl into the extension reader for easier use -- but
494             # it just gets overwritten (and disappears) if ignored.
495 81         318 $pdl = $reader->($fh,$foo,$opt,$pdl);
496             } else {
497 0 0 0     0 warn "rfits: Ignoring unknown extension '$ext_type'...\n"
498             if($PDL::verbose || $PDL::debug);
499 0         0 $pdl = undef;
500             }
501             }
502              
503             #
504             # Note -- $pdl isn't necessarily a PDL. It's only a $pdl if
505             # the extension was an IMAGE.
506             #
507 83 100       3087 push(@extensions,$pdl) if(wantarray);
508 83         537 $currentext++;
509             } while( wantarray && !eof $fh );} # Repeat if we are in list context
510              
511 79         1451 close $fh;
512 79 100       2136 return $pdl if !wantarray;
513             ## By default, ditch primary HDU placeholder
514             shift @extensions if ref($extensions[0]) eq 'HASH' and
515             $extensions[0]{SIMPLE} and
516             exists($extensions[0]{NAXIS}) and
517 2 50 33     28 $extensions[0]{NAXIS} == 0
      33        
      33        
518             ;
519 2         296 @extensions; # Return all the extensions
520             }
521              
522              
523 39     39 1 429482 sub rfits { PDL->rfits(@_); }
524              
525             sub rfitshdr {
526 0     0 1 0 my($file,$opt) = shift;
527 0         0 $opt->{data} =0;
528 0         0 PDL->rfitshdr($file,$opt);
529             }
530              
531              
532             ##############################
533             #
534             # FITS extensions patch-table links extension name to the supported reader.
535             # IMAGE extensions are a special case that gets read just like a normal
536             # FITS file.
537             #
538              
539             $PDL::IO::FITS::_Extension = {
540             IMAGE => \&_rfits_image
541             , BINTABLE => \&_rfits_bintable
542             };
543              
544              
545              
546             ##############################
547             #
548             # IMAGE extension -- this is also the default reader.
549             our $type_table = {
550             8=>$PDL_B,
551             16=>$PDL_S,
552             32=>$PDL_L,
553             64=>$PDL_LL,
554             -32=>$PDL_F,
555             -64=>$PDL_D
556             };
557              
558             our $type_table_2 = {
559             8=>byte,
560             16=>short,
561             32=>long,
562             64=>longlong,
563             -32=>float,
564             -64=>double
565             };
566              
567             sub _rfits_image($$$$) {
568 70 50   70   185 print "Reading IMAGE data...\n" if($PDL::verbose);
569 70         104 my $fh = shift; # file handle to read from
570 70         121 my $foo = shift; # $foo contains the pre-read header
571 70         119 my $opt = shift; # $opt contains the option hash
572 70         108 my $pdl = shift; # $pdl contains a pre-blessed virgin PDL
573              
574             # Setup ndarray structure
575              
576 70 50       321 if( defined($type_table->{0 + $foo->{BITPIX}}) ) {
577 70         2826 $pdl->set_datatype( $type_table->{$foo->{BITPIX}} );
578             } else {
579 0         0 die("rfits: strange BITPIX value ".$foo->{BITPIX}." in header - I give up!\n");
580             }
581              
582 70         2690 my @dims; # Store the dimenions 1..N, compute total number of pixels
583 70         119 my $i = 1;
584 70         95 my $size = 1;
585              
586             ##second part of the conditional guards against a poorly-written hdr.
587 70   66     304 while(defined( $$foo{"NAXIS$i"} ) && $i <= $$foo{NAXIS}) {
588 116         8019 $size *= $$foo{"NAXIS$i"};
589 116         3995 push @dims, $$foo{"NAXIS$i"} ; $i++;
  116         3927  
590             }
591 70         2144 $pdl->setdims([@dims]);
592              
593 70         3247 my $dref = $pdl->get_dataref();
594              
595 70 50       221 print "BITPIX = ",$$foo{BITPIX}," size = $size pixels \n"
596             if $PDL::verbose;
597              
598             # Slurp the FITS binary data
599              
600 70 50       154 print "Reading ",$size*PDL::Core::howbig($pdl->get_datatype) , " bytes\n"
601             if $PDL::verbose;
602              
603             # Read the data and pad to the next HDU
604 70         415 my $rdct = $size * PDL::Core::howbig($pdl->get_datatype);
605 70         3471 read $fh, $$dref, $rdct;
606 70         491 read $fh, my $dummy, 2880 - (($rdct-1) % 2880) - 1;
607 70         305 $pdl->upd_data();
608              
609 70 50       314 $pdl->type->bswap->($pdl) if !isbigendian(); # Need to byte swap on little endian machines
610 70 50       450 $pdl = treat_bscale($pdl, $foo) if exists $opt->{bscale};
611 70         282 $pdl->sethdr($foo);
612 70         302 $pdl->hdrcpy($opt->{hdrcpy});
613 70         221 return $pdl;
614             }
615              
616             sub treat_bscale($$){
617 70     70 0 118 my $pdl = shift;
618 70         124 my $foo = shift;
619              
620 70 50       200 print "treating bscale...\n" if($PDL::debug);
621              
622             # do we have bad values? - needs to be done before BSCALE/BZERO
623             # (at least for integers)
624 70 100 100     341 if ( $$foo{BITPIX} > 0 and exists $$foo{BLANK} ) {
    100          
625             # integer, so bad value == BLANK keyword
626 2         152 my $blank = $foo->{BLANK};
627             # do we have to do any conversion?
628 2 50       91 if ( $blank == $pdl->badvalue() ) {
629 2         9 $pdl->badflag(1);
630             } else {
631             # we change all BLANK values to the current bad value
632             # (would not be needed with a per-ndarray bad value)
633 0         0 $pdl->inplace->setvaltobad( $blank );
634             }
635             } elsif ( $foo->{BITPIX} < 0 ) {
636             # bad values are stored as NaN's in FITS
637             # let setnanbad decide if we need to change anything
638 33         3819 $pdl->inplace->setnantobad();
639             }
640 70 50 66     3199 print "FITS file may contain bad values.\n"
641             if $pdl->badflag() and $PDL::verbose;
642              
643 70         178 my ($bscale, $bzero);
644 70         300 $bscale = $$foo{BSCALE}; $bzero = $$foo{BZERO};
  70         1986  
645 70 50 33     1817 $bscale = 1 if (!defined($bscale) || $bscale eq "");
646 70 50 33     231 $bzero = 0 if (!defined($bzero) || $bzero eq "");
647 70 50       138 print "BSCALE = $bscale && BZERO = $bzero\n" if $PDL::verbose;
648              
649             # Be clever and work out the final datatype before eating
650             # memory
651             #
652             # ensure we pick an element that is not equal to the bad value
653             # (is this OTT?)
654 70         120 my $tmp;
655              
656 70 100       301 if ( $pdl->badflag() == 0 ) {
    50          
657 66         250 $tmp = $pdl->flat()->slice("0:0");
658             } elsif ( $pdl->ngood > 0 ) {
659 4         15 my $index = which( $pdl->flat()->isbad() == 0 )->at(0);
660 4         25 $tmp = $pdl->flat()->slice("${index}:${index}");
661             } else {
662             # all bad, so ignore the type conversion and return
663             # -- too lazy to include this check in the code below,
664             # so just copy the header clean up stuff
665 0 0       0 print "All elements are bad.\n" if $PDL::verbose;
666              
667 0         0 delete $$foo{BSCALE}; delete $$foo{BZERO};
  0         0  
668 0         0 $tmp = $pdl;
669             } #end of BSCALE section (whew!)
670              
671              
672 70 50       449 $tmp = $tmp*$bscale if $bscale != 1; # Dummy run on one element
673 70 50       203 $tmp = $tmp+$bzero if $bzero != 0;
674              
675 70 50       276 $pdl = $pdl->convert($tmp->type) if $tmp->get_datatype != $pdl->get_datatype;
676              
677 70 50       136 $pdl *= $bscale if $bscale != 1;
678 70 50       192 $pdl += $bzero if $bzero != 0;
679              
680 70         301 delete $$foo{BSCALE}; delete $$foo{BZERO};
  70         5789  
681 70         5610 return $pdl;
682             }
683              
684              
685             ##########
686             #
687             # bintable_handlers -- helper table for bintable_row, below.
688             #
689             # Each element of the table is named by the appropriate type letter
690             # from the FITS specification. The value is a list containing the
691             # reading and writing methods.
692             #
693             # This probably ought to be a separate class, but instead it's a tawdry
694             # imitation. Too bad -- except that the ersatz really does run faster than
695             # genuine.
696             #
697             # 0: either a data type or a constructor.
698             # 1: either a length per element or a read method.
699             # 2: either a length per element or a write method.
700             # 3: 'finish' contains finishing-up code or a byte-count to swap.
701             #
702             # Main bintable type handler table.
703             # Elements: (constructor or type, reader or nbytes, writer or nbytes,
704             # finisher or nbytes). The finisher should convert the internal reading
705             # format into the final output format, e.g. by swapping (which is done
706             # automatically in the basic case). Output has row in the 0th dim.
707             #
708             # If present, the constructor should
709             # accept ($rowlen $extra, $nrows, $$size), where $rowlen is the repeat
710             # specifier in the TFORM field, $extra is the extra characters if any
711             # (for added flavor later, if desired), and $nrows is the number of rows in
712             # the table. $$size points to a scalar value that should be incremented by the
713             # size (in bytes) of a single row of the data, for accounting purposes.
714             #
715             # If a read method is specified, it should accept:
716             # ($thing, $rownum, $strptr, $rpt, $extra)
717             # where $rpt is the repeat count and $extra is the extra characters in the
718             # specifier; and it should cut the used characters off the front of the string.
719             #
720             # If a writer is specified it should accept:
721             # ($thing, $row, $rpt, $extra)
722             # and return the generated binary string.
723             #
724             # The finisher just takes the data itself. It should:
725             # * Byteswap
726             # * Condition the data to final dimensional form (if necessary)
727             # * Apply TSCAL/TZERO keywords (as necessary)
728             #
729             # The magic numbers in the table (1,2,4,8, etc.) are kludgey -- they break
730             # the isolation of PDL size and local code -- but they are a part of the FITS
731             # standard. The code will break anyway (damn) on machines that have other
732             # sizes for these datatypes.
733             #
734              
735             $PDL::IO::FITS_bintable_handlers = {
736             'X' => [ byte # Packed bit field
737             , sub {
738             my( $pdl, $row, $strptr ) = @_; # (ignore repeat and extra)
739             my $n = $pdl->dim(0);
740             my $s = unpack( "B".$n, substr(${$strptr}, 0, int(($n+7)/8),''));
741             $s =~ tr/[01]/[\000\001]/;
742             substr( ${$pdl->get_dataref}, $n * $row, length($s)) = $s;
743             }
744             , sub {
745             my( $pdl, $row ) = @_; # Ignore extra and rpt
746             my $n = $pdl->dim(0);
747             my $p2 = byte(($pdl->slice("($row)") != 0));
748             my $s = ${$p2->get_dataref};
749             $s =~ tr/[\000\001]/[01]/;
750             pack( "B".$pdl->dim(0), $s );
751             }
752             , 1
753             ]
754             ,'A' => [ sub { # constructor # String - handle as perl list
755             my($rowlen, $extra, $nrows, $szptr) = @_;
756             $$szptr += $rowlen;
757             [(' 'x$rowlen) x $nrows];
758             }
759             , sub { # reader
760             my( $list, $row, $strptr, $rpt ) = @_;
761             $list->[$row] = substr(${$strptr},0,$rpt,'');
762             }
763             , sub { # writer
764             my($strs, $row, $rpt ) = @_;
765             my $s = substr($strs->[$row],0,$rpt);
766             $s . ' 'x($rpt - length $s);
767             }
768             , undef # no finisher needed
769             ]
770             ,'B' => [ byte, 1, 1, 1 ] # byte
771             ,'L' => [ byte, 1, 1, 1 ] # logical - treat as byte
772             ,'I' => [ short, 2, 2, 2 ] # short (no unsigned shorts?)
773             ,'J' => [ long, 4, 4, 4 ] # long
774             ,'K' => [ longlong,8, 8, 8 ] # longlong
775             ,'E' => [ float, 4, 4, 4 ] # single-precision
776             ,'D' => [ double, 8, 8, 8 ] # double-precision
777             ,'C' => [ sub { _nucomplx(float, eval '@_') }, sub { _rdcomplx(float, eval '@_') },
778             sub { _wrcomplx(float, eval '@_') }, sub { _fncomplx(float, eval '@_') }
779             ]
780             ,'M' => [ sub { _nucomplx(double, eval '@_') }, sub { _rdcomplx(double, eval '@_') },
781             sub { _wrcomplx(double, eval '@_') }, sub { _fncomplx(double, eval '@_') }
782             ]
783             ,'PB' => [ sub { _nuP(byte, eval '@_') }, sub { _rdP(byte, eval '@_') },
784             sub { _wrP(byte, eval '@_') }, sub { _fnP(byte, eval '@_') }
785             ]
786             ,'PL' => [ sub { _nuP(byte, eval '@_') }, sub { _rdP(byte, eval '@_') },
787             sub { _wrP(byte, eval '@_') }, sub { _fnP(byte, eval '@_') }
788             ]
789             ,'PI' => [ sub { _nuP(short, eval '@_') }, sub { _rdP(short, eval '@_') },
790             sub { _wrP(short, eval '@_') }, sub { _fnP(short, eval '@_') }
791             ]
792             ,'PJ' => [ sub { _nuP(long, eval '@_') }, sub { _rdP(long, eval '@_') },
793             sub { _wrP(long, eval '@_') }, sub { _fnP(long, eval '@_') }
794             ]
795             ,'PE' => [ sub { _nuP(float, eval '@_') }, sub { _rdP(float, eval '@_') },
796             sub { _wrP(float, eval '@_') }, sub { _fnP(float, eval '@_') }
797             ]
798             ,'PD' => [ sub { _nuP(double, eval '@_') }, sub { _rdP(double, eval '@_') },
799             sub { _wrP(double, eval '@_') }, sub { _fnP(double, eval '@_') }
800             ]
801             };
802              
803              
804             ##############################
805             # Helpers for complex numbers (construct/read/write/finish)
806             sub _nucomplx { # complex-number constructor
807 0     0   0 my($type, $rowlen, $extra, $nrows, $szptr) = @_;
808 0         0 $szptr += PDL::Core::howbig($type) * $nrows * 2;
809 0         0 return PDL->new_from_specification($type,2,$rowlen,$nrows);
810             }
811             sub _rdcomplx { # complex-number reader
812 0     0   0 my( $type, $pdl, $row, $strptr, $rpt ) = @_; # ignore extra
813 0         0 my $s = $pdl->get_dataref;
814 0         0 my $rlen = 2 * PDL::Core::howbig($type) * $rpt;
815 0         0 substr($$s, $row*$rlen, $rlen) = substr($strptr, 0, $rlen, '');
816             }
817             sub _wrcomplx { # complex-number writer
818 0     0   0 my( $type, $pdl, $row, $rpt ) = @_; # ignore extra
819 0         0 my $rlen = 2 * PDL::Core::howbig($type) * $rpt;
820 0         0 substr( ${$pdl->get_dataref}, $rlen * $row, $rlen );
  0         0  
821             }
822             sub _fncomplx { # complex-number finisher-upper
823 0     0   0 my( $type, $pdl, $n, $hdr, $opt) = shift;
824 0 0       0 $pdl->type->bswap->($pdl) if !isbigendian(); # Need to byte swap on little endian machines
825             warn "Ignoring poorly-defined TSCAL/TZERO for complex data in col. $n (".$hdr->{"TTYPE$n"}.").\n"
826 0 0 0     0 if( length($hdr->{"TSCAL$n"}) or length($hdr->{"TZERO$n"}) );
827 0         0 return $pdl->reorder(2,1,0);
828             }
829              
830             ##############################
831             # Helpers for variable-length array types (construct/read/write/finish)
832             # These look just like the complex-number case, except that they use $extra to determine the
833             # size of the 0 dimension.
834             sub _nuP {
835 8     8   599 my( $type, $rowlen, $extra, $nrows, $szptr, $hdr, $i, $tbl ) = @_;
836 8         53 $extra =~ s/\((.*)\)/$1/; # strip parens from $extra in-place
837 8         18 $$szptr += 8;
838 8 50       31 if($rowlen != 1) {
839 0         0 die("rfits: variable-length record has a repeat count that isn't unity! (got $rowlen); I give up.");
840             }
841             # declare the PDL. Fill it with the blank value or (failing that) 0.
842             # Since P repeat count is required to be 0 or 1, we don't need an additional dimension for the
843             # repeat count -- the variable-length rows take that role.
844 8         148 my $pdl = PDL->new_from_specification($type, $extra, $nrows);
845 8   50     57 $pdl .= ($hdr->{"TNULL$i"} || 0);
846 8         76 my $lenpdl = zeroes(long, $nrows);
847 8         56 $tbl->{"len_".$hdr->{"TTYPE$i"}} = $lenpdl;
848 8         537 return $pdl;
849             }
850              
851             sub _rdP {
852 3840     3840   12092 my( $type, $pdl, $row, $strptr, $rpt, $extra, $heap_ptr, $tbl, $i ) = @_;
853 3840         19263 $extra =~ s/\((.*)\)/$1/;
854 3840         18909 my $s = $pdl->get_dataref;
855             # Read current offset and length
856 3840         11469 my $oflen = pdl(long,0,0);
857 3840         13968 my $ofs = $oflen->get_dataref;
858 3840         11055 substr($$ofs,0,8) = substr($$strptr, 0, 8, '');
859 3840         12528 $oflen->upd_data;
860 3840 50       12340 $oflen->type->bswap->($oflen) if !isbigendian(); # Need to byte swap on little endian machines
861             # Now get 'em
862 3840         11782 my $rlen = $extra * PDL::Core::howbig($type); # rpt should be unity, otherwise we'd have to multiply it in.
863 3840         11758 my $readlen = $oflen->at(0) * PDL::Core::howbig($type);
864             # Store the length of this row in the header field.
865 3840         26119 $tbl->{"len_".$tbl->{hdr}{"TTYPE$i"}}->dice_axis(0,$row) .= $oflen->at(0);
866 3840 50       35058 print "_rdP: pdl is ",join("x",$pdl->dims),"; reading row $row - readlen is $readlen\n"
867             if($PDL::debug);
868             # Copy the data into the output PDL.
869 3840         11451 my $of = $oflen->at(1);
870 3840         13144 substr($$s, $row*$rlen, $readlen) = substr($$heap_ptr, $of, $readlen);
871 3840         37638 $pdl->upd_data;
872             }
873              
874             sub _wrP {
875 0     0   0 die "This code path should never execute - you are trying to write a variable-length array via direct handler, which is wrong. Check the code path in PDL::wfits.\n";
876             }
877              
878             sub _fnP {
879 8     8   26 my( $type, $pdl, $n, $hdr, $opt ) = @_;
880 8 50       27 $pdl->type->bswap->($pdl) if !isbigendian(); # Need to byte swap on little endian machines
881 8 50       73 my $tzero = defined($hdr->{"TZERO$n"}) ? $hdr->{"TZERO$n"} : 0.0;
882 8 50       386 my $tscal = defined($hdr->{"TSCAL$n"}) ? $hdr->{"TSCAL$n"} : 1.0;
883 8         322 my $valid_tzero = ($tzero != 0.0);
884 8         16 my $valid_tscal = ($tscal != 1.0);
885             warn "Ignoring TSCAL/TZERO keywords for binary table array column - sorry, my mind is blown!\n"
886 8 50 33     514 if grep defined && length, map $hdr->{"$_$n"}, qw(TZERO TSCAL);
887 8         765 return $pdl->mv(-1,0);
888             }
889              
890             ##############################
891             #
892             # _rfits_bintable -- snarf up a binary table, returning the named columns
893             # in a hash ref, each element of which is a PDL or list ref according to the
894             # header.
895             #
896              
897             sub _rfits_bintable ($$$$) {
898 11     11   23 my $fh = shift;
899 11         20 my $hdr = shift;
900 11         22 my $opt = shift;
901             ##shift; ### (ignore $pdl argument)
902              
903 11 50       64 warn "Warning: BINTABLE extension should have BITPIX=8, found ".$hdr->{BITPIX}.". Winging it...\n" unless($hdr->{BITPIX} == 8);
904              
905             ### Allocate the main table hash
906 11         690 my $tbl = { hdr=>$hdr, tbl=>'binary' }; # Table is indexed by name
907 11         24 my $tmp = []; # Temporary space is indexed by col. no.
908              
909             ### Allocate all the columns of the table, checking for consistency
910             ### and name duplication.
911              
912 11 50       66 barf "Binary extension has no fields (TFIELDS=0)" unless($hdr->{TFIELDS});
913 11         646 my $rowlen = 0;
914              
915 11         43 for my $i(1..$hdr->{TFIELDS}) {
916 17         613 my $iter;
917 17   50     95 my $name = $tmp->[$i]{name} = $hdr->{"TTYPE$i"} || "COL";
918              
919             ### Allocate some temp space for dealing with this column
920 17         1061 my $tmpcol = $tmp->[$i] = {};
921              
922             ### Check for duplicate name and change accordingly...
923 17   33     103 while( defined( $tbl->{ $name } ) || ($name eq "COL") ) {
924 0         0 $iter++;
925 0         0 $name = ($hdr->{"TTYPE$i"} )."_$iter";
926             }
927              
928             # (Check avoids scrozzling comment fields unnecessarily)
929 17 50       62 $hdr->{"TTYPE$i"} = $name unless($hdr->{"TTYPE$i"} eq $name);
930 17         945 $tmpcol->{name} = $name;
931              
932 17 50       55 if( ($hdr->{"TFORM$i"}) =~ m/(\d*)(P?.)(.*)/ ) {
933 17         1038 ($tmpcol->{rpt}, $tmpcol->{type}, $tmpcol->{extra}) = ($1,$2,$3);
934             # added by DJB 03.18/04 - works for my data file but is it correct?
935 17   50     48 $tmpcol->{rpt} ||= 1;
936             } else {
937             barf "Couldn't parse BINTABLE form '"
938             . $hdr->{"TFORM$i"}
939             . "' for column $i ("
940             . $hdr->{"TTYPE$i"}
941 0 0       0 . ")\n" if $hdr->{"TFORM$i"};
942 0         0 barf "BINTABLE header is missing a crucial field, TFORM$i. I give up.\n";
943             }
944              
945             # "A bit array consists of an integral number of bytes with trailing bits zero"
946 17 50       59 $tmpcol->{rpt} = PDL::ceil($tmpcol->{rpt}/8) if ($tmpcol->{type} eq 'X');
947              
948             $tmpcol->{handler} = # sic - assignment
949             $PDL::IO::FITS_bintable_handlers->{ $tmpcol->{type} }
950             or
951             barf "Unknown type ".$hdr->{"TFORM$i"}." in BINTABLE column $i "."("
952 17 50       91 . $hdr->{"TTYPE$i"}
953             . ")\n That invalidates the byte count, so I give up.\n" ;
954              
955              
956             ### Allocate the actual data space and increment the row length
957              
958 17         41 my $foo = $tmpcol->{handler}[0];
959 17 100       64 if( ref ($foo) eq 'CODE' ) {
960             $tmpcol->{data} = $tbl->{$name} =
961 9         31 &{$foo}( $tmpcol->{rpt}
962             , $tmpcol->{extra}
963             , $hdr->{NAXIS2}
964 9         34 , \$rowlen
965             , $hdr # hdr and column number are passed in, in case extra info needs to be gleaned.
966             , $i
967             , $tbl
968             );
969             } else {
970             $tmpcol->{data} = $tbl->{$name} =
971             PDL->new_from_specification(
972             $foo
973             , $tmpcol->{rpt},
974 8   50     29 , $hdr->{NAXIS2} || 1
975             );
976              
977 8         655 $rowlen += PDL::Core::howbig($foo) * $tmpcol->{rpt};
978             }
979              
980 17 50       101 print "Prefrobnicated col. $i "."(".$hdr->{"TTYPE$i"}.")\ttype is ".$hdr->{"TFORM$i"}."\t length is now $rowlen\n" if $PDL::debug;
981              
982              
983             } ### End of prefrobnication loop...
984              
985             barf "Calculated row length is $rowlen, hdr claims ".$hdr->{NAXIS1}
986             . ". Giving up. (Set \$PDL::debug for more detailed info)\n"
987 11 50       56 if($rowlen != $hdr->{NAXIS1});
988              
989             ### Snarf up the whole extension, and pad to 2880 bytes...
990 11         650 my ($rawtable, $heap, $n1, $n2);
991              
992             # n1 gets number of bytes in table plus gap
993 11         38 $n1 = $hdr->{NAXIS1} * $hdr->{NAXIS2};
994 11 50       1140 if($hdr->{THEAP}) {
995 0 0       0 if($hdr->{THEAP} < $n1) {
996 0         0 die("Inconsistent THEAP keyword in binary table\n");
997             } else {
998 0         0 $n1 = $hdr->{THEAP};
999             }
1000             }
1001              
1002             # n2 gets number of bytes in heap (PCOUNT - gap).
1003 11 50       601 $n2 = $hdr->{PCOUNT} + ($hdr->{THEAP} ? ($hdr->{NAXIS1}*$hdr->{NAXIS2} - $hdr->{THEAP}) : 0);
1004 11         1106 $n2 = ($n1+$n2-1)+2880 - (($n1+$n2-1) % 2880) - $n1;
1005              
1006 11 50       30 print "Reading $n1 bytes of table data and $n2 bytes of heap data....\n"
1007             if($PDL::verbose);
1008 11         83 read $fh, $rawtable, $n1;
1009              
1010 11 50       32 if($n2) {
1011 11         2021 read $fh, $heap, $n2;
1012             } else {
1013 0         0 $heap = which(pdl(0)); # empty PDL
1014             }
1015              
1016             ### Frobnicate the rows, one at a time.
1017 11         75 for my $row(0..$hdr->{NAXIS2}-1) {
1018 3852         272253 my $prelen = length($rawtable);
1019 3852         13690 for my $i(1..$hdr->{TFIELDS}) {
1020 3876         249562 my $tmpcol = $tmp->[$i];
1021 3876         7986 my $reader = $tmpcol->{handler}[1];
1022 3876 100       9631 if(ref $reader eq 'CODE') {
    50          
1023 3844         8380 &{$reader}( $tmpcol->{data}
1024             , $row
1025             , \$rawtable
1026             , $tmpcol->{rpt}
1027             , $tmpcol->{extra}
1028 3844         9816 , \$heap
1029             , $tbl
1030             , $i
1031             );
1032             } elsif(ref $tmpcol->{data} eq 'PDL') {
1033 32         37 my $rlen = $reader * $tmpcol->{rpt};
1034              
1035 32         53 substr( ${$tmpcol->{data}->get_dataref()}, $rlen * $row, $rlen ) =
  32         105  
1036             substr( $rawtable, 0, $rlen, '');
1037 32         68 $tmpcol->{data}->upd_data;
1038              
1039             } else {
1040 0         0 die ("rfits: Bug detected: inconsistent types in BINTABLE reader\n");
1041             }
1042              
1043             } # End of TFIELDS loop
1044              
1045 3852 50       24501 if(length($rawtable) ne $prelen - $hdr->{NAXIS1}) {
1046 0         0 die "rfits BINTABLE: Something got screwed up -- expected a length of $prelen - $hdr->{NAXIS1}, got ".length($rawtable).". Giving up.\n";
1047             }
1048             } # End of NAXIS2 loop
1049              
1050             #
1051             # Note: the above code tickles a bug in most versions of the emacs
1052             # prettyprinter. The following "for my $i..." should be indented
1053             # two spaces.
1054             #
1055              
1056             ### Postfrobnicate the columns.
1057 11         687 for my $i(1..$hdr->{TFIELDS}) { # Postfrobnication loop
1058 17         589 my $tmpcol = $tmp->[$i];
1059 17         34 my $post = $tmpcol->{handler}[3];
1060 17 100       80 if(ref $post eq 'CODE') {
    100          
    50          
1061             # Do postprocessing on all special types
1062 8         34 $tbl->{$tmpcol->{name}} = &$post($tmpcol->{data}, $i, $hdr, $opt);
1063             } elsif( (ref ($tmpcol->{data})) eq 'PDL' ) {
1064             # Do standard PDL-type postprocessing
1065             # Do swapping as necessary
1066 8 50       39 $tmpcol->{data}->type->bswap->($tmpcol->{data}) if !isbigendian();
1067              
1068             # Apply scaling and badval keys, which are illegal for A, L, and X
1069             # types but legal for anyone else. (A shouldn't be here, L and X
1070             # might be)
1071 8 50       19 if($opt->{bscale}) {
1072 8 50       33 my $tzero = defined($hdr->{"TZERO$i"}) ? $hdr->{"TZERO$i"} : 0.0;
1073 8 50       306 my $tscal = defined($hdr->{"TSCAL$i"}) ? $hdr->{"TSCAL$i"} : 1.0;
1074              
1075             # The $valid_ flags let us avoid unnecessary arithmetic.
1076 8         286 my $valid_tzero = ($tzero != 0.0);
1077 8         13 my $valid_tscal = ($tscal != 1.0);
1078              
1079 8 50 33     30 if ( $valid_tzero or $valid_tscal ) {
1080 0 0       0 if ( $tmpcol->{type} =~ m/[ALX]/i ) {
1081             warn "Ignoring illegal TSCAL/TZERO keywords for col $i (" .
1082 0         0 $tmpcol->{name} . "); type is $tmpcol->{type})\n";
1083              
1084             } else { # Not an illegal type -- do the scaling
1085             # (Normal execution path)
1086             # Use PDL's cleverness to work out the final datatype...
1087 0         0 my $tmp;
1088 0         0 my $pdl = $tmpcol->{data};
1089              
1090 0 0       0 if($pdl->badflag() == 0) {
    0          
1091 0         0 $tmp = $pdl->flat()->slice("0:0");
1092             } elsif($pdl->ngood > 0) {
1093 0         0 my $index = which( $pdl->flat()->isbad()==0 )->at(0);
1094 0         0 $tmp = $pdl->flat()->slice("${index}:${index}");
1095             } else { # Do nothing if it's all bad....
1096 0         0 $tmp = $pdl;
1097             }
1098              
1099             # Figure out the type by scaling the single element.
1100 0         0 $tmp = ($tmp - $tzero) * $tscal;
1101             # Convert the whole PDL as necessary for the scaling.
1102 0 0       0 $tmpcol->{data} = $pdl->convert($tmp->type)
1103             if($tmp->get_datatype != $pdl->get_datatype);
1104             # Do the scaling.
1105 0         0 $tmpcol->{data} -= $tzero;
1106 0         0 $tmpcol->{data} *= $tscal;
1107              
1108             } # End of legal-type conditional
1109             } # End of valid_ conditional
1110              
1111 8         35 delete $hdr->{"TZERO$i"};
1112 8         1678 delete $hdr->{"TSCAL$i"};
1113              
1114             } else { # $opt->{bscale} is zero; don't scale.
1115             # Instead, copy factors into individual column headers.
1116 0         0 my %foo = ("TZERO$i"=>"BZERO",
1117             "TSCAL$i"=>"BSCALE",
1118             "TUNIT$i"=>"BUNIT");
1119 0         0 for my $x(keys %foo) {
1120             $tmpcol->{data}->hdr->{$foo{$x}} = $hdr->{$x}
1121 0 0       0 if( defined($hdr->{$x}) );
1122             }
1123             } # End of bscale checking...
1124              
1125             # Try to grab a TDIM dimension list...
1126 8         1500 my @tdims = ();
1127 8         45 $tmpcol->{data}->hdrcpy(1);
1128              
1129 8 50       44 if(exists($hdr->{"TDIM$i"})) {
1130 0 0       0 if($hdr->{"TDIM$i"} =~ m/\((\s*\d+(\s*\,\s*\d+)*\s*)\)/) {
1131 0         0 my $x = $1;
1132 0         0 @tdims = map { $_+0 } split(/\,/,$x);
  0         0  
1133 0         0 my $tdims = pdl(@tdims);
1134 0         0 my $tds = $tdims->prodover;
1135             die("rfits: TDIM$i is too big in binary table. I give up.\n")
1136 0 0       0 if $tds > $tmpcol->{data}->dim(0);
1137             warn "rfits: WARNING: TDIM$i is too small in binary table. Carrying on...\n"
1138 0 0       0 if $tds < $tmpcol->{data}->dim(0);
1139              
1140 0         0 $tmpcol->{data}->hdrcpy(1);
1141 0         0 my $td = $tmpcol->{data}->transpose;
1142 0         0 $tbl->{$tmpcol->{name}} = $td->reshape($td->dim(0),@tdims);
1143             } else {
1144 0         0 warn "rfits: WARNING: invalid TDIM$i field in binary table. Ignoring.\n";
1145             }
1146             } else {
1147             # Copy the PDL out to the table itself.
1148 8 50 33     109 if($hdr->{NAXIS2} > 0 && $tmpcol->{rpt}>0) {
1149             $tbl->{$tmpcol->{name}} =
1150             ( ( $tmpcol->{data}->dim(0) == 1 )
1151             ? $tmpcol->{data}->slice("(0)")
1152             : $tmpcol->{data}->transpose
1153 8 50       555 );
1154             }
1155             }
1156              
1157             # End of PDL postfrobnication case
1158             } elsif(defined $post) {
1159              
1160             warn "Postfrobnication bug detected in column $i ("
1161 0         0 . $tmpcol->{name}. "). Winging it.\n";
1162              
1163             }
1164             } # End of postfrobnication loop over columns
1165              
1166             ### Check whether this is actually a compressed image, in which case we hand it off to the image decompressor
1167 11 100 66     58 return $tbl if !($hdr->{ZIMAGE} && $hdr->{ZCMPTYPE} && $opt->{expand});
      66        
1168 7         667 eval { require PDL::Compression };
  7         1670  
1169 7 50       23 die "rfits: error while loading PDL::Compression to unpack tile-compressed image.\n\t$@\n\tUse option expand=>0 to get the binary table.\n" if $@;
1170 7         26 return _rfits_unpack_zimage($tbl,$opt);
1171             }
1172              
1173             ## List of the eight mandatory keywords and their ZIMAGE preservation pigeonholes, for copying after we
1174             ## expand an image.
1175             our $hdrconv = {
1176             "ZSIMPLE" => "SIMPLE",
1177             "ZTENSION" => "XTENSION",
1178             "ZEXTEND" => "EXTEND",
1179             "ZBLOCKED" => "BLOCKED",
1180             "ZPCOUNT" => "PCOUNT",
1181             "ZGCOUNT" => "GCOUNT",
1182             "ZHECKSUM" => "CHECKSUM",
1183             "ZDATASUM" => "DATASUM"
1184             };
1185              
1186             ##############################
1187             ##############################
1188             #
1189             # _rfits_unpack_zimage - unpack a binary table that actually contains a compressed image
1190             #
1191             # This is implemented to support the partial spec by White, Greenfield, Pence, & Tody dated Oct 21, 1999,
1192             # with reverse-engineered bits from the CFITSIO3240 library where the spec comes up short.
1193             #
1194              
1195             ## keyword is a compression algorithm name; value is an array ref containing tile compressor/uncompressor.
1196             ## The compressor/uncompressor takes (nx, ny, data) and returns the compressed/uncompressed data.
1197             ## The four currently (2010) supported-by-CFITSIO compressors are listed. Not all have been ported, hence
1198             ## the "undef"s in the table. --CED.
1199              
1200             ## Master jump table for compressors/uncompressors.
1201             ## 0 element of each array ref is the compressor; 1 element is the uncompressor.
1202             ## Uncompressed tiles are reshaped to rows of a tile table handed in (to the compressor)
1203             ## or out (of the uncompressor)
1204             our $tile_compressors = {
1205             'GZIP_1' => undef,
1206             'RICE_1' => [
1207             sub { ### RICE_1 compressor
1208             eval { require PDL::Compression };
1209             die "rfits: error while loading PDL::Compression to pack tile-compressed image.\n\t$@\n" if $@;
1210             my ($tiles, $tbl, $params) = @_;
1211             my $blocksize = $params->{BLOCKSIZE} || 32;
1212             my $tiles_ndims = $tiles->ndims;
1213             $tiles = $tiles->clump(1..$tiles_ndims-1) if $tiles_ndims > 2;
1214             my ($compressed,undef,undef,$len) = $tiles->rice_compress($blocksize);
1215             $tbl->{ZNAME1} = "BLOCKSIZE";
1216             $tbl->{ZVAL1} = $blocksize;
1217             $tbl->{ZNAME2} = "BYTEPIX";
1218             $tbl->{ZVAL2} = PDL::howbig($tiles->get_datatype);
1219             # Convert the compressed data to a byte array...
1220             if($tbl->{ZVAL2} != 1) {
1221             my @dims = $compressed->dims;
1222             $dims[0] *= $tbl->{ZVAL2};
1223             my $cd2 = zeroes( byte, @dims );
1224             $cd2->update_data_from(${ $compressed->get_dataref });
1225             $compressed = $cd2;
1226             }
1227             $tbl->{COMPRESSED_DATA} = $compressed->mv(0,-1);
1228             $tbl->{len_COMPRESSED_DATA} = $len;
1229             },
1230             sub { ### RICE_1 expander
1231             my ($tilesize, $tbl, $params) = @_;
1232             my $compressed = $tbl->{COMPRESSED_DATA} -> mv(-1,0);
1233             my $bytepix = $params->{BYTEPIX} || 4;
1234             # Put the compressed tile bitstream into a variable of appropriate type.
1235             # This works by direct copying of the PDL data, which sidesteps local
1236             # byteswap issues in the usual case that the compressed stream is type
1237             # byte. But it does add the extra complication that we have to pad the
1238             # compressed array out to a factor-of-n elements in certain cases.
1239             if( PDL::howbig($compressed->get_datatype) != $bytepix ) {
1240             my @dims = $compressed->dims;
1241             my $newdim0;
1242             my $scaledim0;
1243             $scaledim0 = $dims[0] * PDL::howbig($compressed->get_datatype) / $bytepix;
1244             $newdim0 = pdl($scaledim0)->ceil;
1245             if($scaledim0 != $newdim0) {
1246             my $padding = zeroes($compressed->type,
1247             ($newdim0-$scaledim0) * $bytepix / PDL::howbig($compressed->get_datatype),
1248             @dims[1..$#dims]
1249             );
1250             $compressed = $compressed->append($padding);
1251             }
1252             my $c2 = zeroes( $type_table_2->{$bytepix * 8}, $newdim0, @dims[1..$#dims] );
1253             my $c2dr = $c2->get_dataref;
1254             my $cdr = $compressed->get_dataref;
1255             substr($$c2dr,0,length($$cdr)) = $$cdr;
1256             $c2->upd_data;
1257             $compressed = $c2;
1258             }
1259             $compressed->rice_expand($tbl->{len_COMPRESSED_DATA}, $tilesize, $params->{BLOCKSIZE} || 32);
1260             }
1261             ],
1262             'PLIO_1' => undef,
1263             'HCOMPRESS_1' => undef,
1264             };
1265              
1266             sub _rfits_unpack_zimage($$$) {
1267 7     7   14 my $tbl = shift;
1268 7         15 my $opt = shift;
1269              
1270 7         16 my $hdr = $tbl->{hdr};
1271              
1272 7         29 (my $cmptype = $hdr->{ZCMPTYPE}) =~ s/\s+//g;
1273 7         296 my $tc = $tile_compressors->{$cmptype};
1274 7 50       38 unless(defined $tc) {
1275 0         0 warn "WARNING: rfits: Compressed image has unsupported comp. type ('$hdr->{ZCMPTYPE}').\n";
1276 0         0 return $tbl;
1277             }
1278              
1279             #############
1280             # Declare the output image
1281 7         25 my $type = $type_table_2->{$hdr->{ZBITPIX}};
1282 7 50       315 unless($type) {
1283 0         0 warn "WARNING: rfits: unrecognized ZBITPIX value $hdr->{ZBITPIX} in compressed image. Assuming -64.\n";
1284 0         0 $type = $type_table_2->{-64};
1285             }
1286 7         44 my @dims = @$hdr{map "ZNAXIS$_", 1..$hdr->{ZNAXIS}};
1287 7         994 my $pdl = PDL->new_from_specification( $type, @dims );
1288              
1289             ############
1290             # Calculate tile size and allocate a working tile.
1291             my @tiledims = map $hdr->{"ZTILE$_"} || (($_==1) ? $hdr->{ZNAXIS1} : 1),
1292 7   66     35 1..$hdr->{ZNAXIS};
1293 7         969 my $tiledims = pdl(@tiledims);
1294 7         425 my $tilesize = $tiledims->prodover;
1295             ###########
1296             # Calculate tile counts and compare to the number of stored tiles
1297 7         26 my $ntiles = ( pdl(@dims) / pdl(@tiledims) )->ceil;
1298 7         102 my $tilecount = $ntiles->prodover;
1299             warn "WARNING: rfits: compressed data has $hdr->{NAXIS2} rows; we expected $tilecount (",join("x",list $ntiles),"). Winging it...\n"
1300 7 50       67 if $tilecount != $tbl->{COMPRESSED_DATA}->dim(0);
1301              
1302             ##########
1303             # Quantization - ignore for now
1304             warn "WARNING: rfits: ignoring quantization/dithering (ZQUANTIZ=$hdr->{ZQUANTIZ})\n"
1305 7 50       58 if $hdr->{ZQUANTIZ};
1306              
1307             ##########
1308             # Snarf up compression parameters
1309 7         294 my $params = {};
1310 7         14 my $i = 1;
1311 7         29 while( $hdr->{"ZNAME$i"} ) {
1312 14         587 $params->{ $hdr->{"ZNAME$i"} } = $hdr->{"ZVAL$i"};
1313 14         1100 $i++;
1314             }
1315              
1316             ##########
1317             # Enumerate tile coordinates for looping, and the corresponding row number
1318 7         196 my ($step, @steps) = 1;
1319 7         94 for my $i(0..$ntiles->nelem-1) {
1320 15         23 push(@steps, $step);
1321 15         31 $step *= $ntiles->at($i);
1322             }
1323              
1324             # $tiledex is 2-D (coordinate-index, list-index) and enumerates all tiles by image
1325             # location
1326 7         36 my $tiledex = PDL::ndcoords($ntiles->list)->mv(0,-1)->clump($ntiles->dim(0))->mv(-1,0);
1327              
1328             ##########
1329             # Restore all the tiles at once
1330 7         60 my $tiles = $tc->[1]->( $tilesize, $tbl, $params ); # gets a (tilesize x ntiles) output
1331 7         67 my $patchup = which($tbl->{len_COMPRESSED_DATA} <= 0);
1332 7 50       61 if($patchup->nelem) {
1333             die "rfits: need some uncompressed data for missing compressed rows, but none were found!\n"
1334 0 0       0 unless defined $tbl->{UNCOMPRESSED_DATA};
1335             die "rfits: tile size is $tilesize, but uncompressed data rows have size ".$tbl->{UNCOMPRESSED_DATA}->dim(1)."\n"
1336 0 0       0 if $tbl->{UNCOMPRESSED_DATA}->dim(1) != $tilesize;
1337 0         0 $tiles->dice_axis(1,$patchup) .= $tbl->{UNCOMPRESSED_DATA}->dice_axis(0,$patchup)->transpose;
1338             }
1339              
1340             ##########
1341             # Slice up the output image plane into tiles, and use the broadcasting engine
1342             # to assign everything to them.
1343 7         46 my $cutup = $pdl->range( $tiledex, [@tiledims], 't') # < ntiles, tilesize0..tilesizen >
1344             ->mv(0,-1) # < tilesize0..tilesizen, ntiles >
1345             ->clump($tiledims->nelem); # < tilesize, ntiles >
1346 7         32 $cutup .= $tiles; # dump all the tiles at once into the image - they flow back to $pdl.
1347 7         179605 $cutup->sever; # sever connection to prevent expensive future dataflow.
1348              
1349             ##########
1350             # Perform scaling if necessary ( Just the ZIMAGE quantization step )
1351             # bscaling is handled farther down with treat_bscale.
1352              
1353 7 50       175 $pdl *= $hdr->{ZSCALE} if defined($hdr->{ZSCALE});
1354 7 50       562 $pdl += $hdr->{ZZERO} if defined($hdr->{ZZERO});
1355              
1356             ##########
1357             # Put the FITS header into the newly reconstructed image.
1358 7         324 delete $hdr->{PCOUNT};
1359 7         3926 delete $hdr->{GCOUNT};
1360              
1361             # Copy the mandated name-conversions
1362 7         3423 for my $k (grep $hdr->{$_}, keys %$hdrconv) {
1363 8         3484 $hdr->{$hdrconv->{$k}} = $hdr->{$k};
1364 8         4737 delete $hdr->{$k};
1365             }
1366              
1367             # Copy the ZNAXIS* keywords to NAXIS*
1368             $hdr->{$_} = $hdr->{"Z$_"}
1369 7   66     3103 for grep /^NAXIS/ && defined($hdr->{"Z$_"}), keys %$hdr;
1370              
1371             # Clean up the ZFOO extensions and table cruft
1372 7         10430 delete @$hdr{grep m/^(?:TTYPE|TFORM|Z|TFIELDS$)/, keys %$hdr};
1373              
1374             $pdl = treat_bscale($pdl, $hdr)
1375 7 50 33     44360 if exists $hdr->{BSCALE} || exists $hdr->{BLANK};
1376 7         227 $pdl->sethdr($hdr);
1377 7         47 $pdl->hdrcpy($opt->{hdrcpy});
1378              
1379 7         907 return $pdl;
1380             }
1381              
1382             =head2 wfits()
1383              
1384             =for ref
1385              
1386             Simple PDL FITS writer
1387              
1388             =for example
1389              
1390             wfits $pdl, 'filename.fits', [$BITPIX], [$COMPRESSION_OPTIONS];
1391             wfits $hash, 'filename.fits', [$OPTIONS];
1392             $pdl->wfits('foo.fits',-32);
1393              
1394             Suffix magic:
1395              
1396             # Automatically compress through pipe to gzip
1397             wfits $pdl, 'filename.fits.gz';
1398             # Automatically compress through pipe to compress
1399             wfits $pdl, 'filename.fits.Z';
1400              
1401             Tilde expansion:
1402              
1403             #expand leading ~ to home directory (using glob())
1404             wfits $pdl, '~/filename.fits';
1405              
1406             =over 3
1407              
1408             =item * Ordinary (PDL) data handling:
1409              
1410             If the first argument is a PDL, then the PDL is written out as an
1411             ordinary FITS file with a single Header/Data Unit of data.
1412              
1413             $BITPIX is then optional and coerces the output data type according to
1414             the standard FITS convention for the BITPIX field (with positive
1415             values representing integer types and negative values representing
1416             floating-point types).
1417              
1418             If C<$pdl> has a FITS header attached to it (actually, any hash that
1419             contains a C<< SIMPLE=>T >> keyword), then that FITS header is written
1420             out to the file. The image dimension tags are adjusted to the actual
1421             dataset. If there's a mismatch between the dimensions of the data and
1422             the dimensions in the FITS header, then the header gets corrected and
1423             a warning is printed.
1424              
1425             If C<$pdl> is a slice of another PDL with a FITS header already
1426             present (and header copying enabled), then you must be careful.
1427             C will remove any extraneous C keywords (per the FITS
1428             standard), and also remove the other keywords associated with that
1429             axis: C, C, C, C, and C. This
1430             may cause confusion if the slice is NOT out of the last dimension:
1431             C and you would be best off adjusting
1432             the header yourself before calling C.
1433              
1434             You can tile-compress images according to the CFITSIO extension to the
1435             FITS standard, by adding an option hash to the arguments:
1436              
1437             =over 3
1438              
1439             =item compress
1440              
1441             This can be either unity, in which case Rice compression is used,
1442             or a (case-insensitive) string matching the CFITSIO compression
1443             type names. Currently supported compression algorithms are:
1444              
1445             =over 3
1446              
1447             =item * RICE_1 - linear Rice compression
1448              
1449             This uses limited-symbol-length Rice compression, which works well on
1450             low entropy image data (where most pixels differ from their neighbors
1451             by much less than the dynamic range of the image).
1452              
1453             =back
1454              
1455             =item BLOCKSIZE (RICE_1 only; default C<32>)
1456              
1457             For RICE_1, indicates the number of pixel samples to use
1458             for each compression block within the compression algorithm. The
1459             blocksize is independent of the tile dimensions. For RICE
1460             compression the pixels from each tile are arranged in normal pixel
1461             order (early dims fastest) and compressed as a linear stream.
1462              
1463             =back
1464              
1465             =item * Table handling:
1466              
1467             If you feed in a hash ref instead of a PDL, then the hash ref is
1468             written out as a binary table extension. The hash ref keys are
1469             treated as column names, and their values are treated as the data to
1470             be put in each column.
1471              
1472             For numeric information, the hash values should contain PDLs. The 0th
1473             dim of the PDL runs across rows, and higher dims are written as
1474             multi-value entries in the table (e.g. a 7x5 PDL will yield a single
1475             named column with 7 rows and 5 numerical entries per row, in a binary
1476             table). Note that this is slightly different from the usual concept
1477             of broadcasting, in which dimension 1 runs across rows.
1478              
1479             ASCII tables only allow one entry per column in each row, so
1480             if you plan to write an ASCII table then all of the values of C<$hash>
1481             should have at most one dim.
1482              
1483             All of the columns' 0 dims must agree in the broadcasting sense. That is to
1484             say, the 0th dimension of all of the values of C<$hash> should be the
1485             same (indicating that all columns have the same number of rows). As
1486             an exception, if the 0th dim of any of the values is 1, or if that
1487             value is a PDL scalar (with 0 dims), then that value is "broadcasted"
1488             over -- copied into all rows.
1489              
1490             Data dimensions higher than 2 are preserved in binary tables,
1491             via the TDIMn field (e.g. a 7x5x3 PDL is stored internally as
1492             seven rows with 15 numerical entries per row, and reconstituted
1493             as a 7x5x3 PDL on read).
1494              
1495             Non-PDL Perl scalars are treated as strings, even if they contain
1496             numerical values. For example, a list ref containing 7 values is
1497             treated as 7 rows containing one string each. There is no such thing
1498             as a multi-string column in FITS tables, so any nonscalar values in
1499             the list are stringified before being written. For example, if you
1500             pass in a perl list of 7 PDLs, each PDL will be stringified before
1501             being written, just as if you printed it to the screen. This is
1502             probably not what you want -- you should use L to connect
1503             the separate PDLs into a single one. (e.g. C<$x-Eglue(1,$y,$c)-Emv(1,0)>)
1504              
1505             The column names are case-insensitive, but by convention the keys of
1506             C<$hash> should normally be ALL CAPS, containing only digits, capital
1507             letters, hyphens, and underscores. If you include other characters,
1508             then case is smashed to ALL CAPS, whitespace is converted to
1509             underscores, and unrecognized characters are ignored -- so if you
1510             include the key "Au Purity (%)", it will be written to the file as a
1511             column that is named "AU_PURITY". Since this is not guaranteed to
1512             produce unique column names, subsequent columns by the same name are
1513             disambiguated by the addition of numbers.
1514              
1515             You can specify the use of variable-length rows in the output, saving
1516             space in the file. To specify variable length rows for a column named
1517             "FOO", you can include a separate key "len_FOO" in the hash to be
1518             written. The key's value should be a PDL containing the number of
1519             actual samples in each row. The result is a FITS P-type variable
1520             length column that, upon read with C, will restore to a field
1521             named FOO and a corresponding field named "len_FOO". Invalid data in
1522             the final PDL consist of a padding value (which defaults to 0 but
1523             which you may set by including a TNULL field in the hdr specificaion).
1524             Variable length arrays must be 2-D PDLs, with the variable length in
1525             the 1 dimension.
1526              
1527             Two further special keys, 'hdr' and 'tbl', can contain
1528             meta-information about the type of table you want to write. You may
1529             override them by including an C<$OPTIONS> hash with a 'hdr' and/or
1530             'tbl' key.
1531              
1532             The 'tbl' key, if it exists, must contain either 'ASCII' or 'binary'
1533             (case-insensitive), indicating whether to write an ascii or binary
1534             table. The default is binary. [ASCII table writing is planned but
1535             does not yet exist].
1536              
1537             You can specify the format of the table quite specifically with the
1538             'hdr' key or option field. If it exists, then the 'hdr' key should
1539             contain fields appropriate to the table extension being used. Any
1540             field information that you don't specify will be filled in
1541             automatically, so (for example) you can specify that a particular
1542             column name goes in a particular position, but allow C to
1543             arrange the other columns in the usual alphabetical order into any
1544             unused slots that you leave behind. The C, C,
1545             C, C, C, and C keywords are ignored:
1546             their values are calculated based on the hash that you supply. Any
1547             other fields are passed into the final FITS header verbatim.
1548              
1549             As an example, the following
1550              
1551             $x = long(1,2,4);
1552             $y = double(1,2,4);
1553             wfits { 'COLA'=>$x, 'COLB'=>$y }, "table1.fits";
1554              
1555             will create a binary FITS table called F which
1556             contains two columns called C and C. The order
1557             of the columns is controlled by setting the C
1558             keywords in the header array, so
1559              
1560             $h = { 'TTYPE1'=>'Y', 'TTYPE2'=>'X' };
1561             wfits { 'X'=>$x, 'Y'=>$y, hdr=>$h }, "table2.fits";
1562              
1563             creates F where the first column is
1564             called C and the second column is C.
1565              
1566             =item * multi-value handling
1567              
1568             If you feed in a perl array-ref rather than a PDL or a hash, then
1569             each element is written out as a separate HDU in the FITS file.
1570             Each element of the list must be a PDL or a hash.
1571              
1572             =item * DEVEL NOTES
1573              
1574             ASCII tables are not yet handled but should be.
1575              
1576             Binary tables currently only handle one vector (up to 1-D array)
1577             per table entry; the standard allows more, and should be fully implemented.
1578              
1579             Handling multidim arrays implies that perl multidim lists should also be
1580             handled.
1581              
1582             =back
1583              
1584             =for bad
1585              
1586             For integer types (ie C 0>), the C keyword is set
1587             to the bad value. For floating-point types, the bad value is
1588             converted to NaN (if necessary) before writing.
1589              
1590             =cut
1591              
1592             *wfits = \&PDL::wfits;
1593              
1594             our @wfits_keyword_order = qw(SIMPLE BITPIX NAXIS NAXIS1 BUNIT BSCALE BZERO);
1595             our @wfits_numbered_keywords = qw(CTYPE CRPIX CRVAL CDELT CROTA);
1596              
1597             # Local utility routine of wfits()
1598             sub wheader {
1599 444     444 0 716 my ($fh, $k, $hdr, $nbytes, $comment) = @_;
1600 444 100       998 if ($k =~ m/(HISTORY|COMMENT)/) {
1601 63         196 my $hc = $1;
1602 63 100       247 return $nbytes unless exists $hdr->{$k};
1603 2 50       30 my @vals = ref($hdr->{$k}) eq 'ARRAY' ? @{$hdr->{$k}} : grep length, split /\n+/, $hdr->{$k};
  0         0  
1604 2         8 foreach my $line (@vals) {
1605 14         35 printf $fh "$hc %-72s", substr($line,0,72);
1606 14         25 $nbytes += 80;
1607             }
1608 2         6 delete $hdr->{$k};
1609             } else {
1610             # Check that we are dealing with a scalar value in the header
1611             # Need to make sure that the header does not include PDLs or
1612             # other structures. Return unless $hdr->{$k} is a scalar.
1613 381         699 my $hdrk = $hdr->{$k};
1614 381 100       734 $hdrk = join("\n",@$hdrk) if ref $hdrk eq 'ARRAY';
1615 381 50       557 return $nbytes if ref($hdrk);
1616 381 50       599 if ($hdrk eq "") {
1617 0         0 printf $fh "%-80s", substr($k,0,8);
1618             } else {
1619 381         1814 printf $fh "%-8s= ", substr($k,0,8);
1620 381         687 my $com = delete $comment->{$k};
1621 381 100 66     1938 if ($hdrk =~ /^ *([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))? *$/) { # Number?
    100          
1622 242 50       381 my $cl=60-($com ? 2 : 0);
1623 242         403 my $end=' ' x $cl;
1624 242 50       355 $end =' /'. $com if($com);
1625 242         786 printf $fh "%20s%-50s", substr($hdrk,0,20), substr($end, 0, 50);
1626             } elsif ($hdrk eq 'F' or $hdrk eq 'T') {
1627             # Logical flags ?
1628 59         196 printf $fh "%20s", $hdrk;
1629 59         103 my $end=' ' x 50;
1630 59 50       166 $end =' /'.$com if($com);
1631 59         165 printf $fh "%-50s", $end;
1632             } else {
1633             # Handle strings, truncating as necessary
1634             # (doesn't do multicard strings like Astro::FITS::Header does)
1635             # Convert single quotes to doubled single quotes
1636             # (per FITS standard)
1637 80         167 (my $st = $hdrk) =~ s/\'/\'\'/g;
1638 80         120 my $sl=length($st)+2;
1639 80 50       170 my $cl=70-$sl-($com ? 2 : 0);
1640 80         171 print $fh "'$st'";
1641 80 50       194 if (defined $com) {
1642 0         0 printf $fh " /%-${cl}s", substr($com, 0, $cl);
1643             } else {
1644 80         303 printf $fh "%-${cl}s", ' ' x $cl;
1645             }
1646             }
1647             }
1648 381         578 $nbytes += 80; delete $hdr->{$k};
  381         528  
1649             }
1650 383         782 $nbytes;
1651             }
1652              
1653             # ordered hash: [ \@key_order, \%hdr, \%type, \%comment ]
1654             sub _k_add {
1655 408     408   816 my ($ohash, $key, $val, $type, $comment) = @_;
1656 408         593 my ($k_o, $hdr, $t, $com) = @$ohash;
1657 408         671 push @$k_o, $key;
1658 408         731 $hdr->{$key} = $val;
1659 408 100       711 $t->{$key} = $type if defined $type;
1660 408 100       928 $com->{$key} = $comment if defined $comment;
1661             }
1662              
1663             # Write a PDL to a FITS format file
1664             #
1665             my %wfits_reftype = map +($_=>1), qw(PDL HASH ARRAY);
1666             my %wfits_zpreserve = (reverse(%$hdrconv), map +($_=>"Z$_"), qw(BITPIX NAXIS));
1667             sub PDL::wfits {
1668 74 50 33 74 0 38504 barf 'Usage: wfits($pdl,$file,[$BITPIX],[{options}])' if $#_<1 || $#_>3;
1669 74         328 my ($pdl,$file,$x,$y) = @_;
1670              
1671             #### Figure output type
1672 74 100       224 barf "wfits: needs a HASH, ARRAY or PDL argument to write out\n" if !ref $pdl;
1673 71 50       300 barf "wfits: unknown ref type ".ref($pdl)."\n" if !$wfits_reftype{ref $pdl};
1674 71         325 my $issue_nullhdu = !UNIVERSAL::isa($pdl,'PDL');
1675 71 100       270 my @outputs = ref($pdl) eq 'ARRAY' ? @$pdl : $pdl;
1676              
1677 71         141 my ($opt, $BITPIX);
1678              
1679 71         397 local $\ = undef; # fix sf.net bug #3394327
1680              
1681 71 100 33     478 if(ref $x eq 'HASH') {
    50          
1682 2         5 $opt = $x;
1683 2         4 $BITPIX = $y;
1684             } elsif(ref $y eq 'HASH' || ! defined $y) {
1685 69         129 $BITPIX = $x;
1686 69         113 $opt = $y;
1687             }
1688              
1689 71         193 $file =~ s/^(~)/glob($1)/e; # tilde expansion
  0         0  
1690              
1691 71         1049 local $SIG{PIPE};
1692              
1693 71 50       354 if ($file =~ /\.gz$/) { # Handle suffix-style compression
    50          
1694 0     0   0 $SIG{PIPE}= sub {}; # Prevent crashing if gzip dies
1695 0         0 $file = "|gzip -9 > $file";
1696             }
1697             elsif ($file =~ /\.Z$/) {
1698 0     0   0 $SIG{PIPE}= sub {}; # Prevent crashing if compress dies
1699 0         0 $file = "|compress > $file";
1700             }
1701             else{
1702 71         183 $file = ">$file";
1703             }
1704              
1705             ## Open file & prepare to write binary info
1706 71 50       11969 open my $fh, $file
1707             or barf "Unable to create FITS file $file: $!\n";
1708 71         423 binmode $fh;
1709              
1710 71 100       197 if($issue_nullhdu) {
1711 6         32 _wfits_nullhdu($fh);
1712             }
1713              
1714 71         222 for $pdl(@outputs) {
1715              
1716 73 100       263 if(ref($pdl) eq 'HASH') {
1717             my $table_type = ( exists($pdl->{tbl}) ?
1718 4 50       22 ($pdl->{tbl} =~ m/^a/i ? 'ascii' : 'binary') :
    100          
1719             "binary"
1720             );
1721 4         19 _wfits_table($fh,$pdl,$table_type);
1722 4         41 next;
1723             }
1724 69 50       331 if( !UNIVERSAL::isa($pdl,'PDL') ) {
1725             # Not a PDL and not a hash ref
1726 0         0 barf("wfits: unknown data type - quitting");
1727             }
1728             ### Regular image writing.
1729 69 100       236 $BITPIX = "" unless defined $BITPIX;
1730 69 100       228 if ($BITPIX eq "") {
1731 45 100       273 $BITPIX = 8 if $pdl->get_datatype == $PDL_B;
1732 45 100 66     324 $BITPIX = 16 if $pdl->get_datatype == $PDL_S || $pdl->get_datatype == $PDL_US;
1733 45 100       151 $BITPIX = 32 if $pdl->get_datatype == $PDL_L;
1734 45 100       164 $BITPIX = 64 if $pdl->get_datatype == $PDL_LL;
1735 45 100       126 $BITPIX = -32 if $pdl->get_datatype == $PDL_F;
1736 45 100       137 $BITPIX = -64 if $pdl->get_datatype == $PDL_D;
1737 45 50       133 $BITPIX = 8 * PDL::Core::howbig($PDL_IND) if($pdl->get_datatype==$PDL_IND);
1738             }
1739 69 50       182 if ($BITPIX eq "") {
1740 0         0 $BITPIX = -64;
1741 0         0 warn "wfits: PDL has an unsupported datatype -- defaulting to 64-bit float.\n";
1742             }
1743              
1744 69     0   433 my $convert = sub { return $_[0] }; # Default - do nothing
  0         0  
1745 69 100   16   233 $convert = sub {byte($_[0])} if $BITPIX == 8;
  16         56  
1746 69 100   22   207 $convert = sub {short($_[0])} if $BITPIX == 16;
  22         81  
1747 69 100   28   251 $convert = sub {long($_[0])} if $BITPIX == 32;
  28         140  
1748 69 100   4   164 $convert = sub {longlong($_[0])} if $BITPIX == 64;
  4         14  
1749 69 100   16   246 $convert = sub {float($_[0])} if $BITPIX == -32;
  16         81  
1750 69 100   50   296 $convert = sub {double($_[0])} if $BITPIX == -64;
  50         176  
1751              
1752             # Automatically figure output scaling
1753 69         127 my $bzero = 0; my $bscale = 1;
  69         104  
1754 69 100       194 if ($BITPIX>0) {
1755 36         207 my $min = $pdl->min;
1756 36         817 my $max = $pdl->max;
1757 36 100       143 my ($dmin,$dmax) = (0, 2**8-1) if $BITPIX == 8;
1758 36 100       97 ($dmin,$dmax) = (-2**15, 2**15-1) if $BITPIX == 16;
1759 36 100       133 ($dmin,$dmax) = (-2**31, 2**31-1) if $BITPIX == 32;
1760 36 100       122 ($dmin,$dmax) = (-(pdl(longlong,1)<<63), (pdl(longlong,1)<<63)-1) if $BITPIX==64;
1761 36 50 33     195 if ($min<$dmin || $max>$dmax) {
1762 0         0 $bzero = $min - $dmin;
1763 0         0 $max -= $bzero;
1764 0 0       0 $bscale = $max/$dmax if $max>$dmax;
1765             }
1766 36 50       696 print "BSCALE = $bscale && BZERO = $bzero\n" if $PDL::verbose;
1767             }
1768              
1769             ##############################
1770             ## Check header and prepare to write it out
1771 69         405 my($h) = $pdl->gethdr();
1772             # Extra logic: if we got handed a vanilla hash that that is *not* an Astro::FITS::Header, but
1773             # looks like it's a FITS header encoded in a hash, then attempt to process it with
1774             # Astro::FITS::Header before writing it out -- this helps with cleanup of tags.
1775 69 100 100     355 if ($PDL::Astro_FITS_Header and
      66        
      100        
1776             defined($h) and
1777             ref($h) eq 'HASH' and
1778             !defined( tied %$h )
1779             ) {
1780 3         7 my $all_valid_fits = 1;
1781 3         15 for my $k(keys %$h) {
1782 6 50 33     46 if(length($k) > 8 or
1783             $k !~ m/^[A-Z_][A-Z\d\_]*$/i
1784             ) {
1785 0         0 $all_valid_fits = 0;
1786 0         0 last;
1787             }
1788             }
1789 3 50       10 if($all_valid_fits) {
1790             # All the keys look like valid FITS header keywords -- so
1791             # create a tied FITS header object and use that instead.
1792 3         31 my $afh = Astro::FITS::Header->new;
1793 3         970 my %hh;
1794 3         19 tie %hh, "Astro::FITS::Header", $afh;
1795 3         36 for (keys %$h) {
1796 6         688 $hh{$_} = $h->{$_};
1797             }
1798 3         1989 $h = \%hh;
1799             }
1800             }
1801              
1802             # ordered hash with type and comment capability
1803 69         145 my (@key_order, %ohdr, %type, %comment);
1804 69   66     278 my $use_afh = defined($h) && defined(tied %$h) &&
1805             UNIVERSAL::isa(tied %$h,"Astro::FITS::Header");
1806 69 100       151 if ($use_afh) {
1807             # Delete the END card if necessary (for later addition at the end)
1808 6         32 tied(%$h)->removebyname('END');
1809 6         1602 %ohdr = %$h;
1810             } else {
1811 63 100       165 @ohdr{map uc, keys %$h} = values %$h if $h;
1812 63         128 delete $ohdr{END};
1813             }
1814 69         9402 my $ohash = [ \@key_order, \%ohdr, \%type, \%comment ];
1815 69 100       503 _k_add($ohash, $issue_nullhdu
1816             ? qw(XTENSION IMAGE)
1817             : (qw(SIMPLE T LOGICAL), 'Created with PDL (http://pdl.perl.org)'));
1818 69         171 _k_add($ohash, 'BITPIX', $BITPIX);
1819 69         328 _k_add($ohash, 'NAXIS', $pdl->getndims);
1820 69         111 my $correction = 0;
1821 69         281 for (1..$pdl->getndims) {
1822             $correction ||= exists $ohdr{"NAXIS$_"} &&
1823 115   66     680 $ohdr{"NAXIS$_"} != $pdl->dim($_-1);
      33        
1824 115         428 _k_add($ohash, "NAXIS$_", $pdl->getdim($_-1));
1825             }
1826 69 50       167 carp "Warning: wfits corrected dimensions of FITS header" if $correction;
1827 69 100       269 _k_add($ohash, 'BUNIT', "Data Value") unless exists $ohdr{BUNIT};
1828 69 50       157 _k_add($ohash, 'BSCALE', $bscale) if $bscale != 1;
1829 69 50       178 _k_add($ohash, 'BZERO', $bzero) if $bzero != 0;
1830 69 100       284 if ( $pdl->badflag() ) {
1831 4 100       14 if ( $BITPIX > 0 ) { _k_add($ohash, 'BLANK', $convert->(pdl(0.0))->badvalue->sclr); }
  2         12  
1832 2         5 else { delete $ohdr{BLANK}; }
1833             }
1834 69         122 my %removed_naxis;
1835 69         739 for my $kw_base (map {(my $r=$_)=~s/\d$//;$r} grep /\d$/, @wfits_keyword_order) {
  69         324  
  69         239  
1836 69         116 my $kn = 0;
1837 69         99 do { # Loop over numericised keywords (e.g. NAXIS1)
1838 69         98 my $kw = $kw_base;
1839 69         128 $kw .= ++$kn; # NAXIS1 -> NAXIS
1840 69 50       203 last if !exists $ohdr{$kw};
1841 69 50       342 next if $kn <= $pdl->getndims;
1842             #remove e.g. NAXIS3 from afhdr if NAXIS==2
1843 0         0 delete $ohdr{$kw};
1844 0 0       0 delete $h->{$kw} if $use_afh;
1845 0         0 $removed_naxis{$kn} = 1;
1846             } while(1);
1847             }
1848 69         248 foreach my $n (sort keys %removed_naxis){
1849 0         0 delete @ohdr{map $_.$n, @wfits_numbered_keywords};
1850 0 0       0 delete @$h{map $_.$n, @wfits_numbered_keywords} if $use_afh;
1851             }
1852              
1853             # Check for tile-compression format for the image, and handle it.
1854             # We add the image-compression format tags and reprocess the whole
1855             # shebang as a binary table.
1856 69 100       221 if (my $cmptype = $opt->{compress}) {
1857 2 50       10 $cmptype = 'RICE_1' if $cmptype eq '1';
1858             confess "wfits: given unknown compress '$cmptype'"
1859 2 50       10 unless my $tc = $tile_compressors->{$cmptype};
1860 2         7 _k_add($ohash, qw(ZIMAGE T LOGICAL));
1861 2         7 _k_add($ohash, 'ZCMPTYPE', $cmptype);
1862             _k_add($ohash, $wfits_zpreserve{$_}, delete $ohdr{$_})
1863 2         25 for sort grep exists $ohdr{$_}, keys %wfits_zpreserve;
1864 2         20 _k_add($ohash, "ZNAXIS$_", $ohdr{"NAXIS$_"}) for 1..$pdl->getndims;
1865 2         9 $tc->[0]->( $pdl, \%ohdr, $opt );
1866 2         8 my %tbl;
1867 2         26 $tbl{$_} = delete $ohdr{$_} for map $_."COMPRESSED_DATA", '', 'len_';
1868 2         10 $tbl{hdr} = \%ohdr;
1869 2 50       11 if (!$issue_nullhdu) {
1870 2         10 _wfits_nullhdu($fh); # needed only once
1871 2         5 $issue_nullhdu = 1;
1872             }
1873 2         14 _wfits_table($fh,\%tbl,'binary');
1874 2         203 next;
1875             }
1876              
1877 67         123 my $nbytes = 0;
1878             # Now decide whether to emit a hash or an AFH object
1879 67 100       177 if ($use_afh) {
1880 4         14 my $afhdr = tied %$h;
1881             # Use tied interface to set all the keywords. Note that this
1882             # preserves existing per-line comments, only changing the values.
1883 4         11 for my $key (@key_order) {
1884 22         141 $h->{$key} = $ohdr{$key};
1885 22         4763 my ($afhitem) = $afhdr->itembyname($key);
1886 22 100       514 $afhitem->type($type{$key}) if defined $type{$key};
1887 22 100       95 $afhitem->comment($comment{$key}) if defined $comment{$key};
1888             }
1889             # sort the lines. This is complicated by
1890             # the need for an arbitrary number of NAXIS lines in the middle
1891             # of the sorting. Keywords with a trailing '1' in the sorted-order
1892             # list get looped over.
1893 4         13 my $kk = 0;
1894 4         13 for my $kw_base (@wfits_keyword_order) {
1895 16         24 my $kn = 0;
1896 16         19 do { # Loop over numericised keywords (e.g. NAXIS1)
1897 23         37 my $kw = $kw_base;
1898 23 100       91 $kw .= ++$kn if $kw =~ s/\d$//; # NAXIS1 -> NAXIS
1899 23 100       50 last if !(my ($ind) = $afhdr->index($kw));
1900 19 100       286 $afhdr->insert($kk, $afhdr->remove($ind)) if $ind != $kk;
1901 19         1669 $kk++;
1902             } while($kn);
1903             }
1904             # Make sure that the HISTORY lines all come at the end
1905 4         61 my @hindex = $afhdr->index('HISTORY');
1906 4         68 my @hitems = map $afhdr->item($_), @hindex;
1907 4         147 $afhdr->remove($_) for reverse @hindex; # remove from back as from front disrupts numbering
1908 4         3956 $afhdr->insert(-1, $_) for @hitems;
1909             # Make sure the last card is an END
1910 4         4247 $afhdr->insert(scalar($afhdr->cards),
1911             Astro::FITS::Header::Item->new(Keyword=>'END'));
1912             # Write out all the cards, and note how many bytes for later padding.
1913 4         4133 my $s = join("",$afhdr->cards);
1914 4         950 print $fh $s;
1915 4         16 $nbytes = length $s;
1916             } else {
1917 63         369 my %hdr = %ohdr;
1918 63         184 for my $key (@key_order) {
1919 356         610 $hdr{$key} = $ohdr{$key};
1920 356         631 $nbytes = wheader($fh, $key, \%hdr, $nbytes, \%comment);
1921             }
1922             $nbytes = wheader($fh, $_, \%hdr, $nbytes, \%comment)
1923 63         374 for sort fits_field_cmp grep !/HISTORY/, keys %hdr;
1924 63         141 $nbytes = wheader($fh, 'HISTORY', \%hdr, $nbytes, \%comment); # Make sure that HISTORY entries come last.
1925 63         184 printf $fh "%-80s", "END";
1926 63         125 $nbytes += 80;
1927             }
1928              
1929             #
1930             # Pad the header to a legal value and write the rest of the FITS file.
1931             #
1932 67         114 $nbytes %= 2880;
1933 67 50       434 print $fh " "x(2880-$nbytes)
1934             if $nbytes != 0; # Fill up HDU
1935              
1936             # Write FITS data
1937 67         333 my $p1d = $pdl->copy->reshape($pdl->nelem); # Data as 1D stream;
1938              
1939 67         126 my $off = 0;
1940 67         313 my $sz = PDL::Core::howbig($convert->($p1d->slice('0:0'))->get_datatype);
1941 67         570 $nbytes = $p1d->getdim(0) * $sz;
1942             # Transfer data in blocks (because might need to byte swap)
1943             # Buffer is also type converted on the fly
1944 67         109 my $BUFFSZ = 360*2880; # = ~1Mb - must be multiple of 2880
1945 67         128 my $tmp;
1946              
1947 67 100 100     317 if ( $pdl->badflag() and $BITPIX < 0 ) {
1948             # just print up a message - conversion is actually done in the loop
1949 2 50       6 print "Converting PDL bad value to NaN\n" if $PDL::verbose;
1950             }
1951              
1952 67         196 while ($nbytes - $off > $BUFFSZ) {
1953             # Data to be transferred
1954 0         0 my $buff = $convert->( ($p1d->slice( ($off/$sz).":". (($off+$BUFFSZ)/$sz-1))
1955             -$bzero)/$bscale );
1956             # if there are bad values present, and output type is floating-point,
1957             # convert the bad values to NaN's. We can ignore integer types, since
1958             # we have set the BLANK keyword
1959             #
1960 0 0 0     0 if ( $pdl->badflag() and $BITPIX < 0 ) {
1961 0         0 $buff->inplace->setbadtonan();
1962             }
1963 0 0       0 $buff->type->bswap->($buff) if !isbigendian();
1964 0         0 print $fh ${$buff->get_dataref};
  0         0  
1965 0         0 $off += $BUFFSZ;
1966             }
1967 67         2694 my $buff = $convert->( ($p1d->slice($off/$sz.":-1") - $bzero)/$bscale );
1968              
1969 67 100 100     672 if ( $pdl->badflag() and $BITPIX < 0 ) {
1970 2         64 $buff->inplace->setbadtonan();
1971             }
1972              
1973 67 50       301 $buff->type->bswap->($buff) if !isbigendian();
1974 67         156 print $fh ${$buff->get_dataref};
  67         2889  
1975             # Fill HDU and close
1976             # note that for the data space the fill character is \0 not " "
1977             #
1978 67         1878 print $fh "\0"x(($BUFFSZ - $buff->getdim(0) * $sz)%2880);
1979             } # end of output loop
1980 71         41229 close $fh;
1981 71         1491 1;
1982             }
1983              
1984             ######################################################################
1985             ######################################################################
1986              
1987             # Compare FITS headers in a sensible manner.
1988              
1989             =head2 fits_field_cmp
1990              
1991             =for ref
1992              
1993             fits_field_cmp
1994              
1995             Sorting comparison routine that makes proper sense of the digits at the end
1996             of some FITS header fields. Sort your hash keys using "fits_field_cmp" and
1997             you will get (e.g.) your "TTYPE" fields in the correct order even if there
1998             are 140 of them.
1999              
2000             This is a standard perl comparison sub -- it uses the magical
2001             $a and $b variables, rather than normal argument passing.
2002              
2003             =cut
2004              
2005             sub fits_field_cmp {
2006 1312 100   1312 1 3991 if( $a=~m/^(.*[^\d])(\d+)$/ ) {
2007 642         1455 my ($an,$ad) = ($1,$2);
2008 642 100       1891 if( $b=~m/^(.*[^\d])(\d+)$/ ) {
2009 353         754 my($bn,$bd) = ($1,$2);
2010              
2011 353 100       771 if($an eq $bn) {
2012 91         217 return $ad<=>$bd;
2013             }
2014             }
2015             }
2016 1221         2104 $a cmp $b;
2017             }
2018              
2019             =head2 _rows()
2020              
2021             =for ref
2022              
2023             Return the number of rows in a variable for table entry
2024              
2025             You feed in a PDL or a list ref, and you get back the 0th dimension.
2026              
2027             =cut
2028              
2029             sub _rows {
2030 12     12   19 my $var = shift;
2031              
2032 12 100       88 return $var->dim(0) if( UNIVERSAL::isa($var,'PDL') );
2033 1 50       6 return 1+$#$var if(ref $var eq 'ARRAY');
2034 0 0       0 return 1 unless(ref $var);
2035              
2036 0 0       0 warn "Warning: _rows found an unacceptable ref. ".ref $var.". Ignoring...\n"
2037             if($PDL::verbose);
2038              
2039 0         0 return undef;
2040             }
2041              
2042              
2043              
2044             =head2 _prep_table()
2045              
2046             =for ref
2047              
2048             Accept a hash ref containing a table, and return a header describing the table
2049             and a string to be written out as the table, or barf.
2050              
2051             You can indicate whether the table should be binary or ascii. The default
2052             is binary; it can be overridden by the "tbl" field of the hash (if present)
2053             or by parameter.
2054              
2055             =cut
2056              
2057             # NOTE:
2058             # the conversion from ushort to long below is a hack to work
2059             # around the issue that otherwise perl treats it as a 2-byte
2060             # NOT 4-byte string on writing out, which leads to data corruption
2061             # Really ushort arrays should be written out using SCALE/ZERO
2062             # so that it can be written as an Int2 rather than Int4
2063             our %bintable_types = (
2064             'byte'=>['B',1],
2065             'short'=>['I',2],
2066             'ushort'=>['J',4, sub {shift->long}],
2067             'long'=>['J',4],
2068             'longlong'=>['D', 8, sub {shift->double}],
2069             'float'=>['E',4],
2070             'double'=>['D',8],
2071             # 'complex'=>['M',8] # Complex doubles are supported (actually, they aren't at the moment)
2072             );
2073              
2074              
2075             sub _prep_table {
2076 6     6   22 my ($hash,$tbl,$nosquish) = @_;
2077 6 50       21 barf "_prep_table called without a HASH reference as the first argument"
2078             unless ref $hash eq 'HASH';
2079 6         15 my $ohash = {};
2080 6 100       16 my %hdr = %{$hash->{hdr} || {}};
  6         114  
2081 6   33     4858 $tbl //= $hdr{tbl};
2082 6         13 my $heap = "";
2083              
2084             #####
2085             # Figure out how many columns are in the table
2086 6   66     72 my @colkeys = grep( ( !m/^(hdr|tbl)$/ and !m/^len_/ and defined $hash->{$_}),
2087             sort fits_field_cmp keys %$hash
2088             );
2089 6         21 my $cols = @colkeys;
2090              
2091 6 50       20 print "Table seems to have $cols columns...\n"
2092             if($PDL::verbose);
2093              
2094             #####
2095             # Figure out how many rows are in the table, and store counts...
2096             #
2097 6         21 my $rows;
2098             my $rkey;
2099 6         18 for my $key(@colkeys) {
2100 12         46 my $r = _rows($hash->{$key});
2101 12 100 66     48 ($rows,$rkey) = ($r,$key) unless(defined($rows) && $rows != 1);
2102 12 50 33     56 if($r != $rows && $r != 1) {
2103 0         0 barf "_prep_table: inconsistent number of rows ($rkey: $rows vs. $key: $r)\n";
2104             }
2105             }
2106 6 50       18 print "Table seems to have $rows rows...\n"
2107             if($PDL::verbose);
2108              
2109             #####
2110             # Squish and disambiguate column names
2111             #
2112 6         10 my %keysbyname;
2113             my %namesbykey;
2114              
2115 6 50       22 print "Renaming hash keys...\n"
2116             if($PDL::debug);
2117              
2118 6         13 for my $key(@colkeys) {
2119 12         17 my $name = $key;
2120 12         26 $name =~ tr/[a-z]/[A-Z]/; # Uppercaseify (required by FITS standard)
2121 12         28 $name =~ s/\s+/_/g; # Remove whitespace (required by FITS standard)
2122 12 50       23 unless($nosquish) {
2123 12         24 $name =~ s/[^A-Z0-9_-]//g; # Squish (recommended by FITS standard)
2124             }
2125             ### Disambiguate...
2126 12 50       26 if(defined $ohash->{$name}) {
2127 0         0 my $iter = 1;
2128 0         0 my $name2;
2129 0         0 do { $name2 = $name."_".($iter++); }
2130 0         0 while(defined $ohash->{$name2});
2131 0         0 $name = $name2;
2132             }
2133 12         31 $ohash->{$name} = $hash->{$key};
2134 12         20 $keysbyname{$name} = $key;
2135 12         21 $namesbykey{$key} = $name;
2136 12 0 33     50 print "\tkey '$key'\t-->\tname '$name'\n"
      33        
2137             if($PDL::debug || (($name ne $key) and $PDL::verbose));
2138             }
2139              
2140             # The first element of colnames is ignored (since FITS starts the
2141             # count at 1)
2142             #
2143 6         12 my @colnames; # Names by number
2144             my %colnums; # Numbers by name
2145              
2146             ### Allocate any table columns that are already in the header...
2147 6         10 local $_;
2148 6         52 for (sort fits_field_cmp keys %hdr) {
2149 120 100       289 next unless m/^TTYPE(\d*)$/;
2150 3         6 my $num = $1;
2151 3 50 33     61 if($num > $cols || $num < 1) {
2152 0 0       0 print "Ignoring illegal column number $num ( should be in range 1..$cols )\n"
2153             if($PDL::verbose);
2154 0         0 delete $hdr{$_};
2155 0         0 next;
2156             }
2157 3         7 my $key = $hdr{$_};
2158 3         5 my $name;
2159 3 50       9 unless( $name = $namesbykey{$key}) { # assignment
2160 0         0 $name = $key;
2161 0 0       0 unless( $key = $keysbyname{$key}) {
2162 0 0       0 print "Ignoring column $num in existing header (unknown name '$key')\n"
2163             if($PDL::verbose);
2164 0         0 next;
2165             }
2166             }
2167 3         7 $colnames[$num] = $name;
2168 3         9 $colnums{$name} = $num;
2169             }
2170              
2171             ### Allocate all other table columns in alphabetical order...
2172 6         20 my $i = 0;
2173 6         20 for my $name (@namesbykey{@colkeys}) {
2174 12 100       25 unless($colnums{$name}) {
2175 9         21 1 while($colnames[++$i]);
2176 9         16 $colnames[$i] = $name;
2177 9         19 $colnums{$name} = $i;
2178 3         5 } else { $i++; }
2179             }
2180              
2181 6 50 33     23 print "Assertion failed: i ($i) != colnums ($cols)\n"
2182             if($PDL::debug && $i != $cols);
2183              
2184             print "colnames: " .
2185 6 50       17 join(",", map { $colnames[$_]; } (1..$cols) ) ."\n"
  0         0  
2186             if($PDL::debug);
2187              
2188             ########
2189             # OK, now the columns are allocated -- spew out a header.
2190              
2191 6         13 my @converters = (); # Will fill up with conversion routines for each column
2192 6         15 my @field_len = (); # Will fill up with field lengths for each column
2193 6         12 my @internaltype = (); # Gets flag for PDLhood
2194 6         13 my @fieldvars = (); # Gets refs to all the fields of the hash.
2195              
2196 6 50       17 if ($tbl eq 'binary') {
    0          
2197 6         18 $hdr{XTENSION} = 'BINTABLE';
2198 6         15 $hdr{BITPIX} = 8;
2199 6         12 $hdr{NAXIS} = 2;
2200 6         15 $hdr{NAXIS2} = $rows;
2201 6         14 $hdr{PCOUNT} = 0; # Change this is variable-arrays are adopted
2202 6         17 $hdr{GCOUNT} = 1;
2203 6         21 $hdr{TFIELDS} = $cols;
2204              
2205             # Figure out data types, and accumulate a row length at the same time.
2206              
2207 6         14 my $rowlen = 0;
2208              
2209 6         24 for my $i (1..$cols) {
2210 12         38 $fieldvars[$i] = $hash->{$keysbyname{$colnames[$i]}};
2211 12         18 my $var = $fieldvars[$i];
2212              
2213 12         39 $hdr{"TTYPE$i"} = $colnames[$i];
2214 12         41 my $tform;
2215              
2216             my $tstr;
2217 12         0 my $rpt;
2218 12         0 my $bytes;
2219              
2220 12 100       53 if (UNIVERSAL::isa($var,'PDL')) {
    50          
    0          
2221              
2222 11         19 $internaltype[$i] = 'P';
2223              
2224 11         59 my $dims = $var->shape;
2225 11         58 (my $t = $dims->slice("(0)")) .= pdl($dims->type, 1);
2226 11         86 $rpt = $dims->prod;
2227              
2228             =pod
2229              
2230             =begin WHENCOMPLEXVALUESWORK
2231              
2232             if (!$var->type->real) {
2233             $rpt = $var->dim(1);
2234             $t = 'complex'
2235             } else {
2236             $t = type $var;
2237             }
2238              
2239             =end WHENCOMPLEXVALUESWORK
2240              
2241             =cut
2242              
2243 11 50       39 barf "Error: wfits() currently can not handle complex arrays (column $colnames[$i])\n"
2244             if !$var->type->real;
2245              
2246 11         27 $t = $bintable_types{$var->type};
2247              
2248 11 50       29 unless (defined $t) {
2249 0 0       0 print "Warning: converting unknown type $t (column $colnames[$i]) to double...\n"
2250             if($PDL::verbose);
2251 0         0 $t = $bintable_types{double};
2252             }
2253              
2254 11         77 ($tstr, $bytes, $converters[$i]) = @$t;
2255              
2256             } elsif (ref $var eq 'ARRAY') {
2257              
2258 1         3 $internaltype[$i] = 'A';
2259 1         2 $bytes = 1;
2260             # Got an array (of strings) -- find the longest element
2261 1         9 require List::Util;
2262 1         8 $rpt = List::Util::max(map length, @$var);
2263 1         4 ($tstr, $bytes, $converters[$i]) = ('A',1,undef);
2264              
2265             } elsif( ref $var ) {
2266 0         0 barf "You seem to be writing out a ".(ref $var)." as a table column. I\ndon't know how to do that (yet).\n";
2267              
2268             } else { # Scalar
2269 0         0 $internaltype[$i] = 'A';
2270 0         0 ($tstr, $bytes, $converters[$i]) = ('A',1,undef);
2271 0         0 $rpt = length($var);
2272             }
2273              
2274             # Now check if it's a variable-length array and, if so, insert an
2275             # extra converter
2276 12         30 my $lname = "len_".$keysbyname{$colnames[$i]};
2277 12 100       33 if (exists $hash->{$lname}) {
2278 3         9 my $lengths = $hash->{$lname};
2279              
2280             # Variable length array - add extra handling logic.
2281              
2282             # First, check we're legit
2283 3 50 33     106 if( !UNIVERSAL::isa($var, 'PDL') ||
      33        
      33        
2284             !UNIVERSAL::isa($lengths,'PDL') ||
2285             ($var->ndims - $lengths->ndims) != 1 ||
2286             $lengths->dim(0) != $var->dim(0)
2287             ) {
2288 0         0 die <
2289             wfits(): you specified a '$lname' field in
2290             your binary table output hash, indicating a variable-length array for
2291             each row of the output table, but I'm having trouble interpreting it.
2292             Either your source column isn't a 2-D PDL, or your length column isn't
2293             a 1-D PDL, or the two lengths don't match. I give up.
2294             FOO
2295             }
2296              
2297             # The definition below wraps around the existing converter,
2298             # dumping the variable to the heap and returning the length
2299             # and index of the data for the current row as a PDL LONG.
2300             # This does the Right Thing below in the write loop, with
2301             # the side effect of putting the data into the heap.
2302             #
2303             # The main downside here is that the heap gets copied
2304             # multiple times as we accumulate it, since we are using
2305             # string concatenation to add onto it. It might be better
2306             # to preallocate a large heap, but I'm too lazy to figure
2307             # out how to do that.
2308 3         9 my $csub = $converters[$i];
2309             $converters[$i] = sub {
2310 1920     1920   6024 my ($var, $row, $col) = @_;
2311 1920         5815 my $len = $hash->{$lname};
2312 1920         3413 my $l;
2313 1920 50       11521 if (ref $len eq 'ARRAY') {
    50          
    0          
2314 0         0 $l = $len->[$row];
2315             } elsif ( UNIVERSAL::isa($len,'PDL') ) {
2316 1920         7857 $l = $len->dice_axis(0,$row)->sclr;
2317             } elsif ( ref $len ) {
2318 0         0 die "wfits: Couldn't understand length spec '$lname' in bintable output (length spec must be a PDL or array ref).\n";
2319             } else {
2320 0         0 $l = $len;
2321             }
2322             # The standard says we should give a zero-offset
2323             # pointer if the current row is zero-length; hence
2324             # the ternary operator.
2325 1920 50       127084 my $ret = pdl(long, $l, $l ? length($heap) : 0);
2326 1920 50       6086 if ($l) {
2327             # This echoes the normal-table swap and accumulation
2328             # stuff below, except we're accumulating into the heap.
2329 1920 50       5176 my $tmp = $csub ? &$csub($var, $row, $col) : $var;
2330 1920         12976 $tmp = $tmp->slice("0:".($l-1))->sever;
2331 1920 50       9148 $tmp->type->bswap->($tmp) if !isbigendian();
2332 1920         3648 $heap .= ${ $tmp->get_dataref };
  1920         16058  
2333             }
2334 1920         6466 return $ret;
2335 3         36 };
2336              
2337             # Having defined the conversion routine, now modify tstr to make this a heap-array
2338             # reference.
2339 3         21 $tstr = sprintf("P%s(%d)",$tstr, $hash->{$lname}->max );
2340 3         57 $rpt = 1;
2341 3         13 $bytes = 8; # two longints per row in the main table.
2342             }
2343              
2344 12         57 $hdr{"TFORM$i"} = "$rpt$tstr";
2345              
2346 12 100 100     77 if (UNIVERSAL::isa($var, 'PDL') and $var->ndims > 1) {
2347 3         15 $hdr{"TDIM$i"} = "(".join(",",$var->slice("(0)")->dims).")";
2348             }
2349              
2350 12         66 $rowlen += ($field_len[$i] = $rpt * $bytes);
2351             }
2352              
2353 6         20 $hdr{NAXIS1} = $rowlen;
2354              
2355             ## Now accumulate the binary table
2356              
2357 6         13 my $table = "";
2358 6         20 for my $r (0..$rows-1) {
2359 1932         3553 my $row = "";
2360 1932         4426 for my $c (1..$cols) {
2361 1956         2906 my $tmp;
2362 1956         3645 my $x = $fieldvars[$c];
2363 1956 100       5533 if ($internaltype[$c] eq 'P') { # PDL handling
2364             $tmp = $converters[$c]
2365 1952 100       8652 ? &{$converters[$c]}($x->slice($r)->flat->sever, $r, $c)
  1924         7152  
2366             : $x->slice($r)->flat->sever ;
2367             ## This would go faster if moved outside the loop but I'm too
2368             ## lazy to do it Right just now. Perhaps after it actually works.
2369 1952 50       14839 $tmp->type->bswap->($tmp) if !isbigendian();
2370 1952         3680 $tmp = ${ $tmp->get_dataref };
  1952         7908  
2371             } else { # Only other case is ASCII just now...
2372 4 50       15 $tmp = ( ref $x eq 'ARRAY' ) ? # Switch on array or string
    50          
2373             ( $#$x == 0 ? $x->[0] : $x->[$r] ) # broadcast arrays as needed
2374             : $x;
2375 4         10 $tmp .= " " x ($field_len[$c] - length($tmp));
2376             }
2377             # Now $tmp contains the bytes to be written out...
2378             #
2379 1956         11913 $row .= substr($tmp,0,$field_len[$c]);
2380             } # for: $c
2381 1932         5013 $table .= $row;
2382             } # for: $r
2383              
2384 6         19 my $table_size = $rowlen * $rows;
2385 6 50       33 if (length($table) != $table_size) {
2386 0         0 print "Warning: Table length is ".(length $table)."; expected $table_size\n";
2387             }
2388 6         617 return (\%hdr,$table, $heap);
2389             } elsif ($tbl eq 'ascii') {
2390 0         0 barf "ASCII tables not yet supported...\n";
2391             } else {
2392 0         0 barf "unknown table type '$tbl' -- giving up.";
2393             }
2394             }
2395              
2396             # the header fill value can be blanks, but the data fill value must
2397             # be zeroes in non-ASCII tables
2398             #
2399             sub _print_to_fits ($$$) {
2400 20     20   42 my $fh = shift;
2401 20         53 my $data = shift;
2402 20         49 my $blank = shift;
2403              
2404 20         65 my $len = ((length $data) - 1) % 2880 + 1;
2405 20         2919 print $fh $data . ($blank x (2880-$len));
2406             }
2407              
2408             {
2409             my $ctr = 0;
2410              
2411 6     6 0 15 sub reset_hdr_ctr() { $ctr = 0; }
2412              
2413             sub add_hdr_item ($$$$;$) {
2414 187     187 0 638 my ( $hdr, $key, $value, $type, $comment ) = @_;
2415 187 100       502 $type = uc($type) if defined $type;
2416 187         602 my $item = Astro::FITS::Header::Item->new( Keyword=>$key,
2417             Value=>$value,
2418             Type=>$type );
2419 187 100       22464 $item->comment( $comment ) if defined $comment;
2420 187         758 $hdr->replace( $ctr++, $item );
2421             };
2422             }
2423              
2424             ##############################
2425             #
2426             # _wfits_table -- given a hash ref, try to write it out as a
2427             # table extension. The file FITS should be open when you call it.
2428             # Most of the work of creating the extension header, and all of
2429             # the work of creating the table, is handled by _prep_table().
2430             #
2431             # NOTE:
2432             # can not think of a sensible name for the extension so calling
2433             # it TABLE for now
2434             #
2435             sub _wfits_table {
2436 6     6   11 my $fh = shift;
2437 6         12 my $hash = shift;
2438 6         11 my $tbl = shift;
2439              
2440 6 50       26 barf "FITS BINTABLES are not supported without the Astro::FITS::Header module.\nGet it from www.cpan.org.\n"
2441             unless($PDL::Astro_FITS_Header);
2442              
2443 6         31 my ($hdr,$table, $heap) = _prep_table($hash,$tbl,0);
2444 6 50       31 $heap="" unless defined($heap);
2445              
2446             # Copy the prepared fields into the extension header.
2447 6         74 tie my %newhdr,'Astro::FITS::Header',my $h = Astro::FITS::Header->new;
2448              
2449 6         2476 reset_hdr_ctr();
2450 6 50       41 add_hdr_item $h, "XTENSION", ($tbl eq 'ascii'?"TABLE":"BINTABLE"), 'string', "from perl hash";
2451 6         487 add_hdr_item $h, "BITPIX", $hdr->{BITPIX}, 'int';
2452 6         397 add_hdr_item $h, "NAXIS", 2, 'int';
2453 6         431 add_hdr_item $h, "NAXIS1", $hdr->{NAXIS1}, 'int', 'Bytes per row';
2454 6         536 add_hdr_item $h, "NAXIS2", $hdr->{NAXIS2}, 'int', 'Number of rows';
2455 6 50       621 add_hdr_item $h, "PCOUNT", length($heap), 'int', ($tbl eq 'ascii' ? undef : "No heap") ;
2456 6         611 add_hdr_item $h, "GCOUNT", 1, 'int';
2457 6         683 add_hdr_item $h, "TFIELDS", $hdr->{TFIELDS},'int';
2458 6 100       729 add_hdr_item $h, "THEAP", "0", 'int', "(No gap before heap)" if(length($heap));
2459 6         606 add_hdr_item $h, "HDUNAME", "TABLE", 'string';
2460              
2461 6         998 for my $field( sort fits_field_cmp keys %$hdr ) {
2462 179 100 100     55645 next if( defined $newhdr{$field} or $field =~ m/^end|simple|xtension$/i);
2463             my $type = (UNIVERSAL::isa($hdr->{field},'PDL') ?
2464             $hdr->{$field}->type :
2465 124 100       9278 ((($hdr->{$field})=~ m/^[tf]$/i) ?
    50          
2466             'logical' :
2467             undef ## 'string' seems to have a bug - 'undef' works OK
2468             ));
2469              
2470 124         560 add_hdr_item $h, $field, $hdr->{$field}, $type, $hdr->{"${field}_COMMENT"};
2471             }
2472              
2473 6         2509 add_hdr_item $h, "END", undef, 'undef';
2474              
2475 6         3015 $hdr = join("",$h->cards);
2476 6         11544 _print_to_fits( $fh, $hdr, " " );
2477 6         400 _print_to_fits( $fh, $table.$heap, "\0" ); # use " " if it is an ASCII table
2478             # Add heap dump
2479             }
2480              
2481             sub _wfits_nullhdu {
2482 8     8   23 my $fh = shift;
2483 8 50       30 if($Astro::FITS::Header) {
2484 0         0 my $h = Astro::FITS::Header->new();
2485              
2486 0         0 reset_hdr_ctr();
2487 0         0 add_hdr_item $h, "SIMPLE", "T", 'logical', "Null HDU (no data, only extensions)";
2488 0         0 add_hdr_item $h, "BITPIX", -32, 'int', "Needed to make fverify happy";
2489 0         0 add_hdr_item $h, "NAXIS", 0, 'int';
2490 0         0 add_hdr_item $h, "EXTEND", "T", 'logical', "File contains extensions";
2491 0         0 add_hdr_item $h, "COMMENT", "", "comment",
2492             " File written by perl (PDL::IO::FITS::wfits)";
2493             #
2494             # The following seems to cause a problem so removing for now (I don't
2495             # believe it is required, but may be useful for people who aren't
2496             # FITS connoisseurs). It could also be down to a version issue in
2497             # Astro::FITS::Header since it worked on linux with a newer version
2498             # than on Solaris with an older version of the header module)
2499             #
2500             ## add_hdr_item $h, "COMMENT", "", "comment",
2501             ## " FITS (Flexible Image Transport System) format is defined in 'Astronomy";
2502             ## add_hdr_item $h, "COMMENT", "", "comment",
2503             ## " and Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H";
2504 0         0 add_hdr_item $h, "HDUNAME", "PRIMARY", 'string';
2505 0         0 add_hdr_item $h, "END", undef, 'undef';
2506              
2507 0         0 my $hdr = join("",$h->cards);
2508 0         0 _print_to_fits( $fh, $hdr, " " );
2509             } else {
2510 8         37 _print_to_fits( $fh,
2511             q+SIMPLE = T / Null HDU (no data, only extensions) BITPIX = -32 / Needed to make fverify happy NAXIS = 0 EXTEND = T / File contains extensions COMMENT Written by perl (PDL::IO::FITS::wfits) legacy code. COMMENT For best results, install Astro::FITS::Header. HDUNAME = 'PRIMARY ' END +,
2512             " ");
2513             }
2514             }
2515              
2516             1;