File Coverage

blib/lib/PDL/IO/Dcm/Plugins/MRISiemens.pm
Criterion Covered Total %
statement 20 199 10.0
branch 0 62 0.0
condition 0 30 0.0
subroutine 7 13 53.8
pod 6 6 100.0
total 33 310 10.6


line stmt bran cond sub pod time code
1             package PDL::IO::Dcm::Plugins::MRISiemens;
2             #use base 'PDL::IO::Dcm';
3 1     1   670 use Exporter;
  1         1  
  1         32  
4 1     1   4 use PDL;
  1         1  
  1         4  
5 1     1   1963 use PDL::NiceSlice;
  1         1  
  1         5  
6 1     1   11735 use strict;
  1         1  
  1         20  
7 1     1   9 use 5.10.0;
  1         2  
8              
9              
10             our @ISA=qw/Exporter/;
11             our @EXPORT_OK=qw/init_dims populate_header setup_dcm/;
12              
13             sub setup_dcm {
14 0     0 1   my $opt=shift;
15 0 0         $opt={} unless (ref($opt) eq 'HASH'); # ensure hash context
16             # split on series number by default
17 0           $$opt{id}=\&PDL::IO::Dcm::sort_series;
18 0           $$opt{sort}=\&populate_header;
19 0           $$opt{duplicates}=\&handle_duplicates;
20 0           $$opt{delete_raw}=1; # deletes the raw_dicom structure after parsing
21 0           push @PDL::IO::Dcm::key_list,'0051,100f';
22             #say join ' ',%{$opt};
23 0 0         if ($$opt{c_phase_t} ) {
24 0 0         if ($$opt{c_phase_set} ) {
25 0           $$opt{Dimensions}=[qw/x y z=partitions*slices T*Set*Phases Echo Channel/];
26             } else {
27 0           $$opt{Dimensions}=[qw/x y z=partitions*slices T*Phases Echo Channel Set/];
28             }
29             }else {
30 0 0         if ($$opt{c_phase_set} ) {
31 0           $$opt{Dimensions}=[qw/x y z=partitions*slices T Echo Channel Set*Phase/];
32             } else {
33 0           $$opt{Dimensions}=[qw/x y z=partitions*slices T Echo Channel Phase Set /];
34             }
35             }
36             # part,sl,t,echo,coil,phase,set
37 0           $$opt{dim_order}=[6,10,4,1,0,2,3];
38             $$opt{internal_dims}=[
39             #
40 0           qw/x y coil echo cphase set t ? partition? chron_slice? ? slice ? some_id/];
41             # note the order since dims change by clump!
42             # partitions and slices
43 0           $$opt{clump_dims}=[[0,1],];
44             # phases and set, phases*set and t
45 0 0         push (@{$$opt{clump_dims}},[4,5]) if $$opt{c_phase_set};
  0            
46 0 0         push (@{$$opt{clump_dims}},[1,4]) if $$opt{c_phase_t};
  0            
47 0           $opt;
48             }
49              
50              
51              
52             sub read_text_hdr {
53 0     0 1   my $f=shift; # File
54 0           my $self=shift;
55 0 0         open (HDR,'<',\$f) || die "no header !";
56 0           my $l;
57             #say "file $f line $l";
58 0           do {$l=; } until ($l=~/ASCCONV BEGIN/);
  0            
59 0           while (($l=)!~/ASCCONV END/) {
60 0           chomp $l;
61 0 0         if ( $l) {
62 0           chomp (my ($key,$val)=split /\s*=\s*/,$l);
63 0           chomp($key);
64 0           $key=~s/[\[\].]/_/g;
65 0           $self->hdr->{ascconv}->{$key}=$val;
66             }
67             }
68 0           close HDR;
69             }
70              
71             sub sort_protid {
72 0     0 1   $_[0]->hdr->{ascconv}->{"lProtID"};
73             }
74              
75             sub populate_header {
76 0     0 1   my $dicom =shift;
77 0           my $piddle=shift;
78             # The protocol is in here:
79             #say "populate_header ",$_[1]->info,$_[0]->getValue('0020,0032');
80 0           read_text_hdr($dicom->getValue ('0029,1020','native'),$piddle);
81 0           delete $piddle->hdr->{raw_dicom}->{'0029,1020'}; # Protocol
82 0           my @ret=$dicom->getValue('0029,1010','native')=~/ICE_Dims.{92}((_?(X|\d+)){13})/s;
83 0           (my $str=$ret[0])=~s/X/1/e;
  0            
84             # to make this unique
85 0           say "Instance Number ",$dicom->getValue('InstanceNumber');
86 0   0       $piddle->hdr->{dcm_key}=$dicom->getValue('InstanceNumber').'_'.($dicom->getValue('0051,100f')||0);
87 0           my @d=split ('_',$str);
88 0           my $iced=pdl(short,@d); #badvalue(short)/er)]);
89 0           $iced--;
90 0           $piddle->hdr->{dim_idx}=$iced;
91             #say $piddle->hdr->{dcm_key},": ",$iced,$dicom->getValue ('0020,0032','native');
92             #say "Dims $str pos $iced";
93 0           return $str;
94             }
95              
96             sub handle_duplicates {
97 0     0 1   my $stack=shift;
98 0           my $dcm=shift;
99 0           my $opt=shift;
100             "This entry (". $dcm->hdr->{dim_idx}->($$opt{dim_order}).
101 0           max ($stack->(,,list $dcm->hdr->{dim_idx}->($$opt{dim_order}))).
102             ") is already set! This should not happen, please file a bug report!\n";
103             }
104              
105             sub init_dims {
106 1     1   6 use PDL::NiceSlice;
  1         1  
  1         6  
107 0     0 1   my $self=shift;
108 0           my $opt=shift;
109             # we need these modules, return undef otherwise.
110 0 0         require PDL::Dims || return;
111 0 0         require PDL::Transform || return;
112 0           PDL::Dims->import(qw/is_equidistant dmin dmax vals hpar dinc initdim dimsize drot idx diminfo /);
113 0           say "init_dims: ",$self->hdr->{dicom}->{Rows} ;
114 0           say "init_dims: hpar ",hpar($self,'dicom','Rows');
115 0           PDL::Transform->import(qw/t_linear/);
116             #say diminfo ($self),$self->hdr->{ascconv}->{sGroupArray_asGroup_1__nSize};
117             # center=inner(rot*scale,dim/2)+Image Position (Patient)
118             # xf=t_linear(matrix=>Pixel Scale*$rot->transpose,post=>Image Position (Patient))
119 0 0         if (hpar($self,'ascconv','sGroupArray_asGroup_1__nSize')){
120 0           warn "Multiple slice groups, support dubious!";
121             }
122 1     1   2778 use PDL::NiceSlice;
  1         1  
  1         7  
123 0           my $v=zeroes(3);
124 0   0       $v(0,).=hpar($self,'ascconv','sSliceArray_asSlice_0__sPosition_dSag') ||0; #x
125 0   0       $v(1,).=hpar($self,'ascconv','sSliceArray_asSlice_0__sPosition_dCor') ||0; #y
126 0   0       $v(2,).=hpar($self,'ascconv','sSliceArray_asSlice_0__sPosition_dTra') ||0; #z
127 0           say $v;
128             say "hpar: pos ",hpar($self,'dicom','Image Position (Patient)'),
129 0           $self->hdr->{dicom}->{'Image Position (Patient)'};
130 0           say "init_dims: ",hpar($self,'dicom','Rows');
131 0           my $pos_d=(hpar($self,'dicom','Image Position (Patient)'))->flat->(:5)->reshape(3,2);
132 0   0       my $ir=hpar($self,'ascconv','sSliceArray_asSlice_0__dInPlaneRot') ||0; #radiant
133 0           my $or=pdl(hpar($self,'dicom','Image Orientation (Patient)'))->flat->(:5)
134             ->reshape(3,2)->transpose; #
135 0           my $pe_dir=hpar($self,'dicom','In-plane Phase Encoding Direction');
136             # Scaling
137             # Rotation
138 0           my $srot=zeroes(3,3);
139 0           $srot(:1,).=$or;
140 0           $srot(2,;-).=norm($pos_d(,1;-)-$pos_d(,0;-));
141 0 0         $srot(2,;-).=pdl[0,0,1] unless any ($srot(2,;-));
142             #say "spatial rotation $srot";
143 0           $pos_d=$pos_d(,0;-);
144             # Calculate and initialise the transformation
145 0           my @ors=qw/Cor Sag Tra/;
146 0           $self->hdr->{orientation}=$ors[maximum_ind($srot(2;-))]; # normal vector lookup
147             my $pe=$self->hdr->{dicom}->{"In-plane Phase Encoding Direction"}
148 0   0       ||$self->hdr->{dicom}->{"InPlanePhaseEncodingDirection"};
149 0 0         if ($self->hdr->{orientation} eq 'Tra'){ # transversal slice
150 0           say $self->hdr->{orientation}. " Orientation";
151 0           $self->hdr->{sl}='z';
152 0 0         if ($pe eq 'COL'){
153 0           $self->hdr->{ro}='x';
154 0           $self->hdr->{pe}='y';
155             } else {
156 0           $self->hdr->{ro}='y';
157 0           $self->hdr->{pe}='x';
158             }
159             }
160 0 0         if ($self->hdr->{orientation} eq 'Cor'){ # coronal slice
161 0           $self->hdr->{sl}='y';
162 0 0         if ($pe eq 'COL') {
163 0           $self->hdr->{ro}='x';
164 0           $self->hdr->{pe}='z';
165             } else {
166 0           $self->hdr->{ro}='z';
167 0           $self->hdr->{pe}='x';
168             }
169             }
170 0 0         if ($self->hdr->{orientation} eq 'Sag'){ # sagittal slice
171 0           $self->hdr->{sl}='x';
172 0 0         if ($pe eq 'COL') {
173 0           $self->hdr->{ro}='y';
174 0           $self->hdr->{pe}='z';
175             } else {
176 0           $self->hdr->{ro}='z';
177 0           $self->hdr->{pe}='y';
178             }
179             }
180 0           my $s=zeroes(3); # matrix size
181 0           my $fov=zeroes(3); # FOV
182 0           $fov(0).=$self->hdr->{ascconv}->{sSliceArray_asSlice_0__dReadoutFOV};
183 0           $fov(1).=$self->hdr->{ascconv}->{sSliceArray_asSlice_0__dPhaseFOV};
184 0 0         if ($pe =~ 'COL') {
185 0   0       $s(0).=$self->hdr->{dicom}->{Width}||$self->hdr->{dicom}->{Columns};
186 0   0       $s(1).=$self->hdr->{dicom}->{Height}||$self->hdr->{dicom}->{Rows};
187 0           say "COL! $s";
188             } else {
189 0   0       $s(1).=$self->hdr->{dicom}->{Width}||$self->hdr->{dicom}->{Columns};
190 0   0       $s(0).=$self->hdr->{dicom}->{Height}||$self->hdr->{dicom}->{Rows};
191             #$fov(1).=$self->hdr->{ascconv}->{sSliceArray_asSlice_0__dReadoutFOV};
192             #$fov(0).=$self->hdr->{ascconv}->{sSliceArray_asSlice_0__dPhaseFOV};
193             }
194 0           say "PE $pe $s $fov " ;
195             $self->hdr->{'3d'}=1 if (($self->hdr->{dicom}->{MRAcquisitionType}||
196 0 0 0       hpar($self,'dicom','MR Acquisition Type')) eq '3D'); # 3D
197 0 0         if ($self->hdr->{'3d'}) {
198 0           $s(2).=$self->hdr->{ascconv}->{'sKSpace_lImagesPerSlab'} ;
199 0           $fov(2).=$self->hdr->{ascconv}->{sSliceArray_asSlice_0__dThickness};
200             } else {
201 0           $s(2).=$self->hdr->{ascconv}->{'sSliceArray_lSize'};
202 0           $fov(2).=$self->hdr->{dicom}->{"Spacing Between Slices"}*$s(2);
203 0           say "FOV $fov matrix $s";
204             }
205 0 0         $s(2).=1 if ($s(2)<1);
206 0           my $rot=identity($self->ndims);
207 0           my $inc_d=zeroes(3);
208             #say "Pixel Spacing", hpar($self,'dicom','Pixel Spacing');
209 0           $inc_d(:1).=hpar($self,'dicom','Pixel Spacing')->(:1;-);
210 0           $inc_d(2).=$fov(2,0)/$s(2,0);
211             #say $srot;
212 0           $rot(:2,:2).=$srot;
213 0           say "FOV $fov matrix $s, pixels $inc_d";
214 0 0         barf "dims don't fit! $s vs. ",$self->shape->(:2) if any($self->shape->(:2)-$s);
215             #say "Rot: $rot";
216 0           initdim($self,'x',size=>$s(0),min=>sclr($pos_d(0)),inc=>sclr($inc_d(0)),unit=>'mm');
217 0           initdim($self,'y',size=>$s(1),min=>sclr($pos_d(1)),inc=>sclr($inc_d(1)),unit=>'mm');
218 0           initdim($self,'z',size=>$s(2),rot=>$rot,min=>sclr($pos_d(2)),inc=>sclr($inc_d(2)),unit=>'mm',);
219 0           say "initdim for x,y,z done.";
220 0           say "after init dim ",(diminfo ($self));
221 0           say "size $s min $pos_d inc $inc_d rot $rot";
222 0           idx($self,'x',dimsize($self,'x')/2);
223 0           idx($self,'y',dimsize($self,'y')/2);
224 0           idx($self,'z',dimsize($self,'z')/2);
225 0           say "orientation : ",hpar($self,'orientation'),diminfo ($self);
226             # other dimensions
227 0           for my $n (3..$#{$$opt{Dimensions}}) { # x,y,z are handled above
  0            
228 0           my $dim=$$opt{Dimensions}->[$n];
229 0           print "Init Dim $dim - $n\n";
230 0           my $str=('(0),' x ($n-2)).','.('(0),' x ($#{$$opt{Dimensions}}-$n));
  0            
231 0           say "$str ";
232 0 0         if ($dim eq 'Echo') {
    0          
    0          
    0          
    0          
233             # my $str=('(0),' x ($n-2)).','.('(0),' x ($#{$$opt{Dimensions}}-$n));
234 0           initdim ($self,'echo',unit=>'us',
235             vals=>[list (hpar($self,'dicom','Echo Time')->($str))]);
236             }
237             elsif ($dim eq 'T') {
238             # my $str=('(0),' x ($n-2)).','.('(0),' x ($#{$$opt{Dimensions}}-$n));
239 0           my $t=hpar($self,'dicom','Acquisition Time')->($str);
240 0 0         if (is_equidistant($t,0.003)) {
241 0           initdim ($self,'t',unit=>'s',min=>sclr($t(0)),max=>sclr($t(-1)));
242 0           say "T min ",dmin($self,'t')," max ",dmax($self,'t')," inc ",dinc($self,'t'), $t;
243             } else {
244 0           initdim ($self,'t',unit=>'s',vals=>[list($t)]);
245 0           say "T values :",vals ($self,'t');
246             }
247             } elsif ($dim =~ /Channel/) {
248 0   0       my $coil=hpar($self,'dicom','0051,100f')||'combined';
249 0 0         if ($self->dim($n)>1) {
250 0           initdim ($self,'channel',vals=>[hpar($self,'dicom','0051,100f')->flat->(:2)]);
251             } else {
252 0           initdim ($self,'channel',vals=>[$coil, size=>1]);
253             }
254             } elsif ($dim =~ /Set/) {
255 0           initdim ($self,'set'); # This can be anything, no further info easily available
256             } elsif ($dim =~ /Phase/) {
257 0           my $t=hpar($self,'dicom','Trigger Time');
258 0           say $t->info;
259 0           $t=$t($str);
260 0           say $t;
261 0 0         if (is_equidistant($t)) {
262 0           initdim ($self,'cphase',unit=>'ms',min=>sclr($t(0)),max=>sclr($t(-1)));
263 0           say "Trigger min ",dmin($self,'cphase')," max ",dmax($self,'cphase')," inc ",dinc($self,'cphase'), $t;
264 0           say "Trigger ",vals ($self,'cphase');
265             } else {
266 0           initdim ($self,'cphase',unit=>'ms',vals=>[list($t)]);
267 0           say "Trigger values :",vals ($self,'cphase');
268             }
269             }
270             }
271 0           my $mat=drot($self) x stretcher (pdl(dinc($self)));
272 0           $mat=$mat->transpose ;#if ($pe_dir =~ /COL/);
273             #say $mat;
274 0           my $xf=t_linear(matrix=>$mat,post=>$pos_d(,0;-));
275 0           say "inc ",dinc ($self);
276 0           say diminfo($self);
277 0           hpar($self,'init_transform','matrix',$mat);
278 0           hpar($self,'init_transform','post',$pos_d(,0;-));
279             #barf "initdim fails!" unless ($#{dimname($self)}>2);
280             }
281              
282              
283             =head1 Specific handling of Simenes MRI data
284              
285             Key 0029,1010 is the Siemens specific field that contains the ICE
286             miniheaders with dimension information - and position in matrix
287             0029,1020 is deleted from the header, it is big, containing the whole
288             protocol. The important part, the Siemens protocol ASCCONV part, is stored in
289             the ascconv key, see read_text_hdr.
290              
291              
292             =head1 FUNCTIONS
293              
294             =head2 handle_duplicates
295              
296             What to do if two images with the same position in the stack arrive. Throws an
297             error, atm. Should handle duplicate exports
298              
299             =head2 init_dims
300              
301             provides support for PDL::Dims. Useful in combination with PDL::IO::Sereal to
302             have a fully qualified data set.
303              
304             =head2 populate_header
305              
306             Here happens the vendor/modallity specific stuff like parsing private fields.
307             It is required to return a position vector in the series' piddle.
308              
309             =head2 read_text_hdr
310              
311             parses the ASCCONV part of Siemens data header into the ascconv field of the
312             piddle header. All special characters except [a-z0-9]i are converted to _ -- no
313             quoting of hash keys required! You don't need to load this yourself.
314              
315             =head2 setup_dcm
316              
317             sets useful options for this modality.
318              
319             =head2 sort_protid
320              
321             alternative to split based on lProtID (matches raw data key). To activate,
322             after running setup_dcm, point option id to \&sort_protid.
323              
324             =head1 Specific options
325              
326             =over
327              
328             =item Nifti
329              
330             Do we want Nifti output? May be used by your plugin to apply additional steps,
331             eg. more clumps, reorders, setting header fields ...
332              
333             =item c_phase_t
334              
335             Serialize phase and t dimensions
336              
337             =back
338              
339             =cut
340              
341             1;