File Coverage

blib/lib/PDL/IO/Dcm.pm
Criterion Covered Total %
statement 49 277 17.6
branch 1 96 1.0
condition 0 10 0.0
subroutine 17 25 68.0
pod 8 8 100.0
total 75 416 18.0


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             #
3             package PDL::IO::Dcm;
4              
5              
6             our $VERSION = '1.003';
7              
8              
9 1     1   13241 use PDL;
  1         7  
  1         4  
10 1     1   151867 use PDL::NiceSlice;
  1         2  
  1         10  
11 1     1   2136 use List::MoreUtils; # qw{any};
  1         6624  
  1         5  
12 1     1   853 use Data::Dumper;
  1         4467  
  1         47  
13 1     1   426 use DicomPack::IO::DicomReader;
  1         30266  
  1         34  
14 1     1   598 use Storable qw/dclone/;
  1         2106  
  1         54  
15 1     1   5 use DicomPack::DB::DicomTagDict qw/getTag getTagDesc/;
  1         2  
  1         37  
16 1     1   3 use DicomPack::DB::DicomVRDict qw/getVR/;
  1         2  
  1         28  
17 1     1   3 use Exporter;
  1         1  
  1         21  
18             #use PDL::IO::Nifti;
19 1     1   3 use strict;
  1         1  
  1         819  
