File Coverage

lib/PDL/IO/Pnm.pd
Criterion Covered Total %
statement 101 103 98.0
branch 58 86 67.4
condition 26 45 57.7
subroutine 12 12 100.0
pod 1 4 25.0
total 198 250 79.2


line stmt bran cond sub pod time code
1             use strict;
2             use warnings;
3              
4             { no warnings 'once'; # pass info back to Makefile.PL
5             $PDL::Core::Dev::EXTRAS{$::PDLMOD}{OBJECT} .= join '', map " $::PDLBASE/$_\$(OBJ_EXT)", qw(get);
6             $PDL::Core::Dev::EXTRAS{$::PDLMOD}{INC} .= qq{ "-I$::PDLBASE"};
7             }
8              
9             pp_add_exported('',"rpnm wpnm");
10              
11             pp_addpm({At=>'Top'},<<'EOD');
12 15     15   191  
  15         34  
  15         586  
13 15     15   73 use strict;
  15         26  
  15         1014  
14             use warnings;
15              
16             =head1 NAME
17              
18             PDL::IO::Pnm -- pnm format I/O for PDL
19              
20             =head1 SYNOPSIS
21              
22             use PDL::IO::Pnm;
23             $im = wpnm $pdl, $file, $format[, $raw];
24             rpnm $stack->slice(':,:,:,(0)'),"PDL.ppm";
25              
26             =head1 DESCRIPTION
27              
28             pnm I/O for PDL.
29              
30             =cut
31 15     15   113  
  15         28  
  15         99  
32 15     15   141 use PDL::Core qw/howbig convert/;
  15         31  
  15         2826  
33 15     15   147 use PDL::Types;
  15         30  
  15         106  
34 15     15   3480 use PDL::Basic; # for max/min
  15         43  
  15         136  
35 15     15   116 use PDL::IO::Misc;
  15         45  
  15         1177  
36 15     15   12280 use Carp;
  15         285540  
  15         30430  
