From 4f49bf43592357cf9536d04c992e1079eae5e973 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Wed, 7 Feb 2018 17:48:51 -0500 Subject: [PATCH 1/4] enh: add c3d and c4d interface --- nipype/interfaces/c3.py | 132 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 1 deletion(-) diff --git a/nipype/interfaces/c3.py b/nipype/interfaces/c3.py index 3a3284b32e..32e04e9a4b 100644 --- a/nipype/interfaces/c3.py +++ b/nipype/interfaces/c3.py @@ -10,9 +10,16 @@ """ from __future__ import (print_function, division, unicode_literals, absolute_import) +import os +from glob import glob from .base import (CommandLineInputSpec, traits, TraitedSpec, File, - SEMLikeCommandLine) + SEMLikeCommandLine, InputMultiPath, OutputMultiPath, + CommandLine, isdefined) +from ..utils.filemanip import split_filename +from .. import logging + +iflogger = logging.getLogger("interface") class C3dAffineToolInputSpec(CommandLineInputSpec): @@ -52,3 +59,126 @@ class C3dAffineTool(SEMLikeCommandLine): _cmd = 'c3d_affine_tool' _outputs_filenames = {'itk_transform': 'affine.txt'} + + +class C3dInputSpec(CommandLineInputSpec): + in_file = InputMultiPath(File(), + position=1, + argstr="%s", + mandatory=True, + desc="Input file (wildcard and multiple are supported)") + out_file = File(exists=False, + argstr="-o %s", + position=-1, + xor=["out_files"], + desc="Output file of last image on the stack") + out_files = InputMultiPath(File(), + argstr="-oo %s", + xor=["out_file"], + position=-1, + desc=("Write all images on the convert3d stack as multiple files." + " Supports both list of output files or a pattern for the output" + " filenames (using %d substituion).")) + pix_type = traits.Enum("float", "char", "uchar", "short", "ushort", "int", "uint", "double", + argstr="-type %s", + desc=("Specifies the pixel type for the output image. By default, images are written in" + " floating point (float) format")) + scale = traits.Either(traits.Int(), traits.Float(), + argstr="-scale %s", + desc="Multiplies the intensity of each voxel in the last image on the stack by the given factor.") + shift = traits.Either(traits.Int(), traits.Float(), + argstr="-shift %s", + desc='Adds the given constant to every voxel.') + interp = traits.Enum("Linear", "NearestNeighbor", "Cubic", "Sinc", "Gaussian", + argstr="-interpolation %s", + desc="Specifies the interpolation used with -resample and other commands. Default is Linear.") + resample = traits.Str(argstr="-resample %s", + desc=("Resamples the image, keeping the bounding box the same, but changing the number of" + " voxels in the image. The dimensions can be specified as a percentage, for example to" + " double the number of voxels in each direction. The -interpolation flag affects how" + " sampling is performed.")) + smooth = traits.Str(argstr="-smooth %s", + desc=("Applies Gaussian smoothing to the image. The parameter vector specifies the" + " standard deviation of the Gaussian kernel.")) + multicomp_split = traits.Bool(False, + usedefault=True, + argstr="-mcr", + position=0, + desc="Enable reading of multi-component images.") + is_4d = traits.Bool(False, + usedefault=True, + desc="Changes command to support 4D file operations (default is false).") + +class C3dOutputSpec(TraitedSpec): + out_files = OutputMultiPath(File(exists=False)) + +class C3d(CommandLine): + """ + Convert3d is a command-line tool for converting 3D (or 4D) images between common + file formats. The tool also includes a growing list of commands for image manipulation, + such as thresholding and resampling. The tool can also be used to obtain information about + image files. More information on Convert3d can be found at: + https://sourceforge.net/p/c3d/git/ci/master/tree/doc/c3d.md + + + Example + ======= + + >>> from nipype.interfaces.c3 import C3d + >>> c3 = C3d() + >>> c3.inputs.in_file = "T1.nii" + >>> c3.inputs.pix_type = "short" + >>> c3.inputs.out_file = "T1.img" + >>> c3.cmdline + 'c3d T1.nii -type short -o T1.img' + >>> c3.inputs.is_4d = True + >>> c3.inputs.in_file = "epi.nii" + >>> c3.inputs.out_file = "epi.img" + >>> c3.cmdline + 'c4d epi.nii -type short -o epi.img' + """ + input_spec = C3dInputSpec + output_spec = C3dOutputSpec + + _cmd = "c3d" + + def __init__(self, **inputs): + super(C3d, self).__init__(**inputs) + self.inputs.on_trait_change(self._is_4d, "is_4d") + if self.inputs.is_4d: + self._is_4d() + + def _is_4d(self): + self._cmd = "c4d" if self.inputs.is_4d else "c3d" + + def _run_interface(self, runtime): + cmd = self._cmd + # by default, does not want to override file, so we define a new output file + if not isdefined(self.inputs.out_file) and not isdefined(self.inputs.out_files): + self._gen_outfile() + runtime = super(C3d, self)._run_interface(runtime) + self._cmd = cmd + return runtime + + def _gen_outfile(self): + # if many infiles, raise exception + if (len(self.inputs.in_file) > 1) or ("*" in self.inputs.in_file[0]): + raise AttributeError("Multiple in_files found - specify either out_file or out_files") + _, fn, ext = split_filename(self.inputs.in_file[0]) + self.inputs.out_file = fn + "_generated" + ext + assert not os.path.exists(os.path.abspath(self.inputs.out_file)) + iflogger.info("Generating out_file to avoid overwriting") + + def _list_outputs(self): + outputs = self.output_spec().get() + if isdefined(self.inputs.out_file): + outputs["out_files"] = os.path.abspath(self.inputs.out_file) + if isdefined(self.inputs.out_files): + if not len(self.inputs.out_files) > 1: + _out_files = glob(os.path.abspath(self.inputs.out_files[0])) + else: + _out_files = [os.path.abspath(fl) for fl in self.inputs.out_files + if os.path.exists(os.path.abspath(fl))] + outputs["out_files"] = _out_files + + return outputs From 234c55591799ca0c44254335e7660838480a69cd Mon Sep 17 00:00:00 2001 From: mathiasg Date: Wed, 7 Feb 2018 18:00:55 -0500 Subject: [PATCH 2/4] enh+sty: autotest and styling --- nipype/interfaces/c3.py | 82 ++++++++++++------------ nipype/interfaces/tests/test_auto_C3d.py | 61 ++++++++++++++++++ 2 files changed, 102 insertions(+), 41 deletions(-) create mode 100644 nipype/interfaces/tests/test_auto_C3d.py diff --git a/nipype/interfaces/c3.py b/nipype/interfaces/c3.py index 32e04e9a4b..e77b9ceec2 100644 --- a/nipype/interfaces/c3.py +++ b/nipype/interfaces/c3.py @@ -63,63 +63,63 @@ class C3dAffineTool(SEMLikeCommandLine): class C3dInputSpec(CommandLineInputSpec): in_file = InputMultiPath(File(), - position=1, - argstr="%s", - mandatory=True, - desc="Input file (wildcard and multiple are supported)") + position=1, + argstr="%s", + mandatory=True, + desc="Input file (wildcard and multiple are supported)") out_file = File(exists=False, - argstr="-o %s", - position=-1, - xor=["out_files"], - desc="Output file of last image on the stack") + argstr="-o %s", + position=-1, + xor=["out_files"], + desc="Output file of last image on the stack") out_files = InputMultiPath(File(), - argstr="-oo %s", - xor=["out_file"], - position=-1, - desc=("Write all images on the convert3d stack as multiple files." - " Supports both list of output files or a pattern for the output" - " filenames (using %d substituion).")) + argstr="-oo %s", + xor=["out_file"], + position=-1, + desc=("Write all images on the convert3d stack as multiple files." + " Supports both list of output files or a pattern for the output" + " filenames (using %d substituion).")) pix_type = traits.Enum("float", "char", "uchar", "short", "ushort", "int", "uint", "double", - argstr="-type %s", - desc=("Specifies the pixel type for the output image. By default, images are written in" - " floating point (float) format")) + argstr="-type %s", + desc=("Specifies the pixel type for the output image. By default, images are written in" + " floating point (float) format")) scale = traits.Either(traits.Int(), traits.Float(), - argstr="-scale %s", - desc="Multiplies the intensity of each voxel in the last image on the stack by the given factor.") + argstr="-scale %s", + desc="Multiplies the intensity of each voxel in the last image on the stack by the given factor.") shift = traits.Either(traits.Int(), traits.Float(), - argstr="-shift %s", - desc='Adds the given constant to every voxel.') + argstr="-shift %s", + desc='Adds the given constant to every voxel.') interp = traits.Enum("Linear", "NearestNeighbor", "Cubic", "Sinc", "Gaussian", - argstr="-interpolation %s", - desc="Specifies the interpolation used with -resample and other commands. Default is Linear.") + argstr="-interpolation %s", + desc="Specifies the interpolation used with -resample and other commands. Default is Linear.") resample = traits.Str(argstr="-resample %s", - desc=("Resamples the image, keeping the bounding box the same, but changing the number of" - " voxels in the image. The dimensions can be specified as a percentage, for example to" - " double the number of voxels in each direction. The -interpolation flag affects how" - " sampling is performed.")) + desc=("Resamples the image, keeping the bounding box the same, but changing the number of" + " voxels in the image. The dimensions can be specified as a percentage, for example to" + " double the number of voxels in each direction. The -interpolation flag affects how" + " sampling is performed.")) smooth = traits.Str(argstr="-smooth %s", - desc=("Applies Gaussian smoothing to the image. The parameter vector specifies the" - " standard deviation of the Gaussian kernel.")) + desc=("Applies Gaussian smoothing to the image. The parameter vector specifies the" + " standard deviation of the Gaussian kernel.")) multicomp_split = traits.Bool(False, - usedefault=True, - argstr="-mcr", - position=0, - desc="Enable reading of multi-component images.") - is_4d = traits.Bool(False, - usedefault=True, - desc="Changes command to support 4D file operations (default is false).") + usedefault=True, + argstr="-mcr", + position=0, + desc="Enable reading of multi-component images.") + is_4d = traits.Bool(False, + usedefault=True, + desc="Changes command to support 4D file operations (default is false).") class C3dOutputSpec(TraitedSpec): out_files = OutputMultiPath(File(exists=False)) class C3d(CommandLine): """ - Convert3d is a command-line tool for converting 3D (or 4D) images between common - file formats. The tool also includes a growing list of commands for image manipulation, - such as thresholding and resampling. The tool can also be used to obtain information about + Convert3d is a command-line tool for converting 3D (or 4D) images between common + file formats. The tool also includes a growing list of commands for image manipulation, + such as thresholding and resampling. The tool can also be used to obtain information about image files. More information on Convert3d can be found at: https://sourceforge.net/p/c3d/git/ci/master/tree/doc/c3d.md - + Example ======= @@ -174,7 +174,7 @@ def _list_outputs(self): if isdefined(self.inputs.out_file): outputs["out_files"] = os.path.abspath(self.inputs.out_file) if isdefined(self.inputs.out_files): - if not len(self.inputs.out_files) > 1: + if len(self.inputs.out_files) == 1: _out_files = glob(os.path.abspath(self.inputs.out_files[0])) else: _out_files = [os.path.abspath(fl) for fl in self.inputs.out_files diff --git a/nipype/interfaces/tests/test_auto_C3d.py b/nipype/interfaces/tests/test_auto_C3d.py new file mode 100644 index 0000000000..18300e2aff --- /dev/null +++ b/nipype/interfaces/tests/test_auto_C3d.py @@ -0,0 +1,61 @@ +# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT +from __future__ import unicode_literals +from ..c3 import C3d + + +def test_C3d_inputs(): + input_map = dict( + args=dict(argstr='%s', ), + environ=dict( + nohash=True, + usedefault=True, + ), + ignore_exception=dict( + deprecated='1.0.0', + nohash=True, + usedefault=True, + ), + in_file=dict( + argstr='%s', + mandatory=True, + position=1, + ), + interp=dict(argstr='-interpolation %s', ), + is_4d=dict(usedefault=True, ), + multicomp_split=dict( + argstr='-mcr', + position=0, + usedefault=True, + ), + out_file=dict( + argstr='-o %s', + position=-1, + xor=['out_files'], + ), + out_files=dict( + argstr='-oo %s', + position=-1, + xor=['out_file'], + ), + pix_type=dict(argstr='-type %s', ), + resample=dict(argstr='-resample %s', ), + scale=dict(argstr='-scale %s', ), + shift=dict(argstr='-shift %s', ), + smooth=dict(argstr='-smooth %s', ), + terminal_output=dict( + deprecated='1.0.0', + nohash=True, + ), + ) + inputs = C3d.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_C3d_outputs(): + output_map = dict(out_files=dict(), ) + outputs = C3d.output_spec() + + for key, metadata in list(output_map.items()): + for metakey, value in list(metadata.items()): + assert getattr(outputs.traits()[key], metakey) == value From 392cc12323896e4e3f8858c3ffb2169d3575d6e1 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Thu, 8 Feb 2018 10:41:19 -0500 Subject: [PATCH 3/4] sty: pep8 and better error messages --- nipype/interfaces/c3.py | 94 ++++++++++++++++++++++++++--------------- 1 file changed, 59 insertions(+), 35 deletions(-) diff --git a/nipype/interfaces/c3.py b/nipype/interfaces/c3.py index e77b9ceec2..1704d94446 100644 --- a/nipype/interfaces/c3.py +++ b/nipype/interfaces/c3.py @@ -62,62 +62,80 @@ class C3dAffineTool(SEMLikeCommandLine): class C3dInputSpec(CommandLineInputSpec): - in_file = InputMultiPath(File(), + in_file = InputMultiPath( + File(), position=1, argstr="%s", mandatory=True, - desc="Input file (wildcard and multiple are supported)") - out_file = File(exists=False, + desc="Input file (wildcard and multiple are supported).") + out_file = File( + exists=False, argstr="-o %s", position=-1, xor=["out_files"], - desc="Output file of last image on the stack") - out_files = InputMultiPath(File(), + desc="Output file of last image on the stack.") + out_files = InputMultiPath( + File(), argstr="-oo %s", xor=["out_file"], position=-1, desc=("Write all images on the convert3d stack as multiple files." " Supports both list of output files or a pattern for the output" " filenames (using %d substituion).")) - pix_type = traits.Enum("float", "char", "uchar", "short", "ushort", "int", "uint", "double", + pix_type = traits.Enum( + "float", "char", "uchar", "short", "ushort", "int", "uint", "double", argstr="-type %s", - desc=("Specifies the pixel type for the output image. By default, images are written in" - " floating point (float) format")) - scale = traits.Either(traits.Int(), traits.Float(), + desc=("Specifies the pixel type for the output image. By default," + " images are written in floating point (float) format")) + scale = traits.Either( + traits.Int(), traits.Float(), argstr="-scale %s", - desc="Multiplies the intensity of each voxel in the last image on the stack by the given factor.") - shift = traits.Either(traits.Int(), traits.Float(), + desc=("Multiplies the intensity of each voxel in the last image on the" + " stack by the given factor.") + shift = traits.Either( + traits.Int(), traits.Float(), argstr="-shift %s", desc='Adds the given constant to every voxel.') - interp = traits.Enum("Linear", "NearestNeighbor", "Cubic", "Sinc", "Gaussian", + interp = traits.Enum( + "Linear", "NearestNeighbor", "Cubic", "Sinc", "Gaussian", argstr="-interpolation %s", - desc="Specifies the interpolation used with -resample and other commands. Default is Linear.") - resample = traits.Str(argstr="-resample %s", - desc=("Resamples the image, keeping the bounding box the same, but changing the number of" - " voxels in the image. The dimensions can be specified as a percentage, for example to" - " double the number of voxels in each direction. The -interpolation flag affects how" - " sampling is performed.")) - smooth = traits.Str(argstr="-smooth %s", - desc=("Applies Gaussian smoothing to the image. The parameter vector specifies the" - " standard deviation of the Gaussian kernel.")) - multicomp_split = traits.Bool(False, + desc=("Specifies the interpolation used with -resample and other" + " commands. Default is Linear.") + resample = traits.Str( + argstr="-resample %s", + desc=("Resamples the image, keeping the bounding box the same, but" + " changing the number of voxels in the image. The dimensions can be" + " specified as a percentage, for example to double the number of voxels" + " in each direction. The -interpolation flag affects how sampling is" + " performed.")) + smooth = traits.Str( + argstr="-smooth %s", + desc=("Applies Gaussian smoothing to the image. The parameter vector" + " specifies the standard deviation of the Gaussian kernel.")) + multicomp_split = traits.Bool( + False, usedefault=True, argstr="-mcr", position=0, desc="Enable reading of multi-component images.") - is_4d = traits.Bool(False, + is_4d = traits.Bool( + False, usedefault=True, - desc="Changes command to support 4D file operations (default is false).") + desc=("Changes command to support 4D file operations (default is" + " false).") + class C3dOutputSpec(TraitedSpec): out_files = OutputMultiPath(File(exists=False)) + class C3d(CommandLine): """ - Convert3d is a command-line tool for converting 3D (or 4D) images between common - file formats. The tool also includes a growing list of commands for image manipulation, - such as thresholding and resampling. The tool can also be used to obtain information about - image files. More information on Convert3d can be found at: + Convert3d is a command-line tool for converting 3D (or 4D) images between + common file formats. The tool also includes a growing list of commands for + image manipulation, such as thresholding and resampling. The tool can also + be used to obtain information about image files. More information on + Convert3d can be found at: https://sourceforge.net/p/c3d/git/ci/master/tree/doc/c3d.md @@ -153,8 +171,10 @@ def _is_4d(self): def _run_interface(self, runtime): cmd = self._cmd - # by default, does not want to override file, so we define a new output file - if not isdefined(self.inputs.out_file) and not isdefined(self.inputs.out_files): + if (not isdefined(self.inputs.out_file) + and not isdefined(self.inputs.out_files)): + # Convert3d does not want to override file, by default + # so we define a new output file self._gen_outfile() runtime = super(C3d, self)._run_interface(runtime) self._cmd = cmd @@ -163,11 +183,14 @@ def _run_interface(self, runtime): def _gen_outfile(self): # if many infiles, raise exception if (len(self.inputs.in_file) > 1) or ("*" in self.inputs.in_file[0]): - raise AttributeError("Multiple in_files found - specify either out_file or out_files") + raise AttributeError("Multiple in_files found - specify either" + " `out_file` or `out_files`.") _, fn, ext = split_filename(self.inputs.in_file[0]) self.inputs.out_file = fn + "_generated" + ext - assert not os.path.exists(os.path.abspath(self.inputs.out_file)) - iflogger.info("Generating out_file to avoid overwriting") + # if generated file will overwrite, raise error + if os.path.exists(os.path.abspath(self.inputs.out_file)): + raise IOError("File already found - to overwrite, use `out_file`.") + iflogger.info("Generating `out_file`.") def _list_outputs(self): outputs = self.output_spec().get() @@ -177,8 +200,9 @@ def _list_outputs(self): if len(self.inputs.out_files) == 1: _out_files = glob(os.path.abspath(self.inputs.out_files[0])) else: - _out_files = [os.path.abspath(fl) for fl in self.inputs.out_files - if os.path.exists(os.path.abspath(fl))] + _out_files = [os.path.abspath(f) for f in self.inputs.out_files + if os.path.exists(os.path.abspath(f))] outputs["out_files"] = _out_files return outputs + From ab9f3eae67601b22dd9676495c95ab9e71f299be Mon Sep 17 00:00:00 2001 From: mathiasg Date: Thu, 8 Feb 2018 11:19:56 -0500 Subject: [PATCH 4/4] fix: syntax --- nipype/interfaces/c3.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nipype/interfaces/c3.py b/nipype/interfaces/c3.py index 1704d94446..f4778e7d93 100644 --- a/nipype/interfaces/c3.py +++ b/nipype/interfaces/c3.py @@ -91,7 +91,7 @@ class C3dInputSpec(CommandLineInputSpec): traits.Int(), traits.Float(), argstr="-scale %s", desc=("Multiplies the intensity of each voxel in the last image on the" - " stack by the given factor.") + " stack by the given factor.")) shift = traits.Either( traits.Int(), traits.Float(), argstr="-shift %s", @@ -100,7 +100,7 @@ class C3dInputSpec(CommandLineInputSpec): "Linear", "NearestNeighbor", "Cubic", "Sinc", "Gaussian", argstr="-interpolation %s", desc=("Specifies the interpolation used with -resample and other" - " commands. Default is Linear.") + " commands. Default is Linear.")) resample = traits.Str( argstr="-resample %s", desc=("Resamples the image, keeping the bounding box the same, but" @@ -122,7 +122,7 @@ class C3dInputSpec(CommandLineInputSpec): False, usedefault=True, desc=("Changes command to support 4D file operations (default is" - " false).") + " false).")) class C3dOutputSpec(TraitedSpec):