Skip to content

Commit cfcd8f6

Browse files
authored
Merge pull request #2338 from matteomancini/enh-mrtrix3
[ENH] Extended MRtrix3 interface
2 parents 775e21b + c31d7cd commit cfcd8f6

11 files changed

+480
-210
lines changed

.zenodo.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,11 @@
563563
"affiliation": "MIT, HMS",
564564
"name": "Ghosh, Satrajit",
565565
"orcid": "0000-0002-5312-6729"
566+
},
567+
{
568+
"affiliation": "University College London",
569+
"name": "Mancini, Matteo",
570+
"orcid": "0000-0001-7194-4568"
566571
}
567572
],
568573
"keywords": [

nipype/interfaces/mrtrix3/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# -*- coding: utf-8 -*-
55

66
from .utils import (Mesh2PVE, Generate5tt, BrainMask, TensorMetrics,
7-
ComputeTDI, TCK2VTK)
7+
ComputeTDI, TCK2VTK, MRMath, MRConvert, DWIExtract)
88
from .preprocess import ResponseSD, ACTPrepareFSL, ReplaceFSwithFIRST
99
from .tracking import Tractography
1010
from .reconst import FitTensor, EstimateFOD

nipype/interfaces/mrtrix3/preprocess.py

Lines changed: 23 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -16,87 +16,47 @@
1616
import os.path as op
1717

1818
from ..base import (CommandLineInputSpec, CommandLine, traits, TraitedSpec,
19-
File, isdefined)
19+
File, isdefined, Undefined)
2020
from .base import MRTrix3BaseInputSpec, MRTrix3Base
2121

2222

2323
class ResponseSDInputSpec(MRTrix3BaseInputSpec):
24-
in_file = File(exists=True, argstr='%s', mandatory=True, position=-2,
25-
desc='input diffusion weighted images')
26-
27-
out_file = File(
28-
'response.txt', argstr='%s', mandatory=True, position=-1,
29-
usedefault=True, desc='output file containing SH coefficients')
30-
31-
# DW Shell selection options
32-
shell = traits.List(traits.Float, sep=',', argstr='-shell %s',
33-
desc='specify one or more dw gradient shells')
24+
algorithm = traits.Enum('msmt_5tt','dhollander','tournier','tax', argstr='%s', position=-6,
25+
mandatory=True, desc='response estimation algorithm (multi-tissue)')
26+
in_file = File(exists=True, argstr='%s', position=-5,
27+
mandatory=True, desc='input DWI image')
28+
mtt_file = File(argstr='%s', position=-4, desc='input 5tt image')
29+
wm_file = File('wm.txt', argstr='%s', position=-3, usedefault=True,
30+
desc='output WM response text file')
31+
gm_file = File(argstr='%s', position=-2, desc='output GM response text file')
32+
csf_file = File(argstr='%s', position=-1, desc='output CSF response text file')
3433
in_mask = File(exists=True, argstr='-mask %s',
35-
desc='provide initial mask image')
34+
desc='provide initial mask image')
3635
max_sh = traits.Int(8, argstr='-lmax %d',
37-
desc='maximum harmonic degree of response function')
38-
out_sf = File('sf_mask.nii.gz', argstr='-sf %s',
39-
desc='write a mask containing single-fibre voxels')
40-
test_all = traits.Bool(False, argstr='-test_all',
41-
desc='re-test all voxels at every iteration')
42-
43-
# Optimization
44-
iterations = traits.Int(0, argstr='-max_iters %d',
45-
desc='maximum number of iterations per pass')
46-
max_change = traits.Float(
47-
argstr='-max_change %f',
48-
desc=('maximum percentile change in any response function coefficient;'
49-
' if no individual coefficient changes by more than this '
50-
'fraction, the algorithm is terminated.'))
51-
52-
# Thresholds
53-
vol_ratio = traits.Float(
54-
.15, argstr='-volume_ratio %f',
55-
desc=('maximal volume ratio between the sum of all other positive'
56-
' lobes in the voxel and the largest FOD lobe'))
57-
disp_mult = traits.Float(
58-
1., argstr='-dispersion_multiplier %f',
59-
desc=('dispersion of FOD lobe must not exceed some threshold as '
60-
'determined by this multiplier and the FOD dispersion in other '
61-
'single-fibre voxels. The threshold is: (mean + (multiplier * '
62-
'(mean - min))); default = 1.0. Criterion is only applied in '
63-
'second pass of RF estimation.'))
64-
int_mult = traits.Float(
65-
2., argstr='-integral_multiplier %f',
66-
desc=('integral of FOD lobe must not be outside some range as '
67-
'determined by this multiplier and FOD lobe integral in other'
68-
' single-fibre voxels. The range is: (mean +- (multiplier * '
69-
'stdev)); default = 2.0. Criterion is only applied in second '
70-
'pass of RF estimation.'))
36+
desc='maximum harmonic degree of response function')
7137