37             use File::Temp qw( tempfile );
38              
39             # return the upper limit of data values an integer PDL data type
40             # can hold
41 129     129 0 111 sub dmax {
42 129         211 my $type = shift;
43 129 100       315 my $sz = 8*howbig($type);
44 129         254 $sz-- if !PDL::Type->new($type)->unsigned;
45             return ((1 << $sz)-1);
46             }
47             EOD
48              
49             pp_addpm({At=>'Bot'},<<'EOD'); # the rest of FUNCTIONS section
50             =head2 rpnm
51              
52             =for ref
53              
54             Read a pnm (portable bitmap/pixmap, pbm/ppm) file into an ndarray.
55              
56             =for usage
57              
58             Usage: $im = rpnm $file;
59              
60             Reads a file (or open file-handle) in pnm format (ascii or raw) into a pdl (magic numbers P1-P6).
61             Based on the input format it returns pdls with arrays of size (width,height)
62             if binary or grey value data (pbm and pgm) or (3,width,height) if rgb
63             data (ppm). This also means for a palette image that the distinction between
64             an image and its lookup table is lost which can be a problem in cases (but can
65             hardly be avoided when using netpbm/pbmplus). Datatype is dependent
66             on the maximum grey/color-component value (for raw and binary formats
67             always PDL_B). rpnm tries to read chopped files by zero padding the
68             missing data (well it currently doesn't, it barfs; I'll probably fix it
69             when it becomes a problem for me ;). You can also read directly into an
70             existing pdl that has to have the right size(!). This can come in handy
71             when you want to read a sequence of images into a datacube.
72              
73             For details about the formats see appropriate manpages that come with the
74             netpbm/pbmplus packages.
75              
76             =for example
77              
78             $stack = zeroes(byte,3,500,300,4);
79             rpnm $stack->slice(':,:,:,(0)'),"PDL.ppm";
80              
81             reads an rgb image (that had better be of size (500,300)) into the
82             first plane of a 3D RGB datacube (=4D pdl datacube). You can also do
83             inplace transpose/inversion that way.
84              
85             =cut
86              
87 43     43 1 1378 sub rpnm {PDL->rpnm(@_)}
88             sub PDL::rpnm {
89 43 50 33 43 0 211 barf 'Usage: $im = rpnm($file) or $im = $pdl->rpnm($file)'
90             if !@_ || @_>3;
91 43 50 66     358 my $pdl = ref($_[1]) && UNIVERSAL::isa($_[1], 'PDL')
92             ? (splice @_, 0, 2)[1] : shift->initialize;
93 43         77 my $file = shift;
94              
95 43         52 my $fh;
96 43 100       75 if (ref $file) {
97 27         32 $fh = $file;
98             } else {
99 16 50       491 open $fh, $file or barf "Can't open pnm file '$file': $!";
100             }
101 43         114 binmode $fh;
102              
103 43         488 read($fh,(my $magic),2);
104 43 50       211 barf "Oops, this is not a PNM file" unless $magic =~ /P([1-6])/;
105 43         114 my $magicno = $1;
106 43 50       78 print "reading pnm file with magic $magic\n" if $PDL::debug>1;
107              
108 43 100       73 my $israw = $magicno > 3 ? 1 : 0;
109 43         70 my $isrgb = ($magicno % 3) == 0;
110 43         46 my $ispbm = ($magicno % 3) == 1;
111 43 100       116 my ($params, @dims) = ($ispbm ? 2 : 3, 0, 0, $ispbm ? 1 : 0);
    100          
112             # get the header information
113 43         40 my $pgot = 0;
114 43   66     203 while (($pgot<$params) && defined(my $line=<$fh>)) {
115 117         155 $line =~ s/#.*$//;
116 117 100       407 next if $line =~ /^\s*$/; # just white space
117 74   66     214 while ($line !~ /^\s*$/ && $pgot < $params) {
118 117 50       349 if ($line =~ /\s*(\S+)(.*)$/) {
119 117         221 $dims[$pgot++] = $1; $line = $2; }
  117         485  
120             else {
121 0         0 barf "no valid header info in pnm";}
122             }
123             }
124             # the file ended prematurely
125 43 50       61 barf "no valid header info in pnm" if $pgot < $params;
126 43 50 33     140 barf "Dimensions must be > 0" if ($dims[0] <= 0) || ($dims[1] <= 0);
127              
128 43         114 my ($type) = grep $dims[2] <= dmax($_), $PDL_B,$PDL_US,$PDL_L;
129 43 50       62 barf "rraw: data from ascii pnm file out of range" if !defined $type;
130              
131 43         99 my @Dims = @dims[0,1];
132 43 100       95 $Dims[0] *= 3 if $isrgb;
133 43 100 66     242 $pdl = $pdl->zeroes(PDL::Type->new($type),3,@dims[0,1])
134             if $pdl->isnull and $isrgb;
135 43 100       83 my $npdl = $isrgb ? $pdl->clump(2) : $pdl;
136 43 100       66 if ($israw) {
137 27         88 pnminraw (convert(pdl(0),$type), $npdl, $Dims[0], $Dims[1],
138             $ispbm, $fh);
139             } else {
140 16         24 pnminascii (convert(pdl(0),$type), $npdl, $Dims[0], $Dims[1],
141             $magicno, $fh);
142             }
143 43 0       262 print("loaded pnm file, $dims[0]x$dims[1], gmax: $dims[2]",
    0          
    50          
144             $isrgb ? ", RGB data":"", $israw ? ", raw" : " ASCII"," data\n")
145             if $PDL::debug;
146              
147             # need to byte swap for little endian platforms
148 43 100 66     107 $pdl->type->bswap->($pdl) if !isbigendian() and $israw;
149 43         556 return $pdl;
150             }
151              
152             =head2 wpnm
153              
154             =for ref
155              
156             Write a pnm (portable bitmap/pixmap, pbm/ppm) file into a file or open file-handle.
157              
158             =for usage
159              
160             Usage: $im = wpnm $pdl, $file, $format[, $raw];
161              
162             Writes data in a pdl into pnm format (ascii or raw) (magic numbers P1-P6).
163             The $format is required (normally produced by B) and routine just
164             checks if data is compatible with that format. All conversions should
165             already have been done. If possible, usage of B is preferred. Currently
166             RAW format is chosen if compliant with range of input data. Explicit control
167             of ASCII/RAW is possible through the optional $raw argument. If RAW is
168             set to zero it will enforce ASCII mode. Enforcing RAW is
169             somewhat meaningless as the routine will always try to write RAW
170             format if the data range allows (but maybe it should reduce to a RAW
171             supported type when RAW == 'RAW'?). For details about the formats
172             consult appropriate manpages that come with the netpbm/pbmplus
173             packages.
174              
175             =cut
176              
177             my %type2base = (PBM => 1, PGM => 2, PPM => 3);
178             *wpnm = \&PDL::wpnm;
179             sub PDL::wpnm {
180 37 50   37 0 7443 barf ('Usage: wpnm($pdl,$filename,$format[,$raw]) ' .
181             'or $pdl->wpnm($filename,$format[,$raw])') if $#_ < 2;
182 37         79 my ($pdl,$file,$type,$raw) = @_;
183 37 50       84 barf "wpnm: unknown format '$type'" if !exists $type2base{$type};
184              
185             # need to copy input arg since bswap[24] work inplace
186             # might be better if the bswap calls detected if run in
187             # void context
188 37         102 my $swap_inplace = $pdl->is_inplace;
189              
190             # check the data
191 37         118 my @Dims = $pdl->dims;
192 37 50 33     111 barf "wpnm: expecting 3D (3,w,h) input"
      66        
193             if ($type =~ /PPM/) && (($#Dims != 2) || ($Dims[0] != 3));
194 37 50 66     215 barf "wpnm: expecting 2D (w,h) input"
195             if ($type =~ /P[GB]M/) && ($#Dims != 1);
196 37 50       95 barf "wpnm: user should convert float etc data to appropriate type"
197             if !$pdl->type->integer;
198 37         131 my $max = $pdl->max;
199 37 50 33     89 barf "wpnm: expecting prescaled data (0-65535)"
200             if $pdl->min < 0 or $max > 65535;
201              
202             # check for raw format
203 37 50 100     483 my $israw =
    100 33        
204             (defined($raw) && !$raw) ? 0 :
205             (($pdl->get_datatype == $PDL_B) || ($pdl->get_datatype == $PDL_US) || ($type eq 'PBM')) ? 3 :
206             0;
207              
208 37         87 my $magic = 'P' . ($type2base{$type} + $israw);
209 37         69 my $isrgb = $type eq 'PPM';
210              
211 37 100       144 my $pref = ($file !~ /^\s*[|>]/) ? ">" : ""; # test for plain file name
212 37         54 my ($already_open, $fh) = 0;
213 37 100       45 if (ref $file) {
214 16         17 $fh = $file, $already_open = 1;
215             } else {
216 21 50       2689 open $fh, $pref . $file or barf "Can't open pnm file: $!";
217             }
218 37         125 binmode $fh;
219              
220 37 0       70 print "writing ". ($israw ? "raw" : "ascii") .
    50          
221             "format with magic $magic, max=$max\n" if $PDL::debug;
222             # write header
223 37         243 print $fh "$magic\n";
224 37         111 print $fh "$Dims[-2] $Dims[-1]\n";
225 37 100       63 if ($type ne 'PBM') { # fix maxval for raw output formats
226 25         31 my $outmax = 0;
227 25 100       74 if ($max < 256) {
    50          
228 13         17 $outmax = "255";
229             } elsif ($max < 65536) {
230 12         45 $outmax = "65535";
231             } else {
232 0         0 $outmax = $max;
233             };
234 25         221 print $fh "$outmax\n";
235             };
236              
237             # if rgb clump first two dims together
238 37 100       146 my $out = ($isrgb ? $pdl->slice(':,:,-1:0')->clump(2)
239             : $pdl->slice(':,-1:0'));
240             # handle byte swap issues for little endian platforms
241 37 100 66     99 if (!isbigendian() and $israw) {
242 21 50       64 $out = $out->copy unless $swap_inplace;
243 21         69 $out->type->bswap->($out);
244             }
245 37         763 pnmout($out,$israw,$type eq "PBM",$fh);
246             # check if our child returned an error (in case of a pipe)
247 37 50 66     1633 barf "wpnm: pbmconverter error: $!" if !$already_open and !close $fh;
248             }
249              
250             ;# Exit with OK status
251              
252             1;
253              
254             =head1 BUGS
255              
256             C currently relies on the fact that the header is separated
257             from the image data by a newline. This is not required by the p[bgp]m
258             formats (in fact any whitespace is allowed) but most of the pnm
259             writers seem to comply with that. Truncated files are currently
260             treated ungracefully (C just barfs).
261              
262             =head1 AUTHOR
263              
264             Copyright (C) 1996,1997 Christian Soeller
265             All rights reserved. There is no warranty. You are allowed
266             to redistribute this software / documentation under certain
267             conditions. For details, see the file COPYING in the PDL
268             distribution. If this file is separated from the PDL distribution,
269             the copyright notice should be included in the file.
270              
271              
272             =cut
273              
274              
275             ############################## END PM CODE ################################
276             EOD
277              
278              
279             pp_def('pnminraw',
280             Pars => 'type(); byte+ [o] im(m,n); byte [t] buf(llen)',
281             OtherPars => 'IV ms => m; IV ns => n;
282             int isbin; PerlIO *fp',
283             GenericTypes => [qw(B U L)],
284             RedoDimsCode => '
285             $SIZE(llen) = $COMP(isbin) ? ($SIZE(m)+7) / 8 : $SIZE(m) * sizeof($GENERIC());
286             ',
287             Code => '
288             /* with top to bottom inversion */
289             loop (n=::-1) %{
290             if (PerlIO_read($COMP(fp),$P(buf),$SIZE(llen)) != $SIZE(llen))
291             $CROAK("Error reading pnm file");
292             if ($COMP(isbin)) { /* unpack buffer */
293             PDL_Byte *bp=$P(buf); int k=0, bit=0;
294             loop (m) %{
295             bit &= 7;
296             if (!bit) k = *bp++;
297             /* here we do the inversion */
298             $im() = (k&0x80) ? 0 : 1;
299             k = k << 1;
300             bit++;
301             %}
302             } else {
303             $GENERIC() *gbp = ($GENERIC()*)$P(buf);
304             loop(m) %{ $im() = *gbp++; %}
305             }
306             %}
307             ', Doc => '
308             =for ref
309              
310             Read in a raw pnm file.
311              
312             read a raw pnm file. The C argument is only there to
313             determine the type of the operation when creating C or trigger
314             the appropriate type conversion (maybe we want a byte+ here so that
315             C follows I the type of C).
316             '
317             );
318              
319              
320             pp_def( 'pnminascii',
321             Pars => 'type(); byte+ [o] im(m,n)',
322             OtherPars => 'IV ms => m; IV ns => n;
323             int format; PerlIO *fp',
324             GenericTypes => [qw(B U S L)],
325             CHeader => qq{#include "get.h"\n},
326             Code => q?
327             int ns = $SIZE(n), s, i;
328             switch ($COMP(format)) {
329             case PBM:
330             broadcastloop %{ /* with top to bottom inversion */
331             for (i=ns-1; i>= 0; i--) {
332             loop(m) %{
333             while ((s = PerlIO_getc($COMP(fp))) != EOF) {
334             switch (s) {
335             case '#': /* comment, skip rest of line */
336             SWALLOWLINE($COMP(fp));
337             break;
338             case '0':
339             case '1':
340             /* invert on the fly */
341             $im(n=>i,m=>m) = 1 - (s - '0');
342             goto $PPSYM()next;
343             break;
344             case ' ':
345             case '\t':
346             case '\r':
347             case '\n':
348             case ',':
349             /* skip whitespace */
350             break;
351             default: /* garbage */
352             $CROAK("found garbage, aborting"); /* for now */
353             break;
354             }
355             }
356             $PPSYM()next: ;
357             %}
358             }
359             %}
360             break;
361             case PGM:
362             case PPM:
363             broadcastloop %{ /* with top to bottom inversion */
364             PDL_Long j;
365             for (i=ns-1; i>= 0; i--) {
366             loop(m) %{
367             if (getint($COMP(fp),&j) <= 0)
368             $CROAK("found garbage, aborting"); /* for now */
369             $im(n=>i,m=>m) = j;
370             %}
371             }
372             %}
373             break;
374             default:
375             $CROAK("unknown PNM format");
376             break;
377             } /* end switch */
378             ?, Doc => '
379             =for ref
380              
381             Read in an ascii pnm file.
382             '
383             );
384              
385              
386             # write a line of data supporting broadcasting !
387             pp_def( 'pnmout',
388             Pars => 'a(m);',
389             'NoPthread' => 1, # Pthreading doesn't make sense for an IO function
390             OtherPars => "int israw; int isbin; PerlIO *fp",
391             GenericTypes => [qw(B U S L)],
392             Code => '
393             if ($COMP(israw)) {
394             if ($COMP(isbin)) {
395             broadcastloop %{
396             int k=0, bit=0;
397             loop(m) %{
398             k = (k << 1) | ($a() < 1);
399             bit++;
400             if (bit==8) {
401             PerlIO_putc($COMP(fp),k);
402             bit = k = 0;
403             }
404             %}
405             if (bit) {
406             k = k << (8-bit);
407             PerlIO_putc($COMP(fp),k);
408             }
409             %}
410             } else {
411             PDL_Indx len = $SIZE(m) * sizeof($GENERIC());
412             broadcastloop %{
413             PDL_Indx this_len = len;
414             while (1) {
415             PDL_Indx wrote = PerlIO_write($COMP(fp),$P(a),this_len);
416             if (wrote < 0)
417             $CROAK("Error writing pnm file: %s", strerror(errno));
418             this_len -= wrote;
419             if (this_len <= 0) break;
420             }
421             %}
422             }
423             } else {
424             PDL_Indx len=0;
425             broadcastloop %{
426             loop(m) %{
427             PerlIO_printf($COMP(fp),"%3d ",$COMP(isbin) ?
428             ($a() < 1) :$a());
429             len +=4;
430             if (len>58) { PerlIO_printf($COMP(fp),"\n"); len=0; }
431             %}
432             if (len<=58)
433             PerlIO_printf($COMP(fp),"\n");
434             %}
435             }
436             ', Doc => '
437             =for ref
438              
439             Write a line of pnm data.
440              
441             This function is implemented this way so that broadcasting works
442             naturally.
443             ');
444              
445             pp_done();