File Coverage

blib/lib/PDL/IO/FlexRaw.pm
Criterion Covered Total %
statement 205 306 66.9
branch 96 232 41.3
condition 22 66 33.3
subroutine 14 15 93.3
pod 4 7 57.1
total 341 626 54.4


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             PDL::IO::FlexRaw -- A flexible binary I/O format for PerlDL
4              
5             =head1 SYNOPSIS
6              
7             use PDL;
8             use PDL::IO::FlexRaw;
9              
10             # To obtain the header for reading (if multiple files use the
11             # same header, for example):
12             #
13             $hdr = PDL::IO::FlexRaw::_read_flexhdr("filename.hdr")
14              
15             ($x,$y,...) = readflex("filename" [, $hdr])
16             ($x,$y,...) = mapflex("filename" [, $hdr] [, $opts])
17              
18             $hdr = writeflex($file, $pdl1, $pdl2,...)
19             writeflexhdr($file, $hdr)
20              
21             # if $PDL::IO::FlexRaw::writeflexhdr is true and
22             # $file is a filename, writeflexhdr() is called automatically
23             #
24             $hdr = writeflex($file, $pdl1, $pdl2,...) # need $hdr for something
25             writeflex($file, $pdl1, $pdl2,...) # ..if $hdr not needed
26              
27             =head1 DESCRIPTION
28              
29             FlexRaw is a generic method for the input and output of `raw' data
30             arrays. In particular, it is designed to read output from FORTRAN 77
31             UNFORMATTED files and the low-level C write function, even if the
32             files are compressed or gzipped. As in FastRaw, the data file is
33             supplemented by a header file (although this can be replaced by the
34             optional C<$hdr> argument). More information can be included in the
35             header file than for FastRaw -- the description can be extended to
36             several data objects within a single input file.
37              
38             For example, to read the output of a FORTRAN program
39              
40             real*4 a(4,600,600)
41             open (8,file='banana',status='new',form='unformatted')
42             write (8) a
43             close (8)
44              
45             the header file (`banana.hdr') could look like
46              
47             # FlexRaw file header
48             # Header word for F77 form=unformatted
49             Byte 1 4
50             # Data
51             Float 3 # this is ignored
52             4 600 600
53             Byte 1 4 As is this, as we've got all dims
54              
55             The data can then be input using
56              
57             $x = (readflex('banana'))[1];
58              
59             The format of the hdr file is an extension of that used by FastRaw.
60             Comment lines (starting with #) are allowed, as are descriptive names
61             (as elsewhere: byte, short, ushort, long, float, double) for the data
62             types -- note that case is ignored by FlexRaw. After the type, one
63             integer specifies the number of dimensions of the data `chunk', and
64             subsequent integers the size of each dimension. So the specifier
65             above (`Float 3 4 600 600') describes our FORTRAN array. A scalar can
66             be described as `float 0' (or `float 1 1', or `float 2 1 1', etc.).
67              
68             When all the dimensions are read -- or a # appears after whitespace --
69             the rest of the current input line is ignored, I badvalues
70             are being read or written. In that case, the next token will be the
71             string C followed by the bad value used, if needed.
72              
73             What about the extra 4 bytes at the head and tail, which we just threw
74             away? These are added by FORTRAN (at least on Suns, Alphas and
75             Linux), and specify the number of bytes written by each WRITE -- the
76             same number is put at the start and the end of each chunk of data.
77             You I need to know all this in some cases. In general, FlexRaw
78             tries to handle it itself, if you simply add a line saying `f77' to
79             the header file, I any data specifiers:
80              
81             # FlexRaw file header for F77 form=unformatted
82             F77
83             # Data
84             Float 3
85             4 600 600
86              
87             -- the redundancy in FORTRAN data files even allows FlexRaw to
88             automatically deal with files written on other machines which use
89             back-to-front byte ordering. This won't always work -- it's a 1 in 4
90             billion chance it won't, even if you regularly read 4Gb files! Also,
91             it currently doesn't work for compressed files, so you can say `swap'
92             (again before any data specifiers) to make certain the byte order is
93             swapped.
94              
95             The optional C<$hdr> argument allows the use of an anonymous array to
96             give header information, rather than using a .hdr file. For example,
97              
98             $header = [
99             {Type => 'f77'},
100             {Type => 'float', NDims => 3, Dims => [ 4,600,600 ] }
101             ];
102             @a = readflex('banana',$header);
103              
104             reads our example file again. As a special case, when NDims is 1, Dims
105             may be given as a scalar.
106              
107             Within PDL, readflex and writeflex can be used to write several pdls
108             to a single file -- e.g.
109              
110             use PDL;
111             use PDL::IO::FlexRaw;
112              
113             @pdls = ($pdl1, $pdl2, ...);
114             $hdr = writeflex("fname",@pdls);
115             @pdl2 = readflex("fname",$hdr);
116              
117             writeflexhdr("fname",$hdr); # not needed if $PDL::IO::FlexRaw::writeflexhdr is set
118             @pdl3 = readflex("fname");
119              
120             -- C produces the data file and returns the file header as an
121             anonymous hash, which can be written to a .hdr file using
122             C.
123              
124             If the package variable C<$PDL::IO::FlexRaw::writeflexhdr>
125             is true, and the C call was with a I and not
126             a handle, C will be called automatically (as done by
127             C.
128              
129             The reading of compressed data is switched on automatically if the
130             filename requested ends in .gz or .Z, or if the originally specified
131             filename does not exist, but one of these compressed forms does.
132              
133             If C and C are given a reference to a
134             file handle as a first parameter instead of a filename, then
135             the data is read or written to the open filehandle. This
136             gives an easy way to read an arbitrary slice in a big data
137             volume, as in the following example:
138              
139             use PDL;
140             use PDL::IO::FastRaw;
141              
142             open(DATA, "raw3d.dat");
143             binmode(DATA);
144              
145             # assume we know the data size from an external source
146             ($width, $height, $data_size) = (256,256, 4);
147              
148             my $slice_num = 64; # slice to look at
149             # Seek to slice
150             seek(DATA, $width*$height*$data_size * $slice_num, 0);
151             $pdl = readflex \*DATA, [{Dims=>[$width, $height], Type=>'long'}];
152              
153             WARNING: In later versions of perl (5.8 and up) you must
154             be sure that your file is in "raw" mode (see the perlfunc
155             man page entry for "binmode", for details). Both readflex
156             and writeflex automagically switch the file to raw mode for
157             you -- but in code like the snipped above, you could end up
158             seeking the wrong byte if you forget to make the binmode() call.
159              
160             C memory maps, rather than reads, the data files. Its interface
161             is similar to C. Extra options specify if the data is to be
162             loaded `ReadOnly', if the data file is to be `Creat'-ed anew on the
163             basis of the header information or `Trunc'-ated to the length of the
164             data read. The extra speed of access brings with it some limitations:
165             C won't read compressed data, auto-detect f77 files, or read f77
166             files written by more than a single unformatted write statement. More
167             seriously, data alignment constraints mean that C cannot read
168             some files, depending on the requirements of the host OS (it may also
169             vary depending on the setting of the `uac' flag on any given machine).
170             You may have run into similar problems with common blocks in FORTRAN.
171              
172             For instance, floating point numbers may have to align on 4 byte
173             boundaries -- if the data file consists of 3 bytes then a float, it
174             cannot be read. C will warn about this problem when it occurs,
175             and return the PDLs mapped before the problem arose. This can be
176             dealt with either by reorganizing the data file (large types first
177             helps, as a rule-of-thumb), or more simply by using C.
178              
179             =head1 BUGS
180              
181             The test on two dimensional byte arrays fail using g77 2.7.2, but not
182             Sun f77. I hope this isn't my problem!
183              
184             Assumes gzip is on the PATH.
185              
186             Can't auto-swap compressed files, because it can't seek on them.
187              
188             The header format may not agree with that used elsewhere.
189              
190             Should it handle handles?
191              
192             Mapflex should warn and fallback to reading on SEGV? Would have to
193             make sure that the data was written back after it was `destroyed'.
194              
195             =head1 FUNCTIONS
196              
197             =head2 readflex
198              
199             =for ref
200              
201             Read a binary file with flexible format specification
202              
203             =for usage
204              
205             Usage:
206              
207             ($x,$y,...) = readflex("filename" [, $hdr])
208             ($x,$y,...) = readflex(FILEHANDLE [, $hdr])
209              
210              
211             =head2 writeflex
212              
213             =for ref
214              
215             Write a binary file with flexible format specification
216              
217             =for usage
218              
219             Usage:
220              
221             $hdr = writeflex($file, $pdl1, $pdl2,...) # or
222             $hdr = writeflex(FILEHANDLE, $pdl1, $pdl2,...)
223             # now you must call writeflexhdr()
224             writeflexhdr($file, $hdr)
225              
226             or
227              
228             $PDL::IO::FlexRaw::writeflexhdr = 1; # set so we don't have to call writeflexhdr
229              
230             $hdr = writeflex($file, $pdl1, $pdl2,...) # remember, $file must be filename
231             writeflex($file, $pdl1, $pdl2,...) # remember, $file must be filename
232              
233             =head2 writeflexhdr
234              
235             =for ref
236              
237             Write the header file corresponding to a previous writeflex call
238              
239             =for usage
240              
241             Usage:
242              
243             writeflexhdr($file, $hdr)
244              
245             $file or "filename" is the filename used in a previous writeflex
246             If $file is actually a "filename" then writeflexhdr() will be
247             called automatically if $PDL::IO::FlexRaw::writeflexhdr is true.
248             If writeflex() was to a FILEHANDLE, you will need to call
249             writeflexhdr() yourself since the filename cannot be determined
250             (at least easily).
251              
252             =head2 mapflex
253              
254             =for ref
255              
256             Memory map a binary file with flexible format specification
257              
258             =for usage
259              
260             Usage:
261              
262             ($x,$y,...) = mapflex("filename" [, $hdr] [, $opts])
263              
264             =for options
265              
266             All of these options default to false unless set true:
267              
268             ReadOnly - Data should be readonly
269             Creat - Create file if it doesn't exist
270             Trunc - File should be truncated to a length that conforms
271             with the header
272              
273             =head2 _read_flexhdr
274              
275             Read a FlexRaw header file and return a header structure.
276              
277             =for usage
278              
279             Usage:
280              
281             $hdr = PDL::IO::FlexRaw::_read_flexhdr($file)
282              
283             Note that C<_read_flexhdr> is supposed to be an internal function. It
284             was not originally documented and it is not tested. However, there
285             appeared to be no other method for obtaining a header structure from
286             a file, so I figured I would write a small bit of documentation on it.
287              
288             =head1 Bad Value Support
289              
290             As of PDL-2.4.8, L has support for reading and writing
291             pdls with L values in them.
292              
293             On C, a piddle
294             argument with C<< $pdl->badflag == 1 >> will have the keyword/token "badvalue"
295             added to the header file after the dimension list and an additional token
296             with the bad value for that pdl if C<< $pdl->badvalue != $pdl->orig_badvalue >>.
297              
298             On C, a pdl with the "badvalue" token in the header will
299             automatically have its L set and its
300             L as well if it is not the standard default for that type.
301              
302             =for example
303              
304             The new badvalue support required some additions to the header
305             structure. However, the interface is still being finalized. For
306             reference the current C<$hdr> looks like this:
307              
308             $hdr = {
309             Type => 'byte', # data type
310             NDims => 2, # number of dimensions
311             Dims => [640,480], # dims
312             BadFlag => 1, # is set/set badflag
313             BadValue => undef, # undef==default
314             };
315              
316             $badpdl = readflex('badpdl', [$hdr]);
317              
318             If you use bad values and try the new L bad value
319             support, please let us know via the perldl mailing list.
320             Suggestions and feedback are also welcome.
321              
322              
323             =head1 AUTHOR
324              
325             Copyright (C) Robin Williams 1997.
326             All rights reserved. There is no warranty. You are allowed
327             to redistribute this software / documentation under certain
328             conditions. For details, see the file COPYING in the PDL
329             distribution. If this file is separated from the PDL distribution,
330             the copyright notice should be included in the file.
331              
332             Documentation contributions copyright (C) David Mertens, 2010.
333              
334             =cut
335              
336             package PDL::IO::FlexRaw;
337              
338             BEGIN {
339 3     3   2576 our $have_file_map = 0;
340              
341 3     3   229 eval "use File::Map 0.57 qw(map_file)";
  3         28  
  3         78  
  3         36  
342 3 50       301 $have_file_map = 1 unless $@;
343             }
344              
345 3     3   20 use PDL;
  3         19  
  3         25  
346 3     3   22 use Exporter;
  3         8  
  3         112  
347 3     3   1675 use FileHandle;
  3         3034  
  3         19  
348 3     3   927 use PDL::Types ':All';
  3         7  
  3         598  
349 3     3   25 use PDL::IO::Misc qw(bswap2 bswap4 bswap8);
  3         7  
  3         25  
350              
351             @PDL::IO::FlexRaw::ISA = qw/Exporter/;
352              
353             @EXPORT = qw/writeflex writeflexhdr readflex mapflex/;
354              
355             # Cast type numbers in concrete, for external file's sake...
356             %flexnames = ( map {(typefld($_,'numval') => typefld($_,'ioname'))}
357             typesrtkeys());
358             %flextypes = ( map {(typefld($_,'ioname') => typefld($_,'numval'),
359             typefld($_,'numval') => typefld($_,'numval'),
360             lc typefld($_,'ppsym') => typefld($_,'numval'),
361             )}
362             typesrtkeys());
363             %flexswap = ( map {my $val = typefld($_,'numval');
364             my $nb = PDL::Core::howbig($val);
365             ($val => $nb > 1 ? "bswap$nb" : undef)}
366             typesrtkeys());
367              
368             # use Data::Dumper;
369             # print Dumper \%flexnames;
370             # print Dumper \%flextypes;
371             # print Dumper \%flexswap;
372              
373             # %flexnames = (
374             # $PDL_B => 'byte', $PDL_S => 'short',
375             # $PDL_US => 'ushort', $PDL_L => 'long',
376             # $PDL_F => 'float', $PDL_D => 'double');
377              
378             # %flextypes = (
379             # 'byte' => $PDL_B, '0' => $PDL_B, 'b' => $PDL_B, 'c' => $PDL_B,
380             # 'short' => $PDL_S, '1' => $PDL_S, 's' => $PDL_S,
381             # 'ushort' => $PDL_US,'2' => $PDL_US,'u' => $PDL_US,
382             # 'long' => $PDL_L, '3' => $PDL_L, 'l' => $PDL_L,
383             # 'float' => $PDL_F, '4' => $PDL_F, 'f' => $PDL_F,
384             # 'double' => $PDL_D, '5' => $PDL_D, 'd' => $PDL_D
385             # );
386              
387             $PDL::IO::FlexRaw::verbose = 0;
388             $PDL::IO::FlexRaw::writeflexhdr = defined($PDL::FlexRaw::IO::writeflexhdr) ? $PDL::FlexRaw::IO::writeflexhdr : 0;
389              
390             sub _read_flexhdr {
391 14     14   36 my ($hname) = @_;
392 14 50       72 my $hfile = new FileHandle "$hname"
393             or barf "Couldn't open '$hname' for reading";
394 14         965 binmode $hfile;
395 14         33 my ($newfile) = 1;
396 14         35 my ($tid, @str);
397 14         0 my (@ret);
398             # check for ENVI files and bail (for now)
399 14         250 my $line1 = scalar <$hfile>;
400 14 50       81 barf "This is an ENVI format file, please use readenvi()\n" if $line1 =~ /^ENVI\r?$/;
401 14         154 seek $hfile, 0, 0; # reset file pointer to beginning
402             ITEM:
403 14         128 while (!eof($hfile)) {
404 29         67 my (@dims) = (); my ($ndims) = -1, ($mode) = -2;
  29         61  
405 29         47 my ($have_badvalue) = undef;
406 29         38 my ($badvalue) = undef;
407             LINE:
408 29         97 while (<$hfile>) {
409             ### print STDERR "processing line '$_'\n";
410 74 100 100     508 next LINE if /^#/ or /^\s*$/;
411 45         80 chop;
412 45         75 tr/A-Z/a-z/;
413 45         124 @str = split;
414 45         66 TOKEN:
415             ### print STDERR "Got tokens: " . join(',',@str) . "\n";
416             my $numtokens = scalar @str;
417 45         74 foreach my $token (@str) {
418 50 50       97 next LINE if $token =~ /^#/;
419 50 100 0     142 if ($mode == -2) { # type
    100 0        
    50 0        
    0          
    0          
420             ### print STDERR " \$mode == -2: #tokens=$numtokens, '$token'\n";
421 15 100       50 if ($newfile) {
422 14 50 33     67 if ($token eq 'f77' || $token eq 'swap') {
423 0         0 push @ret, {
424             Type => $token
425             };
426 0         0 $numtokens--;
427 0         0 next ITEM;
428             }
429             }
430 15 50       48 barf("Bad typename '$token' in readflex") if (!exists($flextypes{$token}));
431 15         25 $tid = $flextypes{$token};
432 15         25 $numtokens--;
433 15         21 $newfile = 0;
434 15         47 $mode++;
435             } elsif ($mode == -1) { #ndims
436             ### print STDERR " \$mode == -1: #tokens=$numtokens, '$token'\n";
437 15 50       58 barf("Not number for ndims in readflex") if $token !~ /^\d*$/;
438 15         26 $ndims = $token;
439 15 50       40 barf("Bad ndims in readflex") if ($ndims < 0);
440 15         19 $numtokens--;
441 15         24 $mode++;
442 15 50 33     63 if ($mode == $ndims and $numtokens == 0) {
443 0         0 last LINE;
444             }
445             } elsif ($mode < $ndims) { # get dims
446             ### print STDERR " # get dims: #tokens=$numtokens, '$token'\n";
447 20 50       62 barf("Not number for dimension in readflex")
448             if $token !~ /^\d*$/;
449 20         42 push(@dims,$token);
450 20         32 $numtokens--;
451 20         24 $mode++;
452 20 100 66     74 if ($mode == $ndims and $numtokens == 0) {
453 15         35 last LINE;
454             }
455             } elsif ($mode == $ndims and ! $have_badvalue) { # check for badvalue info
456             ### print STDERR " # ! \$have_badvalue: #tokens=$numtokens, '$token'\n";
457 0 0       0 if ($token =~ /^badvalue$/ ) {
458 0         0 $have_badvalue = 1;
459 0         0 $numtokens--;
460 0 0       0 last LINE if $numtokens==0; # using default bad value
461             } else {
462 0         0 last LINE;
463             }
464             } elsif ($mode == $ndims and $have_badvalue and $numtokens > 0) {
465             ### print STDERR " # \$have_badvalue: #tokens = $numtokens, '$token'\n";
466 0         0 $badvalue = $token;
467 0         0 last LINE;
468             }
469             }
470             }
471 29 100       86 last ITEM if $mode == -2;
472 15 50 33     64 barf("Bad format in readflex header file ($ndims, $mode)") if ($ndims < 0 || $mode != $ndims);
473 15 50       113 push @ret, {
474             Type => $tid,
475             Dims => \@dims,
476             NDims => $ndims,
477             BadFlag => (($have_badvalue) ? 1 : 0),
478             BadValue => $badvalue,
479             };
480             }
481 14         208 return \@ret;
482             }
483              
484             sub readchunk {
485 14     14 0 35 my ($d, $pdl, $len, $name) = @_;
486 14         22 my ($nread);
487 14 50       36 print "Reading $len at $offset from $name\n"
488             if $PDL::IO::FlexRaw::verbose;
489 14 50       25 ($nread = read($d, ${$pdl->get_dataref}, $len)) == $len
  14         248  
490             or barf "Couldn't read $len bytes at offset $offset from '$name', got $nread";
491 14         88 $pdl->upd_data();
492 14         29 $offset += $len;
493 14         43 return 1;
494             }
495              
496             sub myhandler {
497 0     0 0 0 $flexmapok = 0;
498 0         0 barf "Data out of alignment, can't map further\n";
499 0         0 die;
500             }
501              
502             sub mapchunk {
503 2     2 0 7 my ($orig, $pdl, $len, $name) = @_;
504             # link $len at $offset from $orig to $pdl.
505             # print "linking $len bytes from $offset\n";
506 2         13 $pdl->set_data_by_offset($orig,$offset);
507 2         5 local ($flexmapok)=1;
508 2 50       90 local $SIG{BUS} = \&myhandler unless $^O =~ /MSWin32/i;
509 2         33 local $SIG{FPE} = \&myhandler;
510 2         7 eval {$pdl->clump(-1)->at(0)};
  2         12  
511 2         19 $offset += $len;
512 2         50 $flexmapok;
513             }
514              
515             sub readflex {
516 13 50   13 1 995 barf 'Usage ($x,$y,...) = readflex("filename"|FILEHANDLE [, \@hdr])'
517             if $#_ > 1;
518 13         46 my ($name,$h) = @_;
519 13         28 my ($hdr, $pdl, $len, @out, $chunk, $chunkread, $data);
520 13         27 local ($offset) = 0;
521 13         33 my ($newfile, $swapbyte, $f77mode, $zipt) = (1,0,0,0);
522 13         22 my $d;
523             # print("readflex: name is $name\n");
524             # Test if $name is a file handle
525 13 50       87 if (defined fileno($name)) {
526 0         0 $d = $name;
527 0         0 binmode($d);
528             }
529             else {
530 13         44 $name =~ s/\.(gz|Z)$//; # strip any trailing compression suffix
531 13         24 $data = $name;
532 13 50       222 if(! -e $name ) { # If it's still not found, then...
533 0         0 suffix: for my $suffix('gz','Z') {
534 0 0       0 if( -e "$name.$suffix" ) {
535              
536             ## This little fillip detects gzip if we need it, and caches
537             ## the version in a package-global variable. The return string
538             ## is undefined if there is no gzip in the path.
539 0         0 our $gzip_version;
540 0 0       0 unless(defined($gzip_version)) {
541             # Try running gzip -V to get the version. Redirect STDERR to STDOUT since
542             # Apple'z gzip writes its version to STDERR.
543 0         0 $gzip_version = `gzip -V 2>&1`;
544 0 0       0 unless(defined($gzip_version)) {
545             # That may or may not work on Microsoft Windows, so if it doesn't,
546             # try running gzip again without the redirect.
547 0         0 $gzip_version = `gzip -V`;
548             }
549 0 0       0 barf "FlexRaw: couldn't find the external gzip utility (to parse $name.$suffix)!" unless(defined($gzip_version));
550             }
551            
552 0 0       0 if($gzip_version =~ m/^Apple/) {
553             # Apple gzip requires a suffix
554 0         0 $data = "gzip -dcq $name.$suffix |";
555             } else {
556             # Other gzips apparently don't require a suffix - they find it automagically.
557 0         0 $data = "gzip -dcq $name |";
558             }
559              
560 0         0 $zipt = 1;
561 0         0 last suffix;
562             }
563             }
564             }
565 13         159 my ($size) = (stat $name)[7];
566 13 50       104 $d = new FileHandle $data
567             or barf "Couldn't open '$data' for reading";
568 13         981 binmode $d;
569 13 50       69 $h = _read_flexhdr("$name.hdr")
570             unless $h;
571             }
572              
573             # Go through headers which reconfigure
574 13         50 foreach $hdr (@$h) {
575 13         38 my ($type) = $hdr->{Type};
576 13 50       52 if ($type eq 'swap') {
    50          
577 0         0 $swapbyte = 1;
578             } elsif ($type ne 'f77') {
579 13         27 last;
580             }
581             }
582              
583             READ:
584 13         27 foreach $hdr (@$h) {
585 14         31 my ($type) = $hdr->{Type};
586             # Case convert when we have user data
587 14 50       46 $type =~ tr/A-Z/a-z/ if $#_ == 1;
588 14 100       34 if ($newfile) {
589 13 50       49 if ($type eq 'f77') {
    50          
590 0         0 $hdr = {
591             Type => $PDL_L,
592             Dims => [ ],
593             NDims => 0
594             };
595 0         0 $type = $PDL_L;
596 0         0 $f77mode = 1;
597             } elsif ($type eq 'swap') {
598 0         0 next READ;
599             } else {
600 13         20 $newfile = 0;
601             }
602             }
603 14 50       37 if ($#_ == 1) {
604             barf("Bad typename '$type' in readflex")
605 0 0       0 if (!defined($flextypes{$type}));
606 0         0 $type = $flextypes{$type};
607             }
608             $pdl = PDL->zeroes ((new PDL::Type($type)),
609 14 50       102 ref $hdr->{Dims} ? @{$hdr->{Dims}} : $hdr->{Dims});
  14         77  
610 14         47 $len = length $ {$pdl->get_dataref};
  14         53  
611              
612 14 50       48 &readchunk($d,$pdl,$len,$name) or last READ;
613 14         42 $chunkread += $len;
614 14 50       53 if ($swapbyte) {
615 0         0 my $method = $flexswap{$type};
616 0 0       0 $pdl->$method if $method;
617             # bswap2($pdl) if $pdl->get_datatype == $PDL_S;
618             # bswap4($pdl) if $pdl->get_datatype == $PDL_L
619             # || $pdl->get_datatype == $PDL_F;
620             # bswap8($pdl) if $pdl->get_datatype == $PDL_D;
621             }
622 14 50 33     37 if ($newfile && $f77mode) {
623 0 0 0     0 if ($zipt || $swapbyte) {
624 0         0 $chunk = $pdl->copy;
625 0         0 $chunkread = 0;
626 0         0 next READ;
627             } else {
628             SWAP:
629 0         0 foreach (0,1) {
630 0         0 seek($d,4,0);
631 0         0 $swapbyte = $_;
632 0 0       0 bswap4($pdl) if $swapbyte;
633 0         0 $chunk = $pdl->copy;
634 0 0       0 next SWAP if ! seek($d,$pdl->at,1);
635             next SWAP if
636 0 0       0 read($d,$ {$chunk->get_dataref},$len) != $len;
  0         0  
637 0         0 $chunk->upd_data;
638 0 0       0 bswap4($chunk) if $swapbyte;
639 0 0       0 next SWAP if ($pdl->at != $chunk->at);
640 0         0 $chunkread = 0;
641 0 0       0 barf "Error can't rewind" if !seek($d,4,0);
642             # print "OK".($swapbyte?", swapped":""),"\n";
643 0         0 next READ;
644             }
645 0         0 barf "Error: Doesn't look like f77 file (even swapped)";
646             }
647             }
648              
649 14 50       41 if ($hdr->{BadFlag}) { # set badflag and badvalue if needed
650 0         0 $pdl->badflag($hdr->{BadFlag});
651 0 0       0 $pdl->badvalue($hdr->{BadValue}) if defined $hdr->{BadValue};
652             }
653 14         33 push (@out,$pdl);
654              
655 14 50 33     47 if ($f77mode && $chunk->at == $chunkread) {
656 0         0 $chunkread = 0;
657 0         0 my ($check) = $chunk->copy;
658 0 0       0 &readchunk($d,$check,4,$name) or last READ;
659 0 0       0 bswap4($check) if $swapbyte;
660 0 0       0 if ($check->at ne $chunk->at) {
661 0         0 barf "F77 file format error for $check cf $chunk";
662 0         0 last READ;
663             }
664 0 0       0 if (!eof($d)) {
665 0 0       0 &readchunk($d,$chunk,4,$name) or last READ;
666 0 0       0 bswap4($chunk) if $swapbyte;
667             } else {
668 0         0 last READ;
669             }
670             }
671             }
672 13 100       277 wantarray ? @out : $out[0];
673             }
674              
675             sub mapflex {
676 2     2 1 7 my ($usage)
677             = 'Usage ($x,$y,...) = mapflex("filename" [, \@hdr] [,\%opts])';
678 2         7 my $name = shift;
679             # reference to header array
680 2         5 my ($h, $size);
681             # reference to options array, with defaults
682 2         8 my (%opts) = ( 'ReadOnly' => 0, 'Creat' => 0, 'Trunc' => 0 );
683              
684 2         6 my ($hdr, $d, $pdl, $len, @out, $chunk, $chunkread);
685 2         6 local ($offset) = 0;
686 2         8 my ($newfile, $swapbyte, $f77mode, $zipt) = (1,0,0,0);
687              
688 2         9 foreach (@_) {
689 2 100       18 if (ref($_) eq "ARRAY") {
    50          
690 1         4 $h = $_;
691             } elsif (ref($_) eq "HASH") {
692 1         9 %opts = (%opts,%$_);
693             } else {
694 0         0 warn $usage;
695             }
696             }
697              
698 2 50 33     109 if ($name =~ s/\.gz$// || $name =~ s/\.Z$// ||
      33        
      66        
      33        
699             (! -e $name && (-e $name.'.gz' || -e $name.'.Z'))) {
700 0         0 barf "Can't map compressed file";
701             }
702              
703 2 100       11 if (!defined $h) {
704 1         6 $h = _read_flexhdr("$name.hdr");
705             }
706              
707             # Go through headers which reconfigure
708 2         9 foreach $hdr (@$h) {
709 2         8 my ($type) = $hdr->{Type};
710 2 50       12 if ($type eq 'swap') {
    50          
711 0         0 barf "Can't map byte swapped file";
712             } elsif ($type eq 'f77') {
713 0         0 $f77mode = 1;
714             } else {
715 2         4 my($si) = 1;
716 2 50       8 foreach (ref $hdr->{Dims} ? @{$hdr->{Dims}} : $hdr->{Dims}) {
  2         6  
717 4         10 $si *= $_;
718             }
719             barf("Bad typename '$type' in mapflex")
720 2 50       10 unless defined $flextypes{$type};
721 2         5 $type = $flextypes{$type};
722 2         14 $size += $si * PDL::Core::howbig ($type);
723             }
724             }
725             # $s now contains estimated size of data in header --
726             # setting $f77mode means that it will be 8 x n bigger in reality
727 2 50       8 $size += 8 if ($f77mode);
728 2 100       7 if (!($opts{Creat})) {
729 1         4 my ($s) = $size;
730 1         17 $size = (stat $name)[7];
731 1 50       8 barf "File looks too small ($size cf header $s)" if $size < $s;
732             }
733             # print "Size $size f77mode $f77mode\n";
734              
735 2         11 $d = PDL->zeroes(byte());
736              
737             # print "Mapping total size $size\n";
738             # use Data::Dumper;
739             # print "Options: ", Dumper(\%opts), "\n";
740 2 50 33     17 if ($have_file_map and not defined($PDL::force_use_mmap_code) ) {
741             $d->set_data_by_file_map($name,
742             $size,
743             1,
744             ($opts{ReadOnly}?0:1),
745             ($opts{Creat}?1:0),
746             (0644),
747 2 50 66     26 ($opts{Creat} || $opts{Trunc} ? 1:0)
    100          
    100          
748             );
749             } else {
750 0         0 warn "mapflex: direct mmap support being deprecated, please install File::Map\n";
751             $d->set_data_by_mmap($name,
752             $size,
753             1,
754             ($opts{ReadOnly}?0:1),
755             ($opts{Creat}?1:0),
756             (0644),
757 0 0 0     0 ($opts{Creat} || $opts{Trunc} ? 1:0)
    0          
    0          
758             );
759             }
760             READ:
761 2         7 foreach $hdr (@$h) {
762 2         9 my ($type) = $hdr->{Type};
763             # Case convert when we have user data
764 2 100       11 $type =~ tr/A-Z/a-z/ if $#_ == 1;
765 2 50       6 if ($newfile) {
766 2 50       9 if ($type eq 'f77') {
767 0         0 $hdr = {
768             Type => $PDL_L,
769             Dims => [ ],
770             NDims => 0
771             };
772 0         0 $type = $PDL_L;
773             } else {
774 2         5 $newfile = 0;
775             }
776             }
777 2 100       8 if ($#_ == 1) {
778             barf("Bad typename '$type' in mapflex")
779 1 50       7 unless defined $flextypes{$type};
780 1         3 $type = $flextypes{$type};
781             }
782             my $pdl = PDL->zeroes ((new PDL::Type($type)),
783 2 50       26 ref $hdr->{Dims} ? @{$hdr->{Dims}} : $hdr->{Dims});
  2         15  
784 2         7 $len = length $ {$pdl->get_dataref};
  2         9  
785              
786 2 50       11 &mapchunk($d,$pdl,$len,$name) or last READ;
787 2         7 $chunkread += $len;
788 2 50 33     11 if ($newfile && $f77mode) {
789 0 0       0 if ($opts{Creat}) {
790 0         0 $pdl->set(0,$size - 8);
791             } else {
792 0         0 $chunk = $pdl->copy;
793             }
794 0         0 $chunkread = 0;
795 0         0 next READ;
796             }
797              
798 2 50       9 if ($hdr->{BadFlag}) { # set badflag and badvalue if needed
799 0         0 $pdl->badflag($hdr->{BadFlag});
800 0 0       0 $pdl->badvalue($hdr->{BadValue}) if defined $hdr->{BadValue};
801             }
802 2         7 push (@out,$pdl);
803              
804 2 50 33     11 if ($f77mode && $chunk->at == $chunkread) {
805 0         0 $chunkread = 0;
806 0         0 my ($check) = $chunk->copy;
807 0 0       0 &mapchunk($d,$check,4,$name) or last READ;
808 0 0       0 if ($opts{Creat}) {
809 0         0 $check->set(0,$size-8);
810             } else {
811 0 0       0 if ($check->at ne $chunk->at) {
812 0         0 barf "F77 file format error for $check cf $chunk";
813 0         0 last READ;
814             }
815             }
816 0 0       0 barf "Will only map first f77 data statement" if ($offset < $size);
817 0         0 last READ;
818             }
819             }
820 2 50       24 wantarray ? @out : $out[0];
821             }
822              
823             sub writeflex {
824 11     11 1 64 my $usage = 'Usage $hdr = writeflex("filename"|FILEHANDLE,$pdl,...)';
825 11 50       35 barf $usage if $#_<0;
826 11         26 my($name) = shift;
827 11         16 my $isname = 0;
828 11         28 my $hdr;
829             my $d;
830              
831             # Test if $name is a file handle
832 11 50       70 if (defined fileno($name)) {
833 0         0 $d = $name;
834 0         0 binmode $d;
835             }
836             else {
837 11 50       30 barf $usage if ref $name;
838 11         19 $isname = 1;
839 11 50       75 my $modename = ($name =~ /^[+]?[><|]/) ? $name : ">$name";
840 11 50       70 $d = new FileHandle $modename
841             or barf "Couldn't open '$name' for writing";
842 11         2971 binmode $d;
843             }
844 11         45 foreach $pdl (@_) {
845 12 50       40 barf $usage if ! ref $pdl;
846             # print join(' ',$pdl->getndims,$pdl->dims),"\n";
847 12         126 push @{$hdr}, {
848 12 50       23 Type => $flexnames{$pdl->get_datatype},
849             Dims => [ $pdl->dims ],
850             NDims => $pdl->getndims,
851             BadFlag => $pdl->badflag,
852             BadValue => (($pdl->badvalue == $pdl->orig_badvalue) ? undef : $pdl->badvalue),
853             };
854 12         124 print $d $ {$pdl->get_dataref};
  12         143  
855             }
856 11 50       38 if (defined wantarray) {
857             # list or scalar context
858 11 50 33     60 writeflexhdr($name, $hdr) if $isname and $PDL::IO::FlexRaw::writeflexhdr;
859 11         772 return $hdr;
860             } else {
861             # void context so write header file
862 0 0       0 writeflexhdr($name, $hdr) if $isname;
863 0         0 return;
864             }
865             }
866              
867             sub writeflexhdr {
868 12 50 33 12 1 1129 barf 'Usage writeflex("filename", $hdr)' if $#_!=1 || !ref $_[1];
869 12         40 my($name) = shift; my ($hdr) = shift;
  12         22  
870 12         35 my $hname = "$name.hdr";
871 12 50       89 my $h = new FileHandle ">$hname"
872             or barf "Couldn't open '$hname' for writing";
873 12         1330 binmode $h;
874 12         71 print $h
875             "# Output from PDL::IO::writeflex, data in $name\n";
876 12         37 foreach (@$hdr) {
877 13         37 my ($type) = $_->{Type};
878 13 50       45 if (! exists $flextypes{$type}) {
879 0         0 barf "Writeflexhdr: will only print data elements, not $type";
880 0         0 next;
881             }
882             print $h join("\n",$_->{Type},
883             $_->{NDims},
884 13 50       58 (join ' ',ref $_->{Dims} ? @{$_->{Dims}} : $_->{Dims}) . (($_->{BadFlag}) ? " badvalue $_->{BadValue}" : '')),
  13 50       702  
885             "\n\n";
886             }
887             }
888              
889             1;
890              
891