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')

addtwo.inputs.x_input =4
addtwo.run()
addtwo.result.outputs


170904-05:44:35,413 workflow INFO:
	 Executing node add_node in dir: /tmp/tmpppthnq4p/add_node
Out[ ]:
val_output = 6

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.

Note that in the current version of Nipype you don't have to provide input_names as an argument of Function.

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)


170904-05:44:35,445 workflow INFO:
	 Executing node rndArray_node in dir: /tmp/tmphzdxcrk5/rndArray_node

random_array = [[ 0.14761855  0.73302764  0.62524247]
 [ 0.50316618  0.31536037  0.91529448]
 [ 0.8736496   0.80276958  0.603741  ]]

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)


170904-05:44:35,473 workflow INFO:
	 Executing node rndArray_node in dir: /tmp/tmp80g1x7z1/rndArray_node
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-3-96943c3aa6f2> in <module>()
     20 
     21 # Run node
---> 22 rndArray.run()
     23 
     24 # Print output

/opt/conda/envs/neuro3/lib/python3.6/site-packages/nipype/pipeline/engine/nodes.py in run(self, updatehash)
    370                     self.inputs.get_traitsfree())
    371             try:
--> 372                 self._run_interface()
    373             except:
    374                 os.remove(hashfile_unfinished)

/opt/conda/envs/neuro3/lib/python3.6/site-packages/nipype/pipeline/engine/nodes.py in _run_interface(self, execute, updatehash)
    480         old_cwd = os.getcwd()
    481         os.chdir(self.output_dir())
--> 482         self._result = self._run_command(execute)
    483         os.chdir(old_cwd)
    484 

/opt/conda/envs/neuro3/lib/python3.6/site-packages/nipype/pipeline/engine/nodes.py in _run_command(self, execute, copyfiles)
    611                 logger.info('Running: %s' % cmd)
    612             try:
--> 613                 result = self._interface.run()
    614             except Exception as msg:
    615                 self._save_results(result, cwd)

/opt/conda/envs/neuro3/lib/python3.6/site-packages/nipype/interfaces/base.py in run(self, **inputs)
   1082                         version=self.version)
   1083         try:
-> 1084             runtime = self._run_wrapper(runtime)
   1085             outputs = self.aggregate_outputs(runtime)
   1086             runtime.endTime = dt.isoformat(dt.utcnow())

/opt/conda/envs/neuro3/lib/python3.6/site-packages/nipype/interfaces/base.py in _run_wrapper(self, runtime)
   1030             runtime.environ['DISPLAY'] = ':%d' % vdisp_num
   1031 
-> 1032         runtime = self._run_interface(runtime)
   1033 
   1034         if self._redirect_x:

/opt/conda/envs/neuro3/lib/python3.6/site-packages/nipype/interfaces/utility/wrappers.py in _run_interface(self, runtime)
    190             setattr(runtime, 'runtime_threads', num_threads)
    191         else:
--> 192             out = function_handle(**args)
    193 
    194         if len(self._output_names) == 1:

<string> in get_random_array(array_shape)

NameError: 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.