Skip to content

Commit a762317

Browse files
committed
[skip ci] Merge remote-tracking branch 'upstream/master' into fix/resource-monitor-revisions
2 parents 7220ab3 + d6c4957 commit a762317

File tree

8 files changed

+122
-53
lines changed

8 files changed

+122
-53
lines changed

CHANGES

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ Upcoming release
88
###### [Full changelog](https://github.com/nipy/nipype/milestone/13)
99

1010
* FIX+MAINT: Revision of the resource monitor (https://github.com/nipy/nipype/pull/2285)
11-
* ENH: Memoize version checks (https://github.com/nipy/nipype/pull/2274, https://github.com/nipy/nipype/pull/2295)
11+
* FIX: MultiProc mishandling crashes (https://github.com/nipy/nipype/pull/2301)
12+
* MAINT: Revise use of `subprocess.Popen` (https://github.com/nipy/nipype/pull/2289)
13+
* ENH: Memorize version checks (https://github.com/nipy/nipype/pull/2274, https://github.com/nipy/nipype/pull/2295)
1214

1315

1416
0.14.0rc1 (November 21, 2017)

nipype/algorithms/confounds.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from numpy.polynomial import Legendre
2323
from scipy import linalg
2424

25-
from .. import logging
25+
from .. import config, logging
2626
from ..external.due import BibTeX
2727
from ..interfaces.base import (traits, TraitedSpec, BaseInterface,
2828
BaseInterfaceInputSpec, File, isdefined,
@@ -816,7 +816,7 @@ def plot_confound(tseries, figsize, name, units=None,
816816
817817
"""
818818
import matplotlib
819-
matplotlib.use('Agg')
819+
matplotlib.use(config.get('execution', 'matplotlib_backend'))
820820
import matplotlib.pyplot as plt
821821
from matplotlib.gridspec import GridSpec
822822
from matplotlib.backends.backend_pdf import FigureCanvasPdf as FigureCanvas

nipype/algorithms/metrics.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from scipy.spatial.distance import cdist, euclidean, dice, jaccard
2525
from scipy.ndimage.measurements import center_of_mass, label
2626

27-
from .. import logging
27+
from .. import config, logging
2828
from ..utils.misc import package_check
2929

3030
from ..interfaces.base import (BaseInterface, traits, TraitedSpec, File,
@@ -138,6 +138,8 @@ def _eucl_mean(self, nii1, nii2, weighted=False):
138138

139139
dist_matrix = cdist(set1_coordinates.T, set2_coordinates.T)
140140
min_dist_matrix = np.amin(dist_matrix, axis=0)
141+
import matplotlib
142+
matplotlib.use(config.get('execution', 'matplotlib_backend'))
141143
import matplotlib.pyplot as plt
142144
plt.figure()
143145
plt.hist(min_dist_matrix, 50, normed=1, facecolor='green')

nipype/interfaces/ants/registration.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
# -*- coding: utf-8 -*-
2-
"""The ants module provides basic functions for interfacing with ants functions.
2+
"""The ants module provides basic functions for interfacing with ants
3+
functions.
34
45
Change directory to provide relative paths for doctests
56
>>> import os
67
>>> filepath = os.path.dirname( os.path.realpath( __file__ ) )
78
>>> datadir = os.path.realpath(os.path.join(filepath, '../../testing/data'))
89
>>> os.chdir(datadir)
910
"""
10-
from __future__ import print_function, division, unicode_literals, absolute_import
11+
from __future__ import (print_function, division, unicode_literals,
12+
absolute_import)
1113
from builtins import range, str
1214
import os
1315

@@ -20,17 +22,19 @@ class ANTSInputSpec(ANTSCommandInputSpec):
2022
dimension = traits.Enum(3, 2, argstr='%d', usedefault=False,
2123
position=1, desc='image dimension (2 or 3)')
2224
fixed_image = InputMultiPath(File(exists=True), mandatory=True,
23-
desc=('image to which the moving image is warped'))
25+
desc=('image to which the moving image is '
26+
'warped'))
2427
moving_image = InputMultiPath(File(exists=True), argstr='%s',
2528
mandatory=True,
26-
desc=('image to apply transformation to (generally a coregistered '
29+
desc=('image to apply transformation to '
30+
'(generally a coregistered'
2731
'functional)'))
2832

2933
# Not all metrics are appropriate for all modalities. Also, not all metrics
30-
# are efficeint or appropriate at all resolution levels, Some metrics perform
31-
# well for gross global registraiton, but do poorly for small changes (i.e.
32-
# Mattes), and some metrics do well for small changes but don't work well for
33-
# gross level changes (i.e. 'CC').
34+
# are efficeint or appropriate at all resolution levels, Some metrics
35+
# perform well for gross global registraiton, but do poorly for small
36+
# changes (i.e. Mattes), and some metrics do well for small changes but
37+
# don't work well for gross level changes (i.e. 'CC').
3438
#
3539
# This is a two stage registration. in the first stage
3640
# [ 'Mattes', .................]
@@ -49,10 +53,18 @@ class ANTSInputSpec(ANTSCommandInputSpec):
4953
metric = traits.List(traits.Enum('CC', 'MI', 'SMI', 'PR', 'SSD',
5054
'MSQ', 'PSE'), mandatory=True, desc='')
5155

52-
metric_weight = traits.List(traits.Float(), requires=['metric'], desc='')
53-
radius = traits.List(traits.Int(), requires=['metric'], desc='')
56+
metric_weight = traits.List(traits.Float(), value=[1.0], usedefault=True,
57+
requires=['metric'], mandatory=True,
58+
desc='the metric weight(s) for each stage. '
59+
'The weights must sum to 1 per stage.')
5460

55-
output_transform_prefix = Str('out', usedefault=True, argstr='--output-naming %s',
61+
radius = traits.List(traits.Int(), requires=['metric'], mandatory=True,
62+
desc='radius of the region (i.e. number of layers'
63+
' around a voxel point)'
64+
' that is used for computing cross correlation')
65+
66+
output_transform_prefix = Str('out', usedefault=True,
67+
argstr='--output-naming %s',
5668
mandatory=True, desc='')
5769
transformation_model = traits.Enum('Diff', 'Elast', 'Exp', 'Greedy Exp',
5870
'SyN', argstr='%s', mandatory=True,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
2+
# vi: set ft=python sts=4 ts=4 sw=4 et:
3+
from __future__ import unicode_literals
4+
from nipype.interfaces.ants import registration
5+
import os
6+
import pytest
7+
8+
9+
def test_ants_mand(tmpdir):
10+
tmpdir.chdir()
11+
filepath = os.path.dirname(os.path.realpath(__file__))
12+
datadir = os.path.realpath(os.path.join(filepath, '../../../testing/data'))
13+
14+
ants = registration.ANTS()
15+
ants.inputs.transformation_model = "SyN"
16+
ants.inputs.moving_image = [os.path.join(datadir, 'resting.nii')]
17+
ants.inputs.fixed_image = [os.path.join(datadir, 'T1.nii')]
18+
ants.inputs.metric = ['MI']
19+
20+
with pytest.raises(ValueError) as er:
21+
ants.run()
22+
assert "ANTS requires a value for input 'radius'" in str(er.value)

nipype/interfaces/base.py

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@
99
Requires Packages to be installed
1010
"""
1111
from __future__ import print_function, division, unicode_literals, absolute_import
12-
from future import standard_library
13-
standard_library.install_aliases()
12+
import gc
13+
1414
from builtins import range, object, open, str, bytes
1515

16-
from configparser import NoOptionError
1716
from copy import deepcopy
1817
import datetime
1918
from datetime import datetime as dt
@@ -26,7 +25,6 @@
2625
import select
2726
import subprocess as sp
2827
import sys
29-
import time
3028
from textwrap import wrap
3129
from warnings import warn
3230
import simplejson as json
@@ -43,6 +41,8 @@
4341
traits, Undefined, TraitDictObject, TraitListObject, TraitError, isdefined,
4442
File, Directory, DictStrStr, has_metadata, ImageFile)
4543
from ..external.due import due
44+
from future import standard_library
45+
standard_library.install_aliases()
4646

4747
nipype_version = Version(__version__)
4848
iflogger = logging.getLogger('interface')
@@ -58,6 +58,7 @@
5858
class Str(traits.Unicode):
5959
"""Replacement for the default traits.Str based in bytes"""
6060

61+
6162
traits.Str = Str
6263

6364

@@ -1260,6 +1261,7 @@ class SimpleInterface(BaseInterface):
12601261
>>> os.chdir(old.strpath)
12611262
12621263
"""
1264+
12631265
def __init__(self, from_file=None, resource_monitor=None, **inputs):
12641266
super(SimpleInterface, self).__init__(
12651267
from_file=from_file, resource_monitor=resource_monitor, **inputs)
@@ -1387,8 +1389,7 @@ def run_command(runtime, output=None, timeout=0.01):
13871389
shell=True,
13881390
cwd=runtime.cwd,
13891391
env=env,
1390-
close_fds=True,
1391-
)
1392+
close_fds=True)
13921393
result = {
13931394
'stdout': [],
13941395
'stderr': [],
@@ -1427,37 +1428,50 @@ def _process(drain=0):
14271428
temp.sort()
14281429
result['merged'] = [r[1] for r in temp]
14291430

1430-
if output == 'allatonce':
1431-
stdout, stderr = proc.communicate()
1432-
result['stdout'] = read_stream(stdout, logger=iflogger)
1433-
result['stderr'] = read_stream(stderr, logger=iflogger)
1434-
1435-
elif output.startswith('file'):
1431+
if output.startswith('file'):
14361432
proc.wait()
14371433
if outfile is not None:
14381434
stdout.flush()
14391435
stdout.close()
14401436
with open(outfile, 'rb') as ofh:
14411437
stdoutstr = ofh.read()
14421438
result['stdout'] = read_stream(stdoutstr, logger=iflogger)
1439+
del stdoutstr
14431440

14441441
if errfile is not None:
14451442
stderr.flush()
14461443
stderr.close()
14471444
with open(errfile, 'rb') as efh:
14481445
stderrstr = efh.read()
14491446
result['stderr'] = read_stream(stderrstr, logger=iflogger)
1447+
del stderrstr
14501448

14511449
if output == 'file':
14521450
result['merged'] = result['stdout']
14531451
result['stdout'] = []
14541452
else:
1455-
proc.communicate() # Discard stdout and stderr
1453+
stdout, stderr = proc.communicate()
1454+
if output == 'allatonce': # Discard stdout and stderr otherwise
1455+
result['stdout'] = read_stream(stdout, logger=iflogger)
1456+
result['stderr'] = read_stream(stderr, logger=iflogger)
1457+
1458+
runtime.returncode = proc.returncode
1459+
try:
1460+
proc.terminate() # Ensure we are done
1461+
except OSError as error:
1462+
# Python 2 raises when the process is already gone
1463+
if error.errno != errno.ESRCH:
1464+
raise
1465+
1466+
# Dereference & force GC for a cleanup
1467+
del proc
1468+
del stdout
1469+
del stderr
1470+
gc.collect()
14561471

14571472
runtime.stderr = '\n'.join(result['stderr'])
14581473
runtime.stdout = '\n'.join(result['stdout'])
14591474
runtime.merged = '\n'.join(result['merged'])
1460-
runtime.returncode = proc.returncode
14611475
return runtime
14621476

14631477

@@ -1467,21 +1481,26 @@ def get_dependencies(name, environ):
14671481
Uses otool on darwin, ldd on linux. Currently doesn't support windows.
14681482
14691483
"""
1484+
cmd = None
14701485
if sys.platform == 'darwin':
1471-
proc = sp.Popen('otool -L `which %s`' % name,
1472-
stdout=sp.PIPE,
1473-
stderr=sp.PIPE,
1474-
shell=True,
1475-
env=environ)
1486+
cmd = 'otool -L `which {}`'.format
14761487
elif 'linux' in sys.platform:
1477-
proc = sp.Popen('ldd `which %s`' % name,
1478-
stdout=sp.PIPE,
1479-
stderr=sp.PIPE,
1480-
shell=True,
1481-
env=environ)
1482-
else:
1488+
cmd = 'ldd -L `which {}`'.format
1489+
1490+
if cmd is None:
14831491
return 'Platform %s not supported' % sys.platform
1484-
o, e = proc.communicate()
1492+
1493+
try:
1494+
proc = sp.Popen(
1495+
cmd(name), stdout=sp.PIPE, stderr=sp.PIPE, shell=True,
1496+
env=environ, close_fds=True)
1497+
o, _ = proc.communicate()
1498+
proc.terminate()
1499+
gc.collect()
1500+
except:
1501+
iflogger.warning(
1502+
'Could not get linked libraries for "%s".', name)
1503+
return 'Failed collecting dependencies'
14851504
return o.rstrip()
14861505

14871506

@@ -1572,6 +1591,9 @@ def __init__(self, command=None, terminal_output=None, **inputs):
15721591
# Set command. Input argument takes precedence
15731592
self._cmd = command or getattr(self, '_cmd', None)
15741593

1594+
# Store dependencies in runtime object
1595+
self._ldd = str2bool(config.get('execution', 'get_linked_libs', 'true'))
1596+
15751597
if self._cmd is None:
15761598
raise Exception("Missing command")
15771599

@@ -1620,6 +1642,8 @@ def _get_environ(self):
16201642
return getattr(self.inputs, 'environ', {})
16211643

16221644
def version_from_command(self, flag='-v'):
1645+
iflogger.warning('version_from_command member of CommandLine was '
1646+
'Deprecated in nipype-1.0.0 and deleted in 1.1.0')
16231647
cmdname = self.cmd.split()[0]
16241648
env = dict(os.environ)
16251649
if _exists_in_path(cmdname, env):
@@ -1664,7 +1688,8 @@ def _run_interface(self, runtime, correct_return_codes=(0,)):
16641688
(self.cmd.split()[0], runtime.hostname))
16651689

16661690
runtime.command_path = cmd_path
1667-
runtime.dependencies = get_dependencies(executable_name, runtime.environ)
1691+
runtime.dependencies = (get_dependencies(executable_name, runtime.environ)
1692+
if self._ldd else '<skipped>')
16681693
runtime = run_command(runtime, output=self.terminal_output)
16691694
if runtime.returncode is None or \
16701695
runtime.returncode not in correct_return_codes:

nipype/pipeline/engine/workflows.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262

6363
logger = logging.getLogger('workflow')
6464

65+
6566
class Workflow(EngineBase):
6667
"""Controls the setup and execution of a pipeline of processes."""
6768

@@ -196,7 +197,7 @@ def connect(self, *args, **kwargs):
196197
# determine their inputs/outputs depending on
197198
# connection settings. Skip these modules in the check
198199
if dest in connected_ports[destnode]:
199-
raise Exception("""
200+
raise Exception("""\
200201
Trying to connect %s:%s to %s:%s but input '%s' of node '%s' is already
201202
connected.
202203
""" % (srcnode, source, destnode, dest, dest, destnode))
@@ -297,7 +298,7 @@ def disconnect(self, *args):
297298
remove = []
298299
for edge in conn:
299300
if edge in ed_conns:
300-
idx = ed_conns.index(edge)
301+
# idx = ed_conns.index(edge)
301302
remove.append((edge[0], edge[1]))
302303

303304
logger.debug('disconnect(): remove list %s', to_str(remove))
@@ -426,7 +427,7 @@ def write_graph(self, dotfilename='graph.dot', graph2use='hierarchical',
426427
base_dir = os.getcwd()
427428
base_dir = make_output_dir(base_dir)
428429
if graph2use in ['hierarchical', 'colored']:
429-
if self.name[:1].isdigit(): # these graphs break if int
430+
if self.name[:1].isdigit(): # these graphs break if int
430431
raise ValueError('{} graph failed, workflow name cannot begin '
431432
'with a number'.format(graph2use))
432433
dotfilename = op.join(base_dir, dotfilename)
@@ -650,7 +651,7 @@ def _write_report_info(self, workingdir, name, graph):
650651
# Avoid RuntimeWarning: divide by zero encountered in log10
651652
num_nodes = len(nodes)
652653
if num_nodes > 0:
653-
index_name = np.ceil(np.log10(num_nodes)).astype(int)
654+
index_name = np.ceil(np.log10(num_nodes)).astype(int)
654655
else:
655656
index_name = 0
656657
template = '%%0%dd_' % index_name
@@ -798,10 +799,10 @@ def _get_outputs(self):
798799
setattr(outputdict, node.name, outputs)
799800
return outputdict
800801

801-
def _set_input(self, object, name, newvalue):
802+
def _set_input(self, objekt, name, newvalue):
802803
"""Trait callback function to update a node input
803804
"""
804-
object.traits()[name].node.set_input(name, newvalue)
805+
objekt.traits()[name].node.set_input(name, newvalue)
805806

806807
def _set_node_input(self, node, param, source, sourceinfo):
807808
"""Set inputs of a node given the edge connection"""

0 commit comments

Comments
 (0)