20             #use PDL::IO::Sereal;
21             #use 5.10.0;
22              
23             our @ISA=qw/Exporter/;
24             our @EXPORT_OK=qw/read_dcm parse_dcms load_dcm_dir printStruct/;
25              
26             my @key_list=("Echo Time","Echo Number","Echo Number(s)", 'Pixel Bandwidth',
27             "Instance Number",,'Window Center','Content Time',
28             'Nominal Interval','Instance Creation Time','Largest Image Pixel Value',
29             'Trigger Time','Window Width','Acquisition Time','Smallest Image Pixel Value',
30             );
31              
32              
33             sub sort_series {
34 0     0 1   my $ret=$_[0]->hdr->{dicom}->{"Series Number"};
35 0           $ret=~ s/^\s+|\s+$//g; $ret;
  0            
36             }
37              
38             # copied and modified from stackoverflow or perlmonks thread (can't remember atm)
39             sub printStruct {
40 0     0 1   my ($struct,$structName,$pre)=@_;
41             # print "-----------------\n" unless (defined($pre));
42             #
43 0           my $res;
44             #if (!ref($struct)){ # $struct is a scalar.
45 0 0         if (ref($struct) eq "ARRAY") { # Struct is an array reference
    0          
    0          
46             #return ("ARRAY(".scalar(@$struct).")") if (@$struct>100);
47 0           for(my$i=0;$i<@$struct;$i++) {
48 0 0         if (ref($struct->[$i]) eq "HASH") {
    0          
    0          
49 0           $res.=printStruct($struct->[$i],$structName."->[$i]",$pre." ");
50             } elsif (ref($struct->[$i]) eq "ARRAY") { # contents of struct is array ref
51 0 0         $res.= "$structName->"."[$i]: ()\n" if (@{$struct->[$i]}==0);
  0            
52 0           my $string = printStruct($struct->[$i],$structName."->[$i]",$pre." ");
53 0 0         $res.= "$structName->"."[$i]: $string\n" if ($string);
54             } elsif (ref($struct->[$i]) eq "PDL") { # contents of struct is array ref
55 0           $res.= "$structName->"."[$i]: ".(join (' ',list ($struct->[$i])))."\n";
56             } else { # contents of struct is a scalar, just print it.
57            
58 0           $res.= "$structName->"."[$i]: $struct->[$i]\n";
59             }
60             }
61             #return($res);
62             } elsif (ref($struct) eq "HASH"){ # $struct is a hash reference or a scalar
63 0           foreach (sort keys %{$struct}) {
  0            
64 0 0         if (ref($struct->{$_}) eq "HASH") {
    0          
    0          
65 0           $res.=printStruct($struct->{$_},$structName."->{$_}",$pre." ");
66             } elsif (ref($struct->{$_}) eq "ARRAY") { # contents of struct is array ref
67 0           my $string = printStruct($struct->{$_},$structName."->{$_}",$pre." ");
68 0 0         $res.= "$structName->"."{$_}: $string\n" if ($string);
69             } elsif (ref($struct->{$_}) eq "PDL") { # contents of struct is array ref
70 0           $res.= "$structName->"."{$_}: ".(join (' ',list($struct->{$_})))."\n";
71             } else { # contents of struct is a scalar, just print it.
72 0           $res.= "$structName->"."{$_}: $struct->{$_}\n";
73             }
74             }
75             #return($res);
76             } elsif (ref ($struct) eq 'PDL') {
77 0           $res.= "$structName: ".(join (' ',list($struct)))."\n";
78             } else {
79 0           $res.= "$structName: $struct\n";
80             }
81             #print "------------------\n" unless (defined($pre));
82 0           return($res);
83             }
84              
85             sub unpack_field{
86             # recursive parsing of dicom fields down to scalar level.
87 0     0 1   my $id=shift;
88 0           my $tag=shift;
89 0           my $packstring;
90 0           my $value=shift;
91 0           my $return; #=shift;
92 0 0         if (ref($value) eq 'ARRAY') {
    0          
93 0           my @vs=();
94 0           for my $n ($#$value) {
95 0           push @vs,unpack_field ("$id/$n",getTag("$id/$n"),$$value[$n],$return);
96             }
97 0           $return=\@vs;
98             } elsif (ref ($value) eq 'HASH') {
99 0           my %vh=();
100 0           for my $v (keys %$value) {
101 0           $vh{$v}=unpack_field("$id/$v",getTag("$id/$v"),$$value{$v},$return);
102             }
103 0           $return=\%vh;
104             } else { # a scalar
105 0           my $vr=substr($value,0,2);
106 0 0 0       if ($vr eq 'XX' and defined $tag) {
107 0           ($vr)=keys %{DicomPack::DB::DicomTagDict::getTag($id)->{vr}};
  0            
108             }
109 0 0         if ($vr eq 'TM' ) {
110 0           ($return=sprintf('%13.6f',substr($value,3,)))
111 0           =~s/^(\d\d)(\d\d)(\d\d\.\d+$)/3600*$1+60*$2+$3/e;
112             } else {
113 0   0       $packstring=join ('',(eval {getVR($vr)->{type}}||'a').'*');
114 0           $return=unpack ($packstring,substr($value,3,));
115             }
116             }
117 0           $return;
118             }
119              
120             sub read_dcm {
121 0     0 1   my $file=shift;
122 0           my $opt=shift; #options
123 0   0       my $dcm=DicomPack::IO::DicomReader->new($file) || return;
124 0           my $h=unpack('S',substr ($dcm->getValue('Rows','native'),3,2));
125 0           my $w=unpack('S',substr ($dcm->getValue('Columns','native'),3,2));
126 0           my $data=$dcm->getValue('PixelData','native');
127 0 0         return (undef ) unless defined $data;
128 0           my $datatype= (substr($data,0,2));
129 0 0         my $pdl=zeroes(ushort,$w,$h) if ($datatype =~/OW|XX/);
130 0           $pdl->make_physical;
131 0           ${$pdl->get_dataref}=substr($data,3);
  0            
132 0           $pdl->upd_data;
133 0           $pdl->hdr->{raw_dicom}=$dcm->getDicomField;
134 1     1   6 no PDL::NiceSlice;
  1         0  
  1         8  
135 0           delete $pdl->hdr->{raw_dicom}->{'7fe0,0010'}; # Pixel data
136 0           for my $id (keys %{$pdl->hdr->{raw_dicom}}) {
  0            
137 0           my $tag=getTag($id); # field tag for id, if present, store under tag
138 0           my $value=unpack_field($id,$tag,$dcm->getValue($id,'native'));
139 0 0         if (defined $tag) {
140 0           $pdl->hdr->{dicom}->{$tag->{desc}}=$value;
141             } else { }
142 0           $pdl->hdr->{dicom}->{$id} #=~s/([0-9a-fA-F]{4}),([0-9a-fA-F]{4})/$1_$2/r}
143             =$value;
144             } # for loop over dicom ids
145 0           my $dims=$$opt{sort}->($dcm,$pdl); # call to vendor/modality specific stuff
146             # keep the raw_dicom structure?
147 0 0         delete $pdl->hdr->{raw_dicom} if $$opt{delete_raw};
148 0           return $pdl;
149             }
150              
151             sub is_equal {
152 0     0 1   my $a=shift;
153 0           my $b=shift;
154 0           my $opt=shift;
155 0 0         return if (any ($a->shape-$b->shape)); # they have equal dimensions
156 0 0         return 1 if ($opt =~/d/);
157 0 0         return if $a->hdr->{dicom}->{'Pixel Spacing'} ne $b->hdr->{dicom}->{'Pixel Spacing'};
158 0 0         return if $a->hdr->{dicom}->{'Image Orientation (Patient)'} ne $b->hdr->{dicom}->{'Image Orientation (Patient)'};
159 0           1;
160             }
161              
162             sub load_dcm_dir {
163 0     0 1   my %dcms; #([]);
164             my @pid;
165 0           my $dname=shift;
166 0           my %dims;
167 0           my $opt=shift; # field by which to split
168 0           my $id=$$opt{id};
169 0           my $sp=$$opt{split};
170 0           my $n=0;
171 0           my %refs; # reference images for each stack
172 0 0         opendir (my $dir, $dname) ||die "cannot open directory $dname!";
173 0           for my $file (readdir ($dir)) {
174 0 0         next unless (-f "$dname/$file"); # =~m/\.dcm$|\.IMA$/;
175 0           my $p=read_dcm("$dname/$file",$opt);
176 0 0         eval{$p->isa('PDL')} ||next;
  0            
177 0           $n++;
178 1     1   350 no PDL::NiceSlice;
  1         1  
  1         4  
179 0           my $pid=$id->($p); # Call to subroutine reference
180 0 0         $dcms{$pid}={} unless ref $dcms{$pid};
181 0           my $ref =$refs{$pid};
182 0 0         if (defined $ref) {
183             # do files match? Can they be stacked together?
184 0 0         unless ( is_equal($ref,$p )) {
185 0 0 0       if ( !$sp and is_equal($ref,$p->transpose,'d')) {
186 0           $p->hdr->{tp}=1;
187             } else {
188 0           my $flag=0;
189 0           my $n='a';
190 0           my $nid;
191 0           do {
192 0           $nid=$id->($p).$n;
193 0 0         if (ref $dcms{$nid} eq 'HASH'){ # group
194 0           for my $r2 (values %{$dcms{$nid}}){
  0            
195 0           $flag=is_equal($r2,$p);
196 0 0         last unless $flag;
197             }
198             } else {
199 0           $dcms{$nid}={};
200 0           $pid=$nid;
201 0           $flag=1;
202             }
203 0           $n++;
204             } until $flag;
205 0           $pid=$nid;
206             }
207             }
208             } # defined $ref
209 1     1   417 use PDL::NiceSlice;
  1         1  
  1         5  
210 0           my $iced=$p->hdr->{dim_idx}->copy;
211 0 0         unless (grep (/^$pid$/,@pid)) {
212 0           $dims{$pid}=zeroes(short,$iced->dims);
213 0           push @pid,$pid;
214 0           $refs{$pid}=$p;
215             }
216 0           $iced++;
217 0           $dims{$pid}.=$dims{$pid}*($dims{$pid}>=$iced)+$iced*($iced>$dims{$pid});
218             die "This key is not unique! $pid, ",$p->hdr->{dcm_key}
219 0 0         if (ref($dcms{$pid}->{$p->hdr->{dcm_key}}) eq 'PDL') ;
220 0           $dcms{$pid}->{$p->hdr->{dcm_key}}=$p;
221             }
222 0           my $order=pdl($$opt{dim_order});
223             #print "Done reading.\n";
224 0           for my $id (@pid) {
225             #print "Sorting out dims for $id\n";
226 0           my $ldims=$dims{$id}->copy;
227 0           my $test=zeroes(byte,$dims{$id}->($order));
228 0           my $i=0;
229             #print "Test: ",$test->info,"\n";
230 0           for my $dcm (values %{$dcms{$id}}) {
  0            
231 0 0         next unless eval{$dcm->isa('PDL')};
  0            
232 0           $i++;
233             #print "$i: ",$dcm->hdr->{dim_idx}->($order)," ? ";
234             #print $test(list $dcm->hdr->{dim_idx}->($order)),"\n";
235 0 0         if (any ($test(list $dcm->hdr->{dim_idx}->($order)))) {
  0            
236 1     1   2462 no PDL::NiceSlice;
  1         1  
  1         5  
237 0           $test=$$opt{duplicates}->($test,$dcm,$opt);
238 1     1   47 use PDL::NiceSlice;
  1         1  
  1         3  
239             #print "Duplicates detected. ",$test->info;
240             }
241 0           $test(list($dcm->hdr->{dim_idx}->($order))).=1;
242             }
243 0           $ldims($order).=$test->shape->copy;
244 0           $dcms{$id}->{dims}=$ldims;
245             #print "Set dims: id $id, $dims{$id}\n";
246             #print "Dims: $dims{$id} order $order ";
247             }
248 0           \%dcms;
249             }
250              
251             sub clump_data {
252 0     0 1   my $data=shift;
253 0           my $offset=shift;
254 0           my $clumplist=shift;
255 0           for my $clump (@$clumplist) {
256 0           $data=$data->clump( map {$_+$offset} @$clump);
  0            
257             }
258 0           $data;
259             }
260              
261              
262             sub parse_dcms {
263 0     0 1   my %dcms=%{shift()}; # reference to hash of
  0            
264 0           my %data;
265 0           my $opt=shift;
266 0           for my $pid (sort keys %dcms) {
267 0           my %stack=%{$dcms{$pid}};
  0            
268             #next unless $pid;
269 0 0         next unless (ref $stack{dims} eq 'PDL');
270 0           my $dims =$stack{dims};
271             #print "ID: $pid dims $dims transpose? \n";
272 0 0         die "No dims $pid " unless eval {$dims->isa('PDL')};
  0            
273 0           delete $stack{dims};
274 0           my $ref=$stack{(keys %stack)[0]};
275 0           my $x=$ref->hdr->{dicom}->{Columns} ;
276 0 0         die "No $x ",$ref->info unless $x;
277 0           my $y=$ref->hdr->{dicom}->{Rows};
278 0           my $order=pdl($$opt{dim_order});
279             #print "Dims: $dims order $order ";
280             #print $dims($order),"\n";
281 0 0         if ($ref->hdr->{tp}) { $data{$pid}=zeroes(ushort,$y,$x,$dims($order));}
  0            
282 0           else { $data{$pid}=zeroes(ushort,$x,$y,$dims($order));}
283 0           my $header=dclone($ref->gethdr); # populate the header
284 0           $header->{diff}={};
285 0           $header->{Dimensions}=$$opt{Dimensions};
286 0           for my $key (@key_list) {
287 0           $header->{dicom}->{$key}=zeroes(list $dims($order));
288             }
289 0           $header->{dicom}->{'Image Orientation (Patient)'}=zeroes(6,list $dims($order));
290 0           $header->{dicom}->{'Image Position (Patient)'}=zeroes(3,list $dims($order));
291 0           $header->{dicom}->{'Pixel Spacing'}=zeroes(2,list $dims($order));
292 0           for my $dcm (values %stack) {
293 0 0         if ($dcm->hdr->{tp}) {
294 0           $data{$pid}->(,,list $dcm->hdr->{dim_idx}->($order))
295             .=$dcm->transpose;}
296 0           else {$data{$pid}->(,,list $dcm->hdr->{dim_idx}->($order)).=$dcm;}
297 0           for my $key (@key_list) {
298             $header->{dicom}->{$key}->(list $dcm->hdr->{dim_idx}->($order))
299 0           .=$dcm->hdr->{dicom}->{$key};
300             }
301             $header->{dicom}->{'Image Orientation (Patient)'}
302             ->(,list $dcm->hdr->{dim_idx}->($order))
303 0           .=pdl (split /\\/,$dcm->hdr->{dicom}->{'Image Orientation (Patient)'});
304             $header->{dicom}->{'Pixel Spacing'}
305             ->(,list $dcm->hdr->{dim_idx}->($order))
306 0           .=pdl (split /\\/,$dcm->hdr->{dicom}->{'Pixel Spacing'});
307             $header->{dicom}->{'Image Position (Patient)'}
308             ->(,list $dcm->hdr->{dim_idx}->($order))
309 0           .=pdl (split /\\/,$dcm->hdr->{dicom}->{'Image Position (Patient)'});
310 0           for my $field (keys %{$dcm->hdr->{dicom}}) {
  0            
311 0 0         if ($dcm->hdr->{dicom}->{$field} ne $ref->hdr->{dicom}->{$field}) {
312             $header->{diff}->{$field}={}
313 0 0         unless ref ($header->{diff}->{$field});
314             }
315             }
316              
317             } # for ... values %stack
318 0           for my $dcm (values %stack) {
319 0           for my $field (keys %{$header->{diff}}) {
  0            
320             $header->{diff}->{$field}->{$dcm->hdr->{dcm_key}}=
321 0           $dcm->hdr->{dicom}->{$field};
322             }
323             }
324 0           my $ind=whichND(maxover maxover ($data{$pid})); # actually populated fields!
325 0           for my $ax (0..$ind->dim(0)-1) {
326 0           $data{$pid}=$data{$pid}->dice_axis($ax+2,$ind($ax)->uniq); # compact the data!
327             $header->{dicom}->{'Image Position (Patient)'}
328 0           =$header->{dicom}->{'Image Position (Patient)'}->dice_axis($ax+1,$ind($ax)->uniq);
329             $header->{dicom}->{'Image Orientation (Patient)'}
330 0           =$header->{dicom}->{'Image Orientation (Patient)'}->dice_axis($ax+1,$ind($ax)->uniq);
331             $header->{dicom}->{'Pixel Spacing'}
332 0           =$header->{dicom}->{'Pixel Spacing'}->dice_axis($ax+1,$ind($ax)->uniq);
333 0           for my $key (@key_list) {
334 0           $header->{dicom}->{$key}=$header->{dicom}->{$key}->dice_axis($ax,$ind($ax)->uniq);
335             }
336 0           for my $val (values %{$header->{diff}}) {
  0            
337 0 0         $val=$val->dice_axis($ax,$ind($ax)->uniq) if (ref ($val) =~ /PDL/);
338             }
339             }
340             $header->{dicom}->{'Image Position (Patient)'}
341 0           =clump_data($header->{dicom}->{'Image Position (Patient)'},1,$$opt{clump_dims});
342             $header->{dicom}->{'Image Orientation (Patient)'}
343 0           =clump_data($header->{dicom}->{'Image Orientation (Patient)'},0,$$opt{clump_dims});
344             $header->{dicom}->{'Pixel Spacing'}
345 0           =clump_data($header->{dicom}->{'Pixel Spacing'},0,$$opt{clump_dims});
346 0           for my $key (@key_list) {
347 0           $header->{dicom}->{$key}=clump_data($header->{dicom}->{$key},0,$$opt{clump_dims});
348             }
349 0           for my $val (values %{$header->{diff}}) {
  0            
350 0 0         $val=clump_data($val,0,$$opt{clump_dims}) if (ref ($val) =~ /PDL/);
351             }
352 0           $data{$pid}=clump_data($data{$pid},2,$$opt{clump_dims});
353 0           die "Dimensions don't add up! @{$$opt{Dimensions}}, $#{$$opt{Dimensions}} ",
  0            
354 0 0         $data{$pid}->info if ($data{$pid}->ndims != $#{$$opt{Dimensions}}+1);
  0            
355 0           $data{$pid}->sethdr(dclone($header));
356             } # for my $pid ...
357 0           \%data;
358             }
359              
360              
361              
362             BEGIN {
363 1 50   1   11725 if ($_[0] eq q/-d/) {
364 0         0 require Carp;
365 0         0 $SIG{__DIE__} = sub {print Carp::longmess(@_); die;};
  0         0  
  0         0  
366             }
367             }
368             1;
369              
370             =head1 NAME
371              
372             PDL::IO::Dcm - Reads dicom files, sorts them and stores the result into piddles with headers
373              
374             =head1 SYNOPSIS
375              
376             This module is inteded to read and sort dicom images created by medical imaging devices.
377             Either use something like the following from within your module/application
378              
379             use PDL::IO::Dcm::Plugins::Primitive qw/setup_dcm/;
380             my %options=();
381             ...
382             setup_dcm(\%options);
383             # loads all dicom files in this directory
384             my $dcms=load_dcm_dir($dir,\%options);
385             die "no data!" unless (keys %$dcms);
386             print "Read data; IDs: ",join ', ',keys %$dcms,"\n";
387             # sort all individual dicoms into a hash of piddles.
388             my $data=parse_dcms($dcms,\%options);
389              
390             ... # do something with your data.
391              
392             or use the read_dcm.pl script to convert dicom files in a directory to serealised
393             piddles (PDL::IO::Sereal) or NIFTI files with separate text headers (PDL::IO::Nifti).
394              
395             =head1 Plugins
396              
397             Modality/vendor specific treatment and sorting is done by plugins, to be
398             installed under the PDL::IO::Dcm::Plugins name space. Using Primitive should
399             get you started, data will be grouped based on dicom series numbers and sorted
400             by instance number. If you need something more sophisticated, take a look at
401             the MRISiemens plugin.
402              
403             This software is based on the use case of Siemens MRI data based on the
404             author's needs. For general usage, the specific stuff is moved to its own plugin.
405             Each plugin needs to support a setup_dcm() and a populate_header() function.
406              
407             read_dcm function should and probably will be moved to
408             vendor/modality specific plugin modules in future releases.
409              
410             =head1 Some notes on Dicom fields and how they are stored/treated
411              
412             The image data field is stored as the piddle, the other dicom elements are
413             first stored in the header under the raw_dicom key. After parsing, most fields
414             are accessible under the dicom key. The raw_dicom structure is then deleted,
415             use the delete_raw option if you want to change this.
416              
417             Keys are parsed into a hash under the dicom key using the DicomPack module(s)
418             to unpack. Piddles are created for data grouped based on the id option.
419             The header fields dcm_key and dim_idx are used for sorting datasets.
420              
421             =head1 Options
422              
423             The behaviour of the module's routines are controlled through options, stored in a hash. Your
424             plugin may add additional keys as needed. Fields in the options hash used by this module are:
425              
426             =over
427              
428             =item clump_dims
429              
430             these are clumped together to reduce dimensions, required by e.g. Nifti (max. 7).
431              
432             =item delete_raw
433              
434             flag controlling whether the unparsed dicom fields under raw_dicom should be
435             retained; default no.
436              
437             =item dim_order
438              
439             order in which dimensions are stored, used to reorder the data. xy are always
440             at the beginning and are not counted.
441              
442             =item Dimensions
443              
444             list ref to names of expected dims. xy are left out. Should be set by your
445             plugin to help interpret data.
446              
447             =item duplicates
448              
449             a code ref executed if two images have identical positions in stack, e.g. same
450             Series Number Instance Number, this can happen.
451              
452             =item id:
453              
454             code ref expecting to return a key to group files; defaults to \&sort_series.
455              
456             =item internal_dims
457              
458             raw dimension list before any clumping. This is not used at the moment but
459             allows for description of the input dimensions.
460              
461             =item sort
462              
463             code ref typically set to your plugin's populate_header routine. This is called
464             to set dim_idx and dcm_key for each file
465              
466             =item sp:
467              
468             Split slice groups, otherwise they are stacked together if xy-dims match, even transposed.
469              
470             =back
471              
472             =head1 SUBROUTINES/METHODS
473              
474             =head2 clump_data
475              
476             Utitlity to clump a piddle over clump_dims option field, takes an offset
477              
478              
479             =head2 is_equal ($dcm1,$dcm2,$pattern)
480              
481             This is used to check if two dicoms can be stacked based on matrix size,
482             orientation and pixel spacing.
483              
484             If $pattern matches /d/, only dims are checked
485              
486             =head2 load_dcm_dir ( $dir,\%options)
487              
488             reads all dicom files in a dicrectory and returns a hash of hashes of piddles
489             based on the sort option and dcm_key.
490              
491              
492             =head2 parse_dcms ($hashref,\$options)
493              
494             Parses and sorts a hash of hashes of dicoms (such as returned by load_dcm_dir)
495             based on dcm_key and dim_idx. Returns a hash of piddles.
496              
497             =head2 unpack_field
498              
499             unpacks dicom fields and walks subfield structures recursively.
500              
501             =head2 sort_series
502              
503             Groups dicom files based on their series number. If data within the series
504             don't fit, the outcome depends on the split option. If set, it will always
505             produce several piddles, appending a, b, c, etc.; if not, transposition is tried,
506             ignoring Pixel Spacing and Image Rotation. Only if this fails, data is split.
507              
508             =head2 read_dcm ($file, \%options)
509              
510             reads a dicom file and creates a piddle-with-header structure.
511              
512             =head2 printStruct
513              
514             This is used to generate human readable and parsable text from the headers.
515              
516             =head1 TODO
517              
518             write tests!
519              
520             Since all data in a directory are loaded into memeory before sorting, this may
521             cause memory issues. At the moment, you only option is to split the files into
522             several directories, if you face problems.
523              
524             Generalise to other modalities. This will be done based on data available,
525             request or as needed.
526              
527             =cut
528              
529              
530             =head1 LICENSE AND COPYRIGHT
531              
532             Copyright 2016 Albrecht Ingo Schmid.
533              
534             This program is free software; you can redistribute it and/or modify it
535             under the terms of the the Artistic License (2.0). You may obtain a
536             copy of the full license at:
537              
538             L
539              
540             Any use, modification, and distribution of the Standard or Modified
541             Versions is governed by this Artistic License. By using, modifying or
542             distributing the Package, you accept this license. Do not use, modify,
543             or distribute the Package, if you do not accept this license.
544              
545             If your Modified Version has been derived from a Modified Version made
546             by someone other than you, you are nevertheless required to ensure that
547             your Modified Version complies with the requirements of this license.
548              
549             This license does not grant you the right to use any trademark, service
550             mark, tradename, or logo of the Copyright Holder.
551              
552             This license includes the non-exclusive, worldwide, free-of-charge
553             patent license to make, have made, use, offer to sell, sell, import and
554             otherwise transfer the Package with respect to any patent claims
555             licensable by the Copyright Holder that are necessarily infringed by the
556             Package. If you institute patent litigation (including a cross-claim or
557             counterclaim) against any party alleging that the Package constitutes
558             direct or contributory patent infringement, then this Artistic License
559             to you shall terminate on the date that such litigation is filed.
560              
561             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
562             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
563             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
564             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
565             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
566             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
567             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
568             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
569              
570              
571             =cut
572              
573 1     1   5 __END__