7238

7339
class ResponseSDOutputSpec(TraitedSpec):
74-
out_file = File(exists=True, desc='the output response file')
75-
out_sf = File(desc=('mask containing single-fibre voxels'))
40+
wm_file = File(argstr='%s', desc='output WM response text file')
41+
gm_file = File(argstr='%s', desc='output GM response text file')
42+
csf_file = File(argstr='%s', desc='output CSF response text file')
7643

7744

7845
class ResponseSD(MRTrix3Base):
7946

8047
"""
81-
Generate an appropriate response function from the image data for
82-
spherical deconvolution.
83-
84-
.. [1] Tax, C. M.; Jeurissen, B.; Vos, S. B.; Viergever, M. A. and
85-
Leemans, A., Recursive calibration of the fiber response function
86-
for spherical deconvolution of diffusion MRI data. NeuroImage,
87-
2014, 86, 67-80
88-
48+
Estimate response function(s) for spherical deconvolution using the specified algorithm.
8949
9050
Example
9151
-------
9252
9353
>>> import nipype.interfaces.mrtrix3 as mrt
9454
>>> resp = mrt.ResponseSD()
9555
>>> resp.inputs.in_file = 'dwi.mif'
96-
>>> resp.inputs.in_mask = 'mask.nii.gz'
56+
>>> resp.inputs.algorithm = 'tournier'
9757
>>> resp.inputs.grad_fsl = ('bvecs', 'bvals')
9858
>>> resp.cmdline # doctest: +ELLIPSIS
99-
'dwi2response -fslgrad bvecs bvals -mask mask.nii.gz dwi.mif response.txt'
59+
'dwi2response -fslgrad bvecs bvals tournier dwi.mif wm.txt'
10060
>>> resp.run() # doctest: +SKIP
10161
"""
10262

@@ -106,10 +66,11 @@ class ResponseSD(MRTrix3Base):
10666

10767
def _list_outputs(self):
10868
outputs = self.output_spec().get()
109-
outputs['out_file'] = op.abspath(self.inputs.out_file)
110-
111-
if isdefined(self.inputs.out_sf):
112-
outputs['out_sf'] = op.abspath(self.inputs.out_sf)
69+
outputs['wm_file'] = op.abspath(self.inputs.wm_file)
70+
if self.inputs.gm_file != Undefined:
71+
outputs['gm_file'] = op.abspath(self.inputs.gm_file)
72+
if self.inputs.csf_file != Undefined:
73+
outputs['csf_file'] = op.abspath(self.inputs.csf_file)
11374
return outputs
11475

11576

nipype/interfaces/mrtrix3/reconst.py

Lines changed: 29 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
import os.path as op
1717

18-
from ..base import traits, TraitedSpec, File
18+
from ..base import traits, TraitedSpec, File, Undefined
1919
from .base import MRTrix3BaseInputSpec, MRTrix3Base
2020

2121

@@ -74,108 +74,55 @@ def _list_outputs(self):
7474

7575

