Skip to content

Commit f0c7abd

Browse files
author
Mathieu Dubois
committed
Merge branch 'master' into fix_tutorials
2 parents 20032a2 + 0434e47 commit f0c7abd

File tree

12 files changed

+318
-75
lines changed

12 files changed

+318
-75
lines changed

CHANGES

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
Next release
22
============
33

4+
* ENH: Inputs with name_source can be now chained in cascade (https://github.com/nipy/nipype/pull/938)
5+
* ENH: Improve JSON interfaces: default settings when reading and consistent output creation
6+
when writing (https://github.com/nipy/nipype/pull/1047)
7+
* FIX: AddCSVRow problems when using infields (https://github.com/nipy/nipype/pull/1028)
48
* FIX: Removed unused ANTS registration flag (https://github.com/nipy/nipype/pull/999)
59
* FIX: Amend create_tbss_non_fa() workflow to match FSL's tbss_non_fa command. (https://github.com/nipy/nipype/pull/1033)
610
* FIX: remove unused mandatory flag from spm normalize (https://github.com/nipy/nipype/pull/1048)

doc/devel/interface_specs.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,9 @@ CommandLine
358358
``name_source``
359359
Indicates the list of input fields from which the value of the current File
360360
output variable will be drawn. This input field must be the name of a File.
361+
Chaining is allowed, meaning that an input field can point to another as
362+
``name_source``, which also points as ``name_source`` to a third field.
363+
In this situation, the templates for substitutions are also accumulated.
361364

362365
``name_template``
363366
By default a ``%s_generated`` template is used to create the output

examples/rsfmri_vol_surface_preprocessing.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ def build_filter1(motion_params, comp_norm, outliers, detrend_poly=None):
230230
out_params = np.hstack((out_params, outlier_vector))
231231
if detrend_poly:
232232
timepoints = out_params.shape[0]
233-
X = np.ones((timepoints, 1))
233+
X = np.empty((timepoints, 0))
234234
for i in range(detrend_poly):
235235
X = np.hstack((X, legendre(
236236
i + 1)(np.linspace(-1, 1, timepoints))[:, None]))
@@ -542,7 +542,7 @@ def create_reg_workflow(name='registration'):
542542

543543
warpmean = Node(ants.ApplyTransforms(), name='warpmean')
544544
warpmean.inputs.input_image_type = 3
545-
warpmean.inputs.interpolation = 'BSpline'
545+
warpmean.inputs.interpolation = 'Linear'
546546
warpmean.inputs.invert_transform_flags = [False, False]
547547
warpmean.inputs.terminal_output = 'file'
548548
warpmean.inputs.args = '--float'
@@ -771,7 +771,7 @@ def merge_files(in1, in2):
771771
warpall = MapNode(ants.ApplyTransforms(), iterfield=['input_image'],
772772
name='warpall')
773773
warpall.inputs.input_image_type = 3
774-
warpall.inputs.interpolation = 'BSpline'
774+
warpall.inputs.interpolation = 'Linear'
775775
warpall.inputs.invert_transform_flags = [False, False]
776776
warpall.inputs.terminal_output = 'file'
777777
warpall.inputs.reference_image = target_file
@@ -1003,9 +1003,11 @@ def create_resting_workflow(args, name=None):
10031003
parser.add_argument('--surf_fwhm', default=15., dest='surf_fwhm',
10041004
type=float, help="Spatial FWHM" + defstr)
10051005
parser.add_argument("-l", "--lowpass_freq", dest="lowpass_freq",
1006-
default=0.1, help="Low pass frequency (Hz)" + defstr)
1006+
default=0.1, type=float,
1007+
help="Low pass frequency (Hz)" + defstr)
10071008
parser.add_argument("-u", "--highpass_freq", dest="highpass_freq",
1008-
default=0.01, help="High pass frequency (Hz)" + defstr)
1009+
default=0.01, type=float,
1010+
help="High pass frequency (Hz)" + defstr)
10091011
parser.add_argument("-o", "--output_dir", dest="sink",
10101012
help="Output directory base", required=True)
10111013
parser.add_argument("-w", "--work_dir", dest="work_dir",

examples/rsfmri_vol_surface_preprocessing_nipy.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ def build_filter1(motion_params, comp_norm, outliers, detrend_poly=None):
226226
out_params = np.hstack((out_params, outlier_vector))
227227
if detrend_poly:
228228
timepoints = out_params.shape[0]
229-
X = np.ones((timepoints, 1))
229+
X = np.empty((timepoints, 0))
230230
for i in range(detrend_poly):
231231
X = np.hstack((X, legendre(
232232
i + 1)(np.linspace(-1, 1, timepoints))[:, None]))
@@ -538,7 +538,7 @@ def create_reg_workflow(name='registration'):
538538

539539
warpmean = Node(ants.ApplyTransforms(), name='warpmean')
540540
warpmean.inputs.input_image_type = 3
541-
warpmean.inputs.interpolation = 'BSpline'
541+
warpmean.inputs.interpolation = 'Linear'
542542
warpmean.inputs.invert_transform_flags = [False, False]
543543
warpmean.inputs.terminal_output = 'file'
544544
warpmean.inputs.args = '--float'
@@ -759,7 +759,7 @@ def merge_files(in1, in2):
759759
warpall = MapNode(ants.ApplyTransforms(), iterfield=['input_image'],
760760
name='warpall')
761761
warpall.inputs.input_image_type = 3
762-
warpall.inputs.interpolation = 'BSpline'
762+
warpall.inputs.interpolation = 'Linear'
763763
warpall.inputs.invert_transform_flags = [False, False]
764764
warpall.inputs.terminal_output = 'file'
765765
warpall.inputs.reference_image = target_file
@@ -1006,9 +1006,11 @@ def create_resting_workflow(args, name=None):
10061006
parser.add_argument('--surf_fwhm', default=15., dest='surf_fwhm',
10071007
type=float, help="Spatial FWHM" + defstr)
10081008
parser.add_argument("-l", "--lowpass_freq", dest="lowpass_freq",
1009-
default=0.1, help="Low pass frequency (Hz)" + defstr)
1009+
default=0.1, type=float,
1010+
help="Low pass frequency (Hz)" + defstr)
10101011
parser.add_argument("-u", "--highpass_freq", dest="highpass_freq",
1011-
default=0.01, help="High pass frequency (Hz)" + defstr)
1012+
default=0.01, type=float,
1013+
help="High pass frequency (Hz)" + defstr)
10121014
parser.add_argument("-o", "--output_dir", dest="sink",
10131015
help="Output directory base", required=True)
10141016
parser.add_argument("-w", "--work_dir", dest="work_dir",

nipype/algorithms/misc.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from ..interfaces.base import (BaseInterface, traits, TraitedSpec, File,
3535
InputMultiPath, OutputMultiPath,
3636
BaseInterfaceInputSpec, isdefined,
37-
DynamicTraitedSpec)
37+
DynamicTraitedSpec, Undefined)
3838
from nipype.utils.filemanip import fname_presuffix, split_filename
3939
iflogger = logging.getLogger('interface')
4040

@@ -785,7 +785,8 @@ def _list_outputs(self):
785785

786786

787787
class AddCSVRowInputSpec(DynamicTraitedSpec, BaseInterfaceInputSpec):
788-
in_file = traits.File(mandatory=True, desc='Input comma-separated value (CSV) files')
788+
in_file = traits.File(mandatory=True,
789+
desc='Input comma-separated value (CSV) files')
789790
_outputs = traits.Dict(traits.Any, value={}, usedefault=True)
790791

791792
def __setattr__(self, key, value):
@@ -804,13 +805,15 @@ class AddCSVRowOutputSpec(TraitedSpec):
804805

805806

806807
class AddCSVRow(BaseInterface):
808+
807809
"""Simple interface to add an extra row to a csv file
808810
809811
.. note:: Requires `pandas <http://pandas.pydata.org/>`_
810812
811813
.. warning:: Multi-platform thread-safe execution is possible with
812-
`lockfile <https://pythonhosted.org/lockfile/lockfile.html>`_. Please recall that (1)
813-
this module is alpha software; and (2) it should be installed for thread-safe writing.
814+
`lockfile <https://pythonhosted.org/lockfile/lockfile.html>`_. Please
815+
recall that (1) this module is alpha software; and (2) it should be
816+
installed for thread-safe writing.
814817
If lockfile is not installed, then the interface is not thread-safe.
815818
816819
@@ -822,7 +825,7 @@ class AddCSVRow(BaseInterface):
822825
>>> addrow.inputs.in_file = 'scores.csv'
823826
>>> addrow.inputs.si = 0.74
824827
>>> addrow.inputs.di = 0.93
825-
>>> addrow.subject_id = 'S400'
828+
>>> addrow.inputs.subject_id = 'S400'
826829
>>> addrow.inputs.list_of_values = [ 0.4, 0.7, 0.3 ]
827830
>>> addrow.run() # doctest: +SKIP
828831
"""
@@ -850,22 +853,26 @@ def _run_interface(self, runtime):
850853
try:
851854
import pandas as pd
852855
except ImportError:
853-
raise ImportError('This interface requires pandas (http://pandas.pydata.org/) to run.')
856+
raise ImportError(('This interface requires pandas '
857+
'(http://pandas.pydata.org/) to run.'))
854858

855859
try:
856860
import lockfile as pl
857861
self._have_lock = True
858862
except ImportError:
859-
import warnings
860-
warnings.warn(('Python module lockfile was not found: AddCSVRow will not be thread-safe '
861-
'in multi-processor execution'))
863+
from warnings import warn
864+
warn(('Python module lockfile was not found: AddCSVRow will not be'
865+
' thread-safe in multi-processor execution'))
862866

863867
input_dict = {}
864868
for key, val in self.inputs._outputs.items():
865869
# expand lists to several columns
870+
if key == 'trait_added' and val in self.inputs.copyable_trait_names():
871+
continue
872+
866873
if isinstance(val, list):
867-
for i,v in enumerate(val):
868-
input_dict['%s_%d' % (key,i)]=v
874+
for i, v in enumerate(val):
875+
input_dict['%s_%d' % (key, i)] = v
869876
else:
870877
input_dict[key] = val
871878

@@ -887,6 +894,13 @@ def _run_interface(self, runtime):
887894
if self._have_lock:
888895
self._lock.release()
889896

897+
# Using nipype.external.portalocker this might be something like:
898+
# with pl.Lock(self.inputs.in_file, timeout=1) as fh:
899+
# if op.exists(fh):
900+
# formerdf = pd.read_csv(fh, index_col=0)
901+
# df = pd.concat([formerdf, df], ignore_index=True)
902+
# df.to_csv(fh)
903+
890904
return runtime
891905

892906
def _list_outputs(self):

nipype/interfaces/ants/registration.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -394,8 +394,15 @@ class Registration(ANTSCommand):
394394
>>> reg4.inputs.collapse_output_transforms = True
395395
>>> outputs = reg4._list_outputs()
396396
>>> print outputs #doctest: +ELLIPSIS
397-
{'reverse_invert_flags': [True, False], 'inverse_composite_transform': ['.../nipype/testing/data/output_InverseComposite.h5'], 'warped_image': '.../nipype/testing/data/output_warped_image.nii.gz', 'inverse_warped_image': <undefined>, 'forward_invert_flags': [False, False], 'reverse_transforms': ['.../nipype/testing/data/output_0GenericAffine.mat', '.../nipype/testing/data/output_1InverseWarp.nii.gz'], 'composite_transform': ['.../nipype/testing/data/output_Composite.h5'], 'forward_transforms': ['.../nipype/testing/data/output_0GenericAffine.mat', '.../nipype/testing/data/output_1Warp.nii.gz']}
398-
>>> reg4.aggregate_outputs() #doctest: +SKIP
397+
{'reverse_invert_flags': [], 'inverse_composite_transform': ['.../nipype/testing/data/output_InverseComposite.h5'], 'warped_image': '.../nipype/testing/data/output_warped_image.nii.gz', 'inverse_warped_image': <undefined>, 'forward_invert_flags': [], 'reverse_transforms': [], 'composite_transform': ['.../nipype/testing/data/output_Composite.h5'], 'forward_transforms': []}
398+
399+
>>> # Test collapse transforms flag
400+
>>> reg4b = copy.deepcopy(reg4)
401+
>>> reg4b.inputs.write_composite_transform = False
402+
>>> outputs = reg4b._list_outputs()
403+
>>> print outputs #doctest: +ELLIPSIS
404+
{'reverse_invert_flags': [True, False], 'inverse_composite_transform': <undefined>, 'warped_image': '.../nipype/testing/data/output_warped_image.nii.gz', 'inverse_warped_image': <undefined>, 'forward_invert_flags': [False, False], 'reverse_transforms': ['.../nipype/testing/data/output_0GenericAffine.mat', '.../nipype/testing/data/output_1InverseWarp.nii.gz'], 'composite_transform': <undefined>, 'forward_transforms': ['.../nipype/testing/data/output_0GenericAffine.mat', '.../nipype/testing/data/output_1Warp.nii.gz']}
405+
>>> reg4b.aggregate_outputs() #doctest: +SKIP
399406
400407
>>> # Test multiple metrics per stage
401408
>>> reg5 = copy.deepcopy(reg)
@@ -709,7 +716,7 @@ def _list_outputs(self):
709716
outputs[
710717
'reverse_invert_flags'].insert(0, reverseInverseMode)
711718
transformCount += 1
712-
else:
719+
elif not self.inputs.write_composite_transform:
713720
transformCount = 0
714721
isLinear = [any(self._linear_transform_names == t)
715722
for t in self.inputs.transforms]

nipype/interfaces/base.py

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@
4747

4848
__docformat__ = 'restructuredtext'
4949

50+
class NipypeInterfaceError(Exception):
51+
def __init__(self, value):
52+
self.value = value
53+
def __str__(self):
54+
return repr(self.value)
55+
5056
def _lock_files():
5157
tmpdir = '/tmp'
5258
pattern = '.X*-lock'
@@ -1510,9 +1516,13 @@ def _format_arg(self, name, trait_spec, value):
15101516
# Append options using format string.
15111517
return argstr % value
15121518

1513-
def _filename_from_source(self, name):
1519+
def _filename_from_source(self, name, chain=None):
1520+
if chain is None:
1521+
chain = []
1522+
15141523
trait_spec = self.inputs.trait(name)
15151524
retval = getattr(self.inputs, name)
1525+
15161526
if not isdefined(retval) or "%s" in retval:
15171527
if not trait_spec.name_source:
15181528
return retval
@@ -1522,26 +1532,42 @@ def _filename_from_source(self, name):
15221532
name_template = trait_spec.name_template
15231533
if not name_template:
15241534
name_template = "%s_generated"
1525-
if isinstance(trait_spec.name_source, list):
1526-
for ns in trait_spec.name_source:
1527-
if isdefined(getattr(self.inputs, ns)):
1528-
name_source = ns
1529-
break
1535+
1536+
ns = trait_spec.name_source
1537+
while isinstance(ns, list):
1538+
if len(ns) > 1:
1539+
iflogger.warn('Only one name_source per trait is allowed')
1540+
ns = ns[0]
1541+
1542+
if not isinstance(ns, six.string_types):
1543+
raise ValueError(('name_source of \'%s\' trait sould be an '
1544+
'input trait name') % name)
1545+
1546+
if isdefined(getattr(self.inputs, ns)):
1547+
name_source = ns
1548+
source = getattr(self.inputs, name_source)
1549+
while isinstance(source, list):
1550+
source = source[0]
1551+
1552+
# special treatment for files
1553+
try:
1554+
_, base, _ = split_filename(source)
1555+
except AttributeError:
1556+
base = source
15301557
else:
1531-
name_source = trait_spec.name_source
1532-
source = getattr(self.inputs, name_source)
1533-
while isinstance(source, list):
1534-
source = source[0]
1535-
#special treatment for files
1536-
try:
1537-
_, base, _ = split_filename(source)
1538-
except AttributeError:
1539-
base = source
1558+
if name in chain:
1559+
raise NipypeInterfaceError('Mutually pointing name_sources')
1560+
1561+
chain.append(name)
1562+
base = self._filename_from_source(ns, chain)
1563+
1564+
chain = None
15401565
retval = name_template % base
15411566
_, _, ext = split_filename(retval)
15421567
if trait_spec.keep_extension and ext:
15431568
return retval
15441569
return self._overload_extension(retval, name)
1570+
15451571
return retval
15461572

15471573
def _gen_filename(self, name):
@@ -1557,7 +1583,7 @@ def _list_outputs(self):
15571583
outputs = self.output_spec().get()
15581584
for name, trait_spec in traits.iteritems():
15591585
out_name = name
1560-
if trait_spec.output_name != None:
1586+
if trait_spec.output_name is not None:
15611587
out_name = trait_spec.output_name
15621588
outputs[out_name] = \
15631589
os.path.abspath(self._filename_from_source(name))

0 commit comments

Comments
 (0)