Skip to content

Commit 7f2c673

Browse files
committed
Merge branch 'master' into enh/NewDipyInterfaces
2 parents 10476d0 + 1a0b85a commit 7f2c673

File tree

11 files changed

+812
-275
lines changed

11 files changed

+812
-275
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ before_install:
2424
fi
2525
- if $INSTALL_DEB_DEPENDECIES; then source /etc/fsl/fsl.sh; fi
2626
- if $INSTALL_DEB_DEPENDECIES; then source /etc/afni/afni.sh; fi
27+
- export FSLOUTPUTTYPE=NIFTI_GZ
2728
install:
2829
- conda update --yes conda
2930
- conda create -n testenv --yes pip python=$TRAVIS_PYTHON_VERSION

CHANGES

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ Next release
33

44
* ENH: New interfaces in dipy: RESTORE, EstimateResponseSH, CSD and StreamlineTractography
55
(https://github.com/nipy/nipype/pull/1090)
6+
* ENH: Merge S3DataSink into DataSink, added AWS documentation (https://github.com/nipy/nipype/pull/1316)
67
* TST: Cache APT in CircleCI (https://github.com/nipy/nipype/pull/1333)
78
* ENH: Add new flags to the BRAINSABC for new features (https://github.com/nipy/nipype/pull/1322)
89
* ENH: Provides a Nipype wrapper for ANTs DenoiseImage (https://github.com/nipy/nipype/pull/1291)

doc/users/aws.rst

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
.. _aws:
2+
3+
============================================
4+
Using Nipype with Amazon Web Services (AWS)
5+
============================================
6+
Several groups have been successfully using Nipype on AWS. This procedure
7+
involves setting a temporary cluster using StarCluster and potentially
8+
transferring files to/from S3. The latter is supported by Nipype through
9+
DataSink and S3DataGrabber.
10+
11+
12+
Using DataSink with S3
13+
======================
14+
The DataSink class now supports sending output data directly to an AWS S3
15+
bucket. It does this through the introduction of several input attributes to the
16+
DataSink interface and by parsing the `base_directory` attribute. This class
17+
uses the `boto3 <https://boto3.readthedocs.org/en/latest/>`_ and
18+
`botocore <https://botocore.readthedocs.org/en/latest/>`_ Python packages to
19+
interact with AWS. To configure the DataSink to write data to S3, the user must
20+
set the ``base_directory`` property to an S3-style filepath. For example:
21+
22+
::
23+
24+
import nipype.interfaces.io as nio
25+
ds = nio.DataSink()
26+
ds.inputs.base_directory = 's3://mybucket/path/to/output/dir'
27+
28+
With the "s3://" prefix in the path, the DataSink knows that the output
29+
directory to send files is on S3 in the bucket "mybucket". "path/to/output/dir"
30+
is the relative directory path within the bucket "mybucket" where output data
31+
will be uploaded to (NOTE: if the relative path specified contains folders that
32+
don’t exist in the bucket, the DataSink will create them). The DataSink treats
33+
the S3 base directory exactly as it would a local directory, maintaining support
34+
for containers, substitutions, subfolders, "." notation, etc to route output
35+
data appropriately.
36+
37+
There are four new attributes introduced with S3-compatibility: ``creds_path``,
38+
``encrypt_bucket_keys``, ``local_copy``, and ``bucket``.
39+
40+
::
41+
42+
ds.inputs.creds_path = '/home/user/aws_creds/credentials.csv'
43+
ds.inputs.encrypt_bucket_keys = True
44+
ds.local_copy = '/home/user/workflow_outputs/local_backup'
45+
46+
``creds_path`` is a file path where the user's AWS credentials file (typically
47+
a csv) is stored. This credentials file should contain the AWS access key id and
48+
secret access key and should be formatted as one of the following (these formats
49+
are how Amazon provides the credentials file by default when first downloaded).
50+
51+
Root-account user:
52+
53+
::
54+
55+
AWSAccessKeyID=ABCDEFGHIJKLMNOP
56+
AWSSecretKey=zyx123wvu456/ABC890+gHiJk
57+
58+
IAM-user:
59+
60+
::
61+
62+
User Name,Access Key Id,Secret Access Key
63+
"username",ABCDEFGHIJKLMNOP,zyx123wvu456/ABC890+gHiJk
64+
65+
The ``creds_path`` is necessary when writing files to a bucket that has
66+
restricted access (almost no buckets are publicly writable). If ``creds_path``
67+
is not specified, the DataSink will check the ``AWS_ACCESS_KEY_ID`` and
68+
``AWS_SECRET_ACCESS_KEY`` environment variables and use those values for bucket
69+
access.
70+
71+
``encrypt_bucket_keys`` is a boolean flag that indicates whether to encrypt the
72+
output data on S3, using server-side AES-256 encryption. This is useful if the
73+
data being output is sensitive and one desires an extra layer of security on the
74+
data. By default, this is turned off.
75+
76+
``local_copy`` is a string of the filepath where local copies of the output data
77+
are stored in addition to those sent to S3. This is useful if one wants to keep
78+
a backup version of the data stored on their local computer. By default, this is
79+
turned off.
80+
81+
``bucket`` is a boto3 Bucket object that the user can use to overwrite the
82+
bucket specified in their ``base_directory``. This can be useful if one has to
83+
manually create a bucket instance on their own using special credentials (or
84+
using a mock server like `fakes3 <https://github.com/jubos/fake-s3>`_). This is
85+
typically used for developers unit-testing the DataSink class. Most users do not
86+
need to use this attribute for actual workflows. This is an optional argument.
87+
88+
Finally, the user needs only to specify the input attributes for any incoming
89+
data to the node, and the outputs will be written to their S3 bucket.
90+
91+
::
92+
93+
workflow.connect(inputnode, 'subject_id', ds, 'container')
94+
workflow.connect(realigner, 'realigned_files', ds, 'motion')
95+
96+
So, for example, outputs for sub001’s realigned_file1.nii.gz will be in:
97+
s3://mybucket/path/to/output/dir/sub001/motion/realigned_file1.nii.gz
98+
99+
100+
Using S3DataGrabber
101+
======================
102+
Coming soon...

doc/users/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
spmmcr
3939
mipav
4040
nipypecmd
41+
aws
4142

4243

4344

nipype/algorithms/misc.py

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,14 @@ class TSNRInputSpec(BaseInterfaceInputSpec):
263263
in_file = InputMultiPath(File(exists=True), mandatory=True,
264264
desc='realigned 4D file or a list of 3D files')
265265
regress_poly = traits.Range(low=1, desc='Remove polynomials')
266+
tsnr_file = File('tsnr.nii.gz', usedefault=True, hash_files=False,
267+
desc='output tSNR file')
268+
mean_file = File('mean.nii.gz', usedefault=True, hash_files=False,
269+
desc='output mean file')
270+
stddev_file = File('stdev.nii.gz', usedefault=True, hash_files=False,
271+
desc='output tSNR file')
272+
detrended_file = File('detrend.nii.gz', usedefault=True, hash_files=False,
273+
desc='input file after detrending')
266274

267275

268276
class TSNROutputSpec(TraitedSpec):
@@ -288,24 +296,18 @@ class TSNR(BaseInterface):
288296
input_spec = TSNRInputSpec
289297
output_spec = TSNROutputSpec
290298

291-
def _gen_output_file_name(self, suffix=None):
292-
_, base, ext = split_filename(self.inputs.in_file[0])
293-
if suffix in ['mean', 'stddev']:
294-
return os.path.abspath(base + "_tsnr_" + suffix + ext)
295-
elif suffix in ['detrended']:
296-
return os.path.abspath(base + "_" + suffix + ext)
297-
else:
298-
return os.path.abspath(base + "_tsnr" + ext)
299-
300299
def _run_interface(self, runtime):
301300
img = nb.load(self.inputs.in_file[0])
302301
header = img.header.copy()
303302
vollist = [nb.load(filename) for filename in self.inputs.in_file]
304303
data = np.concatenate([vol.get_data().reshape(
305-
vol.shape[:3] + (-1,)) for vol in vollist], axis=3)
304+
vol.get_shape()[:3] + (-1,)) for vol in vollist], axis=3)
305+
data = data.nan_to_num()
306+
306307
if data.dtype.kind == 'i':
307308
header.set_data_dtype(np.float32)
308309
data = data.astype(np.float32)
310+
309311
if isdefined(self.inputs.regress_poly):
310312
timepoints = img.shape[-1]
311313
X = np.ones((timepoints, 1))
@@ -318,26 +320,28 @@ def _run_interface(self, runtime):
318320
betas[1:, :, :, :], 0, 3)),
319321
0, 4)
320322
data = data - datahat
321-
img = nb.Nifti1Image(data, img.affine, header)
322-
nb.save(img, self._gen_output_file_name('detrended'))
323+
img = nb.Nifti1Image(data, img.get_affine(), header)
324+
nb.save(img, op.abspath(self.inputs.detrended_file))
325+
323326
meanimg = np.mean(data, axis=3)
324327
stddevimg = np.std(data, axis=3)
325-
tsnr = meanimg / stddevimg
326-
img = nb.Nifti1Image(tsnr, img.affine, header)
327-
nb.save(img, self._gen_output_file_name())
328-
img = nb.Nifti1Image(meanimg, img.affine, header)
329-
nb.save(img, self._gen_output_file_name('mean'))
330-
img = nb.Nifti1Image(stddevimg, img.affine, header)
331-
nb.save(img, self._gen_output_file_name('stddev'))
328+
tsnr = np.zeros_like(meanimg)
329+
tsnr[stddevimg > 1.e-3] = meanimg[stddevimg > 1.e-3] / stddevimg[stddevimg > 1.e-3]
330+
img = nb.Nifti1Image(tsnr, img.get_affine(), header)
331+
nb.save(img, op.abspath(self.inputs.tsnr_file))
332+
img = nb.Nifti1Image(meanimg, img.get_affine(), header)
333+
nb.save(img, op.abspath(self.inputs.mean_file))
334+
img = nb.Nifti1Image(stddevimg, img.get_affine(), header)
335+
nb.save(img, op.abspath(self.inputs.stddev_file))
332336
return runtime
333337

