From febf69ddda7052fccafe5336452e1e767b692117 Mon Sep 17 00:00:00 2001 From: Chris Gorgolewski Date: Wed, 22 Feb 2017 22:39:06 -0800 Subject: [PATCH 1/4] adedd support for motion parameters produced by AFNI --- nipype/algorithms/confounds.py | 7 +++++-- nipype/algorithms/tests/test_confounds.py | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/nipype/algorithms/confounds.py b/nipype/algorithms/confounds.py index c6503c703a..a42e4c903c 100644 --- a/nipype/algorithms/confounds.py +++ b/nipype/algorithms/confounds.py @@ -195,7 +195,8 @@ def _list_outputs(self): class FramewiseDisplacementInputSpec(BaseInterfaceInputSpec): - in_plots = File(exists=True, mandatory=True, desc='motion parameters as written by FSL MCFLIRT') + in_file = File(exists=True, mandatory=True, desc='motion parameters as written by FSL MCFLIRT or AFNI 3dvolreg') + format = traits.Enum("FSL", "AFNI", desc="Format of the motion parameters file: FSL (radians), AFNI (degrees)") radius = traits.Float(50, usedefault=True, desc='radius in mm to calculate angular FDs, 50mm is the ' 'default since it is used in Power et al. 2012') @@ -249,9 +250,11 @@ class FramewiseDisplacement(BaseInterface): }] def _run_interface(self, runtime): - mpars = np.loadtxt(self.inputs.in_plots) # mpars is N_t x 6 + mpars = np.loadtxt(self.inputs.in_file) # mpars is N_t x 6 diff = mpars[:-1, :] - mpars[1:, :] diff[:, :3] *= self.inputs.radius + if self.inputs.format == "AFNI": + diff[:, :3] *= (np.pi / 180) fd_res = np.abs(diff).sum(axis=1) self._results = { diff --git a/nipype/algorithms/tests/test_confounds.py b/nipype/algorithms/tests/test_confounds.py index a1d6ec9557..275be00769 100644 --- a/nipype/algorithms/tests/test_confounds.py +++ b/nipype/algorithms/tests/test_confounds.py @@ -21,8 +21,9 @@ def test_fd(tmpdir): tempdir = str(tmpdir) ground_truth = np.loadtxt(example_data('fsl_motion_outliers_fd.txt')) - fdisplacement = FramewiseDisplacement(in_plots=example_data('fsl_mcflirt_movpar.txt'), - out_file=tempdir + '/fd.txt') + fdisplacement = FramewiseDisplacement(in_file=example_data('fsl_mcflirt_movpar.txt'), + out_file=tempdir + '/fd.txt', + format="FSL") res = fdisplacement.run() with open(res.outputs.out_file) as all_lines: From 7e8d8713e463ddff2b3108d4af6c3925afe7e2a7 Mon Sep 17 00:00:00 2001 From: Chris Gorgolewski Date: Thu, 23 Feb 2017 09:03:54 -0800 Subject: [PATCH 2/4] fix auto tests --- nipype/algorithms/tests/test_auto_FramewiseDisplacement.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nipype/algorithms/tests/test_auto_FramewiseDisplacement.py b/nipype/algorithms/tests/test_auto_FramewiseDisplacement.py index bd4afa89d0..c5eae5fb7d 100644 --- a/nipype/algorithms/tests/test_auto_FramewiseDisplacement.py +++ b/nipype/algorithms/tests/test_auto_FramewiseDisplacement.py @@ -10,7 +10,7 @@ def test_FramewiseDisplacement_inputs(): ignore_exception=dict(nohash=True, usedefault=True, ), - in_plots=dict(mandatory=True, + in_file=dict(mandatory=True, ), normalize=dict(usedefault=True, ), From 2e2dac148110eb1f16f41ff2341d6dfe68c6d10c Mon Sep 17 00:00:00 2001 From: Chris Gorgolewski Date: Thu, 23 Feb 2017 09:13:44 -0800 Subject: [PATCH 3/4] rename inputs to conform with RapidArt --- nipype/algorithms/confounds.py | 6 ++++-- nipype/algorithms/tests/test_confounds.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/nipype/algorithms/confounds.py b/nipype/algorithms/confounds.py index a42e4c903c..1f4a07f3df 100644 --- a/nipype/algorithms/confounds.py +++ b/nipype/algorithms/confounds.py @@ -196,7 +196,9 @@ def _list_outputs(self): class FramewiseDisplacementInputSpec(BaseInterfaceInputSpec): in_file = File(exists=True, mandatory=True, desc='motion parameters as written by FSL MCFLIRT or AFNI 3dvolreg') - format = traits.Enum("FSL", "AFNI", desc="Format of the motion parameters file: FSL (radians), AFNI (degrees)") + parameter_source = traits.Enum("FSL", "AFNI", + desc="Source of movement parameters", + mandatory=True) radius = traits.Float(50, usedefault=True, desc='radius in mm to calculate angular FDs, 50mm is the ' 'default since it is used in Power et al. 2012') @@ -253,7 +255,7 @@ def _run_interface(self, runtime): mpars = np.loadtxt(self.inputs.in_file) # mpars is N_t x 6 diff = mpars[:-1, :] - mpars[1:, :] diff[:, :3] *= self.inputs.radius - if self.inputs.format == "AFNI": + if self.inputs.parameter_source == "AFNI": diff[:, :3] *= (np.pi / 180) fd_res = np.abs(diff).sum(axis=1) diff --git a/nipype/algorithms/tests/test_confounds.py b/nipype/algorithms/tests/test_confounds.py index 275be00769..f2fadf5cb0 100644 --- a/nipype/algorithms/tests/test_confounds.py +++ b/nipype/algorithms/tests/test_confounds.py @@ -23,7 +23,7 @@ def test_fd(tmpdir): ground_truth = np.loadtxt(example_data('fsl_motion_outliers_fd.txt')) fdisplacement = FramewiseDisplacement(in_file=example_data('fsl_mcflirt_movpar.txt'), out_file=tempdir + '/fd.txt', - format="FSL") + parameter_source="FSL") res = fdisplacement.run() with open(res.outputs.out_file) as all_lines: From ff2160ad4534a1711625c016e1feef034b8ec581 Mon Sep 17 00:00:00 2001 From: Chris Gorgolewski Date: Fri, 24 Feb 2017 11:17:17 -0800 Subject: [PATCH 4/4] refactor --- nipype/algorithms/confounds.py | 16 +++++++++------- nipype/algorithms/rapidart.py | 9 +++------ nipype/utils/misc.py | 12 ++++++++++++ 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/nipype/algorithms/confounds.py b/nipype/algorithms/confounds.py index 1f4a07f3df..d369fec9be 100644 --- a/nipype/algorithms/confounds.py +++ b/nipype/algorithms/confounds.py @@ -27,7 +27,8 @@ from ..interfaces.base import (traits, TraitedSpec, BaseInterface, BaseInterfaceInputSpec, File, isdefined, InputMultiPath) -from nipype.utils import NUMPY_MMAP +from ..utils import NUMPY_MMAP +from ..utils.misc import normalize_mc_params IFLOG = logging.getLogger('interface') @@ -195,8 +196,8 @@ def _list_outputs(self): class FramewiseDisplacementInputSpec(BaseInterfaceInputSpec): - in_file = File(exists=True, mandatory=True, desc='motion parameters as written by FSL MCFLIRT or AFNI 3dvolreg') - parameter_source = traits.Enum("FSL", "AFNI", + in_file = File(exists=True, mandatory=True, desc='motion parameters') + parameter_source = traits.Enum("FSL", "AFNI", "SPM", "FSFAST", desc="Source of movement parameters", mandatory=True) radius = traits.Float(50, usedefault=True, @@ -253,10 +254,11 @@ class FramewiseDisplacement(BaseInterface): def _run_interface(self, runtime): mpars = np.loadtxt(self.inputs.in_file) # mpars is N_t x 6 - diff = mpars[:-1, :] - mpars[1:, :] - diff[:, :3] *= self.inputs.radius - if self.inputs.parameter_source == "AFNI": - diff[:, :3] *= (np.pi / 180) + mpars = np.apply_along_axis(func1d=normalize_mc_params, + axis=1, arr=mpars, + source=self.inputs.parameter_source) + diff = mpars[:-1, :6] - mpars[1:, :6] + diff[:, 3:6] *= self.inputs.radius fd_res = np.abs(diff).sum(axis=1) self._results = { diff --git a/nipype/algorithms/rapidart.py b/nipype/algorithms/rapidart.py index 3b3295eab0..b8aca927b6 100644 --- a/nipype/algorithms/rapidart.py +++ b/nipype/algorithms/rapidart.py @@ -34,7 +34,7 @@ OutputMultiPath, TraitedSpec, File, BaseInterfaceInputSpec, isdefined) from ..utils.filemanip import filename_to_list, save_json, split_filename -from ..utils.misc import find_indices +from ..utils.misc import find_indices, normalize_mc_params from .. import logging, config iflogger = logging.getLogger('interface') @@ -46,15 +46,12 @@ def _get_affine_matrix(params, source): source : the package that generated the parameters supports SPM, AFNI, FSFAST, FSL, NIPY """ - if source == 'FSL': - params = params[[3, 4, 5, 0, 1, 2]] - elif source in ('AFNI', 'FSFAST'): - params = params[np.asarray([4, 5, 3, 1, 2, 0]) + (len(params) > 6)] - params[3:] = params[3:] * np.pi / 180. if source == 'NIPY': # nipy does not store typical euler angles, use nipy to convert from nipy.algorithms.registration import to_matrix44 return to_matrix44(params) + + params = normalize_mc_params(params, source) # process for FSL, SPM, AFNI and FSFAST rotfunc = lambda x: np.array([[np.cos(x), np.sin(x)], [-np.sin(x), np.cos(x)]]) diff --git a/nipype/utils/misc.py b/nipype/utils/misc.py index f01af7a02e..ea99daca36 100644 --- a/nipype/utils/misc.py +++ b/nipype/utils/misc.py @@ -243,3 +243,15 @@ def unflatten(in_list, prev_structure): for item in prev_structure: out.append(unflatten(in_list, item)) return out + + +def normalize_mc_params(params, source): + """ + Normalize a single row of motion parameters to the SPM format. + """ + if source == 'FSL': + params = params[[3, 4, 5, 0, 1, 2]] + elif source in ('AFNI', 'FSFAST'): + params = params[np.asarray([4, 5, 3, 1, 2, 0]) + (len(params) > 6)] + params[3:] = params[3:] * np.pi / 180. + return params \ No newline at end of file