-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathft_read_mri.m
919 lines (812 loc) · 32.9 KB
/
ft_read_mri.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
function [mri] = ft_read_mri(filename, varargin)
% FT_READ_MRI reads anatomical and functional MRI data from different file formats.
% The output data is structured in such a way that it is compatible with
% FT_DATATYPE_VOLUME.
%
% Use as
% [mri] = ft_read_mri(filename, ...)
%
% Additional options should be specified in key-value pairs and can include
% 'dataformat' = string specifying the file format, determining the low-level
% reading routine to be used. If no explicit format is given,
% it is determined automatically from the filename.
% 'volumes' = vector with the volume indices to read from a 4D nifti (only for 'nifti_spm')
% 'outputfield' = string specifying the name of the field in the structure in which the
% numeric data is stored (default = 'anatomy')
% 'fixel2voxel' = string, the operation to apply to the fixels belonging to the
% same voxel, can be 'max', 'min', 'mean' (only for 'mrtrix_mif', default = 'max')
% 'indexfile' = string, pointing to a fixel index file, if not present in the same directory
% as the functional data (only for 'mrtrix_mif')
% 'spmversion' = string, version of SPM to be used (default = 'spm12')
% 'readbids' = string, 'yes', no', or 'ifmakessense', whether to read information from
% the BIDS sidecar files (default = 'ifmakessense')
%
% The supported dataformats are
% 'afni_head'/'afni_brik' uses AFNI code
% 'analyze_img'/'analyze_hdr' uses SPM code
% 'analyze_old' uses Darren Webber's code
% 'asa_mri'
% 'ctf_mri'
% 'ctf_mri4'
% 'ctf_svl'
% 'dicom' uses SPM code, same as dicom_spm
% 'dicom_spm' uses SPM code, also allows for multiframe dicoms (one volume in a file)
% 'dicom_fs' uses FreeSurfer code, which relies on MATLAB image processing toolbox
% 'dicom_old' uses MATLAB image processing toolbox code
% 'freesurfer_mgh' uses FreeSurfer code
% 'freesurfer_mgz' uses FreeSurfer code
% 'jnifti_jnii'
% 'jnifti_bnii'
% 'matlab' assumes a MATLAB *.mat file containing a struct
% 'minc' uses SPM, this requires SPM5 or older
% 'mrtrix_mif' uses mrtrix code
% 'neuromag_fif' uses MNE toolbox code
% 'neuromag_fif_old' uses meg-pd toolbox
% 'nifti' uses FreeSurfer code
% 'nifti_spm' uses SPM code
% 'seg3d_mat' MATLAB file from Seg3D with a scirunnrrd structure
% 'yokogawa_mri'
%
% The following MRI file formats are supported
% AFNI (*.head, *.brik)
% ANT - Advanced Neuro Technology (*.mri)
% Analyze (*.img, *.hdr)
% CTF (*.svl, *.mri version 4 and 5)
% DICOM (*.dcm, *.ima)
% FreeSurfer (*.mgz, *.mgh)
% MATLAB (*.mat)
% MINC (*.mnc)
% Mrtrix image format (*.mif)
% NIFTi (*.nii) and zipped NIFTi (*.nii.gz)
% Neuromag/Elekta/Megin (*.fif)
% Yokogawa (*.mrk, incomplete)
%
% If you have a series of DICOM files, please provide the name of any of the files in
% the series (e.g. the first one). The files corresponding to the whole volume will
% be found automatically.
%
% The output MRI may have a homogenous transformation matrix that converts the
% coordinates of each voxel (in xgrid/ygrid/zgrid) into head coordinates.
%
% If the input file is a 4D nifti, and you wish to load in just a subset of the
% volumes, for example due to memory constraints, you should use as dataformat 'nifti_spm',
% which uses the optional key-value pair 'volumes' = vector, with the indices of the
% to-be-read volumes. The order of the indices is ignored, and the volumes will be
% sorted according to the original numeric indices, i.e., 1:10 yields the same as 10:-1:1
%
% See also FT_DATATYPE_VOLUME, FT_WRITE_MRI, FT_READ_DATA, FT_READ_HEADER, FT_READ_EVENT
% Copyright (C) 2008-2024, Robert Oostenveld & Jan-Mathijs Schoffelen
%
% This file is part of FieldTrip, see http://www.fieldtriptoolbox.org
% for the documentation and details.
%
% FieldTrip is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% FieldTrip is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with FieldTrip. If not, see <http://www.gnu.org/licenses/>.
%
% $Id$
% optionally get the data from the URL and make a temporary local copy
filename = fetch_url(filename);
% get the options
dataformat = ft_getopt(varargin, 'dataformat');
outputfield = ft_getopt(varargin, 'outputfield', 'anatomy');
spmversion = ft_getopt(varargin, 'spmversion');
readbids = ft_getopt(varargin, 'readbids', 'ifmakessense');
% use the version that is on the path, or default to spm12
if isempty(spmversion)
if ~ft_hastoolbox('spm')
spmversion = 'spm12';
else
spmversion = lower(spm('ver'));
end
end
% the following is added for backward compatibility of using 'format' rather than 'dataformat'
format = ft_getopt(varargin, 'format');
if ~isempty(format)
ft_warning('the option ''format'' will be deprecated soon, please use ''dataformat'' instead');
if isempty(dataformat)
dataformat = format;
end
end
% for backward compatibility with https://github.com/fieldtrip/fieldtrip/issues/1585
if islogical(readbids)
% it should be either yes/no/ifmakessense
if readbids
readbids = 'yes';
else
readbids = 'no';
end
end
% test whether the file exists
if ~exist(filename, 'file')
ft_error('file ''%s'' does not exist', filename);
end
if isempty(dataformat)
% only do the autodetection if the format was not specified
dataformat = ft_filetype(filename);
end
% deal with data that is organized according to BIDS
% this has to happen prior to the unzipping of the file
if strcmp(readbids, 'yes') || strcmp(readbids, 'ifmakessense')
[p, f, x] = fileparts(filename);
% check whether it is a BIDS dataset with a json sidecar file
isbids = startsWith(f, 'sub-');
if isbids
% try to read the metadata from the BIDS sidecar files
sidecar = bids_sidecar(filename);
if ~isempty(sidecar)
mri_json = ft_read_json(sidecar);
end
end
end
if strcmp(dataformat, 'compressed') || (strcmp(dataformat, 'freesurfer_mgz') && ispc) || any(filetype_check_extension(filename, {'gz', 'zip', 'tar', 'tgz'}))
% the file is compressed, unzip on the fly,
% -freesurfer mgz files get special treatment only on a pc
% -compressed AFNI BRIKS also need the HEAD copied over to the temp dir
filename_old = filename;
filename = inflate_file(filename_old);
if strcmp(dataformat, 'freesurfer_mgz')
filename_old = filename;
filename = [filename '.mgh'];
movefile(filename_old, filename);
end
if ~strcmp(dataformat, 'nifti_spm')
% replace it with the filetype's default format, but don't overwrite in
% case dataformat was nifti_spm
dataformat = ft_filetype(filename);
end
if strcmp(dataformat, 'afni_brik')
[p, f, e] =fileparts(filename_old);
copyfile(fullfile(p, strrep(f, 'BRIK', 'HEAD')), strrep(filename, 'BRIK', 'HEAD'));
end
inflated = true;
else
inflated = false;
end
% test for the presence of some external functions from other toolboxes
hasspm2 = ft_hastoolbox('spm2'); % see http://www.fil.ion.ucl.ac.uk/spm/
hasspm5 = ft_hastoolbox('spm5'); % see http://www.fil.ion.ucl.ac.uk/spm/
hasspm8 = ft_hastoolbox('spm8'); % see http://www.fil.ion.ucl.ac.uk/spm/
hasspm12 = ft_hastoolbox('spm12'); % see http://www.fil.ion.ucl.ac.uk/spm/
switch dataformat
case 'ctf_mri'
[img, hdr] = read_ctf_mri(filename);
transform = hdr.transformMRI2Head;
coordsys = 'ctf';
if issubfield(hdr, 'fiducial.head')
fid.label = fieldnames(hdr.fiducial.head);
for i=1:length(fid.label)
fid.pos(i,:) = hdr.fiducial.head.(fid.label{i});
end
elseif issubfield(hdr, 'fiducial.mri')
fid.label = fieldnames(hdr.fiducial.mri);
for i=1:length(fid.label)
fid.pos(i,:) = hdr.fiducial.mri.(fid.label{i});
end
% convert from voxel to headcoordinates
fid.pos = ft_warp_apply(transform, fid.pos);
end
case 'ctf_mri4'
[img, hdr] = read_ctf_mri4(filename);
transform = hdr.transformMRI2Head;
coordsys = 'ctf';
if issubfield(hdr, 'fiducial.head')
fid.label = fieldnames(hdr.fiducial.head);
for i=1:length(fid.label)
fid.pos(i,:) = hdr.fiducial.head.(fid.label{i});
end
elseif issubfield(hdr, 'fiducial.mri')
fid.label = fieldnames(hdr.fiducial.mri);
for i=1:length(fid.label)
fid.pos(i,:) = hdr.fiducial.mri.(fid.label{i});
end
% convert from voxel to headcoordinates
fid.pos = ft_warp_apply(transform, fid.pos);
end
case 'ctf_svl'
[img, hdr] = read_ctf_svl(filename);
transform = hdr.transform;
case 'asa_mri'
[img, seg, hdr] = read_asa_mri(filename);
transform = hdr.transformMRI2Head;
if isempty(seg)
% in case seg exists, it will be added to the output
clear seg
end
case 'minc'
if ~(hasspm2 || hasspm5)
fprintf('the SPM2 or SPM5 toolbox is required to read *.mnc files\n');
ft_hastoolbox('spm2', 1);
end
% use the functions from SPM
hdr = spm_vol_minc(filename);
img = spm_read_vols(hdr);
transform = hdr.mat;
case 'nifti_spm'
if ~(hasspm5 || hasspm8 || hasspm12)
fprintf('the SPM5 or later toolbox is required to read *.nii files\n');
ft_hastoolbox(spmversion, 1);
end
volumes = ft_getopt(varargin, 'volumes', []);
% use the functions from SPM
hdr = spm_vol_nifti(filename);
if isempty(volumes)
img = double(hdr.private.dat);
else
volumes = sort(intersect(volumes, 1:size(hdr.private.dat,4)));
img = double(hdr.private.dat(:,:,:,volumes));
end
transform = hdr.mat;
unit = 'mm';
try
% The nifti header allows three methods for specifying the coordinates of the
% voxels. For two of them (sform and qform), the header contains a numerical
% code from 0 to 4 that specifies the coordinate system. SPM8 and SPM12 use a
% field in the private header of the nifti object to code this.
switch hdr.private.mat_intent
case 'UNKNOWN'
coordsys = 'unknown';
case 'Scanner'
coordsys = 'scanras';
case 'Aligned'
coordsys = 'aligned';
case 'Talairach'
coordsys = 'tal';
case 'MNI152'
coordsys = 'mni152';
end
% We cannot trust it yet, see https://github.com/fieldtrip/fieldtrip/issues/1879
ft_notice('the coordinate system appears to be ''%s''\n', coordsys);
clear coordsys
end
case {'analyze_img' 'analyze_hdr'}
if ~(hasspm8 || hasspm12)
fprintf('the SPM8 or newer toolbox is required to read analyze files\n');
ft_hastoolbox(spmversion, 1);
end
% use the image file instead of the header
filename((end-2):end) = 'img';
% use the functions from SPM to read the Analyze MRI
hdr = spm_vol(filename);
img = spm_read_vols(hdr);
transform = hdr.mat;
case 'analyze_old'
% use the functions from Darren Weber's mri_toolbox to read the Analyze MRI
ft_hastoolbox('mri', 1); % from Darren Weber, see http://eeg.sourceforge.net/
avw = avw_img_read(filename, 0); % returned volume is LAS*
img = avw.img;
hdr = avw.hdr;
% The default Analyze orientation is axial unflipped (LAS*), which means
% that the resulting volume is according to the radiological convention.
% Most other fMRI and EEG/MEG software (except Mayo/Analyze) uses
% neurological conventions and a right-handed coordinate system, hence
% the first axis of the 3D volume (right-left) should be flipped to make
% the coordinate system comparable to SPM
ft_warning('flipping 1st dimension (L-R) to obtain volume in neurological convention');
img = flip(img, 1);
transform = diag(hdr.dime.pixdim(2:4));
transform(4,4) = 1;
case {'afni_brik' 'afni_head'}
% needs afni
ft_hastoolbox('afni', 1); % see http://afni.nimh.nih.gov/
[err, hdr] = BrikInfo(filename);
% check the precision of the data, and if scaling is required. If the precision is other than float,
%and no scaling is required, then return the data in its native precision, let the low level code take
% care of that
if any(hdr.BRICK_FLOAT_FACS~=0)
opts.OutPrecision = '';
else
opts.OutPrecision = '*';
opts.Scale = 0;
end
[err, img, hdr, ErrMessage] = BrikLoad(filename, opts);
if err
ft_error('could not read AFNI file');
end
if isfield(hdr, 'ORIENT_SPECIFIC')
[err, orient, flipvec] = AFNI_OrientCode(hdr.ORIENT_SPECIFIC);
% FIXME, I don't understand why the orient vector needs to be like
% this: it seems the opposite of what is reflected in the coordsys
% (see below), but it seems to yield internally consistent results
else
% afni volume info
orient = 'LPI'; % hope for the best
end
% origin and basis vectors in world space
[unused, ix] = AFNI_Index2XYZcontinuous([0 0 0; eye(3)], hdr, orient);
% basis vectors in voxel space
e1 = ix(2,:) - ix(1,:);
e2 = ix(3,:) - ix(1,:);
e3 = ix(4,:) - ix(1,:);
% change from base0 (afni) to base1 (SPM/Matlab)
o = ix(1,:) - (e1+e2+e3);
% create matrix
transform = [e1;e2;e3;o]';
transform = cat(1, transform, [0 0 0 1]);
coordsys = lower(hdr.Orientation(:,2)');
if contains(filename, 'TTatlas') || (isfield(hdr, 'TEMPLATE_SPACE') && ~isempty(hdr.TEMPLATE_SPACE))
if isfield(hdr, 'TEMPLATE_SPACE') && ~isempty(hdr.TEMPLATE_SPACE)
space = hdr.TEMPLATE_SPACE;
else
space = 'tal'; % accommodate the case when this is not specified in the hdr, make assumption
end
if startsWith(space, 'tt_') || startsWith(space, 'TT_')
space = 'tal';
elseif startsWith(space, 'mni')
space = 'mni';
elseif startsWith(space, 'tlrc')
% according to the documentation tlrc is rather generic as a
% specification of the space, but originally it meant tal.
ft_warning('space ''tlrc'' might be ambiguous, here assuming the coordsys to be ''tal''');
space = 'tal';
end
if ismember(space, {'tal' 'mni'})
% xyz orientation should be RAS
if ~strcmp(coordsys, 'ras')
ft_warning('the template space suggests that the image is in %s coordinates, but the xyz orientation %s does not match this', space, coordsys);
xxx2ras = true;
else
coordsys = space;
end
end
end
case 'neuromag_fif'
% needs mne toolbox
ft_hastoolbox('mne', 1);
% use the mne functions to read the Neuromag MRI
hdr = fiff_read_mri(filename);
img_t = cat(3, hdr.slices.data);
img = permute(img_t,[2 1 3]);
hdr.slices = rmfield(hdr.slices, 'data'); % remove the image data to save memory
% information below is from MNE - fiff_define_constants.m
% coordinate system 4 - is the MEG head coordinate system (fiducials)
% coordinate system 5 - is the MRI coordinate system
% coordinate system 2001 - MRI voxel coordinates
% coordinate system 2002 - Surface RAS coordinates (is mainly vertical shift, no rotation to 2001)
% MEG sensor positions come in system 4
% MRI comes in system 2001
transform = eye(4);
if isfield(hdr, 'trans') && issubfield(hdr.trans, 'trans')
if (hdr.trans.from == 4) && (hdr.trans.to == 5)
transform = hdr.trans.trans;
else
ft_warning('W: trans does not transform from 4 to 5.');
ft_warning('W: Please check the MRI fif-file');
end
else
ft_warning('W: trans structure is not defined.');
ft_warning('W: Maybe coregistration is missing?');
end
if isfield(hdr, 'voxel_trans') && issubfield(hdr.voxel_trans, 'trans')
% centers the coordinate system and switches from mm to m
if (hdr.voxel_trans.from == 2001) && (hdr.voxel_trans.to == 5)
% matlab_shift compensates for the different index conventions between C and MATLAB
% the lines below is old code (prior to Jan 3, 2013) and only works with 1 mm resolution MRIs
% matlab_shift = [ 0 0 0 0.001; 0 0 0 -0.001; 0 0 0 0.001; 0 0 0 0];
% transform transforms from 2001 to 5 and further to 4
% transform = transform\(hdr.voxel_trans.trans+matlab_shift);
% the lines below should work with arbitrary resolution
matlab_shift = eye(4);
matlab_shift(1:3,4) = [-1,-1,-1];
transform = transform\(hdr.voxel_trans.trans * matlab_shift);
coordsys = 'neuromag';
mri.unit = 'm';
else
ft_warning('W: voxel_trans does not transform from 2001 to 5.');
ft_warning('W: Please check the MRI fif-file');
end
else
ft_warning('W: voxel_trans structure is not defined.');
ft_warning('W: Please check the MRI fif-file');
end
case 'neuromag_fif_old'
% needs meg_pd toolbox
ft_hastoolbox('meg-pd', 1);
% use the meg_pd functions to read the Neuromag MRI
[img,coords] = loadmri(filename);
dev = loadtrans(filename,'MRI','HEAD');
transform = dev*coords;
hdr.coords = coords;
hdr.dev = dev;
case {'dicom' 'dicom_spm'}
% requires spm12
ft_hastoolbox('spm12', 1);
dicomhdr = spm_dicom_headers(filename); % read the header for a single file
if isfield(dicomhdr{1}, 'SharedFunctionalGroupSequence')
% this seems to be a multiframe image, we don't need to read in the separate headers for each of the slices
else
% this is a 'traditional' image, one file per slice
[f, p, e] = fileparts(filename);
d = dir(fullfile(f,sprintf('*%s',e)));
filenames = {d.name};
for k = 1:numel(filenames)
filenames{k} = fullfile(f, filenames{k});
end
hdr1 = spm_dicom_headers(filename); % read the header for the predefined slice
hdrs = spm_dicom_headers(filenames); % read the headers for all slices in the folder
for k = 1:numel(hdrs)
seriesnumber(k,1) = hdrs{k}.SeriesNumber;
end
sel = seriesnumber==hdr1{1}.SeriesNumber;
dicomhdr = hdrs(sel);
end
tdir = tempdir;
fname = spm_dicom_convert(dicomhdr, 'all', 'flat', 'nii', tdir, 1);
% convert the cell(-array) dicomhdr as generated by SPM into a
% struct(-array)
for k = 1:numel(dicomhdr)
dicomhdr_struct(k) = dicomhdr{k};
end
clear dicomhdr;
mri = ft_read_mri(fname.files{1});
mri.coordsys = 'scanras';
mri.unit = 'mm';
mri.hdr = dicomhdr_struct;
delete(fname.files{1});
case 'dicom_fs'
% this returns a right-handed volume with the transformation matrix stored in the file headers
% see https://github.com/fieldtrip/website/pull/444
% needs the freesurfer toolbox
ft_hastoolbox('freesurfer', 1);
[dcmdir, junk1, junk2] = fileparts(filename);
if isempty(dcmdir)
dcmdir = '.';
end
[img, transform,hdr, mr_params] = load_dicom_series(dcmdir,dcmdir,filename);
transform = vox2ras_0to1(transform);
coordsys = 'scanras';
unit = 'mm';
case 'dicom_old'
% this uses the Image processing toolbox
% the DICOM files represent a stack of slices, and possibly even multiple volumes
orig = dicominfo(filename);
dim(1) = orig.Rows;
dim(2) = orig.Columns;
% this works for the Siemens scanners at the FCDC
[p, f] = fileparts(filename);
tok = tokenize(f, '.');
for i=5:length(tok)
tok{i} = '*';
end
filename = sprintf('%s.', tok{:}); % reconstruct the filename with wildcards and '.' between the segments
filename = filename(1:end-1); % remove the last '.'
dirlist = dir(fullfile(p, filename));
dirlist = {dirlist.name};
if isempty(dirlist)
% this is for the Philips data acquired at KI
ft_warning('could not determine list of dicom files, trying with *.dcm');
dirlist = dir(fullfile(p, '*.dcm'));
dirlist = {dirlist.name};
end
if isempty(dirlist)
ft_warning('could not determine list of dicom files, trying with *.ima');
dirlist = dir(fullfile(p, '*.ima'));
dirlist = {dirlist.name};
end
if length(dirlist)==1
% try something else to get a list of all the slices
dirlist = dir(fullfile(p, '*'));
dirlist = {dirlist(~[dirlist.isdir]).name};
end
keep = false(1, length(dirlist));
for i=1:length(dirlist)
filename = char(fullfile(p, dirlist{i}));
if ~strcmp(dataformat, 'dicom_old')
keep(i) = false;
fprintf('skipping ''%s'' because of incorrect filetype\n', filename);
end
% read the header information
info = dicominfo(filename);
if info.SeriesNumber~=orig.SeriesNumber
keep(i) = false;
fprintf('skipping ''%s'' because of different SeriesNumber\n', filename);
else
keep(i) = true;
hdr(i) = info;
end
end
% remove the files that were skipped
hdr = hdr(keep);
dirlist = dirlist(keep);
% pre-allocate enough space for the subsequent slices
dim(3) = length(dirlist);
img = zeros(dim(1), dim(2), dim(3));
for i=1:length(dirlist)
filename = char(fullfile(p, dirlist{i}));
ft_info('reading image data from ''%s''\n', filename);
img(:,:,i) = dicomread(hdr(i));
end
% reorder and concatenate the slices
[z, indx] = sort(cell2mat({hdr.SliceLocation}));
hdr = hdr(indx);
img = img(:,:,indx);
% construct a homgeneous transformation matrix that performs the scaling from voxels to mm
transform = dicom2transform(hdr);
coordsys = 'dicom'; % identical to scanlps, see https://www.fieldtriptoolbox.org/faq/coordsys/#details-of-the-dicom-coordinate-system
unit = 'mm';
% this makes the mapping of voxels to patient coordinates consistent with Horos
img = permute(img, [2, 1, 3]);
case {'nifti', 'nifti_gz', 'freesurfer_mgz', 'freesurfer_mgh'}
ft_hastoolbox('freesurfer', 1);
tmp = MRIread(filename);
ndims = numel(size(tmp.vol));
if ndims==3
img = permute(tmp.vol, [2 1 3]);
% FIXME although this is probably correct
% see the help of MRIread, anecdotally columns and rows seem to need a swap
% in order to match the transform matrix (alternatively a row switch of the
% latter can be done)
elseif ndims==4
img = permute(tmp.vol, [2 1 3 4]);
end
hdr = rmfield(tmp, 'vol');
transform = hdr.vox2ras1;
unit = 'mm';
if isfield(hdr, 'niftihdr')
% The nifti header allows three methods for specifying the coordinates of the
% voxels. For two of them (sform and qform), the header contains a numerical
% code that specifies the coordinate system.
coordsys = {'unknown', 'scanras', 'aligned', 'tal', 'mni152'}; % corresponding to 0, 1, 2, 3, 4
if isequal(hdr.vox2ras0, hdr.niftihdr.sform)
coordsys = coordsys{hdr.niftihdr.sform_code + 1};
elseif isequal(hdr.vox2ras0, hdr.niftihdr.qform)
coordsys = coordsys{hdr.niftihdr.qform_code + 1};
else
coordsys = 'unknown';
end
% We cannot trust it yet, see https://github.com/fieldtrip/fieldtrip/issues/1879
ft_notice('the coordinate system appears to be ''%s''\n', coordsys);
clear coordsys
end
case 'yokogawa_mri'
ft_hastoolbox('yokogawa', 1);
fid = fopen_or_error(filename, 'rb');
mri_info = GetMeg160MriInfoM(fid);
patient_info = GetMeg160PatientInfoFromMriFileM(fid);
[data_style, model, marker, image_parameter, normalize, besa_fiducial_point] = GetMeg160MriFileHeaderInfoM(fid);
fclose(fid);
% gather all meta-information
hdr.mri_info = mri_info;
hdr.patient_info = patient_info;
hdr.data_style = data_style;
hdr.model = model;
hdr.marker = marker;
hdr.image_parameter = image_parameter;
hdr.normalize = normalize;
hdr.besa_fiducial_point = besa_fiducial_point;
ft_error('FIXME yokogawa_mri implementation is incomplete');
case 'seg3d_mat'
scirunnrrd = loadvar(filename, 'scirunnrrd');
mri.anatomy = scirunnrrd.data;
mri.dim = [scirunnrrd.axis.size];
% construct the homogenous transformation matrix
mri.transform = eye(4);
mri.transform(1,1) = scirunnrrd.axis(1).spacing;
mri.transform(2,2) = scirunnrrd.axis(2).spacing;
mri.transform(3,3) = scirunnrrd.axis(3).spacing;
mri.transform(1,4) = scirunnrrd.axis(1).min - scirunnrrd.axis(1).spacing;
mri.transform(2,4) = scirunnrrd.axis(2).min - scirunnrrd.axis(2).spacing;
mri.transform(3,4) = scirunnrrd.axis(3).min - scirunnrrd.axis(3).spacing;
case 'matlab'
mri = loadvar(filename, 'mri');
case {'mif' 'mrtrix_mif'}
ft_hastoolbox('mrtrix', 1);
tmp = read_mrtrix(filename);
% check if it's sparse fixeldata
isfixel = numel(tmp.dim==3) && tmp.dim(3)==1;
if ~isfixel
mri.hdr = removefields(tmp, {'data'});
mri.(outputfield) = tmp.data;
mri.dim = tmp.dim(1:length(size(tmp.data)));
mri.transform = tmp.transform;
mri.transform(1:3,1:3) = diag(tmp.vox(1:3))*mri.transform(1:3,1:3);
else
fix2vox_fun = ft_getopt(varargin, 'fixel2voxel', 'max');
indexfile = ft_getopt(varargin, 'indexfile');
if isempty(indexfile)
% assume the index file to be in the same directory as the data file
[p,f,e] = fileparts(filename);
indexfile = fullfile(p,'index.mif');
end
index = read_mrtrix(indexfile);
tmpdata = reshape(index.data, [], 2);
vox_index = find(tmpdata(:,1)>0);
num_index = tmpdata(vox_index,1);
fix_index = tmpdata(vox_index,2)+1;
% create a mapping matrix of fixel2voxel -> currently this only works
% for scalar fixel data.
tmpdata = zeros(numel(num_index), max(num_index));
for k = 1:numel(num_index)
tmpdata(k, 1:num_index(k)) = fix_index(k)-1 + (1:num_index(k));
end
tmpdata(tmpdata==0) = nan;
tmpdata = tmpdata.'; % transpose is intended
tmpdata(isfinite(tmpdata)) = tmp.data(tmpdata(isfinite(tmpdata)));
switch fix2vox_fun
case 'magmax'
tmpx = nanmin(tmpdata,[],1).';
tmpdata = nanmax(tmpdata,[],1).';
tmpdata(abs(tmpx)>tmpdata) = tmpx(abs(tmpx)>tmpdata);
case 'max'
tmpdata = nanmax(tmpdata,[],1).';
case 'min'
tmpdata = nanmin(tmpdata,[],1).';
case 'mean'
tmpdata = nanmean(tmpdata,1).';
case 'none'
tmpdata = tmpdata.';
otherwise
ft_error('unsupported fixel2voxel operation requested');
end
mri.hdr = removefields(tmp, {'data'});
mri.(outputfield) = zeros([prod(index.dim(1:3)) size(tmpdata,2)]);
mri.(outputfield)(vox_index,:) = tmpdata;
mri.(outputfield) = reshape(mri.(outputfield), [index.dim(1:3) size(tmpdata,2)]);
mri.dim = size(mri.(outputfield)) ;%index.dim(1:length(size(tmp.data)));
mri.transform = tmp.transform;
mri.transform(1:3,1:3) = diag(tmp.vox(1:3))*mri.transform(1:3,1:3);
end
case {'neurojson_jnii' 'neurojson_bnii'}
% this depends on two external toolboxes
ft_hastoolbox('jsonlab', 1);
ft_hastoolbox('jnifti', 1);
jnii = loadjnifti(filename);
mri.hdr = jnii.NIFTIHeader;
mri.(outputfield) = jnii.NIFTIData;
mri.dim = jnii.NIFTIHeader.Dim;
mri.unit = jnii.NIFTIHeader.Unit.L; % units of length
% see https://brainder.org/2012/09/23/the-nifti-file-format/
coordsys = {'scanras', 'aligned', 'tal', 'mni152'};
if jnii.NIFTIHeader.SForm>0
mri.coordsys = coordsys{jnii.NIFTIHeader.SForm};
mri.transform = [
jnii.NIFTIHeader.Affine
0 0 0 1
];
elseif jnii.NIFTIHeader.QForm>0
mri.coordsys = coordsys{jnii.NIFTIHeader.QForm};
% this is adapted from freesurfer/load_nifti_hdr.m
b = jnii.NIFTIHeader.Quatern.b;
c = jnii.NIFTIHeader.Quatern.c;
d = jnii.NIFTIHeader.Quatern.d;
x = jnii.NIFTIHeader.QuaternOffset.x;
y = jnii.NIFTIHeader.QuaternOffset.y;
z = jnii.NIFTIHeader.QuaternOffset.z;
a = 1.0 - (b*b + c*c + d*d);
if(abs(a) < 1.0e-7)
a = 1.0 / sqrt(b*b + c*c + d*d);
b = b*a;
c = c*a;
d = d*a;
a = 0.0;
else
a = sqrt(a);
end
r11 = a*a + b*b - c*c - d*d;
r12 = 2.0*b*c - 2.0*a*d;
r13 = 2.0*b*d + 2.0*a*c;
r21 = 2.0*b*c + 2.0*a*d;
r22 = a*a + c*c - b*b - d*d;
r23 = 2.0*c*d - 2.0*a*b;
r31 = 2.0*b*d - 2*a*c;
r32 = 2.0*c*d + 2*a*b;
r33 = a*a + d*d - c*c - b*b;
if(jnii.NIFTIHeader.VoxelSize(1) < 0.0)
r13 = -r13;
r23 = -r23;
r33 = -r33;
end
R = [r11 r12 r13; r21 r22 r23; r31 r32 r33];
S = diag(jnii.NIFTIHeader.VoxelSize(2:4));
T = [x y z]';
mri.transform = [R*S T; 0 0 0 1];
else
mri.coordsys = 'unknown';
% Method 1
mri.transform = diag(jnii.NIFTIHeader.VoxelSize(2:4));
mri.transform(4,4) = 1;
end
% ensure that this is double precision and not uint8
mri.transform = double(mri.transform);
% ensure that this is double precision and not uint8
mri.dim = double(mri.dim);
% these are already part of the output structure and should not be reassigned
clear coordsys transform unit
otherwise
ft_error('unrecognized filetype ''%s'' for ''%s''', dataformat, filename);
end % switch dataformat
if exist('img', 'var')
% determine the size of the volume in voxels
mri.dim = size(img);
if numel(mri.dim)>3
mri.dim = mri.dim(1:3); % dim should be the spatial 3D dim
end
% store the anatomical data
mri.(outputfield) = img;
end
if exist('seg', 'var')
% store the segmented data
mri.seg = seg;
end
if exist('hdr', 'var')
% store the header with all file format specific details
mri.hdr = hdr;
end
if exist('transform', 'var')
% store the homogeneous transformation matrix if present
mri.transform = transform;
end
if exist('unit', 'var')
% determine the geometrical units in which it is expressed
mri.unit = unit;
else
% estimate the units from the data
mri = ft_determine_units(mri);
end
if exist('coordsys', 'var')
% add a descriptive label for the coordinate system
mri.coordsys = coordsys;
end
if exist('xxx2ras', 'var') && xxx2ras==true
% this is needed for AFNI formatted data, where the created voxels-to-world
% mapping matrix is diagonal for the 3x3 rotation part (i.e. ijk should
% be ras, in order for the tal/mni coordsys to make sense
mri = ft_convert_coordsys(mri, 'ras', 0);
mri.coordsys = space;
end
if (strcmp(readbids, 'yes') || strcmp(readbids, 'ifmakessense')) && isbids
% the BIDS sidecar files extend/overrule the information that is present in the file header itself
if exist('mri_json', 'var')
fid =[];
if isfield(mri_json, 'AnatomicalLandmarkCoordinates')
fid.label = fieldnames(mri_json.AnatomicalLandmarkCoordinates);
for i=1:length(fid.label)
fid.pos(i,:) = mri_json.AnatomicalLandmarkCoordinates.(fid.label{i});
end
if isfield(mri_json, 'AnatomicalLandmarkCoordinateSystem') && isfield(mri_json, 'AnatomicalLandmarkCoordinateUnits')
fid.coordsys = mri_json.AnatomicalLandmarkCoordinateSystem;
fid.unit = mri_json.AnatomicalLandmarkCoordinateUnits;
else
% assume that it is according to https://bids-specification.readthedocs.io/en/stable/glossary.html#anatomicallandmarkcoordinates-sense-2-metadata
fid.pos = fid.pos+1; % convert from 0 to 1 offset
fid.pos = ft_warp_apply(mri.transform, fid.pos); % convert from voxel to head coordinates
end
end % if fiducials present
end % if mri_json
end % if readbids
if exist('fid', 'var') && ~isempty(fid)
% store the fiducial details
mri.fid = fid;
end
if inflated
% compressed file has been unzipped on the fly, clean up
delete(filename);
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function value = loadvar(filename, varname)
var = whos('-file', filename);
if length(var)==1
filecontent = load(filename); % read the one variable in the file, regardless of how it is called
value = filecontent.(var.name);
clear filecontent
else
filecontent = load(filename, varname);
value = filecontent.(varname); % read the variable named according to the input specification
clear filecontent
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function bool = isrighthanded(orient)
bool = ismember(orient, {'ALS' 'RAS' 'PRS' 'LPS' 'SAL' 'SRA' 'SPR' 'SLP'});