From 32ea82d18dde938d7a5b4dadb9fcbe8ca8927cd4 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Tue, 11 Apr 2017 15:15:11 -0400 Subject: [PATCH 1/7] fix: catch compressed files for spm realign --- nipype/interfaces/afni/tests/test_auto_Autobox.py | 1 + nipype/interfaces/spm/preprocess.py | 9 +++++++++ nipype/interfaces/utility/tests/test_auto_Merge.py | 2 ++ 3 files changed, 12 insertions(+) diff --git a/nipype/interfaces/afni/tests/test_auto_Autobox.py b/nipype/interfaces/afni/tests/test_auto_Autobox.py index 0d2c8a9f4f..91479c241d 100644 --- a/nipype/interfaces/afni/tests/test_auto_Autobox.py +++ b/nipype/interfaces/afni/tests/test_auto_Autobox.py @@ -20,6 +20,7 @@ def test_Autobox_inputs(): ), out_file=dict(argstr='-prefix %s', name_source='in_file', + name_template='%s_autobox', ), outputtype=dict(), padding=dict(argstr='-npad %d', diff --git a/nipype/interfaces/spm/preprocess.py b/nipype/interfaces/spm/preprocess.py index 9039c03aad..39579ffd8a 100644 --- a/nipype/interfaces/spm/preprocess.py +++ b/nipype/interfaces/spm/preprocess.py @@ -217,6 +217,15 @@ def _parse_inputs(self): """validate spm realign options if set to None ignore """ einputs = super(Realign, self)._parse_inputs() + is_gz = lambda x: x.endswith('.gz') + if isdefined(self.inputs.in_files): + if isinstance(self.inputs.in_files, list): + for imgf in self.inputs.in_files: + if is_gz(imgf): + raise TypeError('Input files must be uncompressed') + else: + if is_gz(self.inputs.in_files): + raise TypeError('Input files must be uncompressed') return [{'%s' % (self.inputs.jobtype): einputs[0]}] def _list_outputs(self): diff --git a/nipype/interfaces/utility/tests/test_auto_Merge.py b/nipype/interfaces/utility/tests/test_auto_Merge.py index 56571776fb..f98e70892b 100644 --- a/nipype/interfaces/utility/tests/test_auto_Merge.py +++ b/nipype/interfaces/utility/tests/test_auto_Merge.py @@ -11,6 +11,8 @@ def test_Merge_inputs(): ), no_flatten=dict(usedefault=True, ), + ravel_inputs=dict(usedefault=True, + ), ) inputs = Merge.input_spec() From fdd7e8b1c9dabad32167cdfc8abdd97d2670b5b9 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Wed, 19 Apr 2017 12:12:22 -0400 Subject: [PATCH 2/7] enh: new traits applied to spm --- nipype/interfaces/base.py | 4 +- nipype/interfaces/spm/preprocess.py | 95 ++++++++++++++------------- nipype/interfaces/traits_extension.py | 32 +++++++++ 3 files changed, 82 insertions(+), 49 deletions(-) diff --git a/nipype/interfaces/base.py b/nipype/interfaces/base.py index dc60f2c816..86d704630c 100644 --- a/nipype/interfaces/base.py +++ b/nipype/interfaces/base.py @@ -38,8 +38,8 @@ from ..utils.filemanip import (md5, hash_infile, FileNotFoundError, hash_timestamp, split_filename, to_str) from .traits_extension import ( - traits, Undefined, TraitDictObject, TraitListObject, TraitError, isdefined, File, - Directory, DictStrStr, has_metadata) + traits, Undefined, TraitDictObject, TraitListObject, TraitError, isdefined, + File, Directory, DictStrStr, has_metadata, ImageFile) from ..external.due import due runtime_profile = str2bool(config.get('execution', 'profile_runtime')) diff --git a/nipype/interfaces/spm/preprocess.py b/nipype/interfaces/spm/preprocess.py index 39579ffd8a..0882e0b2cf 100644 --- a/nipype/interfaces/spm/preprocess.py +++ b/nipype/interfaces/spm/preprocess.py @@ -23,16 +23,21 @@ from ...utils.filemanip import (fname_presuffix, filename_to_list, list_to_filename, split_filename) from ..base import (OutputMultiPath, TraitedSpec, isdefined, - traits, InputMultiPath, File) + traits, InputMultiPath, File, ImageFile) from .base import (SPMCommand, scans_for_fname, func_is_3d, scans_for_fnames, SPMCommandInputSpec) __docformat__ = 'restructuredtext' + class SliceTimingInputSpec(SPMCommandInputSpec): - in_files = InputMultiPath(traits.Either(traits.List(File(exists=True)), - File(exists=True)), field='scans', + in_files = InputMultiPath(traits.Either(traits.List(ImageFile( + exists=True, + compressed=False)), + ImageFile(exists=True, + compressed=False)), + field='scans', desc='list of filenames to apply slice timing', mandatory=True, copyfile=False) num_slices = traits.Int(field='nslices', @@ -116,8 +121,10 @@ def _list_outputs(self): class RealignInputSpec(SPMCommandInputSpec): - in_files = InputMultiPath(traits.Either(traits.List(File(exists=True)), - File(exists=True)), field='data', + in_files = InputMultiPath(traits.Either(traits.List(ImageFile(exists=True, + compressed=False)), + ImageFile(exists=True, + compressed=False)), field='data', mandatory=True, copyfile=True, desc='list of filenames to realign') jobtype = traits.Enum('estwrite', 'estimate', 'write', @@ -217,15 +224,6 @@ def _parse_inputs(self): """validate spm realign options if set to None ignore """ einputs = super(Realign, self)._parse_inputs() - is_gz = lambda x: x.endswith('.gz') - if isdefined(self.inputs.in_files): - if isinstance(self.inputs.in_files, list): - for imgf in self.inputs.in_files: - if is_gz(imgf): - raise TypeError('Input files must be uncompressed') - else: - if is_gz(self.inputs.in_files): - raise TypeError('Input files must be uncompressed') return [{'%s' % (self.inputs.jobtype): einputs[0]}] def _list_outputs(self): @@ -279,11 +277,12 @@ def _list_outputs(self): class CoregisterInputSpec(SPMCommandInputSpec): - target = File(exists=True, field='ref', mandatory=True, - desc='reference file to register to', copyfile=False) - source = InputMultiPath(File(exists=True), field='source', - desc='file to register to target', copyfile=True, - mandatory=True) + target = ImageFile(exists=True, compressed=False, mandatory=True, + field='ref', desc='reference file to register to', + copyfile=False) + source = InputMultiPath(ImageFile(exists=True, compressed=False), + field='source', desc='file to register to target', + copyfile=True, mandatory=True) jobtype = traits.Enum('estwrite', 'estimate', 'write', desc='one of: estimate, write, estwrite', usedefault=True) @@ -401,9 +400,9 @@ class NormalizeInputSpec(SPMCommandInputSpec): desc='template file to normalize to', mandatory=True, xor=['parameter_file'], copyfile=False) - source = InputMultiPath(File(exists=True), field='subj.source', + source = InputMultiPath(ImageFile(exists=True, compressed=False), + field='subj.source', xor=['parameter_file'], desc='file to normalize to template', - xor=['parameter_file'], mandatory=True, copyfile=True) jobtype = traits.Enum('estwrite', 'est', 'write', usedefault=True, desc='Estimate, Write or do both') @@ -564,18 +563,19 @@ def _list_outputs(self): class Normalize12InputSpec(SPMCommandInputSpec): - image_to_align = File(exists=True, field='subj.vol', + image_to_align = ImageFile(exists=True, compressed=False, field='subj.vol', desc=('file to estimate normalization parameters ' 'with'), xor=['deformation_file'], mandatory=True, copyfile=True) apply_to_files = InputMultiPath( - traits.Either(File(exists=True), traits.List(File(exists=True))), + traits.Either(ImageFile(exists=True, compressed=False), + traits.List(ImageFile(exists=True, compressed=False))), field='subj.resample', desc='files to apply transformation to', copyfile=True) - deformation_file = File(field='subj.def', mandatory=True, - xor=['image_to_align', 'tpm'], + deformation_file = ImageFile(field='subj.def', mandatory=True, + compressed=False, xor=['image_to_align', 'tpm'], desc=('file y_*.nii containing 3 deformation ' 'fields for the deformation in x, y and z ' 'dimension'), @@ -730,7 +730,7 @@ def _list_outputs(self): class SegmentInputSpec(SPMCommandInputSpec): - data = InputMultiPath(File(exists=True), field='data', + data = InputMultiPath(ImageFile(exists=True, compressed=False), field='data', desc='one scan per subject', copyfile=False, mandatory=True) gm_output_type = traits.List(traits.Bool(), minlen=3, maxlen=3, @@ -899,7 +899,7 @@ def _list_outputs(self): class NewSegmentInputSpec(SPMCommandInputSpec): - channel_files = InputMultiPath(File(exists=True), + channel_files = InputMultiPath(ImageFile(exists=True, compressed=False), desc="A list of files to be segmented", field='channel', copyfile=False, mandatory=True) @@ -911,7 +911,8 @@ class NewSegmentInputSpec(SPMCommandInputSpec): - which maps to save (Corrected, Field) - a tuple of two boolean values""", field='channel') tissues = traits.List( - traits.Tuple(traits.Tuple(File(exists=True), traits.Int()), + traits.Tuple(traits.Tuple(ImageFile(exists=True, compressed=False), + traits.Int()), traits.Int(), traits.Tuple(traits.Bool, traits.Bool), traits.Tuple(traits.Bool, traits.Bool)), desc="""A list of tuples (one per tissue) with the following fields: @@ -1102,8 +1103,8 @@ def _list_outputs(self): class SmoothInputSpec(SPMCommandInputSpec): - in_files = InputMultiPath(File(exists=True), field='data', - desc='list of files to smooth', + in_files = InputMultiPath(ImageFile(exists=True, compressed=False), + field='data', desc='list of files to smooth', mandatory=True, copyfile=False) fwhm = traits.Either(traits.List(traits.Float(), minlen=3, maxlen=3), traits.Float(), field='fwhm', @@ -1165,7 +1166,8 @@ def _list_outputs(self): class DARTELInputSpec(SPMCommandInputSpec): - image_files = traits.List(traits.List(File(exists=True)), + image_files = traits.List(traits.List(ImageFile(exists=True, + compressed=False)), desc="A list of files to be segmented", field='warp.images', copyfile=False, mandatory=True) @@ -1281,15 +1283,14 @@ def _list_outputs(self): class DARTELNorm2MNIInputSpec(SPMCommandInputSpec): - template_file = File(exists=True, - desc="DARTEL template", - field='mni_norm.template', copyfile=False, - mandatory=True) - flowfield_files = InputMultiPath(File(exists=True), + template_file = ImageFile(exists=True, compressed=False, + desc="DARTEL template", field='mni_norm.template', + copyfile=False, mandatory=True) + flowfield_files = InputMultiPath(ImageFile(exists=True, compressed=False), desc="DARTEL flow fields u_rc1*", field='mni_norm.data.subjs.flowfields', mandatory=True) - apply_to_files = InputMultiPath(File(exists=True), + apply_to_files = InputMultiPath(ImageFile(exists=True, compressed=False), desc="Files to apply the transform to", field='mni_norm.data.subjs.images', mandatory=True, copyfile=False) @@ -1379,11 +1380,11 @@ def _list_outputs(self): class CreateWarpedInputSpec(SPMCommandInputSpec): - image_files = InputMultiPath(File(exists=True), + image_files = InputMultiPath(ImageFile(exists=True, compressed=False), desc="A list of files to be warped", field='crt_warped.images', copyfile=False, mandatory=True) - flowfield_files = InputMultiPath(File(exists=True), + flowfield_files = InputMultiPath(ImageFile(exists=True, compressed=False), desc="DARTEL flow fields u_rc1*", field='crt_warped.flowfields', copyfile=False, @@ -1449,10 +1450,10 @@ def _list_outputs(self): class ApplyDeformationFieldInputSpec(SPMCommandInputSpec): - in_files = InputMultiPath(File(exists=True), mandatory=True, - field='fnames') + in_files = InputMultiPath(ImageFile(exists=True, compressed=False), + mandatory=True, field='fnames') deformation_field = File(exists=True, mandatory=True, field='comp{1}.def') - reference_volume = File(exists=True, mandatory=True, + reference_volume = ImageFile(exists=True, mandatory=True, compressed=False, field='comp{2}.id.space') interp = traits.Range(low=0, high=7, field='interp', desc='degree of b-spline used for interpolation') @@ -1495,12 +1496,12 @@ def _list_outputs(self): class VBMSegmentInputSpec(SPMCommandInputSpec): in_files = InputMultiPath( - File(exists=True), + ImageFile(exists=True, compressed=False), desc="A list of files to be segmented", field='estwrite.data', copyfile=False, mandatory=True) - tissues = File( - exists=True, field='estwrite.tpm', + tissues = ImageFile( + exists=True, compressed=False, field='estwrite.tpm', desc='tissue probability map') gaussians_per_class = traits.Tuple( (2, 2, 2, 3, 4, 2), *([traits.Int()] * 6), @@ -1527,8 +1528,8 @@ class VBMSegmentInputSpec(SPMCommandInputSpec): spatial_normalization = traits.Enum( 'high', 'low', usedefault=True,) - dartel_template = File( - exists=True, + dartel_template = ImageFile( + exists=True, compressed=False, field='estwrite.extopts.dartelwarp.normhigh.darteltpm') use_sanlm_denoising_filter = traits.Range( 0, 2, 2, usedefault=True, field='estwrite.extopts.sanlm', diff --git a/nipype/interfaces/traits_extension.py b/nipype/interfaces/traits_extension.py index 02342aa96a..955ea42183 100644 --- a/nipype/interfaces/traits_extension.py +++ b/nipype/interfaces/traits_extension.py @@ -218,6 +218,38 @@ def __init__(self, value='', auto_set=False, entries=0, super(Directory, self).__init__(value, auto_set, entries, exists, **metadata) +class ImageFile(File): + """ Defines a trait of specific neuroimaging files """ + + def __init__(self, value='', filter=None, auto_set=False, entries=0, + exists=False, types=['nii', 'hdr', 'img'], compressed=True, + **metadata): + """ Trait handles neuroimaging files. + + Parameters + ---------- + types : list + The accepted file-types + compressed : boolean + Indicates whether the file-type can compressed + """ + self.types = types + self.compressed = compressed + super(ImageFile, self).__init__(value, filter, auto_set, entries, + exists, **metadata) + + def validate(self, object, name, value): + """ Validates that a specified value is valid for this trait. + """ + validated_value = super(ImageFile, self).validate(object, name, value) + if validated_value and self.types: + if self.compressed: + self.types.extend([x + '.gz' for x in self.types]) + if not any(validated_value.endswith(x) for x in self.types): + raise TraitError( + args="{} is not included in allowed types: {}".format( + validated_value, ','.join(self.types))) + return validated_value """ The functions that pop-up the Traits GUIs, edit_traits and From 3ec370c3ab420f592edd2716726afbc6834bbd39 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Tue, 25 Apr 2017 16:29:40 -0400 Subject: [PATCH 3/7] wip: check against image format --- nipype/interfaces/traits_extension.py | 49 +++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/nipype/interfaces/traits_extension.py b/nipype/interfaces/traits_extension.py index 955ea42183..9be62b799f 100644 --- a/nipype/interfaces/traits_extension.py +++ b/nipype/interfaces/traits_extension.py @@ -85,6 +85,11 @@ def validate(self, object, name, value): return validated_value elif os.path.isfile(value): return validated_value + else: + raise TraitError( + args='The trait \'{}\' of {} instance is {}, but the path ' + ' \'{}\' does not exist.'.format(name, class_of(object), + self.info_text, value)) self.error(object, name, value) @@ -218,37 +223,67 @@ def __init__(self, value='', auto_set=False, entries=0, super(Directory, self).__init__(value, auto_set, entries, exists, **metadata) +# lists of tuples +# each element consists of : +# - uncompressed (tuple[0]) extension +# - compressed (tuple[1]) extension +img_fmt_types = { + 'nifti1': [('.nii', '.nii.gz'), + (('.hdr', '.img'), ('.hdr', '.img.gz'))], + 'mgh': [('.mgh', '.mgz'), ('.mgh', '.mgh.gz')], + 'nifti2': [('.nii', '.nii.gz'), + (('.hdr', '.img'), ('.hdr', '.img.gz'))], + 'cifti2': [('.nii', '.nii.gz'), + (('.hdr', '.img'), ('.hdr', '.img.gz'))], + 'DICOM': [('.dcm', '.dcm'), ('.IMA', '.IMA'), ('.tar', '.tar.gz')], + 'nrrd': [('.nrrd', 'nrrd'), ('nhdr', 'nhdr')], + 'afni': [('.HEAD', '.HEAD'), ('.BRIK', '.BRIK')] + } + class ImageFile(File): """ Defines a trait of specific neuroimaging files """ def __init__(self, value='', filter=None, auto_set=False, entries=0, - exists=False, types=['nii', 'hdr', 'img'], compressed=True, + exists=False, types=[], compressed=True, extensions=[], **metadata): """ Trait handles neuroimaging files. Parameters ---------- types : list - The accepted file-types + Strings of file format types accepted compressed : boolean - Indicates whether the file-type can compressed + Indicates whether the file format can compressed """ self.types = types self.compressed = compressed + self.exts = extensions super(ImageFile, self).__init__(value, filter, auto_set, entries, exists, **metadata) + def grab_exts(self, exts=[]): + for fmt in self.types: + if fmt in img_fmt_types: + exts.extend(sum([[u for u in y[0]] if isinstance(y[0], tuple) + else [y[0]] for y in img_fmt_types[fmt]], [])) + if self.compressed: + exts.extend(sum([[u for u in y[-1]] if isinstance(y[-1], tuple) + else [y[-1]] for y in img_fmt_types[fmt]], [])) + else: + raise AttributeError('Information has not been added for format' + 'type {} yet.'.format(fmt)) + return list(set(exts)) + def validate(self, object, name, value): """ Validates that a specified value is valid for this trait. """ validated_value = super(ImageFile, self).validate(object, name, value) if validated_value and self.types: - if self.compressed: - self.types.extend([x + '.gz' for x in self.types]) - if not any(validated_value.endswith(x) for x in self.types): + self.exts = self.grab_exts(self.exts) + if not any(validated_value.endswith(x) for x in self.exts): raise TraitError( args="{} is not included in allowed types: {}".format( - validated_value, ','.join(self.types))) + validated_value, ','.join(self.exts))) return validated_value """ From e6626affb95c7c44b8269a02bffb5a776e86b845 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Wed, 26 Apr 2017 13:55:34 -0400 Subject: [PATCH 4/7] enh: imagetype base for spm, new image formats, allow_compression --- nipype/interfaces/spm/base.py | 26 +++++++- nipype/interfaces/spm/preprocess.py | 88 ++++++++++++--------------- nipype/interfaces/traits_extension.py | 29 ++++----- 3 files changed, 78 insertions(+), 65 deletions(-) diff --git a/nipype/interfaces/spm/base.py b/nipype/interfaces/spm/base.py index 25ab67d412..f7e7bba67f 100644 --- a/nipype/interfaces/spm/base.py +++ b/nipype/interfaces/spm/base.py @@ -30,7 +30,7 @@ from ... import logging from ...utils import spm_docs as sd, NUMPY_MMAP from ..base import (BaseInterface, traits, isdefined, InputMultiPath, - BaseInterfaceInputSpec, Directory, Undefined) + BaseInterfaceInputSpec, Directory, Undefined, ImageFile) from ..matlab import MatlabCommand from ...external.due import due, Doi, BibTeX @@ -532,3 +532,27 @@ def _make_matlab_command(self, contents, postscript=None): if postscript is not None: mscript += postscript return mscript + +class ImageFileSPM(ImageFile): + """ + Defines an ImageFile trait specific to SPM interfaces. + """ + + def __init__(self, value='', filter=None, auto_set=False, entries=0, + exists=False, types=['nifti1', 'nifti2'], extensions=[], + allow_compressed=False, **metadata): + """ Trait handles neuroimaging files. + + Parameters + ---------- + types : list + Strings of file format types accepted + compressed : boolean + Indicates whether the file format can compressed + """ + self.types = types + self.compressed = compressed + self.exts = extensions + super(ImageFileSPM, self).__init__(value, filter, auto_set, entries, + exists, types, allow_compressed, + extensions, **metadata) diff --git a/nipype/interfaces/spm/preprocess.py b/nipype/interfaces/spm/preprocess.py index 55c625a2fb..c222c250ee 100644 --- a/nipype/interfaces/spm/preprocess.py +++ b/nipype/interfaces/spm/preprocess.py @@ -23,20 +23,18 @@ from ...utils.filemanip import (fname_presuffix, filename_to_list, list_to_filename, split_filename) from ..base import (OutputMultiPath, TraitedSpec, isdefined, - traits, InputMultiPath, File, ImageFile) + traits, InputMultiPath, File) from .base import (SPMCommand, scans_for_fname, func_is_3d, - scans_for_fnames, SPMCommandInputSpec) + scans_for_fnames, SPMCommandInputSpec, ImageFileSPM) __docformat__ = 'restructuredtext' class SliceTimingInputSpec(SPMCommandInputSpec): - in_files = InputMultiPath(traits.Either(traits.List(ImageFile( - exists=True, - compressed=False)), - ImageFile(exists=True, - compressed=False)), + in_files = InputMultiPath(traits.Either(traits.List(ImageFileSPM( + exists=True)), + ImageFileSPM(exists=True), field='scans', desc='list of filenames to apply slice timing', mandatory=True, copyfile=False) @@ -121,10 +119,9 @@ def _list_outputs(self): class RealignInputSpec(SPMCommandInputSpec): - in_files = InputMultiPath(traits.Either(traits.List(ImageFile(exists=True, - compressed=False)), - ImageFile(exists=True, - compressed=False)), field='data', + in_files = InputMultiPath(traits.Either(traits.List( + ImageFileSPM(exists=True)), + ImageFileSPM(exists=True)), field='data', mandatory=True, copyfile=True, desc='list of filenames to realign') jobtype = traits.Enum('estwrite', 'estimate', 'write', @@ -277,10 +274,10 @@ def _list_outputs(self): class CoregisterInputSpec(SPMCommandInputSpec): - target = ImageFile(exists=True, compressed=False, mandatory=True, + target = ImageFileSPM(exists=True, mandatory=True, field='ref', desc='reference file to register to', copyfile=False) - source = InputMultiPath(ImageFile(exists=True, compressed=False), + source = InputMultiPath(ImageFileSPM(exists=True), field='source', desc='file to register to target', copyfile=True, mandatory=True) jobtype = traits.Enum('estwrite', 'estimate', 'write', @@ -400,7 +397,7 @@ class NormalizeInputSpec(SPMCommandInputSpec): desc='template file to normalize to', mandatory=True, xor=['parameter_file'], copyfile=False) - source = InputMultiPath(ImageFile(exists=True, compressed=False), + source = InputMultiPath(ImageFileSPM(exists=True), field='subj.source', xor=['parameter_file'], desc='file to normalize to template', mandatory=True, copyfile=True) @@ -563,23 +560,22 @@ def _list_outputs(self): class Normalize12InputSpec(SPMCommandInputSpec): - image_to_align = ImageFile(exists=True, compressed=False, field='subj.vol', + image_to_align = ImageFileSPM(exists=True, field='subj.vol', desc=('file to estimate normalization parameters ' 'with'), xor=['deformation_file'], mandatory=True, copyfile=True) apply_to_files = InputMultiPath( - traits.Either(ImageFile(exists=True, compressed=False), - traits.List(ImageFile(exists=True, compressed=False))), + traits.Either(ImageFileSPM(exists=True), + traits.List(ImageFileSPM(exists=True))), field='subj.resample', desc='files to apply transformation to', copyfile=True) - deformation_file = ImageFile(field='subj.def', mandatory=True, - compressed=False, xor=['image_to_align', 'tpm'], + deformation_file = ImageFileSPM(field='subj.def', mandatory=True, + xor=['image_to_align', 'tpm'], copyfile=False, desc=('file y_*.nii containing 3 deformation ' 'fields for the deformation in x, y and z ' - 'dimension'), - copyfile=False) + 'dimension')) jobtype = traits.Enum('estwrite', 'est', 'write', usedefault=True, desc='Estimate, Write or do Both') bias_regularization = traits.Enum(0, 0.00001, 0.0001, 0.001, 0.01, 0.1, 1, @@ -730,7 +726,7 @@ def _list_outputs(self): class SegmentInputSpec(SPMCommandInputSpec): - data = InputMultiPath(ImageFile(exists=True, compressed=False), field='data', + data = InputMultiPath(ImageFileSPM(exists=True), field='data', desc='one scan per subject', copyfile=False, mandatory=True) gm_output_type = traits.List(traits.Bool(), minlen=3, maxlen=3, @@ -899,10 +895,9 @@ def _list_outputs(self): class NewSegmentInputSpec(SPMCommandInputSpec): - channel_files = InputMultiPath(ImageFile(exists=True, compressed=False), + channel_files = InputMultiPath(ImageFileSPM(exists=True), mandatory=True, desc="A list of files to be segmented", - field='channel', copyfile=False, - mandatory=True) + field='channel', copyfile=False) channel_info = traits.Tuple(traits.Float(), traits.Float(), traits.Tuple(traits.Bool, traits.Bool), desc="""A tuple with the following fields: @@ -911,8 +906,7 @@ class NewSegmentInputSpec(SPMCommandInputSpec): - which maps to save (Corrected, Field) - a tuple of two boolean values""", field='channel') tissues = traits.List( - traits.Tuple(traits.Tuple(ImageFile(exists=True, compressed=False), - traits.Int()), + traits.Tuple(traits.Tuple(ImageFileSPM(exists=True),traits.Int()), traits.Int(), traits.Tuple(traits.Bool, traits.Bool), traits.Tuple(traits.Bool, traits.Bool)), desc="""A list of tuples (one per tissue) with the following fields: @@ -1103,7 +1097,7 @@ def _list_outputs(self): class SmoothInputSpec(SPMCommandInputSpec): - in_files = InputMultiPath(ImageFile(exists=True, compressed=False), + in_files = InputMultiPath(ImageFileSPM(exists=True), field='data', desc='list of files to smooth', mandatory=True, copyfile=False) fwhm = traits.Either(traits.List(traits.Float(), minlen=3, maxlen=3), @@ -1166,8 +1160,7 @@ def _list_outputs(self): class DARTELInputSpec(SPMCommandInputSpec): - image_files = traits.List(traits.List(ImageFile(exists=True, - compressed=False)), + image_files = traits.List(traits.List(ImageFileSPM(exists=True)), desc="A list of files to be segmented", field='warp.images', copyfile=False, mandatory=True) @@ -1283,14 +1276,12 @@ def _list_outputs(self): class DARTELNorm2MNIInputSpec(SPMCommandInputSpec): - template_file = ImageFile(exists=True, compressed=False, - desc="DARTEL template", field='mni_norm.template', - copyfile=False, mandatory=True) - flowfield_files = InputMultiPath(ImageFile(exists=True, compressed=False), + template_file = ImageFileSPM(exists=True, copyfile=False, mandatory=True, + desc="DARTEL template", field='mni_norm.template') + flowfield_files = InputMultiPath(ImageFileSPM(exists=True), mandatory=True, desc="DARTEL flow fields u_rc1*", - field='mni_norm.data.subjs.flowfields', - mandatory=True) - apply_to_files = InputMultiPath(ImageFile(exists=True, compressed=False), + field='mni_norm.data.subjs.flowfields') + apply_to_files = InputMultiPath(ImageFileSPM(exists=True), desc="Files to apply the transform to", field='mni_norm.data.subjs.images', mandatory=True, copyfile=False) @@ -1380,14 +1371,12 @@ def _list_outputs(self): class CreateWarpedInputSpec(SPMCommandInputSpec): - image_files = InputMultiPath(ImageFile(exists=True, compressed=False), + image_files = InputMultiPath(ImageFileSPM(exists=True), mandatory=True, desc="A list of files to be warped", - field='crt_warped.images', copyfile=False, - mandatory=True) - flowfield_files = InputMultiPath(ImageFile(exists=True, compressed=False), + field='crt_warped.images', copyfile=False) + flowfield_files = InputMultiPath(ImageFileSPM(exists=True), copyfile=False, desc="DARTEL flow fields u_rc1*", field='crt_warped.flowfields', - copyfile=False, mandatory=True) iterations = traits.Range(low=0, high=9, desc=("The number of iterations: log2(number of " @@ -1450,10 +1439,10 @@ def _list_outputs(self): class ApplyDeformationFieldInputSpec(SPMCommandInputSpec): - in_files = InputMultiPath(ImageFile(exists=True, compressed=False), + in_files = InputMultiPath(ImageFileSPM(exists=True), mandatory=True, field='fnames') deformation_field = File(exists=True, mandatory=True, field='comp{1}.def') - reference_volume = ImageFile(exists=True, mandatory=True, compressed=False, + reference_volume = ImageFileSPM(exists=True, mandatory=True, field='comp{2}.id.space') interp = traits.Range(low=0, high=7, field='interp', desc='degree of b-spline used for interpolation') @@ -1496,13 +1485,12 @@ def _list_outputs(self): class VBMSegmentInputSpec(SPMCommandInputSpec): in_files = InputMultiPath( - ImageFile(exists=True, compressed=False), + ImageFileSPM(exists=True), desc="A list of files to be segmented", field='estwrite.data', copyfile=False, mandatory=True) - tissues = ImageFile( - exists=True, compressed=False, field='estwrite.tpm', - desc='tissue probability map') + tissues = ImageFileSPM( + exists=True, field='estwrite.tpm', desc='tissue probability map') gaussians_per_class = traits.Tuple( (2, 2, 2, 3, 4, 2), *([traits.Int()] * 6), usedefault=True, @@ -1528,8 +1516,8 @@ class VBMSegmentInputSpec(SPMCommandInputSpec): spatial_normalization = traits.Enum( 'high', 'low', usedefault=True,) - dartel_template = ImageFile( - exists=True, compressed=False, + dartel_template = ImageFileSPM( + exists=True, field='estwrite.extopts.dartelwarp.normhigh.darteltpm') use_sanlm_denoising_filter = traits.Range( 0, 2, 2, usedefault=True, field='estwrite.extopts.sanlm', diff --git a/nipype/interfaces/traits_extension.py b/nipype/interfaces/traits_extension.py index 9be62b799f..1dbaef23b8 100644 --- a/nipype/interfaces/traits_extension.py +++ b/nipype/interfaces/traits_extension.py @@ -178,14 +178,13 @@ def validate(self, object, name, value): if isinstance(value, (str, bytes)): if not self.exists: return value - if os.path.isdir(value): return value else: raise TraitError( args='The trait \'{}\' of {} instance is {}, but the path ' - ' \'{}\' does not exist.'.format(name, class_of(object), - self.info_text, value)) + ' \'{}\' does not exist.'.format(name, + class_of(object), self.info_text, value)) self.error(object, name, value) @@ -231,11 +230,10 @@ def __init__(self, value='', auto_set=False, entries=0, 'nifti1': [('.nii', '.nii.gz'), (('.hdr', '.img'), ('.hdr', '.img.gz'))], 'mgh': [('.mgh', '.mgz'), ('.mgh', '.mgh.gz')], - 'nifti2': [('.nii', '.nii.gz'), - (('.hdr', '.img'), ('.hdr', '.img.gz'))], - 'cifti2': [('.nii', '.nii.gz'), - (('.hdr', '.img'), ('.hdr', '.img.gz'))], - 'DICOM': [('.dcm', '.dcm'), ('.IMA', '.IMA'), ('.tar', '.tar.gz')], + 'nifti2': [('.nii', '.nii.gz')], + 'cifti2': [('.nii', '.nii.gz')], + 'gifti': [('.gii', '.gii.gz')], + 'dicom': [('.dcm', '.dcm'), ('.IMA', '.IMA'), ('.tar', '.tar.gz')], 'nrrd': [('.nrrd', 'nrrd'), ('nhdr', 'nhdr')], 'afni': [('.HEAD', '.HEAD'), ('.BRIK', '.BRIK')] } @@ -244,7 +242,7 @@ class ImageFile(File): """ Defines a trait of specific neuroimaging files """ def __init__(self, value='', filter=None, auto_set=False, entries=0, - exists=False, types=[], compressed=True, extensions=[], + exists=False, types=[], allow_compressed=True, extensions=[], **metadata): """ Trait handles neuroimaging files. @@ -256,22 +254,25 @@ def __init__(self, value='', filter=None, auto_set=False, entries=0, Indicates whether the file format can compressed """ self.types = types - self.compressed = compressed + self.allow_compressed = allow_compressed self.exts = extensions super(ImageFile, self).__init__(value, filter, auto_set, entries, exists, **metadata) def grab_exts(self, exts=[]): + # TODO: file type validation for fmt in self.types: if fmt in img_fmt_types: exts.extend(sum([[u for u in y[0]] if isinstance(y[0], tuple) else [y[0]] for y in img_fmt_types[fmt]], [])) - if self.compressed: - exts.extend(sum([[u for u in y[-1]] if isinstance(y[-1], tuple) - else [y[-1]] for y in img_fmt_types[fmt]], [])) + if self.allow_compressed: + exts.extend(sum([[u for u in y[-1]] if isinstance(y[-1], + tuple) else [y[-1]] for y in img_fmt_types[fmt]], [])) else: raise AttributeError('Information has not been added for format' - 'type {} yet.'.format(fmt)) + ' type {} yet. Supported formats include: ' + '{}'.format('hello', + ', '.join(img_fmt_types.keys()))) return list(set(exts)) def validate(self, object, name, value): From a2f434fc8470b5210119d6009e1bb64f142eb289 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Wed, 26 Apr 2017 14:33:13 -0400 Subject: [PATCH 5/7] fix: trailing parenthesis --- nipype/interfaces/spm/preprocess.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nipype/interfaces/spm/preprocess.py b/nipype/interfaces/spm/preprocess.py index c222c250ee..0eebf3c6b8 100644 --- a/nipype/interfaces/spm/preprocess.py +++ b/nipype/interfaces/spm/preprocess.py @@ -34,7 +34,7 @@ class SliceTimingInputSpec(SPMCommandInputSpec): in_files = InputMultiPath(traits.Either(traits.List(ImageFileSPM( exists=True)), - ImageFileSPM(exists=True), + ImageFileSPM(exists=True)), field='scans', desc='list of filenames to apply slice timing', mandatory=True, copyfile=False) From 71007689c88e98c2b7177b10d66aec4e99e11601 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Wed, 26 Apr 2017 15:47:01 -0400 Subject: [PATCH 6/7] fix: clarity allow_compression --- nipype/interfaces/spm/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nipype/interfaces/spm/base.py b/nipype/interfaces/spm/base.py index f7e7bba67f..af35a07833 100644 --- a/nipype/interfaces/spm/base.py +++ b/nipype/interfaces/spm/base.py @@ -551,7 +551,7 @@ def __init__(self, value='', filter=None, auto_set=False, entries=0, Indicates whether the file format can compressed """ self.types = types - self.compressed = compressed + self.allow_compressed = allow_compressed self.exts = extensions super(ImageFileSPM, self).__init__(value, filter, auto_set, entries, exists, types, allow_compressed, From bf564f7ea636bb637909fa16193438b6b369c138 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Mon, 1 May 2017 13:19:52 -0400 Subject: [PATCH 7/7] enh: preliminary testing + altered auto tests --- nipype/interfaces/fsl/tests/test_auto_Eddy.py | 3 --- nipype/interfaces/fsl/tests/test_auto_TOPUP.py | 10 +++++----- nipype/interfaces/spm/base.py | 5 ++--- nipype/interfaces/tests/test_base.py | 16 ++++++++++++++++ nipype/interfaces/traits_extension.py | 15 +++++++-------- 5 files changed, 30 insertions(+), 19 deletions(-) diff --git a/nipype/interfaces/fsl/tests/test_auto_Eddy.py b/nipype/interfaces/fsl/tests/test_auto_Eddy.py index e47633fb07..c5f521045f 100644 --- a/nipype/interfaces/fsl/tests/test_auto_Eddy.py +++ b/nipype/interfaces/fsl/tests/test_auto_Eddy.py @@ -88,9 +88,6 @@ def test_Eddy_inputs(): def test_Eddy_outputs(): output_map = dict(out_corrected=dict(), out_movement_rms=dict(), - out_outlier_map=dict(), - out_outlier_n_sd_map=dict(), - out_outlier_n_sqr_sd_map=dict(), out_outlier_report=dict(), out_parameter=dict(), out_restricted_movement_rms=dict(), diff --git a/nipype/interfaces/fsl/tests/test_auto_TOPUP.py b/nipype/interfaces/fsl/tests/test_auto_TOPUP.py index 01f50670ba..28083c6dc0 100644 --- a/nipype/interfaces/fsl/tests/test_auto_TOPUP.py +++ b/nipype/interfaces/fsl/tests/test_auto_TOPUP.py @@ -54,10 +54,6 @@ def test_TOPUP_inputs(): name_source=['in_file'], name_template='%s_field', ), - out_warp_prefix=dict(argstr='--dfout=%s', - hash_files=False, - usedefault=True, - ), out_jac_prefix=dict(argstr='--jacout=%s', hash_files=False, usedefault=True, @@ -68,6 +64,10 @@ def test_TOPUP_inputs(): name_source=['in_file'], name_template='%s_topup.log', ), + out_warp_prefix=dict(argstr='--dfout=%s', + hash_files=False, + usedefault=True, + ), output_type=dict(), readout_times=dict(mandatory=True, requires=['encoding_direction'], @@ -104,10 +104,10 @@ def test_TOPUP_outputs(): out_enc_file=dict(), out_field=dict(), out_fieldcoef=dict(), + out_jacs=dict(), out_logfile=dict(), out_movpar=dict(), out_warps=dict(), - out_jacs=dict(), ) outputs = TOPUP.output_spec() diff --git a/nipype/interfaces/spm/base.py b/nipype/interfaces/spm/base.py index af35a07833..6c3fbab32e 100644 --- a/nipype/interfaces/spm/base.py +++ b/nipype/interfaces/spm/base.py @@ -539,7 +539,7 @@ class ImageFileSPM(ImageFile): """ def __init__(self, value='', filter=None, auto_set=False, entries=0, - exists=False, types=['nifti1', 'nifti2'], extensions=[], + exists=False, types=['nifti1', 'nifti2'], allow_compressed=False, **metadata): """ Trait handles neuroimaging files. @@ -552,7 +552,6 @@ def __init__(self, value='', filter=None, auto_set=False, entries=0, """ self.types = types self.allow_compressed = allow_compressed - self.exts = extensions super(ImageFileSPM, self).__init__(value, filter, auto_set, entries, exists, types, allow_compressed, - extensions, **metadata) + **metadata) diff --git a/nipype/interfaces/tests/test_base.py b/nipype/interfaces/tests/test_base.py index e27779ce02..34d1134e42 100644 --- a/nipype/interfaces/tests/test_base.py +++ b/nipype/interfaces/tests/test_base.py @@ -718,3 +718,19 @@ def to_list(x): failed_dict[key] = (value, newval) return failed_dict +def test_ImageFile(): + x = nib.BaseInterface().inputs + + # setup traits + x.add_trait('nifti', nib.ImageFile(types=['nifti1', 'dicom'])) + x.add_trait('anytype', nib.ImageFile()) + x.add_trait('newtype', nib.ImageFile(types=['nifti10'])) + x.add_trait('nocompress', nib.ImageFile(types=['mgh'], + allow_compressed=False)) + + with pytest.raises(nib.TraitError): x.nifti = 'test.mgz' + x.nifti = 'test.nii' + x.anytype = 'test.xml' + with pytest.raises(AttributeError): x.newtype = 'test.nii' + with pytest.raises(nib.TraitError): x.nocompress = 'test.nii.gz' + x.nocompress = 'test.mgh' diff --git a/nipype/interfaces/traits_extension.py b/nipype/interfaces/traits_extension.py index 1dbaef23b8..0e84c15bce 100644 --- a/nipype/interfaces/traits_extension.py +++ b/nipype/interfaces/traits_extension.py @@ -242,8 +242,7 @@ class ImageFile(File): """ Defines a trait of specific neuroimaging files """ def __init__(self, value='', filter=None, auto_set=False, entries=0, - exists=False, types=[], allow_compressed=True, extensions=[], - **metadata): + exists=False, types=[], allow_compressed=True, **metadata): """ Trait handles neuroimaging files. Parameters @@ -255,12 +254,12 @@ def __init__(self, value='', filter=None, auto_set=False, entries=0, """ self.types = types self.allow_compressed = allow_compressed - self.exts = extensions super(ImageFile, self).__init__(value, filter, auto_set, entries, exists, **metadata) - def grab_exts(self, exts=[]): + def grab_exts(self): # TODO: file type validation + exts = [] for fmt in self.types: if fmt in img_fmt_types: exts.extend(sum([[u for u in y[0]] if isinstance(y[0], tuple) @@ -271,7 +270,7 @@ def grab_exts(self, exts=[]): else: raise AttributeError('Information has not been added for format' ' type {} yet. Supported formats include: ' - '{}'.format('hello', + '{}'.format(fmt, ', '.join(img_fmt_types.keys()))) return list(set(exts)) @@ -280,11 +279,11 @@ def validate(self, object, name, value): """ validated_value = super(ImageFile, self).validate(object, name, value) if validated_value and self.types: - self.exts = self.grab_exts(self.exts) - if not any(validated_value.endswith(x) for x in self.exts): + self._exts = self.grab_exts() + if not any(validated_value.endswith(x) for x in self._exts): raise TraitError( args="{} is not included in allowed types: {}".format( - validated_value, ','.join(self.exts))) + validated_value, ', '.join(self._exts))) return validated_value """