Skip to content

Commit 0af3e6b

Browse files
committed
Merge pull request #646 from satra/enh/restingwf
Example: Resting preprocessing workflow
2 parents 4ed50ce + 489f656 commit 0af3e6b

File tree

8 files changed

+907
-11
lines changed

8 files changed

+907
-11
lines changed

examples/rsfmri_preprocessing.py

Lines changed: 760 additions & 0 deletions
Large diffs are not rendered by default.

nipype/interfaces/freesurfer/model.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -342,9 +342,9 @@ def __init__(self, **kwargs):
342342
class BinarizeInputSpec(FSTraitedSpec):
343343
in_file = File(exists=True, argstr='--i %s', mandatory=True,
344344
copyfile=False, desc='input volume')
345-
min = traits.Float(argstr='--min %f',
345+
min = traits.Float(argstr='--min %f', xor=['wm_ven_csf'],
346346
desc='min thresh')
347-
max = traits.Float(argstr='--max %f',
347+
max = traits.Float(argstr='--max %f', xor=['wm_ven_csf'],
348348
desc='max thresh')
349349
rmin = traits.Float(argstr='--rmin %f',
350350
desc='compute min based on rmin*globalmean')
@@ -356,7 +356,7 @@ class BinarizeInputSpec(FSTraitedSpec):
356356
desc='set match vals to 2 and 41 (aseg for cerebral WM)')
357357
ventricles = traits.Bool(argstr='--ventricles',
358358
desc='set match vals those for aseg ventricles+choroid (not 4th)')
359-
wm_ven_csf = traits.Bool(argstr='--wm+vcsf',
359+
wm_ven_csf = traits.Bool(argstr='--wm+vcsf', xor=['min', 'max'],
360360
desc='WM and ventricular CSF, including choroid (not 4th)')
361361
binary_file = File(argstr='--o %s', genfile=True,
362362
desc='binary output volume')
@@ -430,7 +430,7 @@ def _list_outputs(self):
430430
outfile = fname_presuffix(self.inputs.in_file,
431431
newpath=os.getcwd(),
432432
suffix='_thresh')
433-
outputs['binary_file'] = outfile
433+
outputs['binary_file'] = os.path.abspath(outfile)
434434
value = self.inputs.count_file
435435
if isdefined(value):
436436
if isinstance(value, bool):

nipype/interfaces/fsl/model.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1804,7 +1804,6 @@ def _list_outputs(self):
18041804
return outputs
18051805

18061806
def _gen_filename(self, name):
1807-
if name in ('out_file'):
1808-
return fname_presuffix(self.inputs.in_file,
1809-
suffix='_glm.txt', use_ext=False)
1807+
if name in ['out_file']:
1808+
return self._gen_fname(self.inputs.in_file, suffix='_glm')
18101809
return None

nipype/interfaces/io.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ class DataSink(IOBase):
214214
input_spec = DataSinkInputSpec
215215
output_spec = DataSinkOutputSpec
216216

217-
def __init__(self, infields=None, **kwargs):
217+
def __init__(self, infields=None, force_run=True, **kwargs):
218218
"""
219219
Parameters
220220
----------
@@ -232,6 +232,8 @@ def __init__(self, infields=None, **kwargs):
232232
self.inputs._outputs[key] = Undefined
233233
undefined_traits[key] = Undefined
234234
self.inputs.trait_set(trait_change_notify=False, **undefined_traits)
235+
if force_run:
236+
self._always_run = True
235237

236238
def _get_dst(self, src):
237239
## If path is directory with trailing os.path.sep,

nipype/interfaces/nipy/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
from .model import FitGLM, EstimateContrast
2-
from .preprocess import ComputeMask, FmriRealign4d
2+
from .preprocess import ComputeMask, FmriRealign4d, SpaceTimeRealigner
33
from .utils import Similarity

nipype/interfaces/nipy/preprocess.py

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
from nipy.labs.mask import compute_mask
2525
from nipy.algorithms.registration import FmriRealign4d as FR4d
2626
from nipy import save_image, load_image
27+
from nipy.algorithms.registration import SpaceTimeRealign
28+
from nipy.algorithms.registration.groupwise_registration import SpaceRealign
2729

2830
from ..base import (TraitedSpec, BaseInterface, traits,
2931
BaseInterfaceInputSpec, isdefined, File,
@@ -82,7 +84,7 @@ class FmriRealign4dInputSpec(BaseInterfaceInputSpec):
8284
desc="File to realign")
8385
tr = traits.Float(desc="TR in seconds",
8486
mandatory=True)
85-
slice_order = traits.List(traits.Int(), maxver=0.3,
87+
slice_order = traits.List(traits.Int(),
8688
desc=('0 based slice order. This would be equivalent to entering'
8789
'np.argsort(spm_slice_order) for this field. This effects'
8890
'interleaved acquisition. This field will be deprecated in'
@@ -117,6 +119,7 @@ class FmriRealign4dOutputSpec(TraitedSpec):
117119
desc="Motion parameter files")
118120

119121

122+
@np.deprecate_with_doc('Please use SpaceTimeRealign instead')
120123
class FmriRealign4d(BaseInterface):
121124
"""Simultaneous motion and slice timing correction algorithm
122125
@@ -196,6 +199,131 @@ def _list_outputs(self):
196199
return outputs
197200

198201

202+
class SpaceTimeRealignerInputSpec(BaseInterfaceInputSpec):
203+
204+
in_file = InputMultiPath(exists=True,
205+
mandatory=True,
206+
desc="File to realign")
207+
tr = traits.Float(desc="TR in seconds", requires=['slice_times'])
208+
slice_times = traits.Either(traits.List(traits.Float()),
209+
traits.Enum('asc_alt_2', 'asc_alt_2_1',
210+
'asc_alt_half', 'asc_alt_siemens',
211+
'ascending', 'desc_alt_2',
212+
'desc_alt_half', 'descending'),
213+
desc=('Actual slice acquisition times.'))
214+
slice_info = traits.Either(traits.Int,
215+
traits.List(min_len=2, max_len=2),
216+
desc=('Single integer or length 2 sequence '
217+
'If int, the axis in `images` that is the '
218+
'slice axis. In a 4D image, this will '
219+
'often be axis = 2. If a 2 sequence, then'
220+
' elements are ``(slice_axis, '
221+
'slice_direction)``, where ``slice_axis`` '
222+
'is the slice axis in the image as above, '
223+
'and ``slice_direction`` is 1 if the '
224+
'slices were acquired slice 0 first, slice'
225+
' -1 last, or -1 if acquired slice -1 '
226+
'first, slice 0 last. If `slice_info` is '
227+
'an int, assume '
228+
'``slice_direction`` == 1.'),
229+
requires=['slice_times'],
230+
)
231+
232+
233+
class SpaceTimeRealignerOutputSpec(TraitedSpec):
234+
out_file = OutputMultiPath(File(exists=True),
235+
desc="Realigned files")
236+
par_file = OutputMultiPath(File(exists=True),
237+
desc=("Motion parameter files. Angles are not "
238+
"euler angles"))
239+
240+
241+
class SpaceTimeRealigner(BaseInterface):
242+
"""Simultaneous motion and slice timing correction algorithm
243+
244+
If slice_times is not specified, this algorithm performs spatial motion
245+
correction
246+
247+
This interface wraps nipy's SpaceTimeRealign algorithm [1]_ or simply the
248+
SpatialRealign algorithm when timing info is not provided.
249+
250+
Examples
251+
--------
252+
>>> from nipype.interfaces.nipy import SpaceTimeRealigner
253+
>>> #Run spatial realignment only
254+
>>> realigner = SpaceTimeRealigner()
255+
>>> realigner.inputs.in_file = ['functional.nii']
256+
>>> res = realigner.run() # doctest: +SKIP
257+
258+
>>> realigner = SpaceTimeRealigner()
259+
>>> realigner.inputs.in_file = ['functional.nii']
260+
>>> realigner.inputs.tr = 2
261+
>>> realigner.inputs.slice_times = range(0, 3, 67)
262+
>>> realigner.inputs.slice_info = 2
263+
>>> res = realigner.run() # doctest: +SKIP
264+
265+
266+
References
267+
----------
268+
.. [1] Roche A. A four-dimensional registration algorithm with \
269+
application to joint correction of motion and slice timing \
270+
in fMRI. IEEE Trans Med Imaging. 2011 Aug;30(8):1546-54. DOI_.
271+
272+
.. _DOI: http://dx.doi.org/10.1109/TMI.2011.2131152
273+
274+
"""
275+
276+
input_spec = SpaceTimeRealignerInputSpec
277+
output_spec = SpaceTimeRealignerOutputSpec
278+
keywords = ['slice timing', 'motion correction']
279+
280+
def _run_interface(self, runtime):
281+
282+
all_ims = [load_image(fname) for fname in self.inputs.in_file]
283+
284+
if not isdefined(self.inputs.slice_times):
285+
R = SpaceRealign(all_ims)
286+
else:
287+
R = SpaceTimeRealign(all_ims,
288+
tr=self.inputs.tr,
289+
slice_times=self.inputs.slice_times,
290+
slice_info=self.inputs.slice_info,
291+
)
292+
293+
R.estimate(refscan=None)
294+
295+
corr_run = R.resample()
296+
self._out_file_path = []
297+
self._par_file_path = []
298+
299+
for j, corr in enumerate(corr_run):
300+
self._out_file_path.append(os.path.abspath('corr_%s.nii.gz' %
301+
(split_filename(self.inputs.in_file[j])[1])))
302+
save_image(corr, self._out_file_path[j])
303+
304+
self._par_file_path.append(os.path.abspath('%s.par' %
305+
(os.path.split(self.inputs.in_file[j])[1])))
306+
mfile = open(self._par_file_path[j], 'w')
307+
motion = R._transforms[j]
308+
# nipy does not encode euler angles. return in original form of
309+
# translation followed by rotation vector see:
310+
# http://en.wikipedia.org/wiki/Rodrigues'_rotation_formula
311+
for i, mo in enumerate(motion):
312+
params = ['%.10f' % item for item in np.hstack((mo.translation,
313+
mo.rotation))]
314+
string = ' '.join(params) + '\n'
315+
mfile.write(string)
316+
mfile.close()
317+
318+
return runtime
319+
320+
def _list_outputs(self):
321+
outputs = self._outputs().get()
322+
outputs['out_file'] = self._out_file_path
323+
outputs['par_file'] = self._par_file_path
324+
return outputs
325+
326+
199327
class TrimInputSpec(BaseInterfaceInputSpec):
200328
in_file = File(
201329
exists=True, mandatory=True,

nipype/pipeline/engine.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1211,6 +1211,7 @@ def hash_exists(self, updatehash=False):
12111211
outdir = self.output_dir()
12121212
hashfiles = glob(os.path.join(outdir, '_0x*.json'))
12131213
if len(hashfiles) > 1:
1214+
logger.info(hashfiles)
12141215
logger.info('Removing multiple hashfiles and forcing node to rerun')
12151216
for hashfile in hashfiles:
12161217
os.unlink(hashfile)

nipype/pipeline/plugins/multiproc.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,14 @@ def _get_result(self, taskid):
7575

7676
def _submit_job(self, node, updatehash=False):
7777
self._taskid += 1
78+
try:
79+
if node.inputs.terminal_output == 'stream':
80+
node.inputs.terminal_output = 'file'
81+
except:
82+
pass
7883
self._taskresult[self._taskid] = self.pool.apply_async(run_node,
79-
(node, updatehash,))
84+
(node,
85+
updatehash,))
8086
return self._taskid
8187

8288
def _report_crash(self, node, result=None):

0 commit comments

Comments
 (0)