7676
class EstimateFODInputSpec(MRTrix3BaseInputSpec):
77-
in_file = File(exists=True, argstr='%s', mandatory=True, position=-3,
78-
desc='input diffusion weighted images')
79-
response = File(
80-
exists=True, argstr='%s', mandatory=True, position=-2,
81-
desc=('a text file containing the diffusion-weighted signal response '
82-
'function coefficients for a single fibre population'))
83-
out_file = File(
84-
'fods.mif', argstr='%s', mandatory=True, position=-1,
85-
usedefault=True, desc=('the output spherical harmonics coefficients'
86-
' image'))
77+
algorithm = traits.Enum('csd','msmt_csd', argstr='%s', position=-8,
78+
mandatory=True, desc='FOD algorithm')
79+
in_file = File(exists=True, argstr='%s', position=-7,
80+
mandatory=True, desc='input DWI image')
81+
wm_txt = File(argstr='%s', position=-6,
82+
mandatory=True, desc='WM response text file')
83+
wm_odf = File('wm.mif', argstr='%s', position=-5, usedefault=True,
84+
mandatory=True, desc='output WM ODF')
85+
gm_txt = File(argstr='%s', position=-4, desc='GM response text file')
86+
gm_odf = File('gm.mif', argstr='%s', position=-3, desc='output GM ODF')
87+
csf_txt = File(argstr='%s', position=-2, desc='CSF response text file')
88+
csf_odf = File('csf.mif', argstr='%s', position=-1, desc='output CSF ODF')
89+
mask_file = File(exists=True, argstr='-mask %s', desc='mask image')
8790

8891
# DW Shell selection options
8992
shell = traits.List(traits.Float, sep=',', argstr='-shell %s',
9093
desc='specify one or more dw gradient shells')
91-
92-
# Spherical deconvolution options
9394
max_sh = traits.Int(8, argstr='-lmax %d',
9495
desc='maximum harmonic degree of response function')
95-
in_mask = File(exists=True, argstr='-mask %s',
96-
desc='provide initial mask image')
9796
in_dirs = File(
9897
exists=True, argstr='-directions %s',
9998
desc=('specify the directions over which to apply the non-negativity '
10099
'constraint (by default, the built-in 300 direction set is '
101100
'used). These should be supplied as a text file containing the '
102101
'[ az el ] pairs for the directions.'))
103-
sh_filter = File(
104-
exists=True, argstr='-filter %s',
105-
desc=('the linear frequency filtering parameters used for the initial '
106-
'linear spherical deconvolution step (default = [ 1 1 1 0 0 ]). '
107-
'These should be supplied as a text file containing the '
108-
'filtering coefficients for each even harmonic order.'))
109-
110-
neg_lambda = traits.Float(
111-
1.0, argstr='-neg_lambda %f',
112-
desc=('the regularisation parameter lambda that controls the strength'
113-
' of the non-negativity constraint'))
114-
thres = traits.Float(
115-
0.0, argstr='-threshold %f',
116-
desc=('the threshold below which the amplitude of the FOD is assumed '
117-
'to be zero, expressed as an absolute amplitude'))
118-
119-
n_iter = traits.Int(
120-
50, argstr='-niter %d', desc=('the maximum number of iterations '
121-
'to perform for each voxel'))
122102

123103

124104
class EstimateFODOutputSpec(TraitedSpec):
125-
out_file = File(exists=True, desc='the output response file')
105+
wm_odf = File(argstr='%s', desc='output WM ODF')
106+
gm_odf = File(argstr='%s', desc='output GM ODF')
107+
csf_odf = File(argstr='%s', desc='output CSF ODF')
126108

127109

128110
class EstimateFOD(MRTrix3Base):
129111

