The Function Interface¶
Most Nipype interfaces provide access to external programs, such as FSL
binaries or SPM routines. However, a special interface,
nipype.interfaces.utility.Function
,
allows you to wrap arbitrary Python code in the Interface framework and
seamlessly integrate it into your workflows.
A Simple Function Interface¶
The most important component of a working Function interface is a Python function. There are several ways to associate a function with a Function interface, but the most common way will involve functions you code yourself as part of your Nipype scripts. Consider the following function:
def add_two(val):
return val + 2
This simple function takes a value, adds 2 to it, and returns that new value.
Just as Nipype interfaces have inputs and outputs, Python functions have
inputs, in the form of parameters or arguments, and outputs, in the form
of their return values. When you define a Function interface object with
an existing function, as in the case of add_two()
above, you must pass the
constructor information about the function’s inputs, its outputs, and the
function itself. For example,
from nipype.interfaces.utility import Function
add_two_interface = Function(input_names=["val"],
output_names=["out_val"],
function=add_two)
Then you can set the inputs and run just as you would with any other interface:
add_two_interface.inputs.val = 2
res = add_two_interface.run()
print res.outputs.out_val
Which would print 4
.
Note that, if you are working interactively, the Function interface is
unable to use functions that are defined within your interpreter session.
(Specifically, it can’t use functions that live in the __main__
namespace).
Using External Packages¶
Chances are, you will want to write functions that do more complicated processing, particularly using the growing stack of Python packages geared towards neuroimaging, such as Nibabel, Nipy, or PyMVPA.
While this is completely possible (and, indeed, an intended use of the Function interface), it does come with one important constraint. The function code you write is executed in a standalone environment, which means that any external functions or classes you use have to be imported within the function itself:
def get_n_trs(in_file):
import nibabel
f = nibabel.load(in_file)
return f.shape[-1]
Without explicitly importing Nibabel in the body of the function, this would fail.
Alternatively, it is possible to provide a list of strings corresponding to the imports needed to execute a function as a parameter of the Function constructor. This allows for the use of external functions that do not import all external definitions inside the function body.
Hello World - Function interface in a workflow¶
Contributed by: Hänel Nikolaus Valentin
The following snippet of code demonstrates the use of the function interface in
the context of a workflow. Note the use of import os
within the function as
well as returning the absolute path from the Hello function. The import inside
is necessary because functions are coded as strings and do not have to be on the
PYTHONPATH. However any function called by this function has to be available on
the PYTHONPATH. The absolute path is necessary because all workflow nodes are
executed in their own directory and therefore there is no way of determining
that the input file came from a different directory:
import nipype.pipeline.engine as pe
from nipype.interfaces.utility import Function
def Hello():
import os
from nipype import logging
iflogger = logging.getLogger('interface')
message = "Hello "
file_name = 'hello.txt'
iflogger.info(message)
with open(file_name, 'w') as fp:
fp.write(message)
return os.path.abspath(file_name)
def World(in_file):
from nipype import logging
iflogger = logging.getLogger('interface')
message = "World!"
iflogger.info(message)
with open(in_file, 'a') as fp:
fp.write(message)
hello = pe.Node(name='hello',
interface=Function(input_names=[],
output_names=['out_file'],
function=Hello))
world = pe.Node(name='world',
interface=Function(input_names=['in_file'],
output_names=[],
function=World))
pipeline = pe.Workflow(name='nipype_demo')
pipeline.connect([(hello, world, [('out_file', 'in_file')])])
pipeline.run()
pipeline.write_graph(graph2use='flat')
Advanced Use¶
To use an existing function object (as we have been doing so far) with a Function
interface, it must be passed to the constructor. However, it is also possible
to dynamically set how a Function interface will process its inputs using the
special function_str
input.
This input takes not a function object, but actually a single string that can be parsed to define a function. In the equivalent case to our example above, the string would be
add_two_str = "def add_two(val):\n return val + 2\n"
Unlike when using a function object, this input can be set like any other,
meaning that you could write a function that outputs different function
strings depending on some run-time contingencies, and connect that output
the the function_str
input of a downstream Function interface.