File Coverage

blib/lib/PDL/IO/Dcm/Plugins/MRISiemens.pm
Criterion Covered Total %
statement 20 167 11.9
branch 0 44 0.0
condition 0 22 0.0
subroutine 7 13 53.8
pod 6 6 100.0
total 33 252 13.1


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