Skip to content

Commit 9049156

Browse files
authored
Merge pull request #3176 from effigies/mrtrix3-usedefault-issue
ENH: Add ``ConstrainedSphericalDeconvolution`` interface to replace ``EstimateFOD`` for MRtrix3's ``dwi2fod``
2 parents 07c840b + 7f68dda commit 9049156

File tree

4 files changed

+102
-6
lines changed

4 files changed

+102
-6
lines changed

Diff for: nipype/interfaces/mrtrix3/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,5 @@
2323
DWIBiasCorrect,
2424
)
2525
from .tracking import Tractography
26-
from .reconst import FitTensor, EstimateFOD
26+
from .reconst import FitTensor, EstimateFOD, ConstrainedSphericalDeconvolution
2727
from .connectivity import LabelConfig, LabelConvert, BuildConnectome

Diff for: nipype/interfaces/mrtrix3/reconst.py

+53-4
Original file line numberDiff line numberDiff line change
@@ -156,18 +156,24 @@ class EstimateFOD(MRTrix3Base):
156156
"""
157157
Estimate fibre orientation distributions from diffusion data using spherical deconvolution
158158
159+
.. warning::
160+
161+
The CSD algorithm does not work as intended, but fixing it in this interface could break
162+
existing workflows. This interface has been superseded by
163+
:py:class:`.ConstrainedSphericalDecomposition`.
164+
159165
Example
160166
-------
161167
162168
>>> import nipype.interfaces.mrtrix3 as mrt
163169
>>> fod = mrt.EstimateFOD()
164-
>>> fod.inputs.algorithm = 'csd'
170+
>>> fod.inputs.algorithm = 'msmt_csd'
165171
>>> fod.inputs.in_file = 'dwi.mif'
166172
>>> fod.inputs.wm_txt = 'wm.txt'
167173
>>> fod.inputs.grad_fsl = ('bvecs', 'bvals')
168-
>>> fod.cmdline # doctest: +ELLIPSIS
169-
'dwi2fod -fslgrad bvecs bvals -lmax 8 csd dwi.mif wm.txt wm.mif gm.mif csf.mif'
170-
>>> fod.run() # doctest: +SKIP
174+
>>> fod.cmdline
175+
'dwi2fod -fslgrad bvecs bvals -lmax 8 msmt_csd dwi.mif wm.txt wm.mif gm.mif csf.mif'
176+
>>> fod.run() # doctest: +SKIP
171177
"""
172178

173179
_cmd = "dwi2fod"
@@ -182,3 +188,46 @@ def _list_outputs(self):
182188
if self.inputs.csf_odf != Undefined:
183189
outputs["csf_odf"] = op.abspath(self.inputs.csf_odf)
184190
return outputs
191+
192+
193+
class ConstrainedSphericalDeconvolutionInputSpec(EstimateFODInputSpec):
194+
gm_odf = File(argstr="%s", position=-3, desc="output GM ODF")
195+
csf_odf = File(argstr="%s", position=-1, desc="output CSF ODF")
196+
max_sh = InputMultiObject(
197+
traits.Int,
198+
argstr="-lmax %s",
199+
sep=",",
200+
desc=(
201+
"maximum harmonic degree of response function - single value for single-shell response, list for multi-shell response"
202+
),
203+
)
204+
205+
206+
class ConstrainedSphericalDeconvolution(EstimateFOD):
207+
"""
208+
Estimate fibre orientation distributions from diffusion data using spherical deconvolution
209+
210+
This interface supersedes :py:class:`.EstimateFOD`.
211+
The old interface has contained a bug when using the CSD algorithm as opposed to the MSMT CSD
212+
algorithm, but fixing it could potentially break existing workflows. The new interface works
213+
the same, but does not populate the following inputs by default:
214+
215+
* ``gm_odf``
216+
* ``csf_odf``
217+
* ``max_sh``
218+
219+
Example
220+
-------
221+
222+
>>> import nipype.interfaces.mrtrix3 as mrt
223+
>>> fod = mrt.ConstrainedSphericalDeconvolution()
224+
>>> fod.inputs.algorithm = 'csd'
225+
>>> fod.inputs.in_file = 'dwi.mif'
226+
>>> fod.inputs.wm_txt = 'wm.txt'
227+
>>> fod.inputs.grad_fsl = ('bvecs', 'bvals')
228+
>>> fod.cmdline
229+
'dwi2fod -fslgrad bvecs bvals csd dwi.mif wm.txt wm.mif'
230+
>>> fod.run() # doctest: +SKIP
231+
"""
232+
233+
input_spec = ConstrainedSphericalDeconvolutionInputSpec
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT
2+
from ..reconst import ConstrainedSphericalDeconvolution
3+
4+
5+
def test_ConstrainedSphericalDeconvolution_inputs():
6+
input_map = dict(
7+
algorithm=dict(argstr="%s", mandatory=True, position=-8,),
8+
args=dict(argstr="%s",),
9+
bval_scale=dict(argstr="-bvalue_scaling %s",),
10+
csf_odf=dict(argstr="%s", extensions=None, position=-1,),
11+
csf_txt=dict(argstr="%s", extensions=None, position=-2,),
12+
environ=dict(nohash=True, usedefault=True,),
13+
gm_odf=dict(argstr="%s", extensions=None, position=-3,),
14+
gm_txt=dict(argstr="%s", extensions=None, position=-4,),
15+
grad_file=dict(argstr="-grad %s", extensions=None, xor=["grad_fsl"],),
16+
grad_fsl=dict(argstr="-fslgrad %s %s", xor=["grad_file"],),
17+
in_bval=dict(extensions=None,),
18+
in_bvec=dict(argstr="-fslgrad %s %s", extensions=None,),
19+
in_dirs=dict(argstr="-directions %s", extensions=None,),
20+
in_file=dict(argstr="%s", extensions=None, mandatory=True, position=-7,),
21+
mask_file=dict(argstr="-mask %s", extensions=None,),
22+
max_sh=dict(argstr="-lmax %s", sep=",",),
23+
nthreads=dict(argstr="-nthreads %d", nohash=True,),
24+
shell=dict(argstr="-shell %s", sep=",",),
25+
wm_odf=dict(
26+
argstr="%s", extensions=None, mandatory=True, position=-5, usedefault=True,
27+
),
28+
wm_txt=dict(argstr="%s", extensions=None, mandatory=True, position=-6,),
29+
)
30+
inputs = ConstrainedSphericalDeconvolution.input_spec()
31+
32+
for key, metadata in list(input_map.items()):
33+
for metakey, value in list(metadata.items()):
34+
assert getattr(inputs.traits()[key], metakey) == value
35+
36+
37+
def test_ConstrainedSphericalDeconvolution_outputs():
38+
output_map = dict(
39+
csf_odf=dict(argstr="%s", extensions=None,),
40+
gm_odf=dict(argstr="%s", extensions=None,),
41+
wm_odf=dict(argstr="%s", extensions=None,),
42+
)
43+
outputs = ConstrainedSphericalDeconvolution.output_spec()
44+
45+
for key, metadata in list(output_map.items()):
46+
for metakey, value in list(metadata.items()):
47+
assert getattr(outputs.traits()[key], metakey) == value

Diff for: tools/checkspecs.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ def test_specs(self, uri):
324324
and "xor" not in trait.__dict__
325325
):
326326
if (
327-
trait.trait_type.__class__.__name__ is "Range"
327+
trait.trait_type.__class__.__name__ == "Range"
328328
and trait.default == trait.trait_type._low
329329
):
330330
continue

0 commit comments

Comments
 (0)