Function Node

Satra once called the Function module, the "do anything you want card". Which is a perfect description. Because it allows you to put any code you want into an empty node, which you than can put in your workflow exactly where it needs to be.

You might have already seen the Function module in the example section in the Node tutorial. Let's take a closer look at it again.


In [ ]:
# Import Node and Function module
from nipype import Node, Function

# Create a small example function
def add_two(x_input):
    return x_input + 2

# Create Node
addtwo = Node(Function(input_names=["x_input"],
                       output_names=["val_output"],
                       function=add_two),
              name='add_node')

Trap 1

There are only two traps that you should be aware when you're using the Function module. The first one is about naming the input variables. The variable name for the node input has to be the exactly the same name as the function input parameter, in this case this is x_input.

Otherwise you get the following error:

TypeError: add_two() got an unexpected keyword argument 'x_input'
Interface Function failed to run.

Trap 2

If you want to use another module inside a function, you have to import it again inside the function. Let's take a look at the following example:


In [ ]:
from nipype import Node, Function

# Create the Function object
def get_random_array(array_shape):

    # Import random function
    from numpy.random import random
   
    return random(array_shape)

# Create Function Node that executes get_random_array
rndArray = Node(Function(input_names=["array_shape"],
                         output_names=["random_array"],
                         function=get_random_array),
                name='rndArray_node')

# Specify the array_shape of the random array
rndArray.inputs.array_shape = (3, 3)

# Run node
rndArray.run()

# Print output
print rndArray.result.outputs


170301-21:55:47,598 workflow INFO:
	 Executing node rndArray_node in dir: /tmp/tmpv4BGTx/rndArray_node
170301-21:55:47,633 workflow INFO:
	 Runtime memory and threads stats unavailable

random_array = [[ 0.55392783  0.56238157  0.26244335]
 [ 0.25663815  0.20904142  0.5810782 ]
 [ 0.18068192  0.65697574  0.1218128 ]]

Now, let's see what happens if we move the import of random outside the scope of get_random_array:


In [ ]:
from nipype import Node, Function

# Import random function
from numpy.random import random


# Create the Function object
def get_random_array(array_shape):
  
    return random(array_shape)

# Create Function Node that executes get_random_array
rndArray = Node(Function(input_names=["array_shape"],
                         output_names=["random_array"],
                         function=get_random_array),
                name='rndArray_node')

# Specify the array_shape of the random array
rndArray.inputs.array_shape = (3, 3)

# Run node
rndArray.run()

# Print output
print rndArray.result.outputs


170301-21:55:47,697 workflow INFO:
	 Executing node rndArray_node in dir: /tmp/tmpFBMKdD/rndArray_node
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-3-738ed8b85d1d> in <module>()
     20 
     21 # Run node
---> 22 rndArray.run()
     23 
     24 # Print output

/opt/conda/envs/python2/lib/python2.7/site-packages/nipype/pipeline/engine/nodes.pyc in run(self, updatehash)
    392                     self.inputs.get_traitsfree())
    393             try:
--> 394                 self._run_interface()
    395             except:
    396                 os.remove(hashfile_unfinished)

/opt/conda/envs/python2/lib/python2.7/site-packages/nipype/pipeline/engine/nodes.pyc in _run_interface(self, execute, updatehash)
    502         old_cwd = os.getcwd()
    503         os.chdir(self.output_dir())
--> 504         self._result = self._run_command(execute)
    505         os.chdir(old_cwd)
    506 

/opt/conda/envs/python2/lib/python2.7/site-packages/nipype/pipeline/engine/nodes.pyc in _run_command(self, execute, copyfiles)
    628                 logger.info('Running: %s' % cmd)
    629             try:
--> 630                 result = self._interface.run()
    631             except Exception as msg:
    632                 self._result.runtime.stderr = msg

/opt/conda/envs/python2/lib/python2.7/site-packages/nipype/interfaces/base.pyc in run(self, **inputs)
   1041                         version=self.version)
   1042         try:
-> 1043             runtime = self._run_wrapper(runtime)
   1044             outputs = self.aggregate_outputs(runtime)
   1045             runtime.endTime = dt.isoformat(dt.utcnow())

/opt/conda/envs/python2/lib/python2.7/site-packages/nipype/interfaces/base.pyc in _run_wrapper(self, runtime)
    998             runtime.environ['DISPLAY'] = ':%d' % vdisp_num
    999 
-> 1000         runtime = self._run_interface(runtime)
   1001 
   1002         if self._redirect_x:

/opt/conda/envs/python2/lib/python2.7/site-packages/nipype/interfaces/utility.pyc in _run_interface(self, runtime)
    497             setattr(runtime, 'runtime_threads', num_threads)
    498         else:
--> 499             out = function_handle(**args)
    500 
    501         if len(self._output_names) == 1:

<string> in get_random_array(array_shape)

NameError: global name 'random' is not defined
Interface Function failed to run. 

As you can see, if we don't import random inside the scope of the function, we receive the following error:

NameError: global name 'random' is not defined
Interface Function failed to run.