130112
"""
131-
Convert diffusion-weighted images to tensor images
132-
133-
Note that this program makes use of implied symmetries in the diffusion
134-
profile. First, the fact the signal attenuation profile is real implies
135-
that it has conjugate symmetry, i.e. Y(l,-m) = Y(l,m)* (where * denotes
136-
the complex conjugate). Second, the diffusion profile should be
137-
antipodally symmetric (i.e. S(x) = S(-x)), implying that all odd l
138-
components should be zero. Therefore, this program only computes the even
139-
elements.
140-
141-
Note that the spherical harmonics equations used here differ slightly from
142-
those conventionally used, in that the (-1)^m factor has been omitted.
143-
This should be taken into account in all subsequent calculations.
144-
The spherical harmonic coefficients are stored as follows. First, since
145-
the signal attenuation profile is real, it has conjugate symmetry, i.e.
146-
Y(l,-m) = Y(l,m)* (where * denotes the complex conjugate). Second, the
147-
diffusion profile should be antipodally symmetric (i.e. S(x) = S(-x)),
148-
implying that all odd l components should be zero. Therefore, only the
149-
even elements are computed.
150-
151-
Note that the spherical harmonics equations used here differ slightly from
152-
those conventionally used, in that the (-1)^m factor has been omitted.
153-
This should be taken into account in all subsequent calculations.
154-
Each volume in the output image corresponds to a different spherical
155-
harmonic component. Each volume will correspond to the following:
156-
157-
volume 0: l = 0, m = 0
158-
volume 1: l = 2, m = -2 (imaginary part of m=2 SH)
159-
volume 2: l = 2, m = -1 (imaginary part of m=1 SH)
160-
volume 3: l = 2, m = 0
161-
volume 4: l = 2, m = 1 (real part of m=1 SH)
162-
volume 5: l = 2, m = 2 (real part of m=2 SH)
163-
etc...
164-
165-
113+
Estimate fibre orientation distributions from diffusion data using spherical deconvolution
166114
167115
Example
168116
-------
169117
170118
>>> import nipype.interfaces.mrtrix3 as mrt
171119
>>> fod = mrt.EstimateFOD()
120+
>>> fod.inputs.algorithm = 'csd'
172121
>>> fod.inputs.in_file = 'dwi.mif'
173-
>>> fod.inputs.response = 'response.txt'
174-
>>> fod.inputs.in_mask = 'mask.nii.gz'
122+
>>> fod.inputs.wm_txt = 'wm.txt'
175123
>>> fod.inputs.grad_fsl = ('bvecs', 'bvals')
176124
>>> fod.cmdline # doctest: +ELLIPSIS
177-
'dwi2fod -fslgrad bvecs bvals -mask mask.nii.gz dwi.mif response.txt\
178-
fods.mif'
125+
'dwi2fod -fslgrad bvecs bvals csd dwi.mif wm.txt wm.mif'
179126
>>> fod.run() # doctest: +SKIP
180127
"""
181128

@@ -185,5 +132,12 @@ class EstimateFOD(MRTrix3Base):
185132

186133
def _list_outputs(self):
187134
outputs = self.output_spec().get()
188-
outputs['out_file'] = op.abspath(self.inputs.out_file)
135+
outputs['wm_odf'] = op.abspath(self.inputs.wm_odf)
136+
if self.inputs.gm_odf != Undefined:
137+
outputs['gm_odf'] = op.abspath(self.inputs.gm_odf)
138+
if self.inputs.csf_odf != Undefined:
139+
outputs['csf_odf'] = op.abspath(self.inputs.csf_odf)
189140
return outputs
141+
142+
143+
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT
2+
from __future__ import unicode_literals
3+
from ..utils import DWIExtract
4+
5+
6+
def test_DWIExtract_inputs():
7+
input_map = dict(args=dict(argstr='%s',
8+
),
9+
bval_scale=dict(argstr='-bvalue_scaling %s',
10+
),
11+
bzero=dict(argstr='-bzero',
12+
),
13+
environ=dict(nohash=True,
14+
usedefault=True,
15+
),
16+
grad_file=dict(argstr='-grad %s',
17+
),
18+
grad_fsl=dict(argstr='-fslgrad %s %s',
19+
),
20+
ignore_exception=dict(deprecated='1.0.0',
21+
nohash=True,
22+
usedefault=True,
23+
),
24+
in_bval=dict(),
25+
in_bvec=dict(argstr='-fslgrad %s %s',
26+
),
27+
in_file=dict(argstr='%s',
28+
mandatory=True,
29+
position=-2,
30+
),
31+
nobzero=dict(argstr='-nobzero',
32+
),
33+
nthreads=dict(argstr='-nthreads %d',
34+
nohash=True,
35+
),
36+
out_file=dict(argstr='%s',
37+
mandatory=True,
38+
position=-1,
39+
),
40+
shell=dict(argstr='-shell %s',
41+
sep=',',
42+
),
43+
singleshell=dict(argstr='-singleshell',
44+
),
45+
terminal_output=dict(deprecated='1.0.0',
46+
nohash=True,
47+
),
48+
)
49+
inputs = DWIExtract.input_spec()
50+
51+
for key, metadata in list(input_map.items()):
52+
for metakey, value in list(metadata.items()):
53+
assert getattr(inputs.traits()[key], metakey) == value
54+
55+
56+
def test_DWIExtract_outputs():
57+
output_map = dict(out_file=dict(),
58+
)
59+
outputs = DWIExtract.output_spec()
60+
61+
for key, metadata in list(output_map.items()):
62+
for metakey, value in list(metadata.items()):
63+
assert getattr(outputs.traits()[key], metakey) == value

0 commit comments

Comments
 (0)