File Coverage

blib/lib/DicomPack/IO/DicomWriter.pm
Criterion Covered Total %
statement 15 308 4.8
branch 0 176 0.0
condition 0 54 0.0
subroutine 5 18 27.7
pod 4 4 100.0
total 24 560 4.2


line stmt bran cond sub pod time code
1             ##############################################################################
2             # DicomWriter.pm -- a module to create a Dicom file
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::DicomWriter;
10              
11 1     1   2150 use strict;
  1         2  
  1         43  
12 1     1   7 use warnings;
  1         1  
  1         37  
13 1     1   5 use DicomPack::DB::DicomTagDict qw/getTag/;
  1         2  
  1         49  
14 1     1   5 use DicomPack::DB::DicomVRDict qw/getVR/;
  1         2  
  1         45  
15 1     1   5 use DicomPack::IO::CommonUtil qw/_isLittleEndian _parseDicomFieldPath _pack _showDicomField _getDicomValue/;
  1         2  
  1         3602  
16              
17             our $VERSION = '0.95';
18              
19             # instantiate DicomWriter
20             sub new
21             {
22 0     0 1   my $classname = shift;
23 0           my $dicomFields = shift;
24              
25 0 0         unless(defined $dicomFields)
26             {
27 0           $dicomFields = {};
28             }
29              
30 0           my $self;
31              
32 0           $self->{DicomField} = $dicomFields;
33 0           $self->{IsLittleEndian} = _isLittleEndian($self->{DicomField});
34 0           $self->{IsImplicitVR} = _isImplicitVR($self->{DicomField});
35              
36 0           bless $self, $classname;
37              
38 0           return $self;
39             }
40              
41             # flush the current dicom field to a dicom file.
42             sub flush
43             {
44 0     0 1   my $self = shift;
45 0           my $outfile = shift;
46              
47 0           my $dicomFields = $self->{DicomField};
48              
49 0           my $isLittleEndian = $self->{IsLittleEndian};
50 0           my $isImplicitVR = $self->{IsImplicitVR};
51              
52 0           _setGroupLength($dicomFields, $isLittleEndian, "0002");
53 0           my $dicomStr = _processDicomField($dicomFields, undef, $isLittleEndian, $isImplicitVR);
54              
55 0 0         open OUTDICOM, ">$outfile" or die $!;
56 0           _createDICOMheader(\*OUTDICOM);
57 0           print OUTDICOM $dicomStr;
58 0           close OUTDICOM;
59             }
60              
61             # create dicom file preamble.
62             sub _createDICOMheader
63             {
64 0     0     my $outdicom = shift;
65 0           print $outdicom pack('.', 128), "DICM";
66             }
67              
68             # calculate group length and set (gggg,0000).
69             sub _setGroupLength
70             {
71 0     0     my $dicomField = shift;
72 0           my $isLittleEndian = shift;
73 0           my $group = shift;
74            
75              
76 0 0         if(ref($dicomField) eq "HASH")
77             {
78 0           my $groupLen = 0;
79              
80 0           foreach my $field_t (sort keys %$dicomField)
81             {
82 0 0         if($field_t =~ /^$group,/)
83             {
84 0 0         next if($field_t eq "$group,0000");
85              
86 0 0 0       if((ref($dicomField->{$field_t}) eq "ARRAY") or # if sub-structure exists, exit;
87             (ref($dicomField->{$field_t}) eq "HASH"))
88             {
89 0 0         if(defined $dicomField->{$group.",0000"})
90             {
91 0           delete $dicomField->{$group.",0000"};
92             }
93 0           return -1;
94             }
95             else
96             {
97 0           my $vr = substr($dicomField->{$field_t}, 0, 2);
98 0           my $value = substr($dicomField->{$field_t}, 3);
99 0           my $len = length($value);
100 0 0         if($vr =~ /^(OB|OW|OF|SQ|UT|UN)$/)
101             {
102 0           $len += 12;
103             }
104             else
105             {
106 0           $len += 8;
107             }
108 0           $groupLen += $len;
109             }
110             }
111             }
112 0 0         if($groupLen > 0)
113             {
114 0 0 0       if($isLittleEndian or $group eq "0002")
115             {
116 0           $dicomField->{$group.",0000"} = "UL:".pack("V", $groupLen);
117             }
118             else
119             {
120 0           $dicomField->{$group.",0000"} = "UL:".pack("N", $groupLen);
121             }
122             }
123             }
124             else
125             {
126 0           die "Invalid dicom fields!!!";
127             }
128              
129             }
130              
131             # construct a dicom value
132             sub _setDicomValue
133             {
134 0     0     my $vr = shift;
135 0           my $valueList = shift;
136 0           my $isLittleEndian = shift;
137              
138 0           my $value = "";
139 0           my $vrItem = getVR($vr); # if VR is not valid or not "XX", die
140 0 0         if(defined $vrItem->{type})
    0          
141             {
142 0           my $endianness = "<";
143 0 0         $endianness = ">" unless $isLittleEndian;
144              
145 0 0 0       if($vrItem->{type} eq "C" or $vrItem->{type} eq "c")
146             {
147 0           $endianness = "";
148             }
149             #$value = pack($vrItem->{type}.$endianness."*", @$valueList);
150 0           $value = _pack($vrItem->{type}, $endianness, $valueList);
151              
152 0 0         if(length($value) % 2 != 0)
153             {
154 0           $value .= $vrItem->{tailing};
155             }
156 0           $value = $vr.":".$value;
157             }
158             elsif(defined $vrItem->{delimiter})
159             {
160 0           $value = join("\\", @$valueList);
161 0 0         if(length($value) % 2 != 0)
162             {
163 0           $value .= $vrItem->{tailing};
164             }
165 0           $value = $vr.":".$value;
166             }
167             else
168             {
169 0 0         if(scalar @$valueList > 1)
170             {
171 0           die $vr." is not multi-valued!!!\n";
172             }
173 0 0         if(defined $valueList->[0])
174             {
175 0           $value = $valueList->[0];
176 0 0         if(length($value) % 2 != 0)
177             {
178 0 0         if(defined $vrItem->{tailing})
179             {
180 0           $value .= $vrItem->{tailing};
181             }
182             else
183             {
184 0           die "Length of value must be even number!!!\n";
185             }
186             }
187 0           $value = $vr.":".$value;
188             }
189             else
190             {
191 0           $value = $vr.":";
192             }
193             }
194 0           return $value;
195             }
196              
197             # set a value of a dicom field
198             sub setValue
199             {
200 0     0 1   my $self = shift;
201 0           my $fieldPath = shift;
202 0           my $valueList = shift;
203 0           my $vr = shift;
204              
205 0 0         if(!ref($valueList)) # convert scalar to an array
206             {
207 0           $valueList = [$valueList];
208             }
209              
210 0 0         $vr = "XX" unless defined $vr;
211              
212 0           my $isLittleEndian = $self->{IsLittleEndian};
213 0           my $isImplicitVR = $self->{IsImplicitVR};
214              
215 0           my @fieldID = _parseDicomFieldPath($fieldPath);
216              
217 0 0         if($fieldID[0] eq "0002,0010") # change implicit VR or endianness
218             {
219 0           ($self->{IsLittleEndian}, $self->{IsImplicitVR}) =
220             _checkTransferSyntax($valueList->[0], $self->{DicomField}, $isLittleEndian);
221             }
222              
223 0 0 0       if($fieldID[0] =~ /^0002,/ or ! $self->{IsImplicitVR})
224             {
225 0 0         if($vr eq "XX")
226             {
227 0           $vr = [keys %{getTag($fieldID[-1])->{vr}}]->[0];
  0            
228 0           print "VR is not specified explicitly for ".$fieldPath.". $vr is used.\n";
229             }
230             }
231              
232 0           my $nFields = scalar @fieldID;
233              
234 0           my $value;
235 0 0         if($fieldID[0] =~ /^0002,/)
236             {
237 0           $value = _setDicomValue($vr, $valueList, 1);
238             }
239             else
240             {
241 0           $value = _setDicomValue($vr, $valueList, $isLittleEndian);
242             }
243              
244 0           my $tagList;
245 0           for(my $i=0; $i<$nFields; $i++)
246             {
247 0           my $tagID = lc($fieldID[$i]);
248 0 0         if($tagID =~ /^\d+$/)
    0          
    0          
249             {
250 0           $tagList->[$i] = [$tagID, "ARRAY", 0];
251             }
252             elsif($tagID eq "x")
253             {
254 0           $tagList->[$i] = [$tagID, "ARRAY", 1];
255             }
256             elsif($tagID =~ /([0-9a-fA-F]{4}),([0-9a-fA-F]{4})/)
257             {
258 0           $tagList->[$i] = [$tagID, "HASH", 1];
259             }
260             else
261             {
262 0           die "tagID: $tagID, is not supported yet!!!\n";
263             }
264             }
265 0           for(my $i=0; $i<$nFields; $i++)
266             {
267 0 0         if($i == $nFields-1)
268             {
269 0           $tagList->[$i]->[3] = "SCALAR";
270             }
271             else
272             {
273 0           $tagList->[$i]->[3] = $tagList->[$i+1]->[1];
274             }
275             }
276              
277 0           my $dicomField = $self->{DicomField};
278              
279 0           for(my $i=0; $i<$nFields; $i++)
280             {
281 0           my $tagID = $tagList->[$i]->[0];
282 0           my $tagType = $tagList->[$i]->[1];
283 0           my $valueType = $tagList->[$i]->[3];
284              
285 0 0         if($tagType eq "ARRAY")
    0          
286             {
287 0           my $addFlag = $tagList->[$i]->[2];
288              
289 0 0         if($valueType eq "SCALAR") # finish
290             {
291 0 0         if($addFlag == 1)
292             {
293 0 0         $dicomField = [] unless defined $dicomField;
294 0           push @$dicomField, $value;
295             }
296             else
297             {
298 0 0         if(defined $dicomField->[$tagID])
299             {
300 0           $dicomField->[$tagID] = $value;
301             }
302             else
303             {
304 0           print "The item to be modified is non-existent!!!\n";
305 0           return;
306             }
307             }
308 0           return;
309             }
310             else
311             {
312 0 0         if($addFlag == 1) # add a new array item
313             {
314 0 0         $dicomField = [] unless defined $dicomField;
315 0 0         if($valueType eq "HASH")
    0          
316             {
317 0           push @$dicomField, {};
318             }
319             elsif($valueType eq "ARRAY")
320             {
321 0           push @$dicomField, [];
322             }
323 0           $dicomField = $dicomField->[scalar @$dicomField - 1];
324             }
325             else # modify an existing array item
326             {
327 0 0         if(defined $dicomField->[$tagID])
328             {
329 0           $dicomField = $dicomField->[$tagID];
330             }
331             else
332             {
333 0           print "The item to be modified is non-existent!!!\n";
334 0           return;
335             }
336             }
337             }
338             }
339             elsif($tagType eq "HASH")
340             {
341 0 0         if($valueType eq "SCALAR") # finish
342             {
343 0           $dicomField->{$tagID} = $value;
344              
345 0           return;
346             }
347              
348 0 0         unless(defined $dicomField->{$tagID})
349             {
350 0 0         if($valueType eq "HASH")
    0          
351             {
352 0           $dicomField->{$tagID} = {};
353             }
354             elsif($valueType eq "ARRAY")
355             {
356 0           $dicomField->{$tagID} = [];
357             }
358             }
359              
360 0           $dicomField = $dicomField->{$tagID};
361             }
362             }
363 0           print "dicom field does not exist!!!\n";
364 0           return undef;
365             }
366              
367             # construct a binary dicom tag header
368             sub _constructDicomTag
369             {
370 0     0     my ($group, $element, $vr, $len, $isLittleEndian, $isImplicitVR) = @_;
371              
372 0           my $isMetaInfo;
373              
374 0           my $tagHeader = "";
375              
376 0 0         if($group eq "0002")
377             {
378 0           $isMetaInfo = 1;
379             }
380             else
381             {
382 0           $isMetaInfo = 0;
383             }
384              
385 0 0 0       if($isImplicitVR and $isMetaInfo==0)
386             {
387 0 0         if($isLittleEndian)
388             {
389 0           $tagHeader = pack("v v V", hex($group), hex($element), $len);
390             }
391             else
392             {
393 0           $tagHeader = pack("n n N", hex($group), hex($element), $len);
394             }
395 0           return $tagHeader;
396             }
397            
398 0 0         if($vr =~ m/^(OB|OW|OF|SQ|UT|UN)$/)
    0          
399             {
400 0 0 0       if($isLittleEndian or $isMetaInfo)
401             {
402 0           $tagHeader = pack("v v A2 v V", hex($group), hex($element), $vr, 0, $len);
403             }
404             else
405             {
406 0           $tagHeader = pack("n n A2 n N", hex($group), hex($element), $vr, 0, $len);
407             }
408            
409             }
410             elsif($vr =~ m/^(AE|AS|AT|CS|DA|DS|DT|FL|FD|IS|LO|LT|PN|SH|SL|SS|ST|TM|UI|UL|US)$/)
411             {
412 0 0 0       if($isLittleEndian or $isMetaInfo)
413             {
414 0           $tagHeader = pack("v v A2 v", hex($group), hex($element), $vr, $len);
415             }
416             else
417             {
418 0           $tagHeader = pack("n n A2 n", hex($group), hex($element), $vr, $len);
419             }
420             }
421             else
422             {
423 0 0 0       if($isLittleEndian or $isMetaInfo)
424             {
425 0           $tagHeader = pack("v v V", hex($group), hex($element), $len);
426             }
427             else
428             {
429 0           $tagHeader = pack("n n N", hex($group), hex($element), $len);
430             }
431             }
432 0           return $tagHeader;
433             }
434              
435             # get a binary string from dicom fields(recursive)
436             sub _processDicomField
437             {
438 0     0     my $dicomField = shift;
439 0           my $dicomTag = shift;
440 0           my $isLittleEndian = shift;
441 0           my $isImplicitVR = shift;
442              
443 0           my $fieldType = ref($dicomField);
444            
445 0           my $isMetaInfo = 1;
446              
447 0           my $dicomStr = "";
448              
449 0 0         if($fieldType eq "HASH")
    0          
450             {
451 0           foreach my $field_t (sort keys %$dicomField)
452             {
453 0           $dicomStr .= _processDicomField($dicomField->{$field_t}, $field_t, $isLittleEndian, $isImplicitVR);
454             }
455             }
456             elsif($fieldType eq "ARRAY") # SQ
457             {
458 0           my ($group, $element) = $dicomTag =~ /([0-9a-fA-F]{4}),([0-9a-fA-F]{4})/;
459 0 0 0       if(!defined $group or !defined $element)
460             {
461 0           die "Dicom Tag: $dicomTag, not valid!!!";
462             }
463              
464 0           my $sqStr = "";
465 0           for(my $index=0; $index < scalar @$dicomField; $index++)
466             {
467 0           my $sqItemStr = _processDicomField($dicomField->[$index], undef, $isLittleEndian, $isImplicitVR);
468              
469 0           my $sqItemStartTag = _constructDicomTag("fffe", "e000", "XX", length($sqItemStr), $isLittleEndian, $isImplicitVR);
470 0           my $sqItemEndTag = _constructDicomTag("fffe", "e00d", "XX", 0x0, $isLittleEndian, $isImplicitVR);
471            
472              
473 0           $sqStr .= $sqItemStartTag;
474 0           $sqStr .= $sqItemStr;
475             #$dicomStr .= $sqItemEndTag;
476             }
477              
478 0           my $sqVR = "SQ";
479 0           for(my $index=0; $index < scalar @$dicomField; $index++) # for value array's VR
480             {
481 0 0         if(ref($dicomField->[$index])) # no sub-structure
482             {
483 0           $sqVR = "SQ";
484 0           last;
485             }
486 0 0         if($index == 0)
487             {
488 0           $sqVR = substr($dicomField->[$index], 0, 2);
489 0 0 0       if($sqVR ne "OB" and # no other support value array
      0        
      0        
      0        
490             $sqVR ne "OW" and
491             $sqVR ne "OF" and
492             $sqVR ne "UT" and
493             $sqVR ne "UN")
494             {
495 0           $sqVR = "SQ";
496 0           last;
497             }
498             }
499 0 0         if($sqVR ne substr($dicomField->[$index], 0, 2)) # same VR
500             {
501 0           $sqVR = "SQ";
502 0           last;
503             }
504             }
505 0           my $sqStartTag = _constructDicomTag($group, $element, $sqVR, 0xffffffff, $isLittleEndian, $isImplicitVR);
506 0           my $sqEndTag = _constructDicomTag("fffe", "e0dd", "XX", 0x0, $isLittleEndian, $isImplicitVR);
507 0           $dicomStr .= $sqStartTag;
508 0           $dicomStr .= $sqStr;
509 0           $dicomStr .= $sqEndTag;
510             }
511             else
512             {
513 0 0         if(defined $dicomTag)
514             {
515 0           my ($group, $element) = $dicomTag =~ /([0-9a-fA-F]{4}),([0-9a-fA-F]{4})/;
516              
517 0 0 0       if(!defined $group or !defined $element)
518             {
519 0           die "Dicom Tag: $dicomTag, not valid!!!";
520             }
521              
522 0 0 0       if($element eq "0000" and ($group ne "0000" and
      0        
      0        
      0        
523             $group ne "0002" and
524             $group ne "0004" and
525             $group ne "0006")) # ignore group length
526             {
527 0           return $dicomStr;
528             }
529              
530 0           my $vr = substr($dicomField, 0, 2);
531 0           my $value = substr($dicomField, 3);
532 0           my $len = length($value);
533            
534 0 0         if($len % 2 != 0)
535             {
536 0           my $vrItem = getVR($vr);
537 0 0         if(defined $vrItem->{tailing})
538             {
539 0           $value = $value.$vrItem->{tailing};
540 0           $len = length($value);
541             }
542             else
543             {
544 0           die "the length of a dicom field must be even: $dicomTag.\n";
545             }
546             }
547 0           my $tagHeader = _constructDicomTag($group, $element, $vr, $len, $isLittleEndian, $isImplicitVR);
548 0           $dicomStr .= $tagHeader.$value;
549             }
550             else
551             {
552 0           my $vr = substr($dicomField, 0, 2);
553 0           my $value = substr($dicomField, 3);
554 0           my $len = length($value);
555              
556 0 0         if($len % 2 != 0)
557             {
558 0           my $vrItem = getVR($vr);
559 0 0         if(defined $vrItem->{tailing})
560             {
561 0           $value = $value.$vrItem->{tailing};
562             }
563             else
564             {
565 0           die "the length of a dicom field must be even: $dicomTag.\n";
566             }
567             }
568 0           $dicomStr .= $value;
569             }
570             }
571 0           return $dicomStr;
572             }
573              
574              
575             #################################
576              
577             # show the field data and structure of the current dicom file on STDIN
578             sub showDicomField
579             {
580 0     0 1   my $self = shift;
581 0           my $verbose = shift;
582 0 0         $verbose = 0 unless defined $verbose;
583 0           my $dicomFields = shift;
584 0 0         unless(defined $dicomFields)
585             {
586 0           $dicomFields = $self->{DicomField};
587             }
588 0           _showDicomField($dicomFields, 0, $verbose, $self->{IsLittleEndian});
589             }
590              
591             # check if implicit VR is used according to "0002,0010" of meta info
592             sub _isImplicitVR
593             {
594 0     0     my $dicomFields = shift;
595 0           my $isImplicitVR = 0;
596 0 0         if(defined $dicomFields)
597             {
598 0 0         if(defined $dicomFields->{"0002,0010"})
599             {
600 0           my ($tt_t, $vv_t) = _getDicomValue($dicomFields->{"0002,0010"});
601 0           my $transferSyntax = $vv_t->[0];
602 0 0         if($transferSyntax eq "1.2.840.10008.1.2")
603             {
604 0           $isImplicitVR = 1;
605             }
606             }
607             }
608 0           return $isImplicitVR;
609             }
610              
611             # check if adjustments to new transfer syntax are needed
612             sub _checkTransferSyntax
613             {
614 0     0     my $transferSyntax = shift;
615 0           my $dicomFields = shift;
616 0           my $oldEndian = shift;
617              
618 0           my $isImplicitVR = $transferSyntax eq "1.2.840.10008.1.2";
619              
620 0 0         unless($isImplicitVR)
621             {
622 0 0         if(_checkExplicitVR($dicomFields) == -1)
623             {
624 0           die "Cannot set VR to be explicit because some existing dicom fields are implicit!!!";
625             }
626             }
627              
628 0           my $newEndian;
629 0 0         if($transferSyntax eq "1.2.840.10008.1.2.2")
630             {
631 0           $newEndian = 0;
632             }
633             else
634             {
635 0           $newEndian = 1;
636             }
637              
638 0 0         if($newEndian != $oldEndian)
639             {
640 0           _changeEndianness($dicomFields, $oldEndian, $newEndian);
641             }
642            
643 0           return ($newEndian, $isImplicitVR);
644             }
645              
646             # change dicom value to a different endianness
647             sub _changeEndianness
648             {
649 0     0     my $dicomFields = shift;
650 0           my $oldEndian = shift;
651 0           my $newEndian = shift;
652 0           my $tagPath = shift;
653              
654 0 0         $tagPath = "" unless defined $tagPath;
655              
656 0 0         if(ref($dicomFields) eq "HASH")
    0          
657             {
658 0           foreach my $field_t (sort keys %$dicomFields)
659             {
660 0 0         if(ref($dicomFields->{$field_t}))
661             {
662 0           _changeEndianVR($dicomFields->{$field_t}, $oldEndian, $newEndian, $tagPath."/".$field_t);
663             }
664             else
665             {
666 0 0         if(substr($field_t, 0, 5) ne "0002,")
667             {
668 0           my ($vr, $value) = _getDicomValue($dicomFields->{$field_t}, $oldEndian);
669 0           $dicomFields->{$field_t} = _setDicomValue($vr, $value, $newEndian);
670             }
671             }
672             }
673             }
674             elsif(ref($dicomFields) eq "ARRAY")
675             {
676 0           for(my $index=0; $index < scalar @$dicomFields; $index++)
677             {
678 0 0         if(ref($dicomFields->[$index]))
679             {
680 0           _changeEndianVR($dicomFields->[$index], $oldEndian, $newEndian, $tagPath."/".$index);
681             }
682             else
683             {
684 0           my ($vr, $value) = _getDicomValue($dicomFields->[$index], $oldEndian);
685 0           $dicomFields->[$index] = _setDicomValue($vr, $value, $newEndian);
686             }
687             }
688             }
689             }
690              
691              
692             # check if implicit or explicit
693             sub _checkExplicitVR
694             {
695 0     0     my $dicomFields = shift;
696              
697 0 0         if(ref($dicomFields) eq "HASH")
    0          
698             {
699 0           foreach my $field_t (sort keys %$dicomFields)
700             {
701 0 0         if(substr($field_t, 0, 5) ne "0002,")
702             {
703 0 0         if(_checkExplicitVR($dicomFields->{$field_t}) == -1)
704             {
705 0           return -1;
706             }
707             }
708             }
709             }
710             elsif(ref($dicomFields) eq "ARRAY")
711             {
712 0           for(my $index=0; $index < scalar @$dicomFields; $index++)
713             {
714 0 0         if(_checkExplicitVR($dicomFields->[$index]) == -1)
715             {
716 0           return -1;
717             }
718             }
719             }
720             else
721             {
722 0           my $vr = substr($dicomFields, 0, 2);
723              
724 0 0         if($vr eq "XX")
725             {
726 0           return -1;
727             }
728             }
729 0           return 1;
730             }
731              
732             1;
733              
734             __END__