diff --git a/nipype/interfaces/freesurfer/__init__.py b/nipype/interfaces/freesurfer/__init__.py index 54fe22429d..7f72ff3f6c 100644 --- a/nipype/interfaces/freesurfer/__init__.py +++ b/nipype/interfaces/freesurfer/__init__.py @@ -21,7 +21,7 @@ MRIFill, MRIsInflate, Sphere, FixTopology, EulerNumber, RemoveIntersection, MakeSurfaces, Curvature, CurvatureStats, Jacobian, MRIsCalc, VolumeMask, ParcellationStats, Contrast, - RelabelHypointensities, Aparc2Aseg, Apas2Aseg, MRIsExpand) + RelabelHypointensities, Aparc2Aseg, Apas2Aseg, MRIsExpand, MRIsCombine) from .longitudinal import (RobustTemplate, FuseSegmentations) from .registration import (MPRtoMNI305, RegisterAVItoTalairach, EMRegister, Register, Paint) diff --git a/nipype/interfaces/freesurfer/base.py b/nipype/interfaces/freesurfer/base.py index 4a51f92383..c4ef6f7192 100644 --- a/nipype/interfaces/freesurfer/base.py +++ b/nipype/interfaces/freesurfer/base.py @@ -162,8 +162,44 @@ def version(self): return ver.rstrip().split('-v')[-1] +class FSSurfaceCommand(FSCommand): + """Support for FreeSurfer surface-related functions. + For some functions, if the output file is not specified starting with + 'lh.' or 'rh.', FreeSurfer prepends the prefix from the input file to the + output filename. Output out_file must be adjusted to accommodate this. + By including the full path in the filename, we can also avoid this behavior. + """ + def _get_filecopy_info(self): + self._normalize_filenames() + return super(FSSurfaceCommand, self)._get_filecopy_info() + + def _normalize_filenames(self): + """Filename normalization routine to perform only when run in Node + context + """ + pass + + @staticmethod + def _associated_file(in_file, out_name): + """Based on MRIsBuildFileName in freesurfer/utils/mrisurf.c + + Use in_file prefix to indicate hemisphere for out_name, rather than + inspecting the surface data structure. + Also, output to in_file's directory if path information not provided + for out_name. + """ + path, base = os.path.split(out_name) + if path == '': + path, in_file = os.path.split(in_file) + hemis = ('lh.', 'rh.') + if in_file[:3] in hemis and base[:3] not in hemis: + base = in_file[:3] + base + return os.path.abspath(os.path.join(path, base)) + + class FSScriptCommand(FSCommand): - """ Support for Freesurfer script commands with log inputs.terminal_output """ + """ Support for Freesurfer script commands with log inputs.terminal_output + """ _terminal_output = 'file' _always_run = False diff --git a/nipype/interfaces/freesurfer/tests/test_auto_FSSurfaceCommand.py b/nipype/interfaces/freesurfer/tests/test_auto_FSSurfaceCommand.py new file mode 100644 index 0000000000..0c3fcc8984 --- /dev/null +++ b/nipype/interfaces/freesurfer/tests/test_auto_FSSurfaceCommand.py @@ -0,0 +1,24 @@ +# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT +from __future__ import unicode_literals +from ..base import FSSurfaceCommand + + +def test_FSSurfaceCommand_inputs(): + input_map = dict(args=dict(argstr='%s', + ), + environ=dict(nohash=True, + usedefault=True, + ), + ignore_exception=dict(nohash=True, + usedefault=True, + ), + subjects_dir=dict(), + terminal_output=dict(nohash=True, + ), + ) + inputs = FSSurfaceCommand.input_spec() + + for key, metadata in list(input_map.items()): + for metakey, value in list(metadata.items()): + assert getattr(inputs.traits()[key], metakey) == value + diff --git a/nipype/interfaces/freesurfer/tests/test_auto_MRIsCombine.py b/nipype/interfaces/freesurfer/tests/test_auto_MRIsCombine.py new file mode 100644 index 0000000000..2eae71deea --- /dev/null +++ b/nipype/interfaces/freesurfer/tests/test_auto_MRIsCombine.py @@ -0,0 +1,42 @@ +# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT +from __future__ import unicode_literals +from ..utils import MRIsCombine + + +def test_MRIsCombine_inputs(): + input_map = dict(args=dict(argstr='%s', + ), + environ=dict(nohash=True, + usedefault=True, + ), + ignore_exception=dict(nohash=True, + usedefault=True, + ), + in_files=dict(argstr='--combinesurfs %s', + mandatory=True, + position=1, + ), + out_file=dict(argstr='%s', + genfile=True, + mandatory=True, + position=-1, + ), + subjects_dir=dict(), + terminal_output=dict(nohash=True, + ), + ) + inputs = MRIsCombine.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_MRIsCombine_outputs(): + output_map = dict(out_file=dict(), + ) + outputs = MRIsCombine.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/nipype/interfaces/freesurfer/utils.py b/nipype/interfaces/freesurfer/utils.py index 7cd802e969..6db3337058 100644 --- a/nipype/interfaces/freesurfer/utils.py +++ b/nipype/interfaces/freesurfer/utils.py @@ -21,7 +21,7 @@ from ...utils.filemanip import fname_presuffix, split_filename from ..base import (TraitedSpec, File, traits, OutputMultiPath, isdefined, CommandLine, CommandLineInputSpec) -from .base import (FSCommand, FSTraitedSpec, +from .base import (FSCommand, FSTraitedSpec, FSSurfaceCommand, FSScriptCommand, FSScriptOutputSpec, FSTraitedSpecOpenMP, FSCommandOpenMP) __docformat__ = 'restructuredtext' @@ -954,6 +954,77 @@ def _gen_outfilename(self): return name + ext + "_converted." + self.inputs.out_datatype +class MRIsCombineInputSpec(FSTraitedSpec): + """ + Uses Freesurfer's mris_convert to combine two surface files into one. + """ + in_files = traits.List(File(Exists=True), maxlen=2, minlen=2, + mandatory=True, position=1, argstr='--combinesurfs %s', + desc='Two surfaces to be combined.') + out_file = File(argstr='%s', position=-1, genfile=True, + mandatory=True, + desc='Output filename. Combined surfaces from in_files.') + + +class MRIsCombineOutputSpec(TraitedSpec): + """ + Uses Freesurfer's mris_convert to combine two surface files into one. + """ + out_file = File(exists=True, desc='Output filename. Combined surfaces from ' + 'in_files.') + + +class MRIsCombine(FSSurfaceCommand): + """ + Uses Freesurfer's mris_convert to combine two surface files into one. + + For complete details, see the `mris_convert Documentation. + `_ + + If given an out_file that does not begin with 'lh.' or 'rh.', + mris_convert will prepend 'lh.' to the file name. + To avoid this behavior, consider setting out_file = './', or + leaving out_file blank. + + Example + ------- + + >>> import nipype.interfaces.freesurfer as fs + >>> mris = fs.MRIsCombine() + >>> mris.inputs.in_files = ['lh.pial', 'rh.pial'] + >>> mris.inputs.out_file = 'bh.pial' + >>> mris.cmdline # doctest: +ALLOW_UNICODE + 'mris_convert --combinesurfs lh.pial rh.pial bh.pial' + >>> mris.run() # doctest: +SKIP + """ + _cmd = 'mris_convert' + input_spec = MRIsCombineInputSpec + output_spec = MRIsCombineOutputSpec + + def _list_outputs(self): + outputs = self._outputs().get() + outputs['out_file'] = self._associated_file(self.inputs.in_files[0], + self.inputs.out_file) + return outputs + + @staticmethod + def _associated_file(in_file, out_name): + """Unlike the standard _associated_file, which uses the prefix from + in_file, in MRIsCombine, it uses 'lh.' as the prefix for the output + file no matter what the inputs are. + """ + path, base = os.path.split(out_name) + if path == '': + hemis = ('lh.', 'rh.') + if base[:3] not in hemis: + base = 'lh.' + base + return os.path.abspath(os.path.join(path, base)) + + def _normalize_filenames(self): + if isdefined(self.inputs.out_file): + self.inputs.out_file = os.path.abspath(self.inputs.out_file) + + class MRITessellateInputSpec(FSTraitedSpec): """ Uses Freesurfer's mri_tessellate to create surfaces by tessellating a given input volume diff --git a/nipype/testing/data/rh.pial b/nipype/testing/data/rh.pial new file mode 100644 index 0000000000..e69de29bb2