QuNex extensions
Contents
QuNex extensions#
UNDER DEVELOPMENT
NOTE: The QuNex extensions framework is under development and not yet fully functional. This is the reason that this page is not accessible through any of the documentation menus. Before the official release, we do not promise that what is written below functions as described.
QuNex provides an integrated ecosystem for neuroimaging tasks. Some of its core features are a comprehensive logging system and the ability to run tasks via an HPC scheduler (like, e.g., SLURM). QuNex extensions make these features conveniently available to custom code.
Prerequisites
QuNex extensions are available with QuNex version 0.93.8 or later (TODO: CHECK). As usual, to run QuNex set the environment variables TOOLS
and QUNEXREPO
and then source the qunex_environment.sh
script:
export TOOLS=/software
export QUNEXREPO=qunex
source $TOOLS/$QUNEXREPO/env/qunex_environment.sh
The qunex_environment.sh
script searches for available extensions and acknowledges which ones it found.
Anatomy of a QuNex extension#
QuNex searches for extensions in three locations:
in
$QUNEXPATH/qx_extensions
where$QUNEXPATH
points to the main QuNex directoryin
$TOOLS/qx_extensions
where$TOOLS
can be an arbitrary directory that by default hosts QuNex installationin directories that are collected in the environment variable
QUNEXEXTENSIONFOLDERS
Each extension is a directory that has at least one of the following subdirectories:
bin
matlab
python
Bash extensions#
Bash extensions are shell scripts in the bin
subfolder. These files need to be made executable and are run directly from the command line or can be referenced by other code (e.g. from Python). Note that they cannot be run via a qunex call unless their use is integrated into a python or Matlab command.
Matlab extensions#
Matlab extensions are Matlab (*.m
) files in the matlab
subfolder that contain a function of the same name. For example, the file matlab/hello_matlab.m
must contain the Matlab function hello_matlab
. Functions and scripts hosted in this folder are automatically available from within matlab environment. If there are additional subfolders that should be available within the matlab environment, these folders have to be listed in a matlabpaths
file, each folder to be added to the environment on a separate line in the file. The paths provided should be relative to the extension's matlab
folder.
To be able to call the functions added by the extension also from the command line using the qunex command, they must additionally be listed in a Python file together with their arguments. To do so, create a Python file in the python
subdirectory, e.g. python/matlab_extensions.py
. Then list the Matlab function and its arguments in a Python dictionary called functions
, like so:
functions = {
'hello_matlab': [('argument1', 'string'), ('argument2', 'string)]
}
To execute this function via QuNex run
qunex hello_matlab
Function arguments can be passed in the usual QuNex-style, e.g.
qunex hello_matlab --argument1="Hi" --argument2="Everyone"
Python Extensions#
Python extensions are searched for in the python
subfolder. They can be Python (*.py
) files or Python modules (i.e. subdirectories with an __init__.py
file). To allow for library code that is not intended to be called directly those files and modules that should be callable from QuNex must be listed in a file called qx_modules
contained in the python
subfolder. For example, say there are files hello.py
and world.py
, as well as directories tools
and lib
. Then the qx_modules
file
hello
tools
would expose hello.py
and the module tools
to qunex, but neither the file world.py
nor the module lib
.
QuNex will import the Python files and modules listed in qx_modules
. For modules this means that the code in the module directory's __init__.py
will be executed.
Simple python extensions#
To call an arbitrary Python function via QuNex, QuNex must be made aware of the function and its arguments. For instance, the function
def print_hello_fun(name=None):
if name is None:
print("What's your name?"
else:
print(f"Hello {fname}")
can be made QuNex-compatible by adding the following code to the corresponding file:
commands = {'print_hello' : { 'com': print_hello_fun, 'args': ('name',)}}
Here, print_hello
is the name by which the function under com
, i.e. print_hello_fun
in this case, will be known to QuNex, i.e.
qunex print_hello
will execute print_hello_fun
. The args
entry in the dictionary must be a tuple giving the names of the function's arguments. Thus, we could run
qunex print_hello --name="Python"
Simple python extensions with decorators#
To call an arbitrary Python function via QuNex, QuNex must be made aware of the function and its arguments. Say, for instance, you have the function
def print_hello_fun(name=None):
if name is None:
print("What's your name?"
else:
print(f"Hello {fname}")
To register the function for use with QuNex requires only a few small additions:
from general.qx_ext import qx
@qx()
def print_hello(name=None):
if name is None:
print("What's your name?"
else:
print(f"Hello {fname}")
Here, @qx()
is a decorator. Adding the line @qx()
will leave the function print_hello
unchanged, its purpose is simply to expose the function to QuNex such that it
can be run as:
qunex print_hello --name="Python"
qx
takes one optional argument that can be used to register a different name for the function. For example, define
from general.qx_ext import qx
@qx("say_hi")
def print_hello(name=None):
if name is None:
print("What's your name?"
else:
print(f"Hello {fname}")
Then,
qunex print_hello --name="Python"
will not work, but
qunex say_hi --name="Python"
will.
Python-based processing extensions#
(ToDo: either add explanation of the differences between simple and processing commands, or point to the relevant section in SDK once prepared.) To process sessions QuNex makes use of Python functions with a particular call signature. These functions receive information about the session to be processed when they are called and return status and logging information that qunex will then take into account. To add custom processing functions to QuNex via an extension these functions must have this particular call signature. Here's an example of such a function:
def greet_all_fun(sinfo, options, overwrite=False, thread=0):
process
return (r, (sinfo['id'], rstatus, report))
TODO: Document arguments sinfo
, options
, overwrite
, thread
as well as return values (r, (sinfo['id'], rstatus, report))
In addition, just like for simple Python extension function discussed above, some information about the function must be exposed in a specific way to make the compatible with qunex:
First, the function itself must be registered with QuNex. To do so, define the list calist
and add an entry for the function:
calist = [
['ga', 'greet_all', greet_all_fun, "Greet everyone"]
]
Each such entry is itself a list with four items:
a short name for the command
a longer name for the command
the Python function itself
a description of the function
If the function processes longitudinal data add the command to lalist
instead of calist
. If the command processes multisession data use malist
instead.
Second, as noted above, the argument options
is a dictionary containing command line arguments. These command line arguments must be defined in a Python list called arglist
. For example, to allow a command line argument name
add the following code:
arglist = [
['name', '', str, 'The name to process']
]
Here, arglist
contains one entry per command line argument and each such entry is a list with four entries:
the command line parameter itself
the default value to be used if the parameter is not specified
a Python function converting the given value of the command line parameter from a
string
to a different data type. In the simplest cases this can be the Python functionstr
which effectively keeps the value astring
. Other common possibilities includeint
,float
ortorf
that converts 'true', 'TRUE', 'True', 'yes', ... to a booleanTrue
orFalse
value.a description of the command line parameter
Python-based processing extensions#
To process sessions QuNex makes use of Python functions with a particular call signature. These functions receive information about the session to be processed when they are called and return status and logging information that qunex will then take into account. To add custom processing functions to QuNex via an extension these functions must have this particular call signature. Here's an example of such a function:
def greet_all(sinfo, options, overwrite=False, thread=0):
process
return (r, (sinfo['id'], rstatus, report))
TODO: Document arguments sinfo
, options
, overwrite
, thread
as well as return values (r, (sinfo['id'], rstatus, report))
In addition, just like for simple Python extensions discussed above, processing extensions need to be registered for use with QuNex. To do so, simply decorate the function with qx_process
, i.e.:
from general.extensions import qx_process
@qx_process()
def greet_all(sinfo, options, overwrite=False, thread=0):
process
return (r, (sinfo['id'], rstatus, report))
If the function requires additional arguments that should be given on the command line, add these as keyword-arguments to the function definition:
@qx_process()
def greet_all(sinfo, options, overwrite=False, thread=0, name="All"):
print(f"Hello {name}")
return (r, (sinfo['id'], rstatus, report))
If the command processes longitudinal or multi-session data add "longitudinal" or "multisession" as an argument to qx_process
, i.e. for example:
@qx_process("longitudinal")
def greet_all(sinfo, options, overwrite=False, thread=0, name="All"):
print(f"Hello {name}")
return (r, (sinfo['id'], rstatus, report))
Accessing the core QuNex Python library#
TODO: When I tested this I didn't need to append qx_utilities path, I could directly import functions from general package. CHECK
To use Python functions shipped with QuNex within a Python QuNex extension QUNEXPATH
has to be added to the python path. This can be done by adding the following to the top of your Python file:
import os
import os.path
import sys
if "QUNEXPATH" in os.environ:
sys.path.append(os.path.join(os.environ['QUNEXPATH'], 'python', 'qx_utilities'))
# now QuNex Python modules are available, e.g.:
import general.exceptions as ge
from processing.core import *
else:
exit(1)