Skip to content

Commit 53eed17

Browse files
authored
Merge pull request nipreps#472 from oesteban/maint/cleanup-and-coverage
MAINT: Miscellaneous housekeeping
2 parents 0a6e986 + b3eb01b commit 53eed17

File tree

10 files changed

+101
-184
lines changed

10 files changed

+101
-184
lines changed

docs/api.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ Information on specific functions, classes, and methods.
77
:glob:
88

99
api/niworkflows.anat
10-
api/niworkflows.common
1110
api/niworkflows.dwi
1211
api/niworkflows.engine
1312
api/niworkflows.func

niworkflows/common/__init__.py

Lines changed: 0 additions & 5 deletions
This file was deleted.

niworkflows/common/orient.py

Lines changed: 0 additions & 27 deletions
This file was deleted.

niworkflows/engine/tests/__init__.py

Whitespace-only changes.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""Test the LiterateWorkflow."""
2+
from nipype.pipeline.engine import Node
3+
from nipype.interfaces import afni, utility as niu
4+
from ..workflows import LiterateWorkflow as Workflow
5+
6+
7+
def _reorient_wf(name='ReorientWorkflow'):
8+
"""A workflow to reorient images to 'RPI' orientation."""
9+
workflow = Workflow(name=name)
10+
workflow.__desc__ = "Inner workflow. "
11+
inputnode = Node(niu.IdentityInterface(fields=['in_file']),
12+
name='inputnode')
13+
outputnode = Node(niu.IdentityInterface(
14+
fields=['out_file']), name='outputnode')
15+
deoblique = Node(afni.Refit(deoblique=True), name='deoblique')
16+
reorient = Node(afni.Resample(
17+
orientation='RPI', outputtype='NIFTI_GZ'), name='reorient')
18+
workflow.connect([
19+
(inputnode, deoblique, [('in_file', 'in_file')]),
20+
(deoblique, reorient, [('out_file', 'in_file')]),
21+
(reorient, outputnode, [('out_file', 'out_file')])
22+
])
23+
return workflow
24+
25+
26+
def test_boilerplate():
27+
"""Check the boilerplate is generated."""
28+
workflow = Workflow(name='test')
29+
workflow.__desc__ = "Outer workflow. "
30+
workflow.__postdesc__ = "Outer workflow (postdesc)."
31+
32+
inputnode = Node(niu.IdentityInterface(fields=['in_file']),
33+
name='inputnode')
34+
inner = _reorient_wf()
35+
36+
workflow.connect([
37+
(inputnode, inner, [('in_file', 'inputnode.in_file')]),
38+
])
39+
40+
assert workflow.visit_desc() == "Outer workflow. Inner workflow. Outer workflow (postdesc)."

niworkflows/engine/workflows.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
# -*- coding: utf-8 -*-
21
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
32
# vi: set ft=python sts=4 ts=4 sw=4 et:
43
"""
5-
Supercharging Nipype's workflow engine
6-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4+
Supercharging Nipype's workflow engine.
75
86
Add special features to the Nipype's vanilla workflows
97
"""
@@ -14,23 +12,23 @@ class LiterateWorkflow(pe.Workflow):
1412
"""Controls the setup and execution of a pipeline of processes."""
1513

1614
def __init__(self, name, base_dir=None):
17-
"""Create a workflow object.
15+
"""
16+
Create a workflow object.
17+
1818
Parameters
1919
----------
20-
name : alphanumeric string
20+
name : alphanumeric :obj:`str`
2121
unique identifier for the workflow
22-
base_dir : string, optional
22+
base_dir : :obj:`str`, optional
2323
path to workflow storage
24+
2425
"""
2526
super(LiterateWorkflow, self).__init__(name, base_dir)
2627
self.__desc__ = None
2728
self.__postdesc__ = None
2829

2930
def visit_desc(self):
30-
"""
31-
Builds a citation boilerplate by visiting all workflows
32-
appending their ``__desc__`` field
33-
"""
31+
"""Build a citation boilerplate by visiting all workflows."""
3432
desc = []
3533

3634
if self.__desc__:

niworkflows/interfaces/itk.py

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
import os
55
from mimetypes import guess_type
66
from tempfile import TemporaryDirectory
7-
import numpy as np
8-
import nibabel as nb
97

108
from nipype import logging
119
from nipype.utils.filemanip import fname_presuffix
@@ -142,68 +140,6 @@ def _run_interface(self, runtime):
142140
return runtime
143141

144142

145-
class _FUGUEvsm2ANTSwarpInputSpec(BaseInterfaceInputSpec):
146-
in_file = File(exists=True, mandatory=True,
147-
desc='input displacements field map')
148-
pe_dir = traits.Enum('i', 'i-', 'j', 'j-', 'k', 'k-',
149-
desc='phase-encoding axis')
150-
151-
152-
class _FUGUEvsm2ANTSwarpOutputSpec(TraitedSpec):
153-
out_file = File(desc='the output warp field')
154-
155-
156-
class FUGUEvsm2ANTSwarp(SimpleInterface):
157-
"""Convert a voxel-shift-map to ants warp."""
158-
input_spec = _FUGUEvsm2ANTSwarpInputSpec
159-
output_spec = _FUGUEvsm2ANTSwarpOutputSpec
160-
161-
def _run_interface(self, runtime):
162-
163-
nii = nb.load(self.inputs.in_file)
164-
165-
phaseEncDim = {'i': 0, 'j': 1, 'k': 2}[self.inputs.pe_dir[0]]
166-
167-
if len(self.inputs.pe_dir) == 2:
168-
phaseEncSign = 1.0
169-
else:
170-
phaseEncSign = -1.0
171-
172-
# Fix header
173-
hdr = nii.header.copy()
174-
hdr.set_data_dtype(np.dtype('<f4'))
175-
hdr.set_intent('vector', (), '')
176-
177-
# Get data, convert to mm
178-
data = nii.get_fdata()
179-
180-
aff = np.diag([1.0, 1.0, -1.0])
181-
if np.linalg.det(aff) < 0 and phaseEncDim != 0:
182-
# Reverse direction since ITK is LPS
183-
aff *= -1.0
184-
185-
aff = aff.dot(nii.affine[:3, :3])
186-
187-
data *= phaseEncSign * nii.header.get_zooms()[phaseEncDim]
188-
189-
# Add missing dimensions
190-
zeros = np.zeros_like(data)
191-
field = [zeros, zeros]
192-
field.insert(phaseEncDim, data)
193-
field = np.stack(field, -1)
194-
# Add empty axis
195-
field = field[:, :, :, np.newaxis, :]
196-
197-
# Write out
198-
self._results['out_file'] = fname_presuffix(
199-
self.inputs.in_file, suffix='_antswarp', newpath=runtime.cwd)
200-
nb.Nifti1Image(
201-
field.astype(np.dtype('<f4')), nii.affine, hdr).to_filename(
202-
self._results['out_file'])
203-
204-
return runtime
205-
206-
207143
def _mat2itk(args):
208144
from nipype.interfaces.c3 import C3dAffineTool
209145
from nipype.utils.filemanip import fname_presuffix
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""KeySelect tests."""
2+
import pytest
3+
from ..utility import KeySelect
4+
5+
6+
def test_KeySelect():
7+
"""Test KeySelect."""
8+
with pytest.raises(ValueError):
9+
KeySelect(fields='field1', keys=['a', 'b', 'c', 'a'])
10+
11+
with pytest.raises(ValueError):
12+
KeySelect(fields=[])

niworkflows/interfaces/utility.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,36 @@ class KeySelect(BaseInterface):
8989
output_spec = _KeySelectOutputSpec
9090

9191
def __init__(self, keys=None, fields=None, **inputs):
92+
"""
93+
Instantiate a KeySelect utility interface.
94+
95+
Examples
96+
--------
97+
>>> ks = KeySelect(fields='field1')
98+
>>> ks.inputs
99+
<BLANKLINE>
100+
field1 = <undefined>
101+
key = <undefined>
102+
keys = <undefined>
103+
<BLANKLINE>
104+
105+
>>> ks = KeySelect(fields='field1', field1=['a', 'b'])
106+
>>> ks.inputs
107+
<BLANKLINE>
108+
field1 = ['a', 'b']
109+
key = <undefined>
110+
keys = <undefined>
111+
<BLANKLINE>
112+
113+
>>> ks = KeySelect()
114+
Traceback (most recent call last):
115+
ValueError: A list or multiplexed...
116+
117+
>>> ks = KeySelect(fields='key')
118+
Traceback (most recent call last):
119+
ValueError: Some fields are invalid...
120+
121+
"""
92122
# Call constructor
93123
super(KeySelect, self).__init__(**inputs)
94124

@@ -151,6 +181,5 @@ def _list_outputs(self):
151181

152182
def _outputs(self):
153183
base = super(KeySelect, self)._outputs()
154-
if self._fields:
155-
base = add_traits(base, self._fields)
184+
base = add_traits(base, self._fields)
156185
return base

niworkflows/viz/utils.py

Lines changed: 10 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
1-
# -*- coding: utf-8 -*-
21
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
32
# vi: set ft=python sts=4 ts=4 sw=4 et:
43
"""Helper tools for visualization purposes"""
5-
from __future__ import absolute_import, division, print_function, unicode_literals
6-
7-
import os
84
import os.path as op
5+
from shutil import which
96
import subprocess
107
import base64
118
import re
@@ -22,83 +19,25 @@
2219
from svgutils.transform import SVGFigure
2320
from seaborn import color_palette
2421

25-
from .. import NIWORKFLOWS_LOG
2622
from nipype.utils import filemanip
27-
28-
try:
29-
from shutil import which
30-
except ImportError:
31-
32-
def which(cmd):
33-
"""
34-
A homemade which command
35-
36-
>>> from niworkflows.viz.utils import which
37-
>>> which('ls')
38-
True
39-
>>> which('madeoutcommand')
40-
False
41-
42-
"""
43-
44-
try:
45-
subprocess.run([cmd], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
46-
close_fds=True)
47-
except OSError as e:
48-
from errno import ENOENT
49-
if e.errno == ENOENT:
50-
return False
51-
raise e
52-
return True
23+
from .. import NIWORKFLOWS_LOG
5324

5425

5526
SVGNS = "http://www.w3.org/2000/svg"
5627
PY3 = version_info[0] > 2
5728

58-
# Patch subprocess in python 2
59-
if not hasattr(subprocess, 'DEVNULL'):
60-
setattr(subprocess, 'DEVNULL', -3)
61-
62-
if not hasattr(subprocess, 'run'):
63-
def _run(args, input=None, stdout=None, stderr=None, shell=False, check=False,
64-
close_fds=False):
65-
from collections import namedtuple
66-
67-
devnull = open(os.devnull, 'r+')
68-
stdin = subprocess.PIPE if input is not None else None
6929

70-
if stdout == subprocess.DEVNULL:
71-
stdout = devnull
72-
73-
if stderr == subprocess.DEVNULL:
74-
stderr = devnull
75-
76-
proc = subprocess.Popen(args, stdout=stdout, shell=shell, stdin=stdin,
77-
close_fds=close_fds)
78-
result = namedtuple('CompletedProcess', 'stdout stderr')
79-
res = result(*proc.communicate(input=input))
80-
81-
devnull.close()
82-
83-
if check and proc.returncode != 0:
84-
raise subprocess.CalledProcessError(proc.returncode, args)
85-
86-
return res
87-
setattr(subprocess, 'run', _run)
88-
89-
90-
def robust_set_limits(data, plot_params):
30+
def robust_set_limits(data, plot_params, percentiles=(15, 99.8)):
31+
"""Set (vmax, vmin) based on percentiles of the data."""
9132
plot_params['vmin'] = plot_params.get(
92-
'vmin', np.percentile(data, 15))
33+
'vmin', np.percentile(data, percentiles[0]))
9334
plot_params['vmax'] = plot_params.get(
94-
'vmax', np.percentile(data, 99.8))
35+
'vmax', np.percentile(data, percentiles[1]))
9536
return plot_params
9637

9738

9839
def svg_compress(image, compress='auto'):
99-
''' takes an image as created by nilearn.plotting and returns a blob svg.
100-
Performs compression (can be disabled). A bit hacky. '''
101-
40+
"""Generate a blob SVG from a matplotlib figure, may perform compression."""
10241
# Check availability of svgo and cwebp
10342
has_compress = all((which('svgo'), which('cwebp')))
10443
if compress is True and not has_compress:
@@ -161,9 +100,7 @@ def svg_compress(image, compress='auto'):
161100

162101

163102
def svg2str(display_object, dpi=300):
164-
"""
165-
Serializes a nilearn display object as a string
166-
"""
103+
"""Serialize a nilearn display object to string."""
167104
from io import StringIO
168105
image_buf = StringIO()
169106
display_object.frame_axes.figure.savefig(
@@ -173,9 +110,7 @@ def svg2str(display_object, dpi=300):
173110

174111

175112
def extract_svg(display_object, dpi=300, compress='auto'):
176-
"""
177-
Removes the preamble of the svg files generated with nilearn
178-
"""
113+
"""Remove the preamble of the svg files generated with nilearn."""
179114
image_svg = svg2str(display_object, dpi)
180115
if compress is True or compress == 'auto':
181116
image_svg = svg_compress(image_svg, compress)
@@ -197,7 +132,7 @@ def extract_svg(display_object, dpi=300, compress='auto'):
197132

198133

199134
def cuts_from_bbox(mask_nii, cuts=3):
200-
"""Finds equi-spaced cuts for presenting images"""
135+
"""Find equi-spaced cuts for presenting images."""
201136
from nibabel.affines import apply_affine
202137

203138
mask_data = np.asanyarray(mask_nii.dataobj) > 0.0

0 commit comments

Comments
 (0)