From f4906165bd65ddbf87cfac02fd24fced6d4509ce Mon Sep 17 00:00:00 2001 From: Matteo Mancini Date: Mon, 19 Aug 2019 15:13:50 -0400 Subject: [PATCH 1/9] Fixed issue with CSD estimation in MRtrix3 --- nipype/interfaces/mrtrix3/reconst.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nipype/interfaces/mrtrix3/reconst.py b/nipype/interfaces/mrtrix3/reconst.py index 4e2db8de3f..5dede1244a 100644 --- a/nipype/interfaces/mrtrix3/reconst.py +++ b/nipype/interfaces/mrtrix3/reconst.py @@ -102,10 +102,10 @@ class EstimateFODInputSpec(MRTrix3BaseInputSpec): mandatory=True, desc='output WM ODF') gm_txt = File(argstr='%s', position=-4, desc='GM response text file') - gm_odf = File('gm.mif', usedefault=True, argstr='%s', + gm_odf = File('gm.mif', usedefault=False, argstr='%s', position=-3, desc='output GM ODF') csf_txt = File(argstr='%s', position=-2, desc='CSF response text file') - csf_odf = File('csf.mif', usedefault=True, argstr='%s', + csf_odf = File('csf.mif', usedefault=False, argstr='%s', position=-1, desc='output CSF ODF') mask_file = File(exists=True, argstr='-mask %s', desc='mask image') From 4bca4a44614d11663659e6b81508932b693cb8b8 Mon Sep 17 00:00:00 2001 From: Matteo Mancini Date: Mon, 19 Aug 2019 15:22:11 -0400 Subject: [PATCH 2/9] Removing forced optional argument in MRtrix3 --- nipype/interfaces/mrtrix3/reconst.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nipype/interfaces/mrtrix3/reconst.py b/nipype/interfaces/mrtrix3/reconst.py index 5dede1244a..cdb0dd0a46 100644 --- a/nipype/interfaces/mrtrix3/reconst.py +++ b/nipype/interfaces/mrtrix3/reconst.py @@ -118,7 +118,6 @@ class EstimateFODInputSpec(MRTrix3BaseInputSpec): max_sh = InputMultiObject( traits.Int, value=[8], - usedefault=True, argstr='-lmax %s', sep=',', desc=('maximum harmonic degree of response function - single value for single-shell response, list for multi-shell response')) From 9c5dc6ec2efb80a6fe834aadb8cafa557029ee68 Mon Sep 17 00:00:00 2001 From: Matteo Mancini Date: Mon, 19 Aug 2019 16:15:46 -0400 Subject: [PATCH 3/9] Fix to make test pass --- nipype/interfaces/mrtrix3/reconst.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nipype/interfaces/mrtrix3/reconst.py b/nipype/interfaces/mrtrix3/reconst.py index cdb0dd0a46..6deb4c22b8 100644 --- a/nipype/interfaces/mrtrix3/reconst.py +++ b/nipype/interfaces/mrtrix3/reconst.py @@ -150,7 +150,7 @@ class EstimateFOD(MRTrix3Base): >>> fod.inputs.wm_txt = 'wm.txt' >>> fod.inputs.grad_fsl = ('bvecs', 'bvals') >>> fod.cmdline # doctest: +ELLIPSIS - 'dwi2fod -fslgrad bvecs bvals -lmax 8 csd dwi.mif wm.txt wm.mif gm.mif csf.mif' + 'dwi2fod -fslgrad bvecs bvals csd dwi.mif wm.txt wm.mif' >>> fod.run() # doctest: +SKIP """ From ba66142b92d973458f8da21ec69b0fd3ddb26bd0 Mon Sep 17 00:00:00 2001 From: Matteo Mancini Date: Tue, 3 Sep 2019 11:36:41 -0400 Subject: [PATCH 4/9] Update nipype/interfaces/mrtrix3/reconst.py Co-Authored-By: Chris Markiewicz --- nipype/interfaces/mrtrix3/reconst.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nipype/interfaces/mrtrix3/reconst.py b/nipype/interfaces/mrtrix3/reconst.py index 6deb4c22b8..55c2a82a30 100644 --- a/nipype/interfaces/mrtrix3/reconst.py +++ b/nipype/interfaces/mrtrix3/reconst.py @@ -117,7 +117,6 @@ class EstimateFODInputSpec(MRTrix3BaseInputSpec): desc='specify one or more dw gradient shells') max_sh = InputMultiObject( traits.Int, - value=[8], argstr='-lmax %s', sep=',', desc=('maximum harmonic degree of response function - single value for single-shell response, list for multi-shell response')) From ef4050476ee00cb80f9c5ed635f1d3baba0d2841 Mon Sep 17 00:00:00 2001 From: Matteo Mancini Date: Tue, 3 Sep 2019 11:36:59 -0400 Subject: [PATCH 5/9] Update nipype/interfaces/mrtrix3/reconst.py Co-Authored-By: Chris Markiewicz --- nipype/interfaces/mrtrix3/reconst.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nipype/interfaces/mrtrix3/reconst.py b/nipype/interfaces/mrtrix3/reconst.py index 55c2a82a30..a87bc9aab0 100644 --- a/nipype/interfaces/mrtrix3/reconst.py +++ b/nipype/interfaces/mrtrix3/reconst.py @@ -105,7 +105,7 @@ class EstimateFODInputSpec(MRTrix3BaseInputSpec): gm_odf = File('gm.mif', usedefault=False, argstr='%s', position=-3, desc='output GM ODF') csf_txt = File(argstr='%s', position=-2, desc='CSF response text file') - csf_odf = File('csf.mif', usedefault=False, argstr='%s', + csf_odf = File(argstr='%s', position=-1, desc='output CSF ODF') mask_file = File(exists=True, argstr='-mask %s', desc='mask image') From be919a58c9e09031ba12b3a9441e4d3c2806266e Mon Sep 17 00:00:00 2001 From: Matteo Mancini Date: Tue, 3 Sep 2019 11:37:08 -0400 Subject: [PATCH 6/9] Update nipype/interfaces/mrtrix3/reconst.py Co-Authored-By: Chris Markiewicz --- nipype/interfaces/mrtrix3/reconst.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nipype/interfaces/mrtrix3/reconst.py b/nipype/interfaces/mrtrix3/reconst.py index a87bc9aab0..6487e61d38 100644 --- a/nipype/interfaces/mrtrix3/reconst.py +++ b/nipype/interfaces/mrtrix3/reconst.py @@ -102,7 +102,7 @@ class EstimateFODInputSpec(MRTrix3BaseInputSpec): mandatory=True, desc='output WM ODF') gm_txt = File(argstr='%s', position=-4, desc='GM response text file') - gm_odf = File('gm.mif', usedefault=False, argstr='%s', + gm_odf = File(argstr='%s', position=-3, desc='output GM ODF') csf_txt = File(argstr='%s', position=-2, desc='CSF response text file') csf_odf = File(argstr='%s', From d431f6635381984e2c05189c36522582374de9b0 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Sun, 23 Feb 2020 12:23:58 -0500 Subject: [PATCH 7/9] RF: Add new interface to correctly handle CSD --- nipype/interfaces/mrtrix3/reconst.py | 67 +++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/nipype/interfaces/mrtrix3/reconst.py b/nipype/interfaces/mrtrix3/reconst.py index 551dd3ed5e..3f21e9ad54 100644 --- a/nipype/interfaces/mrtrix3/reconst.py +++ b/nipype/interfaces/mrtrix3/reconst.py @@ -108,9 +108,13 @@ class EstimateFODInputSpec(MRTrix3BaseInputSpec): desc="output WM ODF", ) gm_txt = File(argstr="%s", position=-4, desc="GM response text file") - gm_odf = File(argstr="%s", position=-3, desc="output GM ODF") + gm_odf = File( + "gm.mif", usedefault=True, argstr="%s", position=-3, desc="output GM ODF" + ) csf_txt = File(argstr="%s", position=-2, desc="CSF response text file") - csf_odf = File(argstr="%s", position=-1, desc="output CSF ODF") + csf_odf = File( + "csf.mif", usedefault=True, argstr="%s", position=-1, desc="output CSF ODF" + ) mask_file = File(exists=True, argstr="-mask %s", desc="mask image") # DW Shell selection options @@ -122,6 +126,8 @@ class EstimateFODInputSpec(MRTrix3BaseInputSpec): ) max_sh = InputMultiObject( traits.Int, + value=[8], + usedefault=True, argstr="-lmax %s", sep=",", desc=( @@ -150,18 +156,24 @@ class EstimateFOD(MRTrix3Base): """ Estimate fibre orientation distributions from diffusion data using spherical deconvolution + .. warning:: + + The CSD algorithm does not work as intended, but fixing it in this interface could break + existing workflows. This interface has been superseded by + :py:class:`.ConstrainedSphericalDecomposition`. + Example ------- >>> import nipype.interfaces.mrtrix3 as mrt >>> fod = mrt.EstimateFOD() - >>> fod.inputs.algorithm = 'csd' + >>> fod.inputs.algorithm = 'msmt_csd' >>> fod.inputs.in_file = 'dwi.mif' >>> fod.inputs.wm_txt = 'wm.txt' >>> fod.inputs.grad_fsl = ('bvecs', 'bvals') - >>> fod.cmdline # doctest: +ELLIPSIS - 'dwi2fod -fslgrad bvecs bvals csd dwi.mif wm.txt wm.mif' - >>> fod.run() # doctest: +SKIP + >>> fod.cmdline + 'dwi2fod -fslgrad bvecs bvals -lmax 8 msmt_csd dwi.mif wm.txt wm.mif gm.mif csf.mif' + >>> fod.run() # doctest: +SKIP """ _cmd = "dwi2fod" @@ -176,3 +188,46 @@ def _list_outputs(self): if self.inputs.csf_odf != Undefined: outputs["csf_odf"] = op.abspath(self.inputs.csf_odf) return outputs + + +class ConstrainedSphericalDeconvolutionInputSpec(EstimateFODInputSpec): + gm_odf = File(argstr="%s", position=-3, desc="output GM ODF") + csf_odf = File(argstr="%s", position=-1, desc="output CSF ODF") + max_sh = InputMultiObject( + traits.Int, + argstr="-lmax %s", + sep=",", + desc=( + "maximum harmonic degree of response function - single value for single-shell response, list for multi-shell response" + ), + ) + + +class ConstrainedSphericalDeconvolution(EstimateFOD): + """ + Estimate fibre orientation distributions from diffusion data using spherical deconvolution + + This interface supersedes :py:class:`.EstimateFOD`. + The old interface has contained a bug when using the CSD algorithm as opposed to the MSMT CSD + algorithm, but fixing it could potentially break existing workflows. The new interface works + the same, but does not populate the following inputs by default: + + * ``gm_odf`` + * ``csf_odf`` + * ``max_sh`` + + Example + ------- + + >>> import nipype.interfaces.mrtrix3 as mrt + >>> fod = mrt.ConstrainedSphericalDeconvolution() + >>> fod.inputs.algorithm = 'csd' + >>> fod.inputs.in_file = 'dwi.mif' + >>> fod.inputs.wm_txt = 'wm.txt' + >>> fod.inputs.grad_fsl = ('bvecs', 'bvals') + >>> fod.cmdline + 'dwi2fod -fslgrad bvecs bvals csd dwi.mif wm.txt wm.mif' + >>> fod.run() # doctest: +SKIP + """ + + input_spec = ConstrainedSphericalDeconvolutionInputSpec From 6c0967e9dbe41436adb12e086ee0c05be368b88b Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Sun, 23 Feb 2020 12:36:58 -0500 Subject: [PATCH 8/9] make specs --- ..._auto_ConstrainedSphericalDeconvolution.py | 47 +++++++++++++++++++ tools/checkspecs.py | 2 +- 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 nipype/interfaces/mrtrix3/tests/test_auto_ConstrainedSphericalDeconvolution.py diff --git a/nipype/interfaces/mrtrix3/tests/test_auto_ConstrainedSphericalDeconvolution.py b/nipype/interfaces/mrtrix3/tests/test_auto_ConstrainedSphericalDeconvolution.py new file mode 100644 index 0000000000..9bcba81e49 --- /dev/null +++ b/nipype/interfaces/mrtrix3/tests/test_auto_ConstrainedSphericalDeconvolution.py @@ -0,0 +1,47 @@ +# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT +from ..reconst import ConstrainedSphericalDeconvolution + + +def test_ConstrainedSphericalDeconvolution_inputs(): + input_map = dict( + algorithm=dict(argstr="%s", mandatory=True, position=-8,), + args=dict(argstr="%s",), + bval_scale=dict(argstr="-bvalue_scaling %s",), + csf_odf=dict(argstr="%s", extensions=None, position=-1,), + csf_txt=dict(argstr="%s", extensions=None, position=-2,), + environ=dict(nohash=True, usedefault=True,), + gm_odf=dict(argstr="%s", extensions=None, position=-3,), + gm_txt=dict(argstr="%s", extensions=None, position=-4,), + grad_file=dict(argstr="-grad %s", extensions=None, xor=["grad_fsl"],), + grad_fsl=dict(argstr="-fslgrad %s %s", xor=["grad_file"],), + in_bval=dict(extensions=None,), + in_bvec=dict(argstr="-fslgrad %s %s", extensions=None,), + in_dirs=dict(argstr="-directions %s", extensions=None,), + in_file=dict(argstr="%s", extensions=None, mandatory=True, position=-7,), + mask_file=dict(argstr="-mask %s", extensions=None,), + max_sh=dict(argstr="-lmax %s", sep=",",), + nthreads=dict(argstr="-nthreads %d", nohash=True,), + shell=dict(argstr="-shell %s", sep=",",), + wm_odf=dict( + argstr="%s", extensions=None, mandatory=True, position=-5, usedefault=True, + ), + wm_txt=dict(argstr="%s", extensions=None, mandatory=True, position=-6,), + ) + inputs = ConstrainedSphericalDeconvolution.input_spec() + + for key, metadata in list(input_map.items()): + for metakey, value in list(metadata.items()): + assert getattr(inputs.traits()[key], metakey) == value + + +def test_ConstrainedSphericalDeconvolution_outputs(): + output_map = dict( + csf_odf=dict(argstr="%s", extensions=None,), + gm_odf=dict(argstr="%s", extensions=None,), + wm_odf=dict(argstr="%s", extensions=None,), + ) + outputs = ConstrainedSphericalDeconvolution.output_spec() + + for key, metadata in list(output_map.items()): + for metakey, value in list(metadata.items()): + assert getattr(outputs.traits()[key], metakey) == value diff --git a/tools/checkspecs.py b/tools/checkspecs.py index a4707bd375..e06f862338 100644 --- a/tools/checkspecs.py +++ b/tools/checkspecs.py @@ -324,7 +324,7 @@ def test_specs(self, uri): and "xor" not in trait.__dict__ ): if ( - trait.trait_type.__class__.__name__ is "Range" + trait.trait_type.__class__.__name__ == "Range" and trait.default == trait.trait_type._low ): continue From 7f68dda982be0b5f4e805a5f10db1b24ff47ca21 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Sun, 23 Feb 2020 19:48:07 -0500 Subject: [PATCH 9/9] ENH: Import ConstrainedSphericalDeconvolution into interfaces.mrtrix3 --- nipype/interfaces/mrtrix3/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nipype/interfaces/mrtrix3/__init__.py b/nipype/interfaces/mrtrix3/__init__.py index 2970918844..f60e837310 100644 --- a/nipype/interfaces/mrtrix3/__init__.py +++ b/nipype/interfaces/mrtrix3/__init__.py @@ -23,5 +23,5 @@ DWIBiasCorrect, ) from .tracking import Tractography -from .reconst import FitTensor, EstimateFOD +from .reconst import FitTensor, EstimateFOD, ConstrainedSphericalDeconvolution from .connectivity import LabelConfig, LabelConvert, BuildConnectome