334338
def _list_outputs(self):
335339
outputs = self._outputs().get()
336-
outputs['tsnr_file'] = self._gen_output_file_name()
337-
outputs['mean_file'] = self._gen_output_file_name('mean')
338-
outputs['stddev_file'] = self._gen_output_file_name('stddev')
340+
for k in ['tsnr_file', 'mean_file', 'stddev_file']:
341+
outputs[k] = op.abspath(getattr(self.inputs, k))
342+
339343
if isdefined(self.inputs.regress_poly):
340-
outputs['detrended_file'] = self._gen_output_file_name('detrended')
344+
outputs['detrended_file'] = op.abspath(self.inputs.detrended_file)
341345
return outputs
342346

343347

nipype/algorithms/tests/test_auto_TSNR.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,24 @@
44

55

66
def test_TSNR_inputs():
7-
input_map = dict(ignore_exception=dict(nohash=True,
7+
input_map = dict(detrended_file=dict(hash_files=False,
8+
usedefault=True,
9+
),
10+
ignore_exception=dict(nohash=True,
811
usedefault=True,
912
),
1013
in_file=dict(mandatory=True,
1114
),
15+
mean_file=dict(hash_files=False,
16+
usedefault=True,
17+
),
1218
regress_poly=dict(),
19+
stddev_file=dict(hash_files=False,
20+
usedefault=True,
21+
),
22+
tsnr_file=dict(hash_files=False,
23+
usedefault=True,
24+
),
1325
)
1426
inputs = TSNR.input_spec()
1527

nipype/interfaces/fsl/tests/test_auto_Smooth.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ def test_Smooth_inputs():
99
environ=dict(nohash=True,
1010
usedefault=True,
1111
),
12-
fwhm=dict(argstr='-kernel gauss %f -fmean',
12+
fwhm=dict(argstr='-kernel gauss %.03f -fmean',
1313
mandatory=True,
1414
position=1,
15+
xor=['sigma'],
1516
),
1617
ignore_exception=dict(nohash=True,
1718
usedefault=True,
@@ -21,9 +22,15 @@ def test_Smooth_inputs():
2122
position=0,
2223
),
2324
output_type=dict(),
25+
sigma=dict(argstr='-kernel gauss %.03f -fmean',
26+
mandatory=True,
27+
position=1,
28+
xor=['fwhm'],
29+
),
2430
smoothed_file=dict(argstr='%s',
25-
genfile=True,
2631
hash_files=False,
32+
name_source=['in_file'],
33+
name_template='%s_smooth',
2734
position=2,
2835
),
2936
terminal_output=dict(nohash=True,

nipype/interfaces/fsl/utils.py

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -139,38 +139,59 @@ def _gen_filename(self, name):
139139

140140
class SmoothInputSpec(FSLCommandInputSpec):
141141
in_file = File(exists=True, argstr="%s", position=0, mandatory=True)
142-
fwhm = traits.Float(argstr="-kernel gauss %f -fmean", position=1,
143-
mandatory=True)
142+
sigma = traits.Float(
143+
argstr="-kernel gauss %.03f -fmean", position=1, xor=['fwhm'], mandatory=True,
144+
desc='gaussian kernel sigma in mm (not voxels)')
145+
fwhm = traits.Float(
146+
argstr="-kernel gauss %.03f -fmean", position=1, xor=['sigma'], mandatory=True,
147+
desc='gaussian kernel fwhm, will be converted to sigma in mm (not voxels)')
144148
smoothed_file = File(
145-
argstr="%s", position=2, genfile=True, hash_files=False)
149+
argstr="%s", position=2, name_source=['in_file'], name_template='%s_smooth', hash_files=False)
146150

147151

148152
class SmoothOutputSpec(TraitedSpec):
149153
smoothed_file = File(exists=True)
150154

151155

152156
class Smooth(FSLCommand):
153-
'''Use fslmaths to smooth the image
154-
'''
157+
"""
158+
Use fslmaths to smooth the image
159+
160+
Examples
161+
--------
162+
163+
Setting the kernel width using sigma:
164+
165+
>>> sm = Smooth()
166+
>>> sm.inputs.in_file = 'functional2.nii'
167+
>>> sm.inputs.sigma = 8.0
168+
>>> sm.cmdline #doctest: +ELLIPSIS
169+
'fslmaths functional2.nii -kernel gauss 8.000 -fmean functional2_smooth.nii.gz'
170+
171+
Setting the kernel width using fwhm:
172+
173+
>>> sm = Smooth()
174+
>>> sm.inputs.in_file = 'functional2.nii'
175+
>>> sm.inputs.fwhm = 8.0
176+
>>> sm.cmdline #doctest: +ELLIPSIS
177+
'fslmaths functional2.nii -kernel gauss 3.397 -fmean functional2_smooth.nii.gz'
178+
179+
One of sigma or fwhm must be set:
180+
181+
>>> from nipype.interfaces.fsl import Smooth
182+
>>> sm = Smooth()
183+
>>> sm.inputs.in_file = 'functional2.nii'
184+
>>> sm.cmdline #doctest: +ELLIPSIS
185+
Traceback (most recent call last):
186+
...
187+
ValueError: Smooth requires a value for one of the inputs ...
188+
189+
"""
155190

156191
input_spec = SmoothInputSpec
157192
output_spec = SmoothOutputSpec
158193
_cmd = 'fslmaths'
159194

160-
def _gen_filename(self, name):
161-
if name == 'smoothed_file':
162-
return self._list_outputs()['smoothed_file']
163-
return None
164-
165-
def _list_outputs(self):
166-
outputs = self._outputs().get()
167-
outputs['smoothed_file'] = self.inputs.smoothed_file
168-
if not isdefined(outputs['smoothed_file']):
169-
outputs['smoothed_file'] = self._gen_fname(self.inputs.in_file,
170-
suffix='_smooth')
171-
outputs['smoothed_file'] = os.path.abspath(outputs['smoothed_file'])
172-
return outputs
173-
174195
def _format_arg(self, name, trait_spec, value):
175196
if name == 'fwhm':
176197
sigma = float(value) / np.sqrt(8 * np.log(2))

0 commit comments

Comments
 (0)