File Coverage

blib/lib/DicomPack/IO/DicomReader.pm
Criterion Covered Total %
statement 12 204 5.8
branch 0 98 0.0
condition 0 42 0.0
subroutine 4 14 28.5
pod 7 7 100.0
total 23 365 6.3


line stmt bran cond sub pod time code
1             ##############################################################################
2             # DicomReader.pm -- a module to read Dicom files
3             #
4             # Copyright (c) 2010 Baoshe Zhang. All rights reserved.
5             # This file is part of "DicomPack". DicomReader is free software. You can
6             # redistribute it and/or modify it under the same terms as Perl itself.
7             ##############################################################################
8              
9             package DicomPack::IO::DicomReader;
10              
11 1     1   792 use strict;
  1         2  
  1         30  
12 1     1   5 use warnings;
  1         2  
  1         39  
13              
14 1     1   495 use DicomPack::DB::DicomVRDict qw/getVR/;
  1         3  
  1         67  
15 1     1   507 use DicomPack::IO::CommonUtil qw/_getDicomValue _isLittleEndian _showDicomField _parseDicomFieldPath/;
  1         4  
  1         2173  
16              
17             our $VERSION = '0.95';
18              
19             #instantiate DicomReader
20             sub new
21             {
22 0     0 1   my $classname = shift;
23 0           my $infile = shift;
24 0           my $options = shift;
25              
26 0           my $self = {Options=>$options};
27              
28 0 0         if(_parseDicomFile($self, $infile))
29             {
30 0           bless $self, $classname;
31 0           return $self;
32             }
33 0           return undef;
34             }
35              
36             # parse a dicom file and load all dicom data into a structure
37             sub _parseDicomFile
38             {
39 0     0     my $self = shift;
40 0           my $infile = shift;
41              
42 0 0         open INDICOM, "<$infile" or die $!;
43 0           binmode INDICOM, q{:raw};
44              
45 0           my $filesize = -s $infile;
46              
47 0           my $dicomFileContent;
48              
49 0           my $nret = read(INDICOM, $dicomFileContent, $filesize);
50 0 0         if($nret != $filesize)
51             {
52 0           print "Error: read file: $infile\n";
53 0           exit;
54             }
55 0           close INDICOM;
56              
57 0           my $startPos = 0;
58 0 0         if(substr($dicomFileContent, 128, 4) eq 'DICM')
59             {
60 0           $startPos = 128 + 4;
61             }
62              
63 0           my $isImplicitVR = $self->{Options}->{ImplicitVR};
64              
65 0           my $byteCount;
66 0           ($byteCount, $self->{DicomField}) = _processDicomStr(\$dicomFileContent, $startPos, $filesize-$startPos, 1, $isImplicitVR, 0);
67              
68 0 0         unless(defined $self->{DicomField}) # invalid dicom file
69             {
70 0           print "$infile may not be a valid dicom file!!!\n";
71 0           return undef;
72             }
73             }
74              
75             # parse a dicom string and return a structure containing all dicom data
76             sub parseDicomString
77             {
78 0     0 1   my $self = shift;
79 0           my $dicomStr = shift;
80              
81 0           my $isLittleEndian = $self->isLittleEndian();
82 0           my $isImplicitVR = $self->isImplicitVR();
83              
84 0           my ($byteCount, $fields) = _processDicomStr(\$dicomStr, 0,
85             length($dicomStr), $isLittleEndian, $isImplicitVR, 0);
86              
87 0           return $fields;
88             }
89              
90             # check the endianness of a dicom file according to "0002,0010" of meta info
91             sub isLittleEndian
92             {
93 0     0 1   my $self = shift;
94 0           my $dicomFields = $self->{DicomField};
95              
96 0           return _isLittleEndian($dicomFields);
97             }
98              
99             # check implicit/explicit VR of a dicom file according to "0002,0010" of meta info
100             sub isImplicitVR
101             {
102 0     0 1   my $self = shift;
103 0           my $dicomFields = $self->{DicomField};
104 0           my $isImplicitVR = undef;
105 0 0         if(defined $dicomFields)
106             {
107 0 0         if(defined $dicomFields->{"0002,0010"})
108             {
109 0           my ($tt_t, $vv_t) = _getDicomValue($dicomFields->{"0002,0010"});
110 0           my $transferSyntax = $vv_t->[0];
111 0 0         if($transferSyntax eq "1.2.840.10008.1.2")
112             {
113 0           $isImplicitVR = 1;
114             }
115             else
116             {
117 0           $isImplicitVR = 0;
118             }
119             }
120             }
121 0           return $isImplicitVR;
122             }
123              
124             # parse a dicom tag header
125             sub _processDicomTag
126             {
127 0     0     my $dicomTagStr = shift;
128 0           my $isLittleEndian = shift;
129 0           my $isImplicitVR = shift;
130 0           my ($group, $element, $len, $vr, $tagLen);
131              
132 0           my $isMetaInfo;
133              
134 0           my $t_data;
135              
136 0           $t_data = substr($dicomTagStr, 0, 2);
137 0           ($group) = unpack("v", $t_data);
138              
139 0 0         if($group == 0x0002)
140             {
141 0           $isMetaInfo = 1;
142             }
143             else
144             {
145 0           $isMetaInfo = 0;
146             }
147              
148 0           $t_data = substr($dicomTagStr, 0, 8);
149 0 0 0       if($isLittleEndian or $isMetaInfo)
150             {
151 0           ($group, $element, $vr, $len) = unpack("v v A2 v", $t_data);
152             }
153             else
154             {
155 0           ($group, $element, $vr, $len) = unpack("n n A2 n", $t_data);
156             }
157              
158 0           my $tagID = sprintf "%04x,%04x", $group, $element;
159              
160 0 0         unless(defined $isImplicitVR)
161             {
162 0 0         if($vr =~ m/^(AE|AS|AT|CS|DA|DS|DT|FL|FD|IS|LO|LT|PN|SH|SL|SS|ST|TM|UI|UL|US|OB|OW|OF|SQ|UT|UN)$/)
163             {
164 0           $isImplicitVR = 0;
165             }
166             else
167             {
168 0           $isImplicitVR = 1;
169             }
170             }
171              
172 0 0 0       if($isImplicitVR and !$isMetaInfo) # implicit VR
173             {
174 0           $tagLen = 8;
175 0           $vr = "XX";
176 0           $t_data = substr($dicomTagStr, 4, 4);
177 0 0 0       if($isLittleEndian or $isMetaInfo)
178             {
179 0           $len = unpack("V", $t_data);
180             }
181             else
182             {
183 0           $len = unpack("N", $t_data);
184             }
185 0           return ($tagID, $tagLen, $vr, $len);
186             }
187              
188             # explicit VR
189 0 0         if($vr =~ m/^(AE|AS|AT|CS|DA|DS|DT|FL|FD|IS|LO|LT|PN|SH|SL|SS|ST|TM|UI|UL|US)$/)
190             {
191 0           $tagLen = 8;
192             }
193             else
194             {
195 0 0         if($vr =~ m/^(OB|OW|OF|SQ|UT|UN)$/)
196             {
197 0           $tagLen = 12;
198 0           $t_data = substr($dicomTagStr, 8, 4);
199 0 0 0       if($isLittleEndian or $isMetaInfo)
200             {
201 0           $len = unpack("V", $t_data);
202             }
203             else
204             {
205 0           $len = unpack("N", $t_data);
206             }
207             }
208             else
209             {
210 0           $tagLen = 8;
211 0           $vr = "XX";
212              
213 0 0 0       if($tagID ne "fffe,e000" and $tagID ne "fffe,e00d" and $tagID ne "fffe,e0dd") # no-substructure
      0        
214             {
215 0           return ($tagID, $tagLen, $vr, -1);
216             }
217              
218 0           $t_data = substr($dicomTagStr, 4, 4);
219 0 0 0       if($isLittleEndian or $isMetaInfo)
220             {
221 0           $len = unpack("V", $t_data);
222             }
223             else
224             {
225 0           $len = unpack("N", $t_data);
226             }
227             }
228             }
229              
230 0           return ($tagID, $tagLen, $vr, $len);
231             }
232              
233             # process dicom fields from DICOM data string (recursive)
234             sub _processDicomStr
235             {
236 0     0     my $pDicomStr = shift;
237 0           my $startPos = shift;
238 0           my $strLen = shift;
239 0           my $isLittleEndian = shift;
240 0           my $isImplicitVR = shift;
241 0           my $depth = shift;
242 0           my $vrParent = shift;
243 0           my $byteCount = 0;
244 0           my $dicomFields;
245              
246 0           while(1)
247             {
248 0 0 0       if($byteCount < 0 or $startPos+$byteCount+8 > length($$pDicomStr))
249             {
250 0           return (-1, undef);
251             }
252              
253 0           my ($tagID, $tagLen, $vr, $len) = _processDicomTag(substr($$pDicomStr, $startPos+$byteCount, 12),
254             $isLittleEndian, $isImplicitVR);
255 0 0         if($len == -1) # for explicit VR, tagID not SQ item
256             {
257 0           return (-1, undef);
258             }
259              
260 0           $byteCount += $tagLen;
261              
262 0 0 0       if($tagID eq "fffe,e00d" or $tagID eq "fffe,e0dd")
263             {
264 0           last;
265             }
266              
267             # process SQ structure
268 0 0 0       if($len == 0xffffffff or ($vr eq "SQ" and $len != 0))
      0        
269             {
270 0 0         if($len == 0xffffffff) # set the length of value to 0
271             {
272 0           $len = -1;
273             }
274              
275 0 0         if($tagID eq "fffe,e000") # SQ item
276             {
277 0           my ($nRet, $fRet) = _processDicomStr($pDicomStr,
278             $startPos+$byteCount, $len, $isLittleEndian, $isImplicitVR, $depth+1, $vr);
279 0           push @$dicomFields, $fRet;
280              
281 0           $byteCount += $nRet;
282             }
283             else
284             {
285 0           my ($nRet, $fRet) = _processDicomStr($pDicomStr,
286             $startPos+$byteCount, $len, $isLittleEndian, $isImplicitVR, $depth+1, $vr);
287 0           $dicomFields->{$tagID} = $fRet;
288 0           $byteCount += $nRet;
289             }
290             }
291             else
292             {
293 0 0         if($startPos+$byteCount+$len > length($$pDicomStr)) # if no-structure, return;
294             {
295 0           return (-1, undef);
296             }
297              
298 0           my $value = substr($$pDicomStr, $startPos+$byteCount, $len);
299 0           $byteCount += $len;
300              
301 0           my $isStruct = 0;
302              
303 0 0 0       if(length($value) > 8 and $vr eq "XX") # when implicit-type value is long enough, assume that sub-structure may exist.
304             {
305 0           my ($nRet, $fRet) = _processDicomStr(\$value,
306             0, length($value), $isLittleEndian, $isImplicitVR, $depth+1, $vr);
307 0 0         if(defined $fRet) # return not (-1, undef)
308             {
309 0 0         if($tagID eq "fffe,e000") # SQ item
310             {
311 0           push @$dicomFields, $fRet;
312             }
313             else
314             {
315 0 0         if($tagID ne "fffc,fffc") # ignore dataset trailing padding
316             {
317 0           $dicomFields->{$tagID} = $fRet;
318             }
319             }
320 0           $isStruct = 1;
321             }
322             }
323 0 0         if($isStruct == 0) # no sub-structure
324             {
325 0 0         if($tagID eq "fffe,e000") # SQ item
326             {
327 0 0 0       if(defined $vrParent and $vrParent ne "SQ") # SQ item using
328             {
329 0           push @$dicomFields, $vrParent.":".$value;
330             }
331             else
332             {
333 0           push @$dicomFields, $vr.":".$value;
334             }
335             }
336             else # return (-1, undef)
337             {
338 0 0         if($tagID ne "fffc,fffc") # ignore dataset trailing padding
339             {
340 0           $dicomFields->{$tagID} = $vr.":".$value;
341             }
342             }
343             }
344              
345 0 0         if($tagID eq "0002,0010") # dicom endianness
346             {
347 0           my ($tt_t, $vv_t) = _getDicomValue($dicomFields->{"0002,0010"});
348 0           my $transferSyntax = $vv_t->[0];
349 0 0         if($transferSyntax eq "1.2.840.10008.1.2.2")
350             {
351 0           $isLittleEndian = 0;
352             }
353 0 0         if($transferSyntax eq "1.2.840.10008.1.2")
354             {
355 0           $isImplicitVR = 1;
356             }
357             else
358             {
359 0           $isImplicitVR = 0;
360             }
361             }
362             }
363              
364 0 0 0       if($strLen >= 0 and $byteCount >= $strLen) # if byteCount>=strLen(not -1), exit loop
365             {
366 0 0         if($byteCount != $strLen) # if byteCount != strLen, no sub-structure
367             {
368 0           $byteCount = -1;
369 0           $dicomFields = undef;
370             }
371 0           last;
372             }
373             }
374              
375 0           return ($byteCount, $dicomFields);
376             }
377              
378             # get a value of a dicom field
379             sub getValue
380             {
381 0     0 1   my $self = shift;
382 0           my $fieldPath = shift;
383 0           my $mode = shift;
384            
385 0 0         $mode = "" unless defined $mode;
386              
387 0           my @fieldID = _parseDicomFieldPath($fieldPath);
388              
389 0           my $dicomFields = $self->{DicomField};
390              
391 0           my $nFields = scalar @fieldID;
392 0           for(my $i=0; $i<$nFields; $i++)
393             {
394 0           my $tagID = $fieldID[$i];
395              
396 0 0         if($tagID =~ /^\d+$/)
397             {
398 0           $dicomFields = $dicomFields->[$tagID];
399             }
400             else
401             {
402 0 0         if(ref($dicomFields) eq "HASH")
403             {
404 0 0         if(defined $dicomFields->{$tagID})
405             {
406 0           $dicomFields = $dicomFields->{$tagID};
407             }
408             else
409             {
410 0           print "Dicom field: $fieldPath, does not exist!!!\n";
411 0           return undef;
412             }
413             }
414             }
415              
416 0 0         unless(ref($dicomFields))
417             {
418 0 0         if($i == $nFields-1)
419             {
420 0 0         if($mode eq "native")
421             {
422 0           return $dicomFields;
423             }
424             else
425             {
426 0           my $isLittleEndian = $self->isLittleEndian();
427 0           my ($vr, $value) = _getDicomValue($dicomFields, $isLittleEndian);
428              
429 0 0         if(scalar @$value == 1)
430             {
431 0           return $value->[0];
432             }
433             else
434             {
435 0           return ($value, $vr);
436             }
437             }
438             }
439             else
440             {
441 0           print "Dicom field: $fieldPath, does not exist!!!\n";
442 0           return undef;
443             }
444             }
445             }
446 0           return $dicomFields;
447             }
448              
449             # get a pointer to the structure containing all dicom data
450             sub getDicomField
451             {
452 0     0 1   my $self = shift;
453 0           return $self->{DicomField};
454             }
455              
456             # show the field data and structure of the current dicom file on STDIN
457             sub showDicomField
458             {
459 0     0 1   my $self = shift;
460 0           my $verbose = shift;
461 0           my $dicomFields = shift;
462              
463 0 0         $verbose = 0 unless defined $verbose;
464              
465 0 0         unless(defined $dicomFields)
466             {
467 0           $dicomFields = $self->{DicomField};
468             }
469 0           _showDicomField($dicomFields, 0, $verbose, $self->isLittleEndian());
470             }
471              
472             1;
473              
474             __END__