Nipype workflows
Many neuroimaging tools have idiosyncratic CLIs that are clumsy to use from programming languages like python and R. Nipype provides a unified interface that facilitates the design of workflows within and between packages, lowering the learning curve necessary to use a new package.
Nipype interfaces consist of an input and output specification class, _run_interface
method, and _list_outputs
. To validate inputs are the requested type and outputs get created correctly, nipype uses a package called Traits to automate much of the process. When the .run()
method is called on an instance of your interface, the _run_interface
method is called, and _list_outputs
is used to list the files matched against the output specification. For example, an interface that simply moves a file might look like
class MoveResultFileInputSpec(BaseInterfaceInputSpec):
= File(exists=True, desc='input file to be renamed', mandatory=True)
in_file = traits.String(desc='output name string')
output_name
class MoveResultFileOutputSpec(TraitedSpec):
= File(desc='path of moved file')
out_file
class MoveResultFile(BaseInterface):
= MoveResultFileInputSpec
input_spec = MoveResultFileOutputSpec
output_spec
def _run_interface(self, runtime):
self.inputs.in_file, self.inputs.output_name)
shutil.copyfile(return runtime
def _list_outputs(self):
= self._outputs().get()
outputs 'out_file'] = self.inputs.output_name
outputs[return outputs
A slightly more complex example, which thresholds an input image with nibabel might look like:
from nipype.interfaces.base import BaseInterface, \
BaseInterfaceInputSpec, traits, File, TraitedSpecfrom nipype.utils.filemanip import split_filename
import nibabel as nb
import numpy as np
import os
class SimpleThresholdInputSpec(BaseInterfaceInputSpec):
= File(exists=True, desc='volume to be thresholded', mandatory=True)
volume = traits.Float(desc='everything below this value will be set to zero',
threshold =True)
mandatory
class SimpleThresholdOutputSpec(TraitedSpec):
= File(exists=True, desc="thresholded volume")
thresholded_volume
class SimpleThreshold(BaseInterface):
= SimpleThresholdInputSpec
input_spec = SimpleThresholdOutputSpec
output_spec
def _run_interface(self, runtime):
= self.inputs.volume
fname = nb.load(fname)
img = np.array(img.get_data())
data
= data > self.inputs.threshold
active_map
= np.zeros(data.shape)
thresholded_map = data[active_map]
thresholded_map[active_map]
= nb.Nifti1Image(thresholded_map, img.affine, img.header)
new_img = split_filename(fname)
_, base, _ + '_thresholded.nii')
nb.save(new_img, base
return runtime
def _list_outputs(self):
= self._outputs().get()
outputs = self.inputs.volume
fname = split_filename(fname)
_, base, _ "thresholded_volume"] = os.path.abspath(base + '_thresholded.nii')
outputs[return outputs
When writing workflows, most of the time a tool will already have an interface, see the nipype interfaces index for a complete list. A workflow will typically begin by parsing command line arguments
= argparse.ArgumentParser()
parser '-i', '--input', type=str, default='T1W.nii.gz')
parser.add_argument('-o', '--output', type=str, default='/tmp/tmp')
parser.add_argument(= parser.parse_args() args
setup a workflow and interface node:
= Workflow('threshold')
wf
= Node(SimpleThreshold(), 'thresh_phase')
thresh_phase = args.input
thresh_phase.inputs.volume = 0.5 thresh_phase.inputs.threshold
then another node, connected to the first
= Node(MoveResultFile(), 'move_phase')
move_phase = args.output
move_phase.inputs.output_name connect([(thresh_phase, move_phase, [('out_file', 'input_image')])]) wf.