How to contribute to RABIES
RABIES aims to provide an accessible tool responding to growing needs across the preclinical fMRI community. This effort should be community-driven, and community involvement is paramount in several respects:
adapting and maintaining accessibility for users across the broader community
reproducibility and transparency, as well as scientific scrutiny and rigour
defining and incorporating best practices across image processing, analysis and quality control
leveraging appropriate expertise for the integration of new tools
Suggestions for improvements can be shared through the GitHub issues system and discussion board.
Set up a development environment
Install RABIES locally rather than working through a container — testing and
debugging are much smoother that way. Install the dependencies listed in
dependencies.txt,
then install RABIES from a clone of the repository into a Python environment of
your choice:
git clone --recursive https://github.com/CoBrALab/RABIES.git
cd RABIES
python setup.py install
Note
Use --recursive. RABIES pulls in submodules, and a clone without them will
not run.
Working inside a container instead
You can run operations in a container to avoid installing dependencies by hand, at the cost of losing interactive debugging (Spyder and similar will not be available).
Open a persistent container instance with -d --entrypoint sh --name mycontainer,
binding the paths you need with -v:
docker run -it -v $PWD:/work_dir -v /path_to_local_RABIES_package:/RABIES:ro \
--rm --entrypoint sh -d --name mycontainer rabies:local_testing
Then execute commands inside it:
docker exec mycontainer micromamba run $COMMAND
To check for errors, $COMMAND can be error_check_rabies.py --complete.
To test your changes you must reinstall RABIES inside the container:
mkdir -p /tmp/RABIES
# copy the upgraded package into the container
# (/RABIES must be bound to your local package with -v)
rsync -avz /RABIES/* /tmp/RABIES/.
cd /tmp/RABIES
python setup.py install
Compile these into a .sh script and run it in place of $COMMAND above.
Submit a pull request
Fork the RABIES repository on GitHub.
Clone your fork, with
--recursiveto pull the submodules.Branch:
git checkout -b my_new_branch, with a name describing the change. You are ready to modify the code.Test and debug. Install your updated package with
python setup.py installin a proper development environment. Test the workflow with specific parameters by editingdebug_workflow.pyand running it in debug mode (see below).Important
Before committing, confirm that
error_check_rabies.py --completefinishes with no errors.Commit, push and open a pull request from your fork to the original repository.
Debug interactively with Spyder
Open
debug_workflow.pyin Spyder.Find the scripts in your local installation and add breakpoints. Running
import rabies; os.path.abspath(rabies.__file__)gives you the path to the installed package’s__init__.py, and from there you can locate the file of interest.Run
debug_workflow.pyin debug mode until it reaches the breakpoint.
Add a new module to the pipeline
RABIES workflows are structured with Nipype (see also the Nipype tutorial).
Write the function as a Nipype interface
from nipype.interfaces.base import (
traits, TraitedSpec, BaseInterfaceInputSpec,
File, BaseInterface
)
class NewInterfaceInputSpec(BaseInterfaceInputSpec):
# select an appropriate input type with traits.type (Dict, File, Int, ...)
input_str = traits.Str(exists=True, mandatory=True,
desc="An input string.")
class NewInterfaceOutputSpec(TraitedSpec):
out_file = File(
exists=True, desc="An output file.")
class NewInterface(BaseInterface):
"""
Describe your module.
"""
input_spec = NewInterfaceInputSpec
output_spec = NewInterfaceOutputSpec
def _run_interface(self, runtime):
input_str = self.inputs.input_str
'''
YOUR CODE
'''
setattr(self, 'out_file', out_file)
return runtime
def _list_outputs(self):
return {'out_file': getattr(self, 'out_file')}
Wrap it in a node
from .other_script import NewInterface # if the interface is in a different script
from nipype.pipeline import engine as pe
new_interface_node = pe.Node(NewInterface(),
name='new_interface')
A node can also be built from any plain Python function, without writing an interface:
from nipype.pipeline import engine as pe
from nipype.interfaces.utility import Function
new_function_node = pe.Node(Function(input_names=['input_1', 'input_2'],
output_names=['output_1', 'output_2'],
function=NewFunction),
name='new_function')
Connect the node into a workflow
Once the node carries out the operation you want, integrate it by linking its inputs and outputs to other nodes. Here is a complete minimal workflow, the one that performs slice timing correction:
1from nipype.pipeline import engine as pe
2from nipype.interfaces.utility import Function
3from nipype.interfaces import utility as niu
4
5
6def init_bold_stc_wf(name='bold_stc_wf'):
7
8 workflow = pe.Workflow(name=name)
9 # intermediate node storing the workflow inputs
10 inputnode = pe.Node(niu.IdentityInterface(
11 fields=['bold_file']), name='inputnode')
12 # intermediate node storing the workflow outputs
13 outputnode = pe.Node(niu.IdentityInterface(
14 fields=['stc_file']), name='outputnode')
15
16 slice_timing_correction_node = pe.Node(
17 Function(input_names=['in_file', 'tr', 'tpattern', 'stc_axis',
18 'interp_method', 'rabies_data_type'],
19 output_names=['out_file'],
20 function=slice_timing_correction),
21 name='slice_timing_correction', mem_gb=1.5 * opts.scale_min_memory)
22
23 # feed the inputnode into the STC node, and STC outputs into the outputnode
24 workflow.connect([
25 (inputnode, slice_timing_correction_node, [('bold_file', 'in_file')]),
26 (slice_timing_correction_node,
27 outputnode, [('out_file', 'stc_file')]),
28 ])
29 return workflow
Most contributions integrate a new interface into a pre-existing workflow rather than creating a new one, in which case the work is connecting the right nodes to your interface.
See also
Workflow reference documents the existing workflows and